diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c52ff9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b266ce6 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +# 灵犀 · 环境变量示例(入库,禁止写真实密钥) +# 使用:cp .env.example .env.local(工程规范 09 ENV-09) +# 注意:本地 compose 的宿主端口为 15432/16379/19000/19001,避免与本机既有 PG/Redis 冲突。 + +# ---------- 运行环境 ---------- +NODE_ENV=development +LOG_LEVEL=debug + +# ---------- 数据库(PostgreSQL 18) ---------- +DATABASE_URL=postgresql://lingxi:lingxi_dev_password@127.0.0.1:15432/lingxi_dev?schema=public + +# ---------- Redis(7.4) ---------- +REDIS_URL=redis://127.0.0.1:16379 + +# ---------- 对象存储(MinIO,S3 协议) ---------- +S3_ENDPOINT=http://127.0.0.1:19000 +S3_REGION=us-east-1 +S3_BUCKET_ASSETS=assets +S3_BUCKET_RENDERS=renders +S3_BUCKET_TEMP=temp +S3_ACCESS_KEY=lingxi +S3_SECRET_KEY=lingxi_dev_password + +# ---------- 认证(本地 dev 值,生产由密钥管理注入) ---------- +JWT_SECRET=dev-only-jwt-secret-change-me-32chars +JWT_REFRESH_SECRET=dev-only-jwt-refresh-secret-change-me-32chars + +# ---------- API ---------- +PORT=3000 +HOST=0.0.0.0 +WEB_ORIGIN=http://127.0.0.1:5173 +MAX_UPLOAD_MB=500 + +# ---------- Worker ---------- +QUEUE=default +WORKER_CONCURRENCY=2 + +# ---------- 前端(只能暴露 VITE_PUBLIC_* 前缀,ENV-07) ---------- +VITE_PUBLIC_API_BASE_URL=http://127.0.0.1:3000/api/v1 +# A0 登录壳没有接后端鉴权,置 true 时登录页写入本地演示会话以便查看布局;A1 接入真实登录后删除。 +VITE_PUBLIC_DEMO_SESSION=true + +# ---------- 可观测 ---------- +OTEL_EXPORTER_OTLP_ENDPOINT= + +# ---------- 功能开关 ---------- +FEATURE_VIDEO=false diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..87a67ce --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,116 @@ +name: ci + +on: + push: + branches: [main, 'feat/**'] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: 质量门禁 + runs-on: ubuntu-latest + timeout-minutes: 30 + + services: + postgres: + image: postgres:18-alpine + env: + POSTGRES_USER: lingxi + POSTGRES_PASSWORD: lingxi_dev_password + POSTGRES_DB: lingxi_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U lingxi -d lingxi_test" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + redis: + image: redis:7.4-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + + env: + NODE_ENV: test + LOG_LEVEL: error + DATABASE_URL: postgresql://lingxi:lingxi_dev_password@127.0.0.1:5432/lingxi_test?schema=public + REDIS_URL: redis://127.0.0.1:6379 + JWT_SECRET: ci-only-access-secret + JWT_REFRESH_SECRET: ci-only-refresh-secret + WEB_ORIGIN: http://127.0.0.1:4173 + VITE_PUBLIC_API_BASE_URL: http://127.0.0.1:3000/api/v1 + VITE_PUBLIC_DEMO_SESSION: 'true' + PLAYWRIGHT_CHANNEL: '' + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 安装 pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.34.9 + + - name: 安装 Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.15.0 + cache: pnpm + + - name: 安装依赖 + run: pnpm install --frozen-lockfile + + - name: 格式检查 + run: pnpm format:check + + - name: Lint + run: pnpm lint + + - name: 类型检查 + run: pnpm typecheck + + - name: 单元测试与覆盖率 + run: pnpm test + + - name: 构建 + run: pnpm build + + - name: 数据库迁移 + run: pnpm exec prisma migrate deploy + + - name: 集成测试 + run: pnpm test:int + + - name: 导出 OpenAPI 快照并检查破坏性变更 + run: | + pnpm openapi:export + pnpm openapi:diff + + - name: 安装 Playwright 浏览器 + run: pnpm exec playwright install --with-deps chromium + + - name: 端到端冒烟 + run: pnpm test:e2e + + - name: 归档测试产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts + if-no-files-found: ignore + path: | + coverage + apps/*/coverage + packages/*/coverage + test-results + playwright-report + openapi/lingxi.openapi.json diff --git a/.gitignore b/.gitignore index 236b6ad..2d96cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -62,7 +62,14 @@ temp/ ~$* *.~lock.* +# ========== 测试产物 ========== +playwright-report/ +test-results/ + # ========== 项目自定义 ========== # 参考项目:仅本地参照,不纳入版本控制、不推送到远程仓库 参考项目/ 项目参考/ +# 沙箱环境下的本地 pnpm 10 启动器与设计探索产物 +.tools/ +design/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..2e6b87e --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +pnpm exec commitlint --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..5ee7abd --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm exec lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..0db91f0 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +pnpm typecheck diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs new file mode 100644 index 0000000..05d06b0 --- /dev/null +++ b/.lintstagedrc.mjs @@ -0,0 +1,18 @@ +const quote = (file) => JSON.stringify(file); + +// 生成的声明文件(如 openapi.d.ts)被 ESLint 忽略,跳过 lint 只做格式化, +// 避免 lint-staged 因 "File ignored" 警告而阻断提交。 +const lintScripts = (filenames) => { + const lintable = filenames.filter((file) => !file.endsWith('.d.ts')); + const commands = []; + if (lintable.length > 0) { + commands.push(`eslint --fix --max-warnings=0 ${lintable.map(quote).join(' ')}`); + } + commands.push(`prettier --write ${filenames.map(quote).join(' ')}`); + return commands; +}; + +export default { + '*.{ts,tsx,js,mjs,cjs}': lintScripts, + '*.{json,md,css,scss,yml,yaml}': ['prettier --write'], +}; diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..fbf7f9a --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +auto-install-peers=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..5bf4400 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.15.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..abdf00e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +node_modules/ +dist/ +coverage/ +.turbo/ +.tools/ +design/ +docs/ +项目参考/ +openapi/ +prisma/migrations/ +pnpm-lock.yaml +*.tsbuildinfo + +# 项目总纲保持原排版,避免格式化产生无关 diff +AGENTS.md diff --git a/README.md b/README.md index 2775ddb..e84ee3c 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,16 @@ ## 文档索引 -| 文档 | 路径 | 版本 | 作用 | -| --- | --- | --- | --- | -| **项目总纲** | [AGENTS.md](AGENTS.md) | V1.0 | 硬约束、技术栈、仓库结构、命令契约、流程 | -| **PRD** | [灵犀-信息流广告素材一站式生产平台PRD.md](docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md) | **V1.6(最新)** | 需求、业务规则(R-01~R-30)、验收标准 | -| **技术架构** | [灵犀_信息流素材生产平台_技术架构_V0.1.md](docs/技术架构/灵犀_信息流素材生产平台_技术架构_V0.1.md) | **V0.4(内部版本)** | 技术选型、分层模块、数据模型、接口、ADR | -| **开发计划** | [灵犀_信息流素材生产平台_开发计划_V0.1.md](docs/开发计划/灵犀_信息流素材生产平台_开发计划_V0.1.md) | **V0.1(初稿)** | 全项目 M0–M4:里程碑、批次、WBS、七管理模块专项、门禁、风险、验收 | -| 立项建议 | [灵犀_信息流素材生产平台_立项建议书_V0.1.md](docs/立项建议/灵犀_信息流素材生产平台_立项建议书_V0.1.md) | V0.1 | 背景、目标、投入产出论证(不约束实现) | -| **工程规范** | [docs/工程规范/](docs/工程规范/README.md) | V1.0 | Git、代码风格、格式化、API、数据库、测试、Review、日志、密钥、资产命名 | -| 历史 PRD | `docs/PRD/灵犀_信息流素材生产平台_PRD_V0.1~V0.3.md` | 归档 | 仅作沿革参考,**不具约束力** | +| 文档 | 路径 | 版本 | 作用 | +| ------------ | ------------------------------------------------------------------------------------------------------ | -------------------- | ---------------------------------------------------------------------- | +| **项目总纲** | [AGENTS.md](AGENTS.md) | V1.0 | 硬约束、技术栈、仓库结构、命令契约、流程 | +| **PRD** | [灵犀-信息流广告素材一站式生产平台PRD.md](docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md) | **V1.6(最新)** | 需求、业务规则(R-01~R-30)、验收标准 | +| **技术架构** | [灵犀_信息流素材生产平台_技术架构_V0.1.md](docs/技术架构/灵犀_信息流素材生产平台_技术架构_V0.1.md) | **V0.4(内部版本)** | 技术选型、分层模块、数据模型、接口、ADR | +| **开发计划** | [灵犀_信息流素材生产平台_开发计划_V0.1.md](docs/开发计划/灵犀_信息流素材生产平台_开发计划_V0.1.md) | **V0.1(初稿)** | 全项目 M0–M4:里程碑、批次、WBS、七管理模块专项、门禁、风险、验收 | +| 立项建议 | [灵犀_信息流素材生产平台_立项建议书_V0.1.md](docs/立项建议/灵犀_信息流素材生产平台_立项建议书_V0.1.md) | V0.1 | 背景、目标、投入产出论证(不约束实现) | +| **工程规范** | [docs/工程规范/](docs/工程规范/README.md) | V1.0 | Git、代码风格、格式化、API、数据库、测试、Review、日志、密钥、资产命名 | +| **A0 报告** | [灵犀_A0工程骨架与契约基线_实施报告_V0.1.md](docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md) | V0.1 | A0 完成范围、门禁实测、环境适配与待确认事项 | +| 历史 PRD | `docs/PRD/灵犀_信息流素材生产平台_PRD_V0.1~V0.3.md` | 归档 | 仅作沿革参考,**不具约束力** | > 阅读顺序:`AGENTS.md` → PRD §1/§6 → 技术架构 §0.2/§3 → 按任务查 `docs/工程规范/`。 @@ -26,24 +27,24 @@ ## 核心链路(五步) -| 步骤 | 动作 | 产出 | -| --- | --- | --- | -| 1 | 建行业 / 客户信息,选行业包并套客户资产包 | 归属根节点就绪 | -| 2 | 要素卡 + 卖点库 → 脚本生成 / 校核 / 审核入库 | 已入库脚本(带编号) | -| 3 | 图片批次(模板 × 商品图 × 规格)/ 视频逐镜生成 | 候选素材 | -| 4 | 按组并排快筛,勾选保留项 | 待导出素材 | -| 5 | 导出(固定目录 + 命名规则)+ 自动归档 | 可投素材 + 交付清单 | +| 步骤 | 动作 | 产出 | +| ---- | ---------------------------------------------- | -------------------- | +| 1 | 建行业 / 客户信息,选行业包并套客户资产包 | 归属根节点就绪 | +| 2 | 要素卡 + 卖点库 → 脚本生成 / 校核 / 审核入库 | 已入库脚本(带编号) | +| 3 | 图片批次(模板 × 商品图 × 规格)/ 视频逐镜生成 | 候选素材 | +| 4 | 按组并排快筛,勾选保留项 | 待导出素材 | +| 5 | 导出(固定目录 + 命名规则)+ 自动归档 | 可投素材 + 交付清单 | --- ## 功能模块(PRD V1.6) -| 模块 | 定位 | -| --- | --- | -| **脚本** | 要素卡 / 卖点库 / 模板 / 生成 / 质量校核 / 审核入库 / 版本 | -| **素材** | 图片线(PSD 套版、抠图、规格矩阵)、视频线(逐镜生成、变体)、快筛与预览 | -| **资产** | 15 类资产的库、版本、引用、复审与复用度量 | -| **平台管理** | 组织账号、客户、角色权限、AI 配置、数据字典、审计、行业包与表单设计器 | +| 模块 | 定位 | +| ------------ | ------------------------------------------------------------------------ | +| **脚本** | 要素卡 / 卖点库 / 模板 / 生成 / 质量校核 / 审核入库 / 版本 | +| **素材** | 图片线(PSD 套版、抠图、规格矩阵)、视频线(逐镜生成、变体)、快筛与预览 | +| **资产** | 15 类资产的库、版本、引用、复审与复用度量 | +| **平台管理** | 组织账号、客户、角色权限、AI 配置、数据字典、审计、行业包与表单设计器 | > 早期文档中的「四大模块(脚本生成 / 素材生产 / 编辑 / 管理)」已于 PRD V1.6 重构为「脚本 / 素材 / 资产 / 平台管理」三主线 + 一底座。 @@ -61,20 +62,37 @@ infra/ compose、Nginx、监控、备份 docs/ PRD / 技术架构 / 开发计划 / 立项建议 / 工程规范 ``` -当前仓库处于**规范与设计阶段**,尚未建代码;建仓必须遵循 [AGENTS.md §5](AGENTS.md) 的结构与依赖方向。 +当前已完成 **A0 工程骨架与契约基线**(monorepo、API/Worker/Web 骨架、Prisma 平台表、shared 契约包、UI 包、布局与登录壳、CI 与冒烟测试);业务模块从 A1 起按开发计划推进。目录与依赖方向见 [AGENTS.md §5](AGENTS.md)。 --- -## 本地开发(建仓后适用) +## 本地开发 + +前置:Node.js `24.15.0`(`.nvmrc`)、pnpm `10.x`、Docker。 ```bash cp .env.example .env.local -pnpm compose:up # PostgreSQL 18 / Redis 7.4 / MinIO -pnpm db:migrate -pnpm dev +cp .env.local .env # Prisma CLI 只读取 .env(两者均已 gitignore) +pnpm compose:up # PostgreSQL 18 / Redis 7.4 / S3 兼容存储 +pnpm db:migrate # 首次建表;后续由 Prisma 迁移管理 +pnpm db:seed # 幂等写入 system_configs 基线 +pnpm dev # 并行启动 api(3000) / web(5173) / worker ``` -命令与门禁见 [AGENTS.md §6](AGENTS.md) 与 [06-测试与CI规范](docs/工程规范/06-测试与CI规范.md)。 +- **端口**:为避免与本机已有容器冲突,compose 宿主端口为 PostgreSQL `15432`、Redis `16379`、S3 `19000/19001`,容器内仍是 `5432/6379/9000/9001`;`.env.example` 已按宿主端口给出连接串。 +- **对象存储**:当前 Docker 环境拉取不到 `minio/minio` 镜像(registry denied、二进制 410),A0 的 compose 使用 `adobe/s3mock` 并提供 `assets / renders / temp / archive` 四个初始桶;CI 或生产接入真实 MinIO 时只需替换 `infra/docker-compose.dev.yaml` 的 `s3` 服务。 +- **API 文档**:启动 api 后访问 `http://127.0.0.1:3000/api/docs`,OpenAPI JSON 在 `openapi/lingxi.openapi.json`。 +- **演示登录**:A0 尚未接鉴权(A1 实现),`VITE_PUBLIC_DEMO_SESSION=true` 时登录页使用本地演示会话进入工作台;A1 接入真实登录后删除。 + +常用校验: + +```bash +pnpm lint && pnpm typecheck && pnpm test # 本地门禁 +pnpm build && pnpm test:e2e # 构建与端到端冒烟 +pnpm openapi:export && pnpm openapi:diff # 契约快照与破坏性变更检查 +``` + +命令与门禁见 [AGENTS.md §6](AGENTS.md) 与 [06-测试与CI规范](docs/工程规范/06-测试与CI规范.md)。当前 UI 采用「顶部模块菜单 + 左侧功能菜单」布局,视觉方向为 **A · 云白蓝(传统企业后台 + 现代浅色质感)**。 --- diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..7771712 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,42 @@ +{ + "name": "@lingxi/api", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "tsx watch src/main.ts", + "build": "tsc -p tsconfig.build.json", + "start": "node dist/main.js", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run --coverage", + "test:int": "vitest run --config vitest.int.config.ts" + }, + "dependencies": { + "@fastify/cors": "11.3.1", + "@fastify/helmet": "13.2.0", + "@fastify/static": "10.1.8", + "@lingxi/shared": "workspace:*", + "@nestjs/common": "11.2.8", + "@nestjs/core": "11.2.8", + "@nestjs/platform-fastify": "11.2.8", + "@nestjs/swagger": "11.4.7", + "@prisma/client": "6.19.3", + "dotenv": "18.0.9", + "fastify": "5.12.7", + "ioredis": "5.11.1", + "nestjs-pino": "5.3.1", + "pino": "10.4.0", + "pino-http": "11.0.0", + "reflect-metadata": "0.2.2", + "rxjs": "7.8.2", + "zod": "3.25.76" + }, + "devDependencies": { + "@nestjs/testing": "11.2.8", + "@types/node": "24.19.2", + "@vitest/coverage-v8": "3.2.7", + "pino-pretty": "13.2.0", + "tsx": "4.23.15", + "typescript": "5.9.3", + "vitest": "3.2.7" + } +} diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts new file mode 100644 index 0000000..e1d07c2 --- /dev/null +++ b/apps/api/src/app.module.ts @@ -0,0 +1,51 @@ +import { REQUEST_ID_HEADER, ulid } from '@lingxi/shared'; +import { Module } from '@nestjs/common'; +import { LoggerModule } from 'nestjs-pino'; + +import { APP_CONFIG, AppConfigModule } from './config/config.module'; +import type { AppConfig } from './config/env'; +import { PrismaModule } from './infra/prisma/prisma.module'; +import { RedisModule } from './infra/redis/redis.module'; +import { PlatformModule } from './modules/platform/platform.module'; + +@Module({ + imports: [ + AppConfigModule, + LoggerModule.forRootAsync({ + imports: [AppConfigModule], + inject: [APP_CONFIG], + useFactory: (config: AppConfig) => ({ + pinoHttp: { + level: config.logLevel, + genReqId: (request, response) => { + const header = request.headers[REQUEST_ID_HEADER]; + const existing = Array.isArray(header) ? header[0] : header; + const traceId = typeof existing === 'string' && existing.length > 0 ? existing : ulid(); + response.setHeader(REQUEST_ID_HEADER, traceId); + return traceId; + }, + customProps: (request) => ({ trace_id: request.id }), + redact: { + paths: ['req.headers.authorization', 'req.headers.cookie', 'res.headers["set-cookie"]'], + censor: '[redacted]', + }, + autoLogging: { + ignore: (request) => request.url === '/health', + }, + ...(config.nodeEnv === 'development' && process.env.VITEST === undefined + ? { + transport: { + target: 'pino-pretty', + options: { singleLine: true, translateTime: 'SYS:HH:MM:ss.l' }, + }, + } + : {}), + }, + }), + }), + PrismaModule, + RedisModule, + PlatformModule, + ], +}) +export class AppModule {} diff --git a/apps/api/src/bootstrap.ts b/apps/api/src/bootstrap.ts new file mode 100644 index 0000000..6e08e12 --- /dev/null +++ b/apps/api/src/bootstrap.ts @@ -0,0 +1,44 @@ +import helmet from '@fastify/helmet'; +import { API_PREFIX } from '@lingxi/shared'; +import { NestFactory } from '@nestjs/core'; +import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; +import { Logger } from 'nestjs-pino'; + +import { AppModule } from './app.module'; +import { AllExceptionsFilter } from './common/filters/all-exceptions.filter'; +import { APP_CONFIG } from './config/config.module'; +import type { AppConfig } from './config/env'; +import { buildOpenApiDocument, setupOpenApi } from './openapi/document'; + +export interface CreatedApiApp { + app: NestFastifyApplication; + config: AppConfig; +} + +/** 创建并配置 API 应用(测试与 OpenAPI 导出复用,不负责监听端口) */ +export async function createApp(): Promise { + const app = await NestFactory.create( + AppModule, + new FastifyAdapter({ trustProxy: true, bodyLimit: 1024 * 1024 }), + { bufferLogs: true }, + ); + + const config = app.get(APP_CONFIG); + app.useLogger(app.get(Logger)); + + app.setGlobalPrefix(API_PREFIX, { exclude: ['health'] }); + app.useGlobalFilters(new AllExceptionsFilter()); + app.enableCors({ + origin: config.webOrigins, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + }); + + // 安全响应头;Swagger UI 需要内联脚本,CSP 在关闭文档页后再开启 + await app.register(helmet, { contentSecurityPolicy: false }); + app.enableShutdownHooks(); + + return { app, config }; +} + +export { buildOpenApiDocument, setupOpenApi }; diff --git a/apps/api/src/common/errors/app-exception.ts b/apps/api/src/common/errors/app-exception.ts new file mode 100644 index 0000000..da96d41 --- /dev/null +++ b/apps/api/src/common/errors/app-exception.ts @@ -0,0 +1,25 @@ +import { ERROR_MESSAGES, type ErrorCode, httpStatusOf } from '@lingxi/shared'; + +export interface AppExceptionOptions { + code: ErrorCode; + message?: string; + details?: unknown; +} + +/** + * 业务异常:由统一异常过滤器序列化为 { code, message, details, trace_id }。 + * 业务代码禁止直接 throw new Error 表达可预期错误。 + */ +export class AppException extends Error { + readonly code: ErrorCode; + readonly status: number; + readonly details?: unknown; + + constructor(options: AppExceptionOptions) { + super(options.message ?? ERROR_MESSAGES[options.code]); + this.name = 'AppException'; + this.code = options.code; + this.status = httpStatusOf(options.code); + this.details = options.details; + } +} diff --git a/apps/api/src/common/filters/all-exceptions.filter.spec.ts b/apps/api/src/common/filters/all-exceptions.filter.spec.ts new file mode 100644 index 0000000..ace9200 --- /dev/null +++ b/apps/api/src/common/filters/all-exceptions.filter.spec.ts @@ -0,0 +1,46 @@ +import { ErrorCode } from '@lingxi/shared'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { createTestApp, type TestApp } from '../../testing/test-app'; + +describe('统一异常过滤器(冒烟)', () => { + let testApp: TestApp; + + beforeAll(async () => { + testApp = await createTestApp({ includeErrorRoutes: true }); + }); + + afterAll(async () => { + await testApp.app.close(); + }); + + it('AppException 输出业务错误码与 trace_id', async () => { + const response = await testApp.app.inject({ method: 'GET', url: '/api/v1/__test/app-error' }); + expect(response.statusCode).toBe(409); + const body = response.json<{ code: number; message: string; trace_id: string }>(); + expect(body.code).toBe(ErrorCode.STATE_NOT_ALLOWED); + expect(body.message).toBe('当前状态不允许该操作'); + expect(body.trace_id.length).toBeGreaterThan(0); + }); + + it('HttpException 404 映射为 40401', async () => { + const response = await testApp.app.inject({ method: 'GET', url: '/api/v1/__test/http-404' }); + expect(response.statusCode).toBe(404); + expect(response.json<{ code: number }>().code).toBe(ErrorCode.NOT_FOUND); + }); + + it('未知异常收敛为 50001,不泄露内部信息', async () => { + const response = await testApp.app.inject({ method: 'GET', url: '/api/v1/__test/boom' }); + expect(response.statusCode).toBe(500); + const body = response.json<{ code: number; message: string; trace_id: string }>(); + expect(body.code).toBe(ErrorCode.INTERNAL_ERROR); + expect(body.message).toBe('服务内部错误'); + expect(body.trace_id.length).toBeGreaterThan(0); + }); + + it('未注册路由返回统一 404 错误体', async () => { + const response = await testApp.app.inject({ method: 'GET', url: '/api/v1/not-exists' }); + expect(response.statusCode).toBe(404); + expect(response.json<{ code: number }>().code).toBe(ErrorCode.NOT_FOUND); + }); +}); diff --git a/apps/api/src/common/filters/all-exceptions.filter.ts b/apps/api/src/common/filters/all-exceptions.filter.ts new file mode 100644 index 0000000..7af7f1d --- /dev/null +++ b/apps/api/src/common/filters/all-exceptions.filter.ts @@ -0,0 +1,121 @@ +import { type ApiErrorBody, ErrorCode, httpStatusOf, isErrorCode } from '@lingxi/shared'; +import { type ArgumentsHost, Catch, type ExceptionFilter, HttpException } from '@nestjs/common'; + +import { AppException } from '../errors/app-exception'; + +interface RequestWithTrace { + id?: string; + url?: string; + method?: string; + log?: { + error: (payload: unknown, message?: string) => void; + }; +} + +interface ReplyLike { + status: (code: number) => { send: (body: unknown) => void }; +} + +function codeFromHttpStatus(status: number): ErrorCode { + switch (status) { + case 400: + return ErrorCode.PARAM_INVALID; + case 401: + return ErrorCode.UNAUTHENTICATED; + case 403: + return ErrorCode.PERMISSION_DENIED; + case 404: + return ErrorCode.NOT_FOUND; + case 409: + return ErrorCode.STATE_NOT_ALLOWED; + case 422: + return ErrorCode.BUSINESS_RULE_VIOLATION; + case 429: + return ErrorCode.RATE_LIMITED; + case 502: + case 503: + return ErrorCode.DEPENDENCY_UNAVAILABLE; + case 504: + return ErrorCode.TIMEOUT; + default: + return status >= 400 && status < 500 ? ErrorCode.PARAM_INVALID : ErrorCode.INTERNAL_ERROR; + } +} + +function messageFromHttpException(exception: HttpException): string { + const response = exception.getResponse(); + if (typeof response === 'string') { + return response; + } + if ('message' in response) { + const message = response.message; + if (typeof message === 'string') { + return message; + } + if (Array.isArray(message) && message.length > 0 && typeof message[0] === 'string') { + return message.join('; '); + } + } + return exception.message; +} + +/** + * 全局异常过滤器:所有错误统一输出 { code, message, details, trace_id }(工程规范 04)。 + * trace_id 与 Pino 的 request id 同源,响应用户报障时可反查日志。 + */ +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost): void { + const context = host.switchToHttp(); + const request = context.getRequest(); + const reply = context.getResponse(); + const traceId = request.id ?? ''; + + let status = 500; + let body: ApiErrorBody; + + if (exception instanceof AppException) { + status = exception.status; + body = { + code: exception.code, + message: exception.message, + ...(exception.details === undefined ? {} : { details: exception.details }), + trace_id: traceId, + }; + } else if (exception instanceof HttpException) { + status = exception.getStatus(); + const code = codeFromHttpStatus(status); + body = { + code, + message: status >= 500 ? '服务内部错误' : messageFromHttpException(exception), + trace_id: traceId, + }; + } else { + body = { + code: ErrorCode.INTERNAL_ERROR, + message: '服务内部错误', + trace_id: traceId, + }; + } + + if (status >= 500) { + request.log?.error( + { + err: exception, + trace_id: traceId, + method: request.method, + url: request.url, + code: body.code, + }, + 'request failed', + ); + } + + reply.status(status).send(body); + } +} + +/** 供测试或未来的 OpenAPI 生成复用:确认错误码与 HTTP 状态一致 */ +export function httpStatusForCode(code: number): number { + return isErrorCode(code) ? httpStatusOf(code) : 500; +} diff --git a/apps/api/src/common/http/error-response.dto.ts b/apps/api/src/common/http/error-response.dto.ts new file mode 100644 index 0000000..a4e518c --- /dev/null +++ b/apps/api/src/common/http/error-response.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; + +/** OpenAPI 错误响应模型(工程规范 04 统一错误体) */ +export class ErrorResponseDto { + @ApiProperty({ type: Number, description: '错误码(见 ErrorCode 常量)', example: 42202 }) + code!: number; + + @ApiProperty({ type: String, description: '可直接展示给用户的提示', example: '内容触发合规拦截' }) + message!: string; + + @ApiProperty({ + type: Object, + description: '补充信息(字段错误、命中规则等),结构由错误码决定', + required: false, + example: { issues: [{ path: 'name', message: 'Required' }] }, + }) + details?: unknown; + + @ApiProperty({ + type: String, + description: '请求追踪 ID,与日志同源', + example: '01JABCDEF0123456789ABCDEFG', + }) + trace_id!: string; +} diff --git a/apps/api/src/common/pipes/zod-validation.pipe.spec.ts b/apps/api/src/common/pipes/zod-validation.pipe.spec.ts new file mode 100644 index 0000000..1e5e3c7 --- /dev/null +++ b/apps/api/src/common/pipes/zod-validation.pipe.spec.ts @@ -0,0 +1,39 @@ +import { ErrorCode } from '@lingxi/shared'; +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; + +import { AppException } from '../errors/app-exception'; +import { ZodValidationPipe } from './zod-validation.pipe'; + +const schema = z.object({ + name: z.string().min(2), + limit: z.coerce.number().int().min(1).max(100).default(20), +}); + +describe('ZodValidationPipe', () => { + it('通过校验并返回转换后的值', () => { + const pipe = new ZodValidationPipe(schema); + expect(pipe.transform({ name: '素材', limit: '30' })).toEqual({ name: '素材', limit: 30 }); + }); + + it('缺省值生效', () => { + const pipe = new ZodValidationPipe(schema); + expect(pipe.transform({ name: '素材' })).toEqual({ name: '素材', limit: 20 }); + }); + + it('校验失败抛 40001,details 带字段错误', () => { + const pipe = new ZodValidationPipe(schema); + try { + pipe.transform({ name: 'x' }); + throw new Error('应当抛错'); + } catch (error) { + expect(error).toBeInstanceOf(AppException); + const appError = error instanceof AppException ? error : null; + expect(appError?.code).toBe(ErrorCode.PARAM_INVALID); + expect(appError?.status).toBe(400); + expect(appError?.details).toMatchObject({ + issues: [{ path: 'name' }], + }); + } + }); +}); diff --git a/apps/api/src/common/pipes/zod-validation.pipe.ts b/apps/api/src/common/pipes/zod-validation.pipe.ts new file mode 100644 index 0000000..bac77e2 --- /dev/null +++ b/apps/api/src/common/pipes/zod-validation.pipe.ts @@ -0,0 +1,30 @@ +import { ErrorCode } from '@lingxi/shared'; +import { type PipeTransform } from '@nestjs/common'; +import type { ZodSchema } from 'zod'; + +import { AppException } from '../errors/app-exception'; + +/** + * Zod 校验管道(静态 DTO):body / query / params 共用。 + * 校验失败统一转 40001,并把字段错误放进 details.issues,便于前端定位。 + */ +export class ZodValidationPipe implements PipeTransform { + constructor(private readonly schema: ZodSchema) {} + + transform(value: unknown): T { + const result = this.schema.safeParse(value); + if (!result.success) { + throw new AppException({ + code: ErrorCode.PARAM_INVALID, + message: '参数校验失败', + details: { + issues: result.error.issues.map((issue) => ({ + path: issue.path.join('.'), + message: issue.message, + })), + }, + }); + } + return result.data; + } +} diff --git a/apps/api/src/config/config.module.ts b/apps/api/src/config/config.module.ts new file mode 100644 index 0000000..23c5120 --- /dev/null +++ b/apps/api/src/config/config.module.ts @@ -0,0 +1,17 @@ +import { Global, Module } from '@nestjs/common'; + +import { type AppConfig, loadEnv } from './env'; + +export const APP_CONFIG = Symbol('APP_CONFIG'); + +@Global() +@Module({ + providers: [ + { + provide: APP_CONFIG, + useFactory: (): AppConfig => loadEnv(), + }, + ], + exports: [APP_CONFIG], +}) +export class AppConfigModule {} diff --git a/apps/api/src/config/env.spec.ts b/apps/api/src/config/env.spec.ts new file mode 100644 index 0000000..6ed3f2c --- /dev/null +++ b/apps/api/src/config/env.spec.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { loadEnv } from './env'; + +const validEnv: NodeJS.ProcessEnv = { + NODE_ENV: 'test', + DATABASE_URL: 'postgresql://lingxi:lingxi@127.0.0.1:15432/lingxi_test', + REDIS_URL: 'redis://127.0.0.1:16379', + WEB_ORIGIN: 'http://127.0.0.1:5173,http://localhost:5173', +}; + +describe('loadEnv', () => { + it('解析必填项并给可选项默认值', () => { + const config = loadEnv(validEnv); + expect(config.nodeEnv).toBe('test'); + expect(config.isProduction).toBe(false); + expect(config.port).toBe(3000); + expect(config.host).toBe('0.0.0.0'); + expect(config.logLevel).toBe('info'); + expect(config.maxUploadMb).toBe(500); + expect(config.featureVideo).toBe(false); + expect(config.webOrigins).toEqual(['http://127.0.0.1:5173', 'http://localhost:5173']); + }); + + it('字符串数字会被转成 number', () => { + const config = loadEnv({ ...validEnv, PORT: '8080', MAX_UPLOAD_MB: '200' }); + expect(config.port).toBe(8080); + expect(config.maxUploadMb).toBe(200); + }); + + it('缺少 DATABASE_URL 时抛错并带字段名', () => { + expect(() => loadEnv({ ...validEnv, DATABASE_URL: undefined })).toThrow(/DATABASE_URL/); + }); + + it('拒绝非 postgres 连接串', () => { + expect(() => loadEnv({ ...validEnv, DATABASE_URL: 'mysql://localhost/db' })).toThrow( + /DATABASE_URL/, + ); + }); + + it('拒绝非法 LOG_LEVEL', () => { + expect(() => loadEnv({ ...validEnv, LOG_LEVEL: 'verbose' })).toThrow(/LOG_LEVEL/); + }); + + it('FEATURE_VIDEO=true 转为布尔值', () => { + expect(loadEnv({ ...validEnv, FEATURE_VIDEO: 'true' }).featureVideo).toBe(true); + }); +}); diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts new file mode 100644 index 0000000..abcb654 --- /dev/null +++ b/apps/api/src/config/env.ts @@ -0,0 +1,68 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + PORT: z.coerce.number().int().min(1).max(65535).default(3000), + HOST: z.string().min(1).default('0.0.0.0'), + LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), + DATABASE_URL: z + .string() + .min(1) + .refine( + (value) => value.startsWith('postgresql://') || value.startsWith('postgres://'), + 'DATABASE_URL 必须是 postgresql:// 连接串', + ), + REDIS_URL: z + .string() + .min(1) + .refine( + (value) => value.startsWith('redis://') || value.startsWith('rediss://'), + 'REDIS_URL 必须是 redis:// 连接串', + ), + WEB_ORIGIN: z.string().default('http://127.0.0.1:5173'), + MAX_UPLOAD_MB: z.coerce.number().int().min(1).max(10240).default(500), + FEATURE_VIDEO: z + .enum(['true', 'false']) + .default('false') + .transform((value) => value === 'true'), +}); + +export type RawEnv = z.infer; + +export interface AppConfig { + nodeEnv: RawEnv['NODE_ENV']; + isProduction: boolean; + port: number; + host: string; + logLevel: RawEnv['LOG_LEVEL']; + databaseUrl: string; + redisUrl: string; + webOrigins: string[]; + maxUploadMb: number; + featureVideo: boolean; +} + +export function loadEnv(source: NodeJS.ProcessEnv = process.env): AppConfig { + const result = envSchema.safeParse(source); + if (!result.success) { + const details = result.error.issues + .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`) + .join('; '); + throw new Error(`环境变量校验失败:${details}`); + } + const env = result.data; + return { + nodeEnv: env.NODE_ENV, + isProduction: env.NODE_ENV === 'production', + port: env.PORT, + host: env.HOST, + logLevel: env.LOG_LEVEL, + databaseUrl: env.DATABASE_URL, + redisUrl: env.REDIS_URL, + webOrigins: env.WEB_ORIGIN.split(',') + .map((origin) => origin.trim()) + .filter((origin) => origin.length > 0), + maxUploadMb: env.MAX_UPLOAD_MB, + featureVideo: env.FEATURE_VIDEO, + }; +} diff --git a/apps/api/src/config/load-dotenv.ts b/apps/api/src/config/load-dotenv.ts new file mode 100644 index 0000000..8da0c3f --- /dev/null +++ b/apps/api/src/config/load-dotenv.ts @@ -0,0 +1,17 @@ +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { config as loadDotenvFile } from 'dotenv'; + +/** + * 按工程规范 09 的顺序加载本地环境变量:.env.local 优先,.env 兜底。 + * 只做加载,不做校验(校验见 config/env.ts)。 + */ +export function loadDotenv(): void { + for (const file of ['.env.local', '.env']) { + const path = resolve(process.cwd(), file); + if (existsSync(path)) { + loadDotenvFile({ path }); + } + } +} diff --git a/apps/api/src/health.int.spec.ts b/apps/api/src/health.int.spec.ts new file mode 100644 index 0000000..a12db70 --- /dev/null +++ b/apps/api/src/health.int.spec.ts @@ -0,0 +1,34 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { createApp, type CreatedApiApp } from './bootstrap'; +import { loadDotenv } from './config/load-dotenv'; + +/** + * 集成测试:需要 pnpm compose:up 后真实 PG / Redis。 + * 单独运行:pnpm test:int + */ +describe('GET /health(集成)', () => { + let created: CreatedApiApp; + + beforeAll(async () => { + loadDotenv(); + created = await createApp(); + await created.app.init(); + }); + + afterAll(async () => { + await created.app.close(); + }); + + it('真实依赖健康时返回 200', async () => { + const response = await created.app.inject({ method: 'GET', url: '/health' }); + expect(response.statusCode).toBe(200); + const body = response.json<{ + status: string; + checks: { database: { status: string }; redis: { status: string } }; + }>(); + expect(body.status).toBe('ok'); + expect(body.checks.database.status).toBe('up'); + expect(body.checks.redis.status).toBe('up'); + }); +}); diff --git a/apps/api/src/health.smoke.spec.ts b/apps/api/src/health.smoke.spec.ts new file mode 100644 index 0000000..f676ea7 --- /dev/null +++ b/apps/api/src/health.smoke.spec.ts @@ -0,0 +1,41 @@ +import { ErrorCode } from '@lingxi/shared'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { createTestApp, type TestApp } from './testing/test-app'; + +describe('GET /health(冒烟)', () => { + let testApp: TestApp; + + beforeAll(async () => { + testApp = await createTestApp(); + }); + + afterAll(async () => { + await testApp.app.close(); + }); + + it('依赖健康时返回 200 与检查明细', async () => { + const response = await testApp.app.inject({ method: 'GET', url: '/health' }); + expect(response.statusCode).toBe(200); + const body = response.json<{ + status: string; + checks: { database: { status: string }; redis: { status: string } }; + }>(); + expect(body.status).toBe('ok'); + expect(body.checks.database.status).toBe('up'); + expect(body.checks.redis.status).toBe('up'); + }); +}); + +describe('GET /health(依赖异常)', () => { + it('数据库不可用时返回 503 与统一错误体', async () => { + const testApp = await createTestApp({ databaseUp: false }); + const response = await testApp.app.inject({ method: 'GET', url: '/health' }); + expect(response.statusCode).toBe(503); + const body = response.json<{ code: number; message: string; trace_id: string }>(); + expect(body.code).toBe(ErrorCode.DEPENDENCY_UNAVAILABLE); + expect(body.message).toBe('依赖服务不可用'); + expect(body.trace_id.length).toBeGreaterThan(0); + await testApp.app.close(); + }); +}); diff --git a/apps/api/src/infra/infra.service.spec.ts b/apps/api/src/infra/infra.service.spec.ts new file mode 100644 index 0000000..a91e27b --- /dev/null +++ b/apps/api/src/infra/infra.service.spec.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { testConfig } from '../testing/test-app'; +import { PrismaService } from './prisma/prisma.service'; +import { RedisService } from './redis/redis.service'; + +describe('PrismaService', () => { + it('关闭应用时断开 Prisma 连接', async () => { + const service = new PrismaService(testConfig); + const disconnect = vi.spyOn(service, '$disconnect').mockResolvedValue(); + await service.onModuleDestroy(); + expect(disconnect).toHaveBeenCalledTimes(1); + }); +}); + +describe('RedisService', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('ping 返回 PONG', async () => { + const service = new RedisService(testConfig); + const client = service.getClient(); + vi.spyOn(client, 'connect').mockResolvedValue(undefined); + vi.spyOn(client, 'ping').mockResolvedValue('PONG'); + await expect(service.ping()).resolves.toBe('PONG'); + }); + + it('关闭应用时断开 Redis 连接', () => { + const service = new RedisService(testConfig); + const client = service.getClient(); + const disconnect = vi.spyOn(client, 'disconnect').mockReturnValue(undefined); + service.onModuleDestroy(); + expect(disconnect).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/api/src/infra/prisma/prisma.module.ts b/apps/api/src/infra/prisma/prisma.module.ts new file mode 100644 index 0000000..a363c4a --- /dev/null +++ b/apps/api/src/infra/prisma/prisma.module.ts @@ -0,0 +1,19 @@ +import { Global, Module } from '@nestjs/common'; + +import { APP_CONFIG } from '../../config/config.module'; +import type { AppConfig } from '../../config/env'; +import { PRISMA } from '../tokens'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [ + { + provide: PRISMA, + inject: [APP_CONFIG], + useFactory: (config: AppConfig): PrismaService => new PrismaService(config), + }, + ], + exports: [PRISMA], +}) +export class PrismaModule {} diff --git a/apps/api/src/infra/prisma/prisma.service.ts b/apps/api/src/infra/prisma/prisma.service.ts new file mode 100644 index 0000000..caae23d --- /dev/null +++ b/apps/api/src/infra/prisma/prisma.service.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable, type OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +import { APP_CONFIG } from '../../config/config.module'; +import type { AppConfig } from '../../config/env'; + +/** + * Prisma 封装。 + * 不主动 $connect:Prisma 首次查询时才建连接,这样 OpenAPI 导出、单元测试不需要真实数据库; + * /health 会执行 SELECT 1 做真实检查。 + */ +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleDestroy { + constructor(@Inject(APP_CONFIG) config: AppConfig) { + super({ + datasources: { db: { url: config.databaseUrl } }, + log: config.isProduction ? ['error'] : ['warn', 'error'], + }); + } + + async onModuleDestroy(): Promise { + await this.$disconnect(); + } +} diff --git a/apps/api/src/infra/redis/redis.module.ts b/apps/api/src/infra/redis/redis.module.ts new file mode 100644 index 0000000..8790ac5 --- /dev/null +++ b/apps/api/src/infra/redis/redis.module.ts @@ -0,0 +1,19 @@ +import { Global, Module } from '@nestjs/common'; + +import { APP_CONFIG } from '../../config/config.module'; +import type { AppConfig } from '../../config/env'; +import { REDIS } from '../tokens'; +import { RedisService } from './redis.service'; + +@Global() +@Module({ + providers: [ + { + provide: REDIS, + inject: [APP_CONFIG], + useFactory: (config: AppConfig): RedisService => new RedisService(config), + }, + ], + exports: [REDIS], +}) +export class RedisModule {} diff --git a/apps/api/src/infra/redis/redis.service.ts b/apps/api/src/infra/redis/redis.service.ts new file mode 100644 index 0000000..90d88b1 --- /dev/null +++ b/apps/api/src/infra/redis/redis.service.ts @@ -0,0 +1,44 @@ +import { Inject, Injectable, type OnModuleDestroy } from '@nestjs/common'; +import Redis from 'ioredis'; + +import { APP_CONFIG } from '../../config/config.module'; +import type { AppConfig } from '../../config/env'; + +/** + * Redis 客户端封装。 + * lazyConnect:不在应用启动时连接,避免 OpenAPI 导出 / 单测依赖真实 Redis; + * /health 调 ping 时才会真正连接。 + */ +@Injectable() +export class RedisService implements OnModuleDestroy { + private readonly client: Redis; + + constructor(@Inject(APP_CONFIG) config: AppConfig) { + this.client = new Redis(config.redisUrl, { + lazyConnect: true, + maxRetriesPerRequest: 1, + enableOfflineQueue: false, + }); + this.client.on('error', () => { + // 连接错误由健康检查与请求日志暴露,这里只避免 ioredis 抛未处理异常 + }); + } + + getClient(): Redis { + return this.client; + } + + async ping(): Promise<'PONG'> { + if (this.client.status === 'wait') { + await this.client.connect(); + } + await this.client.ping(); + return 'PONG'; + } + + onModuleDestroy(): void { + if (this.client.status !== 'end') { + this.client.disconnect(); + } + } +} diff --git a/apps/api/src/infra/tokens.ts b/apps/api/src/infra/tokens.ts new file mode 100644 index 0000000..59829e4 --- /dev/null +++ b/apps/api/src/infra/tokens.ts @@ -0,0 +1,3 @@ +/** 依赖注入 token:避免跨模块直接依赖具体实现 */ +export const PRISMA = Symbol('PRISMA'); +export const REDIS = Symbol('REDIS'); diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 0000000..a34ccb3 --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,24 @@ +import 'reflect-metadata'; + +import { Logger } from 'nestjs-pino'; + +import { createApp, setupOpenApi } from './bootstrap'; +import { loadDotenv } from './config/load-dotenv'; + +async function bootstrap(): Promise { + loadDotenv(); + const { app, config } = await createApp(); + await app.init(); + setupOpenApi(app, config); + await app.listen(config.port, config.host); + + const logger = app.get(Logger); + logger.log(`灵犀 API 已启动:http://${config.host}:${config.port}(文档 /api/docs)`); +} + +void bootstrap().catch((error: unknown) => { + process.stderr.write( + `API 启动失败:${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`, + ); + process.exitCode = 1; +}); diff --git a/apps/api/src/modules/platform/health/health.controller.ts b/apps/api/src/modules/platform/health/health.controller.ts new file mode 100644 index 0000000..2fd72ea --- /dev/null +++ b/apps/api/src/modules/platform/health/health.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; + +import { HealthService } from './health.service'; +import { type HealthReport, HealthResponseDto } from './health.types'; + +@ApiTags('platform') +@Controller('health') +export class HealthController { + constructor(@Inject(HealthService) private readonly healthService: HealthService) {} + + @Get() + @ApiOperation({ + summary: '健康检查', + description: '真实检查 PostgreSQL 与 Redis;任一不可用返回 503 与统一错误体。', + }) + @ApiOkResponse({ type: HealthResponseDto, description: '全部依赖健康' }) + @ApiResponse({ + status: 503, + description: '依赖不可用(错误码 50002),details 内附各依赖检查结果', + }) + async check(): Promise { + return this.healthService.check(); + } +} diff --git a/apps/api/src/modules/platform/health/health.module.ts b/apps/api/src/modules/platform/health/health.module.ts new file mode 100644 index 0000000..b486957 --- /dev/null +++ b/apps/api/src/modules/platform/health/health.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { HealthController } from './health.controller'; +import { HealthService } from './health.service'; + +@Module({ + controllers: [HealthController], + providers: [HealthService], +}) +export class HealthModule {} diff --git a/apps/api/src/modules/platform/health/health.service.ts b/apps/api/src/modules/platform/health/health.service.ts new file mode 100644 index 0000000..e97e677 --- /dev/null +++ b/apps/api/src/modules/platform/health/health.service.ts @@ -0,0 +1,94 @@ +import { ErrorCode, nowIso } from '@lingxi/shared'; +import { Inject, Injectable } from '@nestjs/common'; + +import { AppException } from '../../../common/errors/app-exception'; +import { APP_CONFIG } from '../../../config/config.module'; +import type { AppConfig } from '../../../config/env'; +import type { PrismaService } from '../../../infra/prisma/prisma.service'; +import type { RedisService } from '../../../infra/redis/redis.service'; +import { PRISMA } from '../../../infra/tokens'; +import { REDIS } from '../../../infra/tokens'; +import type { ComponentHealth, HealthReport } from './health.types'; + +const API_VERSION = '0.1.0'; +const CHECK_TIMEOUT_MS = 2000; + +function withTimeout(task: Promise, timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`检查超时(${String(timeoutMs)}ms)`)); + }, timeoutMs); + void task.then( + (value) => { + clearTimeout(timer); + resolve(value); + }, + (error: unknown) => { + clearTimeout(timer); + reject(error instanceof Error ? error : new Error(String(error))); + }, + ); + }); +} + +/** + * 健康检查:真实检查数据库(SELECT 1)与 Redis(PING),不只看端口(技术架构 §4.1.1)。 + * 任一依赖不可用时返回 503 + 统一错误体,details 里带完整检查快照。 + */ +@Injectable() +export class HealthService { + constructor( + @Inject(PRISMA) private readonly prisma: PrismaService, + @Inject(REDIS) private readonly redis: RedisService, + @Inject(APP_CONFIG) private readonly config: AppConfig, + ) {} + + async check(): Promise { + const [database, redis] = await Promise.all([this.checkDatabase(), this.checkRedis()]); + const report: HealthReport = { + status: database.status === 'up' && redis.status === 'up' ? 'ok' : 'degraded', + env: this.config.nodeEnv, + version: API_VERSION, + uptimeSeconds: Math.round(process.uptime()), + checks: { database, redis }, + timestamp: nowIso(), + }; + + if (report.status === 'degraded') { + throw new AppException({ + code: ErrorCode.DEPENDENCY_UNAVAILABLE, + message: '依赖服务不可用', + details: report.checks, + }); + } + return report; + } + + private async checkDatabase(): Promise { + const startedAt = Date.now(); + try { + await withTimeout(this.prisma.$queryRaw`SELECT 1`, CHECK_TIMEOUT_MS); + return { status: 'up', latencyMs: Date.now() - startedAt }; + } catch (error) { + return { + status: 'down', + latencyMs: Date.now() - startedAt, + error: error instanceof Error ? error.message : '未知错误', + }; + } + } + + private async checkRedis(): Promise { + const startedAt = Date.now(); + try { + await withTimeout(this.redis.ping(), CHECK_TIMEOUT_MS); + return { status: 'up', latencyMs: Date.now() - startedAt }; + } catch (error) { + return { + status: 'down', + latencyMs: Date.now() - startedAt, + error: error instanceof Error ? error.message : '未知错误', + }; + } + } +} diff --git a/apps/api/src/modules/platform/health/health.types.ts b/apps/api/src/modules/platform/health/health.types.ts new file mode 100644 index 0000000..2888db5 --- /dev/null +++ b/apps/api/src/modules/platform/health/health.types.ts @@ -0,0 +1,58 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export interface ComponentHealth { + status: 'up' | 'down'; + latencyMs: number; + error?: string; +} + +export interface HealthReport { + status: 'ok' | 'degraded'; + env: string; + version: string; + uptimeSeconds: number; + checks: { + database: ComponentHealth; + redis: ComponentHealth; + }; + timestamp: string; +} + +export class ComponentHealthDto { + @ApiProperty({ type: String, enum: ['up', 'down'], example: 'up' }) + status!: 'up' | 'down'; + + @ApiProperty({ type: Number, description: '检查耗时(毫秒)', example: 3 }) + latencyMs!: number; + + @ApiProperty({ type: String, required: false, description: '失败原因(仅 down 时返回)' }) + error?: string; +} + +export class HealthChecksDto { + @ApiProperty({ type: ComponentHealthDto }) + database!: ComponentHealthDto; + + @ApiProperty({ type: ComponentHealthDto }) + redis!: ComponentHealthDto; +} + +export class HealthResponseDto { + @ApiProperty({ type: String, enum: ['ok', 'degraded'], example: 'ok' }) + status!: 'ok' | 'degraded'; + + @ApiProperty({ type: String, example: 'development' }) + env!: string; + + @ApiProperty({ type: String, example: '0.1.0' }) + version!: string; + + @ApiProperty({ type: Number, description: '进程运行秒数', example: 128 }) + uptimeSeconds!: number; + + @ApiProperty({ type: HealthChecksDto }) + checks!: HealthChecksDto; + + @ApiProperty({ type: String, example: '2026-11-02T08:00:00.000Z' }) + timestamp!: string; +} diff --git a/apps/api/src/modules/platform/platform.module.ts b/apps/api/src/modules/platform/platform.module.ts new file mode 100644 index 0000000..dcc6df6 --- /dev/null +++ b/apps/api/src/modules/platform/platform.module.ts @@ -0,0 +1,14 @@ +import { Global, Module } from '@nestjs/common'; + +import { HealthModule } from './health/health.module'; + +/** + * platform 模块:健康检查、系统配置、审计、AI 网关等平台能力的归属模块(技术架构 §3)。 + * A0 只落地健康检查;其余能力按里程碑在本模块内新增。 + */ +@Global() +@Module({ + imports: [HealthModule], + exports: [HealthModule], +}) +export class PlatformModule {} diff --git a/apps/api/src/openapi/document.spec.ts b/apps/api/src/openapi/document.spec.ts new file mode 100644 index 0000000..17609f4 --- /dev/null +++ b/apps/api/src/openapi/document.spec.ts @@ -0,0 +1,36 @@ +import { ErrorCode } from '@lingxi/shared'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { createTestApp, type TestApp,testConfig } from '../testing/test-app'; +import { buildOpenApiDocument } from './document'; + +describe('OpenAPI 产物(冒烟)', () => { + let testApp: TestApp; + + beforeAll(async () => { + testApp = await createTestApp(); + }); + + afterAll(async () => { + await testApp.app.close(); + }); + + it('版本为 3.1 且包含健康检查路由', () => { + const document = buildOpenApiDocument(testApp.app, testConfig); + expect(document.openapi).toBe('3.1.0'); + expect(Object.keys(document.paths).some((path) => path.endsWith('/health'))).toBe(true); + expect(document.info.version).toBe('0.1.0'); + }); + + it('包含全部错误码枚举与统一错误体', () => { + const document = buildOpenApiDocument(testApp.app, testConfig); + const schemas = document.components?.schemas ?? {}; + expect(schemas.ErrorCode).toBeDefined(); + expect(schemas.ErrorResponse).toBeDefined(); + const errorCodeSchema = schemas.ErrorCode; + expect(errorCodeSchema && 'enum' in errorCodeSchema ? errorCodeSchema.enum : []).toHaveLength( + Object.values(ErrorCode).length, + ); + expect('x-error-codes' in document).toBe(true); + }); +}); diff --git a/apps/api/src/openapi/document.ts b/apps/api/src/openapi/document.ts new file mode 100644 index 0000000..3979107 --- /dev/null +++ b/apps/api/src/openapi/document.ts @@ -0,0 +1,74 @@ +import { ErrorCode } from '@lingxi/shared'; +import { type INestApplication } from '@nestjs/common'; +import type { OpenAPIObject } from '@nestjs/swagger'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; + +import { ErrorResponseDto } from '../common/http/error-response.dto'; +import type { AppConfig } from '../config/env'; + +/** + * 生成 OpenAPI 文档。 + * - 版本固定 3.1(技术架构:OpenAPI 3.1 + oasdiff breaking 检测) + * - 错误码以 ErrorCode schema + x-error-codes 扩展显式写入产物,供前端 codegen 与契约比对 + */ +export function buildOpenApiDocument(app: INestApplication, config: AppConfig): OpenAPIObject { + const builder = new DocumentBuilder() + .setTitle('灵犀 API') + .setDescription( + [ + '信息流广告素材生产平台后端 API。', + '', + '统一约定:', + '- 前缀 /api/v1(健康检查 /health 除外)', + '- 错误体 { code, message, details, trace_id }', + '- 列表响应 { items, next_cursor }', + ].join('\n'), + ) + .setVersion('0.1.0') + .setOpenAPIVersion('3.1.0') + .addServer('/api/v1', 'API v1') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, builder, { + extraModels: [ErrorResponseDto], + }); + + const components = document.components ?? {}; + const schemas = components.schemas ?? {}; + + schemas.ErrorCode = { + type: 'integer', + description: '业务错误码(只允许使用 @lingxi/shared 中登记的错误码)', + enum: Object.values(ErrorCode), + }; + schemas.ErrorResponse = { + type: 'object', + required: ['code', 'message', 'trace_id'], + properties: { + code: { $ref: '#/components/schemas/ErrorCode' }, + message: { type: 'string' }, + details: { description: '补充信息,结构由错误码决定' }, + trace_id: { type: 'string' }, + }, + }; + + document.components = { ...components, schemas }; + Object.assign(document, { + 'x-error-codes': Object.values(ErrorCode).map((code) => ({ code })), + 'x-api-config': { + env: config.nodeEnv, + featureVideo: config.featureVideo, + }, + }); + + return document; +} + +export function setupOpenApi(app: INestApplication, config: AppConfig): OpenAPIObject { + const document = buildOpenApiDocument(app, config); + SwaggerModule.setup('api/docs', app, document, { + jsonDocumentUrl: 'api/openapi.json', + }); + return document; +} diff --git a/apps/api/src/scripts/export-openapi.ts b/apps/api/src/scripts/export-openapi.ts new file mode 100644 index 0000000..fad3e79 --- /dev/null +++ b/apps/api/src/scripts/export-openapi.ts @@ -0,0 +1,30 @@ +import 'reflect-metadata'; + +import { mkdir, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +import { createApp } from '../bootstrap'; +import { loadDotenv } from '../config/load-dotenv'; +import { buildOpenApiDocument } from '../openapi/document'; + +async function main(): Promise { + loadDotenv(); + const { app, config } = await createApp(); + await app.init(); + + const document = buildOpenApiDocument(app, config); + const outputDir = resolve(process.cwd(), 'openapi'); + const outputFile = resolve(outputDir, 'lingxi.openapi.json'); + await mkdir(outputDir, { recursive: true }); + await writeFile(outputFile, `${JSON.stringify(document, null, 2)}\n`, 'utf8'); + + process.stdout.write(`OpenAPI 已导出:${outputFile}\n`); + await app.close(); +} + +void main().catch((error: unknown) => { + process.stderr.write( + `OpenAPI 导出失败:${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`, + ); + process.exitCode = 1; +}); diff --git a/apps/api/src/testing/test-app.ts b/apps/api/src/testing/test-app.ts new file mode 100644 index 0000000..6895c42 --- /dev/null +++ b/apps/api/src/testing/test-app.ts @@ -0,0 +1,91 @@ +import { API_PREFIX, ErrorCode } from '@lingxi/shared'; +import { Controller, Get, NotFoundException } from '@nestjs/common'; +import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; +import { Test } from '@nestjs/testing'; +import { type Mock, vi } from 'vitest'; + +import { AppException } from '../common/errors/app-exception'; +import { AllExceptionsFilter } from '../common/filters/all-exceptions.filter'; +import { APP_CONFIG } from '../config/config.module'; +import type { AppConfig } from '../config/env'; +import { PRISMA, REDIS } from '../infra/tokens'; +import { HealthController } from '../modules/platform/health/health.controller'; +import { HealthService } from '../modules/platform/health/health.service'; + +export const testConfig: AppConfig = { + nodeEnv: 'test', + isProduction: false, + port: 3000, + host: '127.0.0.1', + logLevel: 'error', + databaseUrl: 'postgresql://lingxi:lingxi@127.0.0.1:15432/lingxi_test', + redisUrl: 'redis://127.0.0.1:16379', + webOrigins: ['http://127.0.0.1:5173'], + maxUploadMb: 500, + featureVideo: false, +}; + +@Controller('__test') +class ThrowingController { + @Get('app-error') + appError(): never { + throw new AppException({ code: ErrorCode.STATE_NOT_ALLOWED, message: '当前状态不允许该操作' }); + } + + @Get('http-404') + notFound(): never { + throw new NotFoundException('资源不存在'); + } + + @Get('boom') + boom(): never { + throw new Error('unexpected failure'); + } +} + +export interface TestAppOptions { + databaseUp?: boolean; + redisUp?: boolean; + includeErrorRoutes?: boolean; +} + +export interface TestApp { + app: NestFastifyApplication; + prisma: { $queryRaw: Mock }; + redis: { ping: Mock }; +} + +export async function createTestApp(options: TestAppOptions = {}): Promise { + const prisma = { + $queryRaw: + options.databaseUp === false + ? vi.fn().mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:15432')) + : vi.fn().mockResolvedValue([{ '?column?': 1 }]), + }; + const redis = { + ping: + options.redisUp === false + ? vi.fn().mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:16379')) + : vi.fn().mockResolvedValue('PONG'), + }; + + const moduleRef = await Test.createTestingModule({ + controllers: + options.includeErrorRoutes === true + ? [HealthController, ThrowingController] + : [HealthController], + providers: [ + HealthService, + { provide: PRISMA, useValue: prisma }, + { provide: REDIS, useValue: redis }, + { provide: APP_CONFIG, useValue: testConfig }, + ], + }).compile(); + + const app = moduleRef.createNestApplication(new FastifyAdapter()); + app.setGlobalPrefix(API_PREFIX, { exclude: ['health'] }); + app.useGlobalFilters(new AllExceptionsFilter()); + await app.init(); + + return { app, prisma, redis }; +} diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 0000000..cfff73f --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "dist", + "rootDir": "src", + "declaration": false, + "declarationMap": false, + "sourceMap": true + }, + "include": ["src"], + "exclude": ["src/**/*.spec.ts", "src/**/*.int.spec.ts", "src/scripts/**"] +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..f4796a1 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "lib": ["ES2022"], + "types": ["node"], + "noEmit": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "useDefineForClassFields": false + }, + "include": ["src", "vitest.config.ts", "vitest.int.config.ts"] +} diff --git a/apps/api/vitest.config.ts b/apps/api/vitest.config.ts new file mode 100644 index 0000000..5a91445 --- /dev/null +++ b/apps/api/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.spec.ts'], + exclude: ['**/*.int.spec.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'json-summary'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.spec.ts', 'src/**/*.int.spec.ts', 'src/main.ts', 'src/scripts/**'], + thresholds: { lines: 60, functions: 60, statements: 60, branches: 60 }, + }, + }, +}); diff --git a/apps/api/vitest.int.config.ts b/apps/api/vitest.int.config.ts new file mode 100644 index 0000000..fd8a853 --- /dev/null +++ b/apps/api/vitest.int.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.int.spec.ts'], + testTimeout: 30_000, + hookTimeout: 30_000, + }, +}); diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..a6d6327 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,13 @@ + + + + + + + 灵犀 · 信息流素材生产平台 + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..ea55689 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "@lingxi/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.json --noEmit && vite build", + "preview": "vite preview --port 4173 --strictPort", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run --coverage" + }, + "dependencies": { + "@douyinfe/semi-ui-19": "2.104.0", + "@lingxi/shared": "workspace:*", + "@lingxi/ui": "workspace:*", + "@tanstack/react-query": "5.105.1", + "react": "19.3.1", + "react-dom": "19.3.1", + "react-router": "7.18.5", + "zod": "3.25.76", + "zustand": "5.0.16" + }, + "devDependencies": { + "@testing-library/react": "16.3.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "5.2.0", + "@vitest/coverage-v8": "3.2.7", + "jsdom": "28.1.0", + "typescript": "5.9.3", + "vite": "6.4.5", + "vitest": "3.2.7" + } +} diff --git a/apps/web/src/api/client.spec.ts b/apps/web/src/api/client.spec.ts new file mode 100644 index 0000000..3eca30d --- /dev/null +++ b/apps/web/src/api/client.spec.ts @@ -0,0 +1,141 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useAuthStore } from '@/stores/auth-store'; + +import { ApiError, request, setUnauthorizedHandler } from './client'; + +const jsonResponse = (body: unknown, status = 200): Response => + new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }); + +const emptyResponse = (status: number): Response => new Response(null, { status }); + +function signIn(accessToken = 'token-1', refreshToken = 'refresh-1'): void { + useAuthStore.getState().setSession({ + accessToken, + refreshToken, + user: { id: '01J0TEST000000000000000000', name: '测试用户', email: 'test@lingxi.local' }, + permissions: [], + }); +} + +function authorizationOf(init: RequestInit | undefined): string | null { + return init?.headers instanceof Headers ? init.headers.get('Authorization') : null; +} + +function toUrl(input: string | URL | Request | undefined): string { + if (typeof input === 'string') { + return input; + } + if (input instanceof URL) { + return input.href; + } + return input?.url ?? ''; +} + +describe('请求层', () => { + beforeEach(() => { + vi.restoreAllMocks(); + window.localStorage.clear(); + useAuthStore.getState().clearSession(); + setUnauthorizedHandler(null); + }); + + it('注入 Bearer token 并解析 JSON', async () => { + signIn(); + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue(jsonResponse({ status: 'ok' })); + + await expect(request<{ status: string }>('/health')).resolves.toEqual({ status: 'ok' }); + const init = fetchMock.mock.calls[0]?.[1]; + expect(authorizationOf(init)).toBe('Bearer token-1'); + expect(toUrl(fetchMock.mock.calls[0]?.[0])).toBe('http://127.0.0.1:3000/api/v1/health'); + }); + + it('auth: false 时不注入 token', async () => { + signIn(); + const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true })); + await request('/auth/login', { + method: 'POST', + auth: false, + body: { email: 'a', password: 'b' }, + }); + expect(authorizationOf(fetchMock.mock.calls[0]?.[1])).toBeNull(); + }); + + it('204 返回 undefined', async () => { + signIn(); + vi.spyOn(globalThis, 'fetch').mockResolvedValue(emptyResponse(204)); + await expect(request('/resource')).resolves.toBeUndefined(); + }); + + it('统一错误体解析为 ApiError,保留 trace_id', async () => { + signIn(); + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + jsonResponse( + { code: 42201, message: '业务校验失败', details: { field: 'name' }, trace_id: 'trace-1' }, + 422, + ), + ); + const error = await request('/resource').catch((caught: unknown) => caught); + expect(error).toBeInstanceOf(ApiError); + const apiError = error instanceof ApiError ? error : null; + expect(apiError?.code).toBe(42201); + expect(apiError?.status).toBe(422); + expect(apiError?.traceId).toBe('trace-1'); + expect(apiError?.details).toEqual({ field: 'name' }); + }); + + it('401 时单飞刷新并重放原请求', async () => { + signIn('expired-token', 'refresh-1'); + let refreshCalls = 0; + vi.spyOn(globalThis, 'fetch').mockImplementation((input, init) => { + const url = toUrl(input); + const authorization = authorizationOf(init); + if (url.endsWith('/auth/refresh')) { + refreshCalls += 1; + return Promise.resolve( + jsonResponse({ accessToken: 'new-token', refreshToken: 'refresh-2' }), + ); + } + if (authorization === 'Bearer expired-token') { + return Promise.resolve(emptyResponse(401)); + } + return Promise.resolve(jsonResponse({ ok: true })); + }); + + const [first, second] = await Promise.all([request('/a'), request('/b')]); + expect(first).toEqual({ ok: true }); + expect(second).toEqual({ ok: true }); + expect(refreshCalls).toBe(1); + expect(useAuthStore.getState().accessToken).toBe('new-token'); + }); + + it('刷新失败时清空会话并触发未授权回调', async () => { + signIn('expired-token', 'refresh-1'); + const onUnauthorized = vi.fn(); + setUnauthorizedHandler(onUnauthorized); + vi.spyOn(globalThis, 'fetch').mockImplementation((input) => { + const url = toUrl(input); + return Promise.resolve( + url.endsWith('/auth/refresh') ? emptyResponse(401) : emptyResponse(401), + ); + }); + + await expect(request('/secure')).rejects.toBeInstanceOf(ApiError); + expect(onUnauthorized).toHaveBeenCalledTimes(1); + expect(useAuthStore.getState().accessToken).toBeNull(); + }); + + it('把请求体序列化为 JSON', async () => { + signIn(); + const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ ok: true })); + await request('/resource', { method: 'POST', body: { name: '素材' } }); + const init = fetchMock.mock.calls[0]?.[1]; + expect(init?.body).toBe(JSON.stringify({ name: '素材' })); + expect(init?.method).toBe('POST'); + }); +}); diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts new file mode 100644 index 0000000..37dab18 --- /dev/null +++ b/apps/web/src/api/client.ts @@ -0,0 +1,199 @@ +import { apiErrorBodySchema } from '@lingxi/shared'; + +import { useAuthStore } from '@/stores/auth-store'; + +const DEFAULT_BASE_URL = 'http://127.0.0.1:3000/api/v1'; + +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + +export interface RequestOptions { + method?: HttpMethod; + body?: unknown; + headers?: Record; + signal?: AbortSignal; + /** 登录、刷新等接口不需要携带 token */ + auth?: boolean; +} + +export interface ApiErrorInit { + code: number; + status: number; + message: string; + details?: unknown; + traceId?: string; +} + +/** 前端统一 API 错误:与服务端错误体 {code,message,details,trace_id} 对齐 */ +export class ApiError extends Error { + readonly code: number; + readonly status: number; + readonly details?: unknown; + readonly traceId?: string; + + constructor(init: ApiErrorInit) { + super(init.message); + this.name = 'ApiError'; + this.code = init.code; + this.status = init.status; + this.details = init.details; + this.traceId = init.traceId; + } +} + +let unauthorizedHandler: (() => void) | null = null; + +export function setUnauthorizedHandler(handler: (() => void) | null): void { + unauthorizedHandler = handler; +} + +function baseUrl(): string { + const configured = import.meta.env.VITE_PUBLIC_API_BASE_URL; + return (configured ?? DEFAULT_BASE_URL).replace(/\/+$/, ''); +} + +function buildUrl(path: string): string { + if (/^https?:\/\//.test(path)) { + return path; + } + return `${baseUrl()}${path.startsWith('/') ? path : `/${path}`}`; +} + +function serializeBody(body: unknown): BodyInit | undefined { + if (body === undefined) { + return undefined; + } + if ( + typeof body === 'string' || + body instanceof FormData || + body instanceof Blob || + body instanceof URLSearchParams || + body instanceof ArrayBuffer + ) { + return body; + } + return JSON.stringify(body); +} + +function fallbackCode(status: number): number { + if (status === 401) return 40101; + if (status === 403) return 40301; + if (status === 404) return 40401; + if (status === 409) return 40903; + if (status === 429) return 42901; + if (status >= 500) return 50001; + return 40001; +} + +async function toApiError(response: Response): Promise { + let payload: unknown = null; + try { + payload = await response.json(); + } catch { + payload = null; + } + const parsed = apiErrorBodySchema.safeParse(payload); + if (parsed.success) { + return new ApiError({ + code: parsed.data.code, + status: response.status, + message: parsed.data.message, + details: parsed.data.details, + traceId: parsed.data.trace_id, + }); + } + return new ApiError({ + code: fallbackCode(response.status), + status: response.status, + message: `请求失败(HTTP ${response.status})`, + }); +} + +let refreshPromise: Promise | null = null; + +/** 单飞刷新:并发 401 只触发一次 /auth/refresh */ +export function refreshSession(): Promise { + refreshPromise ??= doRefresh().finally(() => { + refreshPromise = null; + }); + return refreshPromise; +} + +async function doRefresh(): Promise { + const { refreshToken, clearSession } = useAuthStore.getState(); + if (refreshToken === null) { + return false; + } + let response: Response; + try { + response = await fetch(buildUrl('/auth/refresh'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ refreshToken }), + }); + } catch { + return false; + } + if (!response.ok) { + clearSession(); + unauthorizedHandler?.(); + return false; + } + const payload: unknown = await response.json().catch(() => null); + const accessToken = + typeof payload === 'object' && payload !== null && 'accessToken' in payload + ? payload.accessToken + : null; + const nextRefreshToken = + typeof payload === 'object' && payload !== null && 'refreshToken' in payload + ? payload.refreshToken + : null; + if (typeof accessToken !== 'string' || accessToken.length === 0) { + clearSession(); + unauthorizedHandler?.(); + return false; + } + useAuthStore.getState().setTokens({ + accessToken, + refreshToken: typeof nextRefreshToken === 'string' ? nextRefreshToken : refreshToken, + }); + return true; +} + +async function send(path: string, options: RequestOptions, allowRefresh: boolean): Promise { + const { accessToken } = useAuthStore.getState(); + const headers = new Headers(options.headers); + const body = serializeBody(options.body); + if (body !== undefined && !(body instanceof FormData) && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/json'); + } + if (options.auth !== false && accessToken !== null) { + headers.set('Authorization', `Bearer ${accessToken}`); + } + + const response = await fetch(buildUrl(path), { + method: options.method ?? 'GET', + headers, + body, + signal: options.signal, + }); + + if (response.status === 401 && allowRefresh && options.auth !== false) { + const refreshed = await refreshSession(); + if (refreshed) { + return send(path, options, false); + } + } + + if (!response.ok) { + throw await toApiError(response); + } + if (response.status === 204) { + return undefined as T; + } + return (await response.json()) as T; +} + +/** 业务请求入口:自动注入 token、401 单飞刷新、统一错误体解析 */ +export function request(path: string, options: RequestOptions = {}): Promise { + return send(path, options, true); +} diff --git a/apps/web/src/api/generated/openapi.d.ts b/apps/web/src/api/generated/openapi.d.ts new file mode 100644 index 0000000..371a9b8 --- /dev/null +++ b/apps/web/src/api/generated/openapi.d.ts @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/health': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 健康检查 + * @description 真实检查 PostgreSQL 与 Redis;任一不可用返回 503 与统一错误体。 + */ + get: operations['HealthController_check']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + ComponentHealthDto: { + /** + * @example up + * @enum {string} + */ + status: 'up' | 'down'; + /** + * @description 检查耗时(毫秒) + * @example 3 + */ + latencyMs: number; + /** @description 失败原因(仅 down 时返回) */ + error?: string; + }; + HealthChecksDto: { + database: components['schemas']['ComponentHealthDto']; + redis: components['schemas']['ComponentHealthDto']; + }; + HealthResponseDto: { + /** + * @example ok + * @enum {string} + */ + status: 'ok' | 'degraded'; + /** @example development */ + env: string; + /** @example 0.1.0 */ + version: string; + /** + * @description 进程运行秒数 + * @example 128 + */ + uptimeSeconds: number; + checks: components['schemas']['HealthChecksDto']; + /** @example 2026-11-02T08:00:00.000Z */ + timestamp: string; + }; + ErrorResponseDto: { + /** + * @description 错误码(见 ErrorCode 常量) + * @example 42202 + */ + code: number; + /** + * @description 可直接展示给用户的提示 + * @example 内容触发合规拦截 + */ + message: string; + /** + * @description 补充信息(字段错误、命中规则等),结构由错误码决定 + * @example { + * "issues": [ + * { + * "path": "name", + * "message": "Required" + * } + * ] + * } + */ + details?: Record; + /** + * @description 请求追踪 ID,与日志同源 + * @example 01JABCDEF0123456789ABCDEFG + */ + trace_id: string; + }; + /** + * @description 业务错误码(只允许使用 @lingxi/shared 中登记的错误码) + * @enum {integer} + */ + ErrorCode: + | 40001 + | 40101 + | 40102 + | 40103 + | 40301 + | 40302 + | 40303 + | 40304 + | 40401 + | 40901 + | 40902 + | 40903 + | 42201 + | 42202 + | 42203 + | 42901 + | 42902 + | 50001 + | 50002 + | 50003; + ErrorResponse: { + code: components['schemas']['ErrorCode']; + message: string; + /** @description 补充信息,结构由错误码决定 */ + details?: unknown; + trace_id: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + HealthController_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 全部依赖健康 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HealthResponseDto']; + }; + }; + /** @description 依赖不可用(错误码 50002),details 内附各依赖检查结果 */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/apps/web/src/api/query-client.ts b/apps/web/src/api/query-client.ts new file mode 100644 index 0000000..8fee4a6 --- /dev/null +++ b/apps/web/src/api/query-client.ts @@ -0,0 +1,37 @@ +import { notifyError } from '@lingxi/ui'; +import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query'; + +import { ApiError } from './client'; + +function reportError(error: unknown): void { + if (error instanceof ApiError) { + if (error.status === 401) { + return; + } + notifyError(error.message, error.traceId); + return; + } + notifyError(error instanceof Error ? error.message : '网络请求失败,请稍后重试'); +} + +function shouldRetry(failureCount: number, error: unknown): boolean { + if (failureCount >= 2) { + return false; + } + if (error instanceof ApiError) { + return error.status >= 500 || error.status === 429; + } + return true; +} + +export const queryClient = new QueryClient({ + queryCache: new QueryCache({ onError: reportError }), + mutationCache: new MutationCache({ onError: reportError }), + defaultOptions: { + queries: { + retry: shouldRetry, + refetchOnWindowFocus: false, + staleTime: 30_000, + }, + }, +}); diff --git a/apps/web/src/layout/AppLayout.tsx b/apps/web/src/layout/AppLayout.tsx new file mode 100644 index 0000000..bc45d99 --- /dev/null +++ b/apps/web/src/layout/AppLayout.tsx @@ -0,0 +1,58 @@ +import { PageSkeleton } from '@lingxi/ui'; +import type { ReactNode } from 'react'; +import { Suspense, useMemo, useState } from 'react'; +import { Navigate, Outlet, useLocation } from 'react-router'; + +import { filterNavByPermissions, findActiveNav, NAV_MODULES } from '@/navigation/nav-config'; +import { ROUTES } from '@/routes'; +import { useAuthStore } from '@/stores/auth-store'; + +import { PageBreadcrumbs } from './PageBreadcrumbs'; +import { SideNav } from './SideNav'; +import { TopBar } from './TopBar'; + +export function AppLayout(): ReactNode { + const location = useLocation(); + const permissions = useAuthStore((state) => state.permissions); + const [collapsed, setCollapsed] = useState(false); + + const modules = useMemo( + () => filterNavByPermissions(NAV_MODULES, new Set(permissions)), + [permissions], + ); + const active = useMemo( + () => findActiveNav(modules, location.pathname), + [modules, location.pathname], + ); + const activeModule = active?.module ?? modules[0]; + + if (!activeModule) { + return ; + } + + return ( +
+ +
+ { + setCollapsed((value) => !value); + }} + /> +
+ +
+ }> + + +
+
+
+
+ ); +} diff --git a/apps/web/src/layout/PageBreadcrumbs.tsx b/apps/web/src/layout/PageBreadcrumbs.tsx new file mode 100644 index 0000000..c672137 --- /dev/null +++ b/apps/web/src/layout/PageBreadcrumbs.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from 'react'; +import { Link } from 'react-router'; + +import { getBreadcrumbs, NAV_MODULES } from '@/navigation/nav-config'; + +export interface PageBreadcrumbsProps { + pathname: string; +} + +export function PageBreadcrumbs({ pathname }: PageBreadcrumbsProps): ReactNode { + const entries = getBreadcrumbs(NAV_MODULES, pathname); + return ( + + ); +} diff --git a/apps/web/src/layout/SideNav.tsx b/apps/web/src/layout/SideNav.tsx new file mode 100644 index 0000000..ee1fa55 --- /dev/null +++ b/apps/web/src/layout/SideNav.tsx @@ -0,0 +1,59 @@ +import { IconBack, IconMenu } from '@lingxi/ui/icons'; +import type { ReactNode } from 'react'; +import { NavLink } from 'react-router'; + +import type { NavModule } from '@/navigation/nav-config'; + +export interface SideNavProps { + module: NavModule; + collapsed: boolean; + onToggle: () => void; +} + +export function SideNav({ module, collapsed, onToggle }: SideNavProps): ReactNode { + return ( + + ); +} diff --git a/apps/web/src/layout/TopBar.tsx b/apps/web/src/layout/TopBar.tsx new file mode 100644 index 0000000..319e1eb --- /dev/null +++ b/apps/web/src/layout/TopBar.tsx @@ -0,0 +1,143 @@ +import { IconChevronDown, IconLogout, IconNotification, IconSearch } from '@lingxi/ui/icons'; +import { type ReactNode, useEffect, useRef, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router'; + +import type { NavModule } from '@/navigation/nav-config'; +import { ROUTES } from '@/routes'; +import { useAuthStore } from '@/stores/auth-store'; + +export interface TopBarProps { + modules: readonly NavModule[]; + activeModuleKey: string; +} + +function ModuleTabs({ + modules, + activeModuleKey, +}: { + modules: readonly NavModule[]; + activeModuleKey: string; +}): ReactNode { + const navigate = useNavigate(); + return ( + + ); +} + +function UserMenu(): ReactNode { + const navigate = useNavigate(); + const location = useLocation(); + const user = useAuthStore((state) => state.user); + const clearSession = useAuthStore((state) => state.clearSession); + const [openPath, setOpenPath] = useState(null); + const menuRef = useRef(null); + const open = openPath === location.pathname; + + useEffect(() => { + const onPointerDown = (event: MouseEvent): void => { + if (menuRef.current !== null && !menuRef.current.contains(event.target as Node)) { + setOpenPath(null); + } + }; + document.addEventListener('mousedown', onPointerDown); + return () => { + document.removeEventListener('mousedown', onPointerDown); + }; + }, []); + + return ( +
+ + { + clearSession(); + void navigate(ROUTES.login, { replace: true }); + }} + /> +
+ ); +} + +function UserMenuDropdown({ + open, + email, + onLogout, +}: { + open: boolean; + email: string; + onLogout: () => void; +}): ReactNode { + if (!open) { + return null; + } + return ( +
+
{email}
+ +
+ ); +} + +export function TopBar({ modules, activeModuleKey }: TopBarProps): ReactNode { + return ( +
+
+ 灵 + + 灵犀 + 素材生产平台 + +
+ + + +
+ + + +
+
+ ); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..b2721a0 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,39 @@ +import '@douyinfe/semi-ui-19/react19-adapter'; +import '@lingxi/ui/semi.css'; +import '@lingxi/ui/theme/tokens.css'; +import '@lingxi/ui/styles.css'; +import '@/styles/global.css'; +import '@/styles/layout.css'; +import '@/styles/pages.css'; + +import { LocaleProvider } from '@douyinfe/semi-ui-19'; +import { localeZhCN } from '@lingxi/ui'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { RouterProvider } from 'react-router'; + +import { setUnauthorizedHandler } from '@/api/client'; +import { queryClient } from '@/api/query-client'; +import { router } from '@/router'; +import { ROUTES } from '@/routes'; + +setUnauthorizedHandler(() => { + void router.navigate(ROUTES.login, { replace: true }); +}); + +const container = document.getElementById('root'); + +if (container === null) { + throw new Error('找不到应用根节点 #root'); +} + +createRoot(container).render( + + + + + + + , +); diff --git a/apps/web/src/navigation/nav-config.spec.ts b/apps/web/src/navigation/nav-config.spec.ts new file mode 100644 index 0000000..d707a8f --- /dev/null +++ b/apps/web/src/navigation/nav-config.spec.ts @@ -0,0 +1,41 @@ +import type { PermissionCode } from '@lingxi/shared'; +import { describe, expect, it } from 'vitest'; + +import { filterNavByPermissions, findActiveNav, getBreadcrumbs, NAV_MODULES } from './nav-config'; + +describe('导航配置', () => { + it('拥有全部权限时展示全部模块与菜单', () => { + const permissions = new Set( + NAV_MODULES.flatMap((module) => module.items.map((item) => item.permission)).filter( + (code): code is PermissionCode => code !== undefined, + ), + ); + const modules = filterNavByPermissions(NAV_MODULES, permissions); + expect(modules.map((module) => module.key)).toEqual(NAV_MODULES.map((module) => module.key)); + }); + + it('缺少权限点时过滤菜单,模块没有子菜单时整组隐藏', () => { + const modules = filterNavByPermissions(NAV_MODULES, new Set(['script:read'])); + const script = modules.find((module) => module.key === 'script'); + expect(script?.items.map((item) => item.key)).toEqual(['library']); + expect(modules.some((module) => module.key === 'platform')).toBe(false); + }); + + it('只有无权限要求的菜单时仍然可见', () => { + const modules = filterNavByPermissions(NAV_MODULES, new Set()); + expect(modules.map((module) => module.key)).toEqual(['dashboard']); + }); + + it('按路径定位当前模块与菜单', () => { + const active = findActiveNav(NAV_MODULES, '/material/quick-review'); + expect(active?.module.key).toBe('material'); + expect(active?.item?.label).toBe('快筛'); + expect(findActiveNav(NAV_MODULES, '/not-exists')).toBeNull(); + }); + + it('面包屑从导航配置派生', () => { + expect(getBreadcrumbs(NAV_MODULES, '/')).toEqual([{ key: 'home', label: '工作台', path: '/' }]); + const breadcrumbs = getBreadcrumbs(NAV_MODULES, '/platform/users'); + expect(breadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '平台', '用户与组织']); + }); +}); diff --git a/apps/web/src/navigation/nav-config.ts b/apps/web/src/navigation/nav-config.ts new file mode 100644 index 0000000..fb4d82a --- /dev/null +++ b/apps/web/src/navigation/nav-config.ts @@ -0,0 +1,410 @@ +import { Permission, type PermissionCode } from '@lingxi/shared'; +import { + IconAsset, + IconCheck, + IconClock, + IconCopy, + IconDelivery, + IconDocs, + IconExport, + IconFilter, + IconGlobal, + IconGrid, + IconHome, + IconImageAdd, + IconMaterial, + IconPlay, + IconRobot, + IconScript, + IconSettings, + IconShield, + IconStack, + IconTask, + IconUser, + IconVideo, + IconView, +} from '@lingxi/ui/icons'; +import type { ComponentType } from 'react'; + +export type NavIcon = ComponentType<{ size?: number | string; className?: string }>; + +export interface NavItem { + key: string; + label: string; + path: string; + icon: NavIcon; + /** 未配置权限点的菜单对所有登录用户可见 */ + permission?: PermissionCode; + description?: string; +} + +export interface NavModule { + key: string; + label: string; + path: string; + icon: NavIcon; + permission?: PermissionCode; + items: NavItem[]; +} + +/** + * 顶部模块菜单 + 左侧功能菜单的唯一配置源(A0-FE-03)。 + * 新增页面时在这里登记;侧边栏与面包屑都从这里派生,禁止在页面内写死导航。 + */ +export const NAV_MODULES: readonly NavModule[] = [ + { + key: 'dashboard', + label: '工作台', + path: '/', + icon: IconHome, + items: [ + { + key: 'overview', + label: '工作台', + path: '/', + icon: IconHome, + description: '今天的生产任务、待审核脚本与最近素材', + }, + ], + }, + { + key: 'script', + label: '脚本', + path: '/scripts/library', + icon: IconScript, + permission: Permission.SCRIPT_READ, + items: [ + { + key: 'library', + label: '脚本库', + path: '/scripts/library', + icon: IconDocs, + permission: Permission.SCRIPT_READ, + description: '按客户、行业与状态检索已生成脚本', + }, + { + key: 'framework-cards', + label: '要素卡', + path: '/scripts/framework-cards', + icon: IconTask, + permission: Permission.SCRIPT_WRITE, + description: '维护行业包的要素卡字段与填写规则', + }, + { + key: 'templates', + label: '脚本模板', + path: '/scripts/templates', + icon: IconStack, + permission: Permission.SCRIPT_WRITE, + description: '结构模板、镜头模板与提示词模板', + }, + { + key: 'batch', + label: '批量生成', + path: '/scripts/batch', + icon: IconRobot, + permission: Permission.SCRIPT_WRITE, + description: '按要素卡批量生成脚本并排队审核', + }, + { + key: 'review', + label: '脚本审核', + path: '/scripts/review', + icon: IconCheck, + permission: Permission.SCRIPT_REVIEW, + description: '审核生成结果,标记低质量脚本', + }, + ], + }, + { + key: 'material', + label: '素材', + path: '/material/image-compose', + icon: IconMaterial, + permission: Permission.MATERIAL_READ, + items: [ + { + key: 'image-compose', + label: '图片合成', + path: '/material/image-compose', + icon: IconImageAdd, + permission: Permission.MATERIAL_RENDER, + description: 'PSD 套版、抠图与确定性合成', + }, + { + key: 'video', + label: '视频生产', + path: '/material/video', + icon: IconVideo, + permission: Permission.MATERIAL_RENDER, + description: '逐镜生成、合成与成片预览', + }, + { + key: 'tasks', + label: '生产任务', + path: '/material/tasks', + icon: IconTask, + permission: Permission.MATERIAL_READ, + description: '任务进度、失败原因与重试', + }, + { + key: 'quick-review', + label: '快筛', + path: '/material/quick-review', + icon: IconFilter, + permission: Permission.MATERIAL_REVIEW, + description: '按快捷键快速筛选候选素材', + }, + { + key: 'preview', + label: '成片预览', + path: '/material/preview', + icon: IconPlay, + permission: Permission.MATERIAL_READ, + description: '播放成片并核对规格与标识', + }, + ], + }, + { + key: 'asset', + label: '资产', + path: '/assets/library', + icon: IconAsset, + permission: Permission.ASSET_READ, + items: [ + { + key: 'library', + label: '资产库', + path: '/assets/library', + icon: IconAsset, + permission: Permission.ASSET_READ, + description: '图片、视频、音频与模板资产', + }, + { + key: 'templates', + label: '模板库', + path: '/assets/templates', + icon: IconCopy, + permission: Permission.ASSET_READ, + description: 'PSD 模板与版式配置', + }, + { + key: 'pools', + label: '人物与场景', + path: '/assets/pools', + icon: IconUser, + permission: Permission.ASSET_READ, + description: '人物、场景与风格池', + }, + { + key: 'prompts', + label: '提示词模板', + path: '/assets/prompts', + icon: IconRobot, + permission: Permission.ASSET_READ, + description: '视频与图片生成的提示词版本', + }, + ], + }, + { + key: 'delivery', + label: '交付', + path: '/delivery/export', + icon: IconDelivery, + permission: Permission.DELIVERY_READ, + items: [ + { + key: 'export', + label: '导出任务', + path: '/delivery/export', + icon: IconExport, + permission: Permission.DELIVERY_EXPORT, + description: '勾选素材导出并按批次归档', + }, + { + key: 'records', + label: '导出记录', + path: '/delivery/records', + icon: IconClock, + permission: Permission.DELIVERY_READ, + description: '导出批次、签名链接与归档状态', + }, + { + key: 'manifests', + label: '交付清单', + path: '/delivery/manifests', + icon: IconTask, + permission: Permission.DELIVERY_READ, + description: '交付清单、客户签收与留痕', + }, + ], + }, + { + key: 'platform', + label: '平台', + path: '/platform/users', + icon: IconSettings, + permission: Permission.USER_MANAGE, + items: [ + { + key: 'users', + label: '用户与组织', + path: '/platform/users', + icon: IconUser, + permission: Permission.USER_MANAGE, + description: '账号、组织与登录状态', + }, + { + key: 'roles', + label: '角色权限', + path: '/platform/roles', + icon: IconShield, + permission: Permission.ROLE_MANAGE, + description: '角色、权限点与职责互斥', + }, + { + key: 'customers', + label: '客户', + path: '/platform/customers', + icon: IconGlobal, + permission: Permission.CUSTOMER_MANAGE, + description: '客户、行业归属与数据范围', + }, + { + key: 'industries', + label: '行业树', + path: '/platform/industries', + icon: IconStack, + permission: Permission.INDUSTRY_MANAGE, + description: '行业分类树与行业包绑定', + }, + { + key: 'dicts', + label: '字典', + path: '/platform/dicts', + icon: IconDocs, + permission: Permission.DICT_MANAGE, + description: '核心字典与业务枚举', + }, + { + key: 'audit', + label: '审计日志', + path: '/platform/audit', + icon: IconView, + permission: Permission.AUDIT_READ, + description: '关键操作留痕与导出记录', + }, + { + key: 'ai', + label: '模型接入', + path: '/platform/ai', + icon: IconRobot, + permission: Permission.AI_CONFIGURE, + description: '供应商、路由与预算阈值', + }, + { + key: 'settings', + label: '系统配置', + path: '/platform/settings', + icon: IconSettings, + permission: Permission.SYSTEM_CONFIGURE, + description: '阈值、上限与功能开关', + }, + { + key: 'industry-packs', + label: '行业包', + path: '/platform/industry-packs', + icon: IconGrid, + permission: Permission.INDUSTRY_MANAGE, + description: '行业包版本、发布与灰度', + }, + { + key: 'forms', + label: '表单设计器', + path: '/platform/forms', + icon: IconGrid, + permission: Permission.FORM_DESIGN, + description: '受控字段白名单与表单布局', + }, + { + key: 'customer-fields', + label: '客户字段', + path: '/platform/customer-fields', + icon: IconTask, + permission: Permission.CUSTOMER_MANAGE, + description: '客户级扩展字段与审批', + }, + { + key: 'compliance', + label: '合规规则', + path: '/platform/compliance', + icon: IconShield, + permission: Permission.SYSTEM_CONFIGURE, + description: '禁忌词、极限词与合规规则集', + }, + ], + }, +]; + +export function filterNavByPermissions( + modules: readonly NavModule[], + permissions: ReadonlySet, +): NavModule[] { + return modules + .map((module) => { + if (module.permission && !permissions.has(module.permission)) { + return null; + } + const items = module.items.filter( + (item) => item.permission === undefined || permissions.has(item.permission), + ); + return items.length > 0 ? { ...module, items } : null; + }) + .filter((module): module is NavModule => module !== null); +} + +export function findNavModule( + modules: readonly NavModule[], + moduleKey: string, +): NavModule | undefined { + return modules.find((module) => module.key === moduleKey); +} + +export interface ActiveNav { + module: NavModule; + item: NavItem | undefined; +} + +export function findActiveNav(modules: readonly NavModule[], pathname: string): ActiveNav | null { + const normalized = pathname !== '/' ? pathname.replace(/\/+$/, '') : pathname; + for (const module of modules) { + const item = module.items.find((candidate) => candidate.path === normalized); + if (item) { + return { module, item }; + } + } + return null; +} + +export interface BreadcrumbEntry { + key: string; + label: string; + path?: string; +} + +export function getBreadcrumbs(modules: readonly NavModule[], pathname: string): BreadcrumbEntry[] { + const active = findActiveNav(modules, pathname); + if (!active) { + return [{ key: 'home', label: '工作台', path: '/' }]; + } + const entries: BreadcrumbEntry[] = [{ key: 'home', label: '工作台', path: '/' }]; + if (active.module.key !== 'dashboard') { + entries.push({ key: active.module.key, label: active.module.label }); + } + if (active.item && active.item.path !== '/' && active.item.key !== active.module.items[0]?.key) { + entries.push({ key: active.item.key, label: active.item.label, path: active.item.path }); + } else if (active.item && active.item.path !== '/') { + entries.push({ key: active.item.key, label: active.item.label, path: active.item.path }); + } + return entries; +} diff --git a/apps/web/src/pages/errors/ErrorLayout.tsx b/apps/web/src/pages/errors/ErrorLayout.tsx new file mode 100644 index 0000000..18f0b6e --- /dev/null +++ b/apps/web/src/pages/errors/ErrorLayout.tsx @@ -0,0 +1,40 @@ +import { IconAlert } from '@lingxi/ui/icons'; +import type { ReactNode } from 'react'; + +export interface ErrorLayoutProps { + code: string; + title: string; + description: string; + traceId?: string; + actions?: ReactNode; +} + +export function ErrorLayout({ + code, + title, + description, + traceId, + actions, +}: ErrorLayoutProps): ReactNode { + return ( +
+ +
+ + + +

灵犀 · 访问异常

+

{title}

+

{description}

+ {traceId !== undefined ? ( +

+ trace_id:{traceId} +

+ ) : null} +
{actions}
+
+
+ ); +} diff --git a/apps/web/src/pages/errors/ForbiddenPage.tsx b/apps/web/src/pages/errors/ForbiddenPage.tsx new file mode 100644 index 0000000..866aad9 --- /dev/null +++ b/apps/web/src/pages/errors/ForbiddenPage.tsx @@ -0,0 +1,38 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; +import { useNavigate } from 'react-router'; + +import { ROUTES } from '@/routes'; + +import { ErrorLayout } from './ErrorLayout'; + +export function ForbiddenPage(): ReactNode { + const navigate = useNavigate(); + return ( + + + + + } + /> + ); +} diff --git a/apps/web/src/pages/errors/NotFoundPage.tsx b/apps/web/src/pages/errors/NotFoundPage.tsx new file mode 100644 index 0000000..5a969ad --- /dev/null +++ b/apps/web/src/pages/errors/NotFoundPage.tsx @@ -0,0 +1,29 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; +import { useNavigate } from 'react-router'; + +import { ROUTES } from '@/routes'; + +import { ErrorLayout } from './ErrorLayout'; + +export function NotFoundPage(): ReactNode { + const navigate = useNavigate(); + return ( + { + void navigate(ROUTES.home); + }} + > + 返回工作台 + + } + /> + ); +} diff --git a/apps/web/src/pages/errors/ServerErrorPage.tsx b/apps/web/src/pages/errors/ServerErrorPage.tsx new file mode 100644 index 0000000..45f3a67 --- /dev/null +++ b/apps/web/src/pages/errors/ServerErrorPage.tsx @@ -0,0 +1,41 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; +import { useNavigate, useSearchParams } from 'react-router'; + +import { ROUTES } from '@/routes'; + +import { ErrorLayout } from './ErrorLayout'; + +export function ServerErrorPage(): ReactNode { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const traceId = searchParams.get('trace_id') ?? undefined; + return ( + + + + + } + /> + ); +} diff --git a/apps/web/src/pages/errors/UnauthorizedPage.tsx b/apps/web/src/pages/errors/UnauthorizedPage.tsx new file mode 100644 index 0000000..844c208 --- /dev/null +++ b/apps/web/src/pages/errors/UnauthorizedPage.tsx @@ -0,0 +1,29 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; +import { useNavigate } from 'react-router'; + +import { ROUTES } from '@/routes'; + +import { ErrorLayout } from './ErrorLayout'; + +export function UnauthorizedPage(): ReactNode { + const navigate = useNavigate(); + return ( + { + void navigate(ROUTES.login); + }} + > + 去登录 + + } + /> + ); +} diff --git a/apps/web/src/pages/home/HomePage.tsx b/apps/web/src/pages/home/HomePage.tsx new file mode 100644 index 0000000..2552f98 --- /dev/null +++ b/apps/web/src/pages/home/HomePage.tsx @@ -0,0 +1,373 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import { + IconChart, + IconChevronRight, + IconClock, + IconDownload, + IconExport, + IconImageAdd, + IconPlay, + IconScript, + IconTask, +} from '@lingxi/ui/icons'; +import type { ReactNode } from 'react'; +import { useNavigate } from 'react-router'; + +import { useAuthStore } from '@/stores/auth-store'; + +interface ProductionTask { + id: string; + name: string; + kind: string; + progress: number; + status: 'running' | 'review' | 'done'; + updatedAt: string; +} + +const TASKS: readonly ProductionTask[] = [ + { + id: 'task-1', + name: '夏日防晒 · 图片合成批次', + kind: '图片合成', + progress: 68, + status: 'running', + updatedAt: '12 分钟前', + }, + { + id: 'task-2', + name: '通勤穿搭 · 视频逐镜生成', + kind: '视频生产', + progress: 42, + status: 'running', + updatedAt: '26 分钟前', + }, + { + id: 'task-3', + name: '会员日 · 脚本批量生成', + kind: '脚本', + progress: 100, + status: 'review', + updatedAt: '1 小时前', + }, + { + id: 'task-4', + name: '春季新品 · 快筛候选集', + kind: '快筛', + progress: 100, + status: 'done', + updatedAt: '今天 10:24', + }, +]; + +const STATUS_LABEL: Record = { + running: '进行中', + review: '待审核', + done: '已完成', +}; + +const QUICK_ACTIONS = [ + { + key: 'script', + label: '新建脚本', + hint: '从要素卡开始', + path: '/scripts/batch', + icon: IconScript, + }, + { + key: 'image', + label: '图片合成', + hint: 'PSD 套版与抠图', + path: '/material/image-compose', + icon: IconImageAdd, + }, + { + key: 'video', + label: '视频生产', + hint: '逐镜生成成片', + path: '/material/video', + icon: IconPlay, + }, + { + key: 'export', + label: '导出素材', + hint: '勾选后导出并归档', + path: '/delivery/export', + icon: IconExport, + }, +] as const; + +const RECENT_ASSETS = [ + { id: 'a1', name: '防晒喷雾 · 主图 01', spec: '1080 × 1920', tone: 1 }, + { id: 'a2', name: '通勤穿搭 · 封面', spec: '1080 × 1920', tone: 2 }, + { id: 'a3', name: '会员日 · 利益点', spec: '1080 × 1920', tone: 3 }, + { id: 'a4', name: '春季新品 · 场景 A', spec: '1080 × 1920', tone: 4 }, + { id: 'a5', name: '防晒喷雾 · 细节', spec: '1080 × 1080', tone: 5 }, + { id: 'a6', name: '通勤穿搭 · 结尾卡', spec: '1080 × 1920', tone: 6 }, +] as const; + +function HomeHero({ userName }: { userName: string }): ReactNode { + const navigate = useNavigate(); + return ( +
+
+

工作台

+

+ {userName},欢迎回来 +

+
+
+ + +
+
+ ); +} + +const METRICS = [ + { + key: 'output', + label: '今日产出', + value: '128', + unit: '条', + foot: '较昨日 +12.4%', + trend: '+12.4%', + icon: IconChart, + }, + { + key: 'running', + label: '进行中任务', + value: '6', + unit: '个', + foot: '2 个排队中 · 预计 4:12 完成', + trend: '', + icon: IconTask, + }, + { + key: 'review', + label: '待审核', + value: '24', + unit: '条', + foot: '8 条已超 2 小时', + trend: '', + icon: IconClock, + }, + { + key: 'export', + label: '本周导出', + value: '512', + unit: '条', + foot: '较上周 +8.1%', + trend: '+8.1%', + icon: IconDownload, + }, +] as const; + +function HomeMetrics(): ReactNode { + return ( +
+ {METRICS.map((metric) => { + const Icon = metric.icon; + return ( +
+
+ {metric.label} + + + +
+

+ {metric.value} + {metric.unit} +

+

+ {metric.trend === '' ? ( + metric.foot + ) : ( + {metric.trend} + )} + {metric.trend === '' ? null : ` · ${metric.foot}`} +

+
+ ); + })} +
+ ); +} + +function TaskRow({ task }: { task: ProductionTask }): ReactNode { + return ( +
+ + {task.name} + + + {task.kind} + + + + + {STATUS_LABEL[task.status]} + + + {task.updatedAt} + +
+ ); +} + +function TaskPanel(): ReactNode { + const navigate = useNavigate(); + return ( +
+
+
+

生产任务

+

按最近更新时间排序

+
+ +
+ +
+
+ 任务 + 类型 + 进度 + 状态 + 更新时间 +
+ {TASKS.map((task) => ( + + ))} +
+
+ ); +} + +function QuickPanel(): ReactNode { + const navigate = useNavigate(); + return ( + + ); +} + +function RecentAssetsPanel(): ReactNode { + const navigate = useNavigate(); + return ( +
+
+
+

最近素材

+

今天产出的图片与视频封面

+
+ +
+ +
+ {RECENT_ASSETS.map((asset) => ( + + ))} +
+
+ ); +} + +export default function HomePage(): ReactNode { + const user = useAuthStore((state) => state.user); + return ( +
+ + +
+ + +
+ +
+ ); +} diff --git a/apps/web/src/pages/login/LoginBrandPanel.tsx b/apps/web/src/pages/login/LoginBrandPanel.tsx new file mode 100644 index 0000000..193dbf7 --- /dev/null +++ b/apps/web/src/pages/login/LoginBrandPanel.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from 'react'; + +export function LoginBrandPanel(): ReactNode { + return ( +
+
+ 灵 + 灵犀 + 素材生产平台 · LINGXI +
+ 内部系统 · 仅限授权成员 +
+ ); +} diff --git a/apps/web/src/pages/login/LoginFormCard.tsx b/apps/web/src/pages/login/LoginFormCard.tsx new file mode 100644 index 0000000..7cd111b --- /dev/null +++ b/apps/web/src/pages/login/LoginFormCard.tsx @@ -0,0 +1,114 @@ +import { Button, Checkbox, Input } from '@douyinfe/semi-ui-19'; +import { IconLock, IconMail } from '@lingxi/ui/icons'; +import type { ReactNode, SyntheticEvent } from 'react'; + +export interface LoginFormCardProps { + account: string; + password: string; + submitting: boolean; + onAccountChange: (value: string) => void; + onPasswordChange: (value: string) => void; + onSubmit: (event: SyntheticEvent) => void; +} + +function LoginFormHeader(): ReactNode { + return ( +
+

登录灵犀

+

使用企业账号登录素材生产平台

+
+ ); +} + +function LoginFormFooter(): ReactNode { + return ( +
+
+ ); +} + +function LoginFields({ + account, + password, + onAccountChange, + onPasswordChange, +}: Pick< + LoginFormCardProps, + 'account' | 'password' | 'onAccountChange' | 'onPasswordChange' +>): ReactNode { + return ( + <> + + + + +
+ 记住我(7 天) + +
+ + ); +} + +export function LoginFormCard({ + account, + password, + submitting, + onAccountChange, + onPasswordChange, + onSubmit, +}: LoginFormCardProps): ReactNode { + return ( +
+ + +
+ + + + + + +
+ ); +} diff --git a/apps/web/src/pages/login/LoginPage.tsx b/apps/web/src/pages/login/LoginPage.tsx new file mode 100644 index 0000000..fea0f3a --- /dev/null +++ b/apps/web/src/pages/login/LoginPage.tsx @@ -0,0 +1,35 @@ +import type { ReactNode } from 'react'; + +import { LoginBrandPanel } from './LoginBrandPanel'; +import { LoginFormCard } from './LoginFormCard'; +import { useLoginForm } from './use-login-form'; + +export default function LoginPage(): ReactNode { + const { account, password, submitting, setAccount, setPassword, handleSubmit } = useLoginForm(); + + return ( +
+ +
+ { + void handleSubmit(event); + }} + /> +
+
+ © 2026 灵犀 · 内部系统 + + 隐私政策 + 服务条款 + 华东一区 + +
+
+ ); +} diff --git a/apps/web/src/pages/login/use-login-form.ts b/apps/web/src/pages/login/use-login-form.ts new file mode 100644 index 0000000..b52dbf7 --- /dev/null +++ b/apps/web/src/pages/login/use-login-form.ts @@ -0,0 +1,70 @@ +import { notifyError, notifyWarning } from '@lingxi/ui'; +import { type SyntheticEvent, useState } from 'react'; +import { useNavigate } from 'react-router'; + +import { ApiError, request } from '@/api/client'; +import { ROUTES } from '@/routes'; +import type { AuthUser } from '@/stores/auth-store'; +import { useAuthStore } from '@/stores/auth-store'; + +interface LoginResponse { + accessToken: string; + refreshToken?: string; + user: AuthUser; + permissions?: string[]; +} + +const demoEnabled = import.meta.env.VITE_PUBLIC_DEMO_SESSION !== 'false'; + +export function useLoginForm(): { + account: string; + password: string; + submitting: boolean; + setAccount: (value: string) => void; + setPassword: (value: string) => void; + handleSubmit: (event: SyntheticEvent) => Promise; +} { + const navigate = useNavigate(); + const signInDemo = useAuthStore((state) => state.signInDemo); + const setSession = useAuthStore((state) => state.setSession); + const [account, setAccount] = useState(''); + const [password, setPassword] = useState(''); + const [submitting, setSubmitting] = useState(false); + + const handleSubmit = async (event: SyntheticEvent): Promise => { + event.preventDefault(); + if (account.trim() === '' || password === '') { + notifyWarning('请输入账号和密码'); + return; + } + setSubmitting(true); + try { + if (demoEnabled) { + signInDemo(); + await navigate(ROUTES.home, { replace: true }); + return; + } + const session = await request('/auth/login', { + method: 'POST', + auth: false, + body: { email: account.trim(), password }, + }); + setSession({ + accessToken: session.accessToken, + refreshToken: session.refreshToken ?? null, + user: session.user, + permissions: session.permissions, + }); + await navigate(ROUTES.home, { replace: true }); + } catch (error) { + notifyError( + error instanceof Error ? error.message : '登录失败,请稍后重试', + error instanceof ApiError ? error.traceId : undefined, + ); + } finally { + setSubmitting(false); + } + }; + + return { account, password, submitting, setAccount, setPassword, handleSubmit }; +} diff --git a/apps/web/src/pages/workspace/WorkspacePage.tsx b/apps/web/src/pages/workspace/WorkspacePage.tsx new file mode 100644 index 0000000..32c6f19 --- /dev/null +++ b/apps/web/src/pages/workspace/WorkspacePage.tsx @@ -0,0 +1,63 @@ +import { PageEmpty } from '@lingxi/ui'; +import type { ReactNode } from 'react'; +import { Link, useParams } from 'react-router'; + +import { + filterNavByPermissions, + findNavModule, + NAV_MODULES, + type NavItem, +} from '@/navigation/nav-config'; +import { ROUTES } from '@/routes'; +import { useAuthStore } from '@/stores/auth-store'; + +function resolvePage( + moduleKey: string | undefined, + itemKey: string | undefined, +): { moduleLabel: string; item: NavItem } | null { + if (moduleKey === undefined) { + return null; + } + const permissions = new Set(useAuthStore.getState().permissions); + const modules = filterNavByPermissions(NAV_MODULES, permissions); + const module = findNavModule(modules, moduleKey); + if (module === undefined) { + return null; + } + const item = module.items.find((candidate) => candidate.key === itemKey) ?? module.items[0]; + return item === undefined ? null : { moduleLabel: module.label, item }; +} + +export default function WorkspacePage(): ReactNode { + const { moduleKey, itemKey } = useParams<{ moduleKey: string; itemKey?: string }>(); + const page = resolvePage(moduleKey, itemKey); + + if (page === null) { + return ( +
+

404

+

页面不存在

+

该功能可能已调整或你没有访问权限。

+ + 返回工作台 + +
+ ); + } + + return ( +
+
+
+

{page.moduleLabel}

+

{page.item.label}

+

{page.item.description}

+
+
+ +
+ +
+
+ ); +} diff --git a/apps/web/src/router.tsx b/apps/web/src/router.tsx new file mode 100644 index 0000000..e6a3a1d --- /dev/null +++ b/apps/web/src/router.tsx @@ -0,0 +1,40 @@ +import { createBrowserRouter } from 'react-router'; + +import { AppLayout } from '@/layout/AppLayout'; +import { ForbiddenPage } from '@/pages/errors/ForbiddenPage'; +import { NotFoundPage } from '@/pages/errors/NotFoundPage'; +import { ServerErrorPage } from '@/pages/errors/ServerErrorPage'; +import { UnauthorizedPage } from '@/pages/errors/UnauthorizedPage'; +import { ROUTES } from '@/routes'; + +export const router = createBrowserRouter([ + { + path: ROUTES.login, + lazy: async () => ({ + Component: (await import('@/pages/login/LoginPage')).default, + }), + }, + { + path: '/', + element: , + children: [ + { + index: true, + lazy: async () => ({ + Component: (await import('@/pages/home/HomePage')).default, + }), + }, + { + path: ':moduleKey/:itemKey?', + lazy: async () => ({ + Component: (await import('@/pages/workspace/WorkspacePage')).default, + }), + }, + ], + }, + { path: ROUTES.unauthorized, element: }, + { path: ROUTES.forbidden, element: }, + { path: ROUTES.notFound, element: }, + { path: ROUTES.serverError, element: }, + { path: '*', element: }, +]); diff --git a/apps/web/src/routes.ts b/apps/web/src/routes.ts new file mode 100644 index 0000000..949c99e --- /dev/null +++ b/apps/web/src/routes.ts @@ -0,0 +1,11 @@ +/** 前端路由常量(工程规范 04:路由集中管理,禁止散落字符串) */ +export const ROUTES = { + home: '/', + login: '/login', + unauthorized: '/401', + forbidden: '/403', + notFound: '/404', + serverError: '/500', +} as const; + +export type RoutePath = (typeof ROUTES)[keyof typeof ROUTES]; diff --git a/apps/web/src/stores/auth-store.ts b/apps/web/src/stores/auth-store.ts new file mode 100644 index 0000000..0baa386 --- /dev/null +++ b/apps/web/src/stores/auth-store.ts @@ -0,0 +1,141 @@ +import { PERMISSION_CODES } from '@lingxi/shared'; +import { create } from 'zustand'; + +export interface AuthUser { + id: string; + name: string; + email: string; + roleName?: string; +} + +export interface AuthSession { + accessToken: string | null; + refreshToken: string | null; + user: AuthUser | null; + permissions: string[]; +} + +export interface SetSessionInput { + accessToken: string; + refreshToken?: string | null; + user: AuthUser; + permissions?: readonly string[]; +} + +export interface AuthState extends AuthSession { + setSession: (input: SetSessionInput) => void; + setTokens: (tokens: { accessToken: string; refreshToken?: string | null }) => void; + clearSession: () => void; + signInDemo: () => void; + hasPermission: (code: string) => boolean; +} + +const STORAGE_KEY = 'lingxi.auth.session'; + +const EMPTY_SESSION: AuthSession = { + accessToken: null, + refreshToken: null, + user: null, + permissions: [], +}; + +export const DEMO_USER: AuthUser = { + id: '01J0DEMO000000000000000000', + name: '灵犀管理员', + email: 'admin@lingxi.local', + roleName: '平台管理员', +}; + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +function isAuthUser(value: unknown): value is AuthUser { + return ( + isRecord(value) && + typeof value.id === 'string' && + typeof value.name === 'string' && + typeof value.email === 'string' + ); +} + +function isAuthSession(value: unknown): value is AuthSession { + return ( + isRecord(value) && + (typeof value.accessToken === 'string' || value.accessToken === null) && + (typeof value.refreshToken === 'string' || value.refreshToken === null) && + (isAuthUser(value.user) || value.user === null) && + Array.isArray(value.permissions) && + value.permissions.every((permission) => typeof permission === 'string') + ); +} + +function readPersistedSession(): AuthSession { + if (typeof window === 'undefined') { + return EMPTY_SESSION; + } + try { + const raw = window.localStorage.getItem(STORAGE_KEY); + if (raw === null) { + return EMPTY_SESSION; + } + const parsed: unknown = JSON.parse(raw); + return isAuthSession(parsed) ? parsed : EMPTY_SESSION; + } catch { + return EMPTY_SESSION; + } +} + +function persistSession(session: AuthSession): void { + if (typeof window === 'undefined') { + return; + } + try { + if (session.accessToken === null) { + window.localStorage.removeItem(STORAGE_KEY); + return; + } + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(session)); + } catch { + // 本地存储不可用(隐私模式等)时退化为内存会话,不影响登录流程 + } +} + +export const useAuthStore = create()((set, get) => ({ + ...readPersistedSession(), + setSession: (input) => { + const session: AuthSession = { + accessToken: input.accessToken, + refreshToken: input.refreshToken ?? null, + user: input.user, + permissions: [...(input.permissions ?? PERMISSION_CODES)], + }; + persistSession(session); + set(session); + }, + setTokens: (tokens) => { + const current = get(); + const session: AuthSession = { + ...current, + accessToken: tokens.accessToken, + refreshToken: tokens.refreshToken ?? current.refreshToken, + }; + persistSession(session); + set({ accessToken: session.accessToken, refreshToken: session.refreshToken }); + }, + clearSession: () => { + persistSession(EMPTY_SESSION); + set(EMPTY_SESSION); + }, + signInDemo: () => { + const session: AuthSession = { + accessToken: 'demo-access-token', + refreshToken: 'demo-refresh-token', + user: DEMO_USER, + permissions: [...PERMISSION_CODES], + }; + persistSession(session); + set(session); + }, + hasPermission: (code) => get().permissions.includes(code), +})); diff --git a/apps/web/src/styles/global.css b/apps/web/src/styles/global.css new file mode 100644 index 0000000..b26928f --- /dev/null +++ b/apps/web/src/styles/global.css @@ -0,0 +1,284 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + font-family: var(--lx-font-sans); + font-size: var(--lx-font-size-md); + line-height: var(--lx-line-height-normal); + color: var(--lx-color-text-primary); + background: var(--lx-color-bg-app); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +h1, +h2, +h3, +h4, +p { + margin: 0; +} + +button, +input, +textarea, +select { + font: inherit; + color: inherit; +} + +button { + padding: 0; + border: none; + background: none; + cursor: pointer; +} + +a { + color: inherit; + text-decoration: none; +} + +ul, +ol { + margin: 0; + padding: 0; + list-style: none; +} + +::selection { + color: #ffffff; + background: var(--lx-color-brand); +} + +:focus-visible { + outline: 2px solid var(--lx-color-focus); + outline-offset: 2px; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + border: 2px solid transparent; + border-radius: var(--lx-radius-pill); + background: var(--lx-color-border-strong); + background-clip: content-box; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +/* ---------- 基础排版 ---------- */ + +.lx-eyebrow { + font-size: 11px; + font-weight: 600; + line-height: 1; + letter-spacing: 0.08em; + color: var(--lx-color-text-tertiary); +} + +.lx-link-button { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-secondary); + transition: color var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-link-button:hover { + color: var(--lx-color-brand); +} + +/* ---------- 面板 ---------- */ + +.lx-panel { + display: flex; + flex-direction: column; + gap: var(--lx-space-4); + padding: var(--lx-space-5); + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: var(--lx-color-bg-panel); + box-shadow: var(--lx-shadow-panel); +} + +.lx-panel__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--lx-space-4); +} + +.lx-panel__head h2 { + font-size: var(--lx-font-size-lg); + font-weight: 600; + line-height: var(--lx-line-height-tight); +} + +.lx-panel__head p { + margin-top: 4px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +/* ---------- 标签 / 进度 ---------- */ + +.lx-chip { + display: inline-flex; + align-items: center; + gap: 4px; + height: 22px; + padding: 0 8px; + border-radius: var(--lx-radius-sm); + font-size: var(--lx-font-size-xs); + font-weight: 500; + white-space: nowrap; +} + +.lx-chip::before { + width: 5px; + height: 5px; + border-radius: 50%; + background: currentColor; + content: ''; +} + +.lx-chip--running { + color: var(--lx-color-brand); + background: var(--lx-color-brand-soft); +} + +.lx-chip--review { + color: var(--lx-color-warning); + background: #fff4e8; +} + +.lx-chip--done { + color: var(--lx-color-success); + background: #e8f8ec; +} + +.lx-chip--failed { + color: var(--lx-color-danger); + background: #ffecec; +} + +.lx-progress { + position: relative; + display: block; + width: 96px; + height: 6px; + overflow: hidden; + border-radius: var(--lx-radius-pill); + background: #eef1f5; +} + +.lx-progress__bar { + position: absolute; + inset: 0 auto 0 0; + border-radius: inherit; + background: var(--lx-color-brand); +} + +.lx-progress__value { + width: 40px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-secondary); + text-align: right; + font-variant-numeric: tabular-nums; +} + +/* ---------- 表格 ---------- */ + +.lx-table { + overflow: hidden; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: var(--lx-color-bg-panel); +} + +.lx-table__head, +.lx-table__row { + display: grid; + grid-template-columns: minmax(240px, 1.6fr) 110px 170px 110px 120px; + align-items: center; + gap: var(--lx-space-4); + padding: 0 var(--lx-space-4); +} + +.lx-table__head { + height: 40px; + border-bottom: 1px solid var(--lx-color-border); + background: var(--lx-color-bg-subtle); + font-size: var(--lx-font-size-xs); + font-weight: 500; + color: var(--lx-color-text-tertiary); +} + +.lx-table__row { + min-height: 52px; + border-bottom: 1px solid var(--lx-color-border); + font-size: var(--lx-font-size-sm); + transition: background var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-table__row:last-child { + border-bottom: none; +} + +.lx-table__row:hover { + background: var(--lx-color-bg-subtle); +} + +.lx-table__name { + overflow: hidden; + font-weight: 500; + color: var(--lx-color-text-primary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lx-table__muted { + color: var(--lx-color-text-secondary); + font-variant-numeric: tabular-nums; +} + +.lx-table__progress { + display: flex; + align-items: center; + gap: var(--lx-space-2); +} + +/* ---------- 字段 ---------- */ + +.lx-field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.lx-field__label { + font-size: var(--lx-font-size-sm); + font-weight: 500; + color: var(--lx-color-text-primary); +} + +.lx-field .semi-input-wrapper { + border-radius: var(--lx-radius-md); +} diff --git a/apps/web/src/styles/layout.css b/apps/web/src/styles/layout.css new file mode 100644 index 0000000..914a74e --- /dev/null +++ b/apps/web/src/styles/layout.css @@ -0,0 +1,475 @@ +/* 方向 A · 云白蓝:白顶栏 + 左栏 + 浅灰工作区 */ + +.lx-shell { + display: flex; + height: 100%; + min-height: 0; + flex-direction: column; + background: var(--lx-color-bg-app); +} + +/* ---------- 顶栏 ---------- */ + +.lx-topbar { + z-index: 20; + display: flex; + flex: 0 0 var(--lx-topbar-height); + align-items: center; + gap: var(--lx-space-6); + height: var(--lx-topbar-height); + padding: 0 var(--lx-space-6) 0 var(--lx-space-5); + border-bottom: 1px solid var(--lx-color-border); + background: var(--lx-color-bg-panel); +} + +.lx-topbar__brand { + display: flex; + min-width: 184px; + align-items: center; + gap: 10px; +} + +.lx-topbar__mark { + display: inline-flex; + width: 34px; + height: 34px; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient(135deg, #1677ff, #4096ff); + color: #ffffff; + font-size: 18px; + font-weight: 700; + line-height: 1; +} + +.lx-topbar__name { + font-size: var(--lx-font-size-lg); + font-weight: 600; + line-height: 1.15; + color: var(--lx-color-text-primary); +} + +.lx-topbar__tag { + margin-top: 1px; + font-size: 10px; + line-height: 1.2; + color: var(--lx-color-text-tertiary); +} + +.lx-topbar__brand-text { + display: flex; + flex-direction: column; + line-height: 1.25; +} + +.lx-topbar__modules { + display: flex; + height: 100%; + align-items: stretch; + gap: var(--lx-space-1); +} + +.lx-topbar__module { + position: relative; + padding: 0 14px; + font-size: var(--lx-font-size-md); + color: var(--lx-color-text-secondary); + transition: color var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-topbar__module:hover { + color: var(--lx-color-text-primary); +} + +.lx-topbar__module.is-active { + color: var(--lx-color-brand); + font-weight: 600; +} + +.lx-topbar__module.is-active::after { + position: absolute; + right: 12px; + bottom: 0; + left: 12px; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--lx-color-brand); + content: ''; +} + +.lx-topbar__actions { + display: flex; + margin-left: auto; + align-items: center; + gap: var(--lx-space-2); +} + +.lx-icon-button { + position: relative; + display: inline-flex; + width: 32px; + height: 32px; + align-items: center; + justify-content: center; + border-radius: var(--lx-radius-md); + color: var(--lx-color-text-secondary); + transition: + color var(--lx-motion-fast) var(--lx-ease-out), + background var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-icon-button:hover { + color: var(--lx-color-text-primary); + background: var(--lx-color-bg-hover); +} + +.lx-icon-button__dot { + position: absolute; + top: 7px; + right: 7px; + width: 6px; + height: 6px; + border: 1.5px solid var(--lx-color-bg-panel); + border-radius: 50%; + background: var(--lx-color-danger); +} + +/* ---------- 用户菜单 ---------- */ + +.lx-user { + position: relative; +} + +.lx-user__trigger { + display: flex; + height: 40px; + align-items: center; + gap: 8px; + padding: 0 6px 0 4px; + border-radius: var(--lx-radius-md); + transition: background var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-user__trigger:hover { + background: var(--lx-color-bg-hover); +} + +.lx-user__avatar { + display: inline-flex; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--lx-color-brand-soft); + color: var(--lx-color-brand); + font-size: var(--lx-font-size-sm); + font-weight: 600; +} + +.lx-user__meta { + display: flex; + min-width: 0; + flex-direction: column; + align-items: flex-start; + line-height: 1.25; +} + +.lx-user__name { + font-size: var(--lx-font-size-sm); + font-weight: 500; + color: var(--lx-color-text-primary); +} + +.lx-user__role { + font-size: 11px; + color: var(--lx-color-text-tertiary); +} + +.lx-user__chevron { + color: var(--lx-color-text-tertiary); +} + +.lx-user__menu { + position: absolute; + top: calc(100% + 6px); + right: 0; + width: 208px; + padding: 6px; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: var(--lx-color-bg-elevated); + box-shadow: var(--lx-shadow-overlay); +} + +.lx-user__menu-header { + padding: 8px 10px; + border-bottom: 1px solid var(--lx-color-border); + margin-bottom: 4px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.lx-user__menu-item { + display: flex; + width: 100%; + height: 34px; + align-items: center; + gap: 8px; + padding: 0 10px; + border-radius: var(--lx-radius-sm); + font-size: var(--lx-font-size-sm); + color: var(--lx-color-text-primary); + text-align: left; +} + +.lx-user__menu-item:hover { + background: var(--lx-color-bg-hover); +} + +/* ---------- 主体 ---------- */ + +.lx-shell__body { + display: flex; + flex: 1; + min-height: 0; +} + +.lx-sidebar { + display: flex; + flex: 0 0 var(--lx-sidebar-width); + width: var(--lx-sidebar-width); + min-height: 0; + flex-direction: column; + border-right: 1px solid var(--lx-color-border); + background: var(--lx-color-bg-panel); + transition: width var(--lx-motion-base) var(--lx-ease-out); +} + +.lx-shell--collapsed .lx-sidebar { + flex-basis: var(--lx-sidebar-collapsed-width); + width: var(--lx-sidebar-collapsed-width); +} + +.lx-sidebar__heading { + display: flex; + height: 48px; + align-items: center; + justify-content: space-between; + padding: 0 var(--lx-space-4) 0 var(--lx-space-5); +} + +.lx-sidebar__title { + font-size: var(--lx-font-size-sm); + font-weight: 600; + color: var(--lx-color-text-primary); +} + +.lx-sidebar__toggle { + display: inline-flex; + width: 24px; + height: 24px; + align-items: center; + justify-content: center; + border-radius: var(--lx-radius-sm); + color: var(--lx-color-text-tertiary); +} + +.lx-sidebar__toggle:hover { + color: var(--lx-color-text-primary); + background: var(--lx-color-bg-hover); +} + +.lx-sidebar__nav { + display: flex; + flex: 1; + flex-direction: column; + gap: 2px; + padding: 0 10px; + overflow-y: auto; +} + +.lx-sidebar__item { + display: flex; + min-height: 38px; + align-items: center; + gap: 10px; + padding: 0 12px; + border-radius: var(--lx-radius-md); + color: var(--lx-color-text-secondary); + font-size: var(--lx-font-size-sm); + transition: + color var(--lx-motion-fast) var(--lx-ease-out), + background var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-sidebar__item:hover { + color: var(--lx-color-text-primary); + background: var(--lx-color-bg-hover); +} + +.lx-sidebar__item.is-active { + color: var(--lx-color-brand); + background: var(--lx-color-brand-soft); + font-weight: 500; +} + +.lx-sidebar__item-icon { + display: inline-flex; + flex: 0 0 18px; +} + +.lx-sidebar__item-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.lx-sidebar__footer { + padding: var(--lx-space-4) var(--lx-space-5); + border-top: 1px solid var(--lx-color-border); +} + +.lx-sidebar__env { + display: flex; + align-items: center; + gap: 6px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-sidebar__env-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--lx-color-success); +} + +.lx-shell--collapsed .lx-sidebar__title, +.lx-shell--collapsed .lx-sidebar__item-label, +.lx-shell--collapsed .lx-sidebar__env-text { + display: none; +} + +.lx-shell--collapsed .lx-sidebar__heading { + justify-content: center; + padding: 0; +} + +.lx-shell--collapsed .lx-sidebar__item { + justify-content: center; + padding: 0; +} + +.lx-shell--collapsed .lx-sidebar__footer { + padding: var(--lx-space-4) 0; +} + +/* ---------- 工作区 ---------- */ + +.lx-workspace { + display: flex; + flex: 1; + min-width: 0; + min-height: 0; + flex-direction: column; + overflow: hidden; +} + +.lx-breadcrumbs { + display: flex; + flex: 0 0 44px; + height: 44px; + align-items: center; + gap: 6px; + padding: 0 var(--lx-content-padding); + border-bottom: 1px solid var(--lx-color-border); + background: var(--lx-color-bg-panel); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-breadcrumbs__entry { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.lx-breadcrumbs__link { + color: var(--lx-color-text-tertiary); +} + +.lx-breadcrumbs__link:hover { + color: var(--lx-color-brand); +} + +.lx-breadcrumbs__current { + color: var(--lx-color-text-primary); + font-weight: 500; +} + +.lx-breadcrumbs__sep { + color: #c9cdd4; +} + +.lx-page { + flex: 1; + min-height: 0; + padding: var(--lx-content-padding); + overflow-y: auto; +} + +@media (width <= 1080px) { + .lx-topbar__tag { + display: none; + } + + .lx-sidebar { + flex-basis: var(--lx-sidebar-collapsed-width); + width: var(--lx-sidebar-collapsed-width); + } + + .lx-sidebar__title, + .lx-sidebar__item-label, + .lx-sidebar__env-text { + display: none; + } + + .lx-sidebar__heading { + justify-content: center; + padding: 0; + } + + .lx-sidebar__item { + justify-content: center; + padding: 0; + } +} + +@media (width <= 900px) { + .lx-topbar { + gap: var(--lx-space-3); + padding: 0 var(--lx-space-4); + } + + .lx-topbar__brand { + min-width: auto; + } + + .lx-topbar__tag { + display: none; + } + + .lx-user__meta { + display: none; + } + + .lx-content-padding, + .lx-page { + padding: var(--lx-space-4); + } +} diff --git a/apps/web/src/styles/pages.css b/apps/web/src/styles/pages.css new file mode 100644 index 0000000..dac6780 --- /dev/null +++ b/apps/web/src/styles/pages.css @@ -0,0 +1,551 @@ +/* 方向 A · 云白蓝:登录页与业务页 */ + +/* ---------- 登录页 ---------- */ + +.lx-login { + position: relative; + display: flex; + min-height: 100%; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 96px var(--lx-space-6) 72px; + background: + radial-gradient(circle at 50% 0%, #eef5ff 0%, transparent 46%), var(--lx-color-bg-app); +} + +.lx-login__topbar { + position: absolute; + top: 0; + right: 0; + left: 0; + display: flex; + height: 72px; + align-items: center; + justify-content: space-between; + padding: 0 var(--lx-space-8); +} + +.lx-login__brand { + display: flex; + align-items: center; + gap: 10px; +} + +.lx-login__mark { + display: inline-flex; + width: 34px; + height: 34px; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient(135deg, #1677ff, #4096ff); + color: #ffffff; + font-size: 18px; + font-weight: 700; +} + +.lx-login__wordmark { + font-size: var(--lx-font-size-lg); + font-weight: 600; + color: var(--lx-color-text-primary); +} + +.lx-login__brand-sub { + font-size: 11px; + color: var(--lx-color-text-tertiary); + letter-spacing: 0.06em; +} + +.lx-login__badge { + display: inline-flex; + align-items: center; + gap: 6px; + height: 28px; + padding: 0 10px; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-sm); + background: var(--lx-color-bg-panel); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-secondary); +} + +.lx-login__badge::before { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--lx-color-success); + content: ''; +} + +.lx-login__panel { + width: 100%; + max-width: 400px; + padding: 32px; + border: 1px solid var(--lx-color-border); + border-radius: 8px; + background: var(--lx-color-bg-panel); + box-shadow: 0 8px 28px rgba(31, 35, 41, 0.08); +} + +.lx-login__form { + display: flex; + flex-direction: column; + gap: var(--lx-space-5); +} + +.lx-login__form-head h2 { + font-size: 24px; + font-weight: 600; + line-height: 1.3; + color: var(--lx-color-text-primary); +} + +.lx-login__form-head p { + margin-top: 6px; + font-size: var(--lx-font-size-sm); + color: var(--lx-color-text-tertiary); +} + +.lx-login__form form { + display: flex; + flex-direction: column; + gap: var(--lx-space-4); +} + +.lx-login__form-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--lx-space-3); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-login__form-foot { + display: flex; + align-items: flex-start; + gap: 6px; + padding-top: var(--lx-space-4); + border-top: 1px solid var(--lx-color-border); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-login__form-foot-dot { + width: 5px; + height: 5px; + flex: 0 0 5px; + margin-top: 6px; + border-radius: 50%; + background: var(--lx-color-border-strong); +} + +.lx-login__footer { + position: absolute; + right: 0; + bottom: 0; + left: 0; + display: flex; + height: 56px; + align-items: center; + justify-content: space-between; + padding: 0 var(--lx-space-8); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-login__footer-links { + display: flex; + gap: var(--lx-space-4); +} + +/* ---------- 工作台 ---------- */ + +.lx-home { + display: flex; + flex-direction: column; + gap: var(--lx-space-4); +} + +.lx-home__hero { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: var(--lx-space-4); +} + +.lx-home__hero h1 { + font-size: 24px; + font-weight: 600; + line-height: 1.3; +} + +.lx-home__hero-meta { + display: flex; + margin-top: 6px; + align-items: center; + gap: 8px; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-home__hero-dot { + width: 3px; + height: 3px; + border-radius: 50%; + background: var(--lx-color-border-strong); +} + +.lx-home__hero-actions { + display: flex; + gap: var(--lx-space-2); +} + +.lx-metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--lx-space-4); +} + +.lx-metric { + display: flex; + flex-direction: column; + gap: 10px; + padding: 18px 20px; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: var(--lx-color-bg-panel); + box-shadow: var(--lx-shadow-panel); +} + +.lx-metric__head { + display: flex; + align-items: center; + justify-content: space-between; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-secondary); +} + +.lx-metric__icon { + display: inline-flex; + width: 28px; + height: 28px; + align-items: center; + justify-content: center; + border-radius: var(--lx-radius-sm); + background: var(--lx-color-brand-soft); + color: var(--lx-color-brand); +} + +.lx-metric__value { + display: flex; + align-items: baseline; + gap: 4px; + font-size: 28px; + font-weight: 600; + line-height: 1; + color: var(--lx-color-text-primary); + font-variant-numeric: tabular-nums; +} + +.lx-metric__unit { + font-size: var(--lx-font-size-xs); + font-weight: 400; + color: var(--lx-color-text-tertiary); +} + +.lx-metric__foot { + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-metric__trend { + color: var(--lx-color-success); +} + +.lx-home__grid { + display: grid; + grid-template-columns: minmax(0, 1fr) 316px; + align-items: start; + gap: var(--lx-space-4); +} + +.lx-home__tasks { + min-width: 0; +} + +.lx-home__quick { + gap: var(--lx-space-3); +} + +.lx-quick-list { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--lx-space-2); +} + +.lx-quick-item { + display: flex; + min-width: 0; + flex-direction: column; + align-items: flex-start; + gap: 8px; + padding: 12px; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: var(--lx-color-bg-panel); + text-align: left; + transition: + border-color var(--lx-motion-fast) var(--lx-ease-out), + background var(--lx-motion-fast) var(--lx-ease-out); +} + +.lx-quick-item:hover { + border-color: var(--lx-color-brand); + background: #f8fbff; +} + +.lx-quick-item__icon { + display: inline-flex; + width: 30px; + height: 30px; + flex: 0 0 30px; + align-items: center; + justify-content: center; + border-radius: var(--lx-radius-sm); + background: var(--lx-color-brand-soft); + color: var(--lx-color-brand); +} + +.lx-quick-item__copy { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.lx-quick-item__label { + font-size: var(--lx-font-size-sm); + font-weight: 500; + color: var(--lx-color-text-primary); + white-space: nowrap; +} + +.lx-quick-item__hint { + overflow: hidden; + font-size: 11px; + color: var(--lx-color-text-tertiary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lx-quick-item__arrow { + display: none; +} + +.lx-home__recent { + min-width: 0; +} + +.lx-asset-grid { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: var(--lx-space-3); +} + +.lx-asset-tile { + display: flex; + min-width: 0; + flex-direction: column; + gap: 8px; + text-align: left; +} + +.lx-asset-tile__preview { + display: block; + aspect-ratio: 3 / 4; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + background: #eef2f7; +} + +.lx-asset-tile__preview--1 { + background: linear-gradient(150deg, #dbeafe, #93c5fd 55%, #60a5fa); +} + +.lx-asset-tile__preview--2 { + background: linear-gradient(150deg, #e0f2fe, #bae6fd 55%, #7dd3fc); +} + +.lx-asset-tile__preview--3 { + background: linear-gradient(150deg, #fef3c7, #fde68a 55%, #fcd34d); +} + +.lx-asset-tile__preview--4 { + background: linear-gradient(150deg, #dcfce7, #bbf7d0 55%, #86efac); +} + +.lx-asset-tile__preview--5 { + background: linear-gradient(150deg, #fae8ff, #f5d0fe 55%, #e9a8f5); +} + +.lx-asset-tile__preview--6 { + background: linear-gradient(150deg, #ffe4e6, #fecdd3 55%, #fda4af); +} + +.lx-asset-tile__meta { + display: flex; + flex-direction: column; + min-width: 0; +} + +.lx-asset-tile__name { + overflow: hidden; + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-primary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lx-asset-tile__spec { + font-size: 11px; + color: var(--lx-color-text-tertiary); +} + +/* ---------- 占位工作区 / 错误页 ---------- */ + +.lx-page-header { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: var(--lx-space-4); +} + +.lx-page-header h1 { + font-size: 22px; + font-weight: 600; +} + +.lx-page-header p { + font-size: var(--lx-font-size-sm); + color: var(--lx-color-text-tertiary); +} + +.lx-panel--empty { + align-items: center; + justify-content: center; + min-height: 320px; +} + +.lx-workspace-missing { + display: flex; + min-height: 320px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--lx-space-3); + color: var(--lx-color-text-secondary); +} + +.lx-workspace-missing strong { + font-size: 40px; + font-weight: 600; + color: var(--lx-color-text-tertiary); +} + +.lx-error-page { + display: flex; + min-height: 70vh; + align-items: center; + justify-content: center; + padding: var(--lx-space-8); +} + +.lx-error-page__body { + display: flex; + max-width: 480px; + flex-direction: column; + align-items: center; + text-align: center; +} + +.lx-error-page__icon { + display: inline-flex; + width: 56px; + height: 56px; + align-items: center; + justify-content: center; + border-radius: 14px; + background: var(--lx-color-brand-soft); + color: var(--lx-color-brand); +} + +.lx-error-page__code { + margin-top: var(--lx-space-4); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.12em; + color: var(--lx-color-text-tertiary); +} + +.lx-error-page__title { + margin-top: 6px; + font-size: 24px; + font-weight: 600; +} + +.lx-error-page__description { + margin-top: 8px; + color: var(--lx-color-text-secondary); +} + +.lx-error-page__trace { + margin-top: 12px; + padding: 6px 10px; + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-sm); + background: var(--lx-color-bg-panel); + font-size: var(--lx-font-size-xs); + color: var(--lx-color-text-tertiary); +} + +.lx-error-page__trace code { + font-family: var(--lx-font-mono); + color: var(--lx-color-text-secondary); +} + +.lx-error-page__actions { + display: flex; + margin-top: var(--lx-space-5); + gap: var(--lx-space-2); +} + +@media (width <= 1200px) { + .lx-metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lx-home__grid { + grid-template-columns: minmax(0, 1fr); + } + + .lx-asset-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@media (width <= 720px) { + .lx-metrics { + grid-template-columns: minmax(0, 1fr); + } + + .lx-asset-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lx-home__hero { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts new file mode 100644 index 0000000..afae500 --- /dev/null +++ b/apps/web/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +interface ImportMetaEnv { + readonly VITE_PUBLIC_API_BASE_URL?: string; + readonly VITE_PUBLIC_DEMO_SESSION?: string; +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..a8e8c04 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client", "node"], + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src", "vite.config.ts", "vitest.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..adb95c8 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,27 @@ +import { fileURLToPath, URL } from 'node:url'; + +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + host: '127.0.0.1', + port: 5173, + strictPort: true, + }, + preview: { + host: '127.0.0.1', + port: 4173, + strictPort: true, + }, + build: { + target: 'es2022', + sourcemap: true, + }, +}); diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 0000000..cc613b5 --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,29 @@ +import { fileURLToPath, URL } from 'node:url'; + +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + test: { + environment: 'jsdom', + include: ['src/**/*.spec.{ts,tsx}'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'json-summary'], + include: ['src/api/**/*.ts', 'src/stores/**/*.ts', 'src/navigation/**/*.ts'], + exclude: ['src/api/generated/**', 'src/**/*.spec.{ts,tsx}'], + thresholds: { + lines: 70, + functions: 70, + statements: 70, + branches: 60, + }, + }, + }, +}); diff --git a/apps/worker/package.json b/apps/worker/package.json new file mode 100644 index 0000000..fb216f0 --- /dev/null +++ b/apps/worker/package.json @@ -0,0 +1,23 @@ +{ + "name": "@lingxi/worker", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "tsx watch src/main.ts", + "build": "tsc -p tsconfig.build.json", + "start": "node dist/main.js", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@lingxi/shared": "workspace:*", + "dotenv": "18.0.9", + "ioredis": "5.11.1", + "pino": "10.4.0", + "zod": "3.25.76" + }, + "devDependencies": { + "@types/node": "24.19.2", + "tsx": "4.23.15", + "typescript": "5.9.3" + } +} diff --git a/apps/worker/src/config.ts b/apps/worker/src/config.ts new file mode 100644 index 0000000..9da7a2e --- /dev/null +++ b/apps/worker/src/config.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), + REDIS_URL: z + .string() + .min(1) + .refine( + (value) => value.startsWith('redis://') || value.startsWith('rediss://'), + 'REDIS_URL 必须是 redis:// 连接串', + ), + QUEUE: z.string().min(1).default('default'), + WORKER_CONCURRENCY: z.coerce.number().int().min(1).max(64).default(2), +}); + +export interface WorkerConfig { + nodeEnv: 'development' | 'test' | 'production'; + logLevel: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; + redisUrl: string; + queue: string; + concurrency: number; +} + +export function loadWorkerConfig(source: NodeJS.ProcessEnv = process.env): WorkerConfig { + const result = envSchema.safeParse(source); + if (!result.success) { + const details = result.error.issues + .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`) + .join('; '); + throw new Error(`Worker 环境变量校验失败:${details}`); + } + return { + nodeEnv: result.data.NODE_ENV, + logLevel: result.data.LOG_LEVEL, + redisUrl: result.data.REDIS_URL, + queue: result.data.QUEUE, + concurrency: result.data.WORKER_CONCURRENCY, + }; +} diff --git a/apps/worker/src/main.ts b/apps/worker/src/main.ts new file mode 100644 index 0000000..064903a --- /dev/null +++ b/apps/worker/src/main.ts @@ -0,0 +1,62 @@ +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { config as loadDotenvFile } from 'dotenv'; +import Redis from 'ioredis'; +import pino from 'pino'; + +import { loadWorkerConfig } from './config'; + +function loadDotenv(): void { + for (const file of ['.env.local', '.env']) { + const path = resolve(process.cwd(), file); + if (existsSync(path)) { + loadDotenvFile({ path }); + } + } +} + +async function main(): Promise { + loadDotenv(); + const config = loadWorkerConfig(); + const logger = pino({ level: config.logLevel, base: { service: 'lingxi-worker' } }); + + const redis = new Redis(config.redisUrl, { + lazyConnect: true, + maxRetriesPerRequest: null, + enableOfflineQueue: false, + }); + redis.on('error', (error: Error) => { + logger.error({ err: error }, 'redis connection error'); + }); + + await redis.connect(); + await redis.ping(); + logger.info({ queue: config.queue, concurrency: config.concurrency }, 'worker 已启动,等待任务'); + + // A0 只验证进程与依赖可用;BullMQ 消费者在 M1 批次接入(QUEUE 环境变量决定队列) + const heartbeat = setInterval(() => { + logger.debug({ queue: config.queue }, 'worker heartbeat'); + }, 30_000); + heartbeat.unref(); + + const shutdown = async (signal: string): Promise => { + clearInterval(heartbeat); + logger.info({ signal }, 'worker 正在退出'); + await redis.quit(); + }; + + process.on('SIGINT', () => { + void shutdown('SIGINT'); + }); + process.on('SIGTERM', () => { + void shutdown('SIGTERM'); + }); +} + +void main().catch((error: unknown) => { + process.stderr.write( + `Worker 启动失败:${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`, + ); + process.exitCode = 1; +}); diff --git a/apps/worker/tsconfig.build.json b/apps/worker/tsconfig.build.json new file mode 100644 index 0000000..1ea0982 --- /dev/null +++ b/apps/worker/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "dist", + "rootDir": "src", + "declaration": false, + "declarationMap": false, + "sourceMap": true + }, + "include": ["src"] +} diff --git a/apps/worker/tsconfig.json b/apps/worker/tsconfig.json new file mode 100644 index 0000000..4f25ee2 --- /dev/null +++ b/apps/worker/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "lib": ["ES2022"], + "types": ["node"], + "noEmit": true + }, + "include": ["src"] +} diff --git a/commitlint.config.mjs b/commitlint.config.mjs new file mode 100644 index 0000000..7c2a03c --- /dev/null +++ b/commitlint.config.mjs @@ -0,0 +1,27 @@ +/** @type {import('@commitlint/types').UserConfig} */ +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'docs', + 'style', + 'refactor', + 'perf', + 'test', + 'build', + 'ci', + 'chore', + 'revert', + ], + ], + 'subject-case': [0], + 'subject-empty': [2, 'never'], + 'header-max-length': [2, 'always', 120], + 'body-max-line-length': [0], + }, +}; diff --git a/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md b/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md new file mode 100644 index 0000000..1244553 --- /dev/null +++ b/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md @@ -0,0 +1,101 @@ +# 灵犀 · A0 工程骨架与契约基线 实施报告 V0.1 + +> 对应开发计划:`docs/开发计划/灵犀_信息流素材生产平台_开发计划_V0.1.md` §6.1「A0 工程骨架与契约基线」 +> 实施分支:`feat/a0-skeleton`(基于 `docs/dev-plan-v0.1`) +> 日期:2026-11-02 + +## 1. 完成范围 + +| WBS | 状态 | 落地内容 | +| --- | --- | --- | +| A0-INF-01 Monorepo / 工具链 | 完成 | pnpm workspace + Turborepo;TS strict、ESLint 9 flat + Prettier + commitlint + lint-staged + husky;`.nvmrc` / `engines` / `packageManager` / preinstall 仅允许 pnpm;目录与 AGENTS.md §5 一致 | +| A0-INF-02 本地依赖与迁移种子 | 完成(种子范围受限,见 §4.4) | `infra/docker-compose.dev.yaml`(PG 18 / Redis 7.4 / S3 兼容服务)+ 健康检查;`.env.example`;Prisma 初始化与首版迁移;`system_configs` 幂等种子 | +| A0-INF-03 CI 与质量门禁 | 完成 | `.gitea/workflows/ci.yml`:install → format → lint → typecheck → test → build → migrate → test:int → openapi:export/diff → e2e;产物归档;失败阻断 | +| A0-BE-01 API 骨架 | 完成 | NestJS 11 + Fastify;Pino + `trace_id`;统一错误体 `{code,message,details,trace_id}`;`GET /health`;`/api/docs` 与 OpenAPI JSON;Zod 管道;CORS / Helmet | +| A0-BE-02 Prisma 基线 | 完成 | `PrismaService` 封装;`outbox_events` / `idempotency_keys` / `system_configs`;ULID `char(26)`、软删/时间字段约定;首版迁移可重复执行 | +| A0-SH-01 shared 包 | 完成 | 20 个错误码(40001–50003)、权限点常量与 `模块:动作` 规范、ULID、游标分页、时间/命名工具、通用 Zod schema;前后端共同引用 | +| A0-FE-01 Web 骨架 | 完成 | Vite 6 + React 19 + Router 7 + TanStack Query 5 + Zustand 5;Semi `@douyinfe/semi-ui-19` 与 React19 适配入口;路径别名;fetch 请求层(token 注入、单飞 401 刷新、统一错误提示) | +| A0-FE-02 UI 包基线 | 完成 | Semi 二次封装(表格/查询表单/弹窗/空态/错误态/加载骨架);`icons.ts` 统一图标出口;locale;主题 token;ESLint 拦截越过出口的图标/UI 库与内联色值 | +| A0-FE-03 布局与登录壳 | 完成 | 顶部模块菜单 + 左侧功能菜单 + 面包屑;用户菜单、任务角标占位;401/403/404/500 页;登录页;导航集中配置并按权限过滤 | +| A0-QA-01 冒烟与报告 | 完成 | 健康检查冒烟、OpenAPI 冒烟、Playwright 登录/403/404/模块切换 4 条;覆盖率报告;CI 归档 | + +## 2. 关键实现位置 + +- 契约与工具:`packages/shared/src/`(`errors/codes.ts`、`errors/api-error.ts`、`permissions.ts`、`ulid.ts`、`naming.ts`、`schemas/`) +- UI 基线:`packages/ui/src/`(`icons.ts`、`components/`、`theme/tokens.css`、`locale.ts`) +- API:`apps/api/src/`(`bootstrap.ts`、`common/filters`、`common/pipes`、`modules/platform/health/`、`openapi/document.ts`、`scripts/export-openapi.ts`) +- 数据:`prisma/schema.prisma`、`prisma/migrations/20261102083445_init_platform_baseline/`、`prisma/seed.ts`、`prisma/SEED.md` +- Web:`apps/web/src/`(`layout/`、`navigation/nav-config.ts`、`api/client.ts`、`pages/`、`stores/auth-store.ts`、`styles/`) +- 基础设施:`infra/docker-compose.dev.yaml`、`infra/compose.dev.yaml`、`.env.example` +- 契约快照:`openapi/lingxi.openapi.json`、`apps/web/src/api/generated/openapi.d.ts` + +## 3. 本地门禁实测 + +| 命令 | 结果 | +| --- | --- | +| `pnpm format:check` | 通过 | +| `pnpm lint` | 通过(0 error / 0 warning) | +| `pnpm typecheck` | 通过(10/10 turbo tasks) | +| `pnpm test` | 通过(shared 42、api 20、web 12) | +| `pnpm build` | 通过(8/8 turbo tasks) | +| `pnpm test:int` | 通过(真实 PostgreSQL + Redis,`/health` 200) | +| `pnpm test:e2e` | 通过(4/4,Playwright/Chrome) | +| `pnpm openapi:export` + `pnpm openapi:diff` | 通过;快照含 20 个错误码,OpenAPI `3.1.0` | +| `pnpm compose:up` | 通过;`lingxi-pg` / `lingxi-redis` / `lingxi-s3` 均 healthy | +| API 启动 | `/health` 200、`/api/docs` 200、`/api/openapi.json` 200 | +| Worker 启动 | 连接 Redis 成功,收到 SIGTERM 后正常退出 | +| 种子幂等 | 连续执行两次,`system_configs` 保持 7 条 | + +## 4. 与文档的差异与待确认 + +### 4.1 宿主端口(环境适配) + +本机已有容器占用 5432 / 6379,A0 compose 宿主端口改为 **15432(PG)/ 16379(Redis)/ 19000–19001(S3)**,容器内端口不变。`.env.example` 已按宿主端口提供连接串;CI 使用 GitHub Actions services 的标准端口。 + +### 4.2 对象存储镜像不可得(环境适配) + +当前 Docker 环境无法拉取 `minio/minio` 与 `minio/mc`(registry denied),`dl.min.io` 二进制返回 410。A0 使用 **`adobe/s3mock`** 并在启动时创建 `assets / renders / temp / archive` 四个桶,仅用于本地开发;CI / 生产接入真实 MinIO 时替换 compose 中 `s3` 服务即可,应用侧仍只依赖 S3 协议与环境变量。 + +### 4.3 演示登录(A0 临时) + +A1 才实现真实鉴权。A0 登录页在 `VITE_PUBLIC_DEMO_SESSION` 非 `false` 时使用本地演示会话进入布局,便于验收布局与权限过滤;A1 接入真实登录后删除该分支。 + +### 4.4 种子范围【待确认 Q-A0-01】 + +开发计划 A0-INF-02 要求种子包含「超管、示例角色、核心字典、电商行业包占位」,但 `users / roles / dict_* / industry_packages` 等表分别在 A1 / A3 / A4 / B0 批次建立。A0 不提前建业务表,仅 seed `system_configs` 7 项,并在 `prisma/SEED.md` 登记待补种子清单。**请确认是否接受该拆分**;若要求 A0 内建表,则需调整开发计划批次顺序。 + +### 4.5 错误码 HTTP 映射 + +工程规范 04 未规定 50002 / 50003 的 HTTP 状态。A0 将 `50002 依赖不可用` 映射为 **503**(与 `/health` 依赖异常返回值一致),`50003 超时` 映射为 504;如需改为 502 / 504,请确认后调整 `packages/shared/src/errors/codes.ts` 与 `codes.spec.ts`。 + +### 4.6 Prisma CLI 与环境文件 + +Prisma CLI 只自动读取 `.env`,不读取 `.env.local`。本地需执行 `cp .env.local .env`(两者均在 `.gitignore`);应用与种子脚本同时兼容 `.env.local` 与 `.env`。 + +### 4.7 OpenAPI 破坏性检查实现 + +npm 上 `oasdiff` 为占位包(0.0.1-security),A0 暂用自研 `scripts/openapi-diff.mjs` 做路径/操作/字段级破坏性检查;CI 可后续替换为 oasdiff 官方二进制,命令仍为 `pnpm openapi:diff`。 + +### 4.8 前端视觉方向 + +已按 oil-ui-pro 流程产出两轮方案: +- v1(概念化:剪辑台 / 稿纸 / 调度台)被否决; +- v2(传统且现代化:云白蓝 / 石墨 / 暖白青)经产品负责人选择 **A · 云白蓝**,已落地到 `packages/ui/src/theme/tokens.css`、`apps/web/src/styles/`、`apps/web/index.html`: + - 顶部模块菜单 + 左侧功能菜单 + 面包屑; + - 白色顶栏、`#F5F7FA` 工作区、白卡片、`#1677FF` 主色、6px 圆角; + - 工作台:4 指标卡 + 生产任务表 + 快速入口 + 最近素材; + - 登录页:左上品牌、居中白卡、浅灰背景。 + +设计探索产物位于 `design/a0-ui-v2/`(含 6 屏、对比页与截图),按 `.gitignore` 约定不入库,仅本地留存评审。 + +## 5. 已知风险与后续动作 + +1. **A1 必须移除演示登录**并接入真实 JWT / 刷新令牌 / 权限守卫。 +2. 种子业务数据待 A1 / A3 / A4 / B0 表就绪后补齐(Q-A0-01)。 +3. MinIO 本地替代方案需在具备镜像的环境回归;生产 compose 与 CI 镜像另行处理。 +4. Web 主包约 599 kB(Semi 全量 CSS 689 kB),A0 接受;M1 前做路由级拆分与 Semi 按需样式。 +5. CI 首次运行需在 Gitea Actions 上验证 runner 是否支持 services 与 Playwright 浏览器安装。 + +## 6. 下一步(A1) + +按开发计划进入 A1:identity / client 模块、真实登录与 RBAC、`ScopeContext`、客户与组织基础表、登录与权限三态测试。 diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts new file mode 100644 index 0000000..d69ff94 --- /dev/null +++ b/e2e/smoke.spec.ts @@ -0,0 +1,38 @@ +import { expect, type Page, test } from '@playwright/test'; + +async function signIn(page: Page): Promise { + await page.goto('/login'); + await page.getByPlaceholder('请输入账号或邮箱').fill('admin'); + await page.getByPlaceholder('请输入密码').fill('demo1234'); + await page.getByTestId('login-submit').click(); + await expect(page.getByTestId('app-shell')).toBeVisible(); +} + +test('登录页打开并进入工作台', async ({ page }) => { + await page.goto('/login'); + await expect(page.getByRole('heading', { name: '登录灵犀' })).toBeVisible(); + await signIn(page); + await expect(page).toHaveURL('/'); + await expect(page.getByRole('heading', { name: '工作台', exact: true })).toBeVisible(); + await expect(page.getByRole('navigation', { name: '模块导航' })).toBeVisible(); +}); + +test('403 页可演示', async ({ page }) => { + await page.goto('/403'); + await expect(page.getByTestId('error-403')).toBeVisible(); + await expect(page.getByRole('heading', { name: '没有访问权限' })).toBeVisible(); +}); + +test('模块切换与侧边栏、面包屑联动', async ({ page }) => { + await signIn(page); + await page.getByRole('button', { name: '素材', exact: true }).click(); + await expect(page).toHaveURL(/\/material/); + await page.getByRole('link', { name: '快筛', exact: true }).click(); + await expect(page.getByRole('heading', { name: '快筛' })).toBeVisible(); + await expect(page.getByRole('navigation', { name: '面包屑' })).toContainText('素材'); +}); + +test('未知路由展示 404', async ({ page }) => { + await page.goto('/404'); + await expect(page.getByTestId('error-404')).toBeVisible(); +}); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..24bdbb8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,226 @@ +// 灵犀 ESLint 9 flat config +// 依据:docs/工程规范/03-格式化与Lint.md +// 分层:根规则(全仓库)→ apps/web(React)→ packages/ui(图标统一出口)→ scripts(允许 console) +import js from '@eslint/js'; +import importX from 'eslint-plugin-import-x'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import simpleImportSort from 'eslint-plugin-simple-import-sort'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +const UI_LIB_MESSAGE = 'UI 组件库只能用 @douyinfe/semi-ui-19(工程规范 03 §3.2);禁装其他组件库。'; +const ICON_MESSAGE = + '图标只能从 @lingxi/ui/icons 按语义名导入;禁止直接引用图标库或内联 SVG(AGENTS §3.3)。'; +const COLOR_MESSAGE = + '业务代码禁止颜色字面量;颜色只能定义在 packages/ui/src/theme/ 的 token 中(工程规范 03 §3.2)。'; + +export default tseslint.config( + { + ignores: [ + '**/node_modules/**', + '**/dist/**', + '**/coverage/**', + '**/.turbo/**', + '.tools/**', + 'design/**', + 'docs/**', + 'openapi/**', + 'prisma/migrations/**', + '项目参考/**', + 'playwright-report/**', + 'test-results/**', + '**/*.d.ts', + ], + }, + js.configs.recommended, + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + { + name: 'lingxi/base', + languageOptions: { + globals: { ...globals.node, ...globals.browser, ...globals.es2022 }, + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + plugins: { + 'import-x': importX, + 'simple-import-sort': simpleImportSort, + }, + settings: { + 'import-x/resolver': { + typescript: { + project: ['./tsconfig.json', './apps/*/tsconfig.json', './packages/*/tsconfig.json'], + }, + }, + }, + linterOptions: { reportUnusedDisableDirectives: 'error' }, + rules: { + // ---- TypeScript 硬约束(AGENTS §3.2)---- + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { prefer: 'type-imports', fixStyle: 'separate-type-imports' }, + ], + '@typescript-eslint/no-unnecessary-condition': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { allowNumber: true, allowBoolean: true }, + ], + '@typescript-eslint/naming-convention': [ + 'error', + { selector: 'typeLike', format: ['PascalCase'] }, + { selector: 'enumMember', format: ['UPPER_CASE'] }, + ], + '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], + // ---- 导入与依赖方向 ---- + 'import-x/no-cycle': ['error', { maxDepth: 8, ignoreExternal: false }], + 'import-x/no-self-import': 'error', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + // ---- 通用 ---- + 'no-console': 'error', + eqeqeq: ['error', 'always'], + 'no-var': 'error', + 'prefer-const': 'error', + complexity: ['warn', { max: 15 }], + 'max-lines-per-function': [ + 'warn', + { max: 50, skipBlankLines: true, skipComments: true, IIFEs: true }, + ], + }, + }, + // 纯 JS 配置文件不做类型检查 + { + name: 'lingxi/js-config', + files: ['**/*.{js,mjs,cjs}'], + extends: [tseslint.configs.disableTypeChecked], + rules: { + 'no-console': 'off', + 'import-x/no-cycle': 'off', + }, + }, + // 应用层:Web(React 19) + { + name: 'lingxi/apps-web', + files: ['apps/web/**/*.{ts,tsx}'], + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + 'jsx-a11y': jsxA11y, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + 'jsx-a11y/alt-text': 'error', + 'jsx-a11y/no-autofocus': 'warn', + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + patterns: [ + { group: ['@douyinfe/semi-ui', '@douyinfe/semi-ui/*'], message: UI_LIB_MESSAGE }, + { + group: [ + '@remixicon/react', + 'react-icons', + 'react-icons/*', + 'lucide-react', + 'lucide', + 'semi-icons', + '@douyinfe/semi-icons', + '@heroicons/*', + '@ant-design/icons', + ], + message: ICON_MESSAGE, + }, + { + group: [ + 'antd', + '@arco-design/web-react', + 'element-plus', + 'naive-ui', + '@mui/*', + '@chakra-ui/*', + ], + message: UI_LIB_MESSAGE, + }, + ], + }, + ], + 'no-restricted-syntax': [ + 'error', + { + selector: 'Literal[value=/^#(?:[0-9a-fA-F]{3,8})$/]', + message: COLOR_MESSAGE, + }, + { + selector: 'Literal[value=/^rgba?\\(/]', + message: COLOR_MESSAGE, + }, + { + selector: "JSXOpeningElement[name.name='svg']", + message: ICON_MESSAGE, + }, + ], + }, + }, + // packages/ui:图标统一出口(只有 icons.ts 可直接引用 Remix Icon) + { + name: 'lingxi/ui-icons', + files: ['packages/ui/src/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'error', + { patterns: [{ group: ['@remixicon/react'], message: ICON_MESSAGE }] }, + ], + }, + }, + { + name: 'lingxi/ui-icons-exit', + files: ['packages/ui/src/icons.ts'], + rules: { + '@typescript-eslint/no-restricted-imports': ['error', { patterns: [] }], + }, + }, + // Nest 模块装饰器类按框架约定是空类 + { + name: 'lingxi/nest-modules', + files: ['apps/api/**/*.module.ts'], + rules: { + '@typescript-eslint/no-extraneous-class': 'off', + }, + }, + // 脚本层:允许 console(CLI 输出) + { + name: 'lingxi/scripts', + files: ['scripts/**/*.{ts,mjs,js}', '**/scripts/**/*.ts', 'prisma/seed.ts', 'e2e/**/*.ts'], + rules: { + 'no-console': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + }, + }, + // 测试文件:允许更松的表达 + { + name: 'lingxi/tests', + files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.int.spec.ts', 'e2e/**/*.ts'], + rules: { + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + 'max-lines-per-function': 'off', + complexity: 'off', + }, + }, +); diff --git a/infra/compose.dev.yaml b/infra/compose.dev.yaml new file mode 100644 index 0000000..cbb95de --- /dev/null +++ b/infra/compose.dev.yaml @@ -0,0 +1,4 @@ +# 技术架构 §4.1.1 引用的路径为 infra/compose.dev.yaml,这里做 include 兼容。 +# 实际定义在 infra/docker-compose.dev.yaml(开发计划 §6.1 与工程规范 README 的命名)。 +include: + - docker-compose.dev.yaml diff --git a/infra/docker-compose.dev.yaml b/infra/docker-compose.dev.yaml new file mode 100644 index 0000000..4a2c6d4 --- /dev/null +++ b/infra/docker-compose.dev.yaml @@ -0,0 +1,70 @@ +# 灵犀 · 本地开发环境编排(PG18 / Redis 7.4 / S3 兼容对象存储) +# 用法:pnpm compose:up / pnpm compose:down +# 说明:本机已有其他 PG/Redis 占用 5432/6379,这里宿主端口统一改用 15432/16379, +# 容器内端口保持标准值;对应 DATABASE_URL / REDIS_URL 见根目录 .env.example。 +name: lingxi-dev + +services: + postgres: + image: postgres:18-alpine + container_name: lingxi-pg + environment: + POSTGRES_USER: lingxi + POSTGRES_PASSWORD: lingxi_dev_password + POSTGRES_DB: lingxi_dev + TZ: Asia/Shanghai + ports: + - '15432:5432' + volumes: + # PG18+ 镜像要求挂载 /var/lib/postgresql(数据放在版本子目录,便于 pg_upgrade) + - lingxi-pg-data:/var/lib/postgresql + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U lingxi -d lingxi_dev'] + interval: 5s + timeout: 3s + retries: 20 + start_period: 10s + + redis: + image: redis:7.4-alpine + container_name: lingxi-redis + command: ['redis-server', '--appendonly', 'yes'] + ports: + - '16379:6379' + volumes: + - lingxi-redis-data:/data + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 3s + retries: 20 + start_period: 5s + + # 对象存储:技术架构要求 MinIO(S3 协议)。2026-11 实测 minio/minio 与 minio/mc + # 官方镜像在当前网络/仓库均无法拉取(registry denied),quay.io 需鉴权; + # 本地开发改用 S3 兼容的 Adobe S3Mock,接口与四桶(assets/renders/temp/archive)保持一致, + # S3_ENDPOINT 仍指向 19000。CI/生产接入真实 MinIO,切换时仅替换本服务。 + s3: + image: adobe/s3mock:latest + container_name: lingxi-s3 + environment: + COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS: assets,renders,temp,archive + COM_ADOBE_TESTING_S3MOCK_STORE_REGION: us-east-1 + COM_ADOBE_TESTING_S3MOCK_STORE_ROOT: /s3mockroot + COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT: 'true' + ports: + - '19000:9090' + - '19001:9191' + volumes: + - lingxi-s3-data:/s3mockroot + healthcheck: + test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1:9090/ || exit 1'] + interval: 5s + timeout: 3s + retries: 20 + start_period: 20s + +volumes: + lingxi-pg-data: + lingxi-redis-data: + lingxi-s3-data: diff --git a/openapi/lingxi.openapi.json b/openapi/lingxi.openapi.json new file mode 100644 index 0000000..7623e29 --- /dev/null +++ b/openapi/lingxi.openapi.json @@ -0,0 +1,288 @@ +{ + "openapi": "3.1.0", + "paths": { + "/health": { + "get": { + "description": "真实检查 PostgreSQL 与 Redis;任一不可用返回 503 与统一错误体。", + "operationId": "HealthController_check", + "parameters": [], + "responses": { + "200": { + "description": "全部依赖健康", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponseDto" + } + } + } + }, + "503": { + "description": "依赖不可用(错误码 50002),details 内附各依赖检查结果" + } + }, + "summary": "健康检查", + "tags": [ + "platform" + ] + } + } + }, + "info": { + "title": "灵犀 API", + "description": "信息流广告素材生产平台后端 API。\n\n统一约定:\n- 前缀 /api/v1(健康检查 /health 除外)\n- 错误体 { code, message, details, trace_id }\n- 列表响应 { items, next_cursor }", + "version": "0.1.0", + "contact": {} + }, + "tags": [], + "servers": [ + { + "url": "/api/v1", + "description": "API v1" + } + ], + "components": { + "securitySchemes": { + "bearer": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http" + } + }, + "schemas": { + "ComponentHealthDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "up", + "down" + ], + "example": "up" + }, + "latencyMs": { + "type": "number", + "description": "检查耗时(毫秒)", + "example": 3 + }, + "error": { + "type": "string", + "description": "失败原因(仅 down 时返回)" + } + }, + "required": [ + "status", + "latencyMs" + ] + }, + "HealthChecksDto": { + "type": "object", + "properties": { + "database": { + "$ref": "#/components/schemas/ComponentHealthDto" + }, + "redis": { + "$ref": "#/components/schemas/ComponentHealthDto" + } + }, + "required": [ + "database", + "redis" + ] + }, + "HealthResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "degraded" + ], + "example": "ok" + }, + "env": { + "type": "string", + "example": "development" + }, + "version": { + "type": "string", + "example": "0.1.0" + }, + "uptimeSeconds": { + "type": "number", + "description": "进程运行秒数", + "example": 128 + }, + "checks": { + "$ref": "#/components/schemas/HealthChecksDto" + }, + "timestamp": { + "type": "string", + "example": "2026-11-02T08:00:00.000Z" + } + }, + "required": [ + "status", + "env", + "version", + "uptimeSeconds", + "checks", + "timestamp" + ] + }, + "ErrorResponseDto": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "错误码(见 ErrorCode 常量)", + "example": 42202 + }, + "message": { + "type": "string", + "description": "可直接展示给用户的提示", + "example": "内容触发合规拦截" + }, + "details": { + "type": "object", + "description": "补充信息(字段错误、命中规则等),结构由错误码决定", + "example": { + "issues": [ + { + "path": "name", + "message": "Required" + } + ] + } + }, + "trace_id": { + "type": "string", + "description": "请求追踪 ID,与日志同源", + "example": "01JABCDEF0123456789ABCDEFG" + } + }, + "required": [ + "code", + "message", + "trace_id" + ] + }, + "ErrorCode": { + "type": "integer", + "description": "业务错误码(只允许使用 @lingxi/shared 中登记的错误码)", + "enum": [ + 40001, + 40101, + 40102, + 40103, + 40301, + 40302, + 40303, + 40304, + 40401, + 40901, + 40902, + 40903, + 42201, + 42202, + 42203, + 42901, + 42902, + 50001, + 50002, + 50003 + ] + }, + "ErrorResponse": { + "type": "object", + "required": [ + "code", + "message", + "trace_id" + ], + "properties": { + "code": { + "$ref": "#/components/schemas/ErrorCode" + }, + "message": { + "type": "string" + }, + "details": { + "description": "补充信息,结构由错误码决定" + }, + "trace_id": { + "type": "string" + } + } + } + } + }, + "x-error-codes": [ + { + "code": 40001 + }, + { + "code": 40101 + }, + { + "code": 40102 + }, + { + "code": 40103 + }, + { + "code": 40301 + }, + { + "code": 40302 + }, + { + "code": 40303 + }, + { + "code": 40304 + }, + { + "code": 40401 + }, + { + "code": 40901 + }, + { + "code": 40902 + }, + { + "code": 40903 + }, + { + "code": 42201 + }, + { + "code": 42202 + }, + { + "code": 42203 + }, + { + "code": 42901 + }, + { + "code": 42902 + }, + { + "code": 50001 + }, + { + "code": 50002 + }, + { + "code": 50003 + } + ], + "x-api-config": { + "env": "development", + "featureVideo": false + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..52f043e --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "lingxi", + "version": "0.1.0", + "private": true, + "description": "灵犀 · 信息流广告素材一站式生产平台", + "packageManager": "pnpm@10.34.9", + "engines": { + "node": "24.15.0", + "pnpm": ">=10.0.0 <11.0.0" + }, + "scripts": { + "preinstall": "node scripts/only-allow-pnpm.mjs", + "prepare": "husky", + "dev": "turbo run dev --parallel", + "build": "turbo run build", + "lint": "eslint . --max-warnings=0", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "turbo run typecheck && tsc -p tsconfig.json --noEmit", + "test": "turbo run test", + "test:int": "turbo run test:int", + "test:e2e": "turbo run build && playwright test", + "compose:up": "docker compose -f infra/docker-compose.dev.yaml up -d --wait", + "compose:down": "docker compose -f infra/docker-compose.dev.yaml down", + "db:generate": "prisma generate", + "db:migrate": "prisma migrate dev", + "db:reset": "prisma migrate reset --force", + "db:seed": "tsx prisma/seed.ts", + "openapi:export": "tsx --tsconfig apps/api/tsconfig.json apps/api/src/scripts/export-openapi.ts && openapi-typescript openapi/lingxi.openapi.json -o apps/web/src/api/generated/openapi.d.ts", + "openapi:diff": "node scripts/openapi-diff.mjs", + "postinstall": "prisma generate" + }, + "devDependencies": { + "@commitlint/cli": "20.5.3", + "@commitlint/config-conventional": "20.5.3", + "@eslint/js": "9.39.5", + "@playwright/test": "1.59.1", + "@prisma/client": "6.19.3", + "@types/node": "24.19.2", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitest/coverage-v8": "3.2.7", + "dotenv": "18.0.9", + "eslint": "9.39.5", + "eslint-import-resolver-typescript": "4.4.5", + "eslint-plugin-import-x": "4.17.1", + "eslint-plugin-jsx-a11y": "6.10.2", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.7", + "eslint-plugin-simple-import-sort": "14.0.0", + "globals": "17.14.0", + "husky": "9.1.7", + "lint-staged": "16.4.0", + "openapi-typescript": "7.13.0", + "prettier": "3.9.10", + "prisma": "6.19.3", + "tsup": "8.5.1", + "tsx": "4.23.15", + "turbo": "2.11.10", + "typescript": "5.9.3", + "typescript-eslint": "8.72.1", + "vitest": "3.2.7" + }, + "prisma": { + "seed": "tsx prisma/seed.ts" + } +} diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json new file mode 100644 index 0000000..f02fcb0 --- /dev/null +++ b/packages/ai-sdk/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lingxi/ai-sdk", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "devDependencies": { + "tsup": "8.5.1", + "typescript": "5.9.3" + } +} diff --git a/packages/ai-sdk/src/index.ts b/packages/ai-sdk/src/index.ts new file mode 100644 index 0000000..8b6b864 --- /dev/null +++ b/packages/ai-sdk/src/index.ts @@ -0,0 +1,5 @@ +/** + * @lingxi/ai-sdk 骨架占位:具体能力在后续批次实现(见开发计划 WBS)。 + * 本包在 A0 阶段仅用于固定仓库结构与依赖方向。 + */ +export const PACKAGE_NAME = '@lingxi/ai-sdk'; diff --git a/packages/ai-sdk/tsconfig.json b/packages/ai-sdk/tsconfig.json new file mode 100644 index 0000000..dee81a1 --- /dev/null +++ b/packages/ai-sdk/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": [] + }, + "include": ["src"] +} diff --git a/packages/form-engine/package.json b/packages/form-engine/package.json new file mode 100644 index 0000000..1bdd6d2 --- /dev/null +++ b/packages/form-engine/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lingxi/form-engine", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "devDependencies": { + "tsup": "8.5.1", + "typescript": "5.9.3" + } +} diff --git a/packages/form-engine/src/index.ts b/packages/form-engine/src/index.ts new file mode 100644 index 0000000..092f6ba --- /dev/null +++ b/packages/form-engine/src/index.ts @@ -0,0 +1,5 @@ +/** + * @lingxi/form-engine 骨架占位:具体能力在后续批次实现(见开发计划 WBS)。 + * 本包在 A0 阶段仅用于固定仓库结构与依赖方向。 + */ +export const PACKAGE_NAME = '@lingxi/form-engine'; diff --git a/packages/form-engine/tsconfig.json b/packages/form-engine/tsconfig.json new file mode 100644 index 0000000..dee81a1 --- /dev/null +++ b/packages/form-engine/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": [] + }, + "include": ["src"] +} diff --git a/packages/render/package.json b/packages/render/package.json new file mode 100644 index 0000000..5d41ef6 --- /dev/null +++ b/packages/render/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lingxi/render", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "devDependencies": { + "tsup": "8.5.1", + "typescript": "5.9.3" + } +} diff --git a/packages/render/src/index.ts b/packages/render/src/index.ts new file mode 100644 index 0000000..7f817c1 --- /dev/null +++ b/packages/render/src/index.ts @@ -0,0 +1,5 @@ +/** + * @lingxi/render 骨架占位:具体能力在后续批次实现(见开发计划 WBS)。 + * 本包在 A0 阶段仅用于固定仓库结构与依赖方向。 + */ +export const PACKAGE_NAME = '@lingxi/render'; diff --git a/packages/render/tsconfig.json b/packages/render/tsconfig.json new file mode 100644 index 0000000..dee81a1 --- /dev/null +++ b/packages/render/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": [] + }, + "include": ["src"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..2f01424 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,34 @@ +{ + "name": "@lingxi/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run --coverage" + }, + "dependencies": { + "zod": "3.25.76" + }, + "devDependencies": { + "@vitest/coverage-v8": "3.2.7", + "tsup": "8.5.1", + "typescript": "5.9.3", + "vitest": "3.2.7" + } +} diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts new file mode 100644 index 0000000..be12e52 --- /dev/null +++ b/packages/shared/src/constants.ts @@ -0,0 +1,16 @@ +/** + * 跨端共享常量(A0) + * 约束:packages/shared 不依赖任何业务包(AGENTS §5)。 + */ +export const API_PREFIX = '/api/v1'; + +export const DEFAULT_PAGE_SIZE = 20; +export const MAX_PAGE_SIZE = 100; + +export const ULID_LENGTH = 26; + +/** 任务提交必须携带的幂等键请求头(AGENTS §3.13) */ +export const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key'; + +/** 请求追踪 ID 请求/响应头 */ +export const REQUEST_ID_HEADER = 'x-request-id'; diff --git a/packages/shared/src/errors/api-error.spec.ts b/packages/shared/src/errors/api-error.spec.ts new file mode 100644 index 0000000..b2dfa59 --- /dev/null +++ b/packages/shared/src/errors/api-error.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; + +import { ApiError } from './api-error.js'; +import { ErrorCode } from './codes.js'; + +describe('ApiError', () => { + it('使用默认文案并按错误码推导 HTTP 状态', () => { + const error = new ApiError({ code: ErrorCode.NOT_FOUND, traceId: 'trace-001' }); + expect(error.message).toBe('资源不存在或已删除'); + expect(error.status).toBe(404); + expect(error.name).toBe('ApiError'); + expect(error).toBeInstanceOf(Error); + }); + + it('toBody 输出统一错误体(trace_id 为 snake_case)', () => { + const error = new ApiError({ + code: ErrorCode.COMPLIANCE_BLOCKED, + message: '触发极限词:最', + details: { words: ['最'] }, + traceId: 'trace-abc', + }); + expect(error.toBody()).toEqual({ + code: 42202, + message: '触发极限词:最', + details: { words: ['最'] }, + trace_id: 'trace-abc', + }); + }); + + it('details 缺省时不写入错误体', () => { + const body = new ApiError({ code: ErrorCode.INTERNAL_ERROR }).toBody(); + expect(body).not.toHaveProperty('details'); + expect(body.trace_id).toBe(''); + }); +}); diff --git a/packages/shared/src/errors/api-error.ts b/packages/shared/src/errors/api-error.ts new file mode 100644 index 0000000..bf3eca7 --- /dev/null +++ b/packages/shared/src/errors/api-error.ts @@ -0,0 +1,32 @@ +import type { ApiErrorBody } from '../schemas/api.js'; +import { ERROR_MESSAGES, type ErrorCode } from './codes.js'; + +/** + * 前后端共用的 API 错误对象。 + * - 服务端:AllExceptionsFilter 用它把异常序列化成统一错误体; + * - 前端:请求层把错误体还原成 ApiError,保留 trace_id 便于用户报障。 + */ +export class ApiError extends Error { + readonly code: ErrorCode; + readonly status: number; + readonly details?: unknown; + readonly traceId: string; + + constructor(params: { code: ErrorCode; message?: string; details?: unknown; traceId?: string }) { + super(params.message ?? ERROR_MESSAGES[params.code]); + this.name = 'ApiError'; + this.code = params.code; + this.status = Math.floor(params.code / 100); + this.details = params.details; + this.traceId = params.traceId ?? ''; + } + + toBody(): ApiErrorBody { + return { + code: this.code, + message: this.message, + ...(this.details === undefined ? {} : { details: this.details }), + trace_id: this.traceId, + }; + } +} diff --git a/packages/shared/src/errors/codes.spec.ts b/packages/shared/src/errors/codes.spec.ts new file mode 100644 index 0000000..58a0047 --- /dev/null +++ b/packages/shared/src/errors/codes.spec.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; + +import { + ERROR_HTTP_STATUS, + ERROR_MESSAGES, + ErrorCode, + httpStatusOf, + isErrorCode, +} from './codes.js'; + +describe('ErrorCode', () => { + it('错误码段与工程规范 04 一致', () => { + expect(ErrorCode.PARAM_INVALID).toBe(40001); + expect(ErrorCode.UNAUTHENTICATED).toBe(40101); + expect(ErrorCode.TOKEN_EXPIRED).toBe(40102); + expect(ErrorCode.PERMISSION_DENIED).toBe(40301); + expect(ErrorCode.OUT_OF_SCOPE).toBe(40302); + expect(ErrorCode.NOT_FOUND).toBe(40401); + expect(ErrorCode.VERSION_CONFLICT).toBe(40901); + expect(ErrorCode.STATE_NOT_ALLOWED).toBe(40903); + expect(ErrorCode.COMPLIANCE_BLOCKED).toBe(42202); + expect(ErrorCode.COST_OVER_BUDGET).toBe(42203); + expect(ErrorCode.RATE_LIMITED).toBe(42901); + expect(ErrorCode.INTERNAL_ERROR).toBe(50001); + expect(ErrorCode.DEPENDENCY_UNAVAILABLE).toBe(50002); + expect(ErrorCode.TIMEOUT).toBe(50003); + }); + + it('每个错误码都有 HTTP 状态与默认提示', () => { + for (const code of Object.values(ErrorCode)) { + expect(ERROR_HTTP_STATUS[code]).toBeGreaterThanOrEqual(400); + expect(ERROR_HTTP_STATUS[code]).toBeLessThan(600); + expect(ERROR_MESSAGES[code].length).toBeGreaterThan(0); + } + }); + + it('依赖不可用映射 503、超时映射 504', () => { + expect(ERROR_HTTP_STATUS[ErrorCode.DEPENDENCY_UNAVAILABLE]).toBe(503); + expect(ERROR_HTTP_STATUS[ErrorCode.TIMEOUT]).toBe(504); + }); + + it('isErrorCode 只认可登记的错误码', () => { + expect(isErrorCode(40001)).toBe(true); + expect(isErrorCode(40099)).toBe(false); + expect(isErrorCode(200)).toBe(false); + }); + + it('httpStatusOf 从百位推导,非法码回退 500', () => { + expect(httpStatusOf(42203)).toBe(422); + expect(httpStatusOf(40101)).toBe(401); + expect(httpStatusOf(123)).toBe(500); + expect(httpStatusOf(70001)).toBe(500); + }); +}); diff --git a/packages/shared/src/errors/codes.ts b/packages/shared/src/errors/codes.ts new file mode 100644 index 0000000..0ffd66a --- /dev/null +++ b/packages/shared/src/errors/codes.ts @@ -0,0 +1,106 @@ +/** + * 统一错误码(docs/工程规范/04-API与路由规范.md §4) + * API-14:新增错误码必须集中登记在这里,禁止业务代码写裸数字。 + */ +export const ErrorCode = { + /** 400xx 参数校验 */ + PARAM_INVALID: 40001, + + /** 401xx 认证 */ + UNAUTHENTICATED: 40101, + TOKEN_EXPIRED: 40102, + SIGNATURE_INVALID: 40103, + + /** 403xx 授权 */ + PERMISSION_DENIED: 40301, + OUT_OF_SCOPE: 40302, + SPECIAL_PERMISSION_REQUIRED: 40303, + SOD_VIOLATION: 40304, + + /** 404xx 不存在 */ + NOT_FOUND: 40401, + + /** 409xx 冲突 */ + VERSION_CONFLICT: 40901, + DUPLICATE_SUBMIT: 40902, + STATE_NOT_ALLOWED: 40903, + + /** 422xx 业务规则 */ + BUSINESS_RULE_VIOLATION: 42201, + COMPLIANCE_BLOCKED: 42202, + COST_OVER_BUDGET: 42203, + + /** 429xx 限流 */ + RATE_LIMITED: 42901, + CONCURRENCY_LIMITED: 42902, + + /** 500xx 系统 */ + INTERNAL_ERROR: 50001, + DEPENDENCY_UNAVAILABLE: 50002, + TIMEOUT: 50003, +} as const; + +export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode]; + +/** 错误码 → HTTP 状态码(4xx/5xx 段由百位决定) */ +export const ERROR_HTTP_STATUS: Readonly> = { + [ErrorCode.PARAM_INVALID]: 400, + [ErrorCode.UNAUTHENTICATED]: 401, + [ErrorCode.TOKEN_EXPIRED]: 401, + [ErrorCode.SIGNATURE_INVALID]: 401, + [ErrorCode.PERMISSION_DENIED]: 403, + [ErrorCode.OUT_OF_SCOPE]: 403, + [ErrorCode.SPECIAL_PERMISSION_REQUIRED]: 403, + [ErrorCode.SOD_VIOLATION]: 403, + [ErrorCode.NOT_FOUND]: 404, + [ErrorCode.VERSION_CONFLICT]: 409, + [ErrorCode.DUPLICATE_SUBMIT]: 409, + [ErrorCode.STATE_NOT_ALLOWED]: 409, + [ErrorCode.BUSINESS_RULE_VIOLATION]: 422, + [ErrorCode.COMPLIANCE_BLOCKED]: 422, + [ErrorCode.COST_OVER_BUDGET]: 422, + [ErrorCode.RATE_LIMITED]: 429, + [ErrorCode.CONCURRENCY_LIMITED]: 429, + [ErrorCode.INTERNAL_ERROR]: 500, + [ErrorCode.DEPENDENCY_UNAVAILABLE]: 503, + [ErrorCode.TIMEOUT]: 504, +}; + +/** 默认中文提示(前端兜底展示;服务端可按场景覆盖 message) */ +export const ERROR_MESSAGES: Readonly> = { + [ErrorCode.PARAM_INVALID]: '参数缺失或格式错误', + [ErrorCode.UNAUTHENTICATED]: '请先登录', + [ErrorCode.TOKEN_EXPIRED]: '登录已过期,请重新登录', + [ErrorCode.SIGNATURE_INVALID]: '签名校验失败', + [ErrorCode.PERMISSION_DENIED]: '没有操作权限', + [ErrorCode.OUT_OF_SCOPE]: '数据范围之外', + [ErrorCode.SPECIAL_PERMISSION_REQUIRED]: '缺少特殊权限', + [ErrorCode.SOD_VIOLATION]: '违反职责互斥,不能执行该操作', + [ErrorCode.NOT_FOUND]: '资源不存在或已删除', + [ErrorCode.VERSION_CONFLICT]: '数据已被他人修改,请刷新后重试', + [ErrorCode.DUPLICATE_SUBMIT]: '请勿重复提交', + [ErrorCode.STATE_NOT_ALLOWED]: '当前状态不允许该操作', + [ErrorCode.BUSINESS_RULE_VIOLATION]: '业务校验未通过', + [ErrorCode.COMPLIANCE_BLOCKED]: '内容触发合规拦截', + [ErrorCode.COST_OVER_BUDGET]: '成本超出预算', + [ErrorCode.RATE_LIMITED]: '请求过于频繁,请稍后再试', + [ErrorCode.CONCURRENCY_LIMITED]: '并发任务已达上限', + [ErrorCode.INTERNAL_ERROR]: '服务内部错误', + [ErrorCode.DEPENDENCY_UNAVAILABLE]: '依赖服务不可用', + [ErrorCode.TIMEOUT]: '请求超时', +}; + +const ERROR_CODE_VALUES: ReadonlySet = new Set(Object.values(ErrorCode)); + +export function isErrorCode(value: number): value is ErrorCode { + return ERROR_CODE_VALUES.has(value); +} + +/** 数字错误码对应的 HTTP 状态;优先查表(502/503/504 等细分),非法码回退 500 */ +export function httpStatusOf(code: number): number { + if (isErrorCode(code)) { + return ERROR_HTTP_STATUS[code]; + } + const segment = Math.floor(code / 100); + return segment >= 400 && segment <= 599 ? segment : 500; +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..7c5f3ea --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,9 @@ +export * from './constants.js'; +export * from './errors/api-error.js'; +export * from './errors/codes.js'; +export * from './naming.js'; +export * from './permissions.js'; +export * from './schemas/api.js'; +export * from './schemas/common.js'; +export * from './time.js'; +export * from './ulid.js'; diff --git a/packages/shared/src/naming.spec.ts b/packages/shared/src/naming.spec.ts new file mode 100644 index 0000000..1f539e9 --- /dev/null +++ b/packages/shared/src/naming.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; + +import { dedupeName, sanitizeFilename } from './naming.js'; + +describe('sanitizeFilename', () => { + it('替换路径分隔符与非法字符,保留中文', () => { + expect(sanitizeFilename('素材/标签:01*?.png')).toBe('素材_标签_01__.png'); + }); + + it('去除首尾空格与点,空名回退', () => { + expect(sanitizeFilename(' ..脚本草稿 .. ')).toBe('脚本草稿'); + expect(sanitizeFilename(' ')).toBe('未命名'); + expect(sanitizeFilename('***', { fallback: 'file' })).toBe('file'); + }); + + it('Windows 保留名追加下划线', () => { + expect(sanitizeFilename('CON')).toBe('CON_'); + expect(sanitizeFilename('lpt1')).toBe('lpt1_'); + }); + + it('超长时保留扩展名', () => { + const name = sanitizeFilename(`${'a'.repeat(200)}.png`, { maxLength: 20 }); + expect(name).toHaveLength(20); + expect(name.endsWith('.png')).toBe(true); + }); +}); + +describe('dedupeName(R-01 同名自动加序号)', () => { + it('不冲突时原样返回', () => { + expect(dedupeName('脚本-01', new Set())).toBe('脚本-01'); + }); + + it('冲突时追加两位序号', () => { + const existing = new Set(['素材.png', '素材-02.png']); + expect(dedupeName('素材.png', existing)).toBe('素材-03.png'); + }); + + it('带序号的名字基于原始基名递增', () => { + const existing = new Set(['素材-02.png', '素材-03.png']); + expect(dedupeName('素材-03.png', existing)).toBe('素材-04.png'); + }); + + it('序号耗尽时抛错', () => { + const existing = new Set(['a', ...Array.from({ length: 3 }, (_, i) => `a-0${String(i + 2)}`)]); + expect(() => dedupeName('a', existing, 3)).toThrow(RangeError); + }); +}); diff --git a/packages/shared/src/naming.ts b/packages/shared/src/naming.ts new file mode 100644 index 0000000..0eec356 --- /dev/null +++ b/packages/shared/src/naming.ts @@ -0,0 +1,107 @@ +/** + * 命名工具(A0-SH-01):文件名清洗 + 同名自动加序号(PRD R-01) + * 浏览器 / Node 通用,不依赖 node:path。 + */ + +const ILLEGAL_FILENAME_CHARS = /[\\/:*?"<>|]/g; +const RESERVED_FILENAMES = new Set([ + 'CON', + 'PRN', + 'AUX', + 'NUL', + 'COM1', + 'COM2', + 'COM3', + 'COM4', + 'COM5', + 'COM6', + 'COM7', + 'COM8', + 'COM9', + 'LPT1', + 'LPT2', + 'LPT3', + 'LPT4', + 'LPT5', + 'LPT6', + 'LPT7', + 'LPT8', + 'LPT9', +]); + +const MAX_FILENAME_LENGTH = 120; + +export interface SanitizeFilenameOptions { + maxLength?: number; + replacement?: string; + fallback?: string; +} + +/** 去掉控制字符(\u0000-\u001f、\u007f),保留常规空白 */ +function stripControlChars(input: string): string { + let output = ''; + for (const char of input) { + const code = char.codePointAt(0) ?? 0; + if (code >= 0x20 && code !== 0x7f) { + output += char; + } + } + return output; +} + +/** 清洗文件名:去除非法字符 / 控制字符 / 首尾空格与点,中文原样保留 */ +export function sanitizeFilename(input: string, options: SanitizeFilenameOptions = {}): string { + const maxLength = options.maxLength ?? MAX_FILENAME_LENGTH; + const replacement = options.replacement ?? '_'; + const fallback = options.fallback ?? '未命名'; + + let name = stripControlChars(input.normalize('NFC')) + .replace(ILLEGAL_FILENAME_CHARS, replacement) + .trim(); + name = name.replace(/^[.\s]+|[.\s]+$/g, ''); + + if (name.length === 0 || name.replaceAll(replacement, '').trim().length === 0) { + return fallback; + } + if (RESERVED_FILENAMES.has(name.toUpperCase())) { + name = `${name}_`; + } + if (name.length > maxLength) { + const dotIndex = name.lastIndexOf('.'); + const extension = dotIndex > 0 ? name.slice(dotIndex) : ''; + const base = dotIndex > 0 ? name.slice(0, dotIndex) : name; + const keep = Math.max(1, maxLength - extension.length); + name = `${base.slice(0, keep)}${extension}`; + } + return name; +} + +const SEQUENCE_PATTERN = /^(.*)-(\d{2,})$/; + +function splitExtension(name: string): { stem: string; extension: string } { + const dotIndex = name.lastIndexOf('.'); + if (dotIndex <= 0 || dotIndex === name.length - 1) { + return { stem: name, extension: '' }; + } + return { stem: name.slice(0, dotIndex), extension: name.slice(dotIndex) }; +} + +/** + * 同名自动加序号(R-01):从 base-02 开始尝试到 maxAttempts,全部占用则抛错。 + * 已带序号的名字会基于其原始基名继续递增;扩展名保持不变。 + */ +export function dedupeName(name: string, existing: ReadonlySet, maxAttempts = 999): string { + if (!existing.has(name)) { + return name; + } + const { stem, extension } = splitExtension(name); + const matched = SEQUENCE_PATTERN.exec(stem); + const root = matched?.[1] ?? stem; + for (let sequence = 2; sequence <= maxAttempts; sequence += 1) { + const candidate = `${root}-${String(sequence).padStart(2, '0')}${extension}`; + if (!existing.has(candidate)) { + return candidate; + } + } + throw new RangeError(`同名文件过多,无法为「${name}」生成序号`); +} diff --git a/packages/shared/src/permissions.spec.ts b/packages/shared/src/permissions.spec.ts new file mode 100644 index 0000000..7451065 --- /dev/null +++ b/packages/shared/src/permissions.spec.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; + +import { + isPermissionCode, + parsePermission, + Permission, + PERMISSION_CODES, + SPECIAL_PERMISSIONS, +} from './permissions.js'; + +describe('权限点常量', () => { + it('全部符合 模块:动作 命名且模块为小写', () => { + for (const code of PERMISSION_CODES) { + expect(code).toMatch(/^[a-z][a-z_]*:[a-z][a-z_]*$/); + expect(parsePermission(code)).not.toBeNull(); + } + }); + + it('特殊权限必须是已登记的权限点', () => { + for (const code of SPECIAL_PERMISSIONS) { + expect(PERMISSION_CODES).toContain(code); + } + }); + + it('isPermissionCode 只认可登记值', () => { + expect(isPermissionCode(Permission.SCRIPT_READ)).toBe(true); + expect(isPermissionCode('script:delete_all')).toBe(false); + }); +}); + +describe('parsePermission', () => { + it('拆分模块与动作', () => { + expect(parsePermission('material:render')).toEqual({ module: 'material', action: 'render' }); + }); + + it('非法格式返回 null', () => { + expect(parsePermission('scriptread')).toBeNull(); + expect(parsePermission(':read')).toBeNull(); + expect(parsePermission('script:')).toBeNull(); + expect(parsePermission('a:b:c')).toBeNull(); + }); +}); diff --git a/packages/shared/src/permissions.ts b/packages/shared/src/permissions.ts new file mode 100644 index 0000000..1c7cedb --- /dev/null +++ b/packages/shared/src/permissions.ts @@ -0,0 +1,78 @@ +/** + * 权限点常量骨架(A0-SH-01) + * 命名规范:<模块>:<动作>,模块取后端八模块语义(identity / client / catalog / industry / + * script / media / delivery / platform),全小写;动作使用动词或名词短语。 + * A1/A2 接入真实角色-权限表后,这里只增不改;删除/改名需评审(AGENTS §10)。 + */ +export const Permission = { + // 脚本 + SCRIPT_READ: 'script:read', + SCRIPT_WRITE: 'script:write', + SCRIPT_REVIEW: 'script:review', + // 素材 + MATERIAL_READ: 'material:read', + MATERIAL_WRITE: 'material:write', + MATERIAL_RENDER: 'material:render', + MATERIAL_REVIEW: 'material:review', + // 资产 + ASSET_READ: 'asset:read', + ASSET_WRITE: 'asset:write', + // 导出交付 + DELIVERY_READ: 'delivery:read', + DELIVERY_EXPORT: 'delivery:export', + // 客户 + CUSTOMER_READ: 'customer:read', + CUSTOMER_MANAGE: 'customer:manage', + CUSTOMER_FIELD_APPROVE: 'customer_field:approve', + // 组织与角色 + USER_MANAGE: 'user:manage', + ROLE_MANAGE: 'role:manage', + // 平台配置 + INDUSTRY_MANAGE: 'industry:manage', + INDUSTRY_PUBLISH: 'industry:publish', + DICT_MANAGE: 'dict:manage', + FORM_DESIGN: 'form:design', + AI_CONFIGURE: 'ai:configure', + SYSTEM_CONFIGURE: 'system:configure', + // 审计 + AUDIT_READ: 'audit:read', + AUDIT_EXPORT: 'audit:export', +} as const; + +export type PermissionCode = (typeof Permission)[keyof typeof Permission]; + +export const PERMISSION_CODES: readonly PermissionCode[] = Object.values(Permission); + +/** 需要额外单独校验的特殊权限(技术架构 §6.2「关键特殊权限」) */ +export const SPECIAL_PERMISSIONS: readonly PermissionCode[] = [ + Permission.DELIVERY_EXPORT, + Permission.AI_CONFIGURE, + Permission.AUDIT_EXPORT, + Permission.INDUSTRY_PUBLISH, + Permission.CUSTOMER_FIELD_APPROVE, +]; + +const PERMISSION_CODE_SET: ReadonlySet = new Set(PERMISSION_CODES); + +export function isPermissionCode(value: string): value is PermissionCode { + return PERMISSION_CODE_SET.has(value); +} + +export interface ParsedPermission { + module: string; + action: string; +} + +/** 拆分权限点为 { module, action };格式不合法返回 null */ +export function parsePermission(code: string): ParsedPermission | null { + const separator = code.indexOf(':'); + if (separator <= 0 || separator === code.length - 1) { + return null; + } + const module = code.slice(0, separator); + const action = code.slice(separator + 1); + if (module.includes(':') || action.includes(':')) { + return null; + } + return { module, action }; +} diff --git a/packages/shared/src/schemas/api.spec.ts b/packages/shared/src/schemas/api.spec.ts new file mode 100644 index 0000000..0982997 --- /dev/null +++ b/packages/shared/src/schemas/api.spec.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; + +import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../constants.js'; +import { apiErrorBodySchema, cursorPaginationQuerySchema, listResponseSchema } from './api.js'; + +describe('apiErrorBodySchema', () => { + it('接受统一错误体', () => { + const parsed = apiErrorBodySchema.parse({ + code: 40001, + message: '参数缺失', + details: { field: 'name' }, + trace_id: 'trace-1', + }); + expect(parsed.code).toBe(40001); + }); + + it('拒绝缺少 trace_id 的错误体', () => { + expect(() => apiErrorBodySchema.parse({ code: 40001, message: 'x' })).toThrow(); + }); +}); + +describe('cursorPaginationQuerySchema', () => { + it('缺省 limit 为默认页大小', () => { + expect(cursorPaginationQuerySchema.parse({})).toEqual({ limit: DEFAULT_PAGE_SIZE }); + expect(DEFAULT_PAGE_SIZE).toBe(20); + }); + + it('字符串 limit 会被转成数字(query 参数场景)', () => { + expect(cursorPaginationQuerySchema.parse({ limit: '50', cursor: 'abc' })).toEqual({ + limit: 50, + cursor: 'abc', + }); + }); + + it('拒绝越界 limit', () => { + expect(() => cursorPaginationQuerySchema.parse({ limit: 0 })).toThrow(); + expect(() => cursorPaginationQuerySchema.parse({ limit: MAX_PAGE_SIZE + 1 })).toThrow(); + expect(() => cursorPaginationQuerySchema.parse({ limit: 'abc' })).toThrow(); + }); +}); + +describe('listResponseSchema', () => { + it('列表信封为 { items, next_cursor }', () => { + const schema = listResponseSchema(apiErrorBodySchema); + const parsed = schema.parse({ items: [], next_cursor: null }); + expect(parsed).toEqual({ items: [], next_cursor: null }); + }); + + it('next_cursor 可为字符串', () => { + const schema = listResponseSchema(apiErrorBodySchema); + expect(schema.parse({ items: [], next_cursor: 'cursor-2' }).next_cursor).toBe('cursor-2'); + }); +}); diff --git a/packages/shared/src/schemas/api.ts b/packages/shared/src/schemas/api.ts new file mode 100644 index 0000000..58378b6 --- /dev/null +++ b/packages/shared/src/schemas/api.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; + +import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../constants.js'; + +/** 统一错误体(工程规范 04 §4):{ code, message, details, trace_id } */ +export const apiErrorBodySchema = z.object({ + code: z.number().int().positive(), + message: z.string().min(1), + details: z.unknown().optional(), + trace_id: z.string().min(1), +}); + +export type ApiErrorBody = z.infer; + +/** + * 游标分页查询参数:query 用 snake_case(C-01) + * cursor:上一页返回的 next_cursor;limit:默认 20、上限 100 + */ +export const cursorPaginationQuerySchema = z.object({ + cursor: z.string().min(1).optional(), + limit: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE), +}); + +export type CursorPaginationQuery = z.infer; + +/** 列表响应统一信封(C-02):{ items, next_cursor },单资源不套 data */ +export interface ListResponse { + items: T[]; + next_cursor: string | null; +} + +export function listResponseSchema( + itemSchema: T, +): z.ZodObject<{ items: z.ZodArray; next_cursor: z.ZodNullable }> { + return z.object({ + items: z.array(itemSchema), + next_cursor: z.string().nullable(), + }); +} diff --git a/packages/shared/src/schemas/common.spec.ts b/packages/shared/src/schemas/common.spec.ts new file mode 100644 index 0000000..2f95e03 --- /dev/null +++ b/packages/shared/src/schemas/common.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; + +import { ulid } from '../ulid.js'; +import { idempotencyKeySchema, isoDateTimeSchema, traceIdSchema, ulidSchema } from './common.js'; + +describe('common schemas', () => { + it('ulidSchema 接受应用侧生成的 ULID', () => { + expect(ulidSchema.parse(ulid())).toHaveLength(26); + expect(() => ulidSchema.parse('not-a-ulid')).toThrow(); + }); + + it('isoDateTimeSchema 要求带时区', () => { + expect(isoDateTimeSchema.parse('2026-11-02T08:00:00.000Z')).toBeTruthy(); + expect(() => isoDateTimeSchema.parse('2026-11-02 08:00:00')).toThrow(); + }); + + it('traceIdSchema 限制长度', () => { + expect(traceIdSchema.parse('trace-12345678')).toBe('trace-12345678'); + expect(() => traceIdSchema.parse('short')).toThrow(); + }); + + it('idempotencyKeySchema 只接受可见 ASCII', () => { + expect(idempotencyKeySchema.parse('idem-key-0001')).toBe('idem-key-0001'); + expect(() => idempotencyKeySchema.parse('幂等键-0001')).toThrow(); + expect(() => idempotencyKeySchema.parse('short')).toThrow(); + }); +}); diff --git a/packages/shared/src/schemas/common.ts b/packages/shared/src/schemas/common.ts new file mode 100644 index 0000000..29883ca --- /dev/null +++ b/packages/shared/src/schemas/common.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +import { ULID_PATTERN } from '../ulid.js'; + +/** 26 位 ULID 主键 */ +export const ulidSchema = z.string().regex(ULID_PATTERN, '必须是 26 位大写 ULID'); + +export const idSchema = ulidSchema; + +/** ISO 8601 时间(必须带时区,A0 统一用 UTC Z) */ +export const isoDateTimeSchema = z.string().datetime({ offset: true }); + +export const sortOrderSchema = z.enum(['asc', 'desc']); + +/** 请求追踪 ID(与 Pino requestId 同源) */ +export const traceIdSchema = z.string().min(8).max(64); + +/** 任务提交幂等键:8–128 位可见 ASCII */ +export const idempotencyKeySchema = z + .string() + .min(8) + .max(128) + .regex(/^[\x21-\x7e]+$/, '幂等键必须是可见 ASCII 字符'); + +/** 客户 ID:ULID */ +export const customerIdSchema = ulidSchema; diff --git a/packages/shared/src/time.spec.ts b/packages/shared/src/time.spec.ts new file mode 100644 index 0000000..eaae5f0 --- /dev/null +++ b/packages/shared/src/time.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; + +import { addSeconds, isIsoDateString, nowIso, toIsoString } from './time.js'; + +describe('time utils', () => { + it('nowIso 返回 UTC ISO 字符串', () => { + expect(isIsoDateString(nowIso())).toBe(true); + }); + + it('toIsoString 支持 Date / 时间戳 / 字符串', () => { + const date = new Date('2026-11-02T08:00:00.000Z'); + expect(toIsoString(date)).toBe('2026-11-02T08:00:00.000Z'); + expect(toIsoString(date.getTime())).toBe('2026-11-02T08:00:00.000Z'); + expect(toIsoString('2026-11-02T08:00:00.000Z')).toBe('2026-11-02T08:00:00.000Z'); + }); + + it('非法时间抛错', () => { + expect(() => toIsoString('not-a-date')).toThrow(TypeError); + }); + + it('addSeconds 不修改原对象', () => { + const date = new Date('2026-11-02T08:00:00.000Z'); + expect(addSeconds(date, 90).toISOString()).toBe('2026-11-02T08:01:30.000Z'); + expect(date.toISOString()).toBe('2026-11-02T08:00:00.000Z'); + }); + + it('isIsoDateString 拒绝非 UTC 格式', () => { + expect(isIsoDateString('2026-11-02T08:00:00+08:00')).toBe(false); + expect(isIsoDateString('2026-13-02T08:00:00.000Z')).toBe(false); + }); +}); diff --git a/packages/shared/src/time.ts b/packages/shared/src/time.ts new file mode 100644 index 0000000..23e049c --- /dev/null +++ b/packages/shared/src/time.ts @@ -0,0 +1,23 @@ +/** 时间工具:统一 ISO 8601(UTC)字符串,跨端序列化口径一致 */ + +export function nowIso(): string { + return new Date().toISOString(); +} + +export function toIsoString(value: Date | number | string): string { + const date = value instanceof Date ? value : new Date(value); + if (Number.isNaN(date.getTime())) { + throw new TypeError(`无法解析时间:${String(value)}`); + } + return date.toISOString(); +} + +export function addSeconds(date: Date, seconds: number): Date { + return new Date(date.getTime() + seconds * 1000); +} + +const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/; + +export function isIsoDateString(value: string): boolean { + return ISO_PATTERN.test(value) && !Number.isNaN(Date.parse(value)); +} diff --git a/packages/shared/src/ulid.spec.ts b/packages/shared/src/ulid.spec.ts new file mode 100644 index 0000000..63a2165 --- /dev/null +++ b/packages/shared/src/ulid.spec.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; + +import { isUlid, ulid, ULID_PATTERN, ulidToDate, ulidToTimestamp } from './ulid.js'; + +describe('ulid', () => { + it('生成 26 位 Crockford Base32 且不含 I/L/O/U', () => { + const id = ulid(); + expect(id).toHaveLength(26); + expect(ULID_PATTERN.test(id)).toBe(true); + expect(id).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/); + }); + + it('时间戳可从 ID 还原', () => { + const timestamp = 1_800_000_000_000; + const id = ulid(timestamp); + expect(ulidToTimestamp(id)).toBe(timestamp); + expect(ulidToDate(id).getTime()).toBe(timestamp); + }); + + it('同一毫秒内生成不重复', () => { + const ids = new Set(Array.from({ length: 1000 }, () => ulid(1_800_000_000_000))); + expect(ids.size).toBe(1000); + }); + + it('isUlid 拒绝非法输入', () => { + expect(isUlid(ulid())).toBe(true); + expect(isUlid('01ARZ3NDEKTSV4RRFFQ69G5FA')).toBe(false); + expect(isUlid('01ARZ3NDEKTSV4RRFFQ69G5FAV'.toLowerCase())).toBe(false); + expect(isUlid('O1ARZ3NDEKTSV4RRFFQ69G5FAV')).toBe(false); + }); + + it('解析非法 ULID 抛错', () => { + expect(() => ulidToTimestamp('bad')).toThrow(TypeError); + expect(() => ulid(-1)).toThrow(RangeError); + }); +}); diff --git a/packages/shared/src/ulid.ts b/packages/shared/src/ulid.ts new file mode 100644 index 0000000..c8a3bd2 --- /dev/null +++ b/packages/shared/src/ulid.ts @@ -0,0 +1,56 @@ +/** + * ULID 生成与解析(Crockford Base32,26 字符 = 48bit 时间 + 80bit 随机) + * 主键统一由应用侧生成 char(26) ULID(工程规范 05)。 + */ +import { ULID_LENGTH } from './constants.js'; + +const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; +const ENCODING_LEN = 32; + +export const ULID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/; + +function randomBytes(length: number): Uint8Array { + const bytes = new Uint8Array(length); + globalThis.crypto.getRandomValues(bytes); + return bytes; +} + +/** 生成 ULID;同一毫秒内依靠 80bit 随机数避免碰撞 */ +export function ulid(timestamp: number = Date.now()): string { + if (!Number.isInteger(timestamp) || timestamp < 0 || timestamp > 0xffffffffffff) { + throw new RangeError('ULID 时间戳必须是 48bit 非负整数'); + } + let time = timestamp; + const timeChars: string[] = new Array(10); + for (let i = 9; i >= 0; i -= 1) { + timeChars[i] = ENCODING[time % ENCODING_LEN] ?? '0'; + time = Math.floor(time / ENCODING_LEN); + } + const randomChars = Array.from(randomBytes(16), (byte) => ENCODING[byte & 31] ?? '0'); + return `${timeChars.join('')}${randomChars.join('')}`; +} + +export function isUlid(value: string): boolean { + return value.length === ULID_LENGTH && ULID_PATTERN.test(value); +} + +/** 解析 ULID 前 10 位得到毫秒时间戳 */ +export function ulidToTimestamp(id: string): number { + if (!isUlid(id)) { + throw new TypeError(`不是合法 ULID:${id}`); + } + let timestamp = 0; + for (let i = 0; i < 10; i += 1) { + const char = id[i] ?? ''; + const value = ENCODING.indexOf(char); + if (value < 0) { + throw new TypeError(`ULID 含非法字符:${char}`); + } + timestamp = timestamp * ENCODING_LEN + value; + } + return timestamp; +} + +export function ulidToDate(id: string): Date { + return new Date(ulidToTimestamp(id)); +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..a50024e --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "noEmit": true, + "types": [] + }, + "include": ["src", "vitest.config.ts"] +} diff --git a/packages/shared/vitest.config.ts b/packages/shared/vitest.config.ts new file mode 100644 index 0000000..6310cef --- /dev/null +++ b/packages/shared/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.spec.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'json-summary'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.spec.ts', 'src/index.ts', 'src/**/index.ts'], + thresholds: { lines: 85, functions: 85, statements: 85, branches: 80 }, + }, + }, +}); diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..ac4f60b --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,58 @@ +{ + "name": "@lingxi/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./icons": { + "types": "./dist/icons.d.ts", + "import": "./dist/icons.js", + "require": "./dist/icons.cjs" + }, + "./theme/tokens.css": "./dist/theme/tokens.css", + "./styles.css": "./dist/styles.css", + "./semi.css": "./dist/semi.css" + }, + "typesVersions": { + "*": { + "icons": [ + "dist/icons.d.ts" + ], + "*": [ + "dist/index.d.ts" + ] + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts src/icons.ts --format esm,cjs --dts --clean --sourcemap && node scripts/copy-assets.mjs", + "dev": "tsup src/index.ts src/icons.ts --format esm,cjs --dts --watch", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "dependencies": { + "@douyinfe/semi-ui-19": "2.104.0", + "@remixicon/react": "4.9.0" + }, + "devDependencies": { + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "react": "19.3.1", + "react-dom": "19.3.1", + "tsup": "8.5.1", + "typescript": "5.9.3" + } +} diff --git a/packages/ui/scripts/copy-assets.mjs b/packages/ui/scripts/copy-assets.mjs new file mode 100644 index 0000000..b524fa6 --- /dev/null +++ b/packages/ui/scripts/copy-assets.mjs @@ -0,0 +1,13 @@ +import { cp, mkdir } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +await mkdir(resolve(root, 'dist/theme'), { recursive: true }); +await cp(resolve(root, 'src/theme/tokens.css'), resolve(root, 'dist/theme/tokens.css')); +await cp(resolve(root, 'src/styles/components.css'), resolve(root, 'dist/styles.css')); +await cp( + resolve(root, 'node_modules/@douyinfe/semi-ui-19/dist/css/semi.min.css'), + resolve(root, 'dist/semi.css'), +); diff --git a/packages/ui/src/components/DataTable.tsx b/packages/ui/src/components/DataTable.tsx new file mode 100644 index 0000000..94012a8 --- /dev/null +++ b/packages/ui/src/components/DataTable.tsx @@ -0,0 +1,50 @@ +import { Table } from '@douyinfe/semi-ui-19'; +import type { TableProps } from '@douyinfe/semi-ui-19/lib/es/table/interface'; +import type { ReactNode } from 'react'; + +import { PageEmpty } from './PageEmpty.js'; + +export interface DataTableProps> { + columns: NonNullable['columns']>; + dataSource: T[]; + loading?: boolean; + rowKey?: TableProps['rowKey']; + pagination?: TableProps['pagination']; + empty?: ReactNode; + size?: TableProps['size']; + scroll?: TableProps['scroll']; + className?: string; + onRow?: TableProps['onRow']; +} + +/** + * 列表表格统一封装:加载态 + 空态 + 行 key 约定。 + * 业务页只负责列定义与数据,不重复写 Empty / loading。 + */ +export function DataTable>({ + columns, + dataSource, + loading = false, + rowKey = 'id', + pagination = false, + empty, + size = 'middle', + scroll, + className, + onRow, +}: DataTableProps): ReactNode { + return ( + + className={`lx-data-table${className === undefined ? '' : ` ${className}`}`} + columns={columns} + dataSource={dataSource} + loading={loading} + rowKey={rowKey} + pagination={pagination} + size={size} + scroll={scroll} + onRow={onRow} + empty={empty ?? } + /> + ); +} diff --git a/packages/ui/src/components/ErrorState.tsx b/packages/ui/src/components/ErrorState.tsx new file mode 100644 index 0000000..44a5cdb --- /dev/null +++ b/packages/ui/src/components/ErrorState.tsx @@ -0,0 +1,42 @@ +import { Button } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; + +import { IconError, IconRefresh } from '../icons.js'; + +export interface ErrorStateProps { + title?: string; + message?: string; + /** 服务端返回的 trace_id;500 类错误必须展示(工程规范 04) */ + traceId?: string; + onRetry?: () => void; + retryText?: string; + className?: string; +} + +/** 统一错误态:说明 + trace_id + 重试 */ +export function ErrorState({ + title = '加载失败', + message = '请稍后重试;若持续失败,请把 trace_id 提供给管理员。', + traceId, + onRetry, + retryText = '重新加载', + className, +}: ErrorStateProps): ReactNode { + return ( +
+ +

{title}

+

{message}

+ {traceId === undefined || traceId.length === 0 ? null : ( +

trace_id: {traceId}

+ )} + {onRetry === undefined ? null : ( + + )} +
+ ); +} diff --git a/packages/ui/src/components/FormModal.tsx b/packages/ui/src/components/FormModal.tsx new file mode 100644 index 0000000..ada5ecb --- /dev/null +++ b/packages/ui/src/components/FormModal.tsx @@ -0,0 +1,47 @@ +import { Modal } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; + +export interface FormModalProps { + visible: boolean; + title: ReactNode; + children: ReactNode; + onCancel: () => void; + onOk: () => void; + confirmLoading?: boolean; + okText?: string; + cancelText?: string; + width?: number | string; + className?: string; +} + +/** 表单弹窗统一封装:确定 / 取消 / 提交中状态 */ +export function FormModal({ + visible, + title, + children, + onCancel, + onOk, + confirmLoading = false, + okText = '确定', + cancelText = '取消', + width = 560, + className, +}: FormModalProps): ReactNode { + return ( + + {children} + + ); +} diff --git a/packages/ui/src/components/PageEmpty.tsx b/packages/ui/src/components/PageEmpty.tsx new file mode 100644 index 0000000..9a0c381 --- /dev/null +++ b/packages/ui/src/components/PageEmpty.tsx @@ -0,0 +1,27 @@ +import { Empty } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; + +import { IconAsset } from '../icons.js'; + +export interface PageEmptyProps { + title?: ReactNode; + description?: ReactNode; + action?: ReactNode; + className?: string; +} + +/** 统一空态:标题 + 说明 + 可选主操作 */ +export function PageEmpty({ + title = '暂无数据', + description, + action, + className, +}: PageEmptyProps): ReactNode { + return ( +
+ } title={title} description={description}> + {action === undefined ? null :
{action}
} +
+
+ ); +} diff --git a/packages/ui/src/components/PageSkeleton.tsx b/packages/ui/src/components/PageSkeleton.tsx new file mode 100644 index 0000000..7ab2c21 --- /dev/null +++ b/packages/ui/src/components/PageSkeleton.tsx @@ -0,0 +1,27 @@ +import { Skeleton } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; + +export interface PageSkeletonProps { + loading?: boolean; + rows?: number; + children?: ReactNode; + className?: string; +} + +/** 统一加载骨架:页面级加载用段落占位,避免布局跳动 */ +export function PageSkeleton({ + loading = true, + rows = 6, + children, + className, +}: PageSkeletonProps): ReactNode { + return ( + } + className={`lx-page-skeleton${className === undefined ? '' : ` ${className}`}`} + > + {children} + + ); +} diff --git a/packages/ui/src/components/QueryForm.tsx b/packages/ui/src/components/QueryForm.tsx new file mode 100644 index 0000000..291d1f3 --- /dev/null +++ b/packages/ui/src/components/QueryForm.tsx @@ -0,0 +1,57 @@ +import { Button, Form, Space } from '@douyinfe/semi-ui-19'; +import type { ReactNode } from 'react'; + +import { IconRefresh, IconSearch } from '../icons.js'; + +export interface QueryFormProps { + children: ReactNode; + /** 查询动作;由页面自行发起请求 */ + onSubmit: () => void; + onReset?: () => void; + loading?: boolean; + /** 额外的右侧操作(如「新建」) */ + extra?: ReactNode; + className?: string; +} + +/** 列表页查询表单:字段一行内排列,查询 / 重置固位;筛选行不超过两行(设计约束) */ +export function QueryForm({ + children, + onSubmit, + onReset, + loading = false, + extra, + className, +}: QueryFormProps): ReactNode { + return ( +
{ + onSubmit(); + }} + > +
{children}
+ + + + {extra} + +
+ ); +} diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts new file mode 100644 index 0000000..d88e3c9 --- /dev/null +++ b/packages/ui/src/components/index.ts @@ -0,0 +1,6 @@ +export { DataTable } from './DataTable.js'; +export { ErrorState } from './ErrorState.js'; +export { FormModal } from './FormModal.js'; +export { PageEmpty } from './PageEmpty.js'; +export { PageSkeleton } from './PageSkeleton.js'; +export { QueryForm } from './QueryForm.js'; diff --git a/packages/ui/src/icons.ts b/packages/ui/src/icons.ts new file mode 100644 index 0000000..1febbbd --- /dev/null +++ b/packages/ui/src/icons.ts @@ -0,0 +1,63 @@ +/** + * Remix Icon 统一出口(AGENTS §3.3:业务代码只能从这里按语义名导入) + * 约定:对外只暴露 Icon* 语义名;新增图标时在这里做一次映射,不要在业务代码里直接引图标库。 + */ +export { + RiAddLine as IconAdd, + RiAlertLine as IconAlert, + RiAppsLine as IconApps, + RiFolderOpenLine as IconAsset, + RiArrowLeftLine as IconBack, + RiCalendarLine as IconCalendar, + RiBarChartLine as IconChart, + RiCheckLine as IconCheck, + RiArrowDownSLine as IconChevronDown, + RiArrowRightSLine as IconChevronRight, + RiTimeLine as IconClock, + RiCloseLine as IconClose, + RiFileCopyLine as IconCopy, + RiDashboardLine as IconDashboard, + RiDeleteBinLine as IconDelete, + RiSendPlaneLine as IconDelivery, + RiBookOpenLine as IconDocs, + RiDownloadLine as IconDownload, + RiDragMoveLine as IconDrag, + RiEditLine as IconEdit, + RiErrorWarningLine as IconError, + RiExportLine as IconExport, + RiFilter3Line as IconFilter, + RiGlobalLine as IconGlobal, + RiLayoutGridLine as IconGrid, + RiHome5Line as IconHome, + RiImageAddLine as IconImageAdd, + RiLoader4Line as IconLoading, + RiLockLine as IconLock, + RiLogoutBoxRLine as IconLogout, + RiMailLine as IconMail, + RiImageLine as IconMaterial, + RiMenuLine as IconMenu, + RiMessage3Line as IconMessage, + RiMoreLine as IconMore, + RiMusicLine as IconMusic, + RiNotification3Line as IconNotification, + RiPaletteLine as IconPalette, + RiPauseLine as IconPause, + RiPlayLine as IconPlay, + RiRefreshLine as IconRefresh, + RiRobotLine as IconRobot, + RiSaveLine as IconSave, + RiFileTextLine as IconScript, + RiSearchLine as IconSearch, + RiServerLine as IconServer, + RiSettings3Line as IconSettings, + RiShareLine as IconShare, + RiShieldKeyholeLine as IconShield, + RiStackLine as IconStack, + RiStarLine as IconStar, + RiTaskLine as IconTask, + RiThumbUpLine as IconThumbUp, + RiUploadLine as IconUpload, + RiUserLine as IconUser, + RiVideoLine as IconVideo, + RiEyeLine as IconView, +} from '@remixicon/react'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 0000000..348dfd2 --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1,4 @@ +export * from './components/index.js'; +export * from './locale.js'; +export * from './notify.js'; +export * from './theme/tokens.js'; diff --git a/packages/ui/src/locale.ts b/packages/ui/src/locale.ts new file mode 100644 index 0000000..76a17a9 --- /dev/null +++ b/packages/ui/src/locale.ts @@ -0,0 +1,5 @@ +import type { Locale } from '@douyinfe/semi-ui-19/lib/es/locale/interface'; +import zhCN from '@douyinfe/semi-ui-19/lib/es/locale/source/zh_CN'; + +/** 全站默认中文语言包(Semi LocaleProvider) */ +export const localeZhCN: Locale = zhCN; diff --git a/packages/ui/src/notify.ts b/packages/ui/src/notify.ts new file mode 100644 index 0000000..2bbe32f --- /dev/null +++ b/packages/ui/src/notify.ts @@ -0,0 +1,19 @@ +import { Toast } from '@douyinfe/semi-ui-19'; + +/** 统一成功提示 */ +export function notifySuccess(content: string): void { + Toast.success({ content, duration: 2 }); +} + +/** 统一错误提示;带 trace_id 时一并展示,便于用户报障(工程规范 04) */ +export function notifyError(message: string, traceId?: string): void { + Toast.error({ + content: + traceId === undefined || traceId.length === 0 ? message : `${message}(trace: ${traceId})`, + duration: 4, + }); +} + +export function notifyWarning(content: string): void { + Toast.warning({ content, duration: 3 }); +} diff --git a/packages/ui/src/styles/components.css b/packages/ui/src/styles/components.css new file mode 100644 index 0000000..c3e4ac6 --- /dev/null +++ b/packages/ui/src/styles/components.css @@ -0,0 +1,100 @@ +/* 灵犀 UI 包组件样式:只使用 tokens.css 中的语义变量,不写颜色字面量 */ +.lx-empty { + padding: 48px 0; + text-align: center; +} + +.lx-empty__action { + margin-top: var(--lx-space-4); +} + +.lx-error-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--lx-space-2); + padding: 64px var(--lx-space-6); + text-align: center; +} + +.lx-error-state__icon { + color: var(--lx-color-danger); + display: inline-flex; +} + +.lx-error-state__title { + margin: var(--lx-space-2) 0 0; + color: var(--lx-color-text-primary); + font-size: var(--lx-font-size-lg); + line-height: var(--lx-line-height-tight); +} + +.lx-error-state__message { + margin: 0; + max-width: 42ch; + color: var(--lx-color-text-secondary); + font-size: var(--lx-font-size-sm); +} + +.lx-error-state__trace { + margin: 0; + color: var(--lx-color-text-tertiary); + font-family: var(--lx-font-mono); + font-size: var(--lx-font-size-xs); +} + +.lx-error-state__retry { + margin-top: var(--lx-space-3); +} + +.lx-page-skeleton { + padding: var(--lx-space-4) 0; +} + +.lx-query-form { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--lx-space-4); + margin-bottom: var(--lx-space-4); +} + +.lx-query-form__fields { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + gap: var(--lx-space-2) var(--lx-space-4); + min-width: 0; +} + +.lx-query-form__actions { + flex: 0 0 auto; +} + +.lx-data-table .semi-table-container { + border: 1px solid var(--lx-color-border); + border-radius: var(--lx-radius-md); + overflow: hidden; +} + +.lx-data-table .semi-table-thead > .semi-table-row > .semi-table-row-head { + background: var(--lx-color-bg-panel); + color: var(--lx-color-text-secondary); + font-size: var(--lx-font-size-xs); + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.lx-data-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell { + border-bottom: 1px solid var(--lx-color-border); +} + +.lx-data-table .semi-table-tbody > .semi-table-row:hover > .semi-table-row-cell { + background: var(--lx-color-bg-hover); +} + +.lx-form-modal .semi-modal-content { + background: var(--lx-color-bg-elevated); +} diff --git a/packages/ui/src/theme/tokens.css b/packages/ui/src/theme/tokens.css new file mode 100644 index 0000000..ac41160 --- /dev/null +++ b/packages/ui/src/theme/tokens.css @@ -0,0 +1,114 @@ +:root { + /* 方向 A · 云白蓝(Cloud Blue):传统企业后台骨架 + 现代浅色质感 */ + --lx-color-bg-app: #f5f7fa; + --lx-color-bg-panel: #ffffff; + --lx-color-bg-elevated: #ffffff; + --lx-color-bg-subtle: #fafbfc; + --lx-color-bg-hover: #f2f4f7; + --lx-color-bg-active: #e8f1ff; + --lx-color-border: #e5e8ef; + --lx-color-border-strong: #d0d5dd; + + --lx-color-text-primary: #1f2329; + --lx-color-text-secondary: #646a73; + --lx-color-text-tertiary: #8f959e; + --lx-color-text-inverse: #ffffff; + + --lx-color-brand: #1677ff; + --lx-color-brand-hover: #4096ff; + --lx-color-brand-pressed: #0958d9; + --lx-color-brand-soft: #e8f1ff; + --lx-color-on-brand: #ffffff; + --lx-color-focus: #1677ff; + + --lx-color-success: #00b42a; + --lx-color-warning: #ff7d00; + --lx-color-danger: #f53f3f; + --lx-color-info: #1677ff; + + --lx-color-chart-1: #1677ff; + --lx-color-chart-2: #00b42a; + --lx-color-chart-3: #ff7d00; + --lx-color-chart-4: #722ed1; + --lx-color-chart-5: #eb2f96; + + /* 字体 */ + --lx-font-sans: + 'PingFang SC', 'HarmonyOS Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, + 'Segoe UI', sans-serif; + --lx-font-mono: 'SF Mono', 'JetBrains Mono', 'Roboto Mono', ui-monospace, monospace; + + --lx-font-size-xs: 12px; + --lx-font-size-sm: 13px; + --lx-font-size-md: 14px; + --lx-font-size-lg: 16px; + --lx-font-size-xl: 20px; + --lx-font-size-display: 28px; + + --lx-line-height-tight: 1.25; + --lx-line-height-normal: 1.55; + + /* 间距(4px 基线) */ + --lx-space-1: 4px; + --lx-space-2: 8px; + --lx-space-3: 12px; + --lx-space-4: 16px; + --lx-space-5: 20px; + --lx-space-6: 24px; + --lx-space-8: 32px; + --lx-space-10: 40px; + + /* 结构尺寸:顶部模块菜单 + 左侧功能菜单 */ + --lx-topbar-height: 56px; + --lx-sidebar-width: 216px; + --lx-sidebar-collapsed-width: 56px; + --lx-content-padding: 24px; + + --lx-radius-sm: 4px; + --lx-radius-md: 6px; + --lx-radius-lg: 10px; + --lx-radius-pill: 999px; + + --lx-shadow-panel: 0 1px 2px rgba(31, 35, 41, 0.04); + --lx-shadow-overlay: 0 12px 32px rgba(31, 35, 41, 0.14); + --lx-shadow-focus: 0 0 0 3px rgba(22, 119, 255, 0.16); + + --lx-motion-fast: 120ms; + --lx-motion-base: 180ms; + --lx-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1); +} + +/* Semi Design 变量桥接:业务侧只使用 --lx-*,Semi 组件跟随同一套 token */ +body[theme-mode='light'] { + --semi-color-primary: var(--lx-color-brand); + --semi-color-primary-hover: var(--lx-color-brand-hover); + --semi-color-primary-active: var(--lx-color-brand-pressed); + --semi-color-primary-disabled: var(--lx-color-border-strong); + --semi-color-primary-light-default: var(--lx-color-brand-soft); + --semi-color-primary-light-hover: #dbe9ff; + --semi-color-primary-light-active: #cfe1ff; + + --semi-color-bg-0: var(--lx-color-bg-app); + --semi-color-bg-1: var(--lx-color-bg-panel); + --semi-color-bg-2: var(--lx-color-bg-panel); + --semi-color-bg-3: var(--lx-color-bg-panel); + --semi-color-bg-4: var(--lx-color-bg-hover); + + --semi-color-text-0: var(--lx-color-text-primary); + --semi-color-text-1: var(--lx-color-text-primary); + --semi-color-text-2: var(--lx-color-text-secondary); + --semi-color-text-3: var(--lx-color-text-tertiary); + + --semi-color-border: var(--lx-color-border); + --semi-color-fill-0: var(--lx-color-bg-hover); + --semi-color-fill-1: #eef1f5; + --semi-color-fill-2: var(--lx-color-bg-active); + + --semi-color-danger: var(--lx-color-danger); + --semi-color-success: var(--lx-color-success); + --semi-color-warning: var(--lx-color-warning); + --semi-color-info: var(--lx-color-info); + + --semi-color-focus-border: var(--lx-color-focus); + --semi-color-shadow: var(--lx-shadow-overlay); +} diff --git a/packages/ui/src/theme/tokens.ts b/packages/ui/src/theme/tokens.ts new file mode 100644 index 0000000..b081830 --- /dev/null +++ b/packages/ui/src/theme/tokens.ts @@ -0,0 +1,15 @@ +/** + * 主题 token 的 JS 副本(供 JS 计算场景使用,如 ECharts / Canvas) + * 注意:颜色字面量只允许出现在 packages/ui/src/theme/(工程规范 03 §3.2)。 + */ +export const themeTokens = { + colorBrand: '#f5a524', + colorBrandHover: '#ffb84d', + colorSuccess: '#3fb950', + colorWarning: '#d29922', + colorDanger: '#f85149', + colorInfo: '#58a6ff', + chartColors: ['#f5a524', '#58a6ff', '#3fb950', '#d29922', '#a371f7'], +} as const; + +export type ThemeTokens = typeof themeTokens; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..4e55bb6 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "noEmit": true, + "types": [] + }, + "include": ["src"] +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..25ccf46 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,36 @@ +import { defineConfig, devices } from '@playwright/test'; + +const channel = process.env.CI ? undefined : 'chrome'; + +export default defineConfig({ + testDir: './e2e', + timeout: 60_000, + expect: { timeout: 10_000 }, + fullyParallel: true, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 2 : 0, + reporter: process.env.CI + ? [['list'], ['html', { open: 'never' }], ['junit', { outputFile: 'test-results/e2e.xml' }]] + : [['list'], ['html', { open: 'never' }]], + use: { + baseURL: 'http://127.0.0.1:4173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + channel, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], channel }, + }, + ], + webServer: { + command: 'node node_modules/vite/bin/vite.js preview --host 127.0.0.1 --port 4173 --strictPort', + cwd: 'apps/web', + url: 'http://127.0.0.1:4173/login', + reuseExistingServer: false, + timeout: 60_000, + stdout: 'ignore', + stderr: 'pipe', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..a7e5625 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9932 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + fastify: 5.12.7 + +importers: + + .: + devDependencies: + '@commitlint/cli': + specifier: 20.5.3 + version: 20.5.3(@types/node@24.19.2)(conventional-commits-parser@6.4.0)(typescript@5.9.3) + '@commitlint/config-conventional': + specifier: 20.5.3 + version: 20.5.3 + '@eslint/js': + specifier: 9.39.5 + version: 9.39.5 + '@playwright/test': + specifier: 1.59.1 + version: 1.59.1 + '@prisma/client': + specifier: 6.19.3 + version: 6.19.3(prisma@6.19.3(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) + '@types/node': + specifier: 24.19.2 + version: 24.19.2 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitest/coverage-v8': + specifier: 3.2.7 + version: 3.2.7(vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1)) + dotenv: + specifier: 18.0.9 + version: 18.0.9 + eslint: + specifier: 9.39.5 + version: 9.39.5(jiti@2.7.0) + eslint-import-resolver-typescript: + specifier: 4.4.5 + version: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import-x: + specifier: 4.17.1 + version: 4.17.1(@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-jsx-a11y: + specifier: 6.10.2 + version: 6.10.2(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react-hooks: + specifier: 7.1.1 + version: 7.1.1(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react-refresh: + specifier: 0.5.7 + version: 0.5.7(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-simple-import-sort: + specifier: 14.0.0 + version: 14.0.0(eslint@9.39.5(jiti@2.7.0)) + globals: + specifier: 17.14.0 + version: 17.14.0 + husky: + specifier: 9.1.7 + version: 9.1.7 + lint-staged: + specifier: 16.4.0 + version: 16.4.0 + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) + prettier: + specifier: 3.9.10 + version: 3.9.10 + prisma: + specifier: 6.19.3 + version: 6.19.3(magicast@0.3.5)(typescript@5.9.3) + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + tsx: + specifier: 4.23.15 + version: 4.23.15 + turbo: + specifier: 2.11.10 + version: 2.11.10 + typescript: + specifier: 5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: 8.72.1 + version: 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + vitest: + specifier: 3.2.7 + version: 3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1) + + apps/api: + dependencies: + '@fastify/cors': + specifier: 11.3.1 + version: 11.3.1 + '@fastify/helmet': + specifier: 13.2.0 + version: 13.2.0 + '@fastify/static': + specifier: 10.1.8 + version: 10.1.8 + '@lingxi/shared': + specifier: workspace:* + version: link:../../packages/shared + '@nestjs/common': + specifier: 11.2.8 + version: 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: 11.2.8 + version: 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-fastify': + specifier: 11.2.8 + version: 11.2.8(@fastify/static@10.1.8)(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/swagger': + specifier: 11.4.7 + version: 11.4.7(@fastify/static@10.1.8)(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + '@prisma/client': + specifier: 6.19.3 + version: 6.19.3(prisma@6.19.3(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) + dotenv: + specifier: 18.0.9 + version: 18.0.9 + fastify: + specifier: 5.12.7 + version: 5.12.7 + ioredis: + specifier: 5.11.1 + version: 5.11.1 + nestjs-pino: + specifier: 5.3.1 + version: 5.3.1(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.4.0)(rxjs@7.8.2) + pino: + specifier: 10.4.0 + version: 10.4.0 + pino-http: + specifier: 11.0.0 + version: 11.0.0 + reflect-metadata: + specifier: 0.2.2 + version: 0.2.2 + rxjs: + specifier: 7.8.2 + version: 7.8.2 + zod: + specifier: 3.25.76 + version: 3.25.76 + devDependencies: + '@nestjs/testing': + specifier: 11.2.8 + version: 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@types/node': + specifier: 24.19.2 + version: 24.19.2 + '@vitest/coverage-v8': + specifier: 3.2.7 + version: 3.2.7(vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1)) + pino-pretty: + specifier: 13.2.0 + version: 13.2.0 + tsx: + specifier: 4.23.15 + version: 4.23.15 + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 3.2.7 + version: 3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1) + + apps/web: + dependencies: + '@douyinfe/semi-ui-19': + specifier: 2.104.0 + version: 2.104.0(@floating-ui/dom@1.8.0)(@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@lingxi/shared': + specifier: workspace:* + version: link:../../packages/shared + '@lingxi/ui': + specifier: workspace:* + version: link:../../packages/ui + '@tanstack/react-query': + specifier: 5.105.1 + version: 5.105.1(@types/react@19.3.0)(react@19.3.1) + react: + specifier: 19.3.1 + version: 19.3.1 + react-dom: + specifier: 19.3.1 + version: 19.3.1(react@19.3.1) + react-router: + specifier: 7.18.5 + version: 7.18.5(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + zod: + specifier: 3.25.76 + version: 3.25.76 + zustand: + specifier: 5.0.16 + version: 5.0.16(@types/react@19.3.0)(react@19.3.1)(use-sync-external-store@1.7.0(react@19.3.1)) + devDependencies: + '@testing-library/react': + specifier: 16.3.3 + version: 16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 5.2.0 + version: 5.2.0(vite@6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@vitest/coverage-v8': + specifier: 3.2.7 + version: 3.2.7(vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1)) + jsdom: + specifier: 28.1.0 + version: 28.1.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: 6.4.5 + version: 6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + vitest: + specifier: 3.2.7 + version: 3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1) + + apps/worker: + dependencies: + '@lingxi/shared': + specifier: workspace:* + version: link:../../packages/shared + dotenv: + specifier: 18.0.9 + version: 18.0.9 + ioredis: + specifier: 5.11.1 + version: 5.11.1 + pino: + specifier: 10.4.0 + version: 10.4.0 + zod: + specifier: 3.25.76 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: 24.19.2 + version: 24.19.2 + tsx: + specifier: 4.23.15 + version: 4.23.15 + typescript: + specifier: 5.9.3 + version: 5.9.3 + + packages/ai-sdk: + devDependencies: + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + + packages/form-engine: + devDependencies: + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + + packages/render: + devDependencies: + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + + packages/shared: + dependencies: + zod: + specifier: 3.25.76 + version: 3.25.76 + devDependencies: + '@vitest/coverage-v8': + specifier: 3.2.7 + version: 3.2.7(vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1)) + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 3.2.7 + version: 3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1) + + packages/ui: + dependencies: + '@douyinfe/semi-ui-19': + specifier: 2.104.0 + version: 2.104.0(@floating-ui/dom@1.8.0)(@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@remixicon/react': + specifier: 4.9.0 + version: 4.9.0(react@19.3.1) + devDependencies: + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + react: + specifier: 19.3.1 + version: 19.3.1 + react-dom: + specifier: 19.3.1 + version: 19.3.1(react@19.3.1) + tsup: + specifier: 8.5.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + +packages: + + '@acemir/cssom@0.9.32': + resolution: {integrity: sha512-OijrAUu7rrJhSIoHNg6LMPS5e344nwUHA+1EFYYjrh64D+GmytUsfBQjci45TjT+rd5Spsp8B5xsZDT+TgyXXQ==} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.12': + resolution: {integrity: sha512-2W76mhjrpQFO1lK5e8IvGYB2D0YZ7S2R1Z2BjO2ZUYuW+amcpK/z60U6ZZmzskwoa7sJ6XohP/Wg8H5KxXI/IA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.11': + resolution: {integrity: sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.10': + resolution: {integrity: sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.12': + resolution: {integrity: sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.10': + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.12': + resolution: {integrity: sha512-MscXe/NAZcOkYEsRajHZFt3ynsb22PUi6CTrp5U7+RYycR9By84mn9mFN54YgfITiqK4hFx3Ju3tZSyzfpS5ew==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.11': + resolution: {integrity: sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@borewit/text-codec@0.2.3': + resolution: {integrity: sha512-49CycTz1aAZmvFcwkeFyhud1HUmRIB5Tabf7ALV+VA4Iu85MTfz2cUwzPagMFAdVBvrWdkCxG67I30tUwwpKsw==} + + '@bramus/specificity@2.4.3': + resolution: {integrity: sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==} + hasBin: true + + '@commitlint/cli@20.5.3': + resolution: {integrity: sha512-OJdL0EXWD5y9LPa0nr/geOwzaS8BsdaybKkcloB0JgsguGxNv2R+hC2FTPqrAcprg35zF33KOQerY0x8W1aesA==} + engines: {node: '>=v18'} + hasBin: true + + '@commitlint/config-conventional@20.5.3': + resolution: {integrity: sha512-j34Qqeaa152chJgz2ysyk0BCpHenJn1lV0Rx0VXf8k3ccQcED+48EZrzMvo9jLmJUyBrrBwvu89I+2er4gW7QQ==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@20.5.0': + resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==} + engines: {node: '>=v18'} + + '@commitlint/ensure@20.5.3': + resolution: {integrity: sha512-4i4AgNvH62owG9MwSiWKrle7HGNpBHHdLnWFIp5fTsHUYe5kRuh15t08L/0pdbbrRk8JKXQxxN4hZQcn+szkrw==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@20.0.0': + resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} + engines: {node: '>=v18'} + + '@commitlint/format@20.5.0': + resolution: {integrity: sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==} + engines: {node: '>=v18'} + + '@commitlint/is-ignored@20.5.0': + resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==} + engines: {node: '>=v18'} + + '@commitlint/lint@20.5.3': + resolution: {integrity: sha512-M7JbWBNr2gXKaPc4i/KipsuW1gkDHpj35KPjWtKy3Z+2AQw5wu1gBi1LIO0uoaij67CqY4K8PxPZSGens4evCw==} + engines: {node: '>=v18'} + + '@commitlint/load@20.5.3': + resolution: {integrity: sha512-1FDZWuKyu98Myb8i7Tp31jPU2rZpOwAdYRyJcy2KoGg7Xk2A+bgHN8smhMaaNSNkmE8fwt53BokywZq8Gv/5XQ==} + engines: {node: '>=v18'} + + '@commitlint/message@20.4.3': + resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==} + engines: {node: '>=v18'} + + '@commitlint/parse@20.5.0': + resolution: {integrity: sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==} + engines: {node: '>=v18'} + + '@commitlint/read@20.5.0': + resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@20.5.3': + resolution: {integrity: sha512-+ogW9v/u9JqpvAgTrLra/YTFo0KkjU6iNblF89pPsj4NebNc+DAWctsludwezI8YnsjBmfHpApSwcXprN/f/ew==} + engines: {node: '>=v18'} + + '@commitlint/rules@20.5.3': + resolution: {integrity: sha512-MPlMnb9D3wbszYMp+1hPtuhtPJndRo6I6yfkZVA4+jR8w7Kqp0u2u/Y+gzbaItx5Lltq5rw7FSZQWJMoXUC4NQ==} + engines: {node: '>=v18'} + + '@commitlint/to-lines@20.0.0': + resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} + engines: {node: '>=v18'} + + '@commitlint/top-level@20.4.3': + resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==} + engines: {node: '>=v18'} + + '@commitlint/types@20.5.0': + resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==} + engines: {node: '>=v18'} + + '@conventional-changelog/git-client@2.7.0': + resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==} + engines: {node: '>=18'} + peerDependencies: + conventional-commits-filter: ^5.0.0 + conventional-commits-parser: ^6.4.0 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-color-parser@4.2.7': + resolution: {integrity: sha512-zgm9yjQz5iJB4VL8RyVwd71TWB8Q89FSxt/NzXoWqPSkEBOVtn9L6vAPtSc9hpKfB0Awo/AL2VKFHjYetBLFHQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16': + resolution: {integrity: sha512-LsDbJPP/B8cPasUJyTA5S99F7C4G93ngW8hUluXz8CFeOaaRnBPtBiIlvgcPYU0HmHNz8CXg+1vwFrIB4F6Dgw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} + engines: {node: '>=20.19.0'} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@7.0.2': + resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==} + peerDependencies: + '@dnd-kit/core': ^6.0.7 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@douyinfe/semi-animation-react@2.104.0': + resolution: {integrity: sha512-jnJHgSjnJO1e87w9AXSQXSTLW10m322uuOJiA8PpHCuhkobH26ZnteZYfgNfa0OsR9Q0HIVJPfuL0UJmwHgX8Q==} + + '@douyinfe/semi-animation-styled@2.104.0': + resolution: {integrity: sha512-kZ+/8GCcpkSXg0AfWg5tez+5KaDH16d5QgsQXGJOyBJOQYaCNqguaQxqEsHATTJWbSt7f23BnZEvyzgIjPqKzA==} + + '@douyinfe/semi-animation@2.104.0': + resolution: {integrity: sha512-JAuy4KZdNfoSxFOwdFuAamhcYJRwU0S2BRGEJ8RibX3sIRsfPLt4DHnO6UmCE+DEptfci9tvVu76UExlSkvT/g==} + + '@douyinfe/semi-foundation@2.104.0': + resolution: {integrity: sha512-EyGrklNxEDuyLH68wfgktE5GpUSjr7pkwcaAhlptx7GWRSSBERbMfdHwgkR1/hB299lHnu2cIQ5k99kQEpLyxg==} + + '@douyinfe/semi-icons@2.104.0': + resolution: {integrity: sha512-Gk+DSuci4nAUM+qfqppzVlxzBPjzKs40YT2NQaAWqPhX68VQLtOAtQffZW0SL27a0/9ucv6qP1ay9S7a+/W4+w==} + peerDependencies: + react: '>=16.0.0' + + '@douyinfe/semi-illustrations@2.104.0': + resolution: {integrity: sha512-zNl/P5szsbBpdz8b/Wkd7vMwgdZEmu9ubxB9sMni5j/mGKolb4GHhNiJJZ0ssDCu3GlxApmXdT96iUlxCJI6fQ==} + peerDependencies: + react: '>=16.0.0' + + '@douyinfe/semi-json-viewer-core@2.104.0': + resolution: {integrity: sha512-kw9E2QCfQED5Y9a/PseZOe9/atO1grdZkSmNoCrJKpa1X+o1a/VTodnbvmwbKWtqNZ3xt6Bf6pTA4whf/9JNXA==} + + '@douyinfe/semi-theme-default@2.104.0': + resolution: {integrity: sha512-eD+FC/OxMrBVKpWmfJ3NLgzK5clzEc5q6KH6dI3T5qnB28Fc9I9Vsri/UaHC0wLGOujFO0eX95OeWow1v1DacQ==} + + '@douyinfe/semi-ui-19@2.104.0': + resolution: {integrity: sha512-No7sDHvoPVGbe6GYkmwTylyyywojJ4nZyqKhT66g4b3w3Cxc6hFyQqZQghz64HQpVENj2STuuVGFi401Yzoi7A==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^19.0.0 + react-dom: ^19.0.0 + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.7': + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@exodus/bytes@1.16.0': + resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@fastify/accept-negotiator@2.1.2': + resolution: {integrity: sha512-LV7MZqTwXTEt3GxBT6BU8ACztWnv++hSkMEsP3usIHiI0tA4F5EO4THhDCOr5J/ginM9DCnLCmZ6u3JLA9x8Iw==} + + '@fastify/ajv-compiler@4.0.7': + resolution: {integrity: sha512-VP0yIfwlc1ohj733keYDqyHm1VWeP9VQxYomunhr90jkC4aNNibbkrRiaNgPgqj1ORxUenILR4qzA/ppYBNAtQ==} + + '@fastify/cors@11.3.0': + resolution: {integrity: sha512-ggQGua+xHv1MvePbPr0v//xLYEsCXbWspquXCJS9Ot5YoRXq8J8ZWzHnxDBVnbtXosvistXo6LtNzOJswf64Fw==} + + '@fastify/cors@11.3.1': + resolution: {integrity: sha512-gZXNzd6FLHzOIfvoK4+Az3Wko2V6Y0MIsLxpyo4AEYl7a9q1JBPwPaxLea4nkr++8FCiJiPpYYo6E02uZW0dqw==} + + '@fastify/deepmerge@3.2.4': + resolution: {integrity: sha512-kc8GQq5gX9RRhw0rov3DMrN8wS8yRlxJRdDzsYwwdBV/wazuVgc/1K7CKL90tlvu3RlHan6Y0R0aE3nJtBz2eQ==} + + '@fastify/error@4.2.1': + resolution: {integrity: sha512-MYBumuIwP+5MIpXSyKm6/o1Eigcoij186lWUssX3LH5U8KWqU0ktZhzC4CErIX9EJeQeRGI+zJZQWakBlFbUcg==} + + '@fastify/fast-json-stringify-compiler@5.1.1': + resolution: {integrity: sha512-pyxRKk7UpUnnrztP9pF4+ZYxusEQ4LifTyzRdiXmyql1QezNHxO02D25MVU2o4kMeAMYWAjG6RKRlojgkbiDAw==} + + '@fastify/formbody@8.0.2': + resolution: {integrity: sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==} + + '@fastify/forwarded@3.1.2': + resolution: {integrity: sha512-89WiRG+hW3VQSfCwg4Hb8b43PeyEX15K5+lEfsZ4EUth/FyOjS/lv8HsVuswUNXWiSnxMGNouE/fq/0w3VumHQ==} + + '@fastify/helmet@13.2.0': + resolution: {integrity: sha512-2u41U5hR9o1u3HUHLT3Njp7vWuWw+XXw8qCBwVY17pRQAN179YYKiU9f7/qEn1ITabgNTkq/Yk6gmrYPSraagA==} + + '@fastify/merge-json-schemas@0.2.2': + resolution: {integrity: sha512-d/gx9sRbUCVgGRGzma46wLcwiY8noTqO2Nmjg/vbTARxHRE5bdh5PIL8s97Lt1X7ScRWRW52eVYxwIoPwIdVUA==} + + '@fastify/middie@9.3.4': + resolution: {integrity: sha512-dd8phTnVWzx3iKPBKSvtRUT8+Igvy/MP+P5HcY2tjejEyeVbyjkD4HgzKessUObue5RRM5J6PZdxtAqgbn84yw==} + + '@fastify/proxy-addr@5.1.2': + resolution: {integrity: sha512-uqB4QsgVoro0XDC+DIftIMGXHdBPn+lK6Ek3xXAIxPMCJfqxnbtyEoMCm2KPUVOuIosd7Cyw9HawEqMFM/UA+g==} + + '@fastify/send@4.2.0': + resolution: {integrity: sha512-B5Hr+IFsPbqJJQu6FHE2MFpnHfbPlCOVQUPTdm6PxCcilYGrEQsulbO0z9blgbjndIyLJwa2NWFIIjXpPhbwUg==} + + '@fastify/static@10.1.8': + resolution: {integrity: sha512-3CbgWOXJX8f46xxsuOgQl9h0lyQQiABMG40kxRF5PWJk4LyBW5m3mLZoW5glnm2TUTC1gVFCBnqOt5S6zIIIrg==} + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nestjs/common@11.2.8': + resolution: {integrity: sha512-5NobtSscYwgS9r2p+ADCJCu9Fy164wRuGVTUJxoa5I3rLIVjRFWFlV+UA37bKf8Om+4tSGJe4j92zOVnISbdbw==} + peerDependencies: + class-transformer: '>=0.4.1' + class-validator: '>=0.13.2' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/core@11.2.8': + resolution: {integrity: sha512-XzD1okTrM160+8M9mWWorEUlFPK6ZMjTy6pVxVOOdxSdqu+QWKKx9kI2Y0yVrvEmi/0HPFUErqi9PCKFZYYZfg==} + engines: {node: '>= 20'} + peerDependencies: + '@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 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/mapped-types@2.1.1': + resolution: {integrity: sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/platform-fastify@11.2.8': + resolution: {integrity: sha512-Aqz+MGImjRwuv5E8nmVjg9KZ9voxqtXF08HrXgxjTmyw/VdlnagszSeBkCx/v7Bp6ha514n2X4Yjv6xTYzEB+w==} + peerDependencies: + '@fastify/static': ^10.1.2 + '@fastify/view': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + '@fastify/view': + optional: true + + '@nestjs/swagger@11.4.7': + resolution: {integrity: sha512-QyDYnmfP4IRucgmtQxMqzgRBdWtjFoDp8eFvvgf92+3wdLCL+Q0xOFO1948j/ntW/Wi7qT2dyck6ka8ADzPWQQ==} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/common': ^11.0.1 + '@nestjs/core': ^11.0.1 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/testing@11.2.8': + resolution: {integrity: sha512-ng3NlbDHehZ6I14R41Q5KFTEfRSkBSKEjnUTwMVCD1oc6KWnzmwW3Ct8BcS15WlGlxC6gmKCibJle9eclvaOaA==} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.59.1': + resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + engines: {node: '>=18'} + hasBin: true + + '@prisma/client@6.19.3': + resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.19.3': + resolution: {integrity: sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==} + + '@prisma/debug@6.19.3': + resolution: {integrity: sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==} + + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': + resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==} + + '@prisma/engines@6.19.3': + resolution: {integrity: sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==} + + '@prisma/fetch-engine@6.19.3': + resolution: {integrity: sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==} + + '@prisma/get-platform@6.19.3': + resolution: {integrity: sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==} + + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.22': + resolution: {integrity: sha512-dEyx4uHh6Pc6ZkdU9Xe8rA4h12Odq6QKU/fqseBn07xwVwskhHiF3KHhqmw5BR+jAiFTvKTmwCafFql/k1pSzA==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + + '@remixicon/react@4.9.0': + resolution: {integrity: sha512-5/jLDD4DtKxH2B4QVXTobvV1C2uL8ab9D5yAYNtFt+w80O0Ys1xFOrspqROL3fjrZi+7ElFUWE37hBfaAl6U+Q==} + peerDependencies: + react: '>=18.2.0' + + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + + '@rollup/rollup-android-arm-eabi@4.67.4': + resolution: {integrity: sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.67.4': + resolution: {integrity: sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-android-x64@4.67.4': + resolution: {integrity: sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==} + cpu: [x64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.67.4': + resolution: {integrity: sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.67.4': + resolution: {integrity: sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.67.4': + resolution: {integrity: sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.67.4': + resolution: {integrity: sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + resolution: {integrity: sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + resolution: {integrity: sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + resolution: {integrity: sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.67.4': + resolution: {integrity: sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + resolution: {integrity: sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.67.4': + resolution: {integrity: sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + resolution: {integrity: sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + resolution: {integrity: sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + resolution: {integrity: sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + resolution: {integrity: sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + resolution: {integrity: sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.67.4': + resolution: {integrity: sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.67.4': + resolution: {integrity: sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.67.4': + resolution: {integrity: sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.67.4': + resolution: {integrity: sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + resolution: {integrity: sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + resolution: {integrity: sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.67.4': + resolution: {integrity: sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.67.4': + resolution: {integrity: sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==} + cpu: [x64] + os: [win32] + + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + + '@simple-libs/child-process-utils@1.0.2': + resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} + engines: {node: '>=18'} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tanstack/query-core@5.105.1': + resolution: {integrity: sha512-B/rNlZoZ54w1dlddiUt9oEe1SLM+37c00SKLJEg9YH+7as3Y54P81o9Fh+2ujZ36tXPnQJ5oVe7AcyEWkyFhPg==} + + '@tanstack/react-query@5.105.1': + resolution: {integrity: sha512-qcu+/+PtBOjW8qU4/0l47QVmNG7hPOWsCLzP0bTRhE1X7HYa757C4732wXTUH0QhCjTV1yedX8onsCiEwlNYrA==} + peerDependencies: + '@types/react': ^18 || ^19 + react: ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.3': + resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tiptap/core@3.32.2': + resolution: {integrity: sha512-EitM8KY3agYfnp/bqhfxcaReVq4CUqVzMqoSIl7xSfYABTGYoTDtVXUdYSGutcCBFjc86BbXjrOlDlqkgC8BsQ==} + peerDependencies: + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-blockquote@3.32.2': + resolution: {integrity: sha512-VLcdnbOm7G52NpUhgbyo3XIOz6Eky7/DyGYuYnCpEUe0TE5xk7Qs6ukrh9vaf2XhyeLDHsmVZuzybGrdtqoC0w==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-bold@3.32.2': + resolution: {integrity: sha512-5Q8bScZEZbIBB2tBgnc7Cr6XuH2Nt/6l1HP3FO0wWO7nqXPOcodEbjslePI3v1iag+LNWX16BZ8fDrJr/PbxXA==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-bubble-menu@3.32.2': + resolution: {integrity: sha512-mwxWtLHiwhtzXpvdjoegH10eIA8CJr5INGAzUSfzRp4v13zH3rDbsoafljwEi+V5JfcSY/bbXf1akOtsny4yXw==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-bullet-list@3.32.2': + resolution: {integrity: sha512-YP7IlC3swrNPlnFHIgVDsDl70geqqWtcAkbuncQEs5FJ9UQg432VwVfwCUW4NsG532p5AqlTTwOKcSLS7p/pqA==} + peerDependencies: + '@tiptap/extension-list': 3.32.2 + + '@tiptap/extension-code-block@3.32.2': + resolution: {integrity: sha512-KFkPMMgXEgXImGpbUsrHNaERtvOGbJ/JiWn0zHNGSJCf6frn44OLeRFPpEMptmwMqtJxftBVFbEbcPOlATYqDw==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-code@3.32.2': + resolution: {integrity: sha512-jm7qXuDmBzQYPemm/9chLepQjCNVtZhaoU9N2f4GxdUi8u/fi3AQLP7xlXjRCQUyam50FVqIIWgD567mLQ3sMg==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-document@3.32.2': + resolution: {integrity: sha512-zKWj8jh1kpckfSwle+kqFn0ZnYvkhb7nz89nrNvpnnsQMbbGU2/+bUAfYU3XSEKZr3VBvW176FtlMPD8ggC3Cg==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-dropcursor@3.32.2': + resolution: {integrity: sha512-mE9muf4KSY3s+LCaHG/G957pVHVF2q46i+YkZF3ZG70errQcyZ5YFKnce9PSL1fiL0VLL+2UhMAL2QDM5oDiNA==} + peerDependencies: + '@tiptap/extensions': 3.32.2 + + '@tiptap/extension-floating-menu@3.32.2': + resolution: {integrity: sha512-JPyJQvJlGDxn2v7Mr8Yft2BmcXaHiapVsXBMYiOJh5BeWbnwUYFWNH9h5PU/xTOz2zVEV79Kh4HmpgXZS3FCbg==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-gapcursor@3.32.2': + resolution: {integrity: sha512-7W/iX8h6KQ2HK2SfvzCIgySOEJwTM4owqz1tXVOPiLPvKSvDDa9GQsp60+xNyqDNHuplE8bRsZ5dH1nilgAaAg==} + peerDependencies: + '@tiptap/extensions': 3.32.2 + + '@tiptap/extension-hard-break@3.32.2': + resolution: {integrity: sha512-oIdXmya2XBzp3Cw9Nr36agPvr9ZXP3p2fQXaFflxYYBKOG3UUzw38CId7J6K1jYxBf8c1zodbiKFEL7rttJ6fQ==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-heading@3.32.2': + resolution: {integrity: sha512-AE2WNA3jVnPV/cVyTwTmtmGZqX8LiiGicDHXX4bdvfzv+Aez4sgzdkuFjOGWtnEy43NnXdoI1UX7y/984NM0iA==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-horizontal-rule@3.32.2': + resolution: {integrity: sha512-Pl0YRS60R339yHrAWGhPT9WHIlD72BO6SGVdj8lBs2OAaYw5RQL/1BGZi+7UvAyc2yJxaRC4XN9X4jdQHDUq9A==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-image@3.32.2': + resolution: {integrity: sha512-TRjp//2r1MTE1emLXxEki6+D0bOPYaX41CnM1hrUnp1iw/cS0a4XBXAWXxK/EFEl94zrqWrafkarU9rXpnBHeg==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-italic@3.32.2': + resolution: {integrity: sha512-aqk8OK4Xo59qZni7YSy42/ltQFb2IOmli3YERIq1XPQ4vl5sxoYhL+sFoOPmvbjHvaHZpK6n8vPh1SmAnB570A==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-link@3.32.2': + resolution: {integrity: sha512-EwQtMXsU1Vc03n+yud7lfaR+RjHTPwFyI/hcEylvJba7iIXDSLA/ey4mnpwt2od8CEx/ZcOutIryORKVR9Py/Q==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-list-item@3.32.2': + resolution: {integrity: sha512-vTdPfzZODLVXog/NMs6zcaTOhtlh6ptJidlMg8l1TLhO+YQyEyO/K45aV429D9xTo/BOVazreav/C+hgemSnYg==} + peerDependencies: + '@tiptap/extension-list': 3.32.2 + + '@tiptap/extension-list-keymap@3.32.2': + resolution: {integrity: sha512-SioQO8TqRb8aq9L+GpI/a/F7HCSBarm3Uvqtt2YttyazjSjEedu1zSnaa93V+WEFmlqyXnxVa24vv2zj6qlavQ==} + peerDependencies: + '@tiptap/extension-list': 3.32.2 + + '@tiptap/extension-list@3.32.2': + resolution: {integrity: sha512-rLYk3GXSswrCakyZwo8VW0+yjnYhLJSBRvzClneIrlPhqQ2r/oDUjeqkpm8lGvwREw1lDSh7wefGhkhTRcQkAw==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-mention@3.32.2': + resolution: {integrity: sha512-q87HWG5iYV5jR8KxgU8shdXWjWgZmHIdnu0UkHE50NSRL88DZeiEt2ZU558P/R0JN85rVKf2z8i98MUR7lVTxQ==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + '@tiptap/suggestion': 3.32.2 + + '@tiptap/extension-ordered-list@3.32.2': + resolution: {integrity: sha512-WQw2fLtJAxCYftKAk+YNlPDc+OxtXK4nNfXRBwr6oC67SoJtoLe0bo5jS6vF+cEqzHjDaboJi+O3MSNp2L2VQA==} + peerDependencies: + '@tiptap/extension-list': 3.32.2 + + '@tiptap/extension-paragraph@3.32.2': + resolution: {integrity: sha512-3bqC//4GEhUiux3dhW/TJuveBp9ADv4nHlqBdVQa3A+USGbaBvE9YvXxA32wZL0leSRykxEnHbNHZ1J4KjIynQ==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-strike@3.32.2': + resolution: {integrity: sha512-i3d4vCrZySS9g5nGa5W0LS6LPMKxcAnJO6uADTnGVlKK0ewklcWWZbZ9GB/Fpx/IjKAvXWeayjfsNpTae/yVZg==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-text-align@3.32.2': + resolution: {integrity: sha512-o1DKPodB0FKqpdYp/OKyM6nYSbZXnPEC9rWx/O/Vccs2OOeF08iOO3cKYs1lS/qNg/xFeR8gKxGMrve7CfCkKA==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-text-style@3.32.2': + resolution: {integrity: sha512-7VHyvO8V7xCOj4BY56gqCylZXF972dtk+ngIIXT19nhBXYuQZKXGrN11xPrfMip/1nGsMWov9PZB2HleYJnXGQ==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-text@3.32.2': + resolution: {integrity: sha512-W6G7UbQ71TyvZvORDdj6u8By8B/Dymy8KoaKGaNHqB82SpM42ZK2TmeFBHI0Lo4wTkZJL3LuI+QthaDvh0USsA==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extension-underline@3.32.2': + resolution: {integrity: sha512-kY7B5ocKx3JzqIl2kLXGvnLGZRxuYEFzMkpgJ6Vh6/VTTK+JCeOVItYEQGFrAxrodIMmhIrXpi1jF57s6iZswg==} + peerDependencies: + '@tiptap/core': 3.32.2 + + '@tiptap/extensions@3.32.2': + resolution: {integrity: sha512-TUE1JedPjYQ/T527f2a4xz+4Xi1a5nIgW8KwC0DjmqbaHirlz0eCNI2nMEN70pu5WPpqgXmki3lCF3LScQxIGw==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tiptap/pm@3.32.2': + resolution: {integrity: sha512-4cuUbYf55NnYBIvDBK/K1VFVQ4pocKwAZC5TAy4s9dptvazxVHkQVMbiQsyhwfIbM4rxmHWkM/6omaFfTB++/g==} + + '@tiptap/react@3.32.2': + resolution: {integrity: sha512-IrraXFTp6S9R+GlCzavMUQel0wK9wafXU3VnaujeC/REi6sYWNuyBe0jICimdbEk1dU32UvlkvPCuIDRPKlzaQ==} + peerDependencies: + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.32.2': + resolution: {integrity: sha512-6K+oOMF4orhnOxivgUTKiSpNAEftHyWOcfFIvIEMUP044b34obBUogzMTfM3RjtewZtQXVafO9zmGU30IllC1g==} + + '@tiptap/suggestion@3.32.2': + resolution: {integrity: sha512-AD6WQ+mEFLok8svJv6Ibffyao+vXBGn3klQo4gCXH+UHwtgct5tF4goL9Rsz8J6zcDmYfx5W8AqYjBiQIyRNHA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.32.2 + '@tiptap/pm': 3.32.2 + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@turbo/darwin-64@2.11.10': + resolution: {integrity: sha512-By4idRap5+nLnMuRzLB4qkOPRlvNdyY1ne4VJYDyUoUB11H94k7uif9mbe3JT8JSNXs1D68uMcBj6hJ/jMQRvA==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.11.10': + resolution: {integrity: sha512-pyEYlVcuexOPa92p0Wlwl9YT1Is5D60lxN3ItWm3g30YLvFaUq86TF4BBWg5ZAc0uMTnrOzCvuCsOBTdzUWnRQ==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.11.10': + resolution: {integrity: sha512-1tYeR/IDZ5HEdsb0jNq2L2xam5nMQ+g80tlV2BZmv4OFw+pMcUM0mMmUcfc5wXPPNHVNPuDMjh9Y8G8HgXPLZA==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.11.10': + resolution: {integrity: sha512-HuekQxvVGhUd46g1Im/DKdOtglHr4/gguZAvzEpPUgtxNkztSa8lWhQEGQ+EmPQ0n7w19qjThFU+OfEEd0uvlw==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.11.10': + resolution: {integrity: sha512-UJOP3BkbreY3/JlMgJR6ZiimxYIBn+c+B4tnBIxD0E9c3Estmk+XoVNVBNBottZLNRXe3/ScgOU4+/k8tIWysg==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.11.10': + resolution: {integrity: sha512-xOkEjF3ws/b/YNlc+bvJHMYBn+vL+QbEISZFVi0qS+ImmhA1X3/gmNmBrjjIG3dg6aeN+n6gQitzSkXQ286qJQ==} + cpu: [arm64] + os: [win32] + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.19.2': + resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} + + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@typescript-eslint/eslint-plugin@8.72.1': + resolution: {integrity: sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.72.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.72.1': + resolution: {integrity: sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.72.1': + resolution: {integrity: sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.72.1': + resolution: {integrity: sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.72.1': + resolution: {integrity: sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.72.1': + resolution: {integrity: sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.72.1': + resolution: {integrity: sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.72.1': + resolution: {integrity: sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.72.1': + resolution: {integrity: sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.72.1': + resolution: {integrity: sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@unrs/resolver-binding-android-arm-eabi@1.13.0': + resolution: {integrity: sha512-60E5mvmIiW+i1E/HYeSX4iE4A8s9NJSGYPu7UM0DJght1c2tTkWwuyDyAwzsEXhKM/XbbndfnTiXdfrf27d2hw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.13.0': + resolution: {integrity: sha512-qlh/JR3Tuy1cJ7xtf5pR4ykvLiK6os7X7EL2+zXZbrVc/s+yB8DoFdQrCAW9/a2TOxPVbR7QVNpkPWh7UWIsEQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.13.0': + resolution: {integrity: sha512-yynnnnJXUC99WeD1GfvuXPKsKNfvgzGYC2eRayFnUfsHVROjqrbFOkFQLZiSx1H8+PLy5BzU8vqyPyLqbRfeBQ==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.13.0': + resolution: {integrity: sha512-t8PXE0Ip9tmGyhRlfVsnEbzGM5iSvJIeNIXciUACjzpg0mmPs13fGv2x7W8sBa0S8zbIiohxKXS9fL/LXW/ERQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.13.0': + resolution: {integrity: sha512-qdtl9GDWDoDx6rhzF4RU5SJutV3lcLOiTiyqD6nBxYfEO6lAgLNukfrA+HJ2u2OiyWvV8JW5iEWD3LoONw7lYA==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.13.0': + resolution: {integrity: sha512-VrMUVwdrScTarsh4B6aUhTtziUavSlglo4Hi8XZ6CpAe4+j3nquY+XoP+tYkiSBDsXZ8386ne4pv4YYYBNi02w==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.13.0': + resolution: {integrity: sha512-zSbTVwaxiTC+zYJ9xVNTC6c+BYO/kOV/odcTI3bHuFFXa+XmjLoUkb/8UxsZeU5YF4K000m17wrXoTblNWuzOg==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.13.0': + resolution: {integrity: sha512-Z/Rax87moZHsuvJ8awdfP+zrMlsCt3d7mggUPIiPe32KpeagimyzJWnZSMQoV67an9uVQghyh92Pc0VprITnYA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.13.0': + resolution: {integrity: sha512-oUzXz8hNvFLNEAag8lBKJe1euiV2dmGHAR+eQ5fNpsZO0F1K7IMRb4J6pqYsd7uLMRQNPiu5A1WKUJHbFLWbbw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-loong64-gnu@1.13.0': + resolution: {integrity: sha512-BxhGYvZBg0YrvNOeSEGR3eNklwXYy0mUJk7oeGJvZoQMKM68zYvbbf52+fQI3Le4X0fW276FTKIoTwjpMob80A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.13.0': + resolution: {integrity: sha512-nZcIuup9Ar3gAlqoZfX/KW6x83TMI11tLpCjldZG5M1KeY8qNUPNJJ71+sPvG8HMBpoh+ix1pAm5gkMFvcSAcQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.13.0': + resolution: {integrity: sha512-AG6AgdDrkRNjAPwD/cftFJGgZUWCiJX+E8vpYCDEnwFHLTHZ7ppBiU6/vyPPwrfjcPUPQCDbbxE/eZUXsx49jQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.13.0': + resolution: {integrity: sha512-6vOqYGcAmzpRWpUURWutzvpf1qJ6eOu5cfYObdtPJnzZ0+Kw4cw+ZkZJqIYEh8fPuTBwSYDL/FwlUhma1G/JVQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.13.0': + resolution: {integrity: sha512-4HeIn0S2KVUowK0A+EweXrDvZ8CKHEfTtif0kx9vt3ORNsqcXNFFIFnHE0tizma3/Gt5OGrU2ovYNuj+D/bN4g==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.13.0': + resolution: {integrity: sha512-JfkvqY7NZbxyhexLE+g6Z8Hsx4utHOGaVazAVghPiBn8Upw2iERZPpKYnW1FBVU4WStv/BiIsoK/Muke0E7asA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.13.0': + resolution: {integrity: sha512-iwjLeVdZo/8v5OKAeRIC+4VQV045t1EPZr5B/AVLgUQ04IdtNthWzg6not2cNjZgH2gHpSf4fOXMmN83aLO1FA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.13.0': + resolution: {integrity: sha512-TOh1fqOjyq1OPoht3BDLd7Y+HeMJRwd5g2VNmCQclknEbjp4py5GFRmT4L/rOybF5Af8q0LT9zHs2WGkwlqtrw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-openharmony-arm64@1.13.0': + resolution: {integrity: sha512-3T2MNtF2dd1yjaXCVqrsi+/3VZeksEzU1hygPzqqkBLVVjn+2iJzXEkxxCKIVFrWnpmEbazeHJp/ruYD0RUVWA==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-win32-arm64-msvc@1.13.0': + resolution: {integrity: sha512-hKWfSLiN3L4cIxg0jrpyP/66AXLTcyD37jmSn//rGdOUYGlizYxTEFdJ+GZbp2NnaML3YS/A73nCysze8FaJdg==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.13.0': + resolution: {integrity: sha512-N4gDKkDoiyIQEcn3vTqe2ksRxUAtBvQ8c5TiT+WonSa6FHgCAY5+RggnZ134hXJoRj57JEjv4/m+i/pAKHWrTQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.13.0': + resolution: {integrity: sha512-DpLMlAqlrk4zBVK3DiDjwKD0oq7pZNy+8BiIBRuByqb6dm5jsdoLMhIiyh/xR7+WejaW/1Ui+XgbgMNa4+zWJg==} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@vitest/coverage-v8@3.2.7': + resolution: {integrity: sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==} + peerDependencies: + '@vitest/browser': 3.2.7 + vitest: 3.2.7 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@3.2.7': + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} + + '@vitest/mocker@3.2.7': + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.7': + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==} + + '@vitest/snapshot@3.2.7': + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + + '@vitest/spy@3.2.7': + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} + + '@vitest/utils@3.2.7': + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.5.0: + resolution: {integrity: sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-includes@3.2.0: + resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async-validator@3.5.2: + resolution: {integrity: sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + avvio@9.3.1: + resolution: {integrity: sha512-QyTKW61/qlba+cUQBm/sT9X8lri4UdomqSDwCnrnj1IvLVDRRiXRkz4CgBRWB+/cmfC7SK5WQc8T1vSdGvaUcQ==} + + axe-core@4.14.0: + resolution: {integrity: sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.34: + resolution: {integrity: sha512-Aqr8mA1kIvyJ3UY5YAY7ib8LSo1DmDMbMnVqGPtsKW1LxaFQFwn9Eb3yvq2fUKJsRvaXjnAV7R5osqETapzNiA==} + engines: {node: '>=6.0.0'} + hasBin: true + + bezier-easing@2.1.0: + resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==} + + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} + + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} + + brace-expansion@2.1.7: + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + browserslist@4.29.5: + resolution: {integrity: sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001820: + resolution: {integrity: sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + citty@0.2.2: + resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + engines: {node: '>=0.10.0'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-parser@1.4.9: + resolution: {integrity: sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==} + engines: {node: '>= 12.0.0'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + confbox@0.3.2: + resolution: {integrity: sha512-ZUAKhKlkCOrIGKc8cj7zek9s9z1mveHif/25nUshI2BzCRzF7KH7KHa+Lnh2ubq+e1tIlaFuP763mzW4qVoehg==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@3.0.0: + resolution: {integrity: sha512-ZH/0Xs9rMIFWCOmGdmS9eHBTF62qqQYNz4nVjQhkdIO/a0fCP4UIM3mRz/wiqL0L14YgAz/1xio4OaSY4+ON/A==} + engines: {node: '>=22'} + + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} + engines: {node: '>=18'} + + conventional-changelog-conventionalcommits@9.3.1: + resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} + engines: {node: '>=18'} + + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cookie@2.0.1: + resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} + engines: {node: '>=22'} + + copy-text-to-clipboard@2.2.0: + resolution: {integrity: sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==} + engines: {node: '>=6'} + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns-tz@1.3.8: + resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==} + peerDependencies: + date-fns: '>=2.0.0' + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@18.0.9: + resolution: {integrity: sha512-3fVOW4cB02R066PUSwn8O4TJyz9NckQl7F81blIbaq9jKBm2tSmOKky7ZHIXzGvkVPXCSn6jkpziBPZdGFs8Tw==} + engines: {node: '>=12'} + hasBin: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + effect@3.21.0: + resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==} + + electron-to-chromium@1.5.472: + resolution: {integrity: sha512-s4+nfT29b0FKpVKoaIxl2pWsoNcn7A/nMPSrxayw4T3BsmPeV2lF9Y06BsAQeMGhCTQ4nYJ5XST7jkqe4ZEqFw==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@9.0.0: + resolution: {integrity: sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg==} + engines: {node: '>=20.19.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + + es-toolkit@1.53.1: + resolution: {integrity: sha512-VhMc6IXmexu8iYFfL2aaAww6IvJTdHlqBLruLoEUs33YD4IcP5DdZcLOybMsMmmLsiy1c8PsiLsrM0DdIuuRUA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-import-context@0.1.9: + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-import-resolver-typescript@4.4.5: + resolution: {integrity: sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==} + engines: {node: ^16.17.0 || >=18.6.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-plugin-import-x@4.17.1: + resolution: {integrity: sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/utils': ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + eslint-import-resolver-node: '*' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + eslint-import-resolver-node: + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react-refresh@0.5.7: + resolution: {integrity: sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-plugin-simple-import-sort@14.0.0: + resolution: {integrity: sha512-NUJO0+XFCkk+o5EsAJruTgnfMEpeWrPWeJS15UVF60GgXmqz1BJ9/3hzlvG7lkL8Bubzos5cCLptThbFfPnSMQ==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.1: + resolution: {integrity: sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + exsolve@1.2.0: + resolution: {integrity: sha512-h1pQPR9XoeSMdntRNCjyTznaume4UpfXrULBnlkZ2CUM/G5eSa+pBByGldO5uR8dxbPC+OuJXsPPNf+JZ81ADg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-copy@3.1.0: + resolution: {integrity: sha512-IuCOCDFM1F/s4LtG7Bjr3yUUwPHybyhqN74xk2cMYoP51vVeErMy8+6HGFchi/+45lW8uzhs6lBeEqks2GWt6A==} + + fast-copy@4.3.1: + resolution: {integrity: sha512-CRzQaTFOFUs6PuhsAq7lk9Zqr995soJBcFNZlfJS/5keTSl1awREV9dZW4xS2nX4pWbhIIR0xp4h+J8PQxCkvg==} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.4.5: + resolution: {integrity: sha512-yKEpcFuRPu/pTQL2VB8YKE2yFldrLlIsoWtJeBnAdqEhvu21OgkFT0C8njf/xYc2LSZGEr3K9JGjthHJxnVLvQ==} + engines: {node: '>=6.0.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-json-stringify@7.2.1: + resolution: {integrity: sha512-GU16K+1IH8XSJ/suUaX/ONlJ5glCFvmBdra2nGI2ZGpBBXzqM/r/lzO02gjHYcRad1KKj6WvC+/RROggpg6acg==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} + + fast-uri@4.2.3: + resolution: {integrity: sha512-I1PXwP5eAhlNlcdpjt9CgB59op2ZvZfxw+BkqGFVIUOgNpCVTcTqp3HhTX7brxj5vsip+nSY4nTvff+eIA76hg==} + + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + + fastify-plugin@6.0.1: + resolution: {integrity: sha512-4wW4RxSmvPXLrh5mgQEiu43qU3bw44N2ss4FTkTkQIbprUhE+/s43QX0j/ttcztdWK7PEkHqVthdc5GdEGDvjA==} + + fastify@5.12.7: + resolution: {integrity: sha512-v4pEPsTRPuqlDIYiQFB+BHjVN4SdHX9u0ABk0ZDUKbbdY1vsNryUd1j4Z+jQEZajvYIVk047Kimva9/AJ79GYw==} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + + find-my-way@9.10.0: + resolution: {integrity: sha512-eru+uCPQ2g+nLBkf84MHP/KyNNMar0BDy0j+i8Fb72GF9/YHBZ58q7FODUtfVV7XU9nfRUHXSmaM8NmZcIbZ4Q==} + engines: {node: '>=20'} + + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} + engines: {node: '>=20'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + git-raw-commits@5.0.1: + resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==} + engines: {node: '>=18'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. + hasBin: true + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.14.0: + resolution: {integrity: sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.7: + resolution: {integrity: sha512-F7DNV2NMcTxA4GqPVHnXzX+mpoPHAIY9rJAVH3CZ1FdBZOcBUEjUuvMkMpHVs/sicEtznh9tMGj5WUR9S4E2rg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + helmet@8.3.0: + resolution: {integrity: sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==} + engines: {node: '>=18.0.0'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.14: + resolution: {integrity: sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + inline-style-parser@0.2.9: + resolution: {integrity: sha512-IttN8BqKLxzUrnTqHI+/hwAJQItBAUjdoHERCDv+oOuX48VP6bzW8+QjOXSfoevzq3YrYAwX1uLHp8TqO9yIeg==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} + engines: {node: '>=12.22.0'} + + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} + engines: {node: '>= 10'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + js-yaml@5.3.0: + resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==} + hasBin: true + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-ref-resolver@3.0.1: + resolution: {integrity: sha512-w+HwgljXpVwZ3IidoUR1qPZxSOQ+NRQWuVe6a5pe8XbHsNZE4UAwVPUvzZagn7yBKbJtt6UJnRFYbNTRPZI45Q==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + + light-my-request@6.8.0: + resolution: {integrity: sha512-5Q8PjoSEdtGIB8fikZsTem5vgRo4F2mR6vUKTDW/H1g6MtAt8e3duQbg/QxL5yzg6BZhor2KOUm3hhWMWLTZwg==} + + lilconfig@3.1.4: + resolution: {integrity: sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkifyjs@4.4.0: + resolution: {integrity: sha512-InTexvtbai+2c+dEVGcc6O6P2DMVIiwD9a/i4zFroazOIYLOS6FSVmAJ7kFeumnCMx6/gQqNAkbUvgMubV4+MA==} + + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lottie-web@5.13.0: + resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-find-and-replace@3.0.4: + resolution: {integrity: sha512-z4B5c6jrnJ1ltOQemhWYPBSAo6y42Z8Gt2yEgfgNTMcLSU59B8bfj8Z8AnAlJrBGLnqkdAUZ7jdDEqQw3KuipQ==} + + mdast-util-from-markdown@2.1.0: + resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.2.1: + resolution: {integrity: sha512-ThatlCBxG2GE+mljhNSfZPv56d3QrGiRh97jjYgIk80IzS4uU6d7cHAaexCYSAmUCCcOs3grJ07kX3oTGsFUOw==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + micromark-core-commonmark@2.0.4: + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.2: + resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.3: + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} + + micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.7: + resolution: {integrity: sha512-jyU4cU4kVAmy/3qjP+DcoQTNXfINHc+dFw54gGqWC8nu0mpV0kOnk7kff+J+Q18SbX0k3Np8Rm7yFjd3phatNQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + nestjs-pino@5.3.1: + resolution: {integrity: sha512-tL3fFSY38Wuv3VS4dCo+zlHVt1xU7+RSXnlgenyTvfy74GLBLgisqvG+76983ilf2C2uFkPe68qUapXCYnoSoA==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@nestjs/common': ^11.0.8 || ^12.0.2 + '@nestjs/core': ^11.0.8 || ^12.0.2 + pino: ^10.0.0 + pino-http: ^11.0.0 + rxjs: ^7.1.0 + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-releases@2.0.63: + resolution: {integrity: sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==} + engines: {node: '>=18'} + + nypm@0.6.12: + resolution: {integrity: sha512-4AhWntz+ds/P5kz1rN25yGegTPYsO5ffOy+8S0TeuXKZrgjlmgcEBLA1xubzcP/dWX4zYfNybv4TQERlL9Z0LQ==} + engines: {node: '>=18'} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + + parse5@8.1.0: + resolution: {integrity: sha512-sq3qBFpee5jeocbFbO48VHlGr8sT7mbXS0vxlKN6sottBtJzrOG3998sr0llSZcQAGgfpdmEYAIZE0P9z0dE8w==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-http@11.0.0: + resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} + + pino-pretty@13.2.0: + resolution: {integrity: sha512-jjghD1ML+Di/WxjJDz6j6Y4FT7hIXOlb8h27AyJ+ohtAXJXH4PS/xkv7si+RCCIW59HMgZsMKTDgzdr0RhuRrg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.4.0: + resolution: {integrity: sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA==} + hasBin: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.4.0: + resolution: {integrity: sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==} + + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.59.1: + resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + engines: {node: '>=18'} + hasBin: true + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prisma@6.19.3: + resolution: {integrity: sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.1.1: + resolution: {integrity: sha512-oCkNVj4OKnKEcocVBcDfiIugN1/cE6rgfT3nCtzeqZPA8+rIxMaQ0xXzIkGP8Gj2qUaP1UR7uhJ1VD8lUCpzCg==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + prosemirror-changeset@2.4.5: + resolution: {integrity: sha512-hv3Qhsn5GyoferiBqL8euGvj3Y8Z+2Ef75NnPLChr5DTWArpPAjK6F96Yc0iM7SBYNDPQbQaA+xEtAR8NuWo9A==} + + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} + + prosemirror-dropcursor@1.8.4: + resolution: {integrity: sha512-VmKxhcA6a+h5oPSYmfF1ZNeQqPbyYaIxqZsKqeYCyTlPtrjUgNaHL3P8vVaO19oqAV0zuaM/axkowZHrB1P3Tg==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.1: + resolution: {integrity: sha512-onlwnqKvgtFg6srZOmDOAhlAy1FIHQpOExORybX0S+DvJepE5tPALrB9iGet/2sqqqrelSnB10OoiOJqAvu2DA==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-model@1.25.12: + resolution: {integrity: sha512-Ue2gTmXMa7EhpLNhC7J+h4+ykD8ha12K6rrZFFKKJHBForfIStw5gJ6Zrf1mqqAa9NmpmEB4wp9bKmA4eUVjWg==} + + prosemirror-schema-list@1.6.0: + resolution: {integrity: sha512-G1srDtan+VqWn+qpmWFj/6j0YsZ5zMU+ZIlGVIW41a5JF2jAI3jzfMRWmSABmEH2Qoq1PmAG/o2mucMMdLOsLg==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.4: + resolution: {integrity: sha512-2/WuPum3pn+O/Vi2vzpi8210ICUe2kIPXdLYjQ09Pv5DtT5bcKWMJhAKPBwvpPjpp5iOYUVwVEr0gOM1BY1Mgw==} + + prosemirror-view@1.42.9: + resolution: {integrity: sha512-bKbfzeKZOu2H2JQU2TaJmYu6EBcor3VnGIR4jR31cbEt1ztUnfUB6bKGV1+fJzOJdrVWR+tRU0208zM3oQx4wA==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-dom@19.3.1: + resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} + peerDependencies: + react: ^19.3.1 + + react-draggable@4.7.2: + resolution: {integrity: sha512-pmqDx72EqhMGoJe/x42ZO2wJ9TIz7qJ5OPnFu6lVWWznEuJumlSjR+J0V4tNyDFhUo6dVvv56FeiTqN+arTgnw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react-resizable@3.2.0: + resolution: {integrity: sha512-3NKQ0SLZV7rs3LQHeXlOzDSRQfFrkX6TVet77/Qk03zqiZyee37b7N8/gwDJAA8UUjRz7PdWCCy49hcso45SMQ==} + peerDependencies: + react: '>= 16.3' + react-dom: '>= 16.3' + + react-router@7.18.5: + resolution: {integrity: sha512-dTgvcXDOrLPrlLHrcZAqWveiK1hMSqN+rGAJzegP8HtmNkfRxOoshGWBNX3ZJ0cZjOHkRdlUJtaIhZMLLyGyPw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-window@1.8.11: + resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react@19.3.1: + resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + ret@0.5.1: + resolution: {integrity: sha512-Z/nyvABwcRnRD2mMP7Kz6G1dFZZqX1mNErOnxF42OCQKE07b9k9hK9SQxHL3LFEZUdzQaLM4dId1E/E6Vm4WhA==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.67.4: + resolution: {integrity: sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex2@5.1.2: + resolution: {integrity: sha512-tAtCGFPPP02VroDVzp4daGi6bC7jWG+41D47GKf18FeH/ubvKQHT9VmhAkvtjNOmJwqtApzWeUpEhkyEYULvmw==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + secure-json-parse@4.1.1: + resolution: {integrity: sha512-lnd81MQEZKjhW/Gd2TQqdd4XP6QYNdDW/EP+I7nTrFHP9y2LmaGPIHk9xsnzcOiEr1c39c/e84xFgGviQKBTRw==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.3: + resolution: {integrity: sha512-irZHtTqurLna57KQG/s+fzOwv/J6XSH4cs8M/vsrY3uCaQ3S6+QJguAH0WdN47SXlk82voMZ4naWMQf8fdEoIg==} + + set-cookie-parser@3.2.2: + resolution: {integrity: sha512-df017qMeYuXo4emL09nlyMpi2i2MtEIKS/6jtXH3vmjFgIRsOi852/5RmhJ4EkvN3ZF8jXadM/EkTPXgc8yPIw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + sonic-boom@5.0.2: + resolution: {integrity: sha512-/JEruzo6yhNVhsLblTRchgTQHIt7SsdJP0YyrfWaNKjIOQDfrdZv9gwYraYCAsmKV39/LAdV31FLL5NCDpoL/A==} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stable-hash-x@0.2.0: + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + strtok3@10.6.3: + resolution: {integrity: sha512-MD8mENgyXMr2mDaDfzP3RWYDSyYQxIIou7HhCiCJiBekrsoDtUG0id3vYnjDfY9ndOEbazFXe13Qw6u/1AB8ew==} + engines: {node: '>=18'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + style-to-object@2.0.2: + resolution: {integrity: sha512-QmbXPUylqU80HAgTOJeEOdbA40W9KtxmjMhQy+tq+b8F2aoh3aQLuy82hh2jm2qsC9hR9eI0+eQidTMydKcqRA==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + swagger-ui-dist@5.32.13: + resolution: {integrity: sha512-qQobzb3DeC2LeK0j3E8812Ef4aIq1y9flJxvZkimkqUC/w4u7wS+yCc+VakqGJLweUUBrI24effhwo8OsAvNAw==} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.20: + resolution: {integrity: sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A==} + + tldts@7.4.19: + resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==} + hasBin: true + + toad-cache@3.7.4: + resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} + engines: {node: '>=20'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tslib@2.9.1: + resolution: {integrity: sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo@2.11.10: + resolution: {integrity: sha512-3GJYwig8ygEI89oaOZdduJX8/aw6O9eRPYHoIt6T3FZl9hkKjiyIspufJoVgqXihPoJu8M5CSV0DlZvzfohBCw==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.5: + resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.72.1: + resolution: {integrity: sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.6.0: + resolution: {integrity: sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==} + engines: {node: '>=18'} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@7.30.1: + resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} + engines: {node: '>=20.18.1'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.1.0: + resolution: {integrity: sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w==} + + unist-util-visit@5.2.0: + resolution: {integrity: sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ==} + + unrs-resolver@1.13.0: + resolution: {integrity: sha512-mcPQo9Vm6kXd84d/WqMiYBi2CcA57QHBX/ev5PoUEb8b2oQzUvJ6xYsfseQblnCncgnVmCZGWiFwK7KkS5JZJA==} + + update-browserslist-db@1.3.4: + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js-replace@1.0.2: + resolution: {integrity: sha512-VeBBt/nMxedczht3KDL2yBBMJqIxpCQLMZp3UJ1otcWwUHdQW4IiyHpJivExzSK+FdxTowRfGL4FmwDqH9+S8Q==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.7.0: + resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.4.5: + resolution: {integrity: sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.7: + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.7 + '@vitest/ui': 3.2.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.24: + resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zustand@5.0.16: + resolution: {integrity: sha512-Uv7e327ihlCfayfja3hDc0M64Upev1LWBKpRobdK4NyovDDPeeX4imWXc/wzC1yc6fkYPoAY/ncKVPAs2jrtQw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@acemir/cssom@0.9.32': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.12 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.10 + '@babel/parser': 7.29.12 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.12 + '@babel/types': 7.29.11 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@10.2.2) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.12': + dependencies: + '@babel/parser': 7.29.12 + '@babel/types': 7.29.11 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.5 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.12 + '@babel/types': 7.29.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.11': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.10': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + + '@babel/parser@7.29.12': + dependencies: + '@babel/types': 7.29.11 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/runtime@7.29.10': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.12 + '@babel/types': 7.29.11 + + '@babel/traverse@7.29.12': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.12 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.12 + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.11': + dependencies: + '@babel/helper-string-parser': 7.29.11 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@borewit/text-codec@0.2.3': {} + + '@bramus/specificity@2.4.3': + dependencies: + css-tree: 3.2.1 + + '@commitlint/cli@20.5.3(@types/node@24.19.2)(conventional-commits-parser@6.4.0)(typescript@5.9.3)': + dependencies: + '@commitlint/format': 20.5.0 + '@commitlint/lint': 20.5.3 + '@commitlint/load': 20.5.3(@types/node@24.19.2)(typescript@5.9.3) + '@commitlint/read': 20.5.0(conventional-commits-parser@6.4.0) + '@commitlint/types': 20.5.0 + tinyexec: 1.3.1 + yargs: 17.7.3 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@20.5.3': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-conventionalcommits: 9.3.1 + + '@commitlint/config-validator@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + ajv: 8.20.0 + + '@commitlint/ensure@20.5.3': + dependencies: + '@commitlint/types': 20.5.0 + es-toolkit: 1.53.1 + + '@commitlint/execute-rule@20.0.0': {} + + '@commitlint/format@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + picocolors: 1.1.1 + + '@commitlint/is-ignored@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + semver: 7.8.5 + + '@commitlint/lint@20.5.3': + dependencies: + '@commitlint/is-ignored': 20.5.0 + '@commitlint/parse': 20.5.0 + '@commitlint/rules': 20.5.3 + '@commitlint/types': 20.5.0 + + '@commitlint/load@20.5.3(@types/node@24.19.2)(typescript@5.9.3)': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.5.3 + '@commitlint/types': 20.5.0 + cosmiconfig: 9.0.2(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@24.19.2)(cosmiconfig@9.0.2(typescript@5.9.3))(typescript@5.9.3) + es-toolkit: 1.53.1 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@20.4.3': {} + + '@commitlint/parse@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-angular: 8.3.1 + conventional-commits-parser: 6.4.0 + + '@commitlint/read@20.5.0(conventional-commits-parser@6.4.0)': + dependencies: + '@commitlint/top-level': 20.4.3 + '@commitlint/types': 20.5.0 + git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0) + minimist: 1.2.8 + tinyexec: 1.3.1 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@20.5.3': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/types': 20.5.0 + es-toolkit: 1.53.1 + global-directory: 5.0.0 + import-meta-resolve: 4.2.0 + resolve-from: 5.0.0 + + '@commitlint/rules@20.5.3': + dependencies: + '@commitlint/ensure': 20.5.3 + '@commitlint/message': 20.4.3 + '@commitlint/to-lines': 20.0.0 + '@commitlint/types': 20.5.0 + + '@commitlint/to-lines@20.0.0': {} + + '@commitlint/top-level@20.4.3': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@20.5.0': + dependencies: + conventional-commits-parser: 6.4.0 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)': + dependencies: + '@simple-libs/child-process-utils': 1.0.2 + '@simple-libs/stream-utils': 1.2.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 6.4.0 + + '@csstools/color-helpers@6.1.2': {} + + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-color-parser@4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.2': {} + + '@dnd-kit/accessibility@3.1.1(react@19.3.1)': + dependencies: + react: 19.3.1 + tslib: 2.9.1 + + '@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.3.1) + '@dnd-kit/utilities': 3.2.2(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + tslib: 2.9.1 + + '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react@19.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@dnd-kit/utilities': 3.2.2(react@19.3.1) + react: 19.3.1 + tslib: 2.9.1 + + '@dnd-kit/utilities@3.2.2(react@19.3.1)': + dependencies: + react: 19.3.1 + tslib: 2.9.1 + + '@douyinfe/semi-animation-react@2.104.0': + dependencies: + '@douyinfe/semi-animation': 2.104.0 + '@douyinfe/semi-animation-styled': 2.104.0 + classnames: 2.5.1 + + '@douyinfe/semi-animation-styled@2.104.0': {} + + '@douyinfe/semi-animation@2.104.0': + dependencies: + bezier-easing: 2.1.0 + + '@douyinfe/semi-foundation@2.104.0': + dependencies: + '@douyinfe/semi-animation': 2.104.0 + '@douyinfe/semi-json-viewer-core': 2.104.0 + '@mdx-js/mdx': 3.1.1 + async-validator: 3.5.2 + classnames: 2.5.1 + date-fns: 2.30.0 + date-fns-tz: 1.3.8(date-fns@2.30.0) + fast-copy: 3.1.0 + lodash: 4.18.1 + lottie-web: 5.13.0 + memoize-one: 5.2.1 + prismjs: 1.30.0 + remark-gfm: 4.0.1 + scroll-into-view-if-needed: 2.2.31 + transitivePeerDependencies: + - supports-color + + '@douyinfe/semi-icons@2.104.0(react@19.3.1)': + dependencies: + classnames: 2.5.1 + react: 19.3.1 + + '@douyinfe/semi-illustrations@2.104.0(react@19.3.1)': + dependencies: + react: 19.3.1 + + '@douyinfe/semi-json-viewer-core@2.104.0': + dependencies: + jsonc-parser: 3.3.1 + + '@douyinfe/semi-theme-default@2.104.0': {} + + '@douyinfe/semi-ui-19@2.104.0(@floating-ui/dom@1.8.0)(@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react@19.3.1) + '@dnd-kit/utilities': 3.2.2(react@19.3.1) + '@douyinfe/semi-animation': 2.104.0 + '@douyinfe/semi-animation-react': 2.104.0 + '@douyinfe/semi-foundation': 2.104.0 + '@douyinfe/semi-icons': 2.104.0(react@19.3.1) + '@douyinfe/semi-illustrations': 2.104.0(react@19.3.1) + '@douyinfe/semi-theme-default': 2.104.0 + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/extension-document': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-hard-break': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-image': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-mention': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)(@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-paragraph': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-text': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-text-align': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-text-style': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extensions': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + '@tiptap/react': 3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@tiptap/starter-kit': 3.32.2 + async-validator: 3.5.2 + classnames: 2.5.1 + copy-text-to-clipboard: 2.2.0 + date-fns: 2.30.0 + date-fns-tz: 1.3.8(date-fns@2.30.0) + fast-copy: 3.1.0 + jsonc-parser: 3.3.1 + lodash: 4.18.1 + prop-types: 15.8.1 + prosemirror-state: 1.4.4 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-resizable: 3.2.0(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react-window: 1.8.11(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + scroll-into-view-if-needed: 2.2.31 + utility-types: 3.11.0 + transitivePeerDependencies: + - '@floating-ui/dom' + - '@tiptap/suggestion' + - '@types/react' + - '@types/react-dom' + - supports-color + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))': + dependencies: + eslint: 9.39.5(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3(supports-color@10.2.2) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.7': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@10.2.2) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.5': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@exodus/bytes@1.16.0': {} + + '@fastify/accept-negotiator@2.1.2': {} + + '@fastify/ajv-compiler@4.0.7': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.2.3 + + '@fastify/cors@11.3.0': + dependencies: + fastify-plugin: 6.0.1 + toad-cache: 3.7.4 + + '@fastify/cors@11.3.1': + dependencies: + fastify-plugin: 6.0.1 + toad-cache: 3.7.4 + + '@fastify/deepmerge@3.2.4': {} + + '@fastify/error@4.2.1': {} + + '@fastify/fast-json-stringify-compiler@5.1.1': + dependencies: + fast-json-stringify: 7.2.1 + + '@fastify/formbody@8.0.2': + dependencies: + fast-querystring: 1.1.2 + fastify-plugin: 5.1.0 + + '@fastify/forwarded@3.1.2': {} + + '@fastify/helmet@13.2.0': + dependencies: + fastify-plugin: 6.0.1 + helmet: 8.3.0 + + '@fastify/merge-json-schemas@0.2.2': + dependencies: + dequal: 2.0.3 + + '@fastify/middie@9.3.4': + dependencies: + '@fastify/error': 4.2.1 + fastify-plugin: 6.0.1 + find-my-way: 9.7.0 + path-to-regexp: 8.4.2 + reusify: 1.1.0 + + '@fastify/proxy-addr@5.1.2': + dependencies: + '@fastify/forwarded': 3.1.2 + ipaddr.js: 2.5.0 + + '@fastify/send@4.2.0': + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.1 + mime: 3.0.0 + + '@fastify/static@10.1.8': + dependencies: + '@fastify/accept-negotiator': 2.1.2 + '@fastify/error': 4.2.1 + '@fastify/send': 4.2.0 + content-disposition: 3.0.0 + fastify-plugin: 6.0.1 + fastq: 1.20.3 + glob: 13.0.6 + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@ioredis/commands@1.10.0': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@lukeed/csprng@1.1.0': {} + + '@lukeed/ms@2.0.2': {} + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.10 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdx': 2.0.14 + acorn: 8.19.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.1 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.7 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.19.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.2.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@microsoft/tsdoc@0.16.0': {} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + file-type: 21.3.4 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + transitivePeerDependencies: + - supports-color + + '@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + + '@nestjs/platform-fastify@11.2.8(@fastify/static@10.1.8)(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@fastify/cors': 11.3.0 + '@fastify/formbody': 8.0.2 + '@fastify/middie': 9.3.4 + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-querystring: 1.1.2 + fastify: 5.12.7 + find-my-way: 9.7.0 + light-my-request: 6.6.0 + path-to-regexp: 8.4.2 + tslib: 2.8.1 + optionalDependencies: + '@fastify/static': 10.1.8 + + '@nestjs/swagger@11.4.7(@fastify/static@10.1.8)(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + js-yaml: 5.3.0 + lodash: 4.18.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.32.13 + optionalDependencies: + '@fastify/static': 10.1.8 + + '@nestjs/testing@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + + '@pinojs/redact@0.4.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/test@1.59.1': + dependencies: + playwright: 1.59.1 + + '@prisma/client@6.19.3(prisma@6.19.3(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)': + optionalDependencies: + prisma: 6.19.3(magicast@0.3.5)(typescript@5.9.3) + typescript: 5.9.3 + + '@prisma/config@6.19.3(magicast@0.3.5)': + dependencies: + c12: 3.1.0(magicast@0.3.5) + deepmerge-ts: 7.1.5 + effect: 3.21.0 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@6.19.3': {} + + '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} + + '@prisma/engines@6.19.3': + dependencies: + '@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 + + '@prisma/fetch-engine@6.19.3': + dependencies: + '@prisma/debug': 6.19.3 + '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + '@prisma/get-platform': 6.19.3 + + '@prisma/get-platform@6.19.3': + dependencies: + '@prisma/debug': 6.19.3 + + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.2 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.22(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.3.2 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + + '@remixicon/react@4.9.0(react@19.3.1)': + dependencies: + react: 19.3.1 + + '@rolldown/pluginutils@1.0.0-rc.3': {} + + '@rollup/rollup-android-arm-eabi@4.67.4': + optional: true + + '@rollup/rollup-android-arm64@4.67.4': + optional: true + + '@rollup/rollup-android-x64@4.67.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.67.4': + optional: true + + '@rollup/rollup-darwin-x64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.67.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.67.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.67.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.67.4': + optional: true + + '@scarf/scarf@1.4.0': {} + + '@simple-libs/child-process-utils@1.0.2': + dependencies: + '@simple-libs/stream-utils': 1.2.0 + + '@simple-libs/stream-utils@1.2.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@tanstack/query-core@5.105.1': {} + + '@tanstack/react-query@5.105.1(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@tanstack/query-core': 5.105.1 + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.10 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@babel/runtime': 7.29.10 + '@testing-library/dom': 10.4.2 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@tiptap/core@3.32.2(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-blockquote@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-bold@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-bubble-menu@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + optional: true + + '@tiptap/extension-bullet-list@3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extension-list': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-code-block@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-code@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-document@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-dropcursor@3.32.2(@tiptap/extensions@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extensions': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-floating-menu@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + optional: true + + '@tiptap/extension-gapcursor@3.32.2(@tiptap/extensions@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extensions': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-hard-break@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-heading@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-horizontal-rule@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-image@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-italic@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-link@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + linkifyjs: 4.4.0 + + '@tiptap/extension-list-item@3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extension-list': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-list-keymap@3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extension-list': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/extension-mention@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)(@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + '@tiptap/suggestion': 3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-ordered-list@3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/extension-list': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + + '@tiptap/extension-paragraph@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-strike@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-text-align@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-text-style@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-text@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extension-underline@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + + '@tiptap/extensions@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/pm@3.32.2': + dependencies: + prosemirror-changeset: 2.4.5 + prosemirror-commands: 1.7.2 + prosemirror-dropcursor: 1.8.4 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.1 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.12 + prosemirror-schema-list: 1.6.0 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.4 + prosemirror-view: 1.42.9 + + '@tiptap/react@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.5 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + use-sync-external-store: 1.7.0(react@19.3.1) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-floating-menu': 3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.32.2': + dependencies: + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/extension-blockquote': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-bold': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-bullet-list': 3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-code': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-code-block': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-document': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-dropcursor': 3.32.2(@tiptap/extensions@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-gapcursor': 3.32.2(@tiptap/extensions@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-hard-break': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-heading': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-horizontal-rule': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-italic': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-link': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-list': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/extension-list-item': 3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-list-keymap': 3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-ordered-list': 3.32.2(@tiptap/extension-list@3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)) + '@tiptap/extension-paragraph': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-strike': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-text': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extension-underline': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2)) + '@tiptap/extensions': 3.32.2(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tiptap/suggestion@3.32.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.32.2(@tiptap/pm@3.32.2))(@tiptap/pm@3.32.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.32.2(@tiptap/pm@3.32.2) + '@tiptap/pm': 3.32.2 + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3(supports-color@10.2.2) + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@turbo/darwin-64@2.11.10': + optional: true + + '@turbo/darwin-arm64@2.11.10': + optional: true + + '@turbo/linux-64@2.11.10': + optional: true + + '@turbo/linux-arm64@2.11.10': + optional: true + + '@turbo/windows-64@2.11.10': + optional: true + + '@turbo/windows-arm64@2.11.10': + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.12 + '@babel/types': 7.29.11 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.11 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.12 + '@babel/types': 7.29.11 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.11 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.10 + + '@types/estree@1.0.10': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.14': {} + + '@types/ms@2.1.0': {} + + '@types/node@24.19.2': + dependencies: + undici-types: 7.24.6 + + '@types/react-dom@19.3.0(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@typescript-eslint/eslint-plugin@8.72.1(@typescript-eslint/parser@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/type-utils': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.1 + eslint: 9.39.5(jiti@2.7.0) + ignore: 7.0.14 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.1 + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.5(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.72.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.72.1(typescript@5.9.3) + '@typescript-eslint/types': 8.72.1 + debug: 4.4.3(supports-color@10.2.2) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.72.1': + dependencies: + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/visitor-keys': 8.72.1 + + '@typescript-eslint/tsconfig-utils@8.72.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.5(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.72.1': {} + + '@typescript-eslint/typescript-estree@8.72.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.72.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.72.1(typescript@5.9.3) + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/visitor-keys': 8.72.1 + debug: 4.4.3(supports-color@10.2.2) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.72.1': + dependencies: + '@typescript-eslint/types': 8.72.1 + eslint-visitor-keys: 5.0.1 + + '@ungap/structured-clone@1.4.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.13.0': + optional: true + + '@unrs/resolver-binding-android-arm64@1.13.0': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.13.0': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.13.0': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.13.0': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.13.0': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.13.0': + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.13.0': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.13.0': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.13.0': + optional: true + + '@vitejs/plugin-react@5.2.0(vite@6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@3.2.7(vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.3(supports-color@10.2.2) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.7': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.7(vite@6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.7': + dependencies: + '@vitest/utils': 3.2.7 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.7': + dependencies: + tinyspy: 4.0.6 + + '@vitest/utils@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + abstract-logging@2.0.1: {} + + acorn-jsx@5.3.2(acorn@8.19.0): + dependencies: + acorn: 8.19.0 + + acorn@8.19.0: {} + + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.5.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-ify@1.0.0: {} + + array-includes@3.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + ast-types-flow@0.0.8: {} + + ast-v8-to-istanbul@0.3.12: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + astring@1.9.0: {} + + async-function@1.0.0: {} + + async-validator@3.5.2: {} + + atomic-sleep@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + avvio@9.3.1: + dependencies: + '@fastify/error': 4.2.1 + fastq: 1.20.3 + + axe-core@4.14.0: {} + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.34: {} + + bezier-easing@2.1.0: {} + + bidi-js@1.1.0: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@1.1.21: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.7: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.29.5: + dependencies: + baseline-browser-mapping: 2.11.34 + caniuse-lite: 1.0.30001820 + electron-to-chromium: 1.5.472 + node-releases: 2.0.63 + update-browserslist-db: 1.3.4(browserslist@4.29.5) + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + c12@3.1.0(magicast@0.3.5): + dependencies: + chokidar: 4.0.3 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 16.6.1 + exsolve: 1.2.0 + giget: 2.0.0 + jiti: 2.7.0 + ohash: 2.0.12 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.4.0 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001820: {} + + ccount@2.0.1: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + change-case@5.4.4: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + check-error@2.1.3: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + citty@0.2.2: {} + + classnames@2.5.1: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.3.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + cluster-key-slot@1.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@1.4.0: {} + + colorette@2.0.20: {} + + comma-separated-tokens@2.0.3: {} + + commander@14.0.3: {} + + commander@4.1.1: {} + + comment-parser@1.4.9: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + confbox@0.3.2: {} + + consola@3.4.2: {} + + content-disposition@3.0.0: {} + + conventional-changelog-angular@8.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@9.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cookie@2.0.1: {} + + copy-text-to-clipboard@2.2.0: {} + + cosmiconfig-typescript-loader@6.3.0(@types/node@24.19.2)(cosmiconfig@9.0.2(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@types/node': 24.19.2 + cosmiconfig: 9.0.2(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 + + cosmiconfig@9.0.2(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.2 + + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@csstools/css-syntax-patches-for-csstree': 1.1.16(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.3 + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns-tz@1.3.8(date-fns@2.30.0): + dependencies: + date-fns: 2.30.0 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.29.10 + + dateformat@4.6.3: {} + + debug@4.4.3(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + decimal.js@10.6.0: {} + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + deepmerge-ts@7.1.5: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + defu@6.1.7: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dotenv@16.6.1: {} + + dotenv@18.0.9: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + effect@3.21.0: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + + electron-to-chromium@1.5.472: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + empathic@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@9.0.0: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.2 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.5 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.24 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es-toolkit@1.53.1: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.19.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-import-context@0.1.9(unrs-resolver@1.13.0): + dependencies: + get-tsconfig: 4.14.3 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.13.0 + + eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-context: 0.1.9(unrs-resolver@1.13.0) + get-tsconfig: 4.14.3 + is-bun-module: 2.0.0 + stable-hash-x: 0.2.0 + tinyglobby: 0.2.17 + unrs-resolver: 1.13.0 + optionalDependencies: + eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)): + dependencies: + '@typescript-eslint/types': 8.72.1 + comment-parser: 1.4.9 + debug: 4.4.3(supports-color@10.2.2) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-context: 0.1.9(unrs-resolver@1.13.0) + is-glob: 4.0.3 + minimatch: 10.2.6 + semver: 7.8.5 + stable-hash-x: 0.2.0 + unrs-resolver: 1.13.0 + optionalDependencies: + '@typescript-eslint/utils': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.2.0 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.14.0 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.5(jiti@2.7.0) + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.12 + eslint: 9.39.5(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 3.25.76 + zod-validation-error: 4.0.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.7(eslint@9.39.5(jiti@2.7.0)): + dependencies: + eslint: 9.39.5(jiti@2.7.0) + + eslint-plugin-simple-import-sort@14.0.0(eslint@9.39.5(jiti@2.7.0)): + dependencies: + eslint: 9.39.5(jiti@2.7.0) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.5(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.7 + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.10 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@10.2.2) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.10 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.1: + dependencies: + '@types/estree': 1.0.10 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.10 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expect-type@1.4.0: {} + + exsolve@1.2.0: {} + + extend@3.0.2: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-copy@3.1.0: {} + + fast-copy@4.3.1: {} + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-equals@5.4.5: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-json-stringify@7.2.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.2 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.2.3 + json-schema-ref-resolver: 3.0.1 + rfdc: 1.4.1 + + fast-levenshtein@2.0.6: {} + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.9: {} + + fast-uri@4.2.3: {} + + fastify-plugin@5.1.0: {} + + fastify-plugin@6.0.1: {} + + fastify@5.12.7: + dependencies: + '@fastify/ajv-compiler': 4.0.7 + '@fastify/error': 4.2.1 + '@fastify/fast-json-stringify-compiler': 5.1.1 + '@fastify/proxy-addr': 5.1.2 + abstract-logging: 2.0.1 + avvio: 9.3.1 + fast-json-stringify: 7.2.1 + find-my-way: 9.10.0 + light-my-request: 6.8.0 + pino: 10.4.0 + process-warning: 5.1.1 + rfdc: 1.4.1 + secure-json-parse: 4.1.1 + semver: 7.8.5 + toad-cache: 3.7.4 + + fastq@1.20.3: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-type@21.3.4: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.6.3 + token-types: 6.1.2 + uint8array-extras: 1.6.0 + transitivePeerDependencies: + - supports-color + + find-my-way@9.10.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.2 + + find-my-way@9.7.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.2 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.67.4 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.7.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.7 + node-fetch-native: 1.6.7 + nypm: 0.6.12 + pathe: 2.0.3 + + git-raw-commits@5.0.1(conventional-commits-parser@6.4.0): + dependencies: + '@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0) + meow: 13.2.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + global-directory@5.0.0: + dependencies: + ini: 6.0.0 + + globals@14.0.0: {} + + globals@17.14.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.10 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.7: + dependencies: + '@types/estree': 1.0.10 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-object: 2.0.2 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + helmet@8.3.0: {} + + help-me@5.0.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.16.0 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + husky@9.1.7: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.14: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + index-to-position@1.2.0: {} + + inherits@2.0.4: {} + + ini@6.0.0: {} + + inline-style-parser@0.2.7: {} + + inline-style-parser@0.2.9: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + ioredis@5.11.1: + dependencies: + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3(supports-color@10.2.2) + denque: 2.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ipaddr.js@2.5.0: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.8.5 + + is-callable@1.2.7: {} + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-decimal@2.0.1: {} + + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.7.0 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.24 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3(supports-color@10.2.2) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterare@1.2.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@2.6.1: {} + + jiti@2.7.0: {} + + joycon@3.1.1: {} + + js-levenshtein@1.1.6: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + js-yaml@5.3.0: + dependencies: + argparse: 2.0.1 + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.32 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.3 + '@exodus/bytes': 1.16.0 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + is-potential-custom-element-name: 1.0.1 + parse5: 8.1.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 7.30.1 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-ref-resolver@3.0.1: + dependencies: + dequal: 2.0.3 + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.2.0 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.3 + + light-my-request@6.8.0: + dependencies: + '@fastify/deepmerge': 3.2.4 + cookie: 2.0.1 + process-warning: 5.1.1 + set-cookie-parser: 3.2.2 + + lilconfig@3.1.4: {} + + lines-and-columns@1.2.4: {} + + linkifyjs@4.4.0: {} + + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.7 + string-argv: 0.3.2 + tinyexec: 1.3.1 + yaml: 2.9.1 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + load-esm@1.0.3: {} + + load-tsconfig@0.2.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.18.1: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lottie-web@5.13.0: {} + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@11.5.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.12 + '@babel/types': 7.29.11 + source-map-js: 1.2.2 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + math-intrinsics@1.1.0: {} + + mdast-util-find-and-replace@3.0.4: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.1.0 + + mdast-util-from-markdown@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.3 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.4 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.1.0 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.1 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.1.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.1 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.2.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.2.1: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + unist-util-visit: 5.2.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.27.1: {} + + memoize-one@5.2.1: {} + + meow@13.2.0: {} + + micromark-core-commonmark@2.0.4: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.1.0 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-edit-map: 1.0.0 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-table@2.1.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.2 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.10 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.10 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.10 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.10 + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.3 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-edit-map@1.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.10 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.3: {} + + micromark@4.0.3: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3(supports-color@10.2.2) + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + transitivePeerDependencies: + - supports-color + + mime@3.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.21 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.7 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.7 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mlly@1.8.2: + dependencies: + acorn: 8.19.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.20: {} + + napi-postinstall@0.3.7: {} + + natural-compare@1.4.0: {} + + nestjs-pino@5.3.1(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.4.0)(rxjs@7.8.2): + dependencies: + '@nestjs/common': 11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.2.8(@nestjs/common@11.2.8(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + pino: 10.4.0 + pino-http: 11.0.0 + rxjs: 7.8.2 + + node-fetch-native@1.6.7: {} + + node-releases@2.0.63: {} + + nypm@0.6.12: + dependencies: + citty: 0.2.2 + pathe: 2.0.3 + tinyexec: 1.3.1 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + ohash@2.0.12: {} + + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.22(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + orderedmap@2.1.1: {} + + own-keys@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + parse5@8.1.0: + dependencies: + entities: 9.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.3 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-http@11.0.0: + dependencies: + get-caller-file: 2.0.5 + pino: 10.4.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.1 + + pino-pretty@13.2.0: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.3.1 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.1 + sonic-boom: 5.0.2 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.4.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.1 + quick-format-unescaped: 4.0.4 + real-require: 1.0.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.4.0: + dependencies: + confbox: 0.3.2 + exsolve: 1.2.0 + pathe: 2.0.3 + + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + + pluralize@8.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(yaml@2.9.1): + dependencies: + lilconfig: 3.1.4 + optionalDependencies: + jiti: 2.7.0 + postcss: 8.5.32 + tsx: 4.23.15 + yaml: 2.9.1 + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + prelude-ls@1.2.1: {} + + prettier@3.9.10: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prisma@6.19.3(magicast@0.3.5)(typescript@5.9.3): + dependencies: + '@prisma/config': 6.19.3(magicast@0.3.5) + '@prisma/engines': 6.19.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - magicast + + prismjs@1.30.0: {} + + process-warning@4.0.1: {} + + process-warning@5.1.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.2.0: {} + + prosemirror-changeset@2.4.5: + dependencies: + prosemirror-transform: 1.12.4 + + prosemirror-commands@1.7.2: + dependencies: + prosemirror-model: 1.25.12 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + + prosemirror-dropcursor@1.8.4: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + prosemirror-view: 1.42.9 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.12 + prosemirror-state: 1.4.4 + prosemirror-view: 1.42.9 + + prosemirror-history@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + prosemirror-view: 1.42.9 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.12: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.6.0: + dependencies: + prosemirror-model: 1.25.12 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.12 + prosemirror-transform: 1.12.4 + prosemirror-view: 1.42.9 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.12 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + prosemirror-view: 1.42.9 + + prosemirror-transform@1.12.4: + dependencies: + prosemirror-model: 1.25.12 + + prosemirror-view@1.42.9: + dependencies: + prosemirror-model: 1.25.12 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.4 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + quick-format-unescaped@4.0.4: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.7 + destr: 2.0.5 + + react-dom@19.3.1(react@19.3.1): + dependencies: + react: 19.3.1 + scheduler: 0.28.0 + + react-draggable@4.7.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-refresh@0.18.0: {} + + react-resizable@3.2.0(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + prop-types: 15.8.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-draggable: 4.7.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + + react-router@7.18.5(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + cookie: 1.1.1 + react: 19.3.1 + set-cookie-parser: 2.7.3 + optionalDependencies: + react-dom: 19.3.1(react@19.3.1) + + react-window@1.8.11(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@babel/runtime': 7.29.10 + memoize-one: 5.2.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + react@19.3.1: {} + + readdirp@4.1.2: {} + + real-require@1.0.0: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.10 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.19.0): + dependencies: + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.10 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.10 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + reflect-metadata@0.2.2: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.10 + '@types/hast': 3.0.5 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.1.0 + micromark-util-types: 2.0.3 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.2.1 + unified: 11.0.5 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + ret@0.5.1: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.67.4: + dependencies: + '@types/estree': 1.0.10 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.67.4 + '@rollup/rollup-android-arm64': 4.67.4 + '@rollup/rollup-android-x64': 4.67.4 + '@rollup/rollup-darwin-arm64': 4.67.4 + '@rollup/rollup-darwin-x64': 4.67.4 + '@rollup/rollup-freebsd-arm64': 4.67.4 + '@rollup/rollup-freebsd-x64': 4.67.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.67.4 + '@rollup/rollup-linux-arm-musleabihf': 4.67.4 + '@rollup/rollup-linux-arm64-gnu': 4.67.4 + '@rollup/rollup-linux-arm64-musl': 4.67.4 + '@rollup/rollup-linux-loong64-gnu': 4.67.4 + '@rollup/rollup-linux-loong64-musl': 4.67.4 + '@rollup/rollup-linux-ppc64-gnu': 4.67.4 + '@rollup/rollup-linux-ppc64-musl': 4.67.4 + '@rollup/rollup-linux-riscv64-gnu': 4.67.4 + '@rollup/rollup-linux-riscv64-musl': 4.67.4 + '@rollup/rollup-linux-s390x-gnu': 4.67.4 + '@rollup/rollup-linux-x64-gnu': 4.67.4 + '@rollup/rollup-linux-x64-musl': 4.67.4 + '@rollup/rollup-openbsd-x64': 4.67.4 + '@rollup/rollup-openharmony-arm64': 4.67.4 + '@rollup/rollup-win32-arm64-msvc': 4.67.4 + '@rollup/rollup-win32-ia32-msvc': 4.67.4 + '@rollup/rollup-win32-x64-gnu': 4.67.4 + '@rollup/rollup-win32-x64-msvc': 4.67.4 + fsevents: 2.3.3 + + rope-sequence@1.3.4: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.9.1 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex2@5.1.2: + dependencies: + ret: 0.5.1 + + safe-stable-stringify@2.5.0: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.28.0: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + secure-json-parse@4.1.1: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + set-cookie-parser@2.7.3: {} + + set-cookie-parser@3.2.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + sonic-boom@5.0.2: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.2: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + split2@4.2.0: {} + + stable-hash-x@0.2.0: {} + + stackback@0.0.2: {} + + standard-as-callback@2.1.0: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.5.0 + + strip-json-comments@3.1.1: {} + + strip-json-comments@5.0.3: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + strtok3@10.6.3: + dependencies: + '@tokenizer/token': 0.3.0 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + style-to-object@2.0.2: + dependencies: + inline-style-parser: 0.2.9 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-color@10.2.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + swagger-ui-dist@5.32.13: + dependencies: + '@scarf/scarf': 1.4.0 + + symbol-tree@3.2.4: {} + + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 10.4.5 + minimatch: 10.2.6 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.6: {} + + tldts-core@7.4.20: {} + + tldts@7.4.19: + dependencies: + tldts-core: 7.4.20 + + toad-cache@3.7.4: {} + + toidentifier@1.0.1: {} + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.3 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.19 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tslib@2.9.1: {} + + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(typescript@5.9.3)(yaml@2.9.1): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3(supports-color@10.2.2) + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15)(yaml@2.9.1) + resolve-from: 5.0.0 + rollup: 4.67.4 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.32 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsx@4.23.15: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + turbo@2.11.10: + optionalDependencies: + '@turbo/darwin-64': 2.11.10 + '@turbo/darwin-arm64': 2.11.10 + '@turbo/linux-64': 2.11.10 + '@turbo/linux-arm64': 2.11.10 + '@turbo/windows-64': 2.11.10 + '@turbo/windows-arm64': 2.11.10 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@4.41.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.5: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.72.1(@typescript-eslint/parser@8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.72.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + ufo@1.6.4: {} + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.6.0: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.24.6: {} + + undici@7.30.1: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.2.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.1.0 + + unrs-resolver@1.13.0: + dependencies: + napi-postinstall: 0.3.7 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.13.0 + '@unrs/resolver-binding-android-arm64': 1.13.0 + '@unrs/resolver-binding-darwin-arm64': 1.13.0 + '@unrs/resolver-binding-darwin-x64': 1.13.0 + '@unrs/resolver-binding-freebsd-x64': 1.13.0 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.13.0 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.13.0 + '@unrs/resolver-binding-linux-arm64-gnu': 1.13.0 + '@unrs/resolver-binding-linux-arm64-musl': 1.13.0 + '@unrs/resolver-binding-linux-loong64-gnu': 1.13.0 + '@unrs/resolver-binding-linux-loong64-musl': 1.13.0 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.13.0 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.13.0 + '@unrs/resolver-binding-linux-riscv64-musl': 1.13.0 + '@unrs/resolver-binding-linux-s390x-gnu': 1.13.0 + '@unrs/resolver-binding-linux-x64-gnu': 1.13.0 + '@unrs/resolver-binding-linux-x64-musl': 1.13.0 + '@unrs/resolver-binding-openharmony-arm64': 1.13.0 + '@unrs/resolver-binding-win32-arm64-msvc': 1.13.0 + '@unrs/resolver-binding-win32-ia32-msvc': 1.13.0 + '@unrs/resolver-binding-win32-x64-msvc': 1.13.0 + + update-browserslist-db@1.3.4(browserslist@4.29.5): + dependencies: + browserslist: 4.29.5 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js-replace@1.0.2: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.7.0(react@19.3.1): + dependencies: + react: 19.3.1 + + utility-types@3.11.0: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@3.2.4(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@10.2.2) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.32 + rollup: 4.67.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.2 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.23.15 + yaml: 2.9.1 + + vitest@3.2.7(@types/debug@4.1.13)(@types/node@24.19.2)(jiti@2.7.0)(jsdom@28.1.0)(tsx@4.23.15)(yaml@2.9.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3(supports-color@10.2.2) + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.5(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + vite-node: 3.2.4(@types/node@24.19.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.13 + '@types/node': 24.19.2 + jsdom: 28.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.16.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.24 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.24: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml-ast-parser@0.0.43: {} + + yaml@2.9.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + + zustand@5.0.16(@types/react@19.3.0)(react@19.3.1)(use-sync-external-store@1.7.0(react@19.3.1)): + optionalDependencies: + '@types/react': 19.3.0 + react: 19.3.1 + use-sync-external-store: 1.7.0(react@19.3.1) + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..e3f3440 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,14 @@ +packages: + - 'apps/*' + - 'packages/*' + +onlyBuiltDependencies: + - '@prisma/client' + - '@prisma/engines' + - esbuild + - prisma + - sharp + - unrs-resolver + +overrides: + fastify: 5.12.7 diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 0000000..e128106 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,11 @@ +/** @type {import('prettier').Config} */ +export default { + printWidth: 100, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'all', + arrowParens: 'always', + endOfLine: 'lf', +}; diff --git a/prisma/SEED.md b/prisma/SEED.md new file mode 100644 index 0000000..83cc575 --- /dev/null +++ b/prisma/SEED.md @@ -0,0 +1,17 @@ +# 种子数据状态(A0) + +A0-INF-02 的计划要求种子包含「超管、示例角色、核心字典、电商行业包占位」。但这四类数据依赖的用户 / 角色 / 字典 / 行业包表分别在 A1(identity)、A3(industry)、A4(catalog)与 B0 批次才建表,A0 阶段提前建表会越过批次边界、造成后续迁移冲突。 + +A0 的处理: + +- 已 seed(`prisma/seed.ts`):`system_configs` 中平台级配置项,按 `config_key` 幂等 upsert,重复执行不重复插入、不覆盖人工调整值。 +- 待后续批次接入(保持 TODO,不在 A0 建表): + +| 种子 | 依赖批次 | 说明 | +| -------------------------------- | ----------- | ------------------------------------------------------------------------ | +| 超级管理员账号 | A1 identity | 需 `users` / `user_roles` 表 | +| 示例角色与权限点 | A2 identity | 需 `roles` / `role_permissions` 表,权限点常量已在 `@lingxi/shared` 定义 | +| 核心字典(素材类型、脚本状态等) | A4 catalog | 需 `dict_types` / `dict_items` 表 | +| 电商行业包占位 | A3 industry | 需 `industry_packs` 表与行业包 schema | + +> 【待确认 Q-A0-01】开发计划 A0-INF-02 的种子验收与批次建表顺序冲突,A0 按「只种已建表数据」执行,待产品/架构确认是否调整该 WBS 描述。 diff --git a/prisma/migrations/20261102083445_init_platform_baseline/migration.sql b/prisma/migrations/20261102083445_init_platform_baseline/migration.sql new file mode 100644 index 0000000..d4ac5e6 --- /dev/null +++ b/prisma/migrations/20261102083445_init_platform_baseline/migration.sql @@ -0,0 +1,60 @@ +-- CreateTable +CREATE TABLE "outbox_events" ( + "id" CHAR(26) NOT NULL, + "aggregate_type" VARCHAR(64) NOT NULL, + "aggregate_id" CHAR(26) NOT NULL, + "event_type" VARCHAR(128) NOT NULL, + "payload_json" JSONB NOT NULL, + "trace_id" VARCHAR(64), + "occurred_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published_at" TIMESTAMPTZ(3), + "attempts" INTEGER NOT NULL DEFAULT 0, + "last_error" TEXT, + + CONSTRAINT "outbox_events_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "idempotency_keys" ( + "id" CHAR(26) NOT NULL, + "scope" VARCHAR(64) NOT NULL, + "key" VARCHAR(128) NOT NULL, + "request_hash" VARCHAR(64) NOT NULL, + "response_status" INTEGER, + "response_body" JSONB, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires_at" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "idempotency_keys_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "system_configs" ( + "id" CHAR(26) NOT NULL, + "config_key" VARCHAR(128) NOT NULL, + "config_value" JSONB NOT NULL, + "value_type" VARCHAR(16) NOT NULL, + "description" VARCHAR(255) NOT NULL DEFAULT '', + "version" INTEGER NOT NULL DEFAULT 1, + "is_editable" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(3) NOT NULL, + "updated_by" CHAR(26), + + CONSTRAINT "system_configs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "outbox_events_published_at_occurred_at_idx" ON "outbox_events"("published_at", "occurred_at"); + +-- CreateIndex +CREATE INDEX "outbox_events_aggregate_type_aggregate_id_idx" ON "outbox_events"("aggregate_type", "aggregate_id"); + +-- CreateIndex +CREATE INDEX "idempotency_keys_expires_at_idx" ON "idempotency_keys"("expires_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "idempotency_keys_scope_key_key" ON "idempotency_keys"("scope", "key"); + +-- CreateIndex +CREATE UNIQUE INDEX "system_configs_config_key_key" ON "system_configs"("config_key"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/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/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..ed7e80f --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,64 @@ +// 灵犀 Prisma schema(A0 基线) +// 依据:docs/工程规范/05-数据库规范.md、技术架构 §5.3 / §5.4.5 +// 约定:表/列 snake_case;主键 char(26) ULID(应用侧生成);时间 timestamptz;JSON 用 JsonB。 +// A0 只建平台侧骨架表(outbox_events / idempotency_keys / system_configs); +// 身份、客户、行业、脚本、素材等业务表按里程碑在各自模块内新增,禁止跨模块表。 + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +/// 事务性发件箱:领域事件先落库,再由 worker 投递(技术架构 §5.3) +model OutboxEvent { + id String @id @db.Char(26) + aggregateType String @map("aggregate_type") @db.VarChar(64) + aggregateId String @map("aggregate_id") @db.Char(26) + eventType String @map("event_type") @db.VarChar(128) + payloadJson Json @map("payload_json") @db.JsonB + traceId String? @map("trace_id") @db.VarChar(64) + occurredAt DateTime @default(now()) @map("occurred_at") @db.Timestamptz(3) + publishedAt DateTime? @map("published_at") @db.Timestamptz(3) + attempts Int @default(0) + lastError String? @map("last_error") @db.Text + + @@index([publishedAt, occurredAt]) + @@index([aggregateType, aggregateId]) + @@map("outbox_events") +} + +/// 请求幂等键:任务提交必须带 Idempotency-Key(TTL 24h) +model IdempotencyKey { + id String @id @db.Char(26) + scope String @db.VarChar(64) + key String @db.VarChar(128) + requestHash String @map("request_hash") @db.VarChar(64) + responseStatus Int? @map("response_status") + responseBody Json? @map("response_body") @db.JsonB + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + expiresAt DateTime @map("expires_at") @db.Timestamptz(3) + + @@unique([scope, key]) + @@index([expiresAt]) + @@map("idempotency_keys") +} + +/// 系统配置:阈值 / 上限类数字唯一存放处(PRD R-26),带版本与变更审计 +model SystemConfig { + id String @id @db.Char(26) + configKey String @unique @map("config_key") @db.VarChar(128) + configValue Json @map("config_value") @db.JsonB + valueType String @map("value_type") @db.VarChar(16) + description String @default("") @db.VarChar(255) + version Int @default(1) + isEditable Boolean @default(true) @map("is_editable") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) + updatedBy String? @map("updated_by") @db.Char(26) + + @@map("system_configs") +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..763b302 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,112 @@ +// 灵犀 · 种子数据(A0) +// 原则: +// 1) 只 seed 已建表的系统配置(system_configs),不提前创建 A1+ 业务表; +// 2) 幂等:按 config_key upsert,已存在的配置不覆盖(避免冲掉人工调整值); +// 3) 超管、角色、字典、电商行业包占位等种子依赖 A1/A3/A4/B0 的建表,见 prisma/SEED.md。 +import { randomBytes } from 'node:crypto'; +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { PrismaClient } from '@prisma/client'; +import { config as loadDotenv } from 'dotenv'; + +for (const file of ['.env.local', '.env']) { + const path = resolve(process.cwd(), file); + if (existsSync(path)) { + loadDotenv({ path }); + } +} + +const prisma = new PrismaClient(); + +const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; + +function ulid(): string { + let time = Date.now(); + const timeChars = new Array(10); + for (let i = 9; i >= 0; i -= 1) { + timeChars[i] = ENCODING[time % 32] ?? '0'; + time = Math.floor(time / 32); + } + const random = randomBytes(16); + const randomChars = Array.from(random, (byte) => ENCODING[byte & 31] ?? '0'); + return `${timeChars.join('')}${randomChars.join('')}`; +} + +interface SeedConfig { + key: string; + value: string | number | boolean; + valueType: 'string' | 'number' | 'boolean'; + description: string; +} + +const systemConfigs: SeedConfig[] = [ + { + key: 'platform.name', + value: '灵犀', + valueType: 'string', + description: '平台名称(展示用)', + }, + { + key: 'platform.export.signed_url_ttl_seconds', + value: 900, + valueType: 'number', + description: '导出产物签名 URL 有效期(秒),短时效', + }, + { + key: 'platform.export.archive_retention_days', + value: 3, + valueType: 'number', + description: '导出打包产物默认保留天数', + }, + { + key: 'platform.upload.max_upload_mb', + value: 500, + valueType: 'number', + description: '单文件上传上限(MB)', + }, + { + key: 'platform.page.default_limit', + value: 20, + valueType: 'number', + description: '列表默认每页数量', + }, + { + key: 'platform.ai.cost_soft_limit_ratio', + value: 0.8, + valueType: 'number', + description: 'AI 成本软上限触发比例', + }, + { + key: 'platform.ai.cost_hard_limit_ratio', + value: 1, + valueType: 'number', + description: 'AI 成本硬上限触发比例(达到即拒绝新任务)', + }, +]; + +async function main(): Promise { + for (const item of systemConfigs) { + await prisma.systemConfig.upsert({ + where: { configKey: item.key }, + update: {}, + create: { + id: ulid(), + configKey: item.key, + configValue: item.value, + valueType: item.valueType, + description: item.description, + }, + }); + } + console.log(`[seed] system_configs 就绪:${String(systemConfigs.length)} 项(幂等)`); +} + +main() + .catch((error: unknown) => { + console.error('[seed] 失败:', error); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/only-allow-pnpm.mjs b/scripts/only-allow-pnpm.mjs new file mode 100644 index 0000000..9655467 --- /dev/null +++ b/scripts/only-allow-pnpm.mjs @@ -0,0 +1,10 @@ +const userAgent = process.env.npm_config_user_agent ?? ''; + +if (!userAgent.startsWith('pnpm/')) { + console.error(''); + console.error('✗ 灵犀项目只允许使用 pnpm 10 安装依赖(见 AGENTS.md §3)。'); + console.error(' 请执行:corepack enable && pnpm install'); + console.error(' 或使用:npx pnpm@10.34.9 install'); + console.error(''); + process.exit(1); +} diff --git a/scripts/openapi-diff.mjs b/scripts/openapi-diff.mjs new file mode 100644 index 0000000..01016f0 --- /dev/null +++ b/scripts/openapi-diff.mjs @@ -0,0 +1,95 @@ +#!/usr/bin/env node +/** + * OpenAPI 兼容性检查(A0 回退实现)。 + * + * 优先使用 oasdiff 二进制;未安装时使用本脚本的结构化回退检查: + * - 删除 path / operation + * - 删除 components.schemas + * - 请求体必填字段新增、字段类型变更 + * + * 基线来自 git HEAD 中的快照;首次提交(快照尚未入库)时跳过。 + */ +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const SNAPSHOT = resolve(process.cwd(), 'openapi/lingxi.openapi.json'); +const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']; + +if (!existsSync(SNAPSHOT)) { + console.error(`未找到 OpenAPI 快照:${SNAPSHOT},请先执行 pnpm openapi:export`); + process.exit(1); +} + +const current = JSON.parse(readFileSync(SNAPSHOT, 'utf8')); + +let baseline = null; +try { + const raw = execFileSync('git', ['show', `HEAD:${'openapi/lingxi.openapi.json'}`], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + baseline = JSON.parse(raw); +} catch { + console.log('基线快照尚未入库,跳过 OpenAPI diff(首次提交)。'); + process.exit(0); +} + +const breaking = []; + +for (const [path, baselinePath] of Object.entries(baseline.paths ?? {})) { + const currentPath = current.paths?.[path]; + if (!currentPath) { + breaking.push(`删除路径:${path}`); + continue; + } + for (const method of HTTP_METHODS) { + if (baselinePath[method] !== undefined && currentPath[method] === undefined) { + breaking.push(`删除操作:${method.toUpperCase()} ${path}`); + } + } +} + +for (const schemaName of Object.keys(baseline.components?.schemas ?? {})) { + if (current.components?.schemas?.[schemaName] === undefined) { + breaking.push(`删除 schema:${schemaName}`); + } +} + +const baselineSchemas = baseline.components?.schemas ?? {}; +const currentSchemas = current.components?.schemas ?? {}; +for (const [name, baselineSchema] of Object.entries(baselineSchemas)) { + const currentSchema = currentSchemas[name]; + if (currentSchema === undefined) { + continue; + } + const baselineRequired = new Set(baselineSchema.required ?? []); + for (const required of currentSchema.required ?? []) { + if (!baselineRequired.has(required)) { + breaking.push(`schema ${name} 新增必填字段:${required}`); + } + } + for (const [property, baselineProperty] of Object.entries(baselineSchema.properties ?? {})) { + const currentProperty = currentSchema.properties?.[property]; + if ( + currentProperty !== undefined && + baselineProperty.type !== undefined && + currentProperty.type !== undefined && + baselineProperty.type !== currentProperty.type + ) { + breaking.push( + `schema ${name}.${property} 类型变更:${baselineProperty.type} -> ${currentProperty.type}`, + ); + } + } +} + +if (breaking.length > 0) { + console.error('检测到破坏性 OpenAPI 变更:'); + for (const item of breaking) { + console.error(` - ${item}`); + } + process.exit(1); +} + +console.log('OpenAPI 无破坏性变更。'); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..2ff5f98 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "useDefineForClassFields": false + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e040e54 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "types": ["node"] + }, + "include": ["playwright.config.ts", "scripts/**/*.ts", "e2e/**/*.ts", "prisma/seed.ts"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..288ec5f --- /dev/null +++ b/turbo.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "stream", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "typecheck": { + "dependsOn": ["^build"] + }, + "test": { + "dependsOn": ["^build"], + "outputs": ["coverage/**"] + }, + "test:int": { + "dependsOn": ["^build"], + "cache": false + } + } +}