commit 3772afd9878167a3d574a3788e706880c856764d Author: iven Date: Sat Jun 13 00:32:50 2026 +0800 chore: 干净 ERP 基座 — 删除 health/ai/wechat 业务代码 删除内容: - 前端: health/(67文件), ai/(2文件), Copilot, MediaPicker, 相关API/Store/Hook - 后端: wechat_handler, wechat_service, wechat_user entity, analytics handler, ai_workflow_seed - 配置: WechatConfig, AppConfig.wechat, AuthState wechat 字段 - 启动: 微信凭据检查块, ensure_ai_workflows() 调用 - 迁移: 新增 m20260613_000170_drop_wechat_users.rs - 脚本: api_test_health_alert.py, api_test_mp.py, mpsync.sh/ps1 - E2E: health-data page, flows/ 目录 保留: erp-core/auth/workflow/message/config/plugin + 基座前端 + 通用组件 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54fc1e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,109 @@ +# Rust +/target/ +**/*.rs.bk + +# Node +node_modules/ +dist/ + +# Tauri +apps/desktop/src-tauri/target/ + +# IDE +.vscode/ +.idea/ +*.swp + +# Environment +.env +*.env.local + +# OS +.DS_Store +Thumbs.db + +# Docker data +docker/postgres_data/ +docker/redis_data/ + +# Test artifacts +.test_token +test-results/ + +# Build outputs +apps/miniprogram/dist-h5/ + +# Runtime uploads +uploads/ + +# Temp logs +_server_out.txt +*.heapsnapshot +perf-trace-*.json +docs/debug-*.png + +# Development env +.env.development +docker/docker-compose.override.yml +.agents/skills/ +.claude/skills/ +.kiro/skills/ +.trae/skills/ +.windsurf/skills/ +skills/ + +# Logs +.logs/ +*.log + +# Playwright reports +**/playwright-report/ + +# Plans +plans/ + +# MCP config +.mcp.json + +# Superpowers temp +.superpowers/brainstorm/ + +# Test temp files +.test_token* +chi_sim.traineddata + +# Local settings +.claude/settings.local.json +tools/ + +# Temp/debug files +_temp/ +tmp/ +screenshots/ +server-log.txt +snapshot_*.txt +_*.txt +_server_*.txt +tmp_*.txt +direct_*.txt +server_*.txt +server_combined.txt +out.txt +_wx_login.json +.claude/settings.json + +# Trace/debug JSON +trace-*.json + +# Graphify knowledge graph (regenerated locally) +graphify-out/ + +# Native miniprogram (separate project) +apps/mp-native/ + +# Misc untracked +err.txt +uploads/g:/hms/.superpowers/ +.claude/skills/design-handoff/node_modules/ +.design/config.yml +.superpowers/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0baf9bc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,535 @@ +@wiki/index.md +整个项目对话都使用中文进行,包括文档、代码注释、事件名称等。 + +# HMS 健康管理平台 — 协作与实现规则 + +> **Health Management System (HMS)** — 面向体检中心/医疗机构的综合健康管理平台。从 ERP 底座分叉独立,继承身份权限/工作流/消息/配置等基础能力,`erp-health` 原生模块承载医疗业务。 + +> **当前阶段: erp-health 模块开发。** 设计规格已确认,开始实施。 + +## 1. 项目定位 + +### 1.1 这是什么 + +一个 **健康管理 + ERP 基础设施** 架构的医疗 SaaS 平台: + +- **医疗核心** — 患者管理、健康数据、预约排班、随访管理、咨询管理(原生 Rust 模块 erp-health) +- **基础底座** — 身份权限、工作流引擎、消息中心、系统配置(继承自 ERP) +- **多租户 + 私有化** — 默认 SaaS 共享数据库隔离,支持独立 schema 私有部署 +- **Web 优先** — 浏览器 SPA 是 PC 管理后台主力,小程序(患者端/医护端)独立开发 + +### 1.2 决策原则 + +**任何改动都要问:这对健康管理平台的医疗业务和可扩展性有帮助吗?** + +- ✅ 完善模块接口和 trait 定义 → 最高优先 +- ✅ 确保多租户隔离的正确性 → 最高优先 +- ✅ 按计划推进 Phase 交付物 → 高优先 +- ✅ 清晰的模块边界和事件契约 → 高优先 +- ❌ 跳过 Phase 顺序提前实现远期功能 → 禁止 +- ❌ 在模块间创建直接耦合 → 永远不做 +- ❌ 硬编码租户 ID 或绕过多租户中间件 → 永远不做 +- ❌ 过度设计未来才需要的能力 → 永远不做 + +### 1.3 架构铁律 + +| 约束 | 原因 | +|------|------| +| 模块间只通过事件总线和 trait 通信 | 保证模块可独立拆分为微服务 | +| 所有数据表必须含 `tenant_id` | 多租户是核心能力,不可事后补 | +| 使用 UUID v7 作为主键 | 时间排序 + 唯一性,分布式友好 | +| 软删除,不硬删除 | ERP 数据不可丢失,审计追溯需要 | +| 所有 API 使用 `/api/v1/` 前缀 | 版本化是 SaaS 产品的基本要求 | + +--- + +## 2. 工作风格 + +### 2.1 按计划推进 + +- **严格按 Phase 顺序执行** — Phase 2 依赖 Phase 1 的基础设施 +- **每个 Task 完成后立即提交** — 不积压,保持可追溯 +- **先测试后实现** — TDD 流程:写失败测试 → 实现 → 通过 → 提交 + +### 2.2 分步编写文档(强制) + +编写计划、设计文档、实施报告等长文档时,**必须分步编写**,禁止一次性输出全文: + +1. **先写大纲** — 确认文档结构和章节划分 +2. **逐章编写** — 每次只写 1-2 个章节,写完确认后继续下一章 +3. **最终整合** — 所有章节完成后合并为完整文档 + +**原因:** 上下文过长会导致输出截断或卡死。分步编写保证每步都能完整输出,且用户可以中途调整方向。 + +**适用范围:** 超过 200 行的文档、实施计划、设计规格、技术报告等。简短的 bugfix 说明、单页 wiki 更新不受此限制。 + +### 2.3 讨论记录 + +每次发散式讨论(brainstorming、方案探索、需求梳理、技术选型等)**必须建立独立文档**: + +- **存放位置:** `docs/discussions/YYYY-MM-DD-{主题简称}.md` +- **文档格式:** + ```markdown + # {讨论主题} + > 日期: YYYY-MM-DD | 参与者: ... + + ## 背景 + 为什么会有这次讨论 + + ## 讨论要点 + - 要点 1 + - 要点 2 + + ## 结论 / 待定 + 达成的共识或遗留问题 + ``` +- **时机:** 讨论结束后立即创建,不要积压。如果讨论横跨多个主题,拆分为多份文档。 +- **用途:** 作为后续实施的输入和决策追溯的依据,避免"之前讨论过但忘了结论"。 + +### 2.4 模块化思维 + +开发任何功能时先问: + +1. **它属于哪个模块?** — 不确定就放到 `erp-core` 共享层 +2. **它的接口是什么?** — 先定义 trait,再实现 +3. **它需要发什么事件?** — 跨模块通知必须走事件总线 +4. **其他模块怎么发现它?** — 通过 `ErpModule` trait 注册 + +### 2.5 闭环工作法(强制) + +每次改动**必须**按顺序完成以下步骤,不允许跳过: + +0. **阅读 Wiki(强制起点)** — 收到任何任务后,**先读 wiki 再动手**: + - 读取 `wiki/index.md` 了解项目全貌和当前进度 + - 根据任务涉及的范围,读取相关 wiki 页面(`wiki/infrastructure.md`、`wiki/testing.md`、`wiki/wasm-plugin.md` 等) + - wiki 中包含实际的环境配置(数据库连接、端口、登录凭据、启动方式),不看 wiki 就无法正确验证 + - **违反此步骤 = 盲目工作,浪费时间去猜环境配置,产出不可信** +1. **现状确认(强制)** — 动手之前,先检查代码里已经有什么: + - 用 Grep/Glob/Read 工具搜索相关文件,确认哪些能力已存在 + - 明确列出"已有"和"缺失",不允许凭印象断言缺失 + - 如果不确定现有实现状态,停下来问用户,不要编造 + - 违反此步骤 = 所有后续工作可能脱离实际,白费力气 +2. **理解需求** — 确认改动的目标模块和影响范围 +3. **最小实现** — 只改必要的代码,保持模块边界 +4. **验证通过** — 必须全部通过才可继续: + - `cargo check` — 编译无错误 + - `cargo test --workspace` — 所有测试通过(有相关测试时) + - 功能验证 — 启动后端 + 前端服务,在浏览器中实际操作验证改动生效(涉及 API 或 UI 时) + - `pnpm build` — 前端生产构建通过(涉及前端时) +5. **提交 + 文档 + 推送(三合一,强制)** — 验证通过后按顺序执行: + - a. 按 §5 规范提交代码 + - b. 检查本次变更是否触发 wiki 更新(见下方 wiki 更新触发条件),触发则更新后单独 `docs(wiki)` 提交 + - c. `git push` 立即推送,不允许"等一下再推" + - **禁止连续 5 个非 docs 提交而不更新 wiki 关键数字** + +#### wiki 更新触发条件(步骤 5b 的判定标准) + +以下任一条件满足时,**必须**更新 wiki 后才能继续下一任务: + +- **fix 提交** → `wiki/index.md` 症状导航新增条目或标记"已修复" +- **feat 提交(新功能)** → `wiki/index.md` 关键数字更新 + 对应模块 wiki 页更新(实体数/路由数/端点数等) +- **数据库迁移变化** → 关键数字中的迁移数/表数更新 +- **API 路由变化** → 路由数更新 +- **测试数量变化** → 测试数/断言数更新 +- **连续 5 个代码提交** → 强制做一次 wiki/index.md 关键数字全文校正(对比代码实际数量) + +**铁律:** +- **步骤 0 阅读 Wiki 是绝对起点** — 不读 wiki 就开干 = 连环境配置都不知道,所有验证步骤都是空谈。 +- **步骤 1 现状确认是强制起点** — 不检查就开干 = 脱离实际,所有产出不可信。 +- **步骤 4 功能验证必须实际操作** — 只看编译通过不算验证,必须启动服务、在浏览器中确认功能正常。 +- **步骤 5 三合一是强制流程** — 提交后必须检查 wiki、必须推送,缺一不可。 +- **每次新会话开始时,先检查是否有未推送的提交并立即推送**。 + +### 2.6 Feature DoD — 功能完成定义(强制) + +> 历史数据显示 24% 的提交是 fix,根因是缺少统一的完成标准。 +> 每个功能标记"完成"前,**必须**逐项检查以下清单,不允许跳过。 + +#### 后端 + +- [ ] Entity 包含所有标准字段(`id`/`tenant_id`/`created_at`/`updated_at`/`created_by`/`updated_by`/`deleted_at`/`version`) +- [ ] Handler 添加 `require_permission` 权限守卫 +- [ ] 权限码已写入 seed 迁移(每个实体 `.list` + `.manage`,权限码前缀与实体名一致) +- [ ] utoipa 注解已添加(`#[derive(utoipa::OpenApi)]` + path/response schema) +- [ ] Service 层核心路径有单元/集成测试 +- [ ] 多租户隔离正确(所有查询含 `tenant_id` 过滤,无手写 SQL 拼接) +- [ ] 输入验证完整(必填字段 + 格式校验 + 长度限制) +- [ ] 错误处理统一(`AppError`,不 panic,不 unwrap 生产代码) +- [ ] 关键操作有 `tracing` 日志(info/warn/error 级别合理) + +#### 前端(Web) + +- [ ] API 路径与后端 OpenAPI spec 一致(不手写路径,从 `api/health/` 模块调用) +- [ ] 路由声明权限码(`permissions: [...]`),与后端 handler 一致 +- [ ] 菜单配置已更新(`parent_id` 正确 + `permission` 字段 + `menu_roles` 关联) +- [ ] 错误状态有用户友好提示(不显示原始 error message) +- [ ] 不使用 `any` 类型(用 `unknown` + 类型守卫) + +#### 前端(小程序) + +- [ ] Service 层接口契约与后端 DTO 一致(字段名/类型/结构体) +- [ ] 登录态处理正确(`useDidShow` 恢复认证、退出清理 Storage) +- [ ] 页面间数据通过 API 获取,不用 Storage 传递 +- [ ] 长者模式适配完成(字号 ≥ 22px) +- [ ] 图片使用合法 URL(HTTPS 或相对路径,不用 HTTP) + +#### 安全 + +- [ ] 新增端点有权限声明(默认拒绝,不是默认放行) +- [ ] 敏感数据有脱敏/加密处理(PII 字段走 AES-256-GCM) +- [ ] 用户输入已验证和消毒(防 SQL 注入、XSS、命令注入、路径穿越) +- [ ] 无 CORS 通配符、无硬编码密钥、无 fallback 默认密钥 +- [ ] 日志中无敏感数据输出(密码、token、身份证号、手机号等) +- [ ] 文件上传有 MIME 类型验证 + 大小限制 + 路径穿越防护 +- [ ] API 响应不暴露内部实现细节(数据库错误、堆栈跟踪、文件路径) +- [ ] 速率限制已配置(认证端点更严格) +- [ ] 密钥通过环境变量注入,`.env.example` 已同步更新 + +#### 文档一致性 + +- [ ] `wiki/index.md` 关键数字与代码实际状态一致(迁移数、路由数、实体数、测试数等) +- [ ] 新增/修复的 bug 已记录在症状导航中(含根因+解决方案) +- [ ] 新增功能已记录在对应模块 wiki 页面中(实体、端点、事件等) +- [ ] wiki 页面的"最后更新"日期已刷新为当天 + +#### 端到端验证 + +- [ ] `cargo check` 全 workspace 通过 +- [ ] `cargo test` 全部通过 +- [ ] 浏览器中手动验证功能正常(列表/创建/编辑/删除/权限拦截) +- [ ] 小程序中验证(涉及小程序页面时) +- [ ] 相关路由权限按角色测试通过(至少 admin + 只读角色) +- [ ] 本地提交已推送到远程仓库 + +--- + +## 3. 实现规则 + +### 3.1 错误处理 + +- **跨 crate 边界**:使用 `thiserror` 定义类型化错误,转换为 `AppError` +- **crate 内部**:可以使用 `anyhow`,但**永远不**跨越 crate 边界 +- **数据库错误**:通过 `From` 自动转换为 `AppError` +- **验证错误**:包含字段级详情,方便 UI 渲染 + +### 3.2 数据库操作 + +- 所有 SeaORM Entity 必须包含:`id`, `tenant_id`, `created_at`, `updated_at`, `created_by`, `updated_by`, `deleted_at`, `version` +- 查询时**始终**带 `tenant_id` 过滤(中间件自动注入) +- 更新时检查 `version` 字段实现乐观锁 +- 删除使用软删除(设置 `deleted_at`) + +### 3.3 API 设计 + +- 所有端点使用 `/api/v1/` 前缀 +- 响应统一使用 `ApiResponse` 包装 +- 分页使用 `Pagination` + `PaginatedResponse` +- utoipa 自动生成 OpenAPI 文档 +- 租户 ID 从 JWT 中间件注入,**不在** API 路径中传递(管理员接口除外) + +#### 新增 API 端点安全检查(强制) + +> 默认拒绝是安全基线 — 绝大多数安全修复源于默认放行模式。 +> 新增端点时**必须**逐项确认: + +- [ ] 端点已添加 `require_permission` 权限守卫(非公开端点) +- [ ] 公开端点已显式标记为 `public`(不继承认证中间件) +- [ ] 路由使用 `.nest()` 注册带中间件的子路由(禁止 `.merge()` 防止中间件泄漏) +- [ ] 敏感操作有速率限制 +- [ ] 无 `format!` 拼接 SQL — 所有查询使用 SeaORM 参数化 +- [ ] FHIR/第三方端点有 `tenant_id` 和 `allowed_patient_ids` 范围过滤 +- [ ] 无硬编码密钥或 fallback 默认值 + +#### 前后端接口同步检查(强制) + +> 前后端接口不一致是高频 bug 来源 — 任何 DTO 变更必须双向同步。 +> 后端 DTO 变更时**必须**同步检查前端: + +- [ ] DTO 字段名变更 → 前端 TypeScript 接口同步更新 +- [ ] DTO 新增必填字段 → 前端表单和请求体同步更新 +- [ ] API 路径变更 → 前端 `api/` 模块路径同步更新 +- [ ] 返回数据结构变更(数组/对象/嵌套)→ 前端解析逻辑同步更新 +- [ ] 枚举值变更 → 前端类型定义和 UI 映射同步更新 +- [ ] 后端新增端点 → 前端 API 模块同步添加调用函数,不允许留空 + +#### DTO 输入校验检查(强制) + +> DTO 输入校验是安全防线 — 缺失校验等于暴露攻击面,Update 和 Create 必须对称。 +> 新增/修改 DTO 时**必须**逐项确认: + +- [ ] 所有请求结构体已 `derive(Validate)`(包括 Update\*Req、查询参数) +- [ ] Update\*Req 与 Create\*Req 校验对称(不允许 Update 降级) +- [ ] 字符串字段有 `#[validate(length(min, max))]` +- [ ] 枚举/类型字段有 `#[validate(custom)]` 限制合法值 +- [ ] 集合字段有 `#[validate(length(min = 1))]` 非空检查 +- [ ] 数值范围字段有 `#[validate(range(min, max))]` +- [ ] URL 字段有 SSRF 防护(禁止 localhost/内网地址,仅 http/https) +- [ ] 密码字段有 `max = 128` 防止 DoS +- [ ] handler 层已调用 `req.validate().map_err(|e| AppError::Validation(e.to_string()))?` + +### 3.4 事件总线 + +- 模块间通信**只能**通过 `EventBus` +- 事件必须持久化到 `domain_events` 表(outbox 模式) +- 事件处理失败记录到 dead-letter 存储 +- 事件类型命名:`{模块}.{动作}` 如 `user.created`, `workflow.task.completed` +- **铁律:每个事件必须有至少一个消费者,否则功能不算完成。** 新增事件发布时必须同步实现消费者和对应测试。详见 `docs/discussions/2026-04-28-architecture-retrospective.md` §4。 + +### 3.5 Rust 代码规范 + +```rust +// 命名:snake_case (函数/变量), PascalCase (类型/trait), SCREAMING_SNAKE (常量) +// 模块公开接口通过 lib.rs 统一导出 +// 每个 public 函数和 trait 必须有文档注释 +// 异步函数返回 Result 时使用 AppResult 类型别名 +// 数据库操作使用 SeaORM 的 Entity + Model + Relation 模式 +``` + +### 3.6 TypeScript / React 代码规范 + +```typescript +// 避免 any,优先 unknown + 类型守卫 +// 函数组件 + hooks +// 复杂状态收敛到 Zustand store +// API 调用封装到独立的 service 层,不在组件中直接 fetch +// 使用 Ant Design 组件,不自行实现已有组件 +// 国际化文案使用 i18n key,不硬编码中文 +``` + +### 3.7 安全规范 + +#### 密钥与凭据管理 + +- 所有密钥、token、密码**必须**通过环境变量或密钥管理服务注入,**禁止**硬编码在源码中 +- 开发环境密钥**必须**与生产环境严格隔离(`cfg(debug_assertions)` 编译期防护) +- 生产密钥**禁止**有 fallback 默认值,缺失时启动 panic +- 新增密钥时必须同步更新 `.env.example` 和 `wiki/infrastructure.md` + +#### 依赖安全 + +- 新增依赖前**必须**检查已知漏洞(`cargo audit` / `npm audit`) +- 禁止引入有未修补高危漏洞的依赖版本 +- 定期更新依赖到最新安全补丁版本 + +#### 数据安全 + +- PII 数据(姓名、身份证号、手机号、地址等)**必须**加密存储(AES-256-GCM) +- 日志中**禁止**输出 PII 数据和认证凭据(密码、token、session key) +- 敏感操作(登录、权限变更、数据导出、批量删除)**必须**记录审计日志(操作者、时间、目标、结果) +- 文件上传**必须**验证 MIME 类型 + 限制文件大小 + 防止路径穿越(文件名 sanitize) + +#### 传输安全 + +- 生产环境**必须**强制 HTTPS,**禁止**降级到 HTTP +- HTTP 响应**必须**包含安全头(HSTS、CSP、X-Frame-Options、X-Content-Type-Options、Permissions-Policy) +- SSE/WebSocket 长连接认证 token 不通过 URL query 参数传递(使用 header 或 cookie) +- API 响应**禁止**暴露内部实现细节(堆栈跟踪、数据库错误、文件路径、SQL 语句) + +#### 认证与授权 + +- 密码**必须**使用单向哈希(bcrypt/argon2),**禁止**明文或可逆加密存储 +- JWT **必须**设置合理过期时间,支持 token 吊销机制 +- 敏感操作(删除数据、权限变更)需要二次确认 +- 权限检查在 handler 层执行,**禁止**仅依赖前端隐藏控制访问 +- `tenant_id` **必须**从 JWT 中间件注入,**禁止**信任客户端传递的值 + +#### 速率限制 + +- 所有 API 端点**必须**配置速率限制 +- 认证相关端点(登录、注册、密码重置)限制更严格 +- 批量操作和数据导出需要独立的速率限制策略 +- 速率限制超出时返回 429 状态码,响应包含 `Retry-After` header + +--- + +## 4. 测试与验证 + +### 4.1 测试要求 + +| 测试类型 | 覆盖目标 | 工具 | +|----------|---------|------| +| 单元测试 | 每个 service 函数 | `#[cfg(test)]` + `tokio::test` | +| 集成测试 | API 端点 → 数据库 | Testcontainers + 真实 PostgreSQL | +| 多租户测试 | 数据隔离验证 | 独立测试 crate | +| E2E 测试 | 前端关键流程 | Playwright | +| 插件测试 | 动态表 CRUD + 租户隔离 | Testcontainers | + +### 4.2 验证命令 + +```bash +# Rust 编译检查 +cargo check + +# Rust 全量测试 +cargo test --workspace + +# 后端服务启动 +cd crates/erp-server && cargo run + +# Docker 环境 +cd docker && docker compose up -d + +# 桌面端开发 +cd apps/desktop && pnpm tauri dev + +# 数据库迁移检查 +docker exec erp-postgres psql -U erp -c "\dt" +``` + +### 4.3 Phase 完成标准 + +每个 Phase 完成时必须满足: + +- [ ] `cargo check` 全 workspace 通过 +- [ ] `cargo test` 全部通过 +- [ ] PostgreSQL 服务正常运行,迁移自动执行 +- [ ] 所有迁移可正/反向执行 +- [ ] API 端点可通过 Swagger UI 测试 +- [ ] 桌面端可正常启动并展示对应 UI +- [ ] 所有代码已提交 + +--- + +## 5. 提交规范 + +``` +(): +``` + +**类型:** +- `feat` — 新功能 +- `fix` — 修复问题 +- `refactor` — 重构 +- `docs` — 文档更新 +- `test` — 测试相关 +- `chore` — 杂项(构建、配置等) +- `perf` — 性能优化 + +**Scope 对应 crate 或模块名:** + +| scope | 范围 | +|-------|------| +| `core` | erp-core | +| `auth` | erp-auth | +| `workflow` | erp-workflow | +| `message` | erp-message | +| `config` | erp-config | +| `server` | erp-server | +| `health` | erp-health | +| `ai` | erp-ai | +| `dialysis` | erp-dialysis | +| `plugin` | erp-plugin / erp-plugin-prototype / erp-plugin-test-sample | +| `assessment` | erp-plugin-assessment | +| `crm` | erp-plugin-crm | +| `inventory` | erp-plugin-inventory | +| `web` | Web 前端 | +| `ui` | React 组件 | +| `db` | 数据库迁移 | +| `docker` | Docker 配置 | + +**示例:** + +``` +feat(auth): 添加用户管理 CRUD +feat(core): 实现事件总线和模块注册 +fix(server): 修复数据库连接池配置 +refactor(auth): 拆分 RBAC 和 ABAC 权限模型 +chore(docker): 添加 PostgreSQL 健康检查 +``` + +--- + +## 6. 反模式警告 + +- ❌ **不要**不看 wiki 就开干 — wiki 包含环境配置、数据库连接、启动方式、已知问题,不看就做等于盲猜,浪费时间且产出不可信 +- ❌ **不要**在业务 crate 之间创建直接依赖 — 只通过事件和 trait 通信 +- ❌ **不要**跳过多租户中间件 — 所有数据操作必须带 `tenant_id` 过滤 +- ❌ **不要**硬编码配置值 — 使用 config.toml + 环境变量 +- ❌ **不要**跳过迁移直接建表 — 所有 schema 变更通过 SeaORM Migration +- ❌ **不要**在前端组件中直接调用 HTTP — 封装到 service 层 +- ❌ **不要**使用 `anyhow` 跨越 crate 边界 — 内部可用,对外必须转 `AppError` +- ❌ **不要**假设只有单租户 — 从第一天就按多租户设计 +- ❌ **不要**提前实现远期功能 — 严格按 Phase 计划推进 +- ❌ **不要**忽略 `version` 字段 — 所有更新操作必须检查乐观锁 +- ❌ **不要**在动态表 SQL 中拼接用户输入 — 使用 `sanitize_identifier` 防注入 +- ❌ **不要**在插件 crate 中直接依赖 erp-auth — 权限注册用 raw SQL,保持模块边界 +- ❌ **不要**在 plugin.toml 中使用与实体名不一致的权限码 — `permissions[].code` 前缀必须与 `schema.entities[].name` 完全一致(如实体 `customer_tag` → 权限码 `customer_tag.list`/`customer_tag.manage`,不能写成 `tag.manage`),否则页面 403 +- ❌ **不要**漏掉实体的 `.list` 权限 — 每个实体必须同时声明 `.list` 和 `.manage`,缺少 `.list` 导致列表页 403 +- ❌ **不要**跳过验证直接提交 — 编译/测试/功能验证必须全部通过 +- ❌ **不要**提交后忘记推送 — 不推送等于没完成,远程仓库必须同步。每次新会话开始先检查未推送提交 +- ❌ **不要**忘记更新文档 — 涉及架构、接口、模块变化时必须同步更新相关文档 +- ❌ **不要**连续 5 个代码提交不更新 wiki — wiki 是团队唯一真相源,过期数据比没有数据更有害 +- ❌ **不要**修复 bug 后跳过症状导航更新 — 每个修复都应该帮助未来遇到同类问题的人快速定位根因 +- ❌ **不要**新增功能后不更新 wiki 关键数字 — 迁移数/路由数/实体数/测试数必须与代码同步,否则 wiki 指标表就是废数据 +- ❌ **不要**一次性输出长文档 — 超过 200 行的文档必须分步编写(先大纲 → 逐章 → 整合),否则会因上下文过长卡死 +- ❌ **不要**忽略讨论记录 — 每次发散式讨论结束后必须建立文档到 `docs/discussions/`,不要口头确认后就忘 +- ❌ **不要**跳过 Feature DoD — 每个功能标记"完成"前必须通过 §2.6 检查清单,不全面验证就提交将导致后续反复修复 +- ❌ **不要**新增端点时默认放行 — 所有端点默认拒绝访问,必须显式声明权限 +- ❌ **不要**后端 DTO 变更不同步前端 — 字段名/路径/类型变更时必须同步更新前端 TypeScript 接口 +- ❌ **不要**写 Update DTO 时省略校验 — Update*Req 必须与 Create*Req 校验对称(Validate derive / 枚举 custom / Vec min=1 / 密码 max=128) +- ❌ **不要**URL 字段不做 SSRF 防护 — 禁止 localhost/127.0.0.1/内网地址,仅允许 http/https 协议 +- ❌ **不要**handler 层跳过 `.validate()` — 所有 `Json` handler 函数体第一行必须调 `req.validate().map_err(\|e\| AppError::Validation(e.to_string()))?` +- ❌ **不要**在日志中输出敏感数据 — 密码、token、身份证号、手机号等 PII 信息禁止写入日志 +- ❌ **不要**信任客户端传递的 `tenant_id` — 必须从 JWT 中间件注入,客户端可伪造 +- ❌ **不要**在生产代码中使用 `unwrap()` — 必须处理所有错误,使用 `?` 或 `map_err` +- ❌ **不要**将内部错误信息返回给客户端 — 数据库错误、堆栈跟踪、文件路径等必须转换为用户友好的错误消息 +- ❌ **不要**使用 HTTP 传输敏感数据 — 生产环境必须 HTTPS +- ❌ **不要**跳过依赖安全检查 — 新增依赖前运行 `cargo audit` / `npm audit`,禁止引入有高危漏洞的版本 +- ❌ **不要**文件上传不做安全处理 — 必须验证 MIME 类型 + 限制大小 + sanitize 文件名防路径穿越 + +### 场景化指令 + +- 当遇到**新增模块** → 实现 `ErpModule` trait,在 `erp-server` 注册 +- 当遇到**跨模块通信** → 定义事件类型,通过 `EventBus` 发布/订阅 +- 当遇到**数据查询** → 确保包含 `tenant_id` 过滤,检查软删除条件 +- 当遇到**新增 API** → 添加 utoipa 注解,确保 OpenAPI 文档同步 +- 当遇到**新增表** → 创建 SeaORM migration + Entity,包含所有标准字段 +- 当遇到**新增页面** → 使用 Ant Design 组件,i18n key 引用文案 +- 当遇到**新增业务模块插件** → 参考 `wiki/wasm-plugin.md` 的插件制作完整流程和 `.claude/skills/plugin-development/SKILL.md`,创建 cdylib crate + 实现 Guest trait + 编译为 WASM Component。**权限码必须与实体名一致(每个实体声明 `.list` + `.manage`)** +- 当遇到**新增/修改 DTO** → 参考 `wiki/architecture.md` §4 DTO 输入校验规范:`derive(Validate)` + 字段级校验 + handler 层 `validate()` 调用 + 单元测试 + +--- + +## 7. 详细参考(wiki) + +以下内容已从本文件迁移到 wiki,需要时查阅: + +| 主题 | wiki 页面 | +|------|----------| +| 目录结构、crate 依赖、技术栈 | `wiki/architecture.md` §2 | +| 模块开发规范、ErpModule trait、迁移规范 | `wiki/architecture.md` §3 | +| 安全注意事项(认证/多租户/通用) | `wiki/architecture.md` §4 | +| UI 布局规范 | `wiki/frontend.md` §2 | +| 常用命令(Rust/前端/数据库/WASM) | `wiki/infrastructure.md` §3 | +| 设计文档索引 | `wiki/index.md` | +| 开发进度、模块状态 | `wiki/index.md` 关键数字 | +| 环境配置、连接信息、登录凭据 | `wiki/infrastructure.md` §2 | + +## graphify — 代码知识图谱 + +> 项目知识图谱位于 `graphify-out/`,当前规模:18,517 节点 / 22,666 边 / 1,841 社区(纯 AST 解析,无 API 成本)。 +> 工具:`python -m graphify`(已安装 graphifyy 0.8.18)。 + +### 开发流程中的使用场景 + +| 时机 | 命令 | 目的 | +|------|------|------| +| **接手新任务,理解代码关系** | `graphify query "概念名"` | 搜索相关节点,比 Grep 更精准(按调用/引用/包含关系) | +| **排查 bug,追踪调用链** | `graphify path "A" "B"` | 查找两个模块/函数间的最短路径 | +| **理解某个模块的职责** | `graphify explain "模块名"` | 自然语言解释节点及其邻居 | +| **代码改动后** | `graphify update .` | 增量更新图谱(AST-only,秒级完成) | +| **宏观架构审查** | 读 `graphify-out/GRAPH_REPORT.md` | 全局社区结构、跨文件关系概览 | + +### 使用优先级(融入 §2.5 闭环工作法) + +在 §2.5 步骤 1「现状确认」中,**优先使用 graphify 替代盲目 Grep**: + +1. **先 `graphify query`** — 精确定位相关节点和社区(比 Grep 返回更结构化的结果) +2. **再 `graphify path`** — 确认模块间依赖路径(避免遗漏间接依赖) +3. **最后 Grep/Glob/Read** — 确认 graphify 发现的具体文件内容 + +### 注意事项 + +- `graphify update .` 纯本地 AST 解析,不消耗 LLM token,每次代码改动后都可以运行 +- 查询结果比 GRAPH_REPORT.md 更精准,优先使用 query/path/explain,仅在需要全局视图时读报告 +- 首次生成需几分钟(1712 文件),后续增量更新秒级完成 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..65eea08 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6707 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "ammonia" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" +dependencies = [ + "cssparser", + "html5ever", + "maplit", + "tendril", + "url", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cap-fs-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.52.0", +] + +[[package]] +name = "cap-net-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" +dependencies = [ + "cap-primitives", + "cap-std", + "rustix 1.1.4", + "smallvec", +] + +[[package]] +name = "cap-primitives" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.4", + "rustix-linux-procfs", + "windows-sys 0.52.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.4", + "winx", +] + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.8.23", + "yaml-rust2", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046d4b584c3bb9b5eb500c8f29549bec36be11000f1ba2a927cef3d1a9875691" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b194a7870becb1490366fc0ae392ccd188065ff35f8391e77ac659db6fb977" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6a4ab44c6b371e661846b97dab687387a60ac4e2f864e2d4257284aad9e889" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a44150c2f471a94023482bda1902710746e4bed9f9973d60c5a94319b06d" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b06598133b1dd76758b8b95f8d6747c124124aade50cea96a3d88b962da9fa" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.16.1", + "libm", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6190e2e7bcf0a678da2f715363d34ed530fedf7a2f0ab75edaefef72a70465ff" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f583cf203d1aa8b79560e3b01f929bdacf9070b015eec4ea9c46e22a3f83e4a0" + +[[package]] +name = "cranelift-control" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803159df35cc398ae54473c150b16d6c77e92ab2948be638488de126a3328fbc" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e417257082d88087f5bcce677525bdaa8322b88dd7f175ed1a1fd41d546c" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14db6b0e0e4994c581092df78d837be2072578f7cb2528f96a6cf895e56dee63" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec66ea5025c7317383699778282ac98741d68444f956e3b1d7b62f12b7216e67" + +[[package]] +name = "cranelift-native" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373ade56438e6232619d85678477d0a88a31b3581936e0503e61e96b546b0800" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.130.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53619d3cd5c78fd998c6d9420547af26b72e6456f94c2a8a2334cb76b42baa" + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erp-auth" +version = "0.1.0" +dependencies = [ + "argon2", + "async-trait", + "axum", + "chrono", + "dashmap", + "erp-core", + "jsonwebtoken", + "redis", + "reqwest", + "sea-orm", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tokio", + "tracing", + "utoipa", + "uuid", + "validator", +] + +[[package]] +name = "erp-config" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "chrono", + "erp-core", + "sea-orm", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "utoipa", + "uuid", + "validator", +] + +[[package]] +name = "erp-core" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "ammonia", + "anyhow", + "async-trait", + "axum", + "base64 0.22.1", + "chrono", + "dashmap", + "hex", + "hmac", + "rand 0.8.5", + "sea-orm", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tokio", + "tracing", + "utoipa", + "uuid", +] + +[[package]] +name = "erp-message" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-stream", + "async-trait", + "axum", + "chrono", + "erp-core", + "futures", + "sea-orm", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "utoipa", + "uuid", + "validator", +] + +[[package]] +name = "erp-plugin" +version = "0.1.0" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "chrono", + "csv", + "dashmap", + "erp-core", + "moka", + "regex", + "rust_xlsxwriter", + "sea-orm", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tokio", + "toml 0.8.23", + "tracing", + "utoipa", + "uuid", + "validator", + "wasmtime", + "wasmtime-wasi", +] + +[[package]] +name = "erp-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "chrono", + "config", + "erp-auth", + "erp-config", + "erp-core", + "erp-message", + "erp-plugin", + "erp-server-migration", + "erp-workflow", + "futures", + "hex", + "hmac", + "metrics", + "metrics-exporter-prometheus", + "moka", + "redis", + "sea-orm", + "serde", + "serde_json", + "sha2", + "sqlx", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "utoipa", + "uuid", +] + +[[package]] +name = "erp-server-migration" +version = "0.1.0" +dependencies = [ + "sea-orm-migration", + "tokio", +] + +[[package]] +name = "erp-workflow" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "chrono", + "erp-core", + "reqwest", + "sea-orm", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "utoipa", + "uuid", + "validator", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.4", + "windows-sys 0.52.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.4", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxprof-processed-profile" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" +dependencies = [ + "bitflags", + "debugid", + "rustc-hash", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +dependencies = [ + "log", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro 0.6.0", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inherent" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.52.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "serde", + "winapi", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash 0.8.12", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.4", + "rand_xoshiro 0.7.0", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +dependencies = [ + "crc32fast", + "hashbrown 0.16.1", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pgvector" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" +dependencies = [ + "serde", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "version_check", + "yansi", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pulley-interpreter" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010dec3755eb61b2f1051ecb3611b718460b7a74c131e474de2af20a845938af" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad360c32e85ca4b083ac0e2b6856e8f11c3d5060dafa7d5dc57b370857fa3018" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redis" +version = "0.27.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc" +dependencies = [ + "arc-swap", + "async-trait", + "backon", + "bytes", + "combine", + "futures", + "futures-util", + "itertools 0.13.0", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2 0.5.10", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regalloc2" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "952ddbfc6f9f64d006c3efd8c9851a6ba2f2b944ba94730db255d55006e0ffda" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-ini" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust_decimal" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rust_xlsxwriter" +version = "0.82.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61a82de4e7b30fc427909f2c5aafaada88cc7ae8316edabae435f74341f9278" +dependencies = [ + "zip", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.4", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sea-bae" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +dependencies = [ + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sea-orm" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc312fedd460a47ea563911761d254a84e7b51d8cc73ec92c929e78f33fa957" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "derive_more", + "futures-util", + "log", + "mac_address", + "ouroboros", + "pgvector", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror 2.0.18", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-cli" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da80ebcdb44571e86f03a2bdcb5532136a87397f366f38bbce64673fc5e6a450" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "glob", + "regex", + "sea-schema", + "sqlx", + "tokio", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9a3f90e336ec74803e8eb98c61bc98754c1adfba3b4f84d946237b752b1c88" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.117", + "unicode-ident", +] + +[[package]] +name = "sea-orm-migration" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c577f2959277e936c1d08109acd1e08fc36a95ef29ec028190ba82cad8f96e" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sea-query" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" +dependencies = [ + "bigdecimal", + "chrono", + "inherent", + "ordered-float", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.117", + "thiserror 2.0.18", +] + +[[package]] +name = "sea-schema" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338" +dependencies = [ + "futures", + "sea-query", + "sea-query-binder", + "sea-schema-derive", + "sqlx", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bigdecimal", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink 0.10.0", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rust_decimal", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.117", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bigdecimal", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bigdecimal", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand 0.8.5", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.52.0", + "winx", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", + "uuid", +] + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "serde", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-compose" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd23d12cc95c451c1306db5bc63075fbebb612bb70c53b4237b1ce5bc178343" +dependencies = [ + "anyhow", + "heck 0.5.0", + "im-rc", + "indexmap", + "log", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", + "wat", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +dependencies = [ + "leb128fmt", + "wasmparser 0.245.1", +] + +[[package]] +name = "wasm-encoder" +version = "0.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" +dependencies = [ + "leb128fmt", + "wasmparser 0.246.2", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags", + "hashbrown 0.16.1", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.245.1", +] + +[[package]] +name = "wasmtime" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce205cd643d661b5ba5ba4717e13730262e8cdbc8f2eacbc7b906d45c1a74026" +dependencies = [ + "addr2line", + "async-trait", + "bitflags", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "futures", + "fxprof-processed-profile", + "gimli", + "ittapi", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix 1.1.4", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "tempfile", + "wasm-compose", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "wat", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-environ" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8b78abf3677d4a0a5db82e5015b4d085ff3a1b8b472cbb8c70d4b769f019ce" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.16.1", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4fd4103ba413c0da2e636f73490c6c8e446d708cbde7573703941bc3d6a448" +dependencies = [ + "base64 0.22.1", + "directories-next", + "log", + "postcard", + "rustix 1.1.4", + "serde", + "serde_derive", + "sha2", + "toml 0.9.12+spec-1.1.0", + "wasmtime-environ", + "windows-sys 0.61.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3d6914f34be2f9d78d8ee9f422e834dfc204e71ccce697205fae95fed87892" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.245.1", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3751b0616b914fdd87fe1bf804694a078f321b000338e6476bc48a4d6e454f21" + +[[package]] +name = "wasmtime-internal-core" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22632b187e1b0716f1b9ac57ad29013bed33175fcb19e10bb6896126f82fac67" +dependencies = [ + "anyhow", + "hashbrown 0.16.1", + "libm", + "serde", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3ca07b3e0bb3429674b173b5800577719d600774dd81bff58f775c0aaa64ee" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.14.0", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c8b2c9704eb1f33ead025ec16038277ccb63d0a14c31e99d5b765d7c36da55" +dependencies = [ + "cc", + "cfg-if", + "libc", + "rustix 1.1.4", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d950310d07391d34369f62c48336ebb14eacbd4d6f772bb5f349c24e838e0664" +dependencies = [ + "cc", + "object", + "rustix 1.1.4", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3606662c156962d096be3127b8b8ae8ee2f8be3f896dad29259ff01ddb64abfd" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75eef0747e52dc545b075f64fd0e0cc237ae738e641266b1970e07e2d744bc32" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b0a5dab02a8fb527f547855ecc0e05f9fdc3d5bd57b8b080349408f9a6cece" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8007342bd12ff400293a817973f7ecd6f1d9a8549a53369a9c1af357166f1f1e" +dependencies = [ + "cranelift-codegen", + "gimli", + "log", + "object", + "target-lexicon", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7900c3e3c1d6e475bc225d73b02d6d5484815f260022e6964dca9558e50dd01a" +dependencies = [ + "anyhow", + "bitflags", + "heck 0.5.0", + "indexmap", + "wit-parser 0.245.1", +] + +[[package]] +name = "wasmtime-wasi" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3e3ddcfad69e9eb025bd19bff70dad45bafe1d6eacd134c0ffdfc4c161d045" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", + "system-interface", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5dd3b9f04a851c422d05f333366722742da46bff9369ae0191f32cf83565a" +dependencies = [ + "async-trait", + "bytes", + "futures", + "tracing", + "wasmtime", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "246.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.246.2", +] + +[[package]] +name = "wat" +version = "1.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" +dependencies = [ + "wast 246.0.2", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "wiggle" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1b1135efc8e5a008971897bea8d41ca56d8d501d4efb807842ae0a1c78f639" +dependencies = [ + "bitflags", + "thiserror 2.0.18", + "tracing", + "wasmtime", + "wasmtime-environ", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7bc2b0d50ec8773b44fbfe1da6cb5cc44a92deaf8483233dcf0831e6db33172" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasmtime-environ", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6c7d44ea552e1fbfdcd7a2cd83f5c2d1e803d5b1a11e3462c06888b77f455f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winch-codegen" +version = "43.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f45f7172a2628c8317766e427babc0a400f9d10b1c0f0b0617c5ed5b79de6" +dependencies = [ + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags", + "windows-sys 0.52.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330698718e82983499419494dd1e3d7811a457a9bf9f69734e8c5f07a2547929" +dependencies = [ + "anyhow", + "hashbrown 0.16.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.245.1", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast 35.0.2", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yaml-rust2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink 0.8.4", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dcf6f86 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,115 @@ +[workspace] +resolver = "2" +members = [ + "crates/erp-core", + "crates/erp-server", + "crates/erp-auth", + "crates/erp-workflow", + "crates/erp-message", + "crates/erp-config", + "crates/erp-server/migration", + "crates/erp-plugin", +] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT" + +[workspace.dependencies] +# Async +tokio = { version = "1", features = ["full"] } + +# Web +axum = { version = "0.8", features = ["multipart"] } +tower = "0.5" +tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs", "set-header"] } + +# Database +sea-orm = { version = "1.1", features = [ + "sqlx-postgres", "runtime-tokio-rustls", "macros", "with-uuid", "with-chrono", "with-json" +] } +sea-orm-migration = { version = "1.1", features = ["sqlx-postgres", "runtime-tokio-rustls"] } +sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "uuid"] } + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# UUID & Time +uuid = { version = "1", features = ["v7", "serde"] } +chrono = { version = "0.4", features = ["serde"] } + +# Error handling +thiserror = "2" +anyhow = "1" + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } + +# Config +config = "0.14" + +# Redis +redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] } + +# JWT +jsonwebtoken = "9" + +# Password hashing +argon2 = "0.5" + +# Cryptographic hashing (token storage) +sha2 = "0.10" + +# API docs +utoipa = { version = "5", features = ["axum_extras", "uuid", "chrono"] } +# utoipa-swagger-ui 需要下载 GitHub 资源,网络受限时暂不使用 +# utoipa-swagger-ui = { version = "8", features = ["axum"] } + +# Validation +validator = { version = "0.19", features = ["derive"] } + +# Async trait +async-trait = "0.1" + +# HTTP client +reqwest = { version = "0.12", features = ["json", "stream"] } + +# Crypto +aes = "0.8" +cbc = "0.1" +hex = "0.4" +regex-lite = "0.1" + +# CSV and Excel export +csv = "1" +rust_xlsxwriter = "0.82" + +# Internal crates +erp-core = { path = "crates/erp-core" } +erp-auth = { path = "crates/erp-auth" } +erp-workflow = { path = "crates/erp-workflow" } +erp-message = { path = "crates/erp-message" } +erp-config = { path = "crates/erp-config" } +erp-plugin = { path = "crates/erp-plugin" } + +# Async streaming +futures = "0.3" +tokio-stream = "0.1" +async-stream = "0.3" +dashmap = "6" + +# Template engine +handlebars = "6" + +# HTML sanitization +ammonia = "4" + +# Document parsing +pdf-extract = "0.7" + +# Metrics +metrics = "0.24" +metrics-exporter-prometheus = "0.16" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b22a9da --- /dev/null +++ b/Dockerfile @@ -0,0 +1,113 @@ +# ============================== +# Stage 1: Build Rust backend +# ============================== +FROM rust:1-bookworm AS rust-builder + +WORKDIR /app + +# 先复制依赖文件以利用 Docker 缓存 +COPY Cargo.toml Cargo.lock ./ +COPY crates/erp-core/Cargo.toml crates/erp-core/Cargo.toml +COPY crates/erp-auth/Cargo.toml crates/erp-auth/Cargo.toml +COPY crates/erp-config/Cargo.toml crates/erp-config/Cargo.toml +COPY crates/erp-workflow/Cargo.toml crates/erp-workflow/Cargo.toml +COPY crates/erp-message/Cargo.toml crates/erp-message/Cargo.toml +COPY crates/erp-plugin/Cargo.toml crates/erp-plugin/Cargo.toml +COPY crates/erp-health/Cargo.toml crates/erp-health/Cargo.toml +COPY crates/erp-ai/Cargo.toml crates/erp-ai/Cargo.toml +COPY crates/erp-dialysis/Cargo.toml crates/erp-dialysis/Cargo.toml +COPY crates/erp-server/Cargo.toml crates/erp-server/Cargo.toml +COPY crates/erp-server/migration/Cargo.toml crates/erp-server/migration/Cargo.toml +COPY crates/erp-plugin-prototype/Cargo.toml crates/erp-plugin-prototype/Cargo.toml +COPY crates/erp-plugin-test-sample/Cargo.toml crates/erp-plugin-test-sample/Cargo.toml +COPY crates/erp-plugin-assessment/Cargo.toml crates/erp-plugin-assessment/Cargo.toml +COPY crates/erp-plugin-crm/Cargo.toml crates/erp-plugin-crm/Cargo.toml +COPY crates/erp-plugin-freelance/Cargo.toml crates/erp-plugin-freelance/Cargo.toml +COPY crates/erp-plugin-inventory/Cargo.toml crates/erp-plugin-inventory/Cargo.toml +COPY crates/erp-plugin-itops/Cargo.toml crates/erp-plugin-itops/Cargo.toml + +# 创建空的 lib.rs/main.rs 占位以缓存依赖 +RUN mkdir -p crates/erp-core/src && echo "" > crates/erp-core/src/lib.rs \ + && mkdir -p crates/erp-auth/src && echo "" > crates/erp-auth/src/lib.rs \ + && mkdir -p crates/erp-config/src && echo "" > crates/erp-config/src/lib.rs \ + && mkdir -p crates/erp-workflow/src && echo "" > crates/erp-workflow/src/lib.rs \ + && mkdir -p crates/erp-message/src && echo "" > crates/erp-message/src/lib.rs \ + && mkdir -p crates/erp-plugin/src && echo "" > crates/erp-plugin/src/lib.rs \ + && mkdir -p crates/erp-health/src && echo "" > crates/erp-health/src/lib.rs \ + && mkdir -p crates/erp-ai/src && echo "" > crates/erp-ai/src/lib.rs \ + && mkdir -p crates/erp-dialysis/src && echo "" > crates/erp-dialysis/src/lib.rs \ + && mkdir -p crates/erp-server/src && echo "fn main(){}" > crates/erp-server/src/main.rs \ + && mkdir -p crates/erp-server/migration/src && echo "" > crates/erp-server/migration/src/lib.rs \ + && for crate in erp-plugin-prototype erp-plugin-test-sample erp-plugin-assessment erp-plugin-crm erp-plugin-freelance erp-plugin-inventory erp-plugin-itops; do \ + mkdir -p crates/$crate/src && echo "" > crates/$crate/src/lib.rs; \ + done + +# 构建依赖(仅当 Cargo.toml/Cargo.lock 变化时重新编译) +RUN cargo build --release -p erp-server 2>/dev/null || true + +# 复制实际源码 +COPY crates/ crates/ + +# 重新构建(增量编译,只编译业务代码) +RUN cargo build --release -p erp-server + +# ============================== +# Stage 2: Build frontend +# ============================== +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@latest --activate + +COPY apps/web/package.json apps/web/pnpm-lock.yaml ./apps/web/ + +RUN cd apps/web && pnpm install --frozen-lockfile + +COPY apps/web/ ./apps/web/ + +RUN cd apps/web && pnpm build + +# ============================== +# Stage 3: Production runtime +# ============================== +FROM debian:bookworm-slim AS runtime + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# 复制 Rust 二进制 +COPY --from=rust-builder /app/target/release/erp-server /app/erp-server + +# 复制配置文件 +COPY config/ /app/config/ + +# 复制前端构建产物(可通过 volume 暴露给 OpenResty) +COPY --from=frontend-builder /app/apps/web/dist/ /app/static/ + +# 创建上传目录 +RUN mkdir -p /app/uploads + +# 非特权用户运行 +RUN useradd -r -s /bin/false appuser \ + && chown -R appuser:appuser /app +USER appuser + +# 环境变量(运行时通过 docker-compose / .env 覆盖) +ENV ERP__SERVER__HOST=0.0.0.0 +ENV ERP__SERVER__PORT=3000 +ENV ERP__SERVER__METRICS_PORT=9090 +ENV ERP__STORAGE__UPLOAD_DIR=/app/uploads + +EXPOSE 3000 9090 + +VOLUME ["/app/uploads", "/app/static"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:3000/api/v1/health || exit 1 + +ENTRYPOINT ["/app/erp-server"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..c37b6df --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# ERP Platform Base + +模块化商业 SaaS ERP 平台底座。 + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端 | Rust (Axum 0.8 + SeaORM + Tokio) | +| 数据库 | PostgreSQL 16+ | +| 缓存 | Redis 7+ | +| 前端 | Vite + React 18 + TypeScript + Ant Design 5 | + +## 项目结构 + +``` +erp/ +├── crates/ +│ ├── erp-core/ # 基础类型、错误、事件总线、模块 trait +│ ├── erp-common/ # 共享工具 +│ ├── erp-auth/ # 身份与权限 (Phase 2) +│ ├── erp-workflow/ # 工作流引擎 (Phase 4) +│ ├── erp-message/ # 消息中心 (Phase 5) +│ ├── erp-config/ # 系统配置 (Phase 3) +│ └── erp-server/ # Axum 服务入口 +│ └── migration/ # SeaORM 数据库迁移 +├── apps/web/ # React SPA 前端 +├── docker/ # Docker 开发环境 +└── docs/ # 文档 +``` + +## 快速开始 + +### 1. 启动基础设施 + +```bash +cd docker && docker compose up -d +``` + +### 2. 启动后端 + +```bash +cargo run -p erp-server +``` + +### 3. 启动前端 + +```bash +cd apps/web && pnpm install && pnpm dev +``` + +### 4. 访问 + +- 前端: http://localhost:5173 +- 后端 API: http://localhost:3000 + +## 开发命令 + +```bash +cargo check # 编译检查 +cargo test --workspace # 运行测试 +cargo run -p erp-server # 启动后端 +cd apps/web && pnpm dev # 启动前端 +``` diff --git a/apps/web/e2e/auth.fixture.ts b/apps/web/e2e/auth.fixture.ts new file mode 100644 index 0000000..79673cc --- /dev/null +++ b/apps/web/e2e/auth.fixture.ts @@ -0,0 +1,43 @@ +import { test as base } from '@playwright/test'; + +const API_BASE = 'http://localhost:3000/api/v1'; + +let loginPromise: Promise<{ token: string; user: unknown }> | null = null; + +function login(): Promise<{ token: string; user: unknown }> { + if (!loginPromise) { + loginPromise = (async () => { + for (let attempt = 0; attempt < 3; attempt++) { + try { + const res = await fetch(`${API_BASE}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'Admin@2026' }), + }); + const json = await res.json(); + if (json.success) { + return { token: json.data.access_token, user: json.data.user }; + } + } catch {} + // Wait before retry on collision + await new Promise((r) => setTimeout(r, 500 * (attempt + 1))); + } + throw new Error('Login failed after 3 attempts'); + })(); + } + return loginPromise; +} + +export const test = base.extend({ + page: async ({ page }, use) => { + const { token, user } = await login(); + await page.addInitScript((args) => { + localStorage.setItem('access_token', args.token); + localStorage.setItem('refresh_token', args.token); + localStorage.setItem('user', JSON.stringify(args.user)); + }, { token, user }); + await use(page); + }, +}); + +export { expect } from '@playwright/test'; diff --git a/apps/web/e2e/check-readiness.ts b/apps/web/e2e/check-readiness.ts new file mode 100644 index 0000000..c0ca8a2 --- /dev/null +++ b/apps/web/e2e/check-readiness.ts @@ -0,0 +1,22 @@ +// apps/web/e2e/check-readiness.ts +import type { FullConfig } from '@playwright/test'; + +async function check(url: string, label: string): Promise { + for (let i = 0; i < 5; i++) { + try { + const res = await fetch(url); + if (res.ok) return; + } catch { /* retry */ } + console.log(`⏳ ${label} 未就绪,等待重试 (${i + 1}/5)...`); + await new Promise((r) => setTimeout(r, 2000)); + } + throw new Error(`❌ ${label} 未就绪: ${url}。请确认后端服务已启动 (cd crates/erp-server && cargo run)`); +} + +export default async function globalSetup(_config: FullConfig) { + const apiBase = process.env.E2E_API_URL || 'http://localhost:3000'; + const webBase = process.env.E2E_BASE_URL || 'http://localhost:5174'; + await check(`${apiBase}/api/v1/health`, '后端 API'); + await check(webBase, '前端 SPA'); + console.log('✅ E2E 环境就绪'); +} diff --git a/apps/web/e2e/fixtures/api-client.ts b/apps/web/e2e/fixtures/api-client.ts new file mode 100644 index 0000000..761befa --- /dev/null +++ b/apps/web/e2e/fixtures/api-client.ts @@ -0,0 +1,200 @@ +// apps/web/e2e/fixtures/api-client.ts + +import type { + PatientData, DoctorData, VitalSignsData, ScheduleData, + AppointmentData, FollowUpTemplateData, FollowUpTaskData, AlertRuleData, +} from './test-data'; + +const API_BASE = process.env.E2E_API_URL || 'http://localhost:3000/api/v1'; + +interface ApiResponse { success: boolean; data: T } +interface Versioned { id: string; version: number } +type VEntity = T & Versioned; + +interface LoginResponse { + access_token: string; + refresh_token: string; + expires_in: number; + user: { id: string; username: string; display_name: string; roles: string[] }; +} + +export class ApiClient { + private token = ''; + + async login(username?: string, password?: string): Promise { + const res = await this.rawPost<{ success: boolean; data: LoginResponse }>( + '/auth/login', + { + username: username || process.env.E2E_ADMIN_USER || 'admin', + password: password || process.env.E2E_ADMIN_PASS || 'Admin@2026', + }, + ); + this.token = res.data.access_token; + return res.data; + } + + async loginAsAdmin(): Promise { + return this.login(); + } + + getToken(): string { return this.token; } + + async createPatient(overrides?: Partial): Promise>> { + return this.post('/health/patients', overrides ?? {}); + } + + async updatePatient(id: string, version: number, data: Partial): Promise>> { + return this.put(`/health/patients/${id}`, { ...data, version }); + } + + async deletePatient(id: string, version: number): Promise { + await this.del(`/health/patients/${id}`, { version }); + } + + async createDoctor(overrides?: Partial): Promise>> { + return this.post('/health/doctors', overrides ?? {}); + } + + async deleteDoctor(id: string, version: number): Promise { + await this.del(`/health/doctors/${id}`, { version }); + } + + async createVitalSigns(patientId: string, overrides?: Partial): Promise>> { + return this.post(`/health/patients/${patientId}/vital-signs`, overrides ?? {}); + } + + async deleteVitalSigns(patientId: string, id: string, version: number): Promise { + await this.del(`/health/patients/${patientId}/vital-signs/${id}`, { version }); + } + + async createSchedule(overrides: ScheduleData): Promise>> { + return this.post('/health/doctor-schedules', overrides); + } + + async deleteSchedule(id: string, version: number): Promise { + await this.del(`/health/doctor-schedules/${id}`, { version }); + } + + async createAppointment(overrides: AppointmentData): Promise>> { + return this.post('/health/appointments', overrides); + } + + async updateAppointmentStatus(id: string, version: number, status: string): Promise>> { + return this.put(`/health/appointments/${id}/status`, { status, version }); + } + + async deleteAppointment(id: string, version: number): Promise { + await this.del(`/health/appointments/${id}`, { version }); + } + + async createFollowUpTemplate(overrides?: Partial): Promise>> { + return this.post('/health/follow-up-templates', overrides ?? {}); + } + + async deleteFollowUpTemplate(id: string, version: number): Promise { + await this.del(`/health/follow-up-templates/${id}`, { version }); + } + + async createFollowUpTask(overrides: FollowUpTaskData): Promise>> { + return this.post('/health/follow-up-tasks', overrides); + } + + async deleteFollowUpTask(id: string, version: number): Promise { + await this.del(`/health/follow-up-tasks/${id}`, { version }); + } + + async createAlertRule(overrides?: Partial): Promise>> { + return this.post('/health/alert-rules', overrides ?? {}); + } + + async deleteAlertRule(id: string, version: number): Promise { + await this.del(`/health/alert-rules/${id}`, { version }); + } + + async listAlerts(): Promise>[]> { + const res = await this.get<{ data: VEntity>[] }>('/health/alerts'); + return res.data ?? []; + } + + async acknowledgeAlert(id: string, version: number): Promise>> { + return this.put(`/health/alerts/${id}/acknowledge`, { version }); + } + + async resolveAlert(id: string, version: number): Promise>> { + return this.put(`/health/alerts/${id}/resolve`, { version }); + } + + async dismissAlert(id: string, version: number): Promise>> { + return this.put(`/health/alerts/${id}/dismiss`, { version }); + } + + private async headers(): Promise> { + return { + 'Content-Type': 'application/json', + ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), + }; + } + + private async parseJson(res: Response, method: string, path: string): Promise { + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`${method} ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); + } + const json = await res.json(); + if (!json.success) throw new Error(`${method} ${path} failed: ${json.error ?? 'unknown'}`); + return json.data as T; + } + + private async get(path: string): Promise { + const res = await fetch(`${API_BASE}${path}`, { headers: await this.headers() }); + return this.parseJson(res, 'GET', path); + } + + private async post(path: string, body: unknown): Promise { + const res = await fetch(`${API_BASE}${path}`, { + method: 'POST', + headers: await this.headers(), + body: JSON.stringify(body), + }); + return this.parseJson(res, 'POST', path); + } + + private async put(path: string, body: unknown): Promise { + const res = await fetch(`${API_BASE}${path}`, { + method: 'PUT', + headers: await this.headers(), + body: JSON.stringify(body), + }); + return this.parseJson(res, 'PUT', path); + } + + private async del(path: string, body?: unknown): Promise { + const res = await fetch(`${API_BASE}${path}`, { + method: 'DELETE', + headers: await this.headers(), + body: body ? JSON.stringify(body) : undefined, + }); + if (res.status === 204) return; + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`DELETE ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); + } + const json = await res.json(); + if (!json.success) throw new Error(`DELETE ${path} failed: ${json.error ?? 'unknown'}`); + } + + private async rawPost(path: string, body: unknown): Promise { + const res = await fetch(`${API_BASE}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`POST ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); + } + const json = await res.json(); + if (!json.success) throw new Error(`POST ${path} failed: ${json.error ?? 'unknown'}`); + return json as T; + } +} diff --git a/apps/web/e2e/fixtures/auth.fixture.ts b/apps/web/e2e/fixtures/auth.fixture.ts new file mode 100644 index 0000000..1966416 --- /dev/null +++ b/apps/web/e2e/fixtures/auth.fixture.ts @@ -0,0 +1,73 @@ +// apps/web/e2e/fixtures/auth.fixture.ts +import { test as base, type Page } from '@playwright/test'; +import { ApiClient } from './api-client'; + +const API_BASE = process.env.E2E_API_URL || 'http://localhost:3000/api/v1'; + +type E2eFixtures = { + api: ApiClient; + authenticatedPage: Page; +}; + +interface LoginResult { + access_token: string; + refresh_token: string; + user: object; +} + +async function login(): Promise { + for (let attempt = 0; attempt < 5; attempt++) { + try { + const res = await fetch(`${API_BASE}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: process.env.E2E_ADMIN_USER || 'admin', + password: process.env.E2E_ADMIN_PASS || 'Admin@2026', + }), + }); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`HTTP ${res.status}: ${text.slice(0, 100)}`); + } + const json = await res.json(); + if (json.success) return json.data; + throw new Error(`Login unsuccessful: ${json.error ?? 'unknown'}`); + } catch (err) { + if (attempt === 4) throw err; + await new Promise((r) => setTimeout(r, 1000 * (attempt + 1))); + } + } + throw new Error('Login failed after 5 attempts'); +} + +export const test = base.extend({ + api: async ({}, use) => { + const { access_token } = await login(); + const client = new ApiClient(); + client['token'] = access_token; + await use(client); + }, + + authenticatedPage: async ({ page }, use) => { + const { access_token, refresh_token, user } = await login(); + await page.addInitScript((args) => { + localStorage.setItem('access_token', args.token); + localStorage.setItem('refresh_token', args.refresh); + localStorage.setItem('user', JSON.stringify(args.userData)); + }, { token: access_token, refresh: refresh_token, userData: user }); + await use(page); + }, + + page: async ({ page }, use) => { + const { access_token, refresh_token, user } = await login(); + await page.addInitScript((args) => { + localStorage.setItem('access_token', args.token); + localStorage.setItem('refresh_token', args.refresh); + localStorage.setItem('user', JSON.stringify(args.userData)); + }, { token: access_token, refresh: refresh_token, userData: user }); + await use(page); + }, +}); + +export { expect } from '@playwright/test'; diff --git a/apps/web/e2e/fixtures/test-data.ts b/apps/web/e2e/fixtures/test-data.ts new file mode 100644 index 0000000..fad0643 --- /dev/null +++ b/apps/web/e2e/fixtures/test-data.ts @@ -0,0 +1,198 @@ +// apps/web/e2e/fixtures/test-data.ts + +export interface PatientData { + name: string; + gender?: string; + birth_date?: string; + blood_type?: string; + id_number?: string; + allergy_history?: string; + medical_history_summary?: string; + emergency_contact_name?: string; + emergency_contact_phone?: string; + source?: string; + notes?: string; +} + +export interface DoctorData { + name: string; + department?: string; + title?: string; + specialty?: string; + phone?: string; + license_number?: string; + status?: string; +} + +export interface VitalSignsData { + record_date: string; + systolic_bp_morning?: number; + diastolic_bp_morning?: number; + heart_rate?: number; + body_temperature?: number; + spo2?: number; + blood_sugar?: number; + weight?: number; + water_intake_ml?: number; + urine_output_ml?: number; + notes?: string; + source?: string; +} + +export interface ScheduleData { + doctor_id: string; + schedule_date: string; + start_time: string; + end_time: string; + max_appointments?: number; + period_type?: string; +} + +export interface AppointmentData { + patient_id: string; + doctor_id: string; + schedule_id: string; + appointment_date: string; + start_time: string; + end_time: string; + reason?: string; +} + +export interface FollowUpTemplateData { + name: string; + description?: string; + follow_up_type: string; + applicable_scope?: string; + fields?: Array<{ + label: string; + field_key: string; + field_type: string; + required?: boolean; + options?: string; + }>; +} + +export interface FollowUpTaskData { + patient_id: string; + follow_up_type: string; + planned_date: string; + assigned_to?: string; + content_template?: string; +} + +export interface AlertRuleData { + name: string; + device_type: string; + condition_type: string; + condition_params: Record; + severity?: string; + description?: string; + apply_tags?: Record; + notify_roles?: Array; + cooldown_minutes?: number; +} + +let counter = 0; + +function uid(): string { + counter += 1; + return `${Date.now()}_${counter}_${Math.random().toString(36).slice(2, 6)}`; +} + +export function makePatient(overrides?: Partial): PatientData { + const id = uid(); + return { + name: `E2E患者_${id}`, + gender: 'male', + birth_date: '1990-01-15', + id_number: `110101199001${String(Math.random()).slice(2, 8)}`, + ...overrides, + }; +} + +export function makeDoctor(overrides?: Partial): DoctorData { + const id = uid(); + return { + name: `E2E医生_${id}`, + department: '内科', + title: '主治医师', + specialty: '全科', + license_number: `DOC${id}`, + ...overrides, + }; +} + +export function makeVitalSigns(overrides?: Partial): VitalSignsData { + return { + record_date: new Date().toISOString().slice(0, 10), + systolic_bp_morning: 120, + diastolic_bp_morning: 80, + heart_rate: 72, + body_temperature: 36.5, + spo2: 98, + source: 'web_e2e', + ...overrides, + }; +} + +export function makeSchedule(doctorId: string, overrides?: Partial): ScheduleData { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + const date = tomorrow.toISOString().slice(0, 10); + return { + doctor_id: doctorId, + schedule_date: date, + start_time: '09:00', + end_time: '12:00', + max_appointments: 10, + ...overrides, + }; +} + +export function makeAppointment(patientId: string, doctorId: string, scheduleId: string, overrides?: Partial): AppointmentData { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + const date = tomorrow.toISOString().slice(0, 10); + return { + patient_id: patientId, + doctor_id: doctorId, + schedule_id: scheduleId, + appointment_date: date, + start_time: '09:00', + end_time: '10:00', + reason: 'E2E测试预约', + ...overrides, + }; +} + +export function makeFollowUpTemplate(overrides?: Partial): FollowUpTemplateData { + return { + name: `E2E随访模板_${uid()}`, + description: 'E2E自动创建的随访模板', + follow_up_type: 'phone', + ...overrides, + }; +} + +export function makeFollowUpTask(patientId: string, _templateId: string, overrides?: Partial): FollowUpTaskData { + const plannedDate = new Date(); + plannedDate.setDate(plannedDate.getDate() + 7); + return { + patient_id: patientId, + follow_up_type: 'phone', + planned_date: plannedDate.toISOString().slice(0, 10), + ...overrides, + }; +} + +export function makeAlertRule(overrides?: Partial): AlertRuleData { + return { + name: `E2E告警规则_${uid()}`, + device_type: 'heart_rate', + condition_type: 'single_threshold', + condition_params: { direction: 'above', value: 50 }, + severity: 'warning', + description: 'E2E测试低阈值规则,用于触发告警', + ...overrides, + }; +} diff --git a/apps/web/e2e/login.spec.ts b/apps/web/e2e/login.spec.ts new file mode 100644 index 0000000..88e24f8 --- /dev/null +++ b/apps/web/e2e/login.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test'; + +test.describe('登录流程', () => { + test('显示登录页面', async ({ page }) => { + await page.goto('/#/login'); + await expect(page.locator('.ant-card, .ant-form')).toBeVisible(); + }); + + test('空表单提交显示验证错误', async ({ page }) => { + await page.goto('/#/login'); + await page.click('button[type="submit"]'); + // Ant Design 应显示验证错误 + await expect(page.locator('.ant-form-item-explain-error')).toHaveCount(2); // 用户名 + 密码 + }); +}); diff --git a/apps/web/e2e/pages/appointment.page.ts b/apps/web/e2e/pages/appointment.page.ts new file mode 100644 index 0000000..c65ce0d --- /dev/null +++ b/apps/web/e2e/pages/appointment.page.ts @@ -0,0 +1,56 @@ +// apps/web/e2e/pages/appointment.page.ts +import type { Page } from '@playwright/test'; + +export class AppointmentPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async gotoSchedule() { + await this.page.goto('/#/health/schedules'); + await this.page.waitForSelector('.ant-table, .ant-fullcalendar, [class*="calendar"]', { timeout: 10000 }); + } + + async gotoAppointments() { + await this.page.goto('/#/health/appointments'); + await this.page.waitForSelector('.ant-table', { timeout: 10000 }); + } + + async clickCreateSchedule() { + await this.page.click('button:has-text("新增排班"), button:has-text("创建")'); + await this.page.waitForSelector('.ant-modal, .ant-drawer', { timeout: 5000 }); + } + + async fillScheduleForm(data: { doctor_id?: string; date: string; start_time: string; end_time: string }) { + if (data.doctor_id) { + await this.page.click('.ant-select'); + await this.page.click('.ant-select-item-option'); + } + await this.page.fill('input[placeholder*="日期"]', data.date); + await this.page.fill('input[placeholder*="开始"]', data.start_time); + await this.page.fill('input[placeholder*="结束"]', data.end_time); + } + + async submitScheduleForm() { + await this.page.click('.ant-modal button[type="submit"], .ant-btn-primary'); + await this.page.waitForSelector('.ant-message-success', { timeout: 10000 }); + } + + async clickCreateAppointment() { + await this.page.click('button:has-text("新增预约"), button:has-text("创建")'); + await this.page.waitForSelector('.ant-modal, .ant-drawer', { timeout: 5000 }); + } + + async fillAppointmentForm(data: { patient_id: string; doctor_id: string; date: string; reason?: string }) { + if (data.reason) { + await this.page.fill('textarea, input[placeholder*="原因"]', data.reason); + } + } + + async submitAppointmentForm() { + await this.page.click('.ant-modal button[type="submit"], .ant-btn-primary'); + await this.page.waitForSelector('.ant-message-success', { timeout: 10000 }); + } +} diff --git a/apps/web/e2e/pages/index.ts b/apps/web/e2e/pages/index.ts new file mode 100644 index 0000000..9f3abe0 --- /dev/null +++ b/apps/web/e2e/pages/index.ts @@ -0,0 +1,6 @@ +// apps/web/e2e/pages/index.ts +export { LoginPage } from './login.page'; +export { PatientListPage } from './patient-list.page'; +export { PatientDetailPage } from './patient-detail.page'; +export { HealthDataPage } from './health-data.page'; +export { AppointmentPage } from './appointment.page'; diff --git a/apps/web/e2e/pages/login.page.ts b/apps/web/e2e/pages/login.page.ts new file mode 100644 index 0000000..7138ac8 --- /dev/null +++ b/apps/web/e2e/pages/login.page.ts @@ -0,0 +1,48 @@ +// apps/web/e2e/pages/login.page.ts +import type { Page } from '@playwright/test'; + +export class LoginPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await this.page.goto('/#/login'); + await this.page.waitForSelector('.ant-card, .ant-form'); + } + + async fillUsername(username: string) { + await this.page.fill('input[id="username"], input[placeholder*="用户名"]', username); + } + + async fillPassword(password: string) { + await this.page.fill('input[type="password"]', password); + } + + async clickSubmit() { + await this.page.click('button[type="submit"]'); + } + + async login(username: string, password: string) { + await this.goto(); + await this.fillUsername(username); + await this.fillPassword(password); + await this.clickSubmit(); + } + + async getErrorMessage(): Promise { + const el = this.page.locator('.ant-form-item-explain-error, .ant-message-error, .ant-alert-error'); + return el.first().textContent() ?? ''; + } + + async isLoggedIn(): Promise { + try { + await this.page.waitForURL('**/#/', { timeout: 5000 }); + return true; + } catch { + return false; + } + } +} diff --git a/apps/web/e2e/pages/patient-detail.page.ts b/apps/web/e2e/pages/patient-detail.page.ts new file mode 100644 index 0000000..d299a88 --- /dev/null +++ b/apps/web/e2e/pages/patient-detail.page.ts @@ -0,0 +1,44 @@ +// apps/web/e2e/pages/patient-detail.page.ts +import type { Page } from '@playwright/test'; + +export class PatientDetailPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(id: string) { + await this.page.goto(`/#/health/patients/${id}`); + await this.page.waitForSelector('.ant-descriptions, .ant-tabs', { timeout: 10000 }); + } + + async getPatientName(): Promise { + const el = this.page.locator('div[style*="font-weight"]').first(); + return el.textContent() ?? ''; + } + + async clickTab(tabName: string) { + await this.page.click(`.ant-tabs-tab:has-text("${tabName}")`); + await this.page.waitForTimeout(500); + } + + async getVitalSignsCount(): Promise { + return this.page.locator('.ant-table-tbody tr').count(); + } + + async clickAssignDoctor() { + await this.page.click('button:has-text("分配医生")'); + await this.page.waitForSelector('.ant-modal, .ant-drawer', { timeout: 5000 }); + } + + async selectDoctor(doctorName: string) { + await this.page.click('.ant-select'); + await this.page.click(`.ant-select-item-option:has-text("${doctorName}")`); + } + + async confirmAssign() { + await this.page.click('.ant-modal button[type="submit"], .ant-btn-primary'); + await this.page.waitForSelector('.ant-message-success', { timeout: 5000 }); + } +} diff --git a/apps/web/e2e/pages/patient-list.page.ts b/apps/web/e2e/pages/patient-list.page.ts new file mode 100644 index 0000000..2ec990c --- /dev/null +++ b/apps/web/e2e/pages/patient-list.page.ts @@ -0,0 +1,66 @@ +// apps/web/e2e/pages/patient-list.page.ts +import type { Page } from '@playwright/test'; + +export class PatientListPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await this.page.goto('/#/health/patients'); + await this.page.waitForSelector('.ant-table', { timeout: 15000 }); + } + + async clickCreate() { + await this.page.click('button:has-text("新增"), button:has-text("新建"), button:has-text("创建")'); + await this.page.waitForSelector('.ant-modal, .ant-drawer', { timeout: 5000 }); + } + + async fillCreateForm(data: { name: string; gender?: string; birth_date?: string }) { + const drawer = this.page.locator('.ant-drawer'); + await drawer.locator('input').first().waitFor({ state: 'visible' }); + await this.page.locator('.ant-drawer [name="name"] input, .ant-drawer input').first().fill(data.name); + if (data.gender) { + await drawer.locator('.ant-select').first().click(); + await this.page.locator(`.ant-select-item-option:has-text("${data.gender === 'male' ? '男' : '女'}")`).first().click(); + } + if (data.birth_date) { + await drawer.locator('[name="birth_date"] input, input[placeholder*="出生"]').fill(data.birth_date); + } + } + + async submitForm() { + await this.page.click('.ant-drawer button.ant-btn-primary, button:has-text("保存"), .ant-modal .ant-btn-primary'); + await this.page.waitForSelector('.ant-message-success', { timeout: 10000 }); + } + + async searchPatient(name: string) { + const searchInput = this.page.locator('input[placeholder*="搜索"]').first(); + await searchInput.fill(name); + await searchInput.press('Enter'); + await this.page.waitForTimeout(1000); + } + + async clickPatientRow(row: number) { + const rows = this.page.locator('.ant-table-tbody tr'); + await rows.nth(row).click(); + } + + async clickPatientByName(name: string) { + await this.searchPatient(name); + const row = this.page.locator(`.ant-table-tbody tr:has-text("${name}")`).first(); + await row.click(); + } + + async getTableRowCount(): Promise { + return this.page.locator('.ant-table-tbody tr').count(); + } + + async hasPatientInTable(name: string): Promise { + await this.searchPatient(name); + const count = await this.page.locator(`.ant-table-tbody tr:has-text("${name}")`).count(); + return count > 0; + } +} diff --git a/apps/web/e2e/plugins.spec.ts b/apps/web/e2e/plugins.spec.ts new file mode 100644 index 0000000..59aae90 --- /dev/null +++ b/apps/web/e2e/plugins.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from './auth.fixture'; + +test.describe('插件管理', () => { + test('插件管理页面加载', async ({ page }) => { + await page.goto('/#/'); + // 侧边栏显示"扩展管理插件管理" + await page.locator('text=扩展管理').first().click(); + await page.waitForLoadState('networkidle'); + // 页面不崩溃 + await expect(page.locator('main')).toBeVisible(); + }); + + test('刷新按钮可点击', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=扩展管理').first().click(); + await page.waitForLoadState('networkidle'); + const refreshBtn = page.locator('button:has-text("刷新")'); + if (await refreshBtn.isVisible().catch(() => false)) { + await expect(refreshBtn).toBeEnabled(); + await refreshBtn.click(); + await expect(page.locator('main')).toBeVisible(); + } + }); +}); diff --git a/apps/web/e2e/smoke/login.spec.ts b/apps/web/e2e/smoke/login.spec.ts new file mode 100644 index 0000000..88e24f8 --- /dev/null +++ b/apps/web/e2e/smoke/login.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test'; + +test.describe('登录流程', () => { + test('显示登录页面', async ({ page }) => { + await page.goto('/#/login'); + await expect(page.locator('.ant-card, .ant-form')).toBeVisible(); + }); + + test('空表单提交显示验证错误', async ({ page }) => { + await page.goto('/#/login'); + await page.click('button[type="submit"]'); + // Ant Design 应显示验证错误 + await expect(page.locator('.ant-form-item-explain-error')).toHaveCount(2); // 用户名 + 密码 + }); +}); diff --git a/apps/web/e2e/smoke/plugins.spec.ts b/apps/web/e2e/smoke/plugins.spec.ts new file mode 100644 index 0000000..48cea8b --- /dev/null +++ b/apps/web/e2e/smoke/plugins.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from '../fixtures/auth.fixture'; + +test.describe('插件管理', () => { + test('插件管理页面加载', async ({ page }) => { + await page.goto('/#/'); + // 侧边栏显示"扩展管理插件管理" + await page.locator('text=扩展管理').first().click(); + await page.waitForLoadState('networkidle'); + // 页面不崩溃 + await expect(page.locator('main')).toBeVisible(); + }); + + test('刷新按钮可点击', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=扩展管理').first().click(); + await page.waitForLoadState('networkidle'); + const refreshBtn = page.locator('button:has-text("刷新")'); + if (await refreshBtn.isVisible().catch(() => false)) { + await expect(refreshBtn).toBeEnabled(); + await refreshBtn.click(); + await expect(page.locator('main')).toBeVisible(); + } + }); +}); diff --git a/apps/web/e2e/smoke/tenant-isolation.spec.ts b/apps/web/e2e/smoke/tenant-isolation.spec.ts new file mode 100644 index 0000000..7a87fb4 --- /dev/null +++ b/apps/web/e2e/smoke/tenant-isolation.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from '../fixtures/auth.fixture'; + +test.describe('多租户隔离', () => { + test('侧边栏按模块分组显示', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 验证侧边栏模块分组 + await expect(page.locator('text=基础模块').first()).toBeVisible({ timeout: 10000 }); + await expect(page.locator('text=业务模块').first()).toBeVisible(); + await expect(page.locator('text=系统').first()).toBeVisible(); + + // 验证关键菜单项 + await expect(page.locator('text=工作台').first()).toBeVisible(); + await expect(page.locator('text=用户管理').first()).toBeVisible(); + }); + + test('顶部导航栏显示用户信息', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 验证顶部导航栏显示管理员信息 + await expect(page.locator('text=系统管理员').first()).toBeVisible({ timeout: 10000 }); + }); + + test('页面间导航正常工作', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 点击侧边栏的用户管理(精确匹配侧边栏区域) + const sidebar = page.locator('complementary, [class*=sider], [class*=menu], nav').first(); + await sidebar.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 点击工作台返回 + await sidebar.locator('text=工作台').first().click(); + await expect(page).toHaveURL(/#\/$/, { timeout: 10000 }); + }); +}); diff --git a/apps/web/e2e/smoke/users.spec.ts b/apps/web/e2e/smoke/users.spec.ts new file mode 100644 index 0000000..eb2543f --- /dev/null +++ b/apps/web/e2e/smoke/users.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '../fixtures/auth.fixture'; + +test.describe('用户管理', () => { + test('用户列表页面加载并显示表格', async ({ page }) => { + await page.goto('/#/'); + // 通过侧边栏导航到用户管理 + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 标题 + await expect(page.locator('h4')).toContainText('用户管理'); + // 新建用户按钮 + await expect(page.locator('button:has-text("新建用户")')).toBeVisible(); + // 搜索框 + await expect(page.locator('input[placeholder*="搜索"]')).toBeVisible(); + // 表格列头 + await expect(page.locator('text=用户').first()).toBeVisible(); + await expect(page.locator('text=状态').first()).toBeVisible(); + }); + + test('新建用户弹窗表单验证', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 点击新建 + await page.click('button:has-text("新建用户")'); + // 弹窗出现 + await expect(page.locator('.ant-modal')).toBeVisible(); + // 直接提交应显示验证错误(点击 modal 内最后一个 button 即确认按钮) + const modalButtons = page.locator('.ant-modal .ant-modal-footer button'); + await modalButtons.last().click(); + // Ant Design 应显示验证错误(用户名 + 密码必填) + await expect(page.locator('.ant-form-item-explain-error')).toHaveCount(2); + // 关闭弹窗(点击第一个按钮即取消) + await modalButtons.first().click(); + }); + + test('搜索框可输入', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + const searchInput = page.locator('input[placeholder*="搜索"]'); + await searchInput.fill('admin'); + await expect(searchInput).toHaveValue('admin'); + }); +}); diff --git a/apps/web/e2e/tenant-isolation.spec.ts b/apps/web/e2e/tenant-isolation.spec.ts new file mode 100644 index 0000000..39f3dbe --- /dev/null +++ b/apps/web/e2e/tenant-isolation.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from './auth.fixture'; + +test.describe('多租户隔离', () => { + test('侧边栏按模块分组显示', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 验证侧边栏模块分组 + await expect(page.locator('text=基础模块').first()).toBeVisible({ timeout: 10000 }); + await expect(page.locator('text=业务模块').first()).toBeVisible(); + await expect(page.locator('text=系统').first()).toBeVisible(); + + // 验证关键菜单项 + await expect(page.locator('text=工作台').first()).toBeVisible(); + await expect(page.locator('text=用户管理').first()).toBeVisible(); + }); + + test('顶部导航栏显示用户信息', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 验证顶部导航栏显示管理员信息 + await expect(page.locator('text=系统管理员').first()).toBeVisible({ timeout: 10000 }); + }); + + test('页面间导航正常工作', async ({ page }) => { + await page.goto('/#/'); + await page.waitForLoadState('networkidle'); + + // 点击侧边栏的用户管理(精确匹配侧边栏区域) + const sidebar = page.locator('complementary, [class*=sider], [class*=menu], nav').first(); + await sidebar.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 点击工作台返回 + await sidebar.locator('text=工作台').first().click(); + await expect(page).toHaveURL(/#\/$/, { timeout: 10000 }); + }); +}); diff --git a/apps/web/e2e/users.spec.ts b/apps/web/e2e/users.spec.ts new file mode 100644 index 0000000..e5c0af5 --- /dev/null +++ b/apps/web/e2e/users.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from './auth.fixture'; + +test.describe('用户管理', () => { + test('用户列表页面加载并显示表格', async ({ page }) => { + await page.goto('/#/'); + // 通过侧边栏导航到用户管理 + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 标题 + await expect(page.locator('h4')).toContainText('用户管理'); + // 新建用户按钮 + await expect(page.locator('button:has-text("新建用户")')).toBeVisible(); + // 搜索框 + await expect(page.locator('input[placeholder*="搜索"]')).toBeVisible(); + // 表格列头 + await expect(page.locator('text=用户').first()).toBeVisible(); + await expect(page.locator('text=状态').first()).toBeVisible(); + }); + + test('新建用户弹窗表单验证', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + // 点击新建 + await page.click('button:has-text("新建用户")'); + // 弹窗出现 + await expect(page.locator('.ant-modal')).toBeVisible(); + // 直接提交应显示验证错误(点击 modal 内最后一个 button 即确认按钮) + const modalButtons = page.locator('.ant-modal .ant-modal-footer button'); + await modalButtons.last().click(); + // Ant Design 应显示验证错误(用户名 + 密码必填) + await expect(page.locator('.ant-form-item-explain-error')).toHaveCount(2); + // 关闭弹窗(点击第一个按钮即取消) + await modalButtons.first().click(); + }); + + test('搜索框可输入', async ({ page }) => { + await page.goto('/#/'); + await page.locator('text=用户管理').first().click(); + await expect(page).toHaveURL(/#\/users/, { timeout: 10000 }); + + const searchInput = page.locator('input[placeholder*="搜索"]'); + await searchInput.fill('admin'); + await expect(searchInput).toHaveValue('admin'); + }); +}); diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml new file mode 100644 index 0000000..b510873 --- /dev/null +++ b/apps/web/pnpm-lock.yaml @@ -0,0 +1,5980 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/charts': + specifier: ^2.6.7 + version: 2.6.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@ant-design/icons': + specifier: ^6.1.1 + version: 6.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + '@wangeditor/editor': + specifier: ^5.1.23 + version: 5.1.23 + '@wangeditor/editor-for-react': + specifier: ^1.0.6 + version: 1.0.6(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/editor@5.1.23)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@xyflow/react': + specifier: ^12.10.2 + version: 12.10.2(@types/react@19.2.14)(immer@9.0.21)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + antd: + specifier: ^6.3.5 + version: 6.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + axios: + specifier: ^1.15.0 + version: 1.15.0 + dayjs: + specifier: ^1.11.20 + version: 1.11.20 + dompurify: + specifier: ^3.4.5 + version: 3.4.5 + react: + specifier: ^19.2.4 + version: 19.2.5 + react-dom: + specifier: ^19.2.4 + version: 19.2.5(react@19.2.5) + react-router-dom: + specifier: ^7.14.0 + version: 7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + snabbdom: + specifier: ^3.6.3 + version: 3.6.3 + zustand: + specifier: ^5.0.12 + version: 5.0.12(@types/react@19.2.14)(immer@9.0.21)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) + devDependencies: + '@eslint/js': + specifier: ^9.39.4 + version: 9.39.4 + '@playwright/test': + specifier: ^1.52.0 + version: 1.59.1 + '@tailwindcss/vite': + specifier: ^4.2.2 + version: 4.2.2(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/dompurify': + specifier: ^3.2.0 + version: 3.2.0 + '@types/node': + specifier: ^24.12.2 + version: 24.12.2 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) + eslint: + specifier: ^9.39.4 + version: 9.39.4(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@9.39.4(jiti@2.6.1)) + globals: + specifier: ^17.4.0 + version: 17.4.0 + jsdom: + specifier: ^29.0.2 + version: 29.0.2 + msw: + specifier: ^2.13.6 + version: 2.13.6(@types/node@24.12.2)(typescript@6.0.2) + tailwindcss: + specifier: ^4.2.2 + version: 4.2.2 + typescript: + specifier: ~6.0.2 + version: 6.0.2 + typescript-eslint: + specifier: ^8.58.0 + version: 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + vite: + specifier: ^8.0.4 + version: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.0.2)(msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@ant-design/charts-util@0.0.1-alpha.7': + resolution: {integrity: sha512-Yh0o6EdO6SvdSnStFZMbnUzjyymkVzV+TQ9ymVW9hlVgO/fUkUII3JYSdV+UVcFnYwUF0YiDKuSTLCZNAzg2bQ==} + peerDependencies: + react: '>=16.8.4' + react-dom: '>=16.8.4' + + '@ant-design/charts-util@0.0.3': + resolution: {integrity: sha512-x1H7UT6t4dXAyGRoHqlOnEsEqBSTANFGTZEAMI0CWYhYUpp13n0o9grl9oPtoL6FEQMjUBTY+zGJKlHkz8smMw==} + peerDependencies: + react: '>=16.8.4' + react-dom: '>=16.8.4' + + '@ant-design/charts@2.6.7': + resolution: {integrity: sha512-XfmsnspUpfrMlRFGTwmHJ2TPKcosq5a5nSxAfIOpEXAvmJBT2N16oejGTZhUFTzba8W3XtBOziwRAXmDmLUqvA==} + peerDependencies: + react: '>=16.8.4' + react-dom: '>=16.8.4' + + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} + + '@ant-design/cssinjs-utils@2.1.2': + resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@ant-design/cssinjs@2.1.2': + resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} + engines: {node: '>=8.x'} + + '@ant-design/graphs@2.1.1': + resolution: {integrity: sha512-qT3Oo8BWeoAmZEy9gfR6uIk+rczbNJ3sWXKonoOD5koATWv7dY0kgvS1JnhdM1QW4FkfPPJTeQVSlRRUtvWDwA==} + peerDependencies: + react: '>=16.8.4' + react-dom: '>=16.8.4' + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@6.1.1': + resolution: {integrity: sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/plots@2.6.8': + resolution: {integrity: sha512-QsunUs2d5rbq/1BwVhga/siA5H50OaG23YopMYwPD4sPsza6NQzPQ8FM3elNIsD/BIk298tihqX1cJ/MmvVJbQ==} + peerDependencies: + react: '>=16.8.4' + react-dom: '>=16.8.4' + + '@ant-design/react-slick@2.0.0': + resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==} + peerDependencies: + react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@antv/algorithm@0.1.26': + resolution: {integrity: sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==} + + '@antv/component@2.1.11': + resolution: {integrity: sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==} + + '@antv/coord@0.4.7': + resolution: {integrity: sha512-UTbrMLhwJUkKzqJx5KFnSRpU3BqrdLORJbwUbHK2zHSCT3q3bjcFA//ZYLVfIlwqFDXp/hzfMyRtp0c77A9ZVA==} + + '@antv/event-emitter@0.1.3': + resolution: {integrity: sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==} + + '@antv/expr@1.0.2': + resolution: {integrity: sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==} + + '@antv/g-canvas@2.2.0': + resolution: {integrity: sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==} + + '@antv/g-lite@2.7.0': + resolution: {integrity: sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==} + + '@antv/g-math@3.1.0': + resolution: {integrity: sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==} + + '@antv/g-plugin-dragndrop@2.1.1': + resolution: {integrity: sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==} + + '@antv/g-svg@2.1.1': + resolution: {integrity: sha512-gVzBkjqA8FzDTbkuIxj6L0Omz/X/hFbYLzK6alWr0sHTfywqP6czcjDUJU8DF2MRIY1Twy55uZYW4dqqLXOXXg==} + + '@antv/g2-extension-plot@0.2.2': + resolution: {integrity: sha512-KJXCXO7as+h0hDqirGXf1omrNuYzQmY3VmBmp7lIvkepbQ7sz3pPwy895r1FWETGF3vTk5UeFcAF5yzzBHWgbw==} + + '@antv/g2@5.4.8': + resolution: {integrity: sha512-IvgIpwmT4M5/QAd3Mn2WiHIDeBqFJ4WA2gcZhRRSZuZ2KmgCqZWZwwIT0hc+kIGxwYeDoCQqf//t6FMVu3ryBg==} + + '@antv/g6-extension-react@0.2.7': + resolution: {integrity: sha512-X/zxGiL/kyJ+5xteX1+P2mI07oLw+zfvKcIHxfynL7IGCQCwQ6q91LkJaOlSDTuWhNRXwnwJ4Cf2Nt/9Dhq5Dg==} + peerDependencies: + '@antv/g6': ^5.1.0 + react: '>=16.8' + react-dom: '>=16.8' + + '@antv/g6@5.1.0': + resolution: {integrity: sha512-tvoBDKypL/zWEG99pgwGJLWr2CKA+6zVixYxaVzDOp0+TrPY2cxB1jevxFGPjbTOLBIMYt/vKCh1jnmDtfvtpg==} + + '@antv/g@6.3.1': + resolution: {integrity: sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==} + + '@antv/graphin@3.0.5': + resolution: {integrity: sha512-V/j8R8Ty44wUqxVIYLdpPuIO8WWCTIVq1eBJg5YRunL5t5o5qAFpC/qkQxslbBMWyKdIH0oWBnvHA74riGi7cw==} + peerDependencies: + react: ^18.0.0 || ^19.1.0 + react-dom: ^18.0.0 || ^19.1.0 + + '@antv/graphlib@2.0.4': + resolution: {integrity: sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==} + + '@antv/hierarchy@0.7.1': + resolution: {integrity: sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==} + + '@antv/layout@2.0.0': + resolution: {integrity: sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==} + + '@antv/scale@0.4.16': + resolution: {integrity: sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==} + + '@antv/scale@0.5.2': + resolution: {integrity: sha512-rTHRAwvpHWC5PGZF/mJ2ZuTDqwwvVBDRph0Uu5PV9BXwzV7K8+9lsqGJ+XHVLxe8c6bKog5nlzvV/dcYb0d5Ow==} + + '@antv/util@2.0.17': + resolution: {integrity: sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==} + + '@antv/util@3.3.11': + resolution: {integrity: sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==} + + '@antv/vendor@1.0.11': + resolution: {integrity: sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==} + + '@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@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@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.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + 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@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/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.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + 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.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + 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 + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + 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'} + + '@inquirer/ansi@2.0.5': + resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/confirm@6.0.12': + resolution: {integrity: sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.1.9': + resolution: {integrity: sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.5': + resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/type@4.0.5': + resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mswjs/interceptors@0.41.6': + resolution: {integrity: sha512-qmDvJIjcNsZ6tXWy2G9yuCgMPTTn35GMA3dPpSLm7QJVpbQzYdw0ALy1bKoivXnEM3U93/OrK+/M719b+fg84Q==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@1.1.3': + resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + + '@playwright/test@1.59.1': + resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + engines: {node: '>=18'} + hasBin: true + + '@rc-component/async-validator@5.1.0': + resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==} + engines: {node: '>=14.x'} + + '@rc-component/cascader@1.14.0': + resolution: {integrity: sha512-Ip9356xwZUR2nbW5PRVGif4B/bDve4pLa/N+PGbvBaTnjbvmN4PFMBGQSmlDlzKP1ovxaYMvwF/dI9lXNLT4iQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/checkbox@2.0.0': + resolution: {integrity: sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/collapse@1.2.0': + resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/color-picker@3.1.1': + resolution: {integrity: sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@2.0.1': + resolution: {integrity: sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/dialog@1.8.4': + resolution: {integrity: sha512-Ay6PM7phkTkquplG8fWfUGFZ2GTLx9diTl4f0d8Eqxd7W1u1KjE9AQooFQHOHnhZf0Ya3z51+5EKCWHmt/dNEw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/drawer@1.4.2': + resolution: {integrity: sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/dropdown@1.0.2': + resolution: {integrity: sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + '@rc-component/form@1.8.0': + resolution: {integrity: sha512-eUD5KKYnIZWmJwRA0vnyO/ovYUfHGU1svydY1OrqU5fw8Oz9Tdqvxvrlh0wl6xI/EW69dT7II49xpgOWzK3T5A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/image@1.8.1': + resolution: {integrity: sha512-JfPCijmMl+EaMvbftsEs/4VHmTyJKsZBh5ujFowSA45i9NTVYS1vuHtgpVV/QrGa27kXwbVOZriffCe/PNKuMw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input-number@1.6.2': + resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input@1.1.2': + resolution: {integrity: sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/mentions@1.6.0': + resolution: {integrity: sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/menu@1.2.0': + resolution: {integrity: sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.3': + resolution: {integrity: sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==} + engines: {node: '>=8.x'} + + '@rc-component/motion@1.3.2': + resolution: {integrity: sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mutate-observer@2.0.1': + resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/notification@1.2.0': + resolution: {integrity: sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/overflow@1.0.0': + resolution: {integrity: sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/pagination@1.2.0': + resolution: {integrity: sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/picker@1.9.1': + resolution: {integrity: sha512-9FBYYsvH3HMLICaPDA/1Th5FLaDkFa7qAtangIdlhKb3ZALaR745e9PsOhheJb6asS4QXc12ffiAcjdkZ4C5/g==} + engines: {node: '>=12.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + '@rc-component/portal@2.2.0': + resolution: {integrity: sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==} + engines: {node: '>=12.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/progress@1.0.2': + resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.1.1': + resolution: {integrity: sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/rate@1.0.1': + resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/resize-observer@1.1.2': + resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/segmented@1.3.0': + resolution: {integrity: sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/select@1.6.15': + resolution: {integrity: sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/slider@1.0.1': + resolution: {integrity: sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/steps@1.2.2': + resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/switch@1.0.3': + resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/table@1.9.1': + resolution: {integrity: sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tabs@1.7.0': + resolution: {integrity: sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/textarea@1.1.2': + resolution: {integrity: sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tooltip@1.4.0': + resolution: {integrity: sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tour@2.3.0': + resolution: {integrity: sha512-K04K9r32kUC+auBSQfr+Fss4SpSIS9JGe56oq/ALAX0p+i2ylYOI1MgR83yBY7v96eO6ZFXcM/igCQmubps0Ow==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tree-select@1.8.0': + resolution: {integrity: sha512-iYsPq3nuLYvGqdvFAW+l+I9ASRIOVbMXyA8FGZg2lGym/GwkaWeJGzI4eJ7c9IOEhRj0oyfIN4S92Fl3J05mjQ==} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/tree@1.2.4': + resolution: {integrity: sha512-5Gli43+m4R7NhpYYz3Z61I6LOw9yI6CNChxgVtvrO6xB1qML7iE6QMLVMB3+FTjo2yF6uFdAHtqWPECz/zbX5w==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/trigger@3.9.0': + resolution: {integrity: sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/upload@1.1.0': + resolution: {integrity: sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.10.1': + resolution: {integrity: sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/virtual-list@1.0.2': + resolution: {integrity: sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + 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 + + '@transloadit/prettier-bytes@0.0.7': + resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/dompurify@3.2.0': + resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} + deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed. + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/event-emitter@0.3.5': + resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@typescript-eslint/eslint-plugin@8.58.1': + resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.58.1': + resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + 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.58.1': + resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.58.1': + resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.1': + resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.58.1': + resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + 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.58.1': + resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.1': + resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.58.1': + resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + 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.58.1': + resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@uppy/companion-client@2.2.2': + resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==} + + '@uppy/core@2.3.4': + resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==} + + '@uppy/store-default@2.1.1': + resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==} + + '@uppy/utils@4.1.3': + resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==} + + '@uppy/xhr-upload@2.1.3': + resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==} + peerDependencies: + '@uppy/core': ^2.3.3 + + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + + '@wangeditor/basic-modules@1.1.7': + resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/code-highlight@1.0.3': + resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/core@1.1.19': + resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==} + peerDependencies: + '@uppy/core': ^2.1.1 + '@uppy/xhr-upload': ^2.0.3 + dom7: ^3.0.0 + is-hotkey: ^0.2.0 + lodash.camelcase: ^4.3.0 + lodash.clonedeep: ^4.5.0 + lodash.debounce: ^4.0.8 + lodash.foreach: ^4.5.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + lodash.toarray: ^4.4.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/editor-for-react@1.0.6': + resolution: {integrity: sha512-KJNSfgMr5Blzae3oyaiz20flMKHZHnvsz4bCYQKDCUs/qkvC+xNTnwedlCmhGP187oPWPEypCIYI8Zg6sz0psQ==} + peerDependencies: + '@wangeditor/core': '>=1.1.0' + '@wangeditor/editor': '>=5.1.0' + react: '>=17.0.2' + react-dom: '>=17.0.2' + + '@wangeditor/editor@5.1.23': + resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==} + + '@wangeditor/list-module@1.0.5': + resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/table-module@1.1.4': + resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/upload-image-module@1.0.2': + resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==} + peerDependencies: + '@uppy/core': ^2.0.3 + '@uppy/xhr-upload': ^2.0.3 + '@wangeditor/basic-modules': 1.x + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.foreach: ^4.5.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/video-module@1.1.4': + resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==} + peerDependencies: + '@uppy/core': ^2.1.4 + '@uppy/xhr-upload': ^2.0.7 + '@wangeditor/core': 1.x + dom7: ^3.0.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@xyflow/react@12.10.2': + resolution: {integrity: sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.76': + resolution: {integrity: sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + 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'} + + antd@6.3.5: + resolution: {integrity: sha512-8BPz9lpZWQm42PTx7yL4KxWAotVuqINiKcoYRcLtdd5BFmAcAZicVyFTnBJyRDlzGZFZeRW3foGu6jXYFnej6Q==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + 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'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} + + 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} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + baseline-browser-mapping@2.10.17: + resolution: {integrity: sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@1.1.13: + resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bubblesets-js@2.3.4: + resolution: {integrity: sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001787: + resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + 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'} + + 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==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + comlink@4.4.2: + resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + 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'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-binarytree@1.0.2: + resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force-3d@3.0.6: + resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo-projection@4.0.0: + resolution: {integrity: sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==} + engines: {node: '>=12'} + hasBin: true + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-octree@1.1.0: + resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-regression@1.3.10: + resolution: {integrity: sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + dagre@0.8.5: + resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + + 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==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dom7@3.0.0: + resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==} + + dompurify@3.4.5: + resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.334: + resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + 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@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + 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 + + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} + peerDependencies: + eslint: ^9 || ^10 + + 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.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + 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-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + flru@1.0.2: + resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} + engines: {node: '>=6'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + 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==} + + 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-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'} + + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphlib@2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + + 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==} + + html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + i18next@20.6.1: + resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-any-array@3.0.0: + resolution: {integrity: sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hotkey@0.2.0: + resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} + + is-mobile@5.0.0: + resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + 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-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@29.0.2: + resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} + engines: {node: ^20.19.0 || ^22.13.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-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.toarray@4.4.0: + resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + 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.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-match@1.0.2: + resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + ml-array-max@2.0.0: + resolution: {integrity: sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==} + + ml-array-min@2.0.0: + resolution: {integrity: sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==} + + ml-array-rescale@2.0.0: + resolution: {integrity: sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==} + + ml-matrix@6.12.2: + resolution: {integrity: sha512-GC+BnW+pBh8Auap8goAxY0senAmF0IEoc3HNVSfnfbvGw0buuDIYb9kAKMS1l+GiwJ1rfK2bzJ8IHhwjzATSFA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.13.6: + resolution: {integrity: sha512-GAJbQy8Ra/Ydjt0Hb2MGT2qhzd83J3+QZMHdH85uW7r/XkKc846+Ma2PLif5hGvTm5Yqa+wkcstpim0WeLZU9g==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + namespace-emitter@2.0.1: + resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + 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'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + 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-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pdfast@0.2.0: + resolution: {integrity: sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + 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 + + postcss@8.5.9: + resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + 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} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-router-dom@7.14.0: + resolution: {integrity: sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.14.0: + resolution: {integrity: sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + 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'} + + rettime@0.11.8: + resolution: {integrity: sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg==} + + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + + 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'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + slate-history@0.66.0: + resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==} + peerDependencies: + slate: '>=0.65.3' + + slate@0.72.8: + resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==} + + snabbdom@3.6.3: + resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==} + engines: {node: '>=12.17.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + ssr-window@3.0.0: + resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-components@6.4.0: + resolution: {integrity: sha512-BL1EDFpt+q10eAeZB0q9ps6pSlPejaBQWBkiuM16pyoVTG4NhZrPrZK0cqNbrozxSsYwUsJ9SQYN6NyeKJYX9A==} + engines: {node: '>= 16'} + peerDependencies: + css-to-react-native: '>= 3.2.0' + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + react-native: '>= 0.68.0' + peerDependenciesMeta: + css-to-react-native: + optional: true + react-dom: + optional: true + react-native: + optional: true + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svg-path-parser@1.1.0: + resolution: {integrity: sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} + + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} + hasBin: true + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@5.6.0: + resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} + engines: {node: '>=20'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + typescript-eslint@8.58.1: + resolution: {integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==} + 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@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + 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@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + 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@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 + + wildcard@1.1.2: + resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + + 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'} + + 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==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + 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@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zustand@5.0.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + 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 + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@ant-design/charts-util@0.0.1-alpha.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@ant-design/charts-util@0.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@ant-design/charts@2.6.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/graphs': 2.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@ant-design/plots': 2.6.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + transitivePeerDependencies: + - css-to-react-native + - react-native + + '@ant-design/colors@8.0.1': + dependencies: + '@ant-design/fast-color': 3.0.1 + + '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@babel/runtime': 7.29.2 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@ant-design/cssinjs@2.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + csstype: 3.2.3 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + stylis: 4.3.6 + + '@ant-design/fast-color@3.0.1': {} + + '@ant-design/graphs@2.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/charts-util': 0.0.1-alpha.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@antv/g6': 5.1.0 + '@antv/g6-extension-react': 0.2.7(@antv/g6@5.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@antv/graphin': 3.0.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + styled-components: 6.4.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + transitivePeerDependencies: + - css-to-react-native + - react-native + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@6.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/icons-svg': 4.4.2 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@ant-design/plots@2.6.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/charts-util': 0.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@antv/event-emitter': 0.1.3 + '@antv/g': 6.3.1 + '@antv/g2': 5.4.8 + '@antv/g2-extension-plot': 0.2.2 + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@ant-design/react-slick@2.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + clsx: 2.1.1 + json2mq: 0.2.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + throttle-debounce: 5.0.2 + + '@antv/algorithm@0.1.26': + dependencies: + '@antv/util': 2.0.17 + tslib: 2.8.1 + + '@antv/component@2.1.11': + dependencies: + '@antv/g': 6.3.1 + '@antv/scale': 0.4.16 + '@antv/util': 3.3.11 + svg-path-parser: 1.1.0 + + '@antv/coord@0.4.7': + dependencies: + '@antv/scale': 0.4.16 + '@antv/util': 2.0.17 + gl-matrix: 3.4.4 + + '@antv/event-emitter@0.1.3': {} + + '@antv/expr@1.0.2': {} + + '@antv/g-canvas@2.2.0': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.2 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-lite@2.7.0': + dependencies: + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@antv/vendor': 1.0.11 + '@babel/runtime': 7.29.2 + eventemitter3: 5.0.4 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-math@3.1.0': + dependencies: + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.2 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-plugin-dragndrop@2.1.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.2 + tslib: 2.8.1 + + '@antv/g-svg@2.1.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.2 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g2-extension-plot@0.2.2': + dependencies: + '@antv/g2': 5.4.8 + '@antv/util': 3.3.11 + '@antv/vendor': 1.0.11 + + '@antv/g2@5.4.8': + dependencies: + '@antv/component': 2.1.11 + '@antv/coord': 0.4.7 + '@antv/event-emitter': 0.1.3 + '@antv/expr': 1.0.2 + '@antv/g': 6.3.1 + '@antv/g-canvas': 2.2.0 + '@antv/g-plugin-dragndrop': 2.1.1 + '@antv/scale': 0.5.2 + '@antv/util': 3.3.11 + '@antv/vendor': 1.0.11 + flru: 1.0.2 + pdfast: 0.2.0 + + '@antv/g6-extension-react@0.2.7(@antv/g6@5.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@antv/g': 6.3.1 + '@antv/g-svg': 2.1.1 + '@antv/g6': 5.1.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@antv/g6@5.1.0': + dependencies: + '@antv/algorithm': 0.1.26 + '@antv/component': 2.1.11 + '@antv/event-emitter': 0.1.3 + '@antv/g': 6.3.1 + '@antv/g-canvas': 2.2.0 + '@antv/g-plugin-dragndrop': 2.1.1 + '@antv/graphlib': 2.0.4 + '@antv/hierarchy': 0.7.1 + '@antv/layout': 2.0.0 + '@antv/util': 3.3.11 + bubblesets-js: 2.3.4 + + '@antv/g@6.3.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.2 + gl-matrix: 3.4.4 + html2canvas: 1.4.1 + + '@antv/graphin@3.0.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@antv/g6': 5.1.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@antv/graphlib@2.0.4': + dependencies: + '@antv/event-emitter': 0.1.3 + + '@antv/hierarchy@0.7.1': {} + + '@antv/layout@2.0.0': + dependencies: + '@antv/event-emitter': 0.1.3 + '@antv/expr': 1.0.2 + '@antv/graphlib': 2.0.4 + '@antv/util': 3.3.11 + comlink: 4.4.2 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + dagre: 0.8.5 + ml-matrix: 6.12.2 + tslib: 2.8.1 + + '@antv/scale@0.4.16': + dependencies: + '@antv/util': 3.3.11 + color-string: 1.9.1 + fecha: 4.2.3 + + '@antv/scale@0.5.2': + dependencies: + '@antv/util': 3.3.11 + color-string: 1.9.1 + fecha: 4.2.3 + + '@antv/util@2.0.17': + dependencies: + csstype: 3.2.3 + tslib: 2.8.1 + + '@antv/util@3.3.11': + dependencies: + fast-deep-equal: 3.1.3 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/vendor@1.0.11': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-color': 3.1.3 + '@types/d3-dispatch': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-geo-projection: 4.0.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-regression: 1.3.10 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@dnd-kit/accessibility@3.1.1(react@19.2.5)': + dependencies: + react: 19.2.5 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.2.5) + '@dnd-kit/utilities': 3.2.2(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@dnd-kit/utilities': 3.2.2(react@19.2.5) + react: 19.2.5 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.2.5)': + dependencies: + react: 19.2.5 + tslib: 2.8.1 + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/hash@0.8.0': {} + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/unitless@0.7.5': {} + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + 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 + 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.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@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.15.0': {} + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/ansi@2.0.5': {} + + '@inquirer/confirm@6.0.12(@types/node@24.12.2)': + dependencies: + '@inquirer/core': 11.1.9(@types/node@24.12.2) + '@inquirer/type': 4.0.5(@types/node@24.12.2) + optionalDependencies: + '@types/node': 24.12.2 + + '@inquirer/core@11.1.9(@types/node@24.12.2)': + dependencies: + '@inquirer/ansi': 2.0.5 + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@24.12.2) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 24.12.2 + + '@inquirer/figures@2.0.5': {} + + '@inquirer/type@4.0.5(@types/node@24.12.2)': + optionalDependencies: + '@types/node': 24.12.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mswjs/interceptors@0.41.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@oxc-project/types@0.124.0': {} + + '@playwright/test@1.59.1': + dependencies: + playwright: 1.59.1 + + '@rc-component/async-validator@5.1.0': + dependencies: + '@babel/runtime': 7.29.2 + + '@rc-component/cascader@1.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/select': 1.6.15(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tree': 1.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/checkbox@2.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/collapse@1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/color-picker@3.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@ant-design/fast-color': 3.0.1 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/context@2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/dialog@1.8.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/portal': 2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/drawer@1.4.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/portal': 2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/dropdown@1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/form@1.8.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/async-validator': 5.1.0 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/image@1.8.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/portal': 2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/input-number@1.6.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/mini-decimal': 1.1.3 + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/input@1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/mentions@1.6.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/menu': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/textarea': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/menu@1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/overflow': 1.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/mini-decimal@1.1.3': + dependencies: + '@babel/runtime': 7.29.2 + + '@rc-component/motion@1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/mutate-observer@2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/notification@1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/overflow@1.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/pagination@1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/picker@1.9.1(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + dayjs: 1.11.20 + + '@rc-component/portal@2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/progress@1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/qrcode@1.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/rate@1.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/resize-observer@1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/segmented@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/select@1.6.15(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/slider@1.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/steps@1.2.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/switch@1.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/table@1.9.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/context': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/tabs@1.7.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/dropdown': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/menu': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/textarea@1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/tooltip@1.4.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/tour@2.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/portal': 2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/tree-select@1.8.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/select': 1.6.15(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tree': 1.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/tree@1.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/trigger@3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/portal': 2.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/upload@1.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rc-component/util@1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + is-mobile: 5.0.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-is: 18.3.1 + + '@rc-component/virtual-list@1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.15': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} + + '@standard-schema/spec@1.1.0': {} + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@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/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@transloadit/prettier-bytes@0.0.7': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/deep-eql@4.0.2': {} + + '@types/dompurify@3.2.0': + dependencies: + dompurify: 3.4.5 + + '@types/estree@1.0.8': {} + + '@types/event-emitter@0.3.5': {} + + '@types/geojson@7946.0.16': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 24.12.2 + + '@types/statuses@2.0.6': {} + + '@types/trusted-types@2.0.7': + optional: true + + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.1 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.1(typescript@6.0.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.1': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.2)': + dependencies: + typescript: 6.0.2 + + '@typescript-eslint/type-utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.58.1': {} + + '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.2)': + dependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.58.1': + dependencies: + '@typescript-eslint/types': 8.58.1 + eslint-visitor-keys: 5.0.1 + + '@uppy/companion-client@2.2.2': + dependencies: + '@uppy/utils': 4.1.3 + namespace-emitter: 2.0.1 + + '@uppy/core@2.3.4': + dependencies: + '@transloadit/prettier-bytes': 0.0.7 + '@uppy/store-default': 2.1.1 + '@uppy/utils': 4.1.3 + lodash.throttle: 4.1.1 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 3.3.11 + preact: 10.29.1 + + '@uppy/store-default@2.1.1': {} + + '@uppy/utils@4.1.3': + dependencies: + lodash.throttle: 4.1.1 + + '@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)': + dependencies: + '@uppy/companion-client': 2.2.2 + '@uppy/core': 2.3.4 + '@uppy/utils': 4.1.3 + nanoid: 3.3.11 + + '@vitejs/plugin-react@6.0.1(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.0.2)(msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + + '@vitest/expect@4.1.5': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.5(msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.13.6(@types/node@24.12.2)(typescript@6.0.2) + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.5': {} + + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-url: 1.2.4 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + prismjs: 1.30.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@types/event-emitter': 0.3.5 + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + dom7: 3.0.0 + event-emitter: 0.3.5 + html-void-elements: 2.0.1 + i18next: 20.6.1 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + scroll-into-view-if-needed: 2.2.31 + slate: 0.72.8 + slate-history: 0.66.0(slate@0.72.8) + snabbdom: 3.6.3 + + '@wangeditor/editor-for-react@1.0.6(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/editor@5.1.23)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/editor': 5.1.23 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@wangeditor/editor@5.1.23': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.foreach: 4.5.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@xyflow/react@12.10.2(@types/react@19.2.14)(immer@9.0.21)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@xyflow/system': 0.0.76 + classcat: 5.0.5 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + zustand: 4.5.7(@types/react@19.2.14)(immer@9.0.21)(react@19.2.5) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.76': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + antd@6.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@ant-design/fast-color': 3.0.1 + '@ant-design/icons': 6.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@ant-design/react-slick': 2.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@babel/runtime': 7.29.2 + '@rc-component/cascader': 1.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/checkbox': 2.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/collapse': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/color-picker': 3.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/dialog': 1.8.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/drawer': 1.4.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/dropdown': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/form': 1.8.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/image': 1.8.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/input': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/input-number': 1.6.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/mentions': 1.6.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/menu': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/motion': 1.3.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/notification': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/pagination': 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/picker': 1.9.1(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/progress': 1.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/qrcode': 1.1.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/rate': 1.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/segmented': 1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/select': 1.6.15(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/slider': 1.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/steps': 1.2.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/switch': 1.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/table': 1.9.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tabs': 1.7.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/textarea': 1.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tooltip': 1.4.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tour': 2.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tree': 1.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/tree-select': 1.8.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/trigger': 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/upload': 1.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rc-component/util': 1.10.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + dayjs: 1.11.20 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + asynckit@0.4.0: {} + + axios@1.15.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-arraybuffer@1.0.2: {} + + baseline-browser-mapping@2.10.17: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@1.1.13: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.17 + caniuse-lite: 1.0.30001787 + electron-to-chromium: 1.5.334 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bubblesets-js@2.3.4: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001787: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + classcat@5.0.5: {} + + cli-width@4.1.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: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comlink@4.4.2: {} + + commander@7.2.0: {} + + compute-scroll-into-view@1.0.20: {} + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-binarytree@1.0.2: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force-3d@3.0.6: + dependencies: + d3-binarytree: 1.0.2 + d3-dispatch: 3.0.1 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo-projection@4.0.0: + dependencies: + commander: 7.2.0 + d3-array: 3.2.4 + d3-geo: 3.1.1 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-octree@1.1.0: {} + + d3-path@3.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-regression@1.3.10: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + dagre@0.8.5: + dependencies: + graphlib: 2.1.8 + lodash: 4.18.1 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + dayjs@1.11.20: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom7@3.0.0: + dependencies: + ssr-window: 3.0.0 + + dompurify@3.4.5: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.334: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + + entities@8.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.0.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + eslint: 9.39.4(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@9.39.4(jiti@2.6.1)): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + + 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.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@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.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + 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.6.1 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.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-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} + + ext@1.7.0: + dependencies: + type: 2.7.3 + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.0: + dependencies: + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fecha@4.2.3: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + flru@1.0.2: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gl-matrix@3.4.4: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@17.4.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphlib@2.1.8: + dependencies: + lodash: 4.18.1 + + graphql@16.13.2: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.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.15.0 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + html-void-elements@2.0.1: {} + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + + i18next@20.6.1: + dependencies: + '@babel/runtime': 7.29.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immer@9.0.21: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + internmap@2.0.3: {} + + is-any-array@3.0.0: {} + + is-arrayish@0.3.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.2.0: {} + + is-mobile@5.0.0: {} + + is-node-process@1.2.0: {} + + is-plain-object@5.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-url@1.2.4: {} + + 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-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@2.6.1: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@29.0.2: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.5 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + 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' + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.foreach@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + + lodash.toarray@4.4.0: {} + + lodash@4.18.1: {} + + lru-cache@11.3.5: {} + + 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.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + mime-db@1.52.0: {} + + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.13 + + ml-array-max@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-min@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-rescale@2.0.0: + dependencies: + is-any-array: 3.0.0 + ml-array-max: 2.0.0 + ml-array-min: 2.0.0 + + ml-matrix@6.12.2: + dependencies: + is-any-array: 3.0.0 + ml-array-rescale: 2.0.0 + + ms@2.1.3: {} + + msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2): + dependencies: + '@inquirer/confirm': 6.0.12(@types/node@24.12.2) + '@mswjs/interceptors': 0.41.6 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.13.2 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.8 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.6.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 6.0.2 + transitivePeerDependencies: + - '@types/node' + + mute-stream@3.0.0: {} + + namespace-emitter@2.0.1: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + next-tick@1.1.0: {} + + node-releases@2.0.37: {} + + obug@2.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 + + outvariant@1.4.3: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + pdfast@0.2.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + + postcss@8.5.9: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.1: {} + + prelude-ls@1.2.1: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prismjs@1.30.0: {} + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-router-dom@7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-router: 7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + + react-router@7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + cookie: 1.1.1 + react: 19.2.5 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.5(react@19.2.5) + + react@19.2.5: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + rettime@0.11.8: {} + + rolldown@1.0.0-rc.15: + dependencies: + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + semver@7.7.4: {} + + set-cookie-parser@2.7.2: {} + + set-cookie-parser@3.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + slate-history@0.66.0(slate@0.72.8): + dependencies: + is-plain-object: 5.0.0 + slate: 0.72.8 + + slate@0.72.8: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + snabbdom@3.6.3: {} + + source-map-js@1.2.1: {} + + ssr-window@3.0.0: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@4.1.0: {} + + strict-event-emitter@0.5.1: {} + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + styled-components@6.4.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + '@emotion/is-prop-valid': 1.4.0 + csstype: 3.2.3 + react: 19.2.5 + stylis: 4.3.6 + optionalDependencies: + react-dom: 19.2.5(react@19.2.5) + + stylis@4.3.6: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svg-path-parser@1.1.0: {} + + symbol-tree@3.2.4: {} + + tagged-tag@1.0.0: {} + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + + throttle-debounce@5.0.2: {} + + tiny-warning@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.1: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.0.28: {} + + tldts@7.0.28: + dependencies: + tldts-core: 7.0.28 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.28 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.5.0(typescript@6.0.2): + dependencies: + typescript: 6.0.2 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@5.6.0: + dependencies: + tagged-tag: 1.0.0 + + type@2.7.3: {} + + typescript-eslint@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2) + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + typescript@6.0.2: {} + + undici-types@7.16.0: {} + + undici@7.25.0: {} + + until-async@3.0.2: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + + vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.9 + rolldown: 1.0.0-rc.15 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.2 + fsevents: 2.3.3 + jiti: 2.6.1 + + vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.0.2)(msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(msw@2.13.6(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.2 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + jsdom: 29.0.2 + transitivePeerDependencies: + - msw + + 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.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + 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 + + wildcard@1.1.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 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + 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@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zustand@4.5.7(@types/react@19.2.14)(immer@9.0.21)(react@19.2.5): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + immer: 9.0.21 + react: 19.2.5 + + zustand@5.0.12(@types/react@19.2.14)(immer@9.0.21)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)): + optionalDependencies: + '@types/react': 19.2.14 + immer: 9.0.21 + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) diff --git a/apps/web/public/crm.wasm b/apps/web/public/crm.wasm new file mode 100644 index 0000000..94339aa Binary files /dev/null and b/apps/web/public/crm.wasm differ diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons.svg b/apps/web/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/apps/web/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/inventory.wasm b/apps/web/public/inventory.wasm new file mode 100644 index 0000000..f402120 Binary files /dev/null and b/apps/web/public/inventory.wasm differ diff --git a/apps/web/public/mockServiceWorker.js b/apps/web/public/mockServiceWorker.js new file mode 100644 index 0000000..80f1930 --- /dev/null +++ b/apps/web/public/mockServiceWorker.js @@ -0,0 +1,349 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.13.6' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/apps/web/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..a3eacbd --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,258 @@ +import { useEffect, lazy, Suspense, useMemo } from 'react'; +import { HashRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom'; +import { ConfigProvider, theme as antdTheme, Spin, Result } from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import MainLayout from './layouts/MainLayout'; +import Login from './pages/Login'; +import { ErrorBoundary } from './components/ErrorBoundary'; +import { useAuthStore } from './stores/auth'; +import { useAppStore } from './stores/app'; +import type { ThemeName } from './stores/app'; +import { ROUTE_PERMISSIONS, FROZEN_ROUTES, validateRouteCoverage } from './routeConfig'; + +const Home = lazy(() => import('./pages/Home')); +const Users = lazy(() => import('./pages/Users')); +const Roles = lazy(() => import('./pages/Roles')); +const Organizations = lazy(() => import('./pages/Organizations')); +const Workflow = lazy(() => import('./pages/Workflow')); +const Messages = lazy(() => import('./pages/Messages')); +const Settings = lazy(() => import('./pages/Settings')); +const PluginAdmin = lazy(() => import('./pages/PluginAdmin')); +const PluginMarket = lazy(() => import('./pages/PluginMarket')); +const PluginCRUDPage = lazy(() => import('./pages/PluginCRUDPage')); +const PluginTabsPage = lazy(() => import('./pages/PluginTabsPage').then((m) => ({ default: m.PluginTabsPage }))); +const PluginTreePage = lazy(() => import('./pages/PluginTreePage').then((m) => ({ default: m.PluginTreePage }))); +const PluginGraphPage = lazy(() => import('./pages/PluginGraphPage').then((m) => ({ default: m.PluginGraphPage }))); +const PluginDashboardPage = lazy(() => import('./pages/PluginDashboardPage').then((m) => ({ default: m.PluginDashboardPage }))); +const PluginKanbanPage = lazy(() => import('./pages/PluginKanbanPage')); + +function FrozenRoute() { + return ; +} + +function ForbiddenPage() { + const navigate = useNavigate(); + return ( +
+ navigate('/')} style={{ cursor: 'pointer', color: 'var(--ant-color-primary)', background: 'none', border: 'none', fontSize: 14 }}>返回首页} + /> +
+ ); +} + +function PrivateRoute({ children }: { children: React.ReactNode }) { + const isAuthenticated = useAuthStore((s) => s.isAuthenticated); + const permissions = useAuthStore((s) => s.permissions); + const location = useLocation(); + + if (!isAuthenticated) return ; + + const path = location.pathname; + + // 冻结路由检查 + if (FROZEN_ROUTES.some((frozen) => path.startsWith(frozen))) { + return ; + } + + // 首页/工作台始终放行 + if (path === '/' || path === '') return <>{children}; + + const matchedPrefix = Object.keys(ROUTE_PERMISSIONS).find( + (prefix) => path === prefix || path.startsWith(prefix + '/'), + ); + if (matchedPrefix) { + const required = ROUTE_PERMISSIONS[matchedPrefix]; + const hasAccess = required.some((r) => permissions.includes(r)); + if (!hasAccess) return ; + } else { + return ; + } + + return <>{children}; +} + +const baseToken = { + borderRadius: 10, + borderRadiusLG: 12, + borderRadiusSM: 6, + fontFamily: "'Noto Sans SC', -apple-system, system-ui, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', Helvetica, Arial, sans-serif", + fontSize: 14, + fontSizeHeading4: 20, + controlHeight: 40, + controlHeightLG: 44, + controlHeightSM: 32, + boxShadow: 'none', + boxShadowSecondary: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)', +}; + +const baseComponents = { + Button: { primaryShadow: 'none', fontWeight: 500 }, + Card: { paddingLG: 20 }, + Menu: { itemBorderRadius: 10, itemMarginInline: 8, itemHeight: 40 }, + Modal: { borderRadiusLG: 16 }, + Tag: { borderRadiusSM: 6 }, +}; + +const themeConfigs: Record; components: Record> }> = { + blue: { + token: { + ...baseToken, + colorPrimary: '#2563eb', + colorSuccess: '#059669', + colorWarning: '#d97706', + colorError: '#dc2626', + colorInfo: '#0284c7', + colorBgLayout: '#f8fafc', + colorBgContainer: '#ffffff', + colorBgElevated: '#ffffff', + colorBorder: '#e2e8f0', + colorBorderSecondary: '#f1f5f9', + }, + components: { + ...baseComponents, + Table: { headerBg: '#f1f5f9', headerColor: '#475569', rowHoverBg: '#f1f5f9', fontSize: 14 }, + }, + }, + warm: { + token: { + ...baseToken, + borderRadius: 12, + borderRadiusLG: 14, + borderRadiusSM: 8, + colorPrimary: '#C4623A', + colorSuccess: '#5B7A5E', + colorWarning: '#C4873A', + colorError: '#B54A4A', + colorInfo: '#8B7A5E', + colorBgLayout: '#F5F0EB', + colorBgContainer: '#ffffff', + colorBgElevated: '#ffffff', + colorBorder: '#E8E2DC', + colorBorderSecondary: '#F0EBE5', + }, + components: { + ...baseComponents, + Table: { headerBg: '#EDE8E2', headerColor: '#7A756E', rowHoverBg: '#F5F0EB', fontSize: 14 }, + }, + }, + dark: { + token: { + ...baseToken, + colorPrimary: '#60A5FA', + colorSuccess: '#34D399', + colorWarning: '#FBBF24', + colorError: '#F87171', + colorInfo: '#38BDF8', + colorBgLayout: '#0F172A', + colorBgContainer: '#1E293B', + colorBgElevated: '#334155', + colorBorder: '#334155', + colorBorderSecondary: 'rgba(255,255,255,0.06)', + boxShadow: 'none', + boxShadowSecondary: '0 2px 8px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2)', + }, + components: { + ...baseComponents, + Table: { headerBg: '#1E293B', headerColor: '#94A3B8', rowHoverBg: '#1E293B', fontSize: 14 }, + }, + }, + emerald: { + token: { + ...baseToken, + borderRadius: 10, + borderRadiusLG: 14, + borderRadiusSM: 8, + colorPrimary: '#5B7A5E', + colorSuccess: '#3D7A42', + colorWarning: '#B8863A', + colorError: '#A54A4A', + colorInfo: '#4A7A8B', + colorBgLayout: '#F4F7F4', + colorBgContainer: '#ffffff', + colorBgElevated: '#ffffff', + colorBorder: '#D5DED5', + colorBorderSecondary: '#E5ECE5', + }, + components: { + ...baseComponents, + Table: { headerBg: '#EDF2ED', headerColor: '#5A6E5A', rowHoverBg: '#F4F7F4', fontSize: 14 }, + }, + }, +}; + +export default function App() { + const loadFromStorage = useAuthStore((s) => s.loadFromStorage); + const themeName = useAppStore((s) => s.theme); + + useEffect(() => { + loadFromStorage(); + }, [loadFromStorage]); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', themeName); + }, [themeName]); + + // DEV mode: validate all routes have permission declarations + useEffect(() => { + validateRouteCoverage([ + "/users", "/roles", "/organizations", "/workflow", "/messages", "/settings", + "/plugins/admin", "/plugins/market", + ]); + }, []); + + const isDark = themeName === 'dark'; + const antTheme = useMemo(() => themeConfigs[themeName] ?? themeConfigs.blue, [themeName]); + + return ( + <> + 跳转到主要内容 + + + + } /> + + + + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + } + /> + + + + + ); +} diff --git a/apps/web/src/api/auditLogs.test.ts b/apps/web/src/api/auditLogs.test.ts new file mode 100644 index 0000000..1a71084 --- /dev/null +++ b/apps/web/src/api/auditLogs.test.ts @@ -0,0 +1,51 @@ +/** + * auditLogs API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as auditLogsApi from './auditLogs' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('auditLogs API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listAuditLogs 应调用 GET /audit-logs 并传递查询参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await auditLogsApi.listAuditLogs({ resource_type: 'user', user_id: 'u-001', page: 1, page_size: 10 }) + + expect(mockGet).toHaveBeenCalledWith('/audit-logs', { + params: expect.objectContaining({ + resource_type: 'user', + user_id: 'u-001', + page: 1, + page_size: 10, + }), + }) + }) + + it('listAuditLogs 默认应传 page=1 page_size=20', async () => { + mockGet.mockResolvedValue(fakeRes) + await auditLogsApi.listAuditLogs() + + expect(mockGet).toHaveBeenCalledWith('/audit-logs', { + params: expect.objectContaining({ page: 1, page_size: 20 }), + }) + }) +}) diff --git a/apps/web/src/api/auditLogs.ts b/apps/web/src/api/auditLogs.ts new file mode 100644 index 0000000..92be6cc --- /dev/null +++ b/apps/web/src/api/auditLogs.ts @@ -0,0 +1,31 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface AuditLogItem { + id: string; + tenant_id: string; + action: string; + resource_type: string; + resource_id: string; + user_id: string; + old_value?: string; + new_value?: string; + ip_address?: string; + user_agent?: string; + created_at: string; +} + +export interface AuditLogQuery { + resource_type?: string; + user_id?: string; + page?: number; + page_size?: number; +} + +export async function listAuditLogs(query: AuditLogQuery = {}) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/audit-logs', + { params: { page: query.page ?? 1, page_size: query.page_size ?? 20, ...query } }, + ); + return data.data; +} diff --git a/apps/web/src/api/auth.test.ts b/apps/web/src/api/auth.test.ts new file mode 100644 index 0000000..5a68920 --- /dev/null +++ b/apps/web/src/api/auth.test.ts @@ -0,0 +1,55 @@ +/** + * auth API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as authApi from './auth' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('auth API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('login 应调用 POST /auth/login 并传递用户名密码', async () => { + mockPost.mockResolvedValue(fakeRes) + await authApi.login({ username: 'admin', password: '123456' }) + + expect(mockPost).toHaveBeenCalledWith('/auth/login', { + username: 'admin', + password: '123456', + }) + }) + + it('logout 应调用 POST /auth/logout', async () => { + mockPost.mockResolvedValue(undefined) + await authApi.logout() + + expect(mockPost).toHaveBeenCalledWith('/auth/logout') + }) + + it('changePassword 应调用 POST /auth/change-password', async () => { + mockPost.mockResolvedValue(undefined) + await authApi.changePassword('oldPass', 'newPass') + + expect(mockPost).toHaveBeenCalledWith('/auth/change-password', { + current_password: 'oldPass', + new_password: 'newPass', + }) + }) +}) diff --git a/apps/web/src/api/auth.ts b/apps/web/src/api/auth.ts new file mode 100644 index 0000000..98cb7f7 --- /dev/null +++ b/apps/web/src/api/auth.ts @@ -0,0 +1,55 @@ +import client from './client'; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface UserInfo { + id: string; + username: string; + email?: string; + phone?: string; + display_name?: string; + avatar_url?: string; + status: string; + roles: RoleInfo[]; + version: number; +} + +export interface RoleInfo { + id: string; + name: string; + code: string; + description?: string; + is_system: boolean; +} + +export interface LoginResponse { + access_token: string; + refresh_token: string; + expires_in: number; + user: UserInfo; +} + +export async function login(req: LoginRequest): Promise { + const { data } = await client.post<{ success: boolean; data: LoginResponse }>( + '/auth/login', + req + ); + return data.data; +} + +export async function logout(): Promise { + await client.post('/auth/logout'); +} + +export async function changePassword( + currentPassword: string, + newPassword: string +): Promise { + await client.post('/auth/change-password', { + current_password: currentPassword, + new_password: newPassword, + }); +} diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts new file mode 100644 index 0000000..c568930 --- /dev/null +++ b/apps/web/src/api/client.ts @@ -0,0 +1,261 @@ +import axios from "axios"; +import { message as antMessage } from "antd"; + +// 请求缓存:短时间内相同请求复用结果 +interface CacheEntry { + data: unknown; + timestamp: number; +} + +const requestCache = new Map(); +const CACHE_TTL = 5000; // 5 秒缓存 + +function getCacheKey(config: { + url?: string; + params?: unknown; + method?: string; +}): string { + return `${config.method || "get"}:${config.url || ""}:${JSON.stringify(config.params || {})}`; +} + +const defaultAdapter = axios.getAdapter(axios.defaults.adapter); + +const client = axios.create({ + baseURL: "/api/v1", + timeout: 10000, + headers: { "Content-Type": "application/json" }, + adapter: (config) => { + // GET 请求检查缓存 + if (config.method === "get" && config.url) { + const key = getCacheKey(config); + const entry = requestCache.get(key); + if (entry && Date.now() - entry.timestamp < CACHE_TTL) { + return Promise.resolve({ + data: entry.data, + status: 200, + statusText: "OK (cached)", + headers: new axios.AxiosHeaders(), + config, + }); + } + } + return defaultAdapter(config); + }, +}); + +// Decode JWT payload without external library +function decodeJwtPayload( + token: string, +): { exp?: number; sub?: string } | null { + try { + const parts = token.split("."); + if (parts.length !== 3) return null; + const payload = JSON.parse( + atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")), + ); + return payload; + } catch { + return null; + } +} + +// Check if token is expired or about to expire (within 30s buffer) +function isTokenExpiringSoon(token: string): boolean { + const payload = decodeJwtPayload(token); + if (!payload?.exp) return true; + return Date.now() / 1000 > payload.exp - 30; +} + +// Request interceptor: attach access token + proactive refresh +client.interceptors.request.use(async (config) => { + const token = localStorage.getItem("access_token"); + if (token) { + // If token is about to expire, proactively refresh before sending the request + if (isTokenExpiringSoon(token)) { + const refreshToken = localStorage.getItem("refresh_token"); + if (refreshToken && !isRefreshing) { + isRefreshing = true; + try { + const { data } = await axios.post("/api/v1/auth/refresh", { + refresh_token: refreshToken, + }); + const newAccess = data.data.access_token; + const newRefresh = data.data.refresh_token; + + // 验证新 token 的用户身份一致 + const currentUserSub = decodeJwtPayload(token)?.sub; + const newTokenSub = decodeJwtPayload(newAccess)?.sub; + if (currentUserSub && newTokenSub && currentUserSub !== newTokenSub) { + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + localStorage.removeItem("user"); + window.location.hash = "/login"; + return Promise.reject(new Error("身份验证失败,请重新登录")); + } + + localStorage.setItem("access_token", newAccess); + localStorage.setItem("refresh_token", newRefresh); + processQueue(null, newAccess); + config.headers.Authorization = `Bearer ${newAccess}`; + return config; + } catch { + processQueue(new Error("refresh failed"), null); + // Continue with old token, let 401 handler deal with it + } finally { + isRefreshing = false; + } + } + } + config.headers.Authorization = `Bearer ${token}`; + } + + return config; +}); + +// 响应拦截器:缓存 GET 响应 + 自动刷新 token +client.interceptors.response.use( + (response) => { + // 缓存 GET 响应 + if (response.config.method === "get" && response.config.url) { + const key = getCacheKey(response.config); + requestCache.set(key, { data: response.data, timestamp: Date.now() }); + } + return response; + }, + async (error) => { + const originalRequest = error.config; + if ( + error.response?.status === 401 && + !originalRequest._retry && + !originalRequest.url?.includes("/auth/login") + ) { + if (isRefreshing) { + return new Promise((resolve, reject) => { + failedQueue.push({ resolve, reject }); + }).then((token) => { + originalRequest.headers.Authorization = `Bearer ${token}`; + return client(originalRequest); + }); + } + + originalRequest._retry = true; + isRefreshing = true; + + try { + const refreshToken = localStorage.getItem("refresh_token"); + if (!refreshToken) throw new Error("No refresh token"); + + const { data } = await axios.post("/api/v1/auth/refresh", { + refresh_token: refreshToken, + }); + + const newAccessToken = data.data.access_token; + const newRefreshToken = data.data.refresh_token; + + // 验证新 token 的用户身份与当前用户一致,防止并发场景下身份切换 + const currentToken = localStorage.getItem("access_token"); + const currentUserSub = currentToken + ? decodeJwtPayload(currentToken)?.sub + : null; + const newTokenSub = decodeJwtPayload(newAccessToken)?.sub; + if (currentUserSub && newTokenSub && currentUserSub !== newTokenSub) { + // 身份不一致,强制登出 + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + localStorage.removeItem("user"); + window.location.hash = "/login"; + return Promise.reject(new Error("身份验证失败,请重新登录")); + } + + localStorage.setItem("access_token", newAccessToken); + localStorage.setItem("refresh_token", newRefreshToken); + + processQueue(null, newAccessToken); + + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + return client(originalRequest); + } catch (refreshError) { + processQueue(refreshError, null); + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + window.location.hash = "/login"; + return Promise.reject(refreshError); + } finally { + isRefreshing = false; + } + } + + return Promise.reject(error); + }, +); + +// 全局错误提示(仅对未被组件处理的错误显示) +let globalErrorTimer: ReturnType | null = null; +function showGlobalError(msg: string) { + // 防止短时间内弹出大量相同提示 + if (globalErrorTimer) return; + antMessage.error(msg, 3); + globalErrorTimer = setTimeout(() => { + globalErrorTimer = null; + }, 3000); +} + +// 全局错误拦截 — 在响应拦截器之后、组件 catch 之前执行 +// 组件可通过 axios config 中设置 skipGlobalError: true 来抑制全局提示 +declare module "axios" { + interface AxiosRequestConfig { + skipGlobalError?: boolean; + } + interface InternalAxiosRequestConfig { + skipGlobalError?: boolean; + } +} + +client.interceptors.response.use( + (response) => response, + (error) => { + if (error.config?.skipGlobalError) { + return Promise.reject(error); + } + if (!error.response) { + showGlobalError("网络连接异常,请检查网络"); + } else if (error.response.status === 403) { + // 403 通常是权限不足,不全局提示 — 组件层通过 AuthButton 已隐藏操作入口 + } else if (error.response.status === 404) { + // 404 通常由组件自行处理(如跳转),不全局提示 + } else if (error.response.status >= 500) { + showGlobalError("服务器异常,请稍后重试"); + } + return Promise.reject(error); + }, +); + +let isRefreshing = false; +let failedQueue: Array<{ + resolve: (token: string) => void; + reject: (error: unknown) => void; +}> = []; + +function processQueue(error: unknown, token: string | null) { + failedQueue.forEach(({ resolve, reject }) => { + if (token) resolve(token); + else reject(error); + }); + failedQueue = []; +} + +// 清除缓存(登录/登出时调用) +export function clearApiCache() { + requestCache.clear(); +} + +// 通用错误处理:提取后端错误消息并展示 +export function handleApiError(err: unknown, fallback = "操作失败"): string { + const msg = + (err as { response?: { data?: { message?: string } } })?.response?.data + ?.message || fallback; + antMessage.error(msg); + return msg; +} + +export default client; diff --git a/apps/web/src/api/config-modules.test.ts b/apps/web/src/api/config-modules.test.ts new file mode 100644 index 0000000..f051af7 --- /dev/null +++ b/apps/web/src/api/config-modules.test.ts @@ -0,0 +1,197 @@ +/** + * config-modules API 契约测试(menus + settings + languages + numberingRules + themes) + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as menusApi from './menus' +import * as settingsApi from './settings' +import * as languagesApi from './languages' +import * as numberingApi from './numberingRules' +import * as themesApi from './themes' + +beforeEach(() => { + vi.clearAllMocks() +}) + +// ============================================================ +// menus +// ============================================================ +describe('menus API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('getMenus 应调用 GET /config/menus', async () => { + mockGet.mockResolvedValue(fakeRes) + await menusApi.getMenus() + + expect(mockGet).toHaveBeenCalledWith('/config/menus') + }) + + it('getMenusForUser 应调用 GET /menus/user', async () => { + mockGet.mockResolvedValue(fakeRes) + await menusApi.getMenusForUser() + + expect(mockGet).toHaveBeenCalledWith('/menus/user') + }) + + it('batchSaveMenus 应调用 PUT /config/menus 并传递 menus 数组', async () => { + mockPut.mockResolvedValue(undefined) + const menus = [{ title: '仪表盘', path: '/dashboard' }] + await menusApi.batchSaveMenus(menus) + + expect(mockPut).toHaveBeenCalledWith('/config/menus', { menus }) + }) + + it('createMenu 应调用 POST /config/menus', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { title: '新菜单', path: '/new', sort_order: 10 } + await menusApi.createMenu(req) + + expect(mockPost).toHaveBeenCalledWith('/config/menus', req) + }) + + it('deleteMenu 应调用 DELETE /config/menus/:id 并在 body 传递 version', async () => { + mockDelete.mockResolvedValue(undefined) + await menusApi.deleteMenu('menu-001', 3) + + expect(mockDelete).toHaveBeenCalledWith('/config/menus/menu-001', { data: { version: 3 } }) + }) +}) + +// ============================================================ +// settings +// ============================================================ +describe('settings API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('getSetting 应调用 GET /config/settings/:key 并传递 scope 参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await settingsApi.getSetting('site.name', 'global', 'org-001') + + expect(mockGet).toHaveBeenCalledWith('/config/settings/site.name', { + params: { scope: 'global', scope_id: 'org-001' }, + }) + }) + + it('updateSetting 应调用 PUT /config/settings/:key', async () => { + mockPut.mockResolvedValue(fakeRes) + await settingsApi.updateSetting('site.name', '新名称', 1) + + expect(mockPut).toHaveBeenCalledWith('/config/settings/site.name', { + setting_value: '新名称', + version: 1, + }) + }) + + it('deleteSetting 应调用 DELETE /config/settings/:key', async () => { + mockDelete.mockResolvedValue(undefined) + await settingsApi.deleteSetting('site.name', 2) + + expect(mockDelete).toHaveBeenCalledWith('/config/settings/site.name', { data: { version: 2 } }) + }) +}) + +// ============================================================ +// languages +// ============================================================ +describe('languages API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listLanguages 应调用 GET /config/languages', async () => { + mockGet.mockResolvedValue(fakeRes) + await languagesApi.listLanguages() + + expect(mockGet).toHaveBeenCalledWith('/config/languages') + }) + + it('updateLanguage 应调用 PUT /config/languages/:code', async () => { + mockPut.mockResolvedValue(fakeRes) + await languagesApi.updateLanguage('zh-CN', { is_active: true, name: '简体中文' }) + + expect(mockPut).toHaveBeenCalledWith('/config/languages/zh-CN', { + is_active: true, + name: '简体中文', + }) + }) +}) + +// ============================================================ +// numberingRules +// ============================================================ +describe('numberingRules API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listNumberingRules 应调用 GET /config/numbering-rules', async () => { + mockGet.mockResolvedValue(fakeRes) + await numberingApi.listNumberingRules(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/config/numbering-rules', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('createNumberingRule 应调用 POST /config/numbering-rules', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '患者编号', code: 'patient', prefix: 'P', seq_length: 6 } + await numberingApi.createNumberingRule(req) + + expect(mockPost).toHaveBeenCalledWith('/config/numbering-rules', req) + }) + + it('updateNumberingRule 应调用 PUT /config/numbering-rules/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { prefix: 'HMS', version: 1 } + await numberingApi.updateNumberingRule('nr-001', req) + + expect(mockPut).toHaveBeenCalledWith('/config/numbering-rules/nr-001', req) + }) + + it('generateNumber 应调用 POST /config/numbering-rules/:id/generate', async () => { + mockPost.mockResolvedValue(fakeRes) + await numberingApi.generateNumber('nr-001') + + expect(mockPost).toHaveBeenCalledWith('/config/numbering-rules/nr-001/generate') + }) + + it('deleteNumberingRule 应调用 DELETE /config/numbering-rules/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await numberingApi.deleteNumberingRule('nr-001', 1) + + expect(mockDelete).toHaveBeenCalledWith('/config/numbering-rules/nr-001', { data: { version: 1 } }) + }) +}) + +// ============================================================ +// themes +// ============================================================ +describe('themes API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('getTheme 应调用 GET /config/themes', async () => { + mockGet.mockResolvedValue(fakeRes) + await themesApi.getTheme() + + expect(mockGet).toHaveBeenCalledWith('/config/themes') + }) + + it('updateTheme 应调用 PUT /config/themes', async () => { + mockPut.mockResolvedValue(fakeRes) + const theme = { primary_color: '#1890ff', brand_name: 'HMS' } + await themesApi.updateTheme(theme) + + expect(mockPut).toHaveBeenCalledWith('/config/themes', theme) + }) +}) diff --git a/apps/web/src/api/dictionaries.test.ts b/apps/web/src/api/dictionaries.test.ts new file mode 100644 index 0000000..a0000a6 --- /dev/null +++ b/apps/web/src/api/dictionaries.test.ts @@ -0,0 +1,96 @@ +/** + * dictionaries API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as dictApi from './dictionaries' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('dictionaries API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listDictionaries 应调用 GET /config/dictionaries 并传递分页参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await dictApi.listDictionaries(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/config/dictionaries', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('createDictionary 应调用 POST /config/dictionaries', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '性别', code: 'gender', description: '性别字典' } + await dictApi.createDictionary(req) + + expect(mockPost).toHaveBeenCalledWith('/config/dictionaries', req) + }) + + it('updateDictionary 应调用 PUT /config/dictionaries/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { name: '性别(更新)', version: 1 } + await dictApi.updateDictionary('dict-001', req) + + expect(mockPut).toHaveBeenCalledWith('/config/dictionaries/dict-001', req) + }) + + it('deleteDictionary 应调用 DELETE /config/dictionaries/:id 并在 body 传递 version', async () => { + mockDelete.mockResolvedValue(undefined) + await dictApi.deleteDictionary('dict-001', 2) + + expect(mockDelete).toHaveBeenCalledWith('/config/dictionaries/dict-001', { + data: { version: 2 }, + }) + }) + + it('listItemsByCode 应调用 GET /config/dictionaries/items 并传递 code 参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await dictApi.listItemsByCode('gender') + + expect(mockGet).toHaveBeenCalledWith('/config/dictionaries/items', { + params: { code: 'gender' }, + }) + }) + + it('createDictionaryItem 应调用 POST /config/dictionaries/:id/items', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { label: '男', value: 'male', sort_order: 1 } + await dictApi.createDictionaryItem('dict-001', req) + + expect(mockPost).toHaveBeenCalledWith('/config/dictionaries/dict-001/items', req) + }) + + it('updateDictionaryItem 应调用 PUT /config/dictionaries/:dictId/items/:itemId', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { label: '女', version: 1 } + await dictApi.updateDictionaryItem('dict-001', 'item-001', req) + + expect(mockPut).toHaveBeenCalledWith('/config/dictionaries/dict-001/items/item-001', req) + }) + + it('deleteDictionaryItem 应调用 DELETE /config/dictionaries/:dictId/items/:itemId', async () => { + mockDelete.mockResolvedValue(undefined) + await dictApi.deleteDictionaryItem('dict-001', 'item-001', 1) + + expect(mockDelete).toHaveBeenCalledWith('/config/dictionaries/dict-001/items/item-001', { + data: { version: 1 }, + }) + }) +}) diff --git a/apps/web/src/api/dictionaries.ts b/apps/web/src/api/dictionaries.ts new file mode 100644 index 0000000..b44d443 --- /dev/null +++ b/apps/web/src/api/dictionaries.ts @@ -0,0 +1,111 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface DictionaryItemInfo { + id: string; + dictionary_id: string; + label: string; + value: string; + sort_order: number; + color?: string; + version: number; +} + +export interface DictionaryInfo { + id: string; + name: string; + code: string; + description?: string; + items: DictionaryItemInfo[]; + version: number; +} + +export interface CreateDictionaryRequest { + name: string; + code: string; + description?: string; +} + +export interface UpdateDictionaryRequest { + name?: string; + description?: string; + version: number; +} + +export async function listDictionaries(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/config/dictionaries', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function createDictionary(req: CreateDictionaryRequest) { + const { data } = await client.post<{ success: boolean; data: DictionaryInfo }>( + '/config/dictionaries', + req, + ); + return data.data; +} + +export async function updateDictionary(id: string, req: UpdateDictionaryRequest) { + const { data } = await client.put<{ success: boolean; data: DictionaryInfo }>( + `/config/dictionaries/${id}`, + req, + ); + return data.data; +} + +export async function deleteDictionary(id: string, version: number) { + await client.delete(`/config/dictionaries/${id}`, { data: { version } }); +} + +export async function listItemsByCode(code: string) { + const { data } = await client.get<{ success: boolean; data: DictionaryItemInfo[] }>( + '/config/dictionaries/items', + { params: { code } }, + ); + return data.data; +} + +export interface CreateDictionaryItemRequest { + label: string; + value: string; + sort_order?: number; + color?: string; +} + +export interface UpdateDictionaryItemRequest { + label?: string; + value?: string; + sort_order?: number; + color?: string; + version: number; +} + +export async function createDictionaryItem( + dictionaryId: string, + req: CreateDictionaryItemRequest, +) { + const { data } = await client.post<{ success: boolean; data: DictionaryItemInfo }>( + `/config/dictionaries/${dictionaryId}/items`, + req, + ); + return data.data; +} + +export async function updateDictionaryItem( + dictionaryId: string, + itemId: string, + req: UpdateDictionaryItemRequest, +) { + const { data } = await client.put<{ success: boolean; data: DictionaryItemInfo }>( + `/config/dictionaries/${dictionaryId}/items/${itemId}`, + req, + ); + return data.data; +} + +export async function deleteDictionaryItem(dictionaryId: string, itemId: string, version: number) { + await client.delete(`/config/dictionaries/${dictionaryId}/items/${itemId}`, { data: { version } }); +} diff --git a/apps/web/src/api/languages.ts b/apps/web/src/api/languages.ts new file mode 100644 index 0000000..ba61fdf --- /dev/null +++ b/apps/web/src/api/languages.ts @@ -0,0 +1,34 @@ +import client from './client'; + +// --- Types --- + +export interface LanguageInfo { + code: string; + name: string; + is_active: boolean; +} + +export interface UpdateLanguageRequest { + is_active: boolean; + name?: string; +} + +// --- API Functions --- + +export async function listLanguages(): Promise { + const { data } = await client.get<{ success: boolean; data: LanguageInfo[] }>( + '/config/languages', + ); + return data.data; +} + +export async function updateLanguage( + code: string, + req: UpdateLanguageRequest, +): Promise { + const { data } = await client.put<{ success: boolean; data: LanguageInfo }>( + `/config/languages/${code}`, + req, + ); + return data.data; +} diff --git a/apps/web/src/api/menus.ts b/apps/web/src/api/menus.ts new file mode 100644 index 0000000..f159088 --- /dev/null +++ b/apps/web/src/api/menus.ts @@ -0,0 +1,63 @@ +import client from './client'; + +export interface MenuInfo { + id: string; + parent_id?: string; + title: string; + path?: string; + icon?: string; + sort_order: number; + visible: boolean; + menu_type: string; + permission?: string; + children?: MenuInfo[]; + version: number; +} + +export interface MenuItemReq { + id?: string; + parent_id?: string; + title: string; + path?: string; + icon?: string; + sort_order?: number; + visible?: boolean; + menu_type?: string; + permission?: string; + role_ids?: string[]; + version?: number; +} + +export async function getMenus() { + const { data } = await client.get<{ success: boolean; data: MenuInfo[] }>('/config/menus'); + return data.data; +} + +export async function getMenusForUser() { + const { data } = await client.get<{ success: boolean; data: MenuInfo[] }>('/menus/user'); + return data.data; +} + +export async function batchSaveMenus(menus: MenuItemReq[]) { + await client.put('/config/menus', { menus }); +} + +export async function createMenu(req: MenuItemReq) { + const { data } = await client.post<{ success: boolean; data: MenuInfo }>( + '/config/menus', + req, + ); + return data.data; +} + +export async function updateMenu(id: string, req: MenuItemReq) { + const { data } = await client.put<{ success: boolean; data: MenuInfo }>( + `/config/menus/${id}`, + req, + ); + return data.data; +} + +export async function deleteMenu(id: string, version: number) { + await client.delete(`/config/menus/${id}`, { data: { version } }); +} diff --git a/apps/web/src/api/messageTemplates.ts b/apps/web/src/api/messageTemplates.ts new file mode 100644 index 0000000..0cfc4d2 --- /dev/null +++ b/apps/web/src/api/messageTemplates.ts @@ -0,0 +1,40 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface MessageTemplateInfo { + id: string; + tenant_id: string; + name: string; + code: string; + channel: string; + title_template: string; + body_template: string; + language: string; + created_at: string; + updated_at: string; +} + +export interface CreateTemplateRequest { + name: string; + code: string; + channel?: string; + title_template: string; + body_template: string; + language?: string; +} + +export async function listTemplates(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/message-templates', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function createTemplate(req: CreateTemplateRequest) { + const { data } = await client.post<{ success: boolean; data: MessageTemplateInfo }>( + '/message-templates', + req, + ); + return data.data; +} diff --git a/apps/web/src/api/messages.test.ts b/apps/web/src/api/messages.test.ts new file mode 100644 index 0000000..b8ed9cd --- /dev/null +++ b/apps/web/src/api/messages.test.ts @@ -0,0 +1,100 @@ +/** + * messages + messageTemplates API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as messagesApi from './messages' +import * as templateApi from './messageTemplates' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('messages API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listMessages 应调用 GET /messages 并传递查询参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await messagesApi.listMessages({ page: 2, page_size: 10, is_read: false, priority: 'high' }) + + expect(mockGet).toHaveBeenCalledWith('/messages', { + params: expect.objectContaining({ + page: 2, + page_size: 10, + is_read: false, + priority: 'high', + }), + }) + }) + + it('getUnreadCount 应调用 GET /messages/unread-count', async () => { + mockGet.mockResolvedValue(fakeRes) + await messagesApi.getUnreadCount() + + expect(mockGet).toHaveBeenCalledWith('/messages/unread-count') + }) + + it('markRead 应调用 PUT /messages/:id/read', async () => { + mockPut.mockResolvedValue({ data: { success: true } }) + await messagesApi.markRead('msg-001') + + expect(mockPut).toHaveBeenCalledWith('/messages/msg-001/read') + }) + + it('markAllRead 应调用 PUT /messages/read-all', async () => { + mockPut.mockResolvedValue({ data: { success: true } }) + await messagesApi.markAllRead() + + expect(mockPut).toHaveBeenCalledWith('/messages/read-all') + }) + + it('deleteMessage 应调用 DELETE /messages/:id', async () => { + mockDelete.mockResolvedValue({ data: { success: true } }) + await messagesApi.deleteMessage('msg-001') + + expect(mockDelete).toHaveBeenCalledWith('/messages/msg-001') + }) + + it('sendMessage 应调用 POST /messages 并传递请求体', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { title: '通知', body: '内容', recipient_id: 'u-001' } + await messagesApi.sendMessage(req) + + expect(mockPost).toHaveBeenCalledWith('/messages', req) + }) +}) + +describe('messageTemplates API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listTemplates 应调用 GET /message-templates 并传递分页参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await templateApi.listTemplates(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/message-templates', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('createTemplate 应调用 POST /message-templates', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '预约提醒', code: 'appointment_reminder', title_template: '预约提醒', body_template: '您有预约' } + await templateApi.createTemplate(req) + + expect(mockPost).toHaveBeenCalledWith('/message-templates', req) + }) +}) diff --git a/apps/web/src/api/messages.ts b/apps/web/src/api/messages.ts new file mode 100644 index 0000000..7e74795 --- /dev/null +++ b/apps/web/src/api/messages.ts @@ -0,0 +1,98 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface MessageInfo { + id: string; + tenant_id: string; + template_id?: string; + sender_id?: string; + sender_type: string; + recipient_id: string; + recipient_type: string; + title: string; + body: string; + priority: string; + business_type?: string; + business_id?: string; + is_read: boolean; + read_at?: string; + is_archived: boolean; + status: string; + sent_at?: string; + created_at: string; + updated_at: string; +} + +export interface SendMessageRequest { + title: string; + body: string; + recipient_id: string; + recipient_type?: string; + priority?: string; + template_id?: string; + business_type?: string; + business_id?: string; +} + +export interface MessageQuery { + page?: number; + page_size?: number; + is_read?: boolean; + priority?: string; + business_type?: string; + status?: string; +} + +export async function listMessages(query: MessageQuery = {}) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/messages', + { params: { page: query.page ?? 1, page_size: query.page_size ?? 20, ...query } }, + ); + return data.data; +} + +export async function getUnreadCount() { + const { data } = await client.get<{ success: boolean; data: { count: number } }>( + '/messages/unread-count', + ); + return data.data; +} + +export async function markRead(id: string) { + const { data } = await client.put<{ success: boolean }>( + `/messages/${id}/read`, + ); + return data; +} + +export async function markAllRead() { + const { data } = await client.put<{ success: boolean }>( + '/messages/read-all', + ); + return data; +} + +export async function deleteMessage(id: string) { + const { data } = await client.delete<{ success: boolean }>( + `/messages/${id}`, + ); + return data; +} + +export async function sendMessage(req: SendMessageRequest) { + const { data } = await client.post<{ success: boolean; data: MessageInfo }>( + '/messages', + req, + ); + return data.data; +} + +export interface SubscriptionUpdateReq { + dnd_enabled: boolean; + dnd_start?: string; + dnd_end?: string; +} + +export async function updateSubscription(req: SubscriptionUpdateReq) { + await client.put('/message-subscriptions', req); +} diff --git a/apps/web/src/api/numberingRules.ts b/apps/web/src/api/numberingRules.ts new file mode 100644 index 0000000..fd29bfe --- /dev/null +++ b/apps/web/src/api/numberingRules.ts @@ -0,0 +1,73 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface NumberingRuleInfo { + id: string; + name: string; + code: string; + prefix: string; + date_format?: string; + seq_length: number; + seq_start: number; + seq_current: number; + separator: string; + reset_cycle: string; + last_reset_date?: string; + version: number; +} + +export interface CreateNumberingRuleRequest { + name: string; + code: string; + prefix?: string; + date_format?: string; + seq_length?: number; + seq_start?: number; + separator?: string; + reset_cycle?: string; +} + +export interface UpdateNumberingRuleRequest { + name?: string; + prefix?: string; + date_format?: string; + seq_length?: number; + separator?: string; + reset_cycle?: string; + version: number; +} + +export async function listNumberingRules(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/config/numbering-rules', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function createNumberingRule(req: CreateNumberingRuleRequest) { + const { data } = await client.post<{ success: boolean; data: NumberingRuleInfo }>( + '/config/numbering-rules', + req, + ); + return data.data; +} + +export async function updateNumberingRule(id: string, req: UpdateNumberingRuleRequest) { + const { data } = await client.put<{ success: boolean; data: NumberingRuleInfo }>( + `/config/numbering-rules/${id}`, + req, + ); + return data.data; +} + +export async function generateNumber(id: string) { + const { data } = await client.post<{ success: boolean; data: { number: string } }>( + `/config/numbering-rules/${id}/generate`, + ); + return data.data; +} + +export async function deleteNumberingRule(id: string, version: number) { + await client.delete(`/config/numbering-rules/${id}`, { data: { version } }); +} diff --git a/apps/web/src/api/orgs.test.ts b/apps/web/src/api/orgs.test.ts new file mode 100644 index 0000000..d5718d7 --- /dev/null +++ b/apps/web/src/api/orgs.test.ts @@ -0,0 +1,126 @@ +/** + * orgs API 契约测试(组织/部门/岗位) + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as orgsApi from './orgs' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('organizations API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listOrgTree 应调用 GET /organizations', async () => { + mockGet.mockResolvedValue(fakeRes) + await orgsApi.listOrgTree() + + expect(mockGet).toHaveBeenCalledWith('/organizations') + }) + + it('createOrg 应调用 POST /organizations', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '总公司', code: 'HQ' } + await orgsApi.createOrg(req) + + expect(mockPost).toHaveBeenCalledWith('/organizations', req) + }) + + it('updateOrg 应调用 PUT /organizations/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { name: '改名', version: 1 } + await orgsApi.updateOrg('org-001', req) + + expect(mockPut).toHaveBeenCalledWith('/organizations/org-001', req) + }) + + it('deleteOrg 应调用 DELETE /organizations/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await orgsApi.deleteOrg('org-001') + + expect(mockDelete).toHaveBeenCalledWith('/organizations/org-001') + }) +}) + +describe('departments API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listDeptTree 应调用 GET /organizations/:orgId/departments', async () => { + mockGet.mockResolvedValue(fakeRes) + await orgsApi.listDeptTree('org-001') + + expect(mockGet).toHaveBeenCalledWith('/organizations/org-001/departments') + }) + + it('createDept 应调用 POST /organizations/:orgId/departments', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '内科', code: 'NK' } + await orgsApi.createDept('org-001', req) + + expect(mockPost).toHaveBeenCalledWith('/organizations/org-001/departments', req) + }) + + it('updateDept 应调用 PUT /departments/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { name: '内科(更新)', version: 1 } + await orgsApi.updateDept('dept-001', req) + + expect(mockPut).toHaveBeenCalledWith('/departments/dept-001', req) + }) + + it('deleteDept 应调用 DELETE /departments/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await orgsApi.deleteDept('dept-001') + + expect(mockDelete).toHaveBeenCalledWith('/departments/dept-001') + }) +}) + +describe('positions API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listPositions 应调用 GET /departments/:deptId/positions', async () => { + mockGet.mockResolvedValue(fakeRes) + await orgsApi.listPositions('dept-001') + + expect(mockGet).toHaveBeenCalledWith('/departments/dept-001/positions') + }) + + it('createPosition 应调用 POST /departments/:deptId/positions', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '主治医师', code: 'ZYS' } + await orgsApi.createPosition('dept-001', req) + + expect(mockPost).toHaveBeenCalledWith('/departments/dept-001/positions', req) + }) + + it('updatePosition 应调用 PUT /positions/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { name: '主任医师', version: 1 } + await orgsApi.updatePosition('pos-001', req) + + expect(mockPut).toHaveBeenCalledWith('/positions/pos-001', req) + }) + + it('deletePosition 应调用 DELETE /positions/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await orgsApi.deletePosition('pos-001') + + expect(mockDelete).toHaveBeenCalledWith('/positions/pos-001') + }) +}) diff --git a/apps/web/src/api/orgs.ts b/apps/web/src/api/orgs.ts new file mode 100644 index 0000000..d70afc3 --- /dev/null +++ b/apps/web/src/api/orgs.ts @@ -0,0 +1,174 @@ +import client from './client'; + +// --- Organization types --- + +export interface OrganizationInfo { + id: string; + name: string; + code?: string; + parent_id?: string; + path?: string; + level: number; + sort_order: number; + children: OrganizationInfo[]; + version: number; +} + +export interface CreateOrganizationRequest { + name: string; + code?: string; + parent_id?: string; + sort_order?: number; +} + +export interface UpdateOrganizationRequest { + name?: string; + code?: string; + sort_order?: number; + version: number; +} + +// --- Department types --- + +export interface DepartmentInfo { + id: string; + org_id: string; + name: string; + code?: string; + parent_id?: string; + manager_id?: string; + path?: string; + sort_order: number; + children: DepartmentInfo[]; + version: number; +} + +export interface CreateDepartmentRequest { + name: string; + code?: string; + parent_id?: string; + manager_id?: string; + sort_order?: number; +} + +export interface UpdateDepartmentRequest { + name?: string; + code?: string; + manager_id?: string; + sort_order?: number; + version: number; +} + +// --- Position types --- + +export interface PositionInfo { + id: string; + dept_id: string; + name: string; + code?: string; + level: number; + sort_order: number; + version: number; +} + +export interface CreatePositionRequest { + name: string; + code?: string; + level?: number; + sort_order?: number; +} + +export interface UpdatePositionRequest { + name?: string; + code?: string; + level?: number; + sort_order?: number; + version: number; +} + +// --- Organization API --- + +export async function listOrgTree() { + const { data } = await client.get<{ success: boolean; data: OrganizationInfo[] }>( + '/organizations', + ); + return data.data; +} + +export async function createOrg(req: CreateOrganizationRequest) { + const { data } = await client.post<{ success: boolean; data: OrganizationInfo }>( + '/organizations', + req, + ); + return data.data; +} + +export async function updateOrg(id: string, req: UpdateOrganizationRequest) { + const { data } = await client.put<{ success: boolean; data: OrganizationInfo }>( + `/organizations/${id}`, + req, + ); + return data.data; +} + +export async function deleteOrg(id: string) { + await client.delete(`/organizations/${id}`); +} + +// --- Department API --- + +export async function listDeptTree(orgId: string) { + const { data } = await client.get<{ success: boolean; data: DepartmentInfo[] }>( + `/organizations/${orgId}/departments`, + ); + return data.data; +} + +export async function createDept(orgId: string, req: CreateDepartmentRequest) { + const { data } = await client.post<{ success: boolean; data: DepartmentInfo }>( + `/organizations/${orgId}/departments`, + req, + ); + return data.data; +} + +export async function deleteDept(id: string) { + await client.delete(`/departments/${id}`); +} + +export async function updateDept(id: string, req: UpdateDepartmentRequest) { + const { data } = await client.put<{ success: boolean; data: DepartmentInfo }>( + `/departments/${id}`, + req, + ); + return data.data; +} + +// --- Position API --- + +export async function listPositions(deptId: string) { + const { data } = await client.get<{ success: boolean; data: PositionInfo[] }>( + `/departments/${deptId}/positions`, + ); + return data.data; +} + +export async function createPosition(deptId: string, req: CreatePositionRequest) { + const { data } = await client.post<{ success: boolean; data: PositionInfo }>( + `/departments/${deptId}/positions`, + req, + ); + return data.data; +} + +export async function deletePosition(id: string) { + await client.delete(`/positions/${id}`); +} + +export async function updatePosition(id: string, req: UpdatePositionRequest) { + const { data } = await client.put<{ success: boolean; data: PositionInfo }>( + `/positions/${id}`, + req, + ); + return data.data; +} diff --git a/apps/web/src/api/pluginData.test.ts b/apps/web/src/api/pluginData.test.ts new file mode 100644 index 0000000..378cefc --- /dev/null +++ b/apps/web/src/api/pluginData.test.ts @@ -0,0 +1,131 @@ +/** + * pluginData API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockPatch = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + patch: (...args: unknown[]) => mockPatch(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as pluginDataApi from './pluginData' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('pluginData CRUD', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listPluginData 应调用 GET /plugins/:pid/:entity 并传递分页和过滤参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginDataApi.listPluginData('crm', 'customer', 1, 20, { + filter: { status: 'active' }, + search: '张', + sort_by: 'name', + sort_order: 'asc', + }) + + expect(mockGet).toHaveBeenCalledWith('/plugins/crm/customer', { + params: expect.objectContaining({ + page: '1', + page_size: '20', + search: '张', + sort_by: 'name', + sort_order: 'asc', + }), + }) + }) + + it('getPluginData 应调用 GET /plugins/:pid/:entity/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginDataApi.getPluginData('crm', 'customer', 'rec-001') + + expect(mockGet).toHaveBeenCalledWith('/plugins/crm/customer/rec-001') + }) + + it('createPluginData 应调用 POST /plugins/:pid/:entity 并包裹 data', async () => { + mockPost.mockResolvedValue(fakeRes) + const recordData = { name: '客户A', phone: '13800138000' } + await pluginDataApi.createPluginData('crm', 'customer', recordData) + + expect(mockPost).toHaveBeenCalledWith('/plugins/crm/customer', { data: recordData }) + }) + + it('updatePluginData 应调用 PUT /plugins/:pid/:entity/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const recordData = { name: '客户A(更新)' } + await pluginDataApi.updatePluginData('crm', 'customer', 'rec-001', recordData, 2) + + expect(mockPut).toHaveBeenCalledWith('/plugins/crm/customer/rec-001', { + data: recordData, + version: 2, + }) + }) + + it('deletePluginData 应调用 DELETE /plugins/:pid/:entity/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await pluginDataApi.deletePluginData('crm', 'customer', 'rec-001') + + expect(mockDelete).toHaveBeenCalledWith('/plugins/crm/customer/rec-001') + }) +}) + +describe('pluginData 高级查询', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('countPluginData 应调用 GET /plugins/:pid/:entity/count', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginDataApi.countPluginData('crm', 'customer', { filter: { status: 'active' } }) + + expect(mockGet).toHaveBeenCalledWith('/plugins/crm/customer/count', { + params: expect.objectContaining({ + filter: '{"status":"active"}', + }), + }) + }) + + it('aggregatePluginData 应调用 GET /plugins/:pid/:entity/aggregate', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginDataApi.aggregatePluginData('crm', 'customer', 'status') + + expect(mockGet).toHaveBeenCalledWith('/plugins/crm/customer/aggregate', { + params: { group_by: 'status' }, + }) + }) + + it('batchPluginData 应调用 POST /plugins/:pid/:entity/batch', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { action: 'delete', ids: ['rec-1', 'rec-2'] } + await pluginDataApi.batchPluginData('crm', 'customer', req) + + expect(mockPost).toHaveBeenCalledWith('/plugins/crm/customer/batch', req) + }) + + it('resolveRefLabels 应调用 POST /plugins/:pid/:entity/resolve-labels', async () => { + mockPost.mockResolvedValue(fakeRes) + const fields = { customer_tag_id: ['tag-1', 'tag-2'] } + await pluginDataApi.resolveRefLabels('crm', 'customer', fields) + + expect(mockPost).toHaveBeenCalledWith('/plugins/crm/customer/resolve-labels', { fields }) + }) + + it('importPluginData 应调用 POST /plugins/:pid/:entity/import', async () => { + mockPost.mockResolvedValue(fakeRes) + const rows = [{ name: '客户A' }, { name: '客户B' }] + await pluginDataApi.importPluginData('crm', 'customer', rows) + + expect(mockPost).toHaveBeenCalledWith('/plugins/crm/customer/import', { rows }) + }) +}) diff --git a/apps/web/src/api/pluginData.ts b/apps/web/src/api/pluginData.ts new file mode 100644 index 0000000..302d42f --- /dev/null +++ b/apps/web/src/api/pluginData.ts @@ -0,0 +1,281 @@ +import client from './client'; + +export interface PluginDataRecord { + id: string; + data: Record; + created_at?: string; + updated_at?: string; + version?: number; +} + +interface PaginatedDataResponse { + data: PluginDataRecord[]; + total: number; + page: number; + page_size: number; + total_pages: number; +} + +export interface PluginDataListOptions { + filter?: Record; + search?: string; + sort_by?: string; + sort_order?: 'asc' | 'desc'; +} + +export async function listPluginData( + pluginId: string, + entity: string, + page = 1, + pageSize = 20, + options?: PluginDataListOptions, +) { + const params: Record = { + page: String(page), + page_size: String(pageSize), + }; + if (options?.filter) params.filter = JSON.stringify(options.filter); + if (options?.search) params.search = options.search; + if (options?.sort_by) params.sort_by = options.sort_by; + if (options?.sort_order) params.sort_order = options.sort_order; + + const { data } = await client.get<{ success: boolean; data: PaginatedDataResponse }>( + `/plugins/${pluginId}/${entity}`, + { params }, + ); + return data.data; +} + +export async function getPluginData(pluginId: string, entity: string, id: string) { + const { data } = await client.get<{ success: boolean; data: PluginDataRecord }>( + `/plugins/${pluginId}/${entity}/${id}`, + ); + return data.data; +} + +export async function createPluginData( + pluginId: string, + entity: string, + recordData: Record, +) { + const { data } = await client.post<{ success: boolean; data: PluginDataRecord }>( + `/plugins/${pluginId}/${entity}`, + { data: recordData }, + ); + return data.data; +} + +export async function updatePluginData( + pluginId: string, + entity: string, + id: string, + recordData: Record, + version: number, +) { + const { data } = await client.put<{ success: boolean; data: PluginDataRecord }>( + `/plugins/${pluginId}/${entity}/${id}`, + { data: recordData, version }, + ); + return data.data; +} + +export async function deletePluginData( + pluginId: string, + entity: string, + id: string, +) { + await client.delete(`/plugins/${pluginId}/${entity}/${id}`); +} + +export async function countPluginData( + pluginId: string, + entity: string, + options?: { filter?: Record; search?: string }, +) { + const params: Record = {}; + if (options?.filter) params.filter = JSON.stringify(options.filter); + if (options?.search) params.search = options.search; + + const { data } = await client.get<{ success: boolean; data: number }>( + `/plugins/${pluginId}/${entity}/count`, + { params }, + ); + return data.data; +} + +export interface AggregateItem { + key: string; + count: number; +} + +export async function aggregatePluginData( + pluginId: string, + entity: string, + groupBy: string, + filter?: Record, +) { + const params: Record = { group_by: groupBy }; + if (filter) params.filter = JSON.stringify(filter); + + const { data } = await client.get<{ success: boolean; data: AggregateItem[] }>( + `/plugins/${pluginId}/${entity}/aggregate`, + { params }, + ); + return data.data; +} + +// ── 批量操作 ── + +export async function batchPluginData( + pluginId: string, + entity: string, + req: { action: string; ids: string[]; data?: Record }, +) { + const { data } = await client.post<{ success: boolean; data: unknown }>( + `/plugins/${pluginId}/${entity}/batch`, + req, + ); + return data.data; +} + +// ── 部分更新 ── + +export async function patchPluginData( + pluginId: string, + entity: string, + id: string, + req: { data: Record; version: number }, +) { + const { data } = await client.patch<{ success: boolean; data: PluginDataRecord }>( + `/plugins/${pluginId}/${entity}/${id}`, + req, + ); + return data.data; +} + +// ── 时间序列 ── + +export async function getPluginTimeseries( + pluginId: string, + entity: string, + params: { + time_field: string; + time_grain: string; + start?: string; + end?: string; + }, +) { + const { data } = await client.get<{ success: boolean; data: unknown }>( + `/plugins/${pluginId}/${entity}/timeseries`, + { params }, + ); + return data.data; +} + +// ─── 跨插件引用 API ────────────────────────────────────────────────── + +export interface ResolveLabelsResult { + labels: Record>; + meta: Record; +} + +export async function resolveRefLabels( + pluginId: string, + entity: string, + fields: Record, +): Promise { + const { data } = await client.post<{ success: boolean; data: ResolveLabelsResult }>( + `/plugins/${pluginId}/${entity}/resolve-labels`, + { fields }, + ); + return data.data; +} + +export interface PublicEntity { + manifest_id: string; + plugin_id: string; + entity_name: string; + display_name: string; +} + +export async function getPluginEntityRegistry(): Promise { + const { data } = await client.get<{ success: boolean; data: PublicEntity[] }>( + '/plugin-registry/entities', + ); + return data.data; +} + +// ─── 数据导入导出 API ────────────────────────────────────────────────── + +export interface ExportOptions { + filter?: Record; + search?: string; + sort_by?: string; + sort_order?: 'asc' | 'desc'; + format?: 'json' | 'csv' | 'xlsx'; +} + +export async function exportPluginData( + pluginId: string, + entity: string, + options?: ExportOptions, +): Promise[]> { + const params: Record = {}; + if (options?.filter) params.filter = JSON.stringify(options.filter); + if (options?.search) params.search = options.search; + if (options?.sort_by) params.sort_by = options.sort_by; + if (options?.sort_order) params.sort_order = options.sort_order; + + const { data } = await client.get<{ success: boolean; data: Record[] }>( + `/plugins/${pluginId}/${entity}/export`, + { params }, + ); + return data.data; +} + +export async function exportPluginDataAsBlob( + pluginId: string, + entity: string, + format: 'csv' | 'xlsx', + options?: Omit, +): Promise { + const params: Record = { format }; + if (options?.filter) params.filter = JSON.stringify(options.filter); + if (options?.search) params.search = options.search; + if (options?.sort_by) params.sort_by = options.sort_by; + if (options?.sort_order) params.sort_order = options.sort_order; + + const response = await client.get( + `/plugins/${pluginId}/${entity}/export`, + { params, responseType: 'blob' }, + ); + return response.data as Blob; +} + +export interface ImportRowError { + row: number; + errors: string[]; +} + +export interface ImportResult { + success_count: number; + error_count: number; + errors: ImportRowError[]; +} + +export async function importPluginData( + pluginId: string, + entity: string, + rows: Record[], +): Promise { + const { data } = await client.post<{ success: boolean; data: ImportResult }>( + `/plugins/${pluginId}/${entity}/import`, + { rows }, + ); + return data.data; +} diff --git a/apps/web/src/api/plugins.test.ts b/apps/web/src/api/plugins.test.ts new file mode 100644 index 0000000..cd5b6e8 --- /dev/null +++ b/apps/web/src/api/plugins.test.ts @@ -0,0 +1,132 @@ +/** + * plugins API 契约测试(插件管理 + 市场) + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as pluginsApi from './plugins' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('plugins management API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listPlugins 应调用 GET /admin/plugins 并传递分页和状态', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.listPlugins(1, 10, 'enabled') + + expect(mockGet).toHaveBeenCalledWith('/admin/plugins', { + params: { page: 1, page_size: 10, status: 'enabled' }, + }) + }) + + it('getPlugin 应调用 GET /admin/plugins/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.getPlugin('plug-001') + + expect(mockGet).toHaveBeenCalledWith('/admin/plugins/plug-001') + }) + + it('installPlugin 应调用 POST /admin/plugins/:id/install', async () => { + mockPost.mockResolvedValue(fakeRes) + await pluginsApi.installPlugin('plug-001') + + expect(mockPost).toHaveBeenCalledWith('/admin/plugins/plug-001/install') + }) + + it('enablePlugin 应调用 POST /admin/plugins/:id/enable', async () => { + mockPost.mockResolvedValue(fakeRes) + await pluginsApi.enablePlugin('plug-001') + + expect(mockPost).toHaveBeenCalledWith('/admin/plugins/plug-001/enable') + }) + + it('disablePlugin 应调用 POST /admin/plugins/:id/disable', async () => { + mockPost.mockResolvedValue(fakeRes) + await pluginsApi.disablePlugin('plug-001') + + expect(mockPost).toHaveBeenCalledWith('/admin/plugins/plug-001/disable') + }) + + it('purgePlugin 应调用 DELETE /admin/plugins/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await pluginsApi.purgePlugin('plug-001') + + expect(mockDelete).toHaveBeenCalledWith('/admin/plugins/plug-001') + }) + + it('getPluginHealth 应调用 GET /admin/plugins/:id/health', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.getPluginHealth('plug-001') + + expect(mockGet).toHaveBeenCalledWith('/admin/plugins/plug-001/health') + }) + + it('updatePluginConfig 应调用 PUT /admin/plugins/:id/config', async () => { + mockPut.mockResolvedValue(fakeRes) + const config = { theme: 'dark' } + await pluginsApi.updatePluginConfig('plug-001', config, 1) + + expect(mockPut).toHaveBeenCalledWith('/admin/plugins/plug-001/config', { + config, + version: 1, + }) + }) + + it('getPluginSchema 应调用 GET /admin/plugins/:id/schema', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.getPluginSchema('plug-001') + + expect(mockGet).toHaveBeenCalledWith('/admin/plugins/plug-001/schema') + }) +}) + +describe('plugin market API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listMarketEntries 应调用 GET /market/entries', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.listMarketEntries({ page: 1, page_size: 10, category: 'crm' }) + + expect(mockGet).toHaveBeenCalledWith('/market/entries', { + params: { page: 1, page_size: 10, category: 'crm' }, + }) + }) + + it('getMarketEntry 应调用 GET /market/entries/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await pluginsApi.getMarketEntry('mkt-001') + + expect(mockGet).toHaveBeenCalledWith('/market/entries/mkt-001') + }) + + it('installFromMarket 应调用 POST /market/entries/:id/install', async () => { + mockPost.mockResolvedValue(fakeRes) + await pluginsApi.installFromMarket('mkt-001') + + expect(mockPost).toHaveBeenCalledWith('/market/entries/mkt-001/install') + }) + + it('submitMarketReview 应调用 POST /market/entries/:id/reviews', async () => { + mockPost.mockResolvedValue(fakeRes) + const review = { rating: 5, review_text: '很好用' } + await pluginsApi.submitMarketReview('mkt-001', review) + + expect(mockPost).toHaveBeenCalledWith('/market/entries/mkt-001/reviews', review) + }) +}) diff --git a/apps/web/src/api/plugins.ts b/apps/web/src/api/plugins.ts new file mode 100644 index 0000000..8450082 --- /dev/null +++ b/apps/web/src/api/plugins.ts @@ -0,0 +1,375 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface PluginEntityInfo { + name: string; + display_name: string; + table_name: string; +} + +export interface PluginPermissionInfo { + code: string; + name: string; + description: string; +} + +export type PluginStatus = 'uploaded' | 'installed' | 'enabled' | 'running' | 'disabled' | 'uninstalled'; + +export interface PluginInfo { + id: string; + name: string; + version: string; + description?: string; + author?: string; + status: PluginStatus; + config: Record; + installed_at?: string; + enabled_at?: string; + entities: PluginEntityInfo[]; + permissions?: PluginPermissionInfo[]; + record_version: number; +} + +export async function listPlugins(page = 1, pageSize = 20, status?: string) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/admin/plugins', + { params: { page, page_size: pageSize, status: status || undefined } }, + ); + return data.data; +} + +export async function getPlugin(id: string) { + const { data } = await client.get<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}`, + ); + return data.data; +} + +export async function uploadPlugin(wasmFile: File, manifestToml: string) { + const formData = new FormData(); + formData.append('wasm', wasmFile); + formData.append('manifest', manifestToml); + + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + '/admin/plugins/upload', + formData, + { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 60000 }, + ); + return data.data; +} + +export async function installPlugin(id: string) { + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}/install`, + ); + return data.data; +} + +export async function enablePlugin(id: string) { + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}/enable`, + ); + return data.data; +} + +export async function disablePlugin(id: string) { + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}/disable`, + ); + return data.data; +} + +export async function uninstallPlugin(id: string) { + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}/uninstall`, + ); + return data.data; +} + +export async function purgePlugin(id: string) { + await client.delete(`/admin/plugins/${id}`); +} + +export async function getPluginHealth(id: string) { + const { data } = await client.get<{ + success: boolean; + data: { plugin_id: string; status: string; details: Record }; + }>(`/admin/plugins/${id}/health`); + return data.data; +} + +export async function updatePluginConfig(id: string, config: Record, version: number) { + const { data } = await client.put<{ success: boolean; data: PluginInfo }>( + `/admin/plugins/${id}/config`, + { config, version }, + ); + return data.data; +} + +export async function getPluginSchema(id: string): Promise { + const { data } = await client.get<{ success: boolean; data: PluginSchemaResponse }>( + `/admin/plugins/${id}/schema`, + ); + return data.data; +} + +// ── Schema 类型定义 ── + +export interface PluginFieldValidation { + pattern?: string; + message?: string; + min_length?: number; + max_length?: number; + min_value?: number; + max_value?: number; +} + +export interface PluginFieldSchema { + name: string; + field_type: string; + required: boolean; + display_name?: string; + ui_widget?: string; + options?: { label: string; value: string }[]; + searchable?: boolean; + filterable?: boolean; + sortable?: boolean; + visible_when?: string; + unique?: boolean; + ref_entity?: string; + ref_label_field?: string; + ref_search_fields?: string[]; + ref_plugin?: string; + ref_fallback_label?: string; + cascade_from?: string; + cascade_filter?: string; + validation?: PluginFieldValidation; +} + +export interface PluginRelationSchema { + entity: string; + foreign_key: string; + on_delete: 'cascade' | 'nullify' | 'restrict'; + name?: string; + type?: 'one_to_many' | 'many_to_one' | 'many_to_many'; + display_field?: string; +} + +export interface PluginEntitySchema { + name: string; + display_name: string; + fields: PluginFieldSchema[]; + relations?: PluginRelationSchema[]; + data_scope?: boolean; + is_public?: boolean; + importable?: boolean; + exportable?: boolean; +} + +export interface PluginSchemaResponse { + entities: PluginEntitySchema[]; + ui?: PluginUiSchema; + settings?: PluginSettings; + numbering?: PluginNumbering[]; + trigger_events?: PluginTriggerEvent[]; +} + +export interface PluginUiSchema { + pages: PluginPageSchema[]; +} + +export type PluginPageSchema = + | { type: 'crud'; entity: string; label: string; icon?: string; enable_search?: boolean; enable_views?: string[] } + | { type: 'tree'; entity: string; label: string; icon?: string; id_field: string; parent_field: string; label_field: string } + | { type: 'detail'; entity: string; label: string; sections: PluginSectionSchema[] } + | { type: 'tabs'; label: string; icon?: string; tabs: PluginPageSchema[] } + | { type: 'graph'; entity: string; label: string; relationship_entity: string; source_field: string; target_field: string; edge_label_field: string; node_label_field: string } + | { type: 'dashboard'; label: string; widgets?: DashboardWidget[] } + | { + type: 'kanban'; + entity: string; + label: string; + icon?: string; + lane_field: string; + lane_order?: string[]; + card_title_field: string; + card_subtitle_field?: string; + card_fields?: string[]; + enable_drag?: boolean; + }; + +export interface DashboardWidget { + type: 'stat_card' | 'bar_chart' | 'pie_chart' | 'funnel_chart' | 'line_chart' + | 'stat_cards' | 'action_list' | 'funnel' | 'card_list'; + entity: string; + title: string; + icon?: string; + color?: string; + dimension_field?: string; + dimension_order?: string[]; + metric?: string; + // stat_cards + cards?: StatCardDef[]; + // action_list + max_items?: number; + queries?: ActionQueryDef[]; + // funnel + lane_field?: string; + value_field?: string; + lane_order?: string[]; + // card_list + filter?: string; + title_field?: string; + subtitle_field?: string; + tags?: string[]; + label?: string; + label_field?: string; + action?: string; + sort?: string; +} + +export interface StatCardDef { + entity: string; + aggregate?: string; + field?: string; + filter?: string; + label: string; + icon?: string; + color?: string; +} + +export interface ActionQueryDef { + entity: string; + filter?: string; + sort?: string; + label_field: string; + subtitle_field?: string; + action: string; + icon?: string; +} + +export type PluginSectionSchema = + | { type: 'fields'; label: string; fields: string[] } + | { type: 'crud'; label: string; entity: string; filter_field?: string; enable_views?: string[] }; + +// ── P2 平台通用服务 — Settings 类型 ── + +export type PluginSettingType = + | 'text' | 'number' | 'boolean' | 'select' | 'multiselect' + | 'color' | 'date' | 'datetime' | 'json'; + +export interface PluginSettingField { + name: string; + display_name: string; + field_type: PluginSettingType; + default_value?: unknown; + required: boolean; + description?: string; + options?: { label: string; value: string }[]; + range?: [number, number]; + group?: string; +} + +export interface PluginSettings { + fields: PluginSettingField[]; +} + +// ── P2 平台通用服务 — Numbering 类型 ── + +export interface PluginNumbering { + entity: string; + field: string; + prefix: string; + format: string; + reset_rule: 'never' | 'daily' | 'monthly' | 'yearly'; + seq_length: number; + separator?: string; +} + +// ── P2 平台通用服务 — TriggerEvent 类型 ── + +export interface PluginTriggerEvent { + name: string; + display_name: string; + description: string; + entity: string; + on: 'create' | 'update' | 'delete' | 'create_or_update'; +} + +// ── 插件市场 API ── + +export interface MarketEntry { + id: string; + plugin_id: string; + name: string; + version: string; + description?: string; + author?: string; + category?: string; + tags?: string[]; + icon_url?: string; + screenshots?: string[]; + min_platform_version?: string; + status: string; + download_count: number; + rating_avg: number; + rating_count: number; + changelog?: string; + created_at?: string; + updated_at?: string; +} + +export interface MarketEntryDetail extends MarketEntry { + dependency_warnings: string[]; +} + +export interface MarketReview { + id: string; + user_id: string; + market_entry_id: string; + rating: number; + review_text?: string; + created_at?: string; +} + +export async function listMarketEntries(params?: { + page?: number; + page_size?: number; + category?: string; + search?: string; +}) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/market/entries', + { params }, + ); + return data.data; +} + +export async function getMarketEntry(id: string) { + const { data } = await client.get<{ success: boolean; data: MarketEntryDetail }>( + `/market/entries/${id}`, + ); + return data.data; +} + +export async function installFromMarket(id: string) { + const { data } = await client.post<{ success: boolean; data: PluginInfo }>( + `/market/entries/${id}/install`, + ); + return data.data; +} + +export async function listMarketReviews(id: string) { + const { data } = await client.get<{ success: boolean; data: MarketReview[] }>( + `/market/entries/${id}/reviews`, + ); + return data.data; +} + +export async function submitMarketReview(id: string, review: { rating: number; review_text?: string }) { + const { data } = await client.post<{ success: boolean; data: MarketReview }>( + `/market/entries/${id}/reviews`, + review, + ); + return data.data; +} diff --git a/apps/web/src/api/roles.test.ts b/apps/web/src/api/roles.test.ts new file mode 100644 index 0000000..ce58021 --- /dev/null +++ b/apps/web/src/api/roles.test.ts @@ -0,0 +1,86 @@ +/** + * roles API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as rolesApi from './roles' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('roles API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listRoles 应调用 GET /roles 并传递分页参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await rolesApi.listRoles(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/roles', { params: { page: 1, page_size: 10 } }) + }) + + it('getRole 应调用 GET /roles/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await rolesApi.getRole('r-001') + + expect(mockGet).toHaveBeenCalledWith('/roles/r-001') + }) + + it('createRole 应调用 POST /roles', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '医生', code: 'doctor', description: '医生角色' } + await rolesApi.createRole(req) + + expect(mockPost).toHaveBeenCalledWith('/roles', req) + }) + + it('updateRole 应调用 PUT /roles/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { name: '高级医生', version: 1 } + await rolesApi.updateRole('r-001', req) + + expect(mockPut).toHaveBeenCalledWith('/roles/r-001', req) + }) + + it('deleteRole 应调用 DELETE /roles/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await rolesApi.deleteRole('r-001') + + expect(mockDelete).toHaveBeenCalledWith('/roles/r-001') + }) + + it('assignPermissions 应调用 POST /roles/:id/permissions', async () => { + mockPost.mockResolvedValue(undefined) + await rolesApi.assignPermissions('r-001', ['p-1', 'p-2']) + + expect(mockPost).toHaveBeenCalledWith('/roles/r-001/permissions', { permission_ids: ['p-1', 'p-2'] }) + }) + + it('getRolePermissions 应调用 GET /roles/:id/permissions', async () => { + mockGet.mockResolvedValue(fakeRes) + await rolesApi.getRolePermissions('r-001') + + expect(mockGet).toHaveBeenCalledWith('/roles/r-001/permissions') + }) + + it('listPermissions 应调用 GET /permissions', async () => { + mockGet.mockResolvedValue(fakeRes) + await rolesApi.listPermissions() + + expect(mockGet).toHaveBeenCalledWith('/permissions') + }) +}) diff --git a/apps/web/src/api/roles.ts b/apps/web/src/api/roles.ts new file mode 100644 index 0000000..d1fd2cb --- /dev/null +++ b/apps/web/src/api/roles.ts @@ -0,0 +1,75 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface RoleInfo { + id: string; + name: string; + code: string; + description?: string; + is_system: boolean; + version: number; +} + +export interface PermissionInfo { + id: string; + code: string; + name: string; + resource: string; + action: string; + description?: string; +} + +export interface CreateRoleRequest { + name: string; + code: string; + description?: string; +} + +export interface UpdateRoleRequest { + name?: string; + description?: string; + version: number; +} + +export async function listRoles(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/roles', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function getRole(id: string) { + const { data } = await client.get<{ success: boolean; data: RoleInfo }>(`/roles/${id}`); + return data.data; +} + +export async function createRole(req: CreateRoleRequest) { + const { data } = await client.post<{ success: boolean; data: RoleInfo }>('/roles', req); + return data.data; +} + +export async function updateRole(id: string, req: UpdateRoleRequest) { + const { data } = await client.put<{ success: boolean; data: RoleInfo }>(`/roles/${id}`, req); + return data.data; +} + +export async function deleteRole(id: string) { + await client.delete(`/roles/${id}`); +} + +export async function assignPermissions(roleId: string, permissionIds: string[]) { + await client.post(`/roles/${roleId}/permissions`, { permission_ids: permissionIds }); +} + +export async function getRolePermissions(roleId: string) { + const { data } = await client.get<{ success: boolean; data: PermissionInfo[] }>( + `/roles/${roleId}/permissions`, + ); + return data.data; +} + +export async function listPermissions() { + const { data } = await client.get<{ success: boolean; data: PermissionInfo[] }>('/permissions'); + return data.data; +} diff --git a/apps/web/src/api/settings.ts b/apps/web/src/api/settings.ts new file mode 100644 index 0000000..82539bc --- /dev/null +++ b/apps/web/src/api/settings.ts @@ -0,0 +1,30 @@ +import client from './client'; + +export interface SettingInfo { + id: string; + scope: string; + scope_id?: string; + setting_key: string; + setting_value: unknown; + version: number; +} + +export async function getSetting(key: string, scope?: string, scopeId?: string) { + const { data } = await client.get<{ success: boolean; data: SettingInfo }>( + `/config/settings/${encodeURIComponent(key)}`, + { params: { scope, scope_id: scopeId } }, + ); + return data.data; +} + +export async function updateSetting(key: string, settingValue: unknown, version?: number) { + const { data } = await client.put<{ success: boolean; data: SettingInfo }>( + `/config/settings/${encodeURIComponent(key)}`, + { setting_value: settingValue, version }, + ); + return data.data; +} + +export async function deleteSetting(key: string, version: number) { + await client.delete(`/config/settings/${encodeURIComponent(key)}`, { data: { version } }); +} diff --git a/apps/web/src/api/themes.ts b/apps/web/src/api/themes.ts new file mode 100644 index 0000000..4ab075e --- /dev/null +++ b/apps/web/src/api/themes.ts @@ -0,0 +1,49 @@ +import client from './client'; + +export interface ThemeConfig { + primary_color?: string; + logo_url?: string; + sidebar_style?: 'light' | 'dark'; + brand_name?: string; + brand_slogan?: string; + brand_features?: string; + brand_copyright?: string; +} + +export async function getTheme() { + const { data } = await client.get<{ success: boolean; data: ThemeConfig }>( + '/config/themes', + ); + return data.data; +} + +export async function updateTheme(theme: ThemeConfig) { + const { data } = await client.put<{ success: boolean; data: ThemeConfig }>( + '/config/themes', + theme, + ); + return data.data; +} + +export interface BrandConfig { + brand_name: string; + brand_slogan: string; + brand_features: string; + brand_copyright: string; +} + +const BRAND_DEFAULTS: BrandConfig = { + brand_name: 'HMS 健康管理平台', + brand_slogan: '新一代健康管理平台', + brand_features: '患者管理 · 健康监测 · 随访管理 · AI 智能分析', + brand_copyright: 'HMS 健康管理平台 · ©汕头市智界科技有限公司', +}; + +export async function getPublicBrand(): Promise { + try { + const res = await fetch('/api/v1/public/brand'); + const json = await res.json(); + if (json?.success && json?.data) return json.data; + } catch {} + return BRAND_DEFAULTS; +} diff --git a/apps/web/src/api/types.ts b/apps/web/src/api/types.ts new file mode 100644 index 0000000..8605eae --- /dev/null +++ b/apps/web/src/api/types.ts @@ -0,0 +1,7 @@ +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + page_size: number; + total_pages: number; +} diff --git a/apps/web/src/api/upload.ts b/apps/web/src/api/upload.ts new file mode 100644 index 0000000..1aa99ce --- /dev/null +++ b/apps/web/src/api/upload.ts @@ -0,0 +1,16 @@ +import client from './client'; + +export interface UploadResult { + url: string; + filename?: string; + size?: number; +} + +export async function uploadFile(file: File): Promise { + const formData = new FormData(); + formData.append('file', file); + const { data: result } = await client.post('/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + return result.data; +} diff --git a/apps/web/src/api/users.test.ts b/apps/web/src/api/users.test.ts new file mode 100644 index 0000000..31f86b1 --- /dev/null +++ b/apps/web/src/api/users.test.ts @@ -0,0 +1,83 @@ +/** + * users API 契约测试 + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as usersApi from './users' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('users API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listUsers 应调用 GET /users 并传递分页和搜索参数', async () => { + mockGet.mockResolvedValue(fakeRes) + await usersApi.listUsers(2, 10, '张') + + expect(mockGet).toHaveBeenCalledWith('/users', { + params: { page: 2, page_size: 10, search: '张' }, + }) + }) + + it('listUsers 空搜索时应传 search 为 undefined', async () => { + mockGet.mockResolvedValue(fakeRes) + await usersApi.listUsers(1, 20, '') + + expect(mockGet).toHaveBeenCalledWith('/users', { + params: { page: 1, page_size: 20, search: undefined }, + }) + }) + + it('getUser 应调用 GET /users/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await usersApi.getUser('u-001') + + expect(mockGet).toHaveBeenCalledWith('/users/u-001') + }) + + it('createUser 应调用 POST /users', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { username: 'newuser', password: 'pass123', display_name: '新用户' } + await usersApi.createUser(req) + + expect(mockPost).toHaveBeenCalledWith('/users', req) + }) + + it('updateUser 应调用 PUT /users/:id', async () => { + mockPut.mockResolvedValue(fakeRes) + const req = { display_name: '改名', version: 1 } + await usersApi.updateUser('u-001', req) + + expect(mockPut).toHaveBeenCalledWith('/users/u-001', req) + }) + + it('deleteUser 应调用 DELETE /users/:id', async () => { + mockDelete.mockResolvedValue(undefined) + await usersApi.deleteUser('u-001') + + expect(mockDelete).toHaveBeenCalledWith('/users/u-001') + }) + + it('assignRoles 应调用 POST /users/:id/roles', async () => { + mockPost.mockResolvedValue(undefined) + await usersApi.assignRoles('u-001', ['role-1', 'role-2']) + + expect(mockPost).toHaveBeenCalledWith('/users/u-001/roles', { role_ids: ['role-1', 'role-2'] }) + }) +}) diff --git a/apps/web/src/api/users.ts b/apps/web/src/api/users.ts new file mode 100644 index 0000000..64b93c6 --- /dev/null +++ b/apps/web/src/api/users.ts @@ -0,0 +1,54 @@ +import client from './client'; +import type { UserInfo } from './auth'; +import type { PaginatedResponse } from './types'; + +export interface CreateUserRequest { + username: string; + password: string; + email?: string; + phone?: string; + display_name?: string; +} + +export interface UpdateUserRequest { + email?: string; + phone?: string; + display_name?: string; + status?: string; + version: number; +} + +export async function listUsers(page = 1, pageSize = 20, search = '') { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/users', + { params: { page, page_size: pageSize, search: search || undefined } } + ); + return data.data; +} + +export async function getUser(id: string) { + const { data } = await client.get<{ success: boolean; data: UserInfo }>(`/users/${id}`); + return data.data; +} + +export async function createUser(req: CreateUserRequest) { + const { data } = await client.post<{ success: boolean; data: UserInfo }>('/users', req); + return data.data; +} + +export async function updateUser(id: string, req: UpdateUserRequest) { + const { data } = await client.put<{ success: boolean; data: UserInfo }>(`/users/${id}`, req); + return data.data; +} + +export async function deleteUser(id: string) { + await client.delete(`/users/${id}`); +} + +export async function assignRoles(userId: string, roleIds: string[]) { + await client.post(`/users/${userId}/roles`, { role_ids: roleIds }); +} + +export async function resetPassword(id: string, req: { new_password: string; version: number }) { + await client.post(`/users/${id}/reset-password`, req); +} diff --git a/apps/web/src/api/workflow.test.ts b/apps/web/src/api/workflow.test.ts new file mode 100644 index 0000000..d2c262a --- /dev/null +++ b/apps/web/src/api/workflow.test.ts @@ -0,0 +1,141 @@ +/** + * workflow API 契约测试(definitions + instances + tasks) + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('./client', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args), + }, +})) + +import * as defApi from './workflowDefinitions' +import * as instApi from './workflowInstances' +import * as taskApi from './workflowTasks' + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('workflowDefinitions API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listProcessDefinitions 应调用 GET /workflow/definitions', async () => { + mockGet.mockResolvedValue(fakeRes) + await defApi.listProcessDefinitions(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/workflow/definitions', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('getProcessDefinition 应调用 GET /workflow/definitions/:id', async () => { + mockGet.mockResolvedValue(fakeRes) + await defApi.getProcessDefinition('wf-001') + + expect(mockGet).toHaveBeenCalledWith('/workflow/definitions/wf-001') + }) + + it('createProcessDefinition 应调用 POST /workflow/definitions', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { name: '审批流程', key: 'approval', nodes: [], edges: [] } + await defApi.createProcessDefinition(req) + + expect(mockPost).toHaveBeenCalledWith('/workflow/definitions', req) + }) + + it('publishProcessDefinition 应调用 POST /workflow/definitions/:id/publish', async () => { + mockPost.mockResolvedValue(fakeRes) + await defApi.publishProcessDefinition('wf-001') + + expect(mockPost).toHaveBeenCalledWith('/workflow/definitions/wf-001/publish') + }) +}) + +describe('workflowInstances API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('startInstance 应调用 POST /workflow/instances', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { definition_id: 'wf-001', business_key: 'BIZ-001' } + await instApi.startInstance(req) + + expect(mockPost).toHaveBeenCalledWith('/workflow/instances', req) + }) + + it('listInstances 应调用 GET /workflow/instances 并传递分页', async () => { + mockGet.mockResolvedValue(fakeRes) + await instApi.listInstances(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/workflow/instances', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('suspendInstance 应调用 POST /workflow/instances/:id/suspend', async () => { + mockPost.mockResolvedValue(fakeRes) + await instApi.suspendInstance('inst-001') + + expect(mockPost).toHaveBeenCalledWith('/workflow/instances/inst-001/suspend') + }) + + it('resumeInstance 应调用 POST /workflow/instances/:id/resume', async () => { + mockPost.mockResolvedValue(fakeRes) + await instApi.resumeInstance('inst-001') + + expect(mockPost).toHaveBeenCalledWith('/workflow/instances/inst-001/resume') + }) + + it('terminateInstance 应调用 POST /workflow/instances/:id/terminate', async () => { + mockPost.mockResolvedValue(fakeRes) + await instApi.terminateInstance('inst-001') + + expect(mockPost).toHaveBeenCalledWith('/workflow/instances/inst-001/terminate') + }) +}) + +describe('workflowTasks API', () => { + const fakeRes = { data: { success: true, data: {} } } + + it('listPendingTasks 应调用 GET /workflow/tasks/pending', async () => { + mockGet.mockResolvedValue(fakeRes) + await taskApi.listPendingTasks(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/workflow/tasks/pending', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('listCompletedTasks 应调用 GET /workflow/tasks/completed', async () => { + mockGet.mockResolvedValue(fakeRes) + await taskApi.listCompletedTasks(1, 10) + + expect(mockGet).toHaveBeenCalledWith('/workflow/tasks/completed', { + params: { page: 1, page_size: 10 }, + }) + }) + + it('completeTask 应调用 POST /workflow/tasks/:id/complete', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { outcome: 'approved', form_data: { comment: '同意' } } + await taskApi.completeTask('task-001', req) + + expect(mockPost).toHaveBeenCalledWith('/workflow/tasks/task-001/complete', req) + }) + + it('delegateTask 应调用 POST /workflow/tasks/:id/delegate', async () => { + mockPost.mockResolvedValue(fakeRes) + const req = { delegate_to: 'u-002' } + await taskApi.delegateTask('task-001', req) + + expect(mockPost).toHaveBeenCalledWith('/workflow/tasks/task-001/delegate', req) + }) +}) diff --git a/apps/web/src/api/workflowDefinitions.ts b/apps/web/src/api/workflowDefinitions.ts new file mode 100644 index 0000000..4490fd3 --- /dev/null +++ b/apps/web/src/api/workflowDefinitions.ts @@ -0,0 +1,90 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface NodeDef { + id: string; + type: 'StartEvent' | 'EndEvent' | 'UserTask' | 'ServiceTask' | 'ExclusiveGateway' | 'ParallelGateway'; + name: string; + assignee_id?: string; + candidate_groups?: string[]; + service_type?: string; + position?: { x: number; y: number }; +} + +export interface EdgeDef { + id: string; + source: string; + target: string; + condition?: string; + label?: string; +} + +export interface ProcessDefinitionInfo { + id: string; + name: string; + key: string; + version: number; + category?: string; + description?: string; + nodes: NodeDef[]; + edges: EdgeDef[]; + status: string; + created_at: string; + updated_at: string; +} + +export interface CreateProcessDefinitionRequest { + name: string; + key: string; + category?: string; + description?: string; + nodes: NodeDef[]; + edges: EdgeDef[]; +} + +export interface UpdateProcessDefinitionRequest { + name?: string; + category?: string; + description?: string; + nodes?: NodeDef[]; + edges?: EdgeDef[]; + version: number; +} + +export async function listProcessDefinitions(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/workflow/definitions', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function getProcessDefinition(id: string) { + const { data } = await client.get<{ success: boolean; data: ProcessDefinitionInfo }>( + `/workflow/definitions/${id}`, + ); + return data.data; +} + +export async function createProcessDefinition(req: CreateProcessDefinitionRequest) { + const { data } = await client.post<{ success: boolean; data: ProcessDefinitionInfo }>( + '/workflow/definitions', + req, + ); + return data.data; +} + +export async function updateProcessDefinition(id: string, req: UpdateProcessDefinitionRequest) { + const { data } = await client.put<{ success: boolean; data: ProcessDefinitionInfo }>( + `/workflow/definitions/${id}`, + req, + ); + return data.data; +} + +export async function publishProcessDefinition(id: string) { + const { data } = await client.post<{ success: boolean; data: ProcessDefinitionInfo }>( + `/workflow/definitions/${id}/publish`, + ); + return data.data; +} diff --git a/apps/web/src/api/workflowInstances.ts b/apps/web/src/api/workflowInstances.ts new file mode 100644 index 0000000..7b99f47 --- /dev/null +++ b/apps/web/src/api/workflowInstances.ts @@ -0,0 +1,72 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface TokenInfo { + id: string; + node_id: string; + status: string; + created_at: string; +} + +export interface ProcessInstanceInfo { + id: string; + definition_id: string; + definition_name?: string; + business_key?: string; + status: string; + started_by: string; + started_at: string; + completed_at?: string; + created_at: string; + active_tokens: TokenInfo[]; +} + +export interface StartInstanceRequest { + definition_id: string; + business_key?: string; + variables?: Array<{ name: string; var_type?: string; value: unknown }>; +} + +export async function startInstance(req: StartInstanceRequest) { + const { data } = await client.post<{ success: boolean; data: ProcessInstanceInfo }>( + '/workflow/instances', + req, + ); + return data.data; +} + +export async function listInstances(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/workflow/instances', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function getInstance(id: string) { + const { data } = await client.get<{ success: boolean; data: ProcessInstanceInfo }>( + `/workflow/instances/${id}`, + ); + return data.data; +} + +export async function suspendInstance(id: string) { + const { data } = await client.post<{ success: boolean; data: null }>( + `/workflow/instances/${id}/suspend`, + ); + return data.data; +} + +export async function resumeInstance(id: string) { + const { data } = await client.post<{ success: boolean; data: null }>( + `/workflow/instances/${id}/resume`, + ); + return data.data; +} + +export async function terminateInstance(id: string) { + const { data } = await client.post<{ success: boolean; data: null }>( + `/workflow/instances/${id}/terminate`, + ); + return data.data; +} diff --git a/apps/web/src/api/workflowTasks.ts b/apps/web/src/api/workflowTasks.ts new file mode 100644 index 0000000..c5b47cb --- /dev/null +++ b/apps/web/src/api/workflowTasks.ts @@ -0,0 +1,61 @@ +import client from './client'; +import type { PaginatedResponse } from './types'; + +export interface TaskInfo { + id: string; + instance_id: string; + token_id: string; + node_id: string; + node_name?: string; + assignee_id?: string; + candidate_groups?: unknown; + status: string; + outcome?: string; + form_data?: unknown; + due_date?: string; + completed_at?: string; + created_at: string; + definition_name?: string; + business_key?: string; +} + +export interface CompleteTaskRequest { + outcome: string; + form_data?: Record; +} + +export interface DelegateTaskRequest { + delegate_to: string; +} + +export async function listPendingTasks(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/workflow/tasks/pending', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function listCompletedTasks(page = 1, pageSize = 20) { + const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( + '/workflow/tasks/completed', + { params: { page, page_size: pageSize } }, + ); + return data.data; +} + +export async function completeTask(id: string, req: CompleteTaskRequest) { + const { data } = await client.post<{ success: boolean; data: TaskInfo }>( + `/workflow/tasks/${id}/complete`, + req, + ); + return data.data; +} + +export async function delegateTask(id: string, req: DelegateTaskRequest) { + const { data } = await client.post<{ success: boolean; data: TaskInfo }>( + `/workflow/tasks/${id}/delegate`, + req, + ); + return data.data; +} diff --git a/apps/web/src/components/AuthButton.tsx b/apps/web/src/components/AuthButton.tsx new file mode 100644 index 0000000..31b9c81 --- /dev/null +++ b/apps/web/src/components/AuthButton.tsx @@ -0,0 +1,13 @@ +import type { ReactNode } from 'react'; +import { usePermission } from '../hooks/usePermission'; + +interface AuthButtonProps { + code: string; + children: ReactNode; +} + +export function AuthButton({ code, children }: AuthButtonProps) { + const { hasPermission } = usePermission(code); + if (!hasPermission) return null; + return <>{children}; +} diff --git a/apps/web/src/components/DrawerForm.tsx b/apps/web/src/components/DrawerForm.tsx new file mode 100644 index 0000000..dbb65cd --- /dev/null +++ b/apps/web/src/components/DrawerForm.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { Drawer, Form, Typography, Divider, Button, Space } from 'antd'; +import { useThemeMode } from '../hooks/useThemeMode'; + +export interface FormSection { + title: string; + fields: React.ReactNode; + defaultCollapsed?: boolean; +} + +interface DrawerFormProps { + title: string; + open: boolean; + onClose: () => void; + onSubmit: (values: Record) => Promise; + initialValues?: Record; + loading?: boolean; + width?: number | string; + sections?: FormSection[]; + children?: React.ReactNode; + columns?: 1 | 2; + form?: ReturnType[0]; + onValuesChange?: (changedValues: Record, allValues: Record) => void; +} + +export function DrawerForm({ + title, + open, + onClose, + onSubmit, + initialValues, + loading, + width = 640, + sections, + children, + columns = 2, + form: externalForm, + onValuesChange, +}: DrawerFormProps) { + const [internalForm] = Form.useForm(); + const form = externalForm ?? internalForm; + const isDark = useThemeMode(); + + React.useEffect(() => { + if (open) { + form.resetFields(); + if (initialValues) { + form.setFieldsValue(initialValues); + } + } + }, [open, initialValues, form]); + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + await onSubmit(values); + } catch (error: unknown) { + // validateFields 失败时 error 包含 errorFields(预期行为,不记录) + // 其他类型的错误才记录 + if (error && typeof error === 'object' && !('errorFields' in error)) { + console.error('[DrawerForm] submit error:', error); + } + } + }; + + const gridStyle: React.CSSProperties = + columns === 2 + ? { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px' } + : {}; + + return ( + + + + + } + > +
+ {sections + ? sections.map((s, i) => ( +
+ {i > 0 && } + + {s.title} + +
{s.fields}
+
+ )) + : children &&
{children}
} +
+
+ ); +} diff --git a/apps/web/src/components/EntityName.tsx b/apps/web/src/components/EntityName.tsx new file mode 100644 index 0000000..d3c8562 --- /dev/null +++ b/apps/web/src/components/EntityName.tsx @@ -0,0 +1,23 @@ +import { Tooltip, Typography } from 'antd'; + +interface EntityNameProps { + name?: string | null; + id?: string; + fallbackLabel?: string; +} + +export function EntityName({ name, id, fallbackLabel = '未知' }: EntityNameProps) { + if (name !== undefined && name !== null && name !== '') return {name}; + + if (id) { + return ( + + + {fallbackLabel} + + + ); + } + + return {fallbackLabel}; +} diff --git a/apps/web/src/components/EntitySelect.tsx b/apps/web/src/components/EntitySelect.tsx new file mode 100644 index 0000000..b5f1795 --- /dev/null +++ b/apps/web/src/components/EntitySelect.tsx @@ -0,0 +1,141 @@ +import { Select, Spin, Input, Tooltip } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { useState, useEffect, useCallback } from 'react'; +import { listPluginData, getPluginEntityRegistry } from '../api/pluginData'; + +interface EntitySelectProps { + pluginId: string; + entity: string; + labelField: string; + searchFields?: string[]; + /** 跨插件引用的目标插件 manifest ID(如 "erp-crm") */ + refPlugin?: string; + /** 目标插件未安装时的降级显示文本 */ + fallbackLabel?: string; + value?: string; + onChange?: (value: string, label: string) => void; + cascadeFrom?: string; + cascadeFilter?: string; + cascadeValue?: string; + placeholder?: string; +} + +export default function EntitySelect({ + pluginId, + entity, + labelField, + searchFields: _searchFields, + refPlugin, + fallbackLabel, + value, + onChange, + cascadeFrom, + cascadeFilter, + cascadeValue, + placeholder, +}: EntitySelectProps) { + const [options, setOptions] = useState<{ value: string; label: string }[]>([]); + const [loading, setLoading] = useState(false); + const [targetUnavailable, setTargetUnavailable] = useState(false); + const [resolvedPluginId, setResolvedPluginId] = useState(null); + + // 跨插件时:先解析 manifest_id → plugin UUID + useEffect(() => { + if (!refPlugin) { + setResolvedPluginId(pluginId); + return; + } + let cancelled = false; + (async () => { + try { + const registry = await getPluginEntityRegistry(); + const match = registry.find((e) => e.manifest_id === refPlugin && e.entity_name === entity); + if (!cancelled) { + setResolvedPluginId(match ? match.plugin_id : null); + if (!match) setTargetUnavailable(true); + } + } catch { + if (!cancelled) { + setTargetUnavailable(true); + setResolvedPluginId(null); + } + } + })(); + return () => { cancelled = true; }; + }, [refPlugin, pluginId, entity]); + + const effectivePluginId = resolvedPluginId || pluginId; + + const fetchData = useCallback( + async (keyword?: string) => { + if (!resolvedPluginId && refPlugin) return; + setLoading(true); + try { + const filter: Record | undefined = + cascadeFrom && cascadeFilter && cascadeValue + ? { [cascadeFilter]: cascadeValue } + : undefined; + + const result = await listPluginData(effectivePluginId, entity, 1, 20, { + search: keyword, + filter, + }); + + const items = (result.data || []).map((item) => ({ + value: item.id, + label: String(item.data?.[labelField] ?? item.id), + })); + setOptions(items); + setTargetUnavailable(false); + } catch { + if (refPlugin) { + setTargetUnavailable(true); + setOptions([]); + } + } finally { + setLoading(false); + } + }, + [effectivePluginId, entity, labelField, cascadeFrom, cascadeFilter, cascadeValue, refPlugin, resolvedPluginId], + ); + + useEffect(() => { + if (resolvedPluginId || !refPlugin) { + fetchData(); + } + }, [fetchData, resolvedPluginId, refPlugin]); + + // 目标插件未安装 → 降级显示 + if (targetUnavailable) { + return ( + + + + } + /> + ); + } + + return ( + ; + case 'number': { + const props: Record = { + disabled: readOnly, + placeholder: `请输入${field.display_name}`, + style: { width: '100%' }, + }; + if (field.range) { + props.min = field.range[0]; + props.max = field.range[1]; + } + return ; + } + case 'boolean': + return ; + case 'select': + return ( + { + if (typeof o === 'object' && o !== null && 'label' in o && 'value' in o) { + return o as { label: string; value: string }; + } + return { label: String(o), value: String(o) }; + })} + /> + ); + case 'color': + return ; + case 'date': + return ; + case 'datetime': + return ; + case 'json': + return ; + default: + return ; + } +} + +function getDefaultForType(type: PluginSettingType): unknown { + switch (type) { + case 'text': + case 'color': + return ''; + case 'number': + return 0; + case 'boolean': + return false; + case 'select': + return undefined; + case 'multiselect': + return []; + case 'date': + case 'datetime': + return undefined; + case 'json': + return ''; + default: + return ''; + } +} + +export default PluginSettingsForm; diff --git a/apps/web/src/components/ThemeSwitcher.tsx b/apps/web/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..1c5ab98 --- /dev/null +++ b/apps/web/src/components/ThemeSwitcher.tsx @@ -0,0 +1,64 @@ +import { Dropdown } from 'antd'; +import { BgColorsOutlined } from '@ant-design/icons'; +import { useAppStore, THEME_OPTIONS } from '../stores/app'; + +export default function ThemeSwitcher() { + const theme = useAppStore((s) => s.theme); + const setTheme = useAppStore((s) => s.setTheme); + + const content = ( +
+ {THEME_OPTIONS.map((opt) => { + const active = theme === opt.key; + return ( +
setTheme(opt.key)} + style={{ + display: 'flex', + alignItems: 'center', + gap: 12, + padding: '10px 12px', + borderRadius: 8, + cursor: 'pointer', + border: `2px solid ${active ? opt.preview.primary : 'transparent'}`, + background: active ? `${opt.preview.primary}08` : 'transparent', + transition: 'all 0.15s ease', + }} + > + {/* 色块预览 */} +
+
+
+
+
+
+
{opt.label}
+
{opt.desc}
+
+ {active && ( +
+ )} +
+ ); + })} +
+ ); + + return ( + content} trigger={['click']} placement="bottomRight"> +
+ +
+
+ ); +} diff --git a/apps/web/src/hooks/useApiRequest.ts b/apps/web/src/hooks/useApiRequest.ts new file mode 100644 index 0000000..623d025 --- /dev/null +++ b/apps/web/src/hooks/useApiRequest.ts @@ -0,0 +1,41 @@ +import { useCallback, useState } from 'react'; +import { message } from 'antd'; + +function extractErrorMessage(err: unknown): string { + if (err && typeof err === 'object' && 'response' in err) { + const resp = (err as { response?: { data?: { message?: string } } }).response; + return resp?.data?.message || ''; + } + if (err instanceof Error) return err.message; + return ''; +} + +interface UseApiRequestReturn { + execute: (fn: () => Promise, successMsg?: string, errorMsg?: string) => Promise; + loading: boolean; +} + +export function useApiRequest(): UseApiRequestReturn { + const [loading, setLoading] = useState(false); + + const execute = useCallback(async ( + fn: () => Promise, + successMsg?: string, + errorMsg = '操作失败', + ): Promise => { + setLoading(true); + try { + const result = await fn(); + if (successMsg) message.success(successMsg); + return result; + } catch (err) { + const msg = extractErrorMessage(err); + message.error(msg || errorMsg); + return null; + } finally { + setLoading(false); + } + }, []); + + return { execute, loading }; +} diff --git a/apps/web/src/hooks/useCountUp.ts b/apps/web/src/hooks/useCountUp.ts new file mode 100644 index 0000000..950fc16 --- /dev/null +++ b/apps/web/src/hooks/useCountUp.ts @@ -0,0 +1,24 @@ +import { useState, useEffect, useRef } from 'react'; + +export function useCountUp(end: number, duration = 800) { + const [count, setCount] = useState(0); + const prevEnd = useRef(end); + + useEffect(() => { + if (end === prevEnd.current && count > 0) return; + prevEnd.current = end; + if (end === 0) { setCount(0); return; } + + const startTime = performance.now(); + function tick(now: number) { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const eased = 1 - Math.pow(1 - progress, 3); + setCount(Math.round(end * eased)); + if (progress < 1) requestAnimationFrame(tick); + } + requestAnimationFrame(tick); + }, [end, duration]); + + return count; +} diff --git a/apps/web/src/hooks/useCrudDrawer.ts b/apps/web/src/hooks/useCrudDrawer.ts new file mode 100644 index 0000000..ff80e75 --- /dev/null +++ b/apps/web/src/hooks/useCrudDrawer.ts @@ -0,0 +1,74 @@ +import { useState, useCallback } from 'react'; +import { useApiRequest } from './useApiRequest'; + +export interface UseCrudDrawerOptions { + getId: (record: T) => string; + onCreate: (values: Record) => Promise; + onUpdate: (id: string, values: Record & { version: number }) => Promise; + onSuccess?: () => void; +} + +export interface UseCrudDrawerReturn { + open: boolean; + editingRecord: T | null; + initialValues: Record | undefined; + openCreate: (defaults?: Record) => void; + openEdit: (record: T, fieldMap?: (record: T) => Record) => void; + close: () => void; + handleSubmit: (values: Record) => Promise; + loading: boolean; +} + +export function useCrudDrawer( + options: UseCrudDrawerOptions, +): UseCrudDrawerReturn { + const [open, setOpen] = useState(false); + const [editingRecord, setEditingRecord] = useState(null); + const [initialValues, setInitialValues] = useState | undefined>(undefined); + const { execute, loading } = useApiRequest(); + + const openCreate = useCallback((defaults?: Record) => { + setEditingRecord(null); + setInitialValues(defaults); + setOpen(true); + }, []); + + const openEdit = useCallback((record: T, fieldMap?: (record: T) => Record) => { + setEditingRecord(record); + setInitialValues(fieldMap ? fieldMap(record) : (record as unknown as Record)); + setOpen(true); + }, []); + + const close = useCallback(() => { + setOpen(false); + setEditingRecord(null); + setInitialValues(undefined); + }, []); + + const handleSubmit = useCallback( + async (values: Record) => { + if (editingRecord) { + await execute( + () => options.onUpdate(options.getId(editingRecord), { ...values, version: (editingRecord as unknown as { version: number }).version }), + '更新成功', + ); + } else { + await execute(() => options.onCreate(values), '创建成功'); + } + close(); + options.onSuccess?.(); + }, + [editingRecord, options, close, execute], + ); + + return { + open, + editingRecord, + initialValues, + openCreate, + openEdit, + close, + handleSubmit, + loading, + }; +} diff --git a/apps/web/src/hooks/useDarkMode.ts b/apps/web/src/hooks/useDarkMode.ts new file mode 100644 index 0000000..0654402 --- /dev/null +++ b/apps/web/src/hooks/useDarkMode.ts @@ -0,0 +1,6 @@ +import { theme } from 'antd'; + +export function useDarkMode(): boolean { + const { token } = theme.useToken(); + return token.colorBgBase !== '#ffffff' && token.colorBgBase !== '#fff'; +} diff --git a/apps/web/src/hooks/useDebouncedValue.test.ts b/apps/web/src/hooks/useDebouncedValue.test.ts new file mode 100644 index 0000000..713e9dc --- /dev/null +++ b/apps/web/src/hooks/useDebouncedValue.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { renderHook, act } from '@testing-library/react' +import { useDebouncedValue } from './useDebouncedValue' + +describe('useDebouncedValue', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('returns initial value immediately', () => { + const { result } = renderHook(() => useDebouncedValue('hello')) + expect(result.current).toBe('hello') + }) + + it('debounces value updates', () => { + const { result, rerender } = renderHook( + ({ value }) => useDebouncedValue(value, 300), + { initialProps: { value: 'a' } }, + ) + + expect(result.current).toBe('a') + + rerender({ value: 'b' }) + expect(result.current).toBe('a') + + act(() => { vi.advanceTimersByTime(299) }) + expect(result.current).toBe('a') + + act(() => { vi.advanceTimersByTime(1) }) + expect(result.current).toBe('b') + }) + + it('resets timer on rapid updates', () => { + const { result, rerender } = renderHook( + ({ value }) => useDebouncedValue(value, 200), + { initialProps: { value: 'a' } }, + ) + + rerender({ value: 'b' }) + act(() => { vi.advanceTimersByTime(100) }) + + rerender({ value: 'c' }) + act(() => { vi.advanceTimersByTime(100) }) + expect(result.current).toBe('a') + + act(() => { vi.advanceTimersByTime(100) }) + expect(result.current).toBe('c') + }) + + it('uses custom delay', () => { + const { result, rerender } = renderHook( + ({ value }) => useDebouncedValue(value, 500), + { initialProps: { value: 'x' } }, + ) + + rerender({ value: 'y' }) + act(() => { vi.advanceTimersByTime(499) }) + expect(result.current).toBe('x') + + act(() => { vi.advanceTimersByTime(1) }) + expect(result.current).toBe('y') + }) + + it('works with numeric values', () => { + const { result, rerender } = renderHook( + ({ value }) => useDebouncedValue(value, 100), + { initialProps: { value: 0 } }, + ) + + rerender({ value: 42 }) + act(() => { vi.advanceTimersByTime(100) }) + expect(result.current).toBe(42) + }) +}) diff --git a/apps/web/src/hooks/useDebouncedValue.ts b/apps/web/src/hooks/useDebouncedValue.ts new file mode 100644 index 0000000..dcd2c7c --- /dev/null +++ b/apps/web/src/hooks/useDebouncedValue.ts @@ -0,0 +1,12 @@ +import { useState, useEffect } from 'react'; + +export function useDebouncedValue(value: T, delay = 300): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debouncedValue; +} diff --git a/apps/web/src/hooks/useDictionary.ts b/apps/web/src/hooks/useDictionary.ts new file mode 100644 index 0000000..66d9dd0 --- /dev/null +++ b/apps/web/src/hooks/useDictionary.ts @@ -0,0 +1,31 @@ +import { useEffect, useState, useMemo } from 'react'; +import { listItemsByCode, type DictionaryItemInfo } from '../api/dictionaries'; + +export interface DictOption { + value: string; + label: string; +} + +export function useDictionary(code: string, fallback?: DictOption[]) { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + listItemsByCode(code) + .then((data) => setItems(data)) + .catch(() => setItems([])) + .finally(() => setLoading(false)); + }, [code]); + + const options = useMemo(() => { + if (items.length > 0) { + return items + .sort((a, b) => a.sort_order - b.sort_order) + .map((item) => ({ value: item.value, label: item.label })); + } + return fallback ?? []; + }, [items, fallback]); + + return { items, options, loading }; +} diff --git a/apps/web/src/hooks/useListData.ts b/apps/web/src/hooks/useListData.ts new file mode 100644 index 0000000..4b08604 --- /dev/null +++ b/apps/web/src/hooks/useListData.ts @@ -0,0 +1,33 @@ +import { useState, useCallback, useEffect, useRef } from 'react'; + +export interface UseListDataReturn { + data: T[]; + loading: boolean; + refresh: () => Promise; +} + +export function useListData(fetchFn: () => Promise, autoFetch = true): UseListDataReturn { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const fetchFnRef = useRef(fetchFn); + fetchFnRef.current = fetchFn; + + const refresh = useCallback(async () => { + setLoading(true); + try { + const result = await fetchFnRef.current(); + setData(result); + } catch { + setData([]); + } + setLoading(false); + }, []); + + useEffect(() => { + if (autoFetch) { + refresh(); + } + }, [refresh, autoFetch]); + + return { data, loading, refresh }; +} diff --git a/apps/web/src/hooks/usePaginatedData.ts b/apps/web/src/hooks/usePaginatedData.ts new file mode 100644 index 0000000..1d894f2 --- /dev/null +++ b/apps/web/src/hooks/usePaginatedData.ts @@ -0,0 +1,125 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import { message } from 'antd'; + +interface PaginatedState { + data: T[]; + total: number; + page: number; + loading: boolean; +} + +type FetchResult = { data: T[]; total: number }; +type OptionsConfig = { pageSize?: number; defaultFilters: F; autoFetch?: boolean }; + +/** + * 通用分页数据 Hook,封装 data / total / page / loading / fetch 逻辑。 + * + * 支持三种签名: + * 1. 泛型筛选 (page, pageSize, filters: F) — 带结构化筛选的列表页 + * 2. 三参数 (page, pageSize, search: string) — 带搜索的列表页 + * 3. 两参数 (page, pageSize) — 纯分页,不含搜索 + */ + +// 重载签名 +export function usePaginatedData( + fetchFn: (page: number, pageSize: number, filters: F) => Promise>, + options: OptionsConfig, +): PaginatedResult; + +export function usePaginatedData( + fetchFn: + | ((page: number, pageSize: number, search: string) => Promise>) + | ((page: number, pageSize: number) => Promise>), + pageSize?: number, + autoFetch?: boolean, +): PaginatedResult; + +/* eslint-disable @typescript-eslint/no-explicit-any -- 实现签名必须兼容所有重载 */ +export function usePaginatedData( + fetchFn: (...args: any[]) => Promise>, + pageSizeOrOptions?: number | OptionsConfig, + autoFetch = true, +): PaginatedResult { + /* eslint-enable @typescript-eslint/no-explicit-any */ + const isOptions = typeof pageSizeOrOptions === 'object' && pageSizeOrOptions !== null; + const options = pageSizeOrOptions as OptionsConfig; + const pageSize = isOptions ? options.pageSize ?? 20 : (pageSizeOrOptions as number) ?? 20; + const shouldAutoFetch = isOptions ? options.autoFetch ?? true : autoFetch; + const defaultFilters = isOptions ? options.defaultFilters : ('' as unknown as F); + + const [state, setState] = useState>({ + data: [], + total: 0, + page: 1, + loading: false, + }); + const [searchText, setSearchText] = useState(''); + const [filters, setFilters] = useState(defaultFilters); + + const fetchFnRef = useRef(fetchFn); + + const searchTextRef = useRef(searchText); + + const filtersRef = useRef(filters); + + const stateRef = useRef(state); + + useEffect(() => { + fetchFnRef.current = fetchFn; + searchTextRef.current = searchText; + filtersRef.current = filters; + stateRef.current = state; + }); + + // 所有 fetch 统一走 useEffect,通过 fetchTrigger 触发 + const [fetchTrigger, setFetchTrigger] = useState(0); + const pendingPageRef = useRef(undefined); + const isFirstRender = useRef(true); + + // refresh 只负责设置目标页并递增 trigger,实际 fetch 在 useEffect 中执行 + const refresh = useCallback((p?: number) => { + pendingPageRef.current = p; + setFetchTrigger((t) => t + 1); + }, []); + + useEffect(() => { + const targetPage = pendingPageRef.current ?? stateRef.current.page; + pendingPageRef.current = undefined; + + if (isFirstRender.current) { + isFirstRender.current = false; + if (!shouldAutoFetch) return; + } + + // eslint-disable-next-line react-hooks/set-state-in-effect -- 数据获取 hook:loading → fetch → setState 是标准模式 + setState((s) => ({ ...s, loading: true })); + + let cancelled = false; + fetchFnRef.current(targetPage, pageSize, filtersRef.current ?? searchTextRef.current) + .then((result) => { + if (!cancelled) { + setState({ data: result.data, total: result.total, page: targetPage, loading: false }); + } + }) + .catch((err) => { + if (!cancelled) { + console.warn('[usePaginatedData] 加载数据失败:', err); + message.error('加载数据失败'); + setState((s) => ({ ...s, loading: false })); + } + }); + + return () => { cancelled = true; }; + // fetchTrigger 变化 = 手动 refresh;filters 变化 = 筛选刷新 + }, [shouldAutoFetch, filters, fetchTrigger, pageSize]); + + return { ...state, searchText, setSearchText, filters, setFilters, refresh }; +} + +interface PaginatedResult extends PaginatedState { + searchText: string; + setSearchText: (text: string) => void; + filters: F; + setFilters: (filters: F | ((prev: F) => F)) => void; + refresh: (page?: number) => void; +} diff --git a/apps/web/src/hooks/usePermFilteredTabs.ts b/apps/web/src/hooks/usePermFilteredTabs.ts new file mode 100644 index 0000000..b78f112 --- /dev/null +++ b/apps/web/src/hooks/usePermFilteredTabs.ts @@ -0,0 +1,38 @@ +import { useAuthStore } from '../stores/auth'; +import { TAB_PERMISSIONS } from '../routeConfig'; + +export interface TabItem { + key: string; + [prop: string]: unknown; +} + +/** + * 根据权限过滤详情页 Tab 列表。 + * + * @param prefix - Tab 权限映射前缀(如 "patient"),对应 routeConfig.ts 中 "patient#tabKey" + * @param tabs - 完整 Tab 列表 + * @returns 过滤后有权限可见的 Tab 列表 + */ +export function usePermFilteredTabs(prefix: string, tabs: T[]): T[] { + const permissions = useAuthStore((s) => s.permissions); + + return tabs.filter((tab) => { + const lookupKey = `${prefix}#${tab.key}`; + const requiredPerm = TAB_PERMISSIONS[lookupKey]; + + // 未在 TAB_PERMISSIONS 中声明的 Tab,安全默认:不显示 + if (requiredPerm === undefined && !(lookupKey in TAB_PERMISSIONS)) { + if (import.meta.env.DEV) { + console.warn( + `[usePermFilteredTabs] Tab "${lookupKey}" 未在 routeConfig.ts TAB_PERMISSIONS 中声明,已隐藏。请添加声明。`, + ); + } + return false; + } + + // 显式声明为 undefined(无需权限)→ 始终可见 + if (requiredPerm === undefined) return true; + + return permissions.includes(requiredPerm); + }); +} diff --git a/apps/web/src/hooks/usePermission.ts b/apps/web/src/hooks/usePermission.ts new file mode 100644 index 0000000..8d11f9a --- /dev/null +++ b/apps/web/src/hooks/usePermission.ts @@ -0,0 +1,6 @@ +import { useAuthStore } from '../stores/auth'; + +export function usePermission(code: string): { hasPermission: boolean } { + const permissions = useAuthStore((s) => s.permissions); + return { hasPermission: permissions.includes(code) }; +} diff --git a/apps/web/src/hooks/useThemeMode.test.ts b/apps/web/src/hooks/useThemeMode.test.ts new file mode 100644 index 0000000..4dfee01 --- /dev/null +++ b/apps/web/src/hooks/useThemeMode.test.ts @@ -0,0 +1,15 @@ +import { describe, it, expect } from 'vitest' +import { renderHook } from '@testing-library/react' +import { useThemeMode } from './useThemeMode' + +describe('useThemeMode', () => { + it('should return false when no ConfigProvider is present (light default)', () => { + const { result } = renderHook(() => useThemeMode()) + expect(result.current).toBe(false) + }) + + it('should return a boolean value', () => { + const { result } = renderHook(() => useThemeMode()) + expect(typeof result.current).toBe('boolean') + }) +}) diff --git a/apps/web/src/hooks/useThemeMode.ts b/apps/web/src/hooks/useThemeMode.ts new file mode 100644 index 0000000..36bde12 --- /dev/null +++ b/apps/web/src/hooks/useThemeMode.ts @@ -0,0 +1,11 @@ +import { useAppStore } from '../stores/app'; + +/** + * 判断当前是否处于暗色主题模式。 + * + * 通过 store 的主题名称判断,替代旧的 token 色值检测, + * 支持多主题系统(blue / warm / dark / emerald)。 + */ +export function useThemeMode(): boolean { + return useAppStore((s) => s.theme) === 'dark'; +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..77b38c1 --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,1368 @@ +@import "tailwindcss"; + +/* ==================================================================== + * ERP Platform — Design System Tokens & Global Styles + * Soft UI Evolution: Professional, warm, accessible for all industries + * Generated by UI UX Pro Max + * ==================================================================== */ + +/* --- Design Tokens (CSS Custom Properties) --- */ +:root { + /* Primary Palette — Trust Blue */ + --erp-primary: #2563eb; + --erp-primary-hover: #1d4ed8; + --erp-primary-active: #1e40af; + --erp-primary-light: #eff6ff; + --erp-primary-light-hover: #dbeafe; + --erp-primary-bg-subtle: #eff6ff; + + /* Semantic Colors — Professional slate tones */ + --erp-success: #059669; + --erp-success-bg: #ecfdf5; + --erp-warning: #d97706; + --erp-warning-bg: #fffbeb; + --erp-error: #dc2626; + --erp-error-bg: #fef2f2; + --erp-info: #0284c7; + --erp-info-bg: #f0f9ff; + + /* Neutral Palette — Slate neutrals with blue undertones */ + --erp-bg-page: #f8fafc; + --erp-bg-container: #ffffff; + --erp-bg-elevated: #ffffff; + --erp-bg-spotlight: #f1f5f9; + --erp-bg-sidebar: #ffffff; + --erp-bg-sidebar-hover: #f1f5f9; + --erp-bg-sidebar-active: #eff6ff; + + /* Text Colors — Deep navy */ + --erp-text-primary: #0f172a; + --erp-text-secondary: #475569; + --erp-text-tertiary: #64748b; + --erp-text-inverse: #ffffff; + --erp-text-sidebar: #475569; + --erp-text-sidebar-active: #2563eb; + + /* Border Colors — Slate borders */ + --erp-border: #e2e8f0; + --erp-border-light: #f1f5f9; + --erp-border-dark: #cbd5e1; + + /* Shadows — Soft UI Evolution: subtle, layered depth */ + --erp-shadow-xs: 0 1px 2px rgba(0,0,0,0.05); + --erp-shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04); + --erp-shadow-md: 0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.05); + --erp-shadow-lg: 0 8px 30px rgba(0,0,0,0.12); + --erp-shadow-xl: 0 12px 40px rgba(0,0,0,0.15); + + /* Radius — Soft UI: friendly but professional */ + --erp-radius-sm: 6px; + --erp-radius-md: 10px; + --erp-radius-lg: 12px; + --erp-radius-xl: 16px; + + /* Spacing — 4px base unit */ + --erp-space-xs: 4px; + --erp-space-sm: 8px; + --erp-space-md: 16px; + --erp-space-lg: 24px; + --erp-space-xl: 32px; + --erp-space-2xl: 48px; + + /* Typography — Noto Sans SC for Chinese-first ERP */ + --erp-font-family: 'Noto Sans SC', -apple-system, system-ui, 'Segoe UI', Roboto, + 'PingFang SC', 'Microsoft YaHei', Helvetica, Arial, sans-serif; + --erp-font-mono: 'JetBrains Mono', 'Fira Code', Consolas, Monaco, monospace; + --erp-font-size-xs: 12px; + --erp-font-size-sm: 13px; + --erp-font-size-base: 14px; + --erp-font-size-lg: 16px; + --erp-font-size-xl: 18px; + --erp-font-size-2xl: 20px; + --erp-font-size-3xl: 24px; + + /* Transitions */ + --erp-transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); + --erp-transition-base: 0.2s cubic-bezier(0.4, 0, 0.2, 1); + --erp-transition-slow: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Trend Colors */ + --erp-trend-up: #059669; + --erp-trend-down: #dc2626; + --erp-trend-neutral: #475569; + + /* Line Height */ + --erp-line-height-tight: 1.25; + --erp-line-height-normal: 1.5; + --erp-line-height-relaxed: 1.625; + + /* Heading font — override per theme */ + --erp-font-heading: 'Noto Sans SC', -apple-system, system-ui, sans-serif; + + /* Layout */ + --erp-sidebar-width: 240px; + --erp-sidebar-collapsed-width: 72px; + --erp-header-height: 56px; +} + +/* ─── Theme: 温润东方 (warm) ─── */ +[data-theme='warm'] { + --erp-primary: #C4623A; + --erp-primary-hover: #B55A33; + --erp-primary-active: #8B3E1F; + --erp-primary-light: #F0DDD4; + --erp-primary-light-hover: #E8CEBF; + --erp-primary-bg-subtle: #FAF5F0; + + --erp-success: #5B7A5E; + --erp-success-bg: #E8F0E8; + --erp-warning: #C4873A; + --erp-warning-bg: #FFF3E0; + --erp-error: #B54A4A; + --erp-error-bg: #FDEAEA; + --erp-info: #8B7A5E; + --erp-info-bg: #F5F0E8; + + --erp-bg-page: #F5F0EB; + --erp-bg-container: #FFFFFF; + --erp-bg-elevated: #FFFFFF; + --erp-bg-spotlight: #EDE8E2; + --erp-bg-sidebar: #FFFFFF; + --erp-bg-sidebar-hover: #F5F0EB; + --erp-bg-sidebar-active: #F0DDD4; + + --erp-text-primary: #2D2A26; + --erp-text-secondary: #7A756E; + --erp-text-tertiary: #A8A29E; + --erp-text-inverse: #FFFFFF; + --erp-text-sidebar: #7A756E; + --erp-text-sidebar-active: #C4623A; + + --erp-border: #E8E2DC; + --erp-border-light: #F0EBE5; + --erp-border-dark: #D5CFC8; + + --erp-shadow-xs: 0 1px 2px rgba(45,42,38,0.04); + --erp-shadow-sm: 0 1px 3px rgba(45,42,38,0.06), 0 1px 2px rgba(45,42,38,0.03); + --erp-shadow-md: 0 4px 6px rgba(45,42,38,0.07), 0 2px 4px rgba(45,42,38,0.04); + --erp-shadow-lg: 0 8px 30px rgba(45,42,38,0.10); + --erp-shadow-xl: 0 12px 40px rgba(45,42,38,0.14); + + --erp-radius-sm: 8px; + --erp-radius-md: 12px; + --erp-radius-lg: 14px; + --erp-radius-xl: 18px; + + --erp-trend-up: #5B7A5E; + --erp-trend-down: #B54A4A; + --erp-trend-neutral: #7A756E; + + --erp-font-heading: 'Noto Serif SC', Georgia, serif; +} + +/* ─── Theme: 深邃夜色 (dark) ─── */ +[data-theme='dark'] { + --erp-primary: #60A5FA; + --erp-primary-hover: #93C5FD; + --erp-primary-active: #3B82F6; + --erp-primary-light: rgba(96,165,250,0.15); + --erp-primary-light-hover: rgba(96,165,250,0.22); + --erp-primary-bg-subtle: rgba(96,165,250,0.10); + + --erp-success: #34D399; + --erp-success-bg: rgba(5,150,105,0.15); + --erp-warning: #FBBF24; + --erp-warning-bg: rgba(217,119,6,0.15); + --erp-error: #F87171; + --erp-error-bg: rgba(220,38,38,0.15); + --erp-info: #38BDF8; + --erp-info-bg: rgba(2,132,199,0.15); + + --erp-bg-page: #0F172A; + --erp-bg-container: #1E293B; + --erp-bg-elevated: #334155; + --erp-bg-spotlight: #1E293B; + --erp-bg-sidebar: #0F172A; + --erp-bg-sidebar-hover: #1E293B; + --erp-bg-sidebar-active: rgba(96,165,250,0.15); + + --erp-text-primary: rgba(255,255,255,0.95); + --erp-text-secondary: #94A3B8; + --erp-text-tertiary: #64748B; + --erp-text-inverse: #0F172A; + --erp-text-sidebar: #94A3B8; + --erp-text-sidebar-active: #60A5FA; + + --erp-border: #334155; + --erp-border-light: rgba(255,255,255,0.06); + --erp-border-dark: rgba(255,255,255,0.12); + + --erp-shadow-xs: 0 1px 2px rgba(0,0,0,0.3); + --erp-shadow-sm: 0 2px 8px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2); + --erp-shadow-md: 0 4px 12px rgba(0,0,0,0.3), 0 2px 6px rgba(0,0,0,0.2); + --erp-shadow-lg: 0 8px 30px rgba(0,0,0,0.4); + --erp-shadow-xl: 0 12px 40px rgba(0,0,0,0.5); + + --erp-trend-up: #34D399; + --erp-trend-down: #F87171; + --erp-trend-neutral: #94A3B8; + + --erp-font-heading: 'Noto Sans SC', -apple-system, system-ui, sans-serif; +} + +/* ─── Theme: 翡翠清雅 (emerald) ─── */ +[data-theme='emerald'] { + --erp-primary: #5B7A5E; + --erp-primary-hover: #4D6B50; + --erp-primary-active: #3E5C41; + --erp-primary-light: #E0EBE1; + --erp-primary-light-hover: #D1E0D3; + --erp-primary-bg-subtle: #F0F5F0; + + --erp-success: #3D7A42; + --erp-success-bg: #E0F0E2; + --erp-warning: #B8863A; + --erp-warning-bg: #FFF3E0; + --erp-error: #A54A4A; + --erp-error-bg: #FDEAEA; + --erp-info: #4A7A8B; + --erp-info-bg: #E0F0F5; + + --erp-bg-page: #F4F7F4; + --erp-bg-container: #FFFFFF; + --erp-bg-elevated: #FFFFFF; + --erp-bg-spotlight: #EDF2ED; + --erp-bg-sidebar: #FFFFFF; + --erp-bg-sidebar-hover: #F4F7F4; + --erp-bg-sidebar-active: #E0EBE1; + + --erp-text-primary: #1A2E1A; + --erp-text-secondary: #5A6E5A; + --erp-text-tertiary: #8FA08F; + --erp-text-inverse: #FFFFFF; + --erp-text-sidebar: #5A6E5A; + --erp-text-sidebar-active: #5B7A5E; + + --erp-border: #D5DED5; + --erp-border-light: #E5ECE5; + --erp-border-dark: #B8C5B8; + + --erp-shadow-xs: 0 1px 2px rgba(30,60,30,0.04); + --erp-shadow-sm: 0 1px 3px rgba(30,60,30,0.06), 0 1px 2px rgba(30,60,30,0.03); + --erp-shadow-md: 0 4px 6px rgba(30,60,30,0.07), 0 2px 4px rgba(30,60,30,0.04); + --erp-shadow-lg: 0 8px 30px rgba(30,60,30,0.10); + --erp-shadow-xl: 0 12px 40px rgba(30,60,30,0.14); + + --erp-radius-sm: 8px; + --erp-radius-md: 10px; + --erp-radius-lg: 14px; + --erp-radius-xl: 18px; + + --erp-trend-up: #3D7A42; + --erp-trend-down: #A54A4A; + --erp-trend-neutral: #5A6E5A; + + --erp-font-heading: 'Noto Sans SC', -apple-system, system-ui, sans-serif; +} + +/* --- Login Page Tokens --- */ +:root { + --login-gradient-start: #312E81; + --login-gradient-mid: #2563eb; + --login-gradient-end: #60a5fa; + --login-form-bg: #ffffff; + --login-form-text: #0f172a; + --login-form-text-secondary: #475569; + --login-input-icon-color: #64748b; +} + +[data-theme='warm'] { + --login-gradient-start: #6B3418; + --login-gradient-mid: #C4623A; + --login-gradient-end: #D4956A; + --login-form-bg: #FAF5F0; + --login-form-text: #2D2A26; + --login-form-text-secondary: #7A756E; + --login-input-icon-color: #A8A29E; +} + +[data-theme='dark'] { + --login-gradient-start: #0F172A; + --login-gradient-mid: #1E293B; + --login-gradient-end: #334155; + --login-form-bg: #1E293B; + --login-form-text: rgba(255,255,255,0.95); + --login-form-text-secondary: #94A3B8; + --login-input-icon-color: #64748B; +} + +[data-theme='emerald'] { + --login-gradient-start: #2D4A2F; + --login-gradient-mid: #5B7A5E; + --login-gradient-end: #8FB092; + --login-form-bg: #F4F7F4; + --login-form-text: #1A2E1A; + --login-form-text-secondary: #5A6E5A; + --login-input-icon-color: #8FA08F; +} + +/* --- Login Page Styles --- */ +.login-root { + display: flex; + min-height: 100vh; +} + +.login-brand-panel { + flex: 1; + background: linear-gradient(135deg, var(--login-gradient-start) 0%, var(--login-gradient-mid) 50%, var(--login-gradient-end) 100%); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 60px; + position: relative; + overflow: hidden; +} + +.login-brand-panel .deco-circle { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.05); +} + +.login-brand-panel .brand-icon { + width: 64px; + height: 64px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 32px; + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.login-brand-panel .brand-icon .anticon { + font-size: 32px; + color: #fff; +} + +.login-brand-panel .brand-title { + color: #fff; + font-size: 36px; + font-weight: 800; + margin: 0 0 16px; + letter-spacing: -1px; + line-height: 1.2; +} + +.login-brand-panel .brand-desc { + color: rgba(255, 255, 255, 0.7); + font-size: 16px; + line-height: 1.6; + margin: 0; +} + +.login-brand-panel .brand-sub-desc { + color: rgba(255, 255, 255, 0.5); + font-size: 14px; + line-height: 1.6; + margin-top: 8px; +} + +.login-brand-panel .feature-item-value { + color: rgba(255, 255, 255, 0.9); + font-size: 18px; + font-weight: 700; +} + +.login-brand-panel .feature-item-label { + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + margin-top: 4px; +} + +.login-form-panel { + width: 480px; + display: flex; + flex-direction: column; + justify-content: center; + padding: 60px; + background: var(--login-form-bg); + position: relative; + transition: background var(--erp-transition-slow); +} + +.login-form-panel .form-title { + margin-bottom: 4px; + font-weight: 700; + font-size: 24px; + color: var(--login-form-text); +} + +.login-form-panel .form-subtitle { + font-size: 14px; + color: var(--login-form-text-secondary); +} + +.login-form-panel .form-footer { + margin-top: 32px; + text-align: center; + font-size: 12px; + color: var(--login-form-text-secondary); +} + +.login-theme-switcher { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; +} + +.login-theme-switcher .erp-header-btn { + background: var(--login-form-bg); + border: 1px solid var(--erp-border); + color: var(--login-form-text-secondary); +} + +/* --- Global Reset & Base --- */ +body { + margin: 0; + font-family: var(--erp-font-family); + font-size: var(--erp-font-size-base); + color: var(--erp-text-primary); + background-color: var(--erp-bg-page); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Force Ant Design Layout backgrounds to follow theme */ +.ant-layout { + background: var(--erp-bg-page) !important; +} +.ant-layout-content { + background: transparent !important; +} +.ant-layout-sider { + background: var(--erp-bg-sidebar) !important; +} +.ant-layout-header { + background: var(--erp-bg-container) !important; +} +.ant-layout-footer { + background: transparent !important; +} + +/* --- Smooth Scrolling --- */ +* { + scroll-behavior: smooth; +} + +/* --- Custom Scrollbar --- */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: var(--erp-text-tertiary); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--erp-text-secondary); +} + +/* --- Selection --- */ +::selection { + background-color: var(--erp-primary-bg-subtle); + color: var(--erp-text-primary); +} + +/* ==================================================================== + * Component Overrides — Ant Design Enhancement + * ==================================================================== */ + +/* --- Card — Soft shadow, clean border --- */ +.ant-card { + border-radius: var(--erp-radius-lg) !important; + border: 1px solid var(--erp-border) !important; + box-shadow: var(--erp-shadow-xs) !important; + transition: box-shadow var(--erp-transition-base) !important; +} + +.ant-card:hover { + box-shadow: var(--erp-shadow-sm) !important; +} + +.ant-card .ant-card-head { + border-bottom: 1px solid var(--erp-border-light) !important; + padding: 12px 20px !important; + min-height: auto !important; +} + +.ant-card .ant-card-head-title { + padding: 8px 0 !important; + font-weight: 600 !important; + font-size: var(--erp-font-size-base) !important; +} + +.ant-card .ant-card-body { + padding: 20px !important; +} + +/* --- Statistic Cards --- */ +.stat-card { + border-radius: var(--erp-radius-lg) !important; + border: 1px solid var(--erp-border) !important; + overflow: hidden; + position: relative; + transition: box-shadow var(--erp-transition-base) !important; +} + +.stat-card:hover { + box-shadow: var(--erp-shadow-sm) !important; +} + +.stat-card .ant-statistic-title { + font-size: var(--erp-font-size-sm) !important; + color: var(--erp-text-secondary) !important; + margin-bottom: 4px !important; +} + +.stat-card .ant-statistic-content { + font-size: 28px !important; + font-weight: 700 !important; +} + +/* --- Table --- */ +.ant-table { + border-radius: var(--erp-radius-lg) !important; + overflow: hidden; +} + +.ant-table-thead > tr > th { + background: var(--erp-bg-spotlight) !important; + font-weight: 600 !important; + font-size: var(--erp-font-size-sm) !important; + color: var(--erp-text-secondary) !important; + border-bottom: 1px solid var(--erp-border) !important; + padding: 12px 16px !important; +} + +.ant-table-tbody > tr { + transition: background-color var(--erp-transition-fast) !important; +} + +.ant-table-tbody > tr:hover > td { + background: var(--erp-bg-spotlight) !important; +} + +.ant-table-tbody > tr > td { + padding: 12px 16px !important; + border-bottom: 1px solid var(--erp-border-light) !important; +} + +/* --- Button --- */ +.ant-btn-primary { + border-radius: var(--erp-radius-md) !important; + font-weight: 500 !important; + box-shadow: none !important; + transition: all var(--erp-transition-fast) !important; +} + +.ant-btn-primary:hover { + opacity: 0.9; +} + +.ant-btn-default { + border-radius: var(--erp-radius-md) !important; + font-weight: 500 !important; +} + +/* --- Input --- */ +.ant-input, +.ant-input-affix-wrapper, +.ant-select-selector, +.ant-picker { + border-radius: var(--erp-radius-md) !important; + transition: all var(--erp-transition-fast) !important; +} + +.ant-input-affix-wrapper:hover, +.ant-select-selector:hover, +.ant-picker:hover { + border-color: var(--erp-primary) !important; +} + +.ant-input-affix-wrapper:focus, +.ant-input-affix-wrapper-focused, +.ant-select-focused .ant-select-selector, +.ant-picker-focused { + border-color: var(--erp-primary) !important; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.12) !important; +} + +/* --- Modal --- */ +.ant-modal .ant-modal-content { + border-radius: var(--erp-radius-xl) !important; + box-shadow: var(--erp-shadow-xl) !important; + overflow: hidden; +} + +.ant-modal .ant-modal-header { + padding: 20px 24px 16px !important; + border-bottom: 1px solid var(--erp-border-light) !important; +} + +.ant-modal .ant-modal-body { + padding: 20px 24px !important; +} + +.ant-modal .ant-modal-footer { + padding: 12px 24px 20px !important; + border-top: 1px solid var(--erp-border-light) !important; +} + +/* --- Tabs --- */ +.ant-tabs .ant-tabs-tab { + padding: 8px 16px !important; + font-weight: 500 !important; + transition: all var(--erp-transition-fast) !important; + border-radius: var(--erp-radius-md) !important; +} + +.ant-tabs .ant-tabs-tab:hover { + color: var(--erp-primary) !important; +} + +.ant-tabs .ant-tabs-tab-active .ant-tabs-tab-btn { + font-weight: 600 !important; +} + +/* --- Tag --- */ +.ant-tag { + border-radius: var(--erp-radius-sm) !important; + font-size: var(--erp-font-size-xs) !important; + padding: 2px 8px !important; + font-weight: 500 !important; +} + +/* --- Badge --- */ +.ant-badge-count { + box-shadow: 0 0 0 2px var(--erp-bg-container) !important; +} + +/* --- Dropdown/Menu --- */ +.ant-dropdown .ant-dropdown-menu { + border-radius: var(--erp-radius-lg) !important; + box-shadow: var(--erp-shadow-lg) !important; + padding: var(--erp-space-xs) !important; + border: 1px solid var(--erp-border-light) !important; +} + +.ant-dropdown .ant-dropdown-menu-item { + border-radius: var(--erp-radius-sm) !important; + padding: 8px 12px !important; +} + +/* --- Form --- */ +.ant-form-item-label > label { + font-weight: 500 !important; + color: var(--erp-text-primary) !important; +} + +/* --- Popover --- */ +.ant-popover .ant-popover-inner { + border-radius: var(--erp-radius-lg) !important; + box-shadow: var(--erp-shadow-lg) !important; +} + +/* ==================================================================== + * Utility Classes + * ==================================================================== */ + +.erp-page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--erp-border-light); +} + +.erp-page-header h4 { + margin: 0; + font-size: var(--erp-font-size-2xl); + font-weight: 700; + color: var(--erp-text-primary); + letter-spacing: -0.3px; +} + +.erp-page-subtitle { + font-size: var(--erp-font-size-sm); + color: var(--erp-text-tertiary); + margin-top: 2px; +} + +.erp-content-card { + background: var(--erp-bg-container); + border-radius: var(--erp-radius-lg); + padding: 24px; + box-shadow: var(--erp-shadow-xs); + border: 1px solid var(--erp-border-light); +} + +.erp-gradient-card { + position: relative; + overflow: hidden; + border: none !important; +} + +.erp-gradient-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + border-radius: var(--erp-radius-lg) var(--erp-radius-lg) 0 0; +} + +.erp-gradient-card.indigo::before { background: linear-gradient(90deg, #2563eb, #60a5fa); } +.erp-gradient-card.emerald::before { background: linear-gradient(90deg, #059669, #34d399); } +.erp-gradient-card.amber::before { background: linear-gradient(90deg, #d97706, #fbbf24); } +.erp-gradient-card.rose::before { background: linear-gradient(90deg, #dc2626, #f87171); } +.erp-gradient-card.sky::before { background: linear-gradient(90deg, #0284c7, #38bdf8); } +.erp-gradient-card.violet::before { background: linear-gradient(90deg, #7c3aed, #a78bfa); } + +/* --- Fade-in Animation --- */ +@keyframes erp-fade-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.erp-fade-in { + animation: erp-fade-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.erp-fade-in-delay-1 { animation-delay: 0.05s; opacity: 0; } +.erp-fade-in-delay-2 { animation-delay: 0.1s; opacity: 0; } +.erp-fade-in-delay-3 { animation-delay: 0.15s; opacity: 0; } +.erp-fade-in-delay-4 { animation-delay: 0.2s; opacity: 0; } + +/* --- Accessibility: Reduced Motion --- */ +@media (prefers-reduced-motion: reduce) { + .erp-fade-in { animation: none; opacity: 1; } + .erp-fade-in-delay-1, + .erp-fade-in-delay-2, + .erp-fade-in-delay-3, + .erp-fade-in-delay-4 { opacity: 1; } + .erp-sidebar-item { transition: none; } + .erp-header-btn { transition: none; } + .ant-card { transition: none !important; } + .stat-card { transition: none !important; } +} + +/* --- Focus States (keyboard navigation) --- */ +*:focus-visible { + outline: 2px solid var(--erp-primary); + outline-offset: 2px; + border-radius: var(--erp-radius-sm); +} + +.erp-sidebar-item:focus-visible { + outline-offset: -2px; +} + +/* --- Skip to main content link --- */ +.erp-skip-link { + position: absolute; + top: -100%; + left: 50%; + transform: translateX(-50%); + background: var(--erp-primary); + color: #fff; + padding: 8px 24px; + border-radius: 0 0 var(--erp-radius-md) var(--erp-radius-md); + z-index: 10000; + font-size: 14px; + font-weight: 600; + text-decoration: none; + transition: top 0.2s ease; +} + +.erp-skip-link:focus { + top: 0; +} + +/* --- Loading Skeleton --- */ +.erp-skeleton { + background: linear-gradient(90deg, var(--erp-bg-spotlight) 25%, var(--erp-border-light) 50%, var(--erp-bg-spotlight) 75%); + background-size: 200% 100%; + animation: erp-skeleton-shimmer 1.5s infinite; + border-radius: var(--erp-radius-sm); +} + +@keyframes erp-skeleton-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +@media (prefers-reduced-motion: reduce) { + .erp-skeleton { animation: none; } +} + +/* --- Glass Effect --- */ +.erp-glass { + backdrop-filter: blur(12px) saturate(180%); + -webkit-backdrop-filter: blur(12px) saturate(180%); + background-color: rgba(255, 255, 255, 0.78); +} + +/* --- Text Ellipsis --- */ +.erp-text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ==================================================================== + * Layout Utilities + * ==================================================================== */ + +/* Ant Design Menu 主题覆盖 — 侧边栏 */ +.erp-sidebar-menu { + border-inline-end: none !important; +} + +.erp-sidebar-menu .ant-menu-item { + margin: 1px 8px !important; + border-radius: var(--erp-radius-md) !important; + height: 36px !important; + line-height: 36px !important; +} + +.erp-sidebar-menu .ant-menu-submenu-title { + margin: 1px 8px !important; + border-radius: var(--erp-radius-md) !important; + height: 36px !important; + line-height: 36px !important; +} + +.erp-sidebar-menu .ant-menu-item-selected { + background: var(--erp-bg-sidebar-active) !important; + color: var(--erp-text-sidebar-active) !important; +} + +.erp-sidebar-menu .ant-menu-item-selected .anticon { + color: var(--erp-text-sidebar-active) !important; +} + +.erp-sidebar-menu .ant-menu-item:not(.ant-menu-item-selected):hover { + background: var(--erp-bg-sidebar-hover) !important; +} + +/* ==================================================================== + * MainLayout — CSS classes replacing inline styles + * ==================================================================== */ + +/* Sider */ +.erp-sider-dark { + background: var(--erp-bg-sidebar) !important; + border-right: 1px solid var(--erp-border) !important; + position: fixed !important; + left: 0; + top: 0; + bottom: 0; + z-index: 100; + overflow: auto; +} + +/* Logo */ +.erp-sidebar-logo { + height: 56px; + display: flex; + align-items: center; + padding: 0 20px; + border-bottom: 1px solid var(--erp-border); + cursor: pointer; +} + +.ant-layout-sider-collapsed .erp-sidebar-logo { + justify-content: center; + padding: 0; +} + +.erp-sidebar-logo-icon { + width: 28px; + height: 28px; + border-radius: var(--erp-radius-sm); + background: var(--erp-primary); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 13px; + font-weight: 800; + color: #fff; +} + +.erp-sidebar-logo-text { + margin-left: 10px; + color: var(--erp-text-primary); + font-size: 15px; + font-weight: 700; + letter-spacing: -0.3px; + white-space: nowrap; +} + +/* Main layout */ +.erp-main-layout { + background: var(--erp-bg-page) !important; + transition: margin-left 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Header */ +.erp-header { + height: 56px !important; + padding: 0 24px !important; + display: flex !important; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; + z-index: 99; + line-height: 56px !important; + background: var(--erp-bg-container) !important; + border-bottom: 1px solid var(--erp-border) !important; + box-shadow: none; +} + +.erp-header-btn { + width: 32px; + height: 32px; + border-radius: var(--erp-radius-md); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.15s ease; + color: var(--erp-text-secondary); + will-change: background; +} + +.erp-header-btn:hover { background: var(--erp-bg-spotlight); } + +.erp-header-title { + font-size: 15px; + font-weight: 600; + color: var(--erp-text-primary); + font-family: var(--erp-font-heading); +} + +.erp-header-divider { width: 1px; height: 24px; margin: 0 8px; background: var(--erp-border-light); } + +/* User avatar */ +.erp-header-user { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + padding: 4px 8px; + border-radius: var(--erp-radius-sm); + transition: all 0.15s ease; +} + +.erp-header-user:hover { background: var(--erp-bg-spotlight); } + +.erp-user-avatar { + background: var(--erp-primary) !important; + font-size: 13px !important; +} + +.erp-user-name { font-size: 13px; font-weight: 500; color: var(--erp-text-secondary); } + +/* Footer */ +.erp-footer { text-align: center; padding: 12px 24px !important; background: transparent !important; font-size: 12px; color: var(--erp-text-tertiary); } + +/* ==================================================================== + * Dashboard — Stat Cards & Quick Actions + * ==================================================================== */ + +/* Stat Card */ +.erp-stat-card { + background: var(--erp-bg-container); + border-radius: var(--erp-radius-lg); + padding: 20px 24px; + border: 1px solid var(--erp-border-light); + box-shadow: var(--erp-shadow-xs); + position: relative; + overflow: hidden; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform; +} + +.erp-stat-card:hover { + transform: translateY(-2px); + box-shadow: var(--erp-shadow-md); +} + +.erp-stat-card-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--card-gradient, linear-gradient(135deg, var(--erp-primary), var(--erp-primary-hover))); +} + +.erp-stat-card-body { + display: flex; + align-items: center; + justify-content: space-between; +} + +.erp-stat-card-info { flex: 1; } + +.erp-stat-card-title { + font-size: var(--erp-font-size-sm); + color: var(--erp-text-secondary); + margin-bottom: 8px; +} + +.erp-stat-card-value { + font-size: 28px; + font-weight: 700; + color: var(--erp-text-primary); + letter-spacing: -0.5px; + min-height: 36px; + display: flex; + align-items: center; +} + +.erp-stat-card-icon { + width: 48px; + height: 48px; + border-radius: var(--erp-radius-lg); + background: var(--card-icon-bg, var(--erp-primary-bg-subtle)); + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + flex-shrink: 0; +} + +/* Section Header */ +.erp-section-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 20px; +} + +.erp-section-icon { + font-size: 16px; + color: var(--erp-primary); +} + +.erp-section-title { + font-size: 15px; + font-weight: 600; + color: var(--erp-text-primary); +} + +/* Quick Action */ +.erp-quick-action { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-radius: var(--erp-radius-lg); + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease; + background: var(--erp-bg-spotlight); + border: 1px solid var(--erp-border-light); +} + +.erp-quick-action:hover { + background: var(--erp-primary-bg-subtle); + border-color: var(--action-color, var(--erp-primary)); +} + +.erp-quick-action-icon { + width: 36px; + height: 36px; + border-radius: var(--erp-radius-md); + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--action-color, var(--erp-primary)) 8%, transparent); + color: var(--action-color, var(--erp-primary)); + font-size: 16px; + flex-shrink: 0; +} + +.erp-quick-action-label { + font-size: var(--erp-font-size-base); + font-weight: 500; + color: var(--erp-text-secondary); +} + +/* System Info */ +.erp-system-info-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.erp-system-info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 12px; + border-bottom: 1px solid var(--erp-border-light); +} + +.erp-system-info-label { + font-size: var(--erp-font-size-sm); + color: var(--erp-text-secondary); +} + +.erp-system-info-value { + font-size: var(--erp-font-size-sm); + font-weight: 500; + color: var(--erp-text-secondary); +} + +/* ==================================================================== + * Dashboard — Trend Indicators & Enhanced Components + * ==================================================================== */ + +/* Stat Card Trend */ +.erp-stat-card-trend { + display: flex; + align-items: center; + gap: 4px; + margin-top: 8px; + font-size: 12px; + font-weight: 500; +} + +.erp-stat-card-trend-up { color: var(--erp-trend-up); } +.erp-stat-card-trend-down { color: var(--erp-trend-down); } +.erp-stat-card-trend-neutral { color: var(--erp-trend-neutral); } + +.erp-stat-card-trend-label { + color: var(--erp-text-secondary); + font-weight: 400; +} + +/* Stat Card Sparkline */ +.erp-stat-card-sparkline { + margin-top: 12px; + height: 32px; + display: flex; + align-items: flex-end; + gap: 2px; +} + +.erp-stat-card-sparkline-bar { + flex: 1; + border-radius: 2px 2px 0 0; + min-height: 3px; + opacity: 0.4; + transition: opacity 0.15s ease; +} + +.erp-stat-card:hover .erp-stat-card-sparkline-bar { + opacity: 0.7; +} + +/* Quick Action — enhanced */ +.erp-quick-action-icon { + width: 40px; + height: 40px; + border-radius: var(--erp-radius-md); + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--action-color, var(--erp-primary)) 8%, transparent); + color: var(--action-color, var(--erp-primary)); + font-size: 18px; + flex-shrink: 0; + transition: transform 0.15s ease; +} + +.erp-quick-action:hover .erp-quick-action-icon { + transform: scale(1.08); +} + +/* ==================================================================== + * Dashboard — Pending Tasks & Activity Sections + * ==================================================================== */ + +/* Pending Task Item */ +.erp-task-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.erp-task-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border-radius: var(--erp-radius-md); + background: var(--erp-bg-spotlight); + border-left: 3px solid var(--task-color, var(--erp-primary)); + cursor: pointer; + transition: all 0.15s ease; +} + +.erp-task-item:hover { + background: var(--erp-primary-bg-subtle); + transform: translateX(2px); +} + +.erp-task-item-icon { + width: 32px; + height: 32px; + border-radius: var(--erp-radius-sm); + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--task-color, #2563eb) 8%, transparent); + color: var(--task-color, var(--erp-primary)); + font-size: 14px; + flex-shrink: 0; +} + +.erp-task-item-content { flex: 1; min-width: 0; } + +.erp-task-item-title { + font-size: var(--erp-font-size-base); + font-weight: 500; + color: var(--erp-text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.erp-task-item-meta { + display: flex; + align-items: center; + gap: 12px; + margin-top: 2px; + font-size: var(--erp-font-size-xs); + color: var(--erp-text-tertiary); +} + +.erp-task-priority { + display: inline-flex; + align-items: center; + padding: 1px 8px; + border-radius: var(--erp-radius-sm); + font-size: 11px; + font-weight: 600; +} + +.erp-task-priority-high { background: var(--erp-error-bg); color: var(--erp-error); } +.erp-task-priority-medium { background: var(--erp-warning-bg); color: var(--erp-warning); } +.erp-task-priority-low { background: var(--erp-success-bg); color: var(--erp-success); } + +/* Activity Timeline */ +.erp-activity-list { + display: flex; + flex-direction: column; +} + +.erp-activity-item { + display: flex; + gap: 12px; + padding: 10px 0; + position: relative; +} + +.erp-activity-item:not(:last-child)::after { + content: ''; + position: absolute; + left: 15px; + top: 38px; + bottom: -2px; + width: 2px; + background: var(--erp-border-light); +} + +.erp-activity-dot { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: var(--erp-bg-spotlight); + border: 2px solid var(--erp-border-light); + font-size: 12px; + color: var(--erp-text-tertiary); + flex-shrink: 0; + position: relative; + z-index: 1; +} + +.erp-activity-content { flex: 1; min-width: 0; } + +.erp-activity-text { + font-size: var(--erp-font-size-sm); + color: var(--erp-text-secondary); + line-height: 1.5; +} + +.erp-activity-text strong { + color: var(--erp-text-primary); + font-weight: 600; +} + +.erp-activity-time { + font-size: 11px; + color: var(--erp-text-tertiary); + margin-top: 2px; +} + +/* Empty State */ +.erp-empty-state { + display: flex; + flex-direction: column; + align-items: center; + padding: 40px 24px; + text-align: center; +} + +.erp-empty-state-icon { + font-size: 40px; + color: var(--erp-text-tertiary); + margin-bottom: 12px; + opacity: 0.5; +} + +.erp-empty-state-text { + font-size: var(--erp-font-size-sm); + color: var(--erp-text-tertiary); +} + +/* CountUp animation */ +@keyframes erp-count-up { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} + +.erp-count-up { + animation: erp-count-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} diff --git a/apps/web/src/layouts/MainLayout.tsx b/apps/web/src/layouts/MainLayout.tsx new file mode 100644 index 0000000..1e06d25 --- /dev/null +++ b/apps/web/src/layouts/MainLayout.tsx @@ -0,0 +1,375 @@ +import { useCallback, useState, useEffect, useMemo } from 'react'; +import { Layout, Avatar, Space, Dropdown, Tooltip, Spin, theme, Menu } from 'antd'; +import type { MenuItemType, SubMenuType } from 'antd/es/menu/hooks/useItems'; +import { + MenuFoldOutlined, + MenuUnfoldOutlined, + LogoutOutlined, + SearchOutlined, + AppstoreOutlined, + UserOutlined, + RobotOutlined, +} from '@ant-design/icons'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAppStore } from '../stores/app'; +import { useAuthStore } from '../stores/auth'; +import { usePluginStore } from '../stores/plugin'; +import type { PluginMenuGroup } from '../stores/plugin'; +import { getMenusForUser, type MenuInfo } from '../api/menus'; +import { getIcon } from '../utils/iconRegistry'; +import NotificationPanel from '../components/NotificationPanel'; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import AiSidebar from '../components/ai/AiSidebar'; + +const { Header, Sider, Content, Footer } = Layout; + +// 路由标题 fallback — 仅保留后端菜单无法覆盖的路由 +// 1. 动态参数路由(:id/:id/edit)— 菜单表不会存储这些路径 +// 2. 无后端菜单记录的静态页面路由 +const routeTitleFallback: Record = { + // 动态参数路由 + '/health/patients/:id': '患者详情', + '/health/consultations/:id': '咨询详情', + '/health/articles/new': '新建文章', + '/health/articles/:id/edit': '编辑文章', + '/health/care-plans/:id': '护理计划详情', + '/health/shifts/:id': '班次详情', + '/health/ble-gateways/:id': '网关详情', + // 无后端菜单的静态路由 + '/health/follow-up-records': '随访记录', + '/health/article-categories': '分类管理', + '/health/article-tags': '标签管理', + '/health/schedules': '排班管理', + '/health/appointments': '预约管理', +}; + +function getTitleFromMenus(path: string, menus: MenuInfo[]): string | undefined { + for (const m of menus) { + if (m.path === path) return m.title; + if (m.children) { + const found = getTitleFromMenus(path, m.children); + if (found) return found; + } + } + return undefined; +} + +// 将后端 MenuInfo 树转为 Ant Design Menu 的 items 格式 +type AntMenuItem = MenuItemType | SubMenuType; + +function buildMenuItems(menus: MenuInfo[]): AntMenuItem[] { + return menus + .filter((m) => m.visible !== false && m.menu_type !== 'button') + .map((m) => { + const visibleChildren = m.children?.filter((c) => c.visible !== false && c.menu_type !== 'button') || []; + if ((m.menu_type === 'directory') && visibleChildren.length > 0) { + return { + key: m.id, + icon: getIcon(m.icon), + label: m.title, + children: buildMenuItems(visibleChildren), + }; + } + return { + key: m.path || m.id, + icon: getIcon(m.icon), + label: m.title, + }; + }); +} + +// 查找包含指定 path 的所有父级 key(用于自动展开 openKeys) +function findParentKeys(menus: MenuInfo[], targetPath: string): string[] { + const keys: string[] = []; + function walk(items: MenuInfo[], parents: string[]): boolean { + for (const m of items) { + if (m.path === targetPath) { + keys.push(...parents); + return true; + } + if (m.children) { + if (walk(m.children, [...parents, m.id])) return true; + } + } + return false; + } + walk(menus, []); + return keys; +} + +// 插件菜单也纳入 Menu items +function buildPluginItems(groups: PluginMenuGroup[]): AntMenuItem[] { + return groups.map((g) => ({ + key: `plugin-${g.pluginId}`, + icon: , + label: g.pluginName, + children: g.items.map((item) => ({ + key: item.key, + icon: getIcon(item.icon), + label: item.label, + })), + })); +} + +export default function MainLayout({ children }: { children: React.ReactNode }) { + const { sidebarCollapsed, toggleSidebar } = useAppStore(); + const themeConfig = useAppStore((s) => s.themeConfig); + const loadThemeConfig = useAppStore((s) => s.loadThemeConfig); + const { user, logout } = useAuthStore(); + const pluginMenuItems = usePluginStore((s) => s.pluginMenuItems); + const pluginMenuGroups = usePluginStore((s) => s.pluginMenuGroups); + const fetchPlugins = usePluginStore((s) => s.fetchPlugins); + theme.useToken(); + const navigate = useNavigate(); + const location = useLocation(); + const currentPath = location.pathname || '/'; + + // 动态菜单状态 + const [dynamicMenus, setDynamicMenus] = useState([]); + const [menuLoading, setMenuLoading] = useState(true); + const [aiSidebarOpen, setAiSidebarOpen] = useState(false); + + useEffect(() => { + let cancelled = false; + (async () => { + try { + const menus = await getMenusForUser(); + if (!cancelled) { + // 根据用户权限过滤菜单:菜单项声明 permission 时,用户必须有对应权限 + const perms = useAuthStore.getState().permissions; + const isAdmin = useAuthStore.getState().user?.roles?.some((r) => typeof r === 'object' && r.code === 'admin') ?? false; + if (isAdmin) { + setDynamicMenus(menus); + } else { + const filterByPerm = (items: MenuInfo[]): MenuInfo[] => + items + .map((m) => ({ + ...m, + children: m.children ? filterByPerm(m.children) : undefined, + })) + .filter((m) => { + if (m.menu_type === 'directory') return true; + if (!m.permission) return false; + return perms.includes(m.permission); + }) + .filter((m) => m.menu_type === 'directory' || (m.children && m.children.length > 0) || (m.permission && perms.includes(m.permission))); + setDynamicMenus(filterByPerm(menus)); + } + } + } catch { + // fallback: 使用空数组,保留插件菜单 + } + if (!cancelled) setMenuLoading(false); + })(); + return () => { cancelled = true; }; + }, []); + + // 合并动态菜单 + 插件菜单为 Ant Design Menu items + const allMenuItems = useMemo(() => { + const items = buildMenuItems(dynamicMenus); + if (pluginMenuGroups.length > 0) { + items.push(...buildPluginItems(pluginMenuGroups)); + } + return items; + }, [dynamicMenus, pluginMenuGroups]); + + // openKeys: 自动展开包含当前路由的父级 + const autoExpandKeys = useMemo(() => { + const keys = findParentKeys(dynamicMenus, currentPath); + for (const g of pluginMenuGroups) { + if (g.items.some((it) => it.key === currentPath)) { + keys.push(`plugin-${g.pluginId}`); + } + } + return keys; + }, [currentPath, dynamicMenus, pluginMenuGroups]); + + const [openKeys, setOpenKeys] = useState([]); + const [lastExpandedPath, setLastExpandedPath] = useState(currentPath); + if (currentPath !== lastExpandedPath) { + setLastExpandedPath(currentPath); + if (autoExpandKeys.length > 0) { + setOpenKeys((prev) => [...new Set([...prev, ...autoExpandKeys])]); + } + } + + // 加载插件菜单 + useEffect(() => { + fetchPlugins(1, 'running'); + }, [fetchPlugins]); + + // 加载主题配置 + useEffect(() => { + loadThemeConfig(); + }, [loadThemeConfig]); + + const handleLogout = useCallback(async () => { + await logout(); + navigate('/login'); + }, [logout, navigate]); + + // 标题查找:先从动态菜单查找,再 fallback(支持动态路径参数匹配) + const headerTitle = useMemo(() => { + const fromMenus = getTitleFromMenus(currentPath, dynamicMenus); + if (fromMenus) return fromMenus; + // 尝试模式匹配 routeTitleFallback 的 key(如 /health/patients/:id) + for (const [pattern, title] of Object.entries(routeTitleFallback)) { + const regex = new RegExp('^' + pattern.replace(/:[^/]+/g, '[^/]+') + '$'); + if (regex.test(currentPath)) return title; + } + return pluginMenuItems.find((p) => p.key === currentPath)?.label || '页面'; + }, [currentPath, dynamicMenus, pluginMenuItems]); + + const userMenuItems = [ + { + key: 'profile', + icon: , + label: user?.display_name || user?.username || '用户', + disabled: true, + }, + { type: 'divider' as const }, + { + key: 'logout', + icon: , + label: '退出登录', + danger: true, + onClick: handleLogout, + }, + ]; + + const sidebarWidth = sidebarCollapsed ? 72 : 240; + + return ( + + {/* 侧边栏 */} + + {/* Logo 区域 */} +
navigate('/')}> +
H
+ {!sidebarCollapsed && ( + {themeConfig?.brand_name || 'HMS 健康'} + )} +
+ + {/* 菜单 */} + {menuLoading ? ( +
+ +
+ ) : ( + navigate(key)} + className="erp-sidebar-menu" + /> + )} + + + {/* 右侧主区域 */} + + {/* 顶部导航栏 */} +
+ {/* 左侧:折叠按钮 + 标题 */} + +
+ {sidebarCollapsed ? : } +
+ + {headerTitle} + +
+ + {/* 右侧:搜索 + 主题切换 + 通知 + 用户 */} + + +
+ +
+
+ + + + + +
+ + +
+ + {(user?.display_name?.[0] || user?.username?.[0] || 'U').toUpperCase()} + + {!sidebarCollapsed && ( + + {user?.display_name || user?.username || 'User'} + + )} +
+
+ +
+ + {/* 内容区域 */} + +
{children}
+
+ + {/* 底部 */} +
+ {themeConfig?.brand_copyright || 'HMS 健康管理平台'} +
+
+ + {/* AI 助手浮动按钮 + 侧边栏 */} + +
setAiSidebarOpen(true)} + style={{ + position: 'fixed', + right: 24, + bottom: 32, + width: 48, + height: 48, + borderRadius: '50%', + background: 'linear-gradient(135deg, #1677ff 0%, #722ed1 100%)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + boxShadow: '0 4px 12px rgba(22, 119, 255, 0.4)', + zIndex: 1000, + transition: 'transform 0.2s, box-shadow 0.2s', + }} + onMouseEnter={(e) => { + e.currentTarget.style.transform = 'scale(1.1)'; + e.currentTarget.style.boxShadow = '0 6px 16px rgba(22, 119, 255, 0.6)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)'; + e.currentTarget.style.boxShadow = '0 4px 12px rgba(22, 119, 255, 0.4)'; + }} + > + +
+
+ setAiSidebarOpen(false)} /> + + ); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/apps/web/src/pages/Home.tsx b/apps/web/src/pages/Home.tsx new file mode 100644 index 0000000..0313258 --- /dev/null +++ b/apps/web/src/pages/Home.tsx @@ -0,0 +1,179 @@ +import { useEffect, useState, useCallback } from 'react'; +import { Row, Col, Spin, Empty } from 'antd'; +import { + UserOutlined, + FileTextOutlined, + RightOutlined, + PartitionOutlined, + ClockCircleOutlined, + CheckCircleOutlined, + BellOutlined, + SafetyCertificateOutlined, + ApartmentOutlined, +} from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; +import { useThemeMode } from '../hooks/useThemeMode'; +import { useMessageStore } from '../stores/message'; +import { listAuditLogs, type AuditLogItem } from '../api/auditLogs'; +import { listPendingTasks, type TaskInfo } from '../api/workflowTasks'; + +// --- Shared utilities --- + +function formatTimeAgo(dateStr: string): string { + const diff = Date.now() - new Date(dateStr).getTime(); + const minutes = Math.floor(diff / 60000); + if (minutes < 1) return '刚刚'; + if (minutes < 60) return `${minutes} 分钟前`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours} 小时前`; + const days = Math.floor(hours / 24); + return `${days} 天前`; +} + +const ACTION_LABELS: Record = { + create: '创建', created: '创建', update: '更新', updated: '更新', delete: '删除', deleted: '删除', + login: '登录', 'user.create': '创建', 'user.update': '更新', 'user.delete': '删除', +}; +const RESOURCE_LABELS: Record = { + user: '用户', role: '角色', process_instance: '流程实例', organization: '组织', + message: '消息', plugin: '插件', +}; +const RESOURCE_ICONS: Record = { + user: , role: , + organization: , + process_instance: , message: , +}; + +function formatActionLabel(action: string): string { + return ACTION_LABELS[action] || ACTION_LABELS[action.split('.').pop() || ''] || action; +} +function formatResourceLabel(resource: string): string { + return RESOURCE_LABELS[resource] || RESOURCE_LABELS[resource.split('.').pop() || ''] || resource; +} + +// --- Component --- + +export default function Home() { + const navigate = useNavigate(); + const isDark = useThemeMode(); + const fetchUnreadCount = useMessageStore((s) => s.fetchUnreadCount); + + const [pendingTasks, setPendingTasks] = useState([]); + const [recentActivities, setRecentActivities] = useState([]); + const [activitiesLoading, setActivitiesLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + fetchUnreadCount(); + + listPendingTasks(1, 5) + .then((result) => { if (!cancelled) setPendingTasks(result.data); }) + .catch((err) => console.warn('[Home] 获取待办任务失败:', err)); + + listAuditLogs({ page: 1, page_size: 5 }) + .then((result) => { + if (!cancelled) setRecentActivities(result.data.filter((a) => a.action !== 'login_failed')); + }) + .catch((err) => console.warn('[Home] 获取审计日志失败:', err)) + .finally(() => { if (!cancelled) setActivitiesLoading(false); }); + + return () => { cancelled = true; }; + }, []); + + const handleNavigate = useCallback((path: string) => { + navigate(path); + }, [navigate]); + + return ( +
+ {/* 欢迎语 */} +
+

+ 工作台 +

+

+ 待办任务与最近动态 +

+
+ + {/* 待办任务 + 最近动态 */} + + +
+
+ + 待办任务 + + {pendingTasks.length} 项待处理 + +
+
+ {pendingTasks.length === 0 ? ( + + ) : ( + pendingTasks.map((task) => ( +
handleNavigate('/workflow')} + role="button" + tabIndex={0} + onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate('/workflow'); }} + > +
+
+
{task.node_name || task.definition_name || '流程任务'}
+
+ {task.definition_name || '工作流'} + {task.status === 'pending' ? '待处理' : task.status} +
+
+ 一般 + +
+ )) + )} +
+
+ + + +
+
+ + 最近动态 +
+
+ {activitiesLoading ? ( +
+ ) : recentActivities.length === 0 ? ( + + ) : ( + recentActivities.map((log) => ( +
+
+ {RESOURCE_ICONS[log.resource_type] || } +
+
+
+ {formatActionLabel(log.action)}了{formatResourceLabel(log.resource_type)} +
+
{formatTimeAgo(log.created_at)}
+
+
+ )) + )} +
+
+ +
+
+ ); +} diff --git a/apps/web/src/pages/Login.tsx b/apps/web/src/pages/Login.tsx new file mode 100644 index 0000000..a54c221 --- /dev/null +++ b/apps/web/src/pages/Login.tsx @@ -0,0 +1,117 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Form, Input, Button, message, Divider } from 'antd'; +import { UserOutlined, LockOutlined, SafetyCertificateOutlined } from '@ant-design/icons'; +import { useAuthStore } from '../stores/auth'; +import { handleApiError } from '../api/client'; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import { getPublicBrand, type BrandConfig } from '../api/themes'; + +export default function Login() { + const navigate = useNavigate(); + const login = useAuthStore((s) => s.login); + const loading = useAuthStore((s) => s.loading); + const [messageApi, contextHolder] = message.useMessage(); + const [brand, setBrand] = useState(null); + + useEffect(() => { + getPublicBrand().then(setBrand); + }, []); + + const onFinish = async (values: { username: string; password: string }) => { + try { + await login(values.username, values.password); + messageApi.success('登录成功'); + navigate('/'); + } catch (err: unknown) { + handleApiError(err, '登录失败,请检查用户名和密码'); + } + }; + + return ( +
+ {contextHolder} + + {/* 左侧品牌展示区 */} +
+
+
+ +
+
+ +
+ +

{brand?.brand_name || 'HMS 健康管理平台'}

+

{brand?.brand_slogan || '新一代健康管理平台'}

+

{brand?.brand_features || '患者管理 · 健康监测 · 随访管理 · AI 智能分析'}

+ +
+ {[ + { label: '多租户架构', value: 'SaaS' }, + { label: '模块化设计', value: '可插拔' }, + { label: '事件驱动', value: '可扩展' }, + ].map((item) => ( +
+
{item.value}
+
{item.label}
+
+ ))} +
+
+
+ + {/* 右侧登录表单区 */} +
+
+ +
+ +
+

欢迎回来

+

请登录您的账户以继续

+ + + +
+ + } + placeholder="用户名" + style={{ height: 44, borderRadius: 'var(--erp-radius-md)' }} + /> + + + } + placeholder="密码" + style={{ height: 44, borderRadius: 'var(--erp-radius-md)' }} + /> + + + + +
+ +
+ {brand?.brand_copyright || 'HMS 健康管理平台 · ©汕头市智界科技有限公司'} +
+
+
+
+ ); +} diff --git a/apps/web/src/pages/Messages.tsx b/apps/web/src/pages/Messages.tsx new file mode 100644 index 0000000..7311610 --- /dev/null +++ b/apps/web/src/pages/Messages.tsx @@ -0,0 +1,72 @@ +import { useState } from 'react'; +import { Tabs } from 'antd'; +import { BellOutlined, MailOutlined, FileTextOutlined, SettingOutlined } from '@ant-design/icons'; +import NotificationList from './messages/NotificationList'; +import MessageTemplates from './messages/MessageTemplates'; +import NotificationPreferences from './messages/NotificationPreferences'; +import type { MessageQuery } from '../api/messages'; + +const UNREAD_FILTER: MessageQuery = { is_read: false }; + +export default function Messages() { + const [activeKey, setActiveKey] = useState('all'); + + return ( +
+
+
+

消息中心

+
管理站内消息、模板和通知偏好
+
+
+ + + + 全部消息 + + ), + children: , + }, + { + key: 'unread', + label: ( + + + 未读消息 + + ), + children: , + }, + { + key: 'templates', + label: ( + + + 消息模板 + + ), + children: , + }, + { + key: 'preferences', + label: ( + + + 通知设置 + + ), + children: , + }, + ]} + /> +
+ ); +} diff --git a/apps/web/src/pages/Organizations.tsx b/apps/web/src/pages/Organizations.tsx new file mode 100644 index 0000000..a0f6155 --- /dev/null +++ b/apps/web/src/pages/Organizations.tsx @@ -0,0 +1,371 @@ +import { useState, useCallback, useEffect, useRef } from 'react'; +import { + Tree, + Button, + Space, + Form, + Input, + InputNumber, + Table, + Popconfirm, + Empty, + Tag, +} from 'antd'; +import { + PlusOutlined, + DeleteOutlined, + EditOutlined, + ApartmentOutlined, +} from '@ant-design/icons'; +import type { DataNode } from 'antd/es/tree'; +import { useThemeMode } from '../hooks/useThemeMode'; +import { DrawerForm } from '../components/DrawerForm'; +import { useCrudDrawer } from '../hooks/useCrudDrawer'; +import { useApiRequest } from '../hooks/useApiRequest'; +import { + listOrgTree, + createOrg, + updateOrg, + deleteOrg, + listDeptTree, + createDept, + deleteDept, + listPositions, + createPosition, + deletePosition, + type OrganizationInfo, + type DepartmentInfo, + type PositionInfo, +} from '../api/orgs'; + +export default function Organizations() { + const isDark = useThemeMode(); + const { execute } = useApiRequest(); + + const cardStyle = { + background: isDark ? '#111827' : '#FFFFFF', + borderRadius: 12, + border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`, + }; + + // --- Org tree state --- + const [orgTree, setOrgTree] = useState([]); + const [selectedOrg, setSelectedOrg] = useState(null); + + // --- Department tree state --- + const [deptTree, setDeptTree] = useState([]); + const [selectedDept, setSelectedDept] = useState(null); + + // --- Position list state --- + const [positions, setPositions] = useState([]); + + // --- Ref for drawer onSuccess callback (avoids before-declaration issue) --- + const refreshOrgTreeRef = useRef<() => void>(() => {}); + + // --- Fetch org tree --- + const fetchOrgTree = useCallback(async () => { + try { + const tree = await listOrgTree(); + setOrgTree(tree); + if (selectedOrg) { + const stillExists = findOrgInTree(tree, selectedOrg.id); + if (!stillExists) { setSelectedOrg(null); setDeptTree([]); setPositions([]); } + } + } catch { /* silent */ } + }, [selectedOrg]); + + refreshOrgTreeRef.current = () => { fetchOrgTree(); }; + + useEffect(() => { fetchOrgTree(); }, [fetchOrgTree]); + + // --- Dept Drawer --- + const [deptDrawerOpen, setDeptDrawerOpen] = useState(false); + + // --- Position Drawer --- + const [positionDrawerOpen, setPositionDrawerOpen] = useState(false); + + // --- Org Drawer (uses ref to avoid before-declaration) --- + const orgDrawer = useCrudDrawer({ + getId: (r) => r.id, + onCreate: async (values) => { + await createOrg({ ...(values as { name: string; code?: string; sort_order?: number }), parent_id: selectedOrg?.id }); + }, + onUpdate: async (id, values) => { + await updateOrg(id, values as { name: string; code?: string; sort_order?: number; version: number }); + }, + onSuccess: () => { refreshOrgTreeRef.current(); }, + }); + + // --- Fetch dept tree --- + const fetchDeptTree = useCallback(async () => { + if (!selectedOrg) return; + try { + const tree = await listDeptTree(selectedOrg.id); + setDeptTree(tree); + if (selectedDept) { + const stillExists = findDeptInTree(tree, selectedDept.id); + if (!stillExists) { setSelectedDept(null); setPositions([]); } + } + } catch { /* silent */ } + }, [selectedOrg, selectedDept]); + + useEffect(() => { fetchDeptTree(); }, [fetchDeptTree]); + + // --- Fetch positions --- + const fetchPositions = useCallback(async () => { + if (!selectedDept) return; + try { + setPositions(await listPositions(selectedDept.id)); + } catch { /* silent */ } + }, [selectedDept]); + + useEffect(() => { fetchPositions(); }, [fetchPositions]); + + // --- Org handlers --- + const handleDeleteOrg = async (id: string) => { + await execute(() => deleteOrg(id), '组织已删除'); + setSelectedOrg(null); setDeptTree([]); setPositions([]); + fetchOrgTree(); + }; + + // --- Dept handlers --- + const handleCreateDept = async (values: Record) => { + if (!selectedOrg) return; + await execute(() => createDept(selectedOrg.id, { + name: values.name as string, code: values.code as string | undefined, + parent_id: selectedDept?.id, sort_order: values.sort_order as number | undefined, + }), '部门创建成功'); + setDeptDrawerOpen(false); + fetchDeptTree(); + }; + + const handleDeleteDept = async (id: string) => { + await execute(() => deleteDept(id), '部门已删除'); + setSelectedDept(null); setPositions([]); + fetchDeptTree(); + }; + + // --- Position handlers --- + const handleCreatePosition = async (values: Record) => { + if (!selectedDept) return; + await execute(() => createPosition(selectedDept.id, { + name: values.name as string, code: values.code as string | undefined, + level: values.level as number | undefined, sort_order: values.sort_order as number | undefined, + }), '岗位创建成功'); + setPositionDrawerOpen(false); + fetchPositions(); + }; + + const handleDeletePosition = async (id: string) => { + await execute(() => deletePosition(id), '岗位已删除'); + fetchPositions(); + }; + + // --- Tree node converters --- + const convertOrgTree = (items: OrganizationInfo[]): DataNode[] => + items.map((item) => ({ + key: item.id, + title: {item.name} {item.code && {item.code}}, + children: convertOrgTree(item.children), + })); + + const convertDeptTree = (items: DepartmentInfo[]): DataNode[] => + items.map((item) => ({ + key: item.id, + title: {item.name} {item.code && {item.code}}, + children: convertDeptTree(item.children), + })); + + const onSelectOrg = (selectedKeys: React.Key[]) => { + if (selectedKeys.length === 0) { setSelectedOrg(null); setDeptTree([]); setSelectedDept(null); setPositions([]); return; } + setSelectedOrg(findOrgInTree(orgTree, selectedKeys[0] as string)); + setSelectedDept(null); setPositions([]); + }; + + const onSelectDept = (selectedKeys: React.Key[]) => { + if (selectedKeys.length === 0) { setSelectedDept(null); setPositions([]); return; } + setSelectedDept(findDeptInTree(deptTree, selectedKeys[0] as string)); + }; + + const positionColumns = [ + { title: '岗位名称', dataIndex: 'name', key: 'name' }, + { title: '编码', dataIndex: 'code', key: 'code', render: (v?: string) => v || '-' }, + { title: '级别', dataIndex: 'level', key: 'level' }, + { title: '排序', dataIndex: 'sort_order', key: 'sort_order' }, + { + title: '操作', key: 'actions', + render: (_: unknown, record: PositionInfo) => ( + handleDeletePosition(record.id)}> + + + ), + }, + ]; + + return ( +
+
+
+

组织架构管理

+
管理组织、部门和岗位的层级结构
+
+
+ +
+ {/* 左栏:组织树 */} +
+
+ 组织 + +
+
+ {orgTree.length > 0 ? ( + + ) : } +
+
+ + {/* 中栏:部门树 */} +
+
+ {selectedOrg ? `${selectedOrg.name} · 部门` : '部门'} + {selectedOrg && ( + +
+
+ {selectedOrg ? ( + deptTree.length > 0 ? ( + + ) : + ) : } +
+
+ + {/* 右栏:岗位表 */} +
+
+ {selectedDept ? `${selectedDept.name} · 岗位` : '岗位'} + {selectedDept && ( + + )} +
+
+ {selectedDept ? ( + + ) :
} + + + + + {/* Org Drawer */} + + + + + + + + + + + {/* Dept Drawer */} + setDeptDrawerOpen(false)} + onSubmit={handleCreateDept} + initialValues={{ sort_order: 0 }} + loading={false} + width={480} + columns={1} + > + + + + + + + + + + {/* Position Drawer */} + setPositionDrawerOpen(false)} + onSubmit={handleCreatePosition} + initialValues={{ level: 1, sort_order: 0 }} + loading={false} + width={480} + columns={1} + > + + + + + + + + + + + + + ); +} + +function findOrgInTree(tree: OrganizationInfo[], id: string): OrganizationInfo | null { + for (const item of tree) { + if (item.id === id) return item; + const found = findOrgInTree(item.children, id); + if (found) return found; + } + return null; +} + +function findDeptInTree(tree: DepartmentInfo[], id: string): DepartmentInfo | null { + for (const item of tree) { + if (item.id === id) return item; + const found = findDeptInTree(item.children, id); + if (found) return found; + } + return null; +} diff --git a/apps/web/src/pages/PluginAdmin.tsx b/apps/web/src/pages/PluginAdmin.tsx new file mode 100644 index 0000000..b4b1f0d --- /dev/null +++ b/apps/web/src/pages/PluginAdmin.tsx @@ -0,0 +1,409 @@ +import { useEffect, useState, useCallback } from 'react'; +import { + Table, + Button, + Space, + Tag, + message, + Upload, + Modal, + Input, + Drawer, + Descriptions, + Popconfirm, + Form, + Tabs, + theme, +} from 'antd'; +import { + UploadOutlined, + PlayCircleOutlined, + PauseCircleOutlined, + CloudDownloadOutlined, + DeleteOutlined, + ReloadOutlined, + HeartOutlined, + SettingOutlined, +} from '@ant-design/icons'; +import type { PluginInfo, PluginStatus, PluginSchemaResponse } from '../api/plugins'; +import { + listPlugins, + uploadPlugin, + installPlugin, + enablePlugin, + disablePlugin, + uninstallPlugin, + purgePlugin, + getPluginHealth, + getPluginSchema, + updatePluginConfig, +} from '../api/plugins'; +import PluginSettingsForm from '../components/PluginSettingsForm'; + +const STATUS_CONFIG: Record = { + uploaded: { color: '#475569', label: '已上传' }, + installed: { color: '#2563EB', label: '已安装' }, + enabled: { color: '#059669', label: '已启用' }, + running: { color: '#059669', label: '运行中' }, + disabled: { color: '#dc2626', label: '已禁用' }, + uninstalled: { color: '#9333EA', label: '已卸载' }, +}; + +export default function PluginAdmin() { + const [plugins, setPlugins] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [loading, setLoading] = useState(false); + const [uploadModalOpen, setUploadModalOpen] = useState(false); + const [manifestText, setManifestText] = useState(''); + const [wasmFile, setWasmFile] = useState(null); + const [detailPlugin, setDetailPlugin] = useState(null); + const [schemaData, setSchemaData] = useState(null); + const [healthDetail, setHealthDetail] = useState | null>(null); + const [actionLoading, setActionLoading] = useState(null); + const { token } = theme.useToken(); + + const fetchPlugins = useCallback(async (p = page) => { + setLoading(true); + try { + const result = await listPlugins(p); + setPlugins(result.data); + setTotal(result.total); + } catch { + message.error('加载插件列表失败'); + } + setLoading(false); + }, [page]); + + useEffect(() => { + fetchPlugins(); + }, [fetchPlugins]); + + // 打开详情时加载 schema(含 settings) + useEffect(() => { + if (!detailPlugin) { + setSchemaData(null); + return; + } + getPluginSchema(detailPlugin.id) + .then(setSchemaData) + .catch(() => setSchemaData(null)); + }, [detailPlugin]); + + const handleUpload = async () => { + if (!wasmFile || !manifestText.trim()) { + message.warning('请选择 WASM 文件并填写 Manifest'); + return; + } + try { + await uploadPlugin(wasmFile, manifestText); + message.success('插件上传成功'); + setUploadModalOpen(false); + setWasmFile(null); + setManifestText(''); + fetchPlugins(); + } catch { + message.error('插件上传失败'); + } + }; + + const handleAction = async (id: string, action: () => Promise, label: string) => { + setActionLoading(id); + try { + await action(); + message.success(`${label}成功`); + fetchPlugins(); + if (detailPlugin?.id === id) { + setDetailPlugin(null); + } + } catch { + message.error(`${label}失败`); + } + setActionLoading(null); + }; + + const handleHealthCheck = async (id: string) => { + try { + const result = await getPluginHealth(id); + setHealthDetail(result.details); + } catch { + message.error('健康检查失败'); + } + }; + + const getActions = (record: PluginInfo) => { + const id = record.id; + const btns: React.ReactNode[] = []; + + switch (record.status) { + case 'uploaded': + btns.push( + , + ); + break; + case 'installed': + btns.push( + , + ); + break; + case 'enabled': + case 'running': + btns.push( + , + ); + break; + case 'disabled': + btns.push( + , + , + ); + break; + } + + return btns; + }; + + const columns = [ + { title: '名称', dataIndex: 'name', key: 'name', width: 180 }, + { title: '版本', dataIndex: 'version', key: 'version', width: 80 }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (status: PluginStatus) => { + const cfg = STATUS_CONFIG[status] || { color: '#475569', label: status }; + return {cfg.label}; + }, + }, + { title: '作者', dataIndex: 'author', key: 'author', width: 120 }, + { + title: '描述', + dataIndex: 'description', + key: 'description', + ellipsis: true, + }, + { + title: '操作', + key: 'action', + width: 320, + render: (_: unknown, record: PluginInfo) => ( + + {getActions(record)} + + handleAction(record.id, async () => { await purgePlugin(record.id); return record; }, '清除')} + > + + + + ), + }, + ]; + + return ( +
+
+ + + + +
+ +
setPage(p), + showTotal: (t) => `共 ${t} 个插件`, + }} + /> + + setUploadModalOpen(false)} + okText="上传" + width={600} + > +
+ + { + setWasmFile(file); + return false; + }} + maxCount={1} + accept=".wasm" + fileList={[]} + onRemove={() => setWasmFile(null)} + > + + + + + setManifestText(e.target.value)} + placeholder="[metadata] +id = "my-plugin" +name = "我的插件" +version = "0.1.0"" + /> + + +
+ + { + setDetailPlugin(null); + setHealthDetail(null); + setSchemaData(null); + }} + width={500} + > + {detailPlugin && ( + + + {detailPlugin.id} + {detailPlugin.name} + {detailPlugin.version} + + + {STATUS_CONFIG[detailPlugin.status]?.label || detailPlugin.status} + + + {detailPlugin.author || '-'} + {detailPlugin.description || '-'} + {detailPlugin.installed_at || '-'} + {detailPlugin.enabled_at || '-'} + {detailPlugin.entities.length} + +
+ + {healthDetail && ( +
+                          {JSON.stringify(healthDetail, null, 2)}
+                        
+ )} +
+ + ), + }, + ...(schemaData?.settings + ? [ + { + key: 'settings', + label: ( + + 配置 + + ), + children: ( + } + recordVersion={detailPlugin.record_version} + onSave={async (config, version) => { + const updated = await updatePluginConfig( + detailPlugin.id, + config, + version, + ); + setDetailPlugin({ ...detailPlugin, ...updated }); + }} + readOnly={detailPlugin.status !== 'enabled' && detailPlugin.status !== 'running'} + /> + ), + }, + ] + : []), + ]} + /> + )} +
+ + ); +} diff --git a/apps/web/src/pages/PluginCRUDPage.tsx b/apps/web/src/pages/PluginCRUDPage.tsx new file mode 100644 index 0000000..ec545cb --- /dev/null +++ b/apps/web/src/pages/PluginCRUDPage.tsx @@ -0,0 +1 @@ +export { default } from './PluginCRUDPage/PluginCRUDPageInner'; diff --git a/apps/web/src/pages/PluginCRUDPage/DetailDrawer.tsx b/apps/web/src/pages/PluginCRUDPage/DetailDrawer.tsx new file mode 100644 index 0000000..122f931 --- /dev/null +++ b/apps/web/src/pages/PluginCRUDPage/DetailDrawer.tsx @@ -0,0 +1,102 @@ +import { Drawer, Descriptions, Tag } from 'antd'; +import type { PluginFieldSchema, PluginEntitySchema, PluginSectionSchema } from '../../api/plugins'; +import PluginCRUDPageInner from './PluginCRUDPageInner'; + +interface DetailDrawerProps { + open: boolean; + record: Record | null; + displayName: string; + fields: PluginFieldSchema[]; + sections: PluginSectionSchema[]; + allEntities: PluginEntitySchema[]; + pluginId: string; + entityName: string; + onClose: () => void; +} + +export default function DetailDrawer({ + open, + record, + displayName, + fields, + sections, + allEntities, + pluginId, + entityName: _entityName, + onClose, +}: DetailDrawerProps) { + if (!record) return null; + + return ( + + {sections.length > 0 ? ( + sections.map((section, idx) => { + if (section.type === 'fields') { + return ( +
+

{section.label}

+ + {section.fields.map((fieldName) => { + const fieldDef = fields.find((f) => f.name === fieldName); + const val = record[fieldName]; + return ( + + {typeof val === 'boolean' ? ( + val ? : + ) : ( + String(val ?? '-') + )} + + ); + })} + +
+ ); + } + if (section.type === 'crud') { + const secEntity = allEntities.find((e) => e.name === section.entity); + return ( +
+

{section.label}

+ {secEntity && ( + + )} +
+ ); + } + return null; + }) + ) : ( + + {fields.map((field) => { + const val = record[field.name]; + return ( + + {typeof val === 'boolean' ? ( + val ? : + ) : ( + String(val ?? '-') + )} + + ); + })} + + )} +
+ ); +} diff --git a/apps/web/src/pages/PluginCRUDPage/ImportModal.tsx b/apps/web/src/pages/PluginCRUDPage/ImportModal.tsx new file mode 100644 index 0000000..d7ff577 --- /dev/null +++ b/apps/web/src/pages/PluginCRUDPage/ImportModal.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react'; +import { Modal, Upload, Alert, Button, message } from 'antd'; +import { importPluginData, type ImportResult } from '../../api/pluginData'; + +interface ImportModalProps { + open: boolean; + pluginId: string; + entityName: string; + onClose: () => void; + onSuccess: () => void; +} + +export default function ImportModal({ open, pluginId, entityName, onClose, onSuccess }: ImportModalProps) { + const [importing, setImporting] = useState(false); + const [importResult, setImportResult] = useState(null); + + const handleClose = () => { + onClose(); + setImportResult(null); + }; + + return ( + 关闭 + ) : null} + destroyOnHidden + > + {importResult ? ( +
+ 0 ? 'warning' : 'success'} + message={`导入完成:成功 ${importResult.success_count} 条,失败 ${importResult.error_count} 条`} + style={{ marginBottom: 16 }} + /> + {importResult.errors.length > 0 && ( +
+

错误详情

+ {importResult.errors.map((err, i) => ( + + ))} +
+ )} +
+ ) : ( + { + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const text = e.target?.result as string; + const rows = JSON.parse(text); + if (!Array.isArray(rows)) { + message.error('文件格式错误:需要 JSON 数组'); + return; + } + setImporting(true); + const result = await importPluginData(pluginId, entityName, rows); + setImportResult(result); + if (result.success_count > 0) onSuccess(); + } catch { + message.error('文件解析失败,请确认格式为 JSON 数组'); + } + setImporting(false); + }; + reader.readAsText(file); + return false; + }} + showUploadList={false} + disabled={importing} + > +

+ {importing ? '导入中...' : '点击或拖拽 JSON 文件到此处'} +

+

支持 JSON 数组格式,单次上限 1000 行

+
+ )} +
+ ); +} diff --git a/apps/web/src/pages/PluginCRUDPage/PluginCRUDPageInner.tsx b/apps/web/src/pages/PluginCRUDPage/PluginCRUDPageInner.tsx new file mode 100644 index 0000000..848292c --- /dev/null +++ b/apps/web/src/pages/PluginCRUDPage/PluginCRUDPageInner.tsx @@ -0,0 +1,488 @@ +import { useState, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { + Table, + Button, + Space, + Modal, + Form, + Input, + InputNumber, + DatePicker, + Switch, + Select, + Tag, + message, + Popconfirm, + Segmented, + Timeline, + Dropdown, +} from 'antd'; +import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + ReloadOutlined, + EyeOutlined, + DownloadOutlined, + UploadOutlined, +} from '@ant-design/icons'; +import { + createPluginData, + updatePluginData, + deletePluginData, + batchPluginData, + exportPluginData, + exportPluginDataAsBlob, +} from '../../api/pluginData'; +import EntitySelect from '../../components/EntitySelect'; +import type { PluginFieldSchema } from '../../api/plugins'; +import { evaluateVisibleWhen } from '../../utils/exprEvaluator'; +import { usePluginData } from './usePluginData'; +import DetailDrawer from './DetailDrawer'; +import ImportModal from './ImportModal'; + +const { Search } = Input; +const { TextArea } = Input; + +interface PluginCRUDPageProps { + pluginIdOverride?: string; + entityOverride?: string; + filterField?: string; + filterValue?: string; + enableViews?: string[]; + compact?: boolean; +} + +export default function PluginCRUDPageInner({ + pluginIdOverride, + entityOverride, + filterField, + filterValue, + enableViews: enableViewsProp, + compact, +}: PluginCRUDPageProps = {}) { + const routeParams = useParams<{ pluginId: string; entityName: string }>(); + const pluginId = pluginIdOverride || routeParams.pluginId || ''; + const entityName = entityOverride || routeParams.entityName || ''; + + const { + records, total, page, loading, fields, displayName, + sortBy, sortOrder, + resolvedLabels, labelMeta, + entityDef, allEntities, detailSections, + hasDetailPage, filterableFields, + setPage, setSortBy, setSortOrder, + fetchData, + } = usePluginData(pluginId, entityName, filterField, filterValue); + + const [modalOpen, setModalOpen] = useState(false); + const [editRecord, setEditRecord] = useState | null>(null); + const [form] = Form.useForm(); + const [formValues, setFormValues] = useState>({}); + + const [viewMode, setViewMode] = useState('table'); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const [detailOpen, setDetailOpen] = useState(false); + const [detailRecord, setDetailRecord] = useState | null>(null); + + const [importModalOpen, setImportModalOpen] = useState(false); + const [exporting, setExporting] = useState(false); + + const enableViews = enableViewsProp || + (() => { + return ['table']; + })(); + + const handleSubmit = async (values: Record) => { + if (!pluginId || !entityName) return; + const { _id, _version, ...data } = values as Record & { + _id?: string; + _version?: number; + }; + + try { + if (editRecord) { + await updatePluginData( + pluginId, entityName, + editRecord._id as string, data, + editRecord._version as number, + ); + message.success('更新成功'); + } else { + await createPluginData(pluginId, entityName, data); + message.success('创建成功'); + } + setModalOpen(false); + setEditRecord(null); + fetchData(); + } catch { + message.error('操作失败'); + } + }; + + const handleDelete = async (record: Record) => { + if (!pluginId || !entityName) return; + try { + await deletePluginData(pluginId, entityName, record._id as string); + message.success('删除成功'); + fetchData(); + } catch { + message.error('删除失败'); + } + }; + + const handleBatchDelete = async () => { + if (!pluginId || !entityName || selectedRowKeys.length === 0) return; + try { + await batchPluginData(pluginId, entityName, { + action: 'delete', + ids: selectedRowKeys, + }); + message.success(`已删除 ${selectedRowKeys.length} 条记录`); + setSelectedRowKeys([]); + fetchData(); + } catch { + message.error('批量删除失败'); + } + }; + + const columns = useMemo(() => [ + ...fields.slice(0, 5).map((f) => ({ + title: f.display_name || f.name, + dataIndex: f.name, + key: f.name, + ellipsis: true, + sorter: f.sortable ? true : undefined, + render: (val: unknown) => { + if (typeof val === 'boolean') return val ? : ; + if (f.ref_entity) { + const uuid = String(val ?? ''); + if (!uuid || uuid === '-') return '-'; + const label = resolvedLabels[f.name]?.[uuid]; + const installed = labelMeta[f.name]?.plugin_installed !== false; + if (!installed) return {f.ref_fallback_label || '外部引用'}; + if (label === null) return 无效引用; + if (label) return {label}; + } + return String(val ?? '-'); + }, + })), + { + title: '操作', + key: 'action', + width: hasDetailPage ? 200 : 150, + render: (_: unknown, record: Record) => ( + + {hasDetailPage && ( + + )} + + handleDelete(record)}> + + + + ), + }, + ], [fields, resolvedLabels, labelMeta, hasDetailPage, handleDelete]); + + const renderFormField = (field: PluginFieldSchema) => { + const widget = field.ui_widget || field.field_type; + switch (widget) { + case 'number': + case 'integer': + case 'float': + case 'decimal': + return ; + case 'boolean': + return ; + case 'date': + case 'datetime': + return ; + case 'select': + return ( + + ); + case 'textarea': + return