From aa4b14181c2612054ac6c97d1278ba95085a32b6 Mon Sep 17 00:00:00 2001
From: root <13910913995@163.com>
Date: Mon, 19 Jan 2026 14:27:41 +0800
Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?=
=?UTF-8?q?=E7=9B=AE=E5=B9=B6=E6=B7=BB=E5=8A=A0=20.gitignore?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 62 +
.trae/skills/code-reviewer/SKILL.md | 61 +
.trae/skills/pr-reviewer/SKILL.md | 86 +
DB_PERFORMANCE_ANALYSIS.md | 38 +
INSTALL_DB.md | 64 +
README.md | 66 +
backend/__init__.py | 1 +
backend/api/__init__.py | 1 +
backend/api/routes.py | 54 +
backend/api/schemas.py | 29 +
backend/api/ws.py | 19 +
backend/build_backend.ps1 | 50 +
backend/config/__init__.py | 1 +
backend/config/config.ini | 12 +
backend/config/settings.py | 101 +
backend/database/__init__.py | 1 +
backend/database/check_db.py | 66 +
backend/database/engine.py | 18 +
backend/database/schema.py | 224 +
backend/database/test_db.py | 153 +
backend/device/__init__.py | 1 +
backend/device/base.py | 25 +
backend/device/mock_vehicle.py | 89 +
backend/main.py | 149 +
backend/requirements.txt | 8 +
backend/requirements_build.txt | 2 +
backend/services/__init__.py | 1 +
backend/services/broadcaster.py | 30 +
backend/services/server_monitor.py | 127 +
backend/services/simulation_manager.py | 144 +
backend/smartedt_backend.spec | 95 +
backend/utils.py | 54 +
frontend/build_frontend.ps1 | 65 +
frontend/index.html | 13 +
frontend/main/main.cjs | 308 ++
frontend/package-lock.json | 5699 ++++++++++++++++++++++++
frontend/package.json | 60 +
frontend/query | 1 +
frontend/src/App.vue | 81 +
frontend/src/lib/api.js | 26 +
frontend/src/lib/ws.js | 15 +
frontend/src/main.js | 36 +
frontend/src/pages/BigDashboard.vue | 54 +
frontend/src/pages/BigSimMonitor.vue | 53 +
frontend/src/pages/BigSystemIntro.vue | 16 +
frontend/src/pages/CarSim.vue | 36 +
frontend/src/pages/ControlAdmin.vue | 16 +
frontend/src/pages/ControlAnalysis.vue | 16 +
frontend/src/pages/ControlConfig.vue | 76 +
frontend/src/pages/ControlOperate.vue | 51 +
frontend/src/pages/ControlQuery.vue | 16 +
frontend/src/pages/ServerMonitor.vue | 191 +
frontend/src/stores/simulation.js | 76 +
frontend/vite.config.js | 16 +
unity3D/unity开发的代码 | 2 +
智能电动车数字孪生系统功能规划2026.pdf | Bin 0 -> 681208 bytes
系统开发技术方案.md | 416 ++
系统框架结构评估.md | 66 +
58 files changed, 9237 insertions(+)
create mode 100644 .gitignore
create mode 100644 .trae/skills/code-reviewer/SKILL.md
create mode 100644 .trae/skills/pr-reviewer/SKILL.md
create mode 100644 DB_PERFORMANCE_ANALYSIS.md
create mode 100644 INSTALL_DB.md
create mode 100644 README.md
create mode 100644 backend/__init__.py
create mode 100644 backend/api/__init__.py
create mode 100644 backend/api/routes.py
create mode 100644 backend/api/schemas.py
create mode 100644 backend/api/ws.py
create mode 100644 backend/build_backend.ps1
create mode 100644 backend/config/__init__.py
create mode 100644 backend/config/config.ini
create mode 100644 backend/config/settings.py
create mode 100644 backend/database/__init__.py
create mode 100644 backend/database/check_db.py
create mode 100644 backend/database/engine.py
create mode 100644 backend/database/schema.py
create mode 100644 backend/database/test_db.py
create mode 100644 backend/device/__init__.py
create mode 100644 backend/device/base.py
create mode 100644 backend/device/mock_vehicle.py
create mode 100644 backend/main.py
create mode 100644 backend/requirements.txt
create mode 100644 backend/requirements_build.txt
create mode 100644 backend/services/__init__.py
create mode 100644 backend/services/broadcaster.py
create mode 100644 backend/services/server_monitor.py
create mode 100644 backend/services/simulation_manager.py
create mode 100644 backend/smartedt_backend.spec
create mode 100644 backend/utils.py
create mode 100644 frontend/build_frontend.ps1
create mode 100644 frontend/index.html
create mode 100644 frontend/main/main.cjs
create mode 100644 frontend/package-lock.json
create mode 100644 frontend/package.json
create mode 100644 frontend/query
create mode 100644 frontend/src/App.vue
create mode 100644 frontend/src/lib/api.js
create mode 100644 frontend/src/lib/ws.js
create mode 100644 frontend/src/main.js
create mode 100644 frontend/src/pages/BigDashboard.vue
create mode 100644 frontend/src/pages/BigSimMonitor.vue
create mode 100644 frontend/src/pages/BigSystemIntro.vue
create mode 100644 frontend/src/pages/CarSim.vue
create mode 100644 frontend/src/pages/ControlAdmin.vue
create mode 100644 frontend/src/pages/ControlAnalysis.vue
create mode 100644 frontend/src/pages/ControlConfig.vue
create mode 100644 frontend/src/pages/ControlOperate.vue
create mode 100644 frontend/src/pages/ControlQuery.vue
create mode 100644 frontend/src/pages/ServerMonitor.vue
create mode 100644 frontend/src/stores/simulation.js
create mode 100644 frontend/vite.config.js
create mode 100644 unity3D/unity开发的代码
create mode 100644 智能电动车数字孪生系统功能规划2026.pdf
create mode 100644 系统开发技术方案.md
create mode 100644 系统框架结构评估.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..980232a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,62 @@
+# ===== OS / Editor =====
+.DS_Store
+Thumbs.db
+Desktop.ini
+*.swp
+*.swo
+*.tmp
+*.bak
+
+.idea/
+.vscode/
+
+# ===== Logs =====
+*.log
+
+# ===== Python =====
+__pycache__/
+*.py[cod]
+*.pyo
+*.pyd
+.Python
+.pytest_cache/
+.mypy_cache/
+.ruff_cache/
+.coverage
+coverage.xml
+htmlcov/
+*.egg-info/
+dist/
+build/
+
+.venv/
+venv/
+env/
+ENV/
+
+.env
+.env.*
+!.env.example
+
+# ===== Backend (PyInstaller outputs) =====
+/backend/build/
+/backend/dist/
+/backend/venv/
+
+# ===== Node / Frontend =====
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+/.turbo/
+/.cache/
+
+/frontend/node_modules/
+/frontend/dist/
+/frontend/release/
+
+# ===== Electron =====
+*.asar
+
diff --git a/.trae/skills/code-reviewer/SKILL.md b/.trae/skills/code-reviewer/SKILL.md
new file mode 100644
index 0000000..3d76b23
--- /dev/null
+++ b/.trae/skills/code-reviewer/SKILL.md
@@ -0,0 +1,61 @@
+---
+name: "code-reviewer"
+description: "审查代码的正确性、安全性、可维护性与一致性。用户要求“代码审查/Review”、准备合并、或你完成较大改动后调用。"
+---
+
+# Code Reviewer(代码审查)
+
+## 何时使用
+
+- 用户明确提出“代码审查/Review/帮我看看这段代码/有没有坑”
+- 你完成了较大改动(多文件、涉及业务逻辑/安全/性能/并发/数据库/鉴权),准备交付前自检
+- 修复 bug 后,需要确认没有引入回归
+
+## 输入要求(你需要我提供什么)
+
+优先按可获得的信息审查,不强制全部具备:
+
+- 需要审查的范围:文件路径/目录/提交 diff/粘贴代码片段
+- 目标与约束:功能期望、性能目标、兼容性、上线环境、风格偏好
+- 相关上下文:调用链入口、接口契约(请求/响应)、数据库表/索引、配置项
+
+## 审查维度(检查清单)
+
+### 正确性
+- 边界条件、空值、异常路径、重试/幂等、并发与竞态
+- 输入校验与错误信息一致性
+- 资源释放(文件句柄、连接、锁)、超时设置
+
+### 安全性
+- 注入风险(SQL/命令/模板/路径穿越)、反序列化风险
+- 鉴权/鉴别:敏感操作是否校验权限,是否存在越权路径
+- 机密信息:日志/错误信息/返回值中是否泄露 token、密码、密钥、PII
+
+### 可维护性
+- 代码结构是否清晰,职责是否单一,命名是否表达意图
+- 重复逻辑是否可抽取,是否遵循既有项目模式
+- 配置/常量是否集中管理,错误码/异常是否统一
+
+### 性能与稳定性
+- 热路径复杂度、N+1、无界循环/递归、潜在阻塞 I/O
+- 缓存策略、批处理、分页、索引友好性
+- 观测性:日志粒度、指标点、错误上下文是否足够定位
+
+### 风格与一致性
+- 与仓库既有格式、依赖、工具链一致(类型、lint、格式化、文件组织)
+- 公共工具与约定(例如 logger、配置、错误处理)是否复用而非自造
+
+## 输出格式(你应如何给出审查结果)
+
+按“可执行、可落地”的方式输出,默认用中文:
+
+1. 结论摘要(风险等级:高/中/低)
+2. 必须修复(Blockers):每条包含 位置、问题、影响、建议修复
+3. 建议改进(Suggestions):同上,但不阻塞
+4. 可选优化(Nice-to-have)
+5. 若适用:给出最小改动的补丁方案(优先)或替代实现思路
+
+## 约束
+
+- 不凭空假设依赖已存在;若引入新依赖,先确认仓库已有或给出替代方案
+- 不输出或建议写入任何密钥/token
diff --git a/.trae/skills/pr-reviewer/SKILL.md b/.trae/skills/pr-reviewer/SKILL.md
new file mode 100644
index 0000000..e295f13
--- /dev/null
+++ b/.trae/skills/pr-reviewer/SKILL.md
@@ -0,0 +1,86 @@
+---
+name: "pr-reviewer"
+description: "对变更/PR 做代码审查并给出可执行修改建议。用户要求代码评审、准备合并、或完成较大改动后自检时调用。"
+---
+
+# PR Reviewer(变更/PR 代码审查)
+
+## 目标
+
+- 快速识别高风险缺陷(正确性/安全/稳定性/兼容性)
+- 以“可落地修改”为导向给出建议(位置、原因、影响、最小修复)
+- 在不引入不必要依赖的前提下,尽量复用仓库现有模式与工具
+
+## 何时使用
+
+- 用户要求“评审这个 PR/diff/改动”
+- 你完成了多文件或关键路径改动,准备合并前自检
+- 修复 bug 或安全问题后,需要确认无回归
+
+## 你需要获取的信息(尽量自己补齐)
+
+优先使用现有上下文完成评审,不强制用户补全:
+
+- 变更范围:diff/文件列表/目录
+- 预期行为:功能目标、边界条件、失败语义
+- 运行环境:语言版本、部署方式、配置来源、数据库/外部依赖
+- 质量门槛:性能目标、SLA、兼容性、测试要求
+
+## 审查方法(按优先级)
+
+1. 先看变更入口与数据流:输入→处理→持久化/外部调用→输出
+2. 先找会导致事故的点:鉴权、注入、并发、资源泄露、回滚困难
+3. 再看可维护性:重复、耦合、命名、可读性、错误处理一致性
+4. 最后看体验与一致性:日志、可观测性、风格、文档与配置
+
+## 检查清单
+
+### 正确性
+
+- 边界条件:空值/缺省、极端长度、时区与编码、浮点与精度
+- 错误路径:异常是否被吞、错误码与消息是否一致、重试/幂等性
+- 并发:竞态、锁粒度、事务边界、重复提交
+- 资源:连接/句柄/任务是否可控,是否有超时与取消
+
+### 安全性
+
+- 注入:SQL/命令/模板/路径穿越/正则 DoS
+- 鉴权:敏感操作是否校验权限与主体一致性,是否存在越权路径
+- 会话:token 处理是否安全(存储/传输/日志/错误信息)
+- 密钥:不得输出或建议写入任何密钥/token/密码/私钥/PII
+
+### 稳定性与性能
+
+- 热路径复杂度、N+1、无界队列/循环、阻塞 I/O
+- 缓存/分页/批处理是否合理,索引友好性
+- 外部依赖:熔断/退避/限流/超时/重试策略是否一致
+
+### 可维护性与一致性
+
+- 是否复用项目既有抽象(配置、logger、错误处理、领域模型)
+- 是否引入了不必要的新依赖;若必须引入,说明理由与替代方案
+- API/DTO/Schema 是否向后兼容;破坏性变更是否标注与迁移路径
+
+### 测试与验证
+
+- 是否覆盖关键分支与失败路径
+- 是否新增或更新了与变更一致的测试(单测/集成/端到端)
+- 是否需要补充回归用例(最小复现)
+
+## 输出格式(必须可执行)
+
+按以下结构输出,中文为默认:
+
+1. 结论摘要(风险:高/中/低,是否建议合并:是/否/有条件)
+2. Blockers(必须修复):每条包含
+ - 位置:文件 + 行号范围(若可获得)
+ - 问题:一句话描述
+ - 影响:会导致什么
+ - 修复:最小改动建议(必要时给补丁)
+3. Suggestions(建议改进):同上,但不阻塞
+4. Tests(建议验证):列出要跑/要补的测试与覆盖点
+
+## 约束
+
+- 不凭空假设依赖或环境已存在;不确定时先在仓库中确认
+- 不输出或建议写入任何密钥/token
diff --git a/DB_PERFORMANCE_ANALYSIS.md b/DB_PERFORMANCE_ANALYSIS.md
new file mode 100644
index 0000000..d321a9d
--- /dev/null
+++ b/DB_PERFORMANCE_ANALYSIS.md
@@ -0,0 +1,38 @@
+# TimescaleDB 性能测试分析报告
+
+基于 `test_db.py` 对 30 万条车辆仿真数据的写入与查询测试,本次性能评估结论如下:
+
+## 1. 核心结论
+
+**TimescaleDB 完全满足智能电动车数字孪生系统(SmartEDT)的实时采集与回放需求。**
+
+- **写入能力**:单线程下 **3,500+ TPS** 的写入速度远超单车采集需求(通常单车高频信号仅需 50-100Hz,即 50-100 TPS)。即使并发接入 10-20 台车,当前配置也绰绰有余。
+- **查询响应**:**毫秒级(30ms)** 的时间切片查询能力,保证了“前端实时回放”与“大屏曲线刷新”的流畅度,不会出现卡顿。
+- **存储架构**:JSONB 混合存储方案在保持了极高灵活性的同时(0.2s 过滤查询),依然维持了优秀的查询性能,验证了“结构化字段(索引)+ 半结构化 Payload(JSONB)”建模方案的正确性。
+
+## 2. 详细指标分析
+
+### 2.1 写入性能 (Insertion)
+* **指标**:3,547.78 条/秒 (TPS)
+* **场景**:单连接、Batch=1000、Python `asyncpg` 驱动。
+* **分析**:
+ * **满足度**:假设单车采集频率为 50Hz(即每秒 50 个数据包),当前性能理论上可支持 **70 台车同时在线采集**。
+ * **瓶颈推测**:当前瓶颈主要在 Python 端的序列化与网络 IO 往返(RTT)。
+ * **优化空间**:如果未来需要支持上千台车,改用 `COPY` 协议或增加并发写入 worker,吞吐量可轻松提升至 5-10 万 TPS 以上。
+
+### 2.2 查询性能 (Query)
+
+| 查询类型 | 耗时 | 业务场景 | 评价 |
+| :--- | :--- | :--- | :--- |
+| **最新数据 (Latest 1000)** | **0.0301s** | 实时大屏监控、轨迹回放 | **极优**。利用 TimescaleDB 的时间分区索引,无论历史数据多大,提取“最新 N 条”永远是毫秒级。 |
+| **全量计数 (Count)** | 0.0845s | 报表统计、数据完整性校验 | **优秀**。30万数据秒出,说明元数据管理高效。 |
+| **JSONB 内容过滤** | 0.2137s | 复杂故障诊断、特定工况筛选 | **良好**。在没有对 JSON 内部字段建索引的情况下,0.2s 扫描 30 万条 JSON 数据属于高性能表现。 |
+
+## 3. 架构建议
+
+基于测试结果,对后续开发提出以下建议:
+
+1. **保持 JSONB 方案**:当前的表结构(`ts`, `simulation_id`, `signals(JSONB)`)在性能与灵活性之间取得了完美平衡,无需拆分更多列。
+2. **索引优化**:目前 `ts` 和 `simulation_id` 已有索引。如果未来经常需要按“车速”或“故障码”过滤,建议对 JSONB 内部高频查询字段建立 **GIN 索引**。
+ * *示例 SQL*:`CREATE INDEX idx_speed ON vehicle_signals USING gin ((signals->'vehicle_speed_kmh'));`
+3. **保留策略**:建议配置 TimescaleDB 的 `retention_policy`,例如自动删除 30 天前的原始高频数据,只保留降采样后的聚合数据,以节省磁盘空间。
diff --git a/INSTALL_DB.md b/INSTALL_DB.md
new file mode 100644
index 0000000..e28b044
--- /dev/null
+++ b/INSTALL_DB.md
@@ -0,0 +1,64 @@
+# 数据库安装指南 (Windows)
+
+由于本项目依赖 **TimescaleDB** 时序数据库插件,而该插件目前在 Windows 上**仅官方支持到 PostgreSQL 17**(暂未提供适配 PostgreSQL 18 的 Windows 安装包),因此我们需要安装 **PostgreSQL 17**。
+
+## 方案一:使用 Docker(强烈推荐)
+
+如果您已安装 Docker Desktop,这是最简单的方法,无需配置环境。
+
+1. 确保 Docker Desktop 已启动。
+2. 在项目根目录打开终端,运行:
+ ```powershell
+ docker-compose up -d
+ ```
+3. 完成!数据库已在端口 `5432` 启动,且已包含 TimescaleDB。
+
+---
+
+## 方案二:本机手动安装
+
+如果您必须在 Windows 本机安装,请严格按照以下步骤操作。
+
+### 第一步:安装 PostgreSQL 17
+
+1. **下载**:[PostgreSQL 17.2 Windows x64 安装程序](https://get.enterprisedb.com/postgresql/postgresql-17.2-1-windows-x64.exe)
+ - 或者访问官网:https://www.postgresql.org/download/windows/
+2. **安装**:
+ - 运行安装程序。
+ - **记住您设置的密码**(后续配置需要用到,建议设为 `postgres` 或修改项目配置)。
+ - 端口保持默认 `5432`。
+ - 安装目录建议保持默认(如 `C:\Program Files\PostgreSQL\17`)。
+ - **Stack Builder**:安装结束后会询问是否运行 Stack Builder,**取消勾选**,我们不需要它。
+
+### 第二步:安装 TimescaleDB 插件
+
+1. **下载**:[TimescaleDB v2.23.0 for PostgreSQL 17 (Windows zip)](https://github.com/timescale/timescaledb/releases/download/2.23.0/timescaledb-postgresql-17-windows-amd64.zip)
+ - 备用链接:访问 [GitHub Releases](https://github.com/timescale/timescaledb/releases),找到 `timescaledb-postgresql-17-windows-amd64.zip`。
+2. **解压**:
+ - 将压缩包解压到一个临时文件夹。
+3. **安装**:
+ - 在解压后的文件夹中找到 `setup.exe`。
+ - **右键 -> 以管理员身份运行**。
+ - 按照提示操作:
+ - 输入 PostgreSQL 的安装路径(通常会自动检测)。
+ - 输入 `postgres` 用户的密码。
+ - 允许它修改 `postgresql.conf` 配置(输入 `y`)。
+4. **重启服务**:
+ - 打开 Windows 服务管理器(Win+R -> `services.msc`)。
+ - 找到 `postgresql-x64-17` 服务。
+ - 右键 -> **重新启动**。
+
+### 第三步:验证安装
+
+打开项目目录下的 `tools/check_db.py`(如果不存在可手动创建测试),或者使用 `psql`:
+
+```powershell
+psql -U postgres
+```
+
+在 SQL 命令行中输入:
+```sql
+CREATE EXTENSION IF NOT EXISTS timescaledb;
+SELECT * FROM timescaledb_information.hypertables;
+```
+如果不报错,说明安装成功。
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ca09175
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# SmartEDT(智能电动车数字孪生系统)代码框架
+
+本仓库提供一套可扩展的系统骨架代码,用于支撑《系统开发技术方案.md》中定义的后端采集处理与桌面端多屏显示能力。
+
+## 目录结构
+
+```
+SmartEDT/
+ backend/
+ frontend/
+ 系统开发技术方案.md
+```
+
+## 后端(Python 3.13.1 + FastAPI + WebSocket + PostgreSQL/TimescaleDB)
+
+### 1) 环境准备
+
+- 建议使用 venv:
+ - `python -m venv venv`
+ - `venv\Scripts\activate`
+ - `pip install -r backend/requirements.txt`
+
+### 2) 配置
+
+- 复制示例配置:
+ - `copy backend\\config.ini.example backend\\config.ini`
+- 按需修改 `[DATABASE].url`(示例密码为占位符 `CHANGE_ME`)。
+
+也可以通过环境变量覆盖:
+- `SMARTEDT_DATABASE_URL`
+- `SMARTEDT_FILE_ROOT`
+- `SMARTEDT_HOST`
+- `SMARTEDT_PORT`
+
+### 3) 数据库
+
+需要 PostgreSQL 并安装 TimescaleDB 扩展(数据库侧操作)。后端启动时会尝试:
+- `CREATE EXTENSION IF NOT EXISTS timescaledb`
+- `create_hypertable('vehicle_signals', 'ts', if_not_exists => TRUE)`
+
+### 4) 启动
+
+- 开发启动:
+ - `python backend/main.py --host 127.0.0.1 --port 5000 --debug`
+
+接口:
+- `GET /health`
+- `POST /api/simulation/start`
+- `POST /api/simulation/{simulation_id}/stop`
+- `WS /ws`
+
+## 前端(Electron + Vue)
+
+### 1) 安装依赖
+
+在 `frontend/` 目录下执行:
+- `npm install`
+
+### 2) 开发启动(Vite + Electron)
+
+- `npm run dev`
+
+Electron 主进程会尝试启动后端:
+- 默认使用 `python backend/main.py`
+- 可通过 `SMARTEDT_PYTHON` 指定 Python 可执行文件路径
+
diff --git a/backend/__init__.py b/backend/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/api/__init__.py b/backend/api/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/api/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/api/routes.py b/backend/api/routes.py
new file mode 100644
index 0000000..20976d8
--- /dev/null
+++ b/backend/api/routes.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+from fastapi import APIRouter, Depends, HTTPException
+from fastapi.responses import FileResponse
+
+from backend.api.schemas import HealthResponse, SimulationStartRequest, SimulationStartResponse, SimulationStopResponse
+from backend.services.simulation_manager import SimulationManager
+from backend.utils import safe_join
+
+
+def get_router(simulation_manager: SimulationManager, file_root: Path) -> APIRouter:
+ router = APIRouter()
+
+ @router.get("/health", response_model=HealthResponse)
+ async def health() -> HealthResponse:
+ return HealthResponse()
+
+ @router.get("/api/devices")
+ async def devices():
+ runtime = simulation_manager.current()
+ return {
+ "data": [
+ {
+ "device_id": "controlbox_01",
+ "device_type": "mock_vehicle",
+ "connected": bool(runtime and runtime.status == "running"),
+ }
+ ]
+ }
+
+ @router.post("/api/simulation/start", response_model=SimulationStartResponse)
+ async def start_simulation(body: SimulationStartRequest) -> SimulationStartResponse:
+ simulation_id = await simulation_manager.start(body.model_dump())
+ return SimulationStartResponse(simulation_id=simulation_id)
+
+ @router.post("/api/simulation/{simulation_id}/stop", response_model=SimulationStopResponse)
+ async def stop_simulation(simulation_id: str) -> SimulationStopResponse:
+ await simulation_manager.stop(simulation_id)
+ return SimulationStopResponse(simulation_id=simulation_id, status="stopped")
+
+ @router.get("/files/{file_path:path}")
+ async def files(file_path: str):
+ try:
+ resolved = safe_join(file_root, file_path)
+ except ValueError:
+ raise HTTPException(status_code=400, detail="invalid path")
+ if not resolved.exists() or not resolved.is_file():
+ raise HTTPException(status_code=404, detail="not found")
+ return FileResponse(str(resolved))
+
+ return router
+
diff --git a/backend/api/schemas.py b/backend/api/schemas.py
new file mode 100644
index 0000000..19791c4
--- /dev/null
+++ b/backend/api/schemas.py
@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+from typing import Any
+
+from pydantic import BaseModel, Field
+
+
+class HealthResponse(BaseModel):
+ status: str = "ok"
+
+
+class SimulationStartRequest(BaseModel):
+ scenario: str | None = None
+ weather: str | None = None
+ time_period: str | None = None
+ max_speed_kmh: int | None = Field(default=None, ge=0, le=300)
+ duration_minutes: int | None = Field(default=None, ge=1, le=360)
+ driver: str | None = None
+ extra: dict[str, Any] = Field(default_factory=dict)
+
+
+class SimulationStartResponse(BaseModel):
+ simulation_id: str
+
+
+class SimulationStopResponse(BaseModel):
+ simulation_id: str
+ status: str
+
diff --git a/backend/api/ws.py b/backend/api/ws.py
new file mode 100644
index 0000000..4f81703
--- /dev/null
+++ b/backend/api/ws.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from fastapi import WebSocket, WebSocketDisconnect
+
+from backend.services.broadcaster import Broadcaster
+
+
+async def websocket_handler(ws: WebSocket, broadcaster: Broadcaster) -> None:
+ await ws.accept()
+ await broadcaster.add(ws)
+ try:
+ while True:
+ await ws.receive_text()
+ except WebSocketDisconnect:
+ # 客户端正常断开连接,无需打印堆栈信息
+ pass
+ finally:
+ await broadcaster.remove(ws)
+
diff --git a/backend/build_backend.ps1 b/backend/build_backend.ps1
new file mode 100644
index 0000000..9c09616
--- /dev/null
+++ b/backend/build_backend.ps1
@@ -0,0 +1,50 @@
+# Ensure we are in the backend directory
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+Set-Location $ScriptDir
+
+Write-Host "Starting Backend Build Process..." -ForegroundColor Cyan
+
+# Define paths
+$VenvPath = Join-Path $ScriptDir "venv"
+$PythonExe = Join-Path $VenvPath "Scripts\python.exe"
+$PipExe = Join-Path $VenvPath "Scripts\pip.exe"
+$PyInstallerExe = Join-Path $VenvPath "Scripts\pyinstaller.exe"
+$SpecFile = "smartedt_backend.spec"
+
+# 1. Check/Create Virtual Environment
+if (-not (Test-Path $VenvPath)) {
+ Write-Host "Virtual environment not found. Creating..." -ForegroundColor Yellow
+ python -m venv venv
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to create virtual environment."
+ exit 1
+ }
+} else {
+ Write-Host "Using existing virtual environment." -ForegroundColor Green
+}
+
+# 2. Install Dependencies
+Write-Host "Installing/Updating build dependencies..." -ForegroundColor Yellow
+& $PipExe install -r requirements.txt
+& $PipExe install -r requirements_build.txt
+if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to install dependencies."
+ exit 1
+}
+
+# 3. Clean previous builds
+Write-Host "Cleaning up previous builds..." -ForegroundColor Yellow
+if (Test-Path "dist") { Remove-Item -Recurse -Force "dist" }
+if (Test-Path "build") { Remove-Item -Recurse -Force "build" }
+
+# 4. Run PyInstaller
+Write-Host "Running PyInstaller..." -ForegroundColor Cyan
+& $PyInstallerExe --clean --noconfirm $SpecFile
+
+if ($LASTEXITCODE -eq 0) {
+ Write-Host "`nBackend build successful!" -ForegroundColor Green
+ Write-Host "Executable located at: $(Join-Path $ScriptDir 'dist\smartedt_backend\smartedt_backend.exe')" -ForegroundColor Green
+} else {
+ Write-Error "`nBackend build failed!"
+ exit 1
+}
diff --git a/backend/config/__init__.py b/backend/config/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/config/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/config/config.ini b/backend/config/config.ini
new file mode 100644
index 0000000..e6635c6
--- /dev/null
+++ b/backend/config/config.ini
@@ -0,0 +1,12 @@
+[SERVER]
+host = 0.0.0.0
+port = 5000
+debug = True
+
+[FILEPATH]
+path = data
+
+[DATABASE]
+url = postgresql+psycopg://smartedt:postgres@127.0.0.1:5432/smartedt
+timescaledb = True
+
diff --git a/backend/config/settings.py b/backend/config/settings.py
new file mode 100644
index 0000000..1d6f473
--- /dev/null
+++ b/backend/config/settings.py
@@ -0,0 +1,101 @@
+from __future__ import annotations
+
+import configparser
+import os
+from dataclasses import dataclass
+from pathlib import Path
+
+
+@dataclass(frozen=True)
+class ServerSettings:
+ host: str = "0.0.0.0"
+ port: int = 5000
+ debug: bool = False
+
+
+@dataclass(frozen=True)
+class FileSettings:
+ root_path: Path
+
+
+@dataclass(frozen=True)
+class DatabaseSettings:
+ url: str
+ timescaledb: bool = True
+
+
+@dataclass(frozen=True)
+class AppSettings:
+ server: ServerSettings
+ files: FileSettings
+ database: DatabaseSettings
+
+
+import sys
+
+def _find_config_file() -> Path | None:
+ # Handle PyInstaller frozen state
+ if getattr(sys, 'frozen', False):
+ # If onefile, _MEIPASS. If onedir, executable dir or _internal
+ # With onedir, the exe is in root, internal files in _internal.
+ # But our spec put config in backend/config
+
+ # Check relative to executable
+ exe_dir = Path(sys.executable).parent
+ candidates = [
+ exe_dir / "backend" / "config" / "config.ini",
+ exe_dir / "_internal" / "backend" / "config" / "config.ini",
+ exe_dir / "config.ini",
+ ]
+ for path in candidates:
+ if path.exists():
+ return path
+
+ candidates = [
+ Path(__file__).resolve().parent / "config.ini",
+ Path(__file__).resolve().parents[1] / "config.ini",
+ Path(__file__).resolve().parents[2] / "config.ini",
+ ]
+ for path in candidates:
+ if path.exists():
+ return path
+ return None
+
+
+def load_settings() -> AppSettings:
+ config = configparser.ConfigParser()
+ config_path = os.getenv("SMARTEDT_CONFIG")
+ if config_path:
+ config.read(config_path, encoding="utf-8")
+ else:
+ found = _find_config_file()
+ if found:
+ config.read(found, encoding="utf-8")
+
+ server = ServerSettings(
+ host=config.get("SERVER", "host", fallback=os.getenv("SMARTEDT_HOST", "0.0.0.0")),
+ port=config.getint("SERVER", "port", fallback=int(os.getenv("SMARTEDT_PORT", "5000"))),
+ debug=config.getboolean("SERVER", "debug", fallback=os.getenv("SMARTEDT_DEBUG", "False").lower() == "true"),
+ )
+
+ default_root = Path(os.getenv("SMARTEDT_FILE_ROOT", "data"))
+ root_value = config.get("FILEPATH", "path", fallback=str(default_root))
+ root_path = Path(root_value)
+ if not root_path.is_absolute():
+ root_path = (Path(__file__).resolve().parents[1] / root_path).resolve()
+
+ database_url = config.get("DATABASE", "url", fallback=os.getenv("SMARTEDT_DATABASE_URL", "")).strip()
+ if not database_url:
+ database_url = "postgresql+psycopg://smartedt:CHANGE_ME@127.0.0.1:5432/smartedt"
+ timescaledb = config.getboolean(
+ "DATABASE",
+ "timescaledb",
+ fallback=os.getenv("SMARTEDT_TIMESCALEDB", "True").lower() == "true",
+ )
+
+ return AppSettings(
+ server=server,
+ files=FileSettings(root_path=root_path),
+ database=DatabaseSettings(url=database_url, timescaledb=timescaledb),
+ )
+
diff --git a/backend/database/__init__.py b/backend/database/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/database/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/database/check_db.py b/backend/database/check_db.py
new file mode 100644
index 0000000..0ca5625
--- /dev/null
+++ b/backend/database/check_db.py
@@ -0,0 +1,66 @@
+import os
+import sys
+
+def check_database():
+ print("正在检查数据库连接...")
+
+ # 连接参数:通过环境变量覆盖(不在输出中打印密码)
+ user = os.getenv("PG_USER", "smartedt")
+ password = os.getenv("PG_PASSWORD", "postgres")
+ host = os.getenv("PG_HOST", "127.0.0.1")
+ port = os.getenv("PG_PORT", "5432")
+ dbname = "smartedt"
+
+ try:
+ import psycopg
+ conn = psycopg.connect(
+ dbname=dbname,
+ user=user,
+ password=password,
+ host=host,
+ port=port,
+ autocommit=True,
+ )
+ server_version = getattr(conn.info, "server_version", None)
+ print(f"✅ 成功连接到 PostgreSQL 数据库 '{dbname}' (用户: {user}, v{server_version})")
+
+ cur = conn.cursor()
+
+ # 检查 TimescaleDB 扩展是否已安装
+ print("正在检查 TimescaleDB 扩展...")
+ try:
+ # 尝试创建扩展(如果不存在)
+ cur.execute("CREATE EXTENSION IF NOT EXISTS timescaledb;")
+ print("✅ TimescaleDB 扩展加载成功")
+
+ # 检查版本
+ cur.execute("SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';")
+ version = cur.fetchone()
+ if version:
+ print(f"✅ TimescaleDB 版本: {version[0]}")
+ else:
+ print("❌ 未找到 TimescaleDB 版本信息")
+
+ except Exception as e:
+ print(f"❌ TimescaleDB 检查失败: {e}")
+
+ cur.close()
+ conn.close()
+ return True
+
+ except Exception as e:
+ print(f"❌ 连接失败: {e}")
+ print("\n可能有以下原因:")
+ print("1. 数据库服务未启动 (请运行 'net start postgresql-x64-17')")
+ print("2. 密码错误 (请设置环境变量 PG_PASSWORD)")
+ print("3. 端口被占用或配置不同")
+ return False
+
+if __name__ == "__main__":
+ try:
+ import psycopg # noqa: F401
+ except ImportError:
+ print("缺少依赖 psycopg,请先安装后再运行该脚本。")
+ raise
+
+ check_database()
diff --git a/backend/database/engine.py b/backend/database/engine.py
new file mode 100644
index 0000000..145ec5b
--- /dev/null
+++ b/backend/database/engine.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
+
+from backend.config.settings import DatabaseSettings
+
+
+def create_engine(settings: DatabaseSettings) -> AsyncEngine:
+ return create_async_engine(
+ settings.url,
+ pool_pre_ping=True,
+ future=True,
+ )
+
+
+def create_session_factory(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]:
+ return async_sessionmaker(engine, expire_on_commit=False)
+
diff --git a/backend/database/schema.py b/backend/database/schema.py
new file mode 100644
index 0000000..b9b7ffa
--- /dev/null
+++ b/backend/database/schema.py
@@ -0,0 +1,224 @@
+from __future__ import annotations
+
+from datetime import datetime
+
+from sqlalchemy import JSON, BigInteger, Boolean, Column, DateTime, Float, ForeignKey, Index, String, Table, text
+from sqlalchemy.dialects.postgresql import JSONB
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
+
+
+class Base(DeclarativeBase):
+ pass
+
+
+class Simulation(Base):
+ __tablename__ = "simulations"
+
+ simulation_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="仿真 ID")
+ status: Mapped[str] = mapped_column(String(32), index=True, comment="仿真状态(running/stopped 等)")
+ started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True, comment="开始时间(UTC)")
+ ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="结束时间(UTC)")
+ scenario_name: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True, comment="仿真场景名称")
+ scenario_config: Mapped[dict] = mapped_column(JSON, default=dict, comment="仿真场景配置(JSON)")
+ config_created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="配置创建时间(UTC)")
+ operator: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True, comment="仿真操作员")
+ archived: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否归档")
+
+
+vehicle_signals = Table(
+ "sim_vehicle_signals",
+ Base.metadata,
+ Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="信号采样时间(UTC)"),
+ Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
+ Column("device_id", String(64), nullable=False, index=True, comment="设备 ID"),
+ Column("seq", BigInteger, nullable=False, comment="信号序列号(单仿真内递增)"),
+ Column("signals", JSONB, nullable=False, comment="车辆信号载荷(JSONB)"),
+ Index("idx_vehicle_signals_sim_ts", "simulation_id", "ts"),
+ comment="车辆信号时序数据(TimescaleDB hypertable)",
+)
+
+unity_vehicle_frames = Table(
+ "sim_unity_vehicle_frames",
+ Base.metadata,
+ Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="帧时间(UTC)"),
+ Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
+ Column("vehicle_id", String(64), nullable=False, index=True, comment="虚拟车辆 ID"),
+ Column("seq", BigInteger, nullable=False, comment="帧序号(单仿真单车内递增)"),
+ Column("pos_x", Float, nullable=False, comment="位置 X(世界坐标)"),
+ Column("pos_y", Float, nullable=False, comment="位置 Y(世界坐标)"),
+ Column("pos_z", Float, nullable=False, comment="位置 Z(世界坐标)"),
+ Column("rot_x", Float, nullable=False, comment="旋转四元数 X"),
+ Column("rot_y", Float, nullable=False, comment="旋转四元数 Y"),
+ Column("rot_z", Float, nullable=False, comment="旋转四元数 Z"),
+ Column("rot_w", Float, nullable=False, comment="旋转四元数 W"),
+ Column("lin_vel_x", Float, nullable=True, comment="线速度 X(可选)"),
+ Column("lin_vel_y", Float, nullable=True, comment="线速度 Y(可选)"),
+ Column("lin_vel_z", Float, nullable=True, comment="线速度 Z(可选)"),
+ Column("ang_vel_x", Float, nullable=True, comment="角速度 X(可选)"),
+ Column("ang_vel_y", Float, nullable=True, comment="角速度 Y(可选)"),
+ Column("ang_vel_z", Float, nullable=True, comment="角速度 Z(可选)"),
+ Column("controls", JSONB, nullable=True, comment="控制量(油门/刹车/方向/档位等,JSONB)"),
+ Column("extra", JSONB, nullable=True, comment="扩展字段(仿真引擎自定义,JSONB)"),
+ Index("idx_unity_frames_sim_vehicle_ts", "simulation_id", "vehicle_id", "ts"),
+ comment="虚拟车辆驱动仿真帧数据(用于 Unity 车辆模型运动与回放,TimescaleDB hypertable)",
+)
+
+screen_recordings = Table(
+ "sim_screen_videos",
+ Base.metadata,
+ Column("video_id", String(64), primary_key=True, comment="录制文件记录 ID"),
+ Column("simulation_id", String(64), nullable=False, index=True, comment="仿真 ID"),
+ Column("screen_type", String(32), nullable=False, index=True, comment="屏幕类型(big_screen/vehicle_screen 等)"),
+ Column("source_name", String(64), nullable=True, index=True, comment="录制源名称(可选,如设备号/通道号)"),
+ Column("status", String(32), nullable=False, index=True, comment="状态(recording/ready/failed 等)"),
+ Column("relative_path", String(1024), nullable=False, comment="相对文件根目录的路径(用于下载/归档)"),
+ Column("file_name", String(255), nullable=True, comment="文件名(可选)"),
+ Column("format", String(32), nullable=True, comment="容器格式(mp4/mkv 等)"),
+ Column("codec", String(64), nullable=True, comment="编码信息(H264/H265 等)"),
+ Column("width", BigInteger, nullable=True, comment="视频宽度(像素)"),
+ Column("height", BigInteger, nullable=True, comment="视频高度(像素)"),
+ Column("fps", Float, nullable=True, comment="帧率(可选)"),
+ Column("duration_ms", BigInteger, nullable=True, comment="时长(毫秒,可选)"),
+ Column("size_bytes", BigInteger, nullable=True, comment="文件大小(字节,可选)"),
+ Column("recorded_started_at", DateTime(timezone=True), nullable=True, index=True, comment="录制开始时间(UTC,可选)"),
+ Column("recorded_ended_at", DateTime(timezone=True), nullable=True, index=True, comment="录制结束时间(UTC,可选)"),
+ Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="记录创建时间(UTC)"),
+ Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
+ Index("idx_screen_recordings_sim_screen_created", "simulation_id", "screen_type", "created_at"),
+ Index("idx_screen_recordings_sim_screen_time", "simulation_id", "screen_type", "recorded_started_at"),
+ comment="仿真过程屏幕录制文件元数据(显示大屏/车载屏等)",
+)
+
+sys_role = Table(
+ "sys_role",
+ Base.metadata,
+ Column("role_id", String(64), primary_key=True, comment="角色 ID"),
+ Column("role_name", String(64), nullable=False, unique=True, index=True, comment="角色名称(唯一)"),
+ Column("role_desc", String(255), nullable=True, comment="角色描述"),
+ Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
+ Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
+ Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
+ Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
+ comment="系统角色",
+)
+
+sys_permission = Table(
+ "sys_permission",
+ Base.metadata,
+ Column("perm_code", String(128), primary_key=True, comment="权限编码(唯一)"),
+ Column("perm_name", String(128), nullable=False, index=True, comment="权限名称"),
+ Column("perm_group", String(64), nullable=True, index=True, comment="权限分组(可选)"),
+ Column("perm_desc", String(255), nullable=True, comment="权限描述"),
+ Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
+ comment="系统功能权限",
+)
+
+sys_role_permission = Table(
+ "sys_role_permission",
+ Base.metadata,
+ Column("role_id", String(64), ForeignKey("sys_role.role_id", ondelete="CASCADE"), primary_key=True, comment="角色 ID"),
+ Column("perm_code", String(128), ForeignKey("sys_permission.perm_code", ondelete="CASCADE"), primary_key=True, comment="权限编码"),
+ Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
+ Index("idx_sys_role_permission_role", "role_id"),
+ Index("idx_sys_role_permission_perm", "perm_code"),
+ comment="角色功能权限关联表",
+)
+
+sys_user = Table(
+ "sys_user",
+ Base.metadata,
+ Column("user_id", String(64), primary_key=True, comment="用户 ID"),
+ Column("username", String(64), nullable=False, unique=True, index=True, comment="登录名(唯一)"),
+ Column("display_name", String(64), nullable=True, index=True, comment="显示名称"),
+ Column("password_hash", String(255), nullable=False, comment="密码哈希"),
+ Column("role_id", String(64), ForeignKey("sys_role.role_id"), nullable=False, index=True, comment="所属角色 ID"),
+ Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
+ Column("last_login_at", DateTime(timezone=True), nullable=True, index=True, comment="最近登录时间(UTC)"),
+ Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
+ Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
+ Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
+ comment="系统用户(含所属角色)",
+)
+
+sys_logs = Table(
+ "sys_logs",
+ Base.metadata,
+ Column("log_id", BigInteger, primary_key=True, comment="日志 ID"),
+ Column("ts", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="操作时间(UTC)"),
+ Column("user_id", String(64), nullable=True, index=True, comment="用户 ID(可为空,如匿名)"),
+ Column("username", String(64), nullable=True, index=True, comment="登录名快照(可选)"),
+ Column("role_id", String(64), nullable=True, index=True, comment="角色 ID 快照(可选)"),
+ Column("action", String(128), nullable=False, index=True, comment="操作动作(如 login/start_simulation)"),
+ Column("resource", String(255), nullable=True, index=True, comment="资源标识(如 URL/对象 ID)"),
+ Column("success", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否成功"),
+ Column("ip", String(64), nullable=True, comment="客户端 IP(可选)"),
+ Column("user_agent", String(512), nullable=True, comment="User-Agent(可选)"),
+ Column("detail", JSONB, nullable=True, comment="操作明细(JSONB,可选)"),
+ Index("idx_sys_logs_user_ts", "user_id", "ts"),
+ Index("idx_sys_logs_action_ts", "action", "ts"),
+ comment="系统操作日志",
+)
+
+
+server_metrics = Table(
+ "server_metrics",
+ Base.metadata,
+ Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="采样时间(UTC)"),
+ Column("host_name", String(64), nullable=False, index=True, comment="主机名"),
+ Column("cpu_usage_percent", JSONB, nullable=False, comment="CPU 使用率(百分比,JSONB)"),
+ Column("memory_usage_bytes", JSONB, nullable=False, comment="内存使用情况(字节,JSONB)"),
+ Column("disk_usage_bytes", JSONB, nullable=True, comment="磁盘使用情况(字节,JSONB)"),
+ Index("idx_server_metrics_host_ts", "host_name", "ts"),
+ comment="服务器监控指标时序数据(TimescaleDB hypertable)",
+)
+
+
+async def init_schema(engine) -> None:
+ from sqlalchemy.ext.asyncio import AsyncEngine
+
+ if not isinstance(engine, AsyncEngine):
+ raise TypeError("engine must be AsyncEngine")
+
+ async with engine.begin() as conn:
+ await conn.run_sync(Base.metadata.create_all)
+ await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS scenario_name VARCHAR(255)"))
+ await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS config_created_at TIMESTAMPTZ"))
+ await conn.execute(text("ALTER TABLE simulations ADD COLUMN IF NOT EXISTS operator VARCHAR(64)"))
+ await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_scenario_name ON simulations (scenario_name)"))
+ await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_config_created_at ON simulations (config_created_at)"))
+ await conn.execute(text("CREATE INDEX IF NOT EXISTS idx_simulations_operator ON simulations (operator)"))
+
+
+async def init_timescaledb(engine) -> None:
+ async with engine.begin() as conn:
+ await conn.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb"))
+ await conn.execute(
+ text(
+ "SELECT create_hypertable('sim_vehicle_signals', 'ts', if_not_exists => TRUE)"
+ )
+ )
+ await conn.execute(
+ text(
+ "CREATE INDEX IF NOT EXISTS idx_vehicle_signals_sim_ts_desc ON sim_vehicle_signals (simulation_id, ts DESC)"
+ )
+ )
+ await conn.execute(
+ text(
+ "SELECT create_hypertable('server_metrics', 'ts', if_not_exists => TRUE)"
+ )
+ )
+ await conn.execute(
+ text(
+ "CREATE INDEX IF NOT EXISTS idx_server_metrics_host_ts_desc ON server_metrics (host_name, ts DESC)"
+ )
+ )
+ await conn.execute(
+ text(
+ "SELECT create_hypertable('sim_unity_vehicle_frames', 'ts', if_not_exists => TRUE)"
+ )
+ )
+ await conn.execute(
+ text(
+ "CREATE INDEX IF NOT EXISTS idx_unity_frames_sim_vehicle_ts_desc ON sim_unity_vehicle_frames (simulation_id, vehicle_id, ts DESC)"
+ )
+ )
diff --git a/backend/database/test_db.py b/backend/database/test_db.py
new file mode 100644
index 0000000..c8674ae
--- /dev/null
+++ b/backend/database/test_db.py
@@ -0,0 +1,153 @@
+import asyncio
+import os
+import time
+import json
+import random
+from datetime import datetime, timezone
+from sqlalchemy import insert, select, text
+from sqlalchemy.ext.asyncio import create_async_engine
+from sqlalchemy.engine.url import make_url
+from backend.database.schema import vehicle_signals, Simulation, init_schema, init_timescaledb
+from backend.config.settings import load_settings
+
+# 模拟数据生成
+def generate_payload():
+ return {
+ "steering_wheel_angle_deg": round(random.uniform(-450, 450), 1),
+ "brake_pedal_travel_mm": round(random.uniform(0, 100), 1),
+ "throttle_pedal_travel_mm": round(random.uniform(0, 100), 1),
+ "gear": random.choice(["P", "N", "D", "R"]),
+ "handbrake": random.choice([0, 1]),
+ "vehicle_speed_kmh": round(random.uniform(0, 180), 1),
+ "wheel_speed_rpm": {
+ "FL": random.randint(0, 2000),
+ "FR": random.randint(0, 2000),
+ "RL": random.randint(0, 2000),
+ "RR": random.randint(0, 2000)
+ },
+ "lights": {
+ "left_turn": random.choice([0, 1]),
+ "right_turn": random.choice([0, 1]),
+ "hazard": random.choice([0, 1]),
+ "brake": random.choice([0, 1])
+ },
+ "soc_percent": round(random.uniform(0, 100), 1),
+ "voltage_v": round(random.uniform(300, 400), 1),
+ "current_a": round(random.uniform(-50, 200), 1),
+ "temperature_c": round(random.uniform(20, 80), 1)
+ }
+
+def _redact_url(url: str) -> str:
+ try:
+ parsed = make_url(url)
+ if parsed.password:
+ parsed = parsed.set(password="***")
+ return str(parsed)
+ except Exception:
+ return url
+
+async def run_test():
+ settings = load_settings()
+
+ db_url = os.getenv("SMARTEDT_TEST_DATABASE_URL", settings.database.url).strip()
+ print(f"Connecting to DB: {_redact_url(db_url)}")
+ engine = create_async_engine(db_url, echo=False)
+
+ # 0. 初始化表结构 (如果不存在)
+ print("Initializing schema...")
+ await init_schema(engine)
+ try:
+ await init_timescaledb(engine)
+ print("Schema and TimescaleDB initialized.")
+ except Exception as e:
+ print(f"TimescaleDB init warning (might already exist): {e}")
+
+ # 1. 准备测试数据
+ total_records = int(os.getenv("SMARTEDT_TEST_RECORDS", "300000"))
+ batch_size = int(os.getenv("SMARTEDT_TEST_BATCH_SIZE", "1000"))
+ simulation_id = f"TEST_SIM_{int(time.time())}"
+ device_id = "test_device_01"
+
+ print(f"Generating {total_records} records for simulation {simulation_id}...")
+
+ print("Starting insertion test...")
+
+ # 2. 插入性能测试
+ insert_start_time = time.time()
+
+ async with engine.begin() as conn:
+ # 分批插入
+ for base_seq in range(0, total_records, batch_size):
+ batch = []
+ end_seq = min(base_seq + batch_size, total_records)
+ for seq in range(base_seq, end_seq):
+ batch.append(
+ {
+ "ts": datetime.now(timezone.utc),
+ "simulation_id": simulation_id,
+ "device_id": device_id,
+ "seq": seq,
+ "signals": generate_payload(),
+ }
+ )
+ await conn.execute(insert(vehicle_signals), batch)
+ if end_seq % 50000 == 0:
+ print(f"Inserted {end_seq} records...")
+
+ insert_end_time = time.time()
+ insert_duration = insert_end_time - insert_start_time
+ print(f"\n✅ Insertion Test Complete:")
+ print(f"Total Records: {total_records}")
+ print(f"Time Taken: {insert_duration:.4f} seconds")
+ print(f"Throughput: {total_records / insert_duration:.2f} records/sec")
+
+ # 3. 查询性能测试
+ print("\nStarting query performance test...")
+
+ # 3.1 简单计数查询
+ query_start = time.time()
+ async with engine.connect() as conn:
+ result = await conn.execute(
+ select(text("count(*)")).select_from(vehicle_signals).where(vehicle_signals.c.simulation_id == simulation_id)
+ )
+ count = result.scalar()
+ query_end = time.time()
+ print(f"Query 1 (Count): Found {count} records in {query_end - query_start:.4f} seconds")
+
+ # 3.2 复杂 JSONB 查询 (查询车速 > 100 的记录数)
+ # 注意:JSONB 查询语法取决于数据库和 SQLAlchemy 版本,这里使用 text() 以确保兼容性
+ query_start = time.time()
+ async with engine.connect() as conn:
+ # 查询 signals->>'vehicle_speed_kmh' > 100
+ stmt = text(
+ "SELECT count(*) FROM vehicle_signals "
+ "WHERE simulation_id = :sim_id "
+ "AND (signals->>'vehicle_speed_kmh')::float > 100"
+ )
+ result = await conn.execute(stmt, {"sim_id": simulation_id})
+ high_speed_count = result.scalar()
+ query_end = time.time()
+ print(f"Query 2 (JSONB Filter): Found {high_speed_count} records with speed > 100 in {query_end - query_start:.4f} seconds")
+
+ # 3.3 时间范围查询 (查询最近 1000 条)
+ query_start = time.time()
+ async with engine.connect() as conn:
+ stmt = (
+ select(vehicle_signals)
+ .where(vehicle_signals.c.simulation_id == simulation_id)
+ .order_by(vehicle_signals.c.ts.desc())
+ .limit(1000)
+ )
+ result = await conn.execute(stmt)
+ rows = result.fetchall()
+ query_end = time.time()
+ print(f"Query 3 (Time Range Limit): Retrieved {len(rows)} records in {query_end - query_start:.4f} seconds")
+
+ await engine.dispose()
+
+if __name__ == "__main__":
+ # 确保在 Windows 上正确运行 asyncio
+ if hasattr(asyncio, 'WindowsSelectorEventLoopPolicy'):
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+
+ asyncio.run(run_test())
diff --git a/backend/device/__init__.py b/backend/device/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/device/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/device/base.py b/backend/device/base.py
new file mode 100644
index 0000000..244b1e0
--- /dev/null
+++ b/backend/device/base.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True)
+class DeviceInfo:
+ device_id: str
+ device_type: str
+ connected: bool
+
+
+class DeviceAdapter:
+ device_id: str
+ device_type: str
+
+ async def connect(self) -> None:
+ raise NotImplementedError
+
+ async def disconnect(self) -> None:
+ raise NotImplementedError
+
+ async def is_connected(self) -> bool:
+ raise NotImplementedError
+
diff --git a/backend/device/mock_vehicle.py b/backend/device/mock_vehicle.py
new file mode 100644
index 0000000..f5242b3
--- /dev/null
+++ b/backend/device/mock_vehicle.py
@@ -0,0 +1,89 @@
+from __future__ import annotations
+
+import random
+from dataclasses import dataclass
+
+from backend.device.base import DeviceAdapter
+
+
+@dataclass(frozen=True)
+class VehicleSignalPayload:
+ steering_wheel_angle_deg: float
+ brake_pedal_travel_mm: float
+ throttle_pedal_travel_mm: float
+ gear: str
+ handbrake: int
+ vehicle_speed_kmh: float
+ wheel_speed_rpm: dict
+ lights: dict
+ soc_percent: float
+ voltage_v: float
+ current_a: float
+ temperature_c: float
+
+ def to_dict(self) -> dict:
+ return {
+ "steering_wheel_angle_deg": self.steering_wheel_angle_deg,
+ "brake_pedal_travel_mm": self.brake_pedal_travel_mm,
+ "throttle_pedal_travel_mm": self.throttle_pedal_travel_mm,
+ "gear": self.gear,
+ "handbrake": self.handbrake,
+ "vehicle_speed_kmh": self.vehicle_speed_kmh,
+ "wheel_speed_rpm": self.wheel_speed_rpm,
+ "lights": self.lights,
+ "soc_percent": self.soc_percent,
+ "voltage_v": self.voltage_v,
+ "current_a": self.current_a,
+ "temperature_c": self.temperature_c,
+ }
+
+
+class MockVehicleDevice(DeviceAdapter):
+ def __init__(self, device_id: str = "controlbox_01") -> None:
+ self.device_id = device_id
+ self.device_type = "mock_vehicle"
+ self._connected = False
+
+ async def connect(self) -> None:
+ self._connected = True
+
+ async def disconnect(self) -> None:
+ self._connected = False
+
+ async def is_connected(self) -> bool:
+ return self._connected
+
+ def sample(self) -> VehicleSignalPayload:
+ steering = random.uniform(-180.0, 180.0)
+ brake = max(0.0, random.gauss(2.0, 1.0))
+ throttle = max(0.0, random.gauss(15.0, 5.0))
+ gear = random.choice(["P", "N", "D", "S"])
+ handbrake = 1 if gear == "P" else 0
+ speed = max(0.0, random.gauss(40.0, 10.0)) if gear in {"D", "S"} else 0.0
+ rpm = int(speed * 9 + random.uniform(-5, 5))
+ wheel = {"FL": rpm, "FR": rpm, "RL": rpm, "RR": rpm}
+ lights = {
+ "left_turn": int(random.random() < 0.05),
+ "right_turn": int(random.random() < 0.05),
+ "hazard": int(random.random() < 0.02),
+ "brake": int(brake > 5.0),
+ }
+ soc = max(0.0, min(100.0, 80.0 - random.random() * 0.1))
+ voltage = 360.0 + random.uniform(-0.5, 0.5)
+ current = max(0.0, random.gauss(15.0, 2.0))
+ temp = 28.0 + random.uniform(-0.2, 0.2)
+ return VehicleSignalPayload(
+ steering_wheel_angle_deg=steering,
+ brake_pedal_travel_mm=brake,
+ throttle_pedal_travel_mm=throttle,
+ gear=gear,
+ handbrake=handbrake,
+ vehicle_speed_kmh=speed,
+ wheel_speed_rpm=wheel,
+ lights=lights,
+ soc_percent=soc,
+ voltage_v=voltage,
+ current_a=current,
+ temperature_c=temp,
+ )
+
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 0000000..c49f64d
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,149 @@
+from __future__ import annotations
+
+import argparse
+import asyncio
+import logging
+import multiprocessing
+import platform
+import sys
+from pathlib import Path
+
+# 修复 Windows 下 psycopg 异步连接的 Event Loop 问题
+if platform.system() == "Windows":
+ # 1. 强制设置 SelectorEventLoopPolicy
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+
+ # 2. Monkeypatch 阻止 uvicorn 覆盖策略 (uvicorn 默认在 Windows 上强制使用 Proactor)
+ # 这对 psycopg 3 (SQLAlchemy async) 是必须的,因为 Proactor 不支持 add_reader/add_writer
+ original_set_policy = asyncio.set_event_loop_policy
+ def patched_set_policy(policy):
+ # 如果尝试设置 Proactor,则忽略并保持 Selector
+ if isinstance(policy, asyncio.WindowsProactorEventLoopPolicy):
+ return
+ original_set_policy(policy)
+ asyncio.set_event_loop_policy = patched_set_policy
+
+import uvicorn
+from dotenv import load_dotenv
+from fastapi import FastAPI, WebSocket
+
+from contextlib import asynccontextmanager
+
+from backend.config.settings import load_settings
+from backend.database.engine import create_engine, create_session_factory
+from backend.database.schema import init_schema, init_timescaledb
+from backend.services.broadcaster import Broadcaster
+from backend.services.simulation_manager import SimulationManager
+from backend.services.server_monitor import ServerMonitorService
+from backend.device.mock_vehicle import MockVehicleDevice
+from backend.api import routes, ws
+from backend.utils import configure_logging
+
+logger = logging.getLogger("backend")
+
+def _default_backend_log_file() -> Path | None:
+ if getattr(sys, "frozen", False):
+ exe_dir = Path(sys.executable).resolve().parent
+ return exe_dir / "logs" / "backend.log"
+ return None
+
+def _force_windows_selector_event_loop_for_uvicorn() -> None:
+ if platform.system() != "Windows":
+ return
+ try:
+ import uvicorn.loops.asyncio as uvicorn_asyncio_loop
+ except Exception:
+ return
+
+ def _selector_loop_factory(use_subprocess: bool = False):
+ return asyncio.SelectorEventLoop
+
+ uvicorn_asyncio_loop.asyncio_loop_factory = _selector_loop_factory
+
+# 全局单例容器(简单实现)
+class Container:
+ def __init__(self):
+ load_dotenv()
+ self.settings = load_settings()
+ configure_logging(
+ "INFO" if not self.settings.server.debug else "DEBUG",
+ log_file=_default_backend_log_file(),
+ )
+
+ self.file_root = self.settings.files.root_path
+ self.file_root.mkdir(parents=True, exist_ok=True)
+
+ self.engine = create_engine(self.settings.database)
+ self.session_factory = create_session_factory(self.engine)
+ self.broadcaster = Broadcaster()
+
+ # 实例化服务
+ self.simulation_manager = SimulationManager(
+ self.session_factory,
+ self.broadcaster
+ )
+
+ # 实例化监控服务
+ self.server_monitor = ServerMonitorService(
+ self.session_factory,
+ self.broadcaster
+ )
+
+container = Container()
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ # 启动前初始化
+ await init_schema(container.engine)
+ if container.settings.database.timescaledb:
+ try:
+ await init_timescaledb(container.engine)
+ except Exception as e:
+ # 可能是已存在或权限问题,仅打印日志
+ logger.warning("TimescaleDB init warning: %s", e)
+
+ # 注册默认设备
+ mock_dev = MockVehicleDevice(device_id="mock_01")
+ await container.simulation_manager.register_device(mock_dev)
+
+ # 启动监控服务
+ await container.server_monitor.start()
+
+ yield
+
+ # 停机清理
+ await container.server_monitor.stop()
+ await container.simulation_manager.stop()
+ await container.engine.dispose()
+
+app = FastAPI(title="SmartEDT Backend", version="0.1.0", lifespan=lifespan)
+app.include_router(routes.get_router(simulation_manager=container.simulation_manager, file_root=container.file_root))
+
+@app.websocket("/ws")
+async def ws_endpoint(websocket: WebSocket):
+ await ws.websocket_handler(websocket, broadcaster=container.broadcaster)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--host", default=None)
+ parser.add_argument("--port", type=int, default=None)
+ parser.add_argument("--debug", action="store_true")
+ args = parser.parse_args()
+
+ settings = load_settings()
+ host = args.host or settings.server.host
+ port = args.port or settings.server.port
+ debug = args.debug or settings.server.debug
+
+ _force_windows_selector_event_loop_for_uvicorn()
+
+ if getattr(sys, 'frozen', False):
+ uvicorn.run(app, host=host, port=port, log_level="debug" if debug else "info")
+ else:
+ uvicorn.run("backend.main:app", host=host, port=port, reload=debug, log_level="debug" if debug else "info")
+
+
+if __name__ == "__main__":
+ multiprocessing.freeze_support()
+ main()
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000..c0fe056
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,8 @@
+fastapi>=0.115.0
+uvicorn[standard]>=0.30.0
+sqlalchemy[asyncio]>=2.0.30
+psycopg[binary]>=3.2.0
+python-dotenv>=1.0.1
+pydantic>=2.8.0
+psutil>=5.9.0
+
diff --git a/backend/requirements_build.txt b/backend/requirements_build.txt
new file mode 100644
index 0000000..07bf186
--- /dev/null
+++ b/backend/requirements_build.txt
@@ -0,0 +1,2 @@
+pyinstaller>=6.9.0
+
diff --git a/backend/services/__init__.py b/backend/services/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/backend/services/__init__.py
@@ -0,0 +1 @@
+
diff --git a/backend/services/broadcaster.py b/backend/services/broadcaster.py
new file mode 100644
index 0000000..c1a0a7f
--- /dev/null
+++ b/backend/services/broadcaster.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+import asyncio
+from typing import Any
+
+from starlette.websockets import WebSocket
+
+
+class Broadcaster:
+ def __init__(self) -> None:
+ self._clients: set[WebSocket] = set()
+ self._lock = asyncio.Lock()
+
+ async def add(self, ws: WebSocket) -> None:
+ async with self._lock:
+ self._clients.add(ws)
+
+ async def remove(self, ws: WebSocket) -> None:
+ async with self._lock:
+ self._clients.discard(ws)
+
+ async def broadcast_json(self, message: dict[str, Any]) -> None:
+ async with self._lock:
+ clients = list(self._clients)
+ for ws in clients:
+ try:
+ await ws.send_json(message)
+ except Exception:
+ await self.remove(ws)
+
diff --git a/backend/services/server_monitor.py b/backend/services/server_monitor.py
new file mode 100644
index 0000000..46e175f
--- /dev/null
+++ b/backend/services/server_monitor.py
@@ -0,0 +1,127 @@
+import asyncio
+import time
+import logging
+import psutil
+import socket
+import platform
+from datetime import datetime, timezone
+from sqlalchemy import insert
+from sqlalchemy.ext.asyncio import async_sessionmaker
+
+from backend.database.schema import server_metrics
+from backend.services.broadcaster import Broadcaster
+
+logger = logging.getLogger("backend.monitor")
+
+class ServerMonitorService:
+ def __init__(self, session_factory: async_sessionmaker, broadcaster: Broadcaster):
+ self._session_factory = session_factory
+ self._broadcaster = broadcaster
+ self._host_name = socket.gethostname()
+ self._running = False
+ self._task = None
+ self._sample_interval = 1.0 / 50.0 # 50Hz (20ms)
+ self._report_interval = 1.0 / 10.0 # 10Hz (100ms)
+ self._last_report_time = 0.0
+
+ # Buffer for downsampling
+ self._buffer_cpu = []
+ self._buffer_mem = []
+
+ async def start(self):
+ if self._running:
+ return
+ self._running = True
+ self._task = asyncio.create_task(self._run_loop())
+ logger.info("ServerMonitorService started")
+
+ async def stop(self):
+ self._running = False
+ if self._task:
+ try:
+ await self._task
+ except asyncio.CancelledError:
+ pass
+ logger.info("ServerMonitorService stopped")
+
+ async def _run_loop(self):
+ loop = asyncio.get_running_loop()
+ next_time = loop.time()
+
+ while self._running:
+ # High frequency sampling (50Hz)
+ # psutil.cpu_percent(interval=None) is non-blocking
+ cpu_percent = psutil.cpu_percent(interval=None)
+ mem = psutil.virtual_memory()
+
+ self._buffer_cpu.append(cpu_percent)
+ self._buffer_mem.append(mem)
+
+ current_time = loop.time()
+
+ # Check if it's time to report (10Hz)
+ if current_time - self._last_report_time >= self._report_interval:
+ await self._process_and_report()
+ self._last_report_time = current_time
+ self._buffer_cpu.clear()
+ self._buffer_mem.clear()
+
+ # Precise timing control
+ next_time += self._sample_interval
+ sleep_time = next_time - loop.time()
+ if sleep_time > 0:
+ await asyncio.sleep(sleep_time)
+ else:
+ # If we are lagging, yield execution but don't sleep
+ await asyncio.sleep(0)
+
+ async def _process_and_report(self):
+ if not self._buffer_cpu:
+ return
+
+ # Downsampling: Calculate average of buffered samples
+ avg_cpu = sum(self._buffer_cpu) / len(self._buffer_cpu)
+
+ # Take the latest memory reading (memory doesn't fluctuate as fast as CPU)
+ last_mem = self._buffer_mem[-1]
+
+ payload = {
+ "ts": datetime.now(timezone.utc).isoformat(),
+ "host_name": self._host_name,
+ "cpu_usage_percent": {
+ "total": round(avg_cpu, 2),
+ # Note: per-core usage is expensive to query at 50Hz, so we only track total here
+ # or we could sample per-core at lower frequency
+ },
+ "memory_usage_bytes": {
+ "total": last_mem.total,
+ "available": last_mem.available,
+ "used": last_mem.used,
+ "percent": last_mem.percent
+ },
+ "disk_usage_bytes": {} # Optional: disk usage changes slowly, maybe check every 1s
+ }
+
+ # 1. Broadcast via WebSocket (10Hz)
+ await self._broadcaster.broadcast_json({
+ "type": "server.metrics",
+ "payload": payload
+ })
+
+ # 2. Persist to Database (10Hz)
+ # Note: In production, consider batching inserts further (e.g., every 1s)
+ # to reduce DB load, but 10Hz single insert is manageable for TimescaleDB.
+ async with self._session_factory() as session:
+ try:
+ stmt = insert(server_metrics).values(
+ ts=datetime.fromisoformat(payload["ts"]),
+ host_name=payload["host_name"],
+ cpu_usage_percent=payload["cpu_usage_percent"],
+ memory_usage_bytes=payload["memory_usage_bytes"],
+ disk_usage_bytes=payload["disk_usage_bytes"]
+ )
+ await session.execute(stmt)
+ await session.commit()
+ except Exception as e:
+ logger.warning("Failed to persist server metrics: %s", e)
+ # Don't raise, keep monitoring running
diff --git a/backend/services/simulation_manager.py b/backend/services/simulation_manager.py
new file mode 100644
index 0000000..6508054
--- /dev/null
+++ b/backend/services/simulation_manager.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+import asyncio
+import logging
+import secrets
+from dataclasses import dataclass
+from typing import Any
+
+from sqlalchemy import insert
+from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
+
+from backend.database.schema import Simulation, vehicle_signals
+from backend.device.mock_vehicle import MockVehicleDevice
+from backend.services.broadcaster import Broadcaster
+from backend.utils import utc_now
+
+
+logger = logging.getLogger("backend.simulation")
+
+
+@dataclass
+class SimulationRuntime:
+ simulation_id: str
+ status: str
+ task: asyncio.Task | None = None
+
+
+class SimulationManager:
+ def __init__(
+ self,
+ session_factory: async_sessionmaker[AsyncSession],
+ broadcaster: Broadcaster,
+ ) -> None:
+ self._session_factory = session_factory
+ self._broadcaster = broadcaster
+ self._runtime: SimulationRuntime | None = None
+ self._device = MockVehicleDevice()
+ self._seq = 0
+
+ def current(self) -> SimulationRuntime | None:
+ return self._runtime
+
+ async def register_device(self, device: MockVehicleDevice) -> None:
+ self._device = device
+
+ async def start(self, scenario_config: dict[str, Any]) -> str:
+ if self._runtime and self._runtime.status == "running":
+ return self._runtime.simulation_id
+
+ simulation_id = "SIM" + utc_now().strftime("%Y%m%d%H%M%S") + secrets.token_hex(2).upper()
+ started_at = utc_now()
+ scenario_name = scenario_config.get("scenario")
+ operator = scenario_config.get("driver") or scenario_config.get("operator")
+ config_created_at = started_at
+
+ async with self._session_factory() as session:
+ session.add(
+ Simulation(
+ simulation_id=simulation_id,
+ status="running",
+ started_at=started_at,
+ ended_at=None,
+ scenario_name=scenario_name,
+ scenario_config=scenario_config,
+ config_created_at=config_created_at,
+ operator=operator,
+ archived=False,
+ )
+ )
+ await session.commit()
+
+ await self._device.connect()
+ self._runtime = SimulationRuntime(simulation_id=simulation_id, status="running")
+ self._runtime.task = asyncio.create_task(self._run_loop(simulation_id))
+ await self._broadcaster.broadcast_json(
+ {"type": "simulation.status", "ts": started_at.timestamp(), "simulation_id": simulation_id, "payload": {"status": "running"}}
+ )
+ return simulation_id
+
+ async def stop(self, simulation_id: str) -> None:
+ runtime = self._runtime
+ if not runtime or runtime.simulation_id != simulation_id:
+ return
+
+ runtime.status = "stopping"
+ if runtime.task:
+ runtime.task.cancel()
+ try:
+ await runtime.task
+ except asyncio.CancelledError:
+ pass
+
+ await self._device.disconnect()
+ ended_at = utc_now()
+
+ async with self._session_factory() as session:
+ sim = await session.get(Simulation, simulation_id)
+ if sim:
+ sim.status = "stopped"
+ sim.ended_at = ended_at
+ await session.commit()
+
+ await self._broadcaster.broadcast_json(
+ {"type": "simulation.status", "ts": ended_at.timestamp(), "simulation_id": simulation_id, "payload": {"status": "stopped"}}
+ )
+ self._runtime = None
+
+ async def _run_loop(self, simulation_id: str) -> None:
+ try:
+ while True:
+ await asyncio.sleep(0.05)
+ if not await self._device.is_connected():
+ continue
+
+ self._seq += 1
+ ts = utc_now()
+ payload = self._device.sample().to_dict()
+ message = {
+ "type": "vehicle.signal",
+ "ts": ts.timestamp(),
+ "simulation_id": simulation_id,
+ "device_id": self._device.device_id,
+ "seq": self._seq,
+ "payload": payload,
+ }
+ await self._broadcaster.broadcast_json(message)
+ await self._persist_signal(ts, simulation_id, self._device.device_id, self._seq, payload)
+ except asyncio.CancelledError:
+ raise
+ except Exception:
+ logger.exception("simulation loop crashed")
+
+ async def _persist_signal(self, ts, simulation_id: str, device_id: str, seq: int, signals: dict[str, Any]) -> None:
+ async with self._session_factory() as session:
+ await session.execute(
+ insert(vehicle_signals).values(
+ ts=ts,
+ simulation_id=simulation_id,
+ device_id=device_id,
+ seq=seq,
+ signals=signals,
+ )
+ )
+ await session.commit()
diff --git a/backend/smartedt_backend.spec b/backend/smartedt_backend.spec
new file mode 100644
index 0000000..ea48d07
--- /dev/null
+++ b/backend/smartedt_backend.spec
@@ -0,0 +1,95 @@
+# -*- mode: python ; coding: utf-8 -*-
+
+from PyInstaller.utils.hooks import collect_all
+
+datas = [
+ ('config/config.ini', 'backend/config')
+]
+binaries = []
+hiddenimports = [
+ 'uvicorn.logging',
+ 'uvicorn.loops',
+ 'uvicorn.loops.auto',
+ 'uvicorn.protocols',
+ 'uvicorn.protocols.http',
+ 'uvicorn.protocols.http.auto',
+ 'uvicorn.protocols.websockets',
+ 'uvicorn.protocols.websockets.auto',
+ 'uvicorn.lifespan',
+ 'uvicorn.lifespan.on',
+ 'backend.api',
+ 'backend.api.routes',
+ 'backend.api.schemas',
+ 'backend.api.ws',
+ 'backend.config',
+ 'backend.config.settings',
+ 'backend.database',
+ 'backend.database.engine',
+ 'backend.database.schema',
+ 'backend.device',
+ 'backend.device.base',
+ 'backend.device.mock_vehicle',
+ 'backend.services',
+ 'backend.services.broadcaster',
+ 'backend.services.server_monitor',
+ 'backend.services.simulation_manager',
+ 'backend.utils',
+ 'psycopg',
+ 'psycopg_binary',
+ 'sqlalchemy.ext.asyncio',
+ 'sqlalchemy.dialects.postgresql',
+ 'asyncpg', # Just in case, though we use psycopg
+]
+
+tmp_ret = collect_all('uvicorn')
+datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
+
+tmp_ret = collect_all('psycopg')
+datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
+
+block_cipher = None
+
+a = Analysis(
+ ['main.py'],
+ pathex=[],
+ binaries=binaries,
+ datas=datas,
+ hiddenimports=hiddenimports,
+ hookspath=[],
+ hooksconfig={},
+ runtime_hooks=[],
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False,
+)
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+exe = EXE(
+ pyz,
+ a.scripts,
+ [],
+ exclude_binaries=True,
+ name='smartedt_backend',
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ console=True,
+ disable_windowed_traceback=False,
+ argv_emulation=False,
+ target_arch=None,
+ codesign_identity=None,
+ entitlements_file=None,
+)
+coll = COLLECT(
+ exe,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ strip=False,
+ upx=True,
+ upx_exclude=[],
+ name='smartedt_backend',
+)
diff --git a/backend/utils.py b/backend/utils.py
new file mode 100644
index 0000000..382163a
--- /dev/null
+++ b/backend/utils.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+import logging
+import os
+from datetime import datetime, timezone
+from logging.handlers import TimedRotatingFileHandler
+from pathlib import Path
+
+
+def utc_now() -> datetime:
+ return datetime.now(timezone.utc)
+
+
+def configure_logging(level: str, log_file: Path | None = None) -> None:
+ level_value = getattr(logging, level.upper(), logging.INFO)
+ logging_handlers: list[logging.Handler] = [logging.StreamHandler()]
+ if log_file is not None:
+ log_file.parent.mkdir(parents=True, exist_ok=True)
+ rotating_handler = TimedRotatingFileHandler(
+ log_file,
+ when="midnight",
+ interval=1,
+ backupCount=90,
+ encoding="utf-8",
+ delay=True,
+ )
+ rotating_handler.suffix = "%Y-%m-%d"
+ logging_handlers.append(rotating_handler)
+ logging.basicConfig(
+ level=level_value,
+ format="%(asctime)s %(levelname)s %(name)s %(message)s",
+ handlers=logging_handlers,
+ force=True,
+ )
+ for logger_name in ("uvicorn", "uvicorn.error", "uvicorn.access", "uvicorn.asgi"):
+ logger = logging.getLogger(logger_name)
+ logger.handlers = []
+ logger.propagate = True
+
+
+def project_root() -> Path:
+ return Path(__file__).resolve().parents[1]
+
+
+def safe_join(root: Path, untrusted_path: str) -> Path:
+ if untrusted_path.startswith(("\\\\", "//")):
+ raise ValueError("UNC path is not allowed")
+ if os.path.isabs(untrusted_path):
+ raise ValueError("Absolute path is not allowed")
+ candidate = (root / untrusted_path).resolve()
+ root_resolved = root.resolve()
+ if root_resolved not in candidate.parents and candidate != root_resolved:
+ raise ValueError("Path traversal detected")
+ return candidate
diff --git a/frontend/build_frontend.ps1 b/frontend/build_frontend.ps1
new file mode 100644
index 0000000..8805e52
--- /dev/null
+++ b/frontend/build_frontend.ps1
@@ -0,0 +1,65 @@
+# Ensure we are in the frontend directory
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+Set-Location $ScriptDir
+
+# $ProjectRoot = Split-Path -Parent $ScriptDir
+# $BackendBuildScript = Join-Path $ProjectRoot "backend\build_backend.ps1"
+
+# Write-Host "Starting Full Build Process..." -ForegroundColor Cyan
+
+# # 1. Build Backend
+# Write-Host "Invoking Backend Build..." -ForegroundColor Yellow
+# & $BackendBuildScript
+# if ($LASTEXITCODE -ne 0) {
+# Write-Error "Backend build failed. Aborting."
+# exit 1
+# }
+
+# 2. Build Frontend (Electron)
+Write-Host "`nBuilding Frontend (Electron)..." -ForegroundColor Yellow
+
+# Ensure we are back in frontend directory (Backend script changes location)
+Set-Location $ScriptDir
+
+try {
+ Get-Process -Name "SmartEDT" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
+} catch {}
+
+# Clean release directory to prevent file lock issues
+if (Test-Path "release") {
+ Write-Host "Cleaning release directory..." -ForegroundColor Yellow
+
+ # Try multiple times to remove the directory
+ $maxRetries = 3
+ $retryCount = 0
+ while ($retryCount -lt $maxRetries) {
+ try {
+ Remove-Item -Recurse -Force "release" -ErrorAction Stop
+ break
+ } catch {
+ Write-Host "Failed to clean release directory. Retrying in 2 seconds... ($($retryCount + 1)/$maxRetries)" -ForegroundColor Yellow
+ Start-Sleep -Seconds 2
+ try {
+ Get-Process -Name "SmartEDT" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
+ } catch {}
+ $retryCount++
+ }
+ }
+
+ if (Test-Path "release") {
+ Write-Warning "Could not fully clean release directory. Attempting to proceed..."
+ } else {
+ # Wait a bit to ensure filesystem is ready
+ Start-Sleep -Seconds 1
+ }
+}
+
+npm run dist
+
+if ($LASTEXITCODE -eq 0) {
+ Write-Host "`nFull build successful!" -ForegroundColor Green
+ Write-Host "Installer located at: $(Join-Path $ScriptDir 'release\SmartEDT Setup 0.1.0.exe')" -ForegroundColor Green
+} else {
+ Write-Error "`nFrontend build failed!"
+ exit 1
+}
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..a15fbfb
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ SmartEDT
+
+
+
+
+
+
+
diff --git a/frontend/main/main.cjs b/frontend/main/main.cjs
new file mode 100644
index 0000000..40901d4
--- /dev/null
+++ b/frontend/main/main.cjs
@@ -0,0 +1,308 @@
+const { app, BrowserWindow, screen, Menu } = require("electron");
+const { spawn } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const os = require("os");
+const http = require("http");
+
+// 简单的日志记录函数
+function log(message) {
+ const logPath = path.join(app.getPath("userData"), "smartedt.log");
+ const timestamp = new Date().toISOString();
+ const logMessage = `[${timestamp}] ${message}\n`;
+ try {
+ fs.appendFileSync(logPath, logMessage);
+ } catch (e) {
+ console.error("Failed to write log:", e);
+ }
+}
+
+let backendProcess = null;
+let staticServer = null;
+let backendLogStream = null;
+
+function resolveRepoRoot() {
+ return path.resolve(__dirname, "..", "..");
+}
+
+function getPythonPath(repoRoot) {
+ if (process.env.SMARTEDT_PYTHON) {
+ return process.env.SMARTEDT_PYTHON;
+ }
+ // Check for venv in backend (Windows)
+ const venvPythonWin = path.join(repoRoot, "backend", "venv", "Scripts", "python.exe");
+ if (fs.existsSync(venvPythonWin)) {
+ return venvPythonWin;
+ }
+ // Check for venv in backend (Linux/Mac)
+ const venvPythonUnix = path.join(repoRoot, "backend", "venv", "bin", "python");
+ if (fs.existsSync(venvPythonUnix)) {
+ return venvPythonUnix;
+ }
+ // Fallback to system python
+ return "python";
+}
+
+function startBackend() {
+ log("Starting backend process...");
+ if (backendLogStream) {
+ try {
+ backendLogStream.end();
+ } catch (_) {}
+ backendLogStream = null;
+ }
+
+ const repoRoot = resolveRepoRoot();
+ let backendCmd;
+ let args;
+ let cwd;
+
+ const safeTimestamp = new Date().toISOString().replace(/[:.]/g, "-");
+ const backendLogPath = path.join(app.getPath("userData"), `backend-${safeTimestamp}.log`);
+ try {
+ backendLogStream = fs.createWriteStream(backendLogPath, { flags: "a" });
+ backendLogStream.write(`[${new Date().toISOString()}] Backend log start\n`);
+ log(`Backend log file: ${backendLogPath}`);
+ } catch (e) {
+ backendLogStream = null;
+ log(`Failed to open backend log file: ${e.message}`);
+ }
+
+ if (app.isPackaged) {
+ // In production, the backend is in resources/backend
+ const backendDir = path.join(process.resourcesPath, "backend");
+ backendCmd = path.join(backendDir, "smartedt_backend.exe");
+ args = ["--host", "127.0.0.1", "--port", "5000"];
+ cwd = backendDir;
+ log(`Production mode. Backend cmd: ${backendCmd}`);
+ log(`Backend cwd: ${cwd}`);
+ } else {
+ // In development
+ const backendMain = path.join(repoRoot, "backend", "main.py");
+ backendCmd = getPythonPath(repoRoot);
+ args = [backendMain, "--host", "127.0.0.1", "--port", "5000"];
+ cwd = repoRoot;
+ log(`Development mode. Backend cmd: ${backendCmd}`);
+ }
+
+ // 设置 PYTHONPATH 环境变量,确保能找到 backend 模块 (only for dev)
+ const env = { ...process.env };
+ if (!app.isPackaged) {
+ env.PYTHONPATH = repoRoot;
+ }
+
+ try {
+ if (!fs.existsSync(backendCmd) && app.isPackaged) {
+ log(`ERROR: Backend executable not found at ${backendCmd}`);
+ }
+
+ backendProcess = spawn(backendCmd, args, {
+ cwd: cwd,
+ env: env,
+ stdio: "pipe", // Capture stdio
+ windowsHide: true
+ });
+
+ backendProcess.stdout.on("data", (data) => {
+ const text = data.toString();
+ log(`[Backend stdout] ${text.trim()}`);
+ if (backendLogStream) {
+ backendLogStream.write(text.endsWith("\n") ? text : `${text}\n`);
+ }
+ });
+
+ backendProcess.stderr.on("data", (data) => {
+ const text = data.toString();
+ log(`[Backend stderr] ${text.trim()}`);
+ if (backendLogStream) {
+ backendLogStream.write(text.endsWith("\n") ? text : `${text}\n`);
+ }
+ });
+
+ backendProcess.on("error", (err) => {
+ log(`Backend failed to start: ${err.message}`);
+ if (backendLogStream) {
+ backendLogStream.write(`[${new Date().toISOString()}] Backend failed to start: ${err.message}\n`);
+ }
+ });
+
+ backendProcess.on("exit", (code) => {
+ log(`Backend exited with code ${code}`);
+ backendProcess = null;
+ if (backendLogStream) {
+ backendLogStream.write(`[${new Date().toISOString()}] Backend exited with code ${code}\n`);
+ try {
+ backendLogStream.end();
+ } catch (_) {}
+ backendLogStream = null;
+ }
+ });
+ } catch (e) {
+ log(`Exception starting backend: ${e.message}`);
+ if (backendLogStream) {
+ backendLogStream.write(`[${new Date().toISOString()}] Exception starting backend: ${e.message}\n`);
+ try {
+ backendLogStream.end();
+ } catch (_) {}
+ backendLogStream = null;
+ }
+ }
+}
+
+function getMimeType(filePath) {
+ const ext = path.extname(filePath).toLowerCase();
+ const mimeTypes = {
+ '.html': 'text/html',
+ '.js': 'text/javascript',
+ '.css': 'text/css',
+ '.json': 'application/json',
+ '.png': 'image/png',
+ '.jpg': 'image/jpg',
+ '.gif': 'image/gif',
+ '.svg': 'image/svg+xml',
+ '.ico': 'image/x-icon',
+ '.woff': 'application/font-woff',
+ '.woff2': 'font/woff2',
+ '.ttf': 'application/font-ttf'
+ };
+ return mimeTypes[ext] || 'application/octet-stream';
+}
+
+function startLocalServer(callback) {
+ const distPath = path.join(__dirname, "..", "dist");
+ log(`Starting local static server serving: ${distPath}`);
+
+ staticServer = http.createServer((req, res) => {
+ try {
+ const url = new URL(req.url, `http://localhost`);
+ let filePath = path.join(distPath, url.pathname);
+
+ // Security check
+ if (!filePath.startsWith(distPath)) {
+ res.statusCode = 403;
+ res.end('Forbidden');
+ return;
+ }
+
+ // Default to index.html for directories
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
+ filePath = path.join(filePath, 'index.html');
+ }
+
+ // SPA Fallback: if file not found and no extension, serve index.html
+ if (!fs.existsSync(filePath)) {
+ if (path.extname(filePath) === '') {
+ filePath = path.join(distPath, 'index.html');
+ } else {
+ res.statusCode = 404;
+ res.end('Not Found');
+ return;
+ }
+ }
+
+ const data = fs.readFileSync(filePath);
+ const mimeType = getMimeType(filePath);
+ res.setHeader('Content-Type', mimeType);
+ res.end(data);
+ } catch (err) {
+ log(`Server error: ${err.message}`);
+ res.statusCode = 500;
+ res.end(`Internal Server Error`);
+ }
+ });
+
+ // Listen on a random available port
+ staticServer.listen(0, '127.0.0.1', () => {
+ const port = staticServer.address().port;
+ const url = `http://127.0.0.1:${port}`;
+ log(`Local static server running at ${url}`);
+ callback(url);
+ });
+
+ staticServer.on('error', (err) => {
+ log(`Static server error: ${err.message}`);
+ });
+}
+
+function createWindowForDisplay(display, routePath, baseUrl) {
+ const bounds = display.bounds;
+ const win = new BrowserWindow({
+ x: bounds.x,
+ y: bounds.y,
+ width: bounds.width,
+ height: bounds.height,
+ autoHideMenuBar: true,
+ webPreferences: {
+ contextIsolation: true
+ }
+ });
+
+ win.setMenuBarVisibility(false);
+ if (process.platform !== "darwin") {
+ win.removeMenu();
+ }
+
+ // Combine baseUrl with route hash
+ // e.g. http://127.0.0.1:5173/#/control/config
+ // or http://127.0.0.1:xxxxx/#/control/config
+ const fullUrl = `${baseUrl}/#${routePath}`;
+
+ log(`Loading window URL: ${fullUrl}`);
+ win.loadURL(fullUrl).catch(e => {
+ log(`Failed to load URL ${fullUrl}: ${e.message}`);
+ });
+
+ return win;
+}
+
+function createWindows(baseUrl) {
+ log(`Creating windows with base URL: ${baseUrl}`);
+ const displays = screen.getAllDisplays();
+ const primary = screen.getPrimaryDisplay();
+ const others = displays.filter((d) => d.id !== primary.id);
+
+ createWindowForDisplay(primary, "/control/config", baseUrl);
+
+ if (others[0]) {
+ createWindowForDisplay(others[0], "/big/dashboard", baseUrl);
+ }
+ if (others[1]) {
+ createWindowForDisplay(others[1], "/car/sim", baseUrl);
+ }
+}
+
+app.whenReady().then(() => {
+ log("App ready");
+ if (process.platform !== "darwin") {
+ Menu.setApplicationMenu(null);
+ }
+ startBackend();
+
+ if (app.isPackaged) {
+ // Production: Start local static server
+ startLocalServer((serverUrl) => {
+ createWindows(serverUrl);
+ });
+ } else {
+ // Development: Use Vite dev server
+ const devServerUrl = process.env.VITE_DEV_SERVER_URL || "http://127.0.0.1:5173";
+ createWindows(devServerUrl);
+ }
+});
+
+app.on("before-quit", () => {
+ if (backendProcess) {
+ backendProcess.kill();
+ backendProcess = null;
+ }
+ if (backendLogStream) {
+ try {
+ backendLogStream.end();
+ } catch (_) {}
+ backendLogStream = null;
+ }
+ if (staticServer) {
+ staticServer.close();
+ staticServer = null;
+ }
+});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..4f808dc
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,5699 @@
+{
+ "name": "smartedt-frontend",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "smartedt-frontend",
+ "version": "0.1.0",
+ "dependencies": {
+ "pinia": "^3.0.4",
+ "uplot": "^1.6.32",
+ "vue": "^3.5.0",
+ "vue-router": "^4.4.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.0",
+ "concurrently": "^9.0.0",
+ "cross-env": "^10.1.0",
+ "electron": "^27.3.11",
+ "electron-builder": "^24.13.3",
+ "electron-packager": "^17.1.2",
+ "vite": "^5.4.0",
+ "wait-on": "^7.2.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@develar/schema-utils": {
+ "version": "2.6.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.0",
+ "ajv-keywords": "^3.4.1"
+ },
+ "engines": {
+ "node": ">= 8.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/@electron/asar": {
+ "version": "3.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^5.0.0",
+ "glob": "^7.1.6",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "asar": "bin/asar.js"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/@electron/notarize": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "promise-retry": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "compare-version": "^0.1.2",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.0.0",
+ "isbinaryfile": "^4.0.8",
+ "minimist": "^1.2.6",
+ "plist": "^3.0.5"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/universal": {
+ "version": "1.5.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "@malept/cross-spawn-promise": "^1.1.0",
+ "debug": "^4.3.1",
+ "dir-compare": "^3.0.0",
+ "fs-extra": "^9.0.1",
+ "minimatch": "^3.0.4",
+ "plist": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@epic-web/invariant": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
+ "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "9.3.0",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/topo": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "license": "MIT"
+ },
+ "node_modules/@malept/cross-spawn-promise": {
+ "version": "1.1.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.0",
+ "lodash": "^4.17.15",
+ "tmp-promise": "^3.0.2"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sideway/address": {
+ "version": "4.1.5",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/fs-extra": {
+ "version": "9.0.13",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.30",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/plist": {
+ "version": "3.0.5",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*",
+ "xmlbuilder": ">=11.0.1"
+ }
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/verror": {
+ "version": "1.10.11",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@vue/shared": "3.5.26",
+ "entities": "^7.0.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@vue/compiler-core": "3.5.26",
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/compiler-ssr": "3.5.26",
+ "@vue/shared": "3.5.26",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.9",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.9"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.9",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.9",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.9",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.26",
+ "@vue/runtime-core": "3.5.26",
+ "@vue/shared": "3.5.26",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.26",
+ "@vue/shared": "3.5.26"
+ },
+ "peerDependencies": {
+ "vue": "3.5.26"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.26",
+ "license": "MIT"
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.11",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/7zip-bin": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/app-builder-bin": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/app-builder-lib": {
+ "version": "24.13.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@develar/schema-utils": "~2.6.5",
+ "@electron/notarize": "2.2.1",
+ "@electron/osx-sign": "1.0.5",
+ "@electron/universal": "1.5.1",
+ "@malept/flatpak-bundler": "^0.4.0",
+ "@types/fs-extra": "9.0.13",
+ "async-exit-hook": "^2.0.1",
+ "bluebird-lst": "^1.0.9",
+ "builder-util": "24.13.1",
+ "builder-util-runtime": "9.2.4",
+ "chromium-pickle-js": "^0.2.0",
+ "debug": "^4.3.4",
+ "ejs": "^3.1.8",
+ "electron-publish": "24.13.1",
+ "form-data": "^4.0.0",
+ "fs-extra": "^10.1.0",
+ "hosted-git-info": "^4.1.0",
+ "is-ci": "^3.0.0",
+ "isbinaryfile": "^5.0.0",
+ "js-yaml": "^4.1.0",
+ "lazy-val": "^1.0.5",
+ "minimatch": "^5.1.1",
+ "read-config-file": "6.3.2",
+ "sanitize-filename": "^1.6.3",
+ "semver": "^7.3.8",
+ "tar": "^6.1.12",
+ "temp-file": "^3.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "dmg-builder": "24.13.3",
+ "electron-builder-squirrel-windows": "24.13.3"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/semver": {
+ "version": "7.7.3",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/archiver": {
+ "version": "5.3.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "archiver-utils": "^2.1.0",
+ "async": "^3.2.4",
+ "buffer-crc32": "^0.2.1",
+ "readable-stream": "^3.6.0",
+ "readdir-glob": "^1.1.2",
+ "tar-stream": "^2.2.0",
+ "zip-stream": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/archiver-utils": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/archiver-utils/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-exit-hook": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/author-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz",
+ "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/birpc": {
+ "version": "2.9.0",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bluebird-lst": {
+ "version": "1.0.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.5.5"
+ }
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-equal": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/builder-util": {
+ "version": "24.13.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.1.6",
+ "7zip-bin": "~5.2.0",
+ "app-builder-bin": "4.0.0",
+ "bluebird-lst": "^1.0.9",
+ "builder-util-runtime": "9.2.4",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.3",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-ci": "^3.0.0",
+ "js-yaml": "^4.1.0",
+ "source-map-support": "^0.5.19",
+ "stat-mode": "^1.0.0",
+ "temp-file": "^3.4.0"
+ }
+ },
+ "node_modules/builder-util-runtime": {
+ "version": "9.2.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "sax": "^1.2.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/builder-util/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/builder-util/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/builder-util/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chromium-pickle-js": {
+ "version": "0.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/compress-commons": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "buffer-crc32": "^0.2.13",
+ "crc32-stream": "^4.0.2",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/config-file-ts": {
+ "version": "0.2.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^10.3.10",
+ "typescript": "^5.3.3"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/glob": {
+ "version": "10.5.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/minimatch": {
+ "version": "9.0.5",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/minipass": {
+ "version": "7.1.2",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/copy-anything": {
+ "version": "4.0.5",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/crc": {
+ "version": "3.8.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "buffer": "^5.1.0"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/crc32-stream": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "readable-stream": "^3.4.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/cross-env": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
+ "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@epic-web/invariant": "^1.0.0",
+ "cross-spawn": "^7.0.6"
+ },
+ "bin": {
+ "cross-env": "dist/bin/cross-env.js",
+ "cross-env-shell": "dist/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn-windows-exe": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz",
+ "integrity": "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-cross-spawn-windows-exe?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@malept/cross-spawn-promise": "^1.1.0",
+ "is-wsl": "^2.2.0",
+ "which": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/dir-compare": {
+ "version": "3.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal": "^1.0.0",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "node_modules/dir-compare/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/dir-compare/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dmg-builder": {
+ "version": "24.13.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "24.13.3",
+ "builder-util": "24.13.1",
+ "builder-util-runtime": "9.2.4",
+ "fs-extra": "^10.1.0",
+ "iconv-lite": "^0.6.2",
+ "js-yaml": "^4.1.0"
+ },
+ "optionalDependencies": {
+ "dmg-license": "^1.0.11"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/dmg-license": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
+ "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "@types/plist": "^3.0.1",
+ "@types/verror": "^1.10.3",
+ "ajv": "^6.10.0",
+ "crc": "^3.8.0",
+ "iconv-corefoundation": "^1.1.7",
+ "plist": "^3.0.4",
+ "smart-buffer": "^4.0.2",
+ "verror": "^1.10.0"
+ },
+ "bin": {
+ "dmg-license": "bin/dmg-license.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/iconv-corefoundation": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
+ "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "cli-truncate": "^2.1.0",
+ "node-addon-api": "^1.6.3"
+ },
+ "engines": {
+ "node": "^8.11.2 || >=10"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "9.0.2",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron": {
+ "version": "27.3.11",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-27.3.11.tgz",
+ "integrity": "sha512-E1SiyEoI8iW5LW/MigCr7tJuQe7+0105UjqY7FkmCD12e2O6vtUbQ0j05HaBh2YgvkcEVgvQ2A8suIq5b5m6Gw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/get": "^2.0.0",
+ "@types/node": "^18.11.18",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js"
+ },
+ "engines": {
+ "node": ">= 12.20.55"
+ }
+ },
+ "node_modules/electron-builder": {
+ "version": "24.13.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "24.13.3",
+ "builder-util": "24.13.1",
+ "builder-util-runtime": "9.2.4",
+ "chalk": "^4.1.2",
+ "dmg-builder": "24.13.3",
+ "fs-extra": "^10.1.0",
+ "is-ci": "^3.0.0",
+ "lazy-val": "^1.0.5",
+ "read-config-file": "6.3.2",
+ "simple-update-notifier": "2.0.0",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "electron-builder": "cli.js",
+ "install-app-deps": "install-app-deps.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows": {
+ "version": "24.13.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "app-builder-lib": "24.13.3",
+ "archiver": "^5.3.1",
+ "builder-util": "24.13.1",
+ "fs-extra": "^10.1.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-builder/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-builder/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-builder/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-packager": {
+ "version": "17.1.2",
+ "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-17.1.2.tgz",
+ "integrity": "sha512-XofXdikjYI7MVBcnXeoOvRR+yFFFHOLs3J7PF5KYQweigtgLshcH4W660PsvHr4lYZ03JBpLyEcUB8DzHZ+BNw==",
+ "deprecated": "Please use @electron/packager moving forward. There is no API change, just a package name change",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "@electron/get": "^2.0.0",
+ "@electron/notarize": "^1.2.3",
+ "@electron/osx-sign": "^1.0.5",
+ "@electron/universal": "^1.3.2",
+ "cross-spawn-windows-exe": "^1.2.0",
+ "debug": "^4.0.1",
+ "extract-zip": "^2.0.0",
+ "filenamify": "^4.1.0",
+ "fs-extra": "^11.1.0",
+ "galactus": "^1.0.0",
+ "get-package-info": "^1.0.0",
+ "junk": "^3.1.0",
+ "parse-author": "^2.0.0",
+ "plist": "^3.0.0",
+ "rcedit": "^3.0.1",
+ "resolve": "^1.1.6",
+ "semver": "^7.1.3",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "electron-packager": "bin/electron-packager.js"
+ },
+ "engines": {
+ "node": ">= 14.17.5"
+ },
+ "funding": {
+ "url": "https://github.com/electron/electron-packager?sponsor=1"
+ }
+ },
+ "node_modules/electron-packager/node_modules/@electron/notarize": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.4.tgz",
+ "integrity": "sha512-W5GQhJEosFNafewnS28d3bpQ37/s91CDWqxVchHfmv2dQSTWpOzNlUVQwYzC1ay5bChRV/A9BTL68yj0Pa+TSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-packager/node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/electron-packager/node_modules/fs-extra": {
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+ "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/electron-packager/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-packager/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/electron-packager/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-publish": {
+ "version": "24.13.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^9.0.11",
+ "builder-util": "24.13.1",
+ "builder-util-runtime": "9.2.4",
+ "chalk": "^4.1.2",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "mime": "^2.5.2"
+ }
+ },
+ "node_modules/electron-publish/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-publish/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-publish/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron/node_modules/@types/node": {
+ "version": "18.19.130",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/electron/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "7.0.0",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "license": "MIT"
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/extsprintf": {
+ "version": "1.4.1",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/flora-colossus": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz",
+ "integrity": "sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs-minipass/node_modules/minipass": {
+ "version": "3.3.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/galactus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz",
+ "integrity": "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "flora-colossus": "^2.0.0",
+ "fs-extra": "^10.1.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/galactus/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/galactus/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/galactus/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-info": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz",
+ "integrity": "sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.1.1",
+ "debug": "^2.2.0",
+ "lodash.get": "^4.0.0",
+ "read-pkg-up": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 4.0"
+ }
+ },
+ "node_modules/get-package-info/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/get-package-info/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/global-agent/node_modules/semver": {
+ "version": "7.7.3",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "license": "MIT"
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-ci": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ci-info": "^3.2.0"
+ },
+ "bin": {
+ "is-ci": "bin.js"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "5.5.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/isbinaryfile": {
+ "version": "5.0.7",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/joi": {
+ "version": "17.13.3",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.3.0",
+ "@hapi/topo": "^5.1.0",
+ "@sideway/address": "^4.1.5",
+ "@sideway/formula": "^3.0.1",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/junk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
+ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/lazy-val": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lazystream": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "readable-stream": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.6.3"
+ }
+ },
+ "node_modules/lazystream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/lazystream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lazystream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.union": {
+ "version": "4.6.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "5.1.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minizlib/node_modules/minipass": {
+ "version": "3.3.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "license": "MIT"
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "1.7.2",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parse-author": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz",
+ "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "author-regex": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "error-ex": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "license": "ISC"
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
+ "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.7"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.5.0",
+ "vue": "^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rcedit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz",
+ "integrity": "sha512-WRlRdY1qZbu1L11DklT07KuHfRk42l0NFFJdaExELEu4fEQ982bP5Z6OWGPj/wLLIuKRQDCxZJGAwoFsxhZhNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn-windows-exe": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/read-config-file": {
+ "version": "6.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "config-file-ts": "^0.2.4",
+ "dotenv": "^9.0.2",
+ "dotenv-expand": "^5.1.0",
+ "js-yaml": "^4.1.0",
+ "json5": "^2.2.0",
+ "lazy-val": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdir-glob": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "minimatch": "^5.1.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "license": "MIT"
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/rollup/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sanitize-filename": {
+ "version": "1.6.3",
+ "dev": true,
+ "license": "WTFPL OR ISC",
+ "dependencies": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.7.3",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.22",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz",
+ "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/stat-mode": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-outer/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/superjson": {
+ "version": "2.2.6",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.2.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/temp-file": {
+ "version": "3.4.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-exit-hook": "^2.0.1",
+ "fs-extra": "^10.0.0"
+ }
+ },
+ "node_modules/temp-file/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/temp-file/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/temp-file/node_modules/universalify": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/tmp-promise": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tmp": "^0.2.0"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/trim-repeated/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "WTFPL",
+ "dependencies": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/uplot": {
+ "version": "1.6.32",
+ "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.32.tgz",
+ "integrity": "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==",
+ "license": "MIT"
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utf8-byte-length": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/verror": {
+ "version": "1.10.1",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.26",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/compiler-sfc": "3.5.26",
+ "@vue/runtime-dom": "3.5.26",
+ "@vue/server-renderer": "3.5.26",
+ "@vue/shared": "3.5.26"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.4",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "license": "MIT"
+ },
+ "node_modules/wait-on": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.6.1",
+ "joi": "^17.11.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "rxjs": "^7.8.1"
+ },
+ "bin": {
+ "wait-on": "bin/wait-on"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/zip-stream": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "archiver-utils": "^3.0.4",
+ "compress-commons": "^4.1.2",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/zip-stream/node_modules/archiver-utils": {
+ "version": "3.0.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.2.3",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..d2803fe
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "smartedt-frontend",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "main": "main/main.cjs",
+ "scripts": {
+ "dev": "concurrently -k \"vite\" \"wait-on http://127.0.0.1:5173 && cross-env ELECTRON_OVERRIDE_DIST_PATH=d:\\electron-v39.2.7-win32-x64 electron .\"",
+ "build": "vite build",
+ "preview": "vite preview",
+ "pack": "npm run build && electron-builder --dir",
+ "dist": "npm run build && electron-builder"
+ },
+ "dependencies": {
+ "pinia": "^3.0.4",
+ "uplot": "^1.6.32",
+ "vue": "^3.5.0",
+ "vue-router": "^4.4.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.0",
+ "concurrently": "^9.0.0",
+ "cross-env": "^10.1.0",
+ "electron": "^27.3.11",
+ "electron-builder": "^24.13.3",
+ "electron-packager": "^17.1.2",
+ "vite": "^5.4.0",
+ "wait-on": "^7.2.0"
+ },
+ "build": {
+ "appId": "com.smartedt.app",
+ "productName": "SmartEDT",
+ "directories": {
+ "output": "release"
+ },
+ "win": {
+ "target": [
+ "nsis"
+ ]
+ },
+ "nsis": {
+ "oneClick": false,
+ "allowToChangeInstallationDirectory": true
+ },
+ "files": [
+ "dist/**",
+ "main/**",
+ "package.json"
+ ],
+ "extraResources": [
+ {
+ "from": "../backend/dist/smartedt_backend",
+ "to": "backend",
+ "filter": [
+ "**/*"
+ ]
+ }
+ ]
+ }
+}
diff --git a/frontend/query b/frontend/query
new file mode 100644
index 0000000..1a0aea4
--- /dev/null
+++ b/frontend/query
@@ -0,0 +1 @@
+postgresql-x64-17
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
new file mode 100644
index 0000000..d93135f
--- /dev/null
+++ b/frontend/src/App.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js
new file mode 100644
index 0000000..9b85e1b
--- /dev/null
+++ b/frontend/src/lib/api.js
@@ -0,0 +1,26 @@
+export const API_BASE = "http://127.0.0.1:5000";
+
+export async function getHealth() {
+ const res = await fetch(`${API_BASE}/health`);
+ if (!res.ok) throw new Error(`health failed: ${res.status}`);
+ return res.json();
+}
+
+export async function startSimulation(payload) {
+ const res = await fetch(`${API_BASE}/api/simulation/start`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload || {})
+ });
+ if (!res.ok) throw new Error(`start failed: ${res.status}`);
+ return res.json();
+}
+
+export async function stopSimulation(simulationId) {
+ const res = await fetch(`${API_BASE}/api/simulation/${encodeURIComponent(simulationId)}/stop`, {
+ method: "POST"
+ });
+ if (!res.ok) throw new Error(`stop failed: ${res.status}`);
+ return res.json();
+}
+
diff --git a/frontend/src/lib/ws.js b/frontend/src/lib/ws.js
new file mode 100644
index 0000000..4fc60f7
--- /dev/null
+++ b/frontend/src/lib/ws.js
@@ -0,0 +1,15 @@
+export function connectWs(onMessage) {
+ const ws = new WebSocket("ws://127.0.0.1:5000/ws");
+ ws.onmessage = (evt) => {
+ try {
+ onMessage(JSON.parse(evt.data));
+ } catch {
+ onMessage({ type: "raw", payload: evt.data });
+ }
+ };
+ ws.onopen = () => {
+ ws.send("hello");
+ };
+ return ws;
+}
+
diff --git a/frontend/src/main.js b/frontend/src/main.js
new file mode 100644
index 0000000..4e9ac0a
--- /dev/null
+++ b/frontend/src/main.js
@@ -0,0 +1,36 @@
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import { createRouter, createWebHashHistory } from "vue-router";
+import App from "./App.vue";
+import BigDashboard from "./pages/BigDashboard.vue";
+import BigSystemIntro from "./pages/BigSystemIntro.vue";
+import BigSimMonitor from "./pages/BigSimMonitor.vue";
+import CarSim from "./pages/CarSim.vue";
+import ControlConfig from "./pages/ControlConfig.vue";
+import ControlOperate from "./pages/ControlOperate.vue";
+import ControlQuery from "./pages/ControlQuery.vue";
+import ControlAnalysis from "./pages/ControlAnalysis.vue";
+import ControlAdmin from "./pages/ControlAdmin.vue";
+import ServerMonitor from "./pages/ServerMonitor.vue";
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ routes: [
+ { path: "/", redirect: "/control/config" },
+ { path: "/big/system-intro", component: BigSystemIntro },
+ { path: "/big/dashboard", component: BigDashboard },
+ { path: "/big/sim-monitor", component: BigSimMonitor },
+ { path: "/car/sim", component: CarSim },
+ { path: "/control/config", component: ControlConfig },
+ { path: "/control/operate", component: ControlOperate },
+ { path: "/control/query", component: ControlQuery },
+ { path: "/control/analysis", component: ControlAnalysis },
+ { path: "/control/admin", component: ControlAdmin },
+ { path: "/control/server-monitor", component: ServerMonitor }
+ ]
+});
+
+const pinia = createPinia();
+createApp(App).use(router).use(pinia).mount("#app");
+
+
diff --git a/frontend/src/pages/BigDashboard.vue b/frontend/src/pages/BigDashboard.vue
new file mode 100644
index 0000000..4f6e792
--- /dev/null
+++ b/frontend/src/pages/BigDashboard.vue
@@ -0,0 +1,54 @@
+
+
+
数据看板
+
+
+
仿真记录
+
{{ simulationId || "-" }}
+
+
+
车速
+
{{ latest.vehicle_speed_kmh ?? "-" }}
+
+
+
SOC(%)
+
{{ latest.soc_percent ?? "-" }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/BigSimMonitor.vue b/frontend/src/pages/BigSimMonitor.vue
new file mode 100644
index 0000000..c7c7ece
--- /dev/null
+++ b/frontend/src/pages/BigSimMonitor.vue
@@ -0,0 +1,53 @@
+
+
+
仿真监控
+
+
+
档位
+
{{ latest.gear ?? "-" }}
+
+
+
手刹
+
{{ latest.handbrake ?? "-" }}
+
+
+
方向盘角度
+
{{ latest.steering_wheel_angle_deg ?? "-" }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/BigSystemIntro.vue b/frontend/src/pages/BigSystemIntro.vue
new file mode 100644
index 0000000..0228610
--- /dev/null
+++ b/frontend/src/pages/BigSystemIntro.vue
@@ -0,0 +1,16 @@
+
+
+
系统介绍
+
高精度数据采集、实时同步控制、三维虚拟仿真、算法验证与运维管理。
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/CarSim.vue b/frontend/src/pages/CarSim.vue
new file mode 100644
index 0000000..9d6819b
--- /dev/null
+++ b/frontend/src/pages/CarSim.vue
@@ -0,0 +1,36 @@
+
+
+
车载屏
+
+
天气:{{ weather }}
+
时段:{{ timePeriod }}
+
状态:{{ simStatus }}
+
车速:{{ latest.vehicle_speed_kmh ?? "-" }} km/h
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ControlAdmin.vue b/frontend/src/pages/ControlAdmin.vue
new file mode 100644
index 0000000..73347a8
--- /dev/null
+++ b/frontend/src/pages/ControlAdmin.vue
@@ -0,0 +1,16 @@
+
+
+
控制屏 - 管理
+
用于用户/角色与权限配置(后续扩展)。
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ControlAnalysis.vue b/frontend/src/pages/ControlAnalysis.vue
new file mode 100644
index 0000000..0e6ecb1
--- /dev/null
+++ b/frontend/src/pages/ControlAnalysis.vue
@@ -0,0 +1,16 @@
+
+
+
控制屏 - 数据分析
+
用于车辆数据分析、故障模拟诊断与 CAN 数据解析(后续扩展)。
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ControlConfig.vue b/frontend/src/pages/ControlConfig.vue
new file mode 100644
index 0000000..e41cc81
--- /dev/null
+++ b/frontend/src/pages/ControlConfig.vue
@@ -0,0 +1,76 @@
+
+
+
控制屏 - 场景配置
+
+
+
+
+
+
+
+
+ 进入仿真控制
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ControlOperate.vue b/frontend/src/pages/ControlOperate.vue
new file mode 100644
index 0000000..b101f61
--- /dev/null
+++ b/frontend/src/pages/ControlOperate.vue
@@ -0,0 +1,51 @@
+
+
+
控制屏 - 仿真控制
+
+
+
+
当前仿真记录:{{ simulationId || "-" }}
+
+
+ 打开显示大屏 - 数据看板
+ 打开显示大屏 - 仿真监控
+ 打开车载屏
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ControlQuery.vue b/frontend/src/pages/ControlQuery.vue
new file mode 100644
index 0000000..a9bae63
--- /dev/null
+++ b/frontend/src/pages/ControlQuery.vue
@@ -0,0 +1,16 @@
+
+
+
控制屏 - 数据查询
+
用于查询仿真记录、系统日志与回放数据。
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/ServerMonitor.vue b/frontend/src/pages/ServerMonitor.vue
new file mode 100644
index 0000000..756b2f6
--- /dev/null
+++ b/frontend/src/pages/ServerMonitor.vue
@@ -0,0 +1,191 @@
+
+
+
服务器性能监控
+
+ 主机名: {{ hostname }}
+ 状态: {{ connected ? '🟢 已连接' : '🔴 断开' }}
+ 采样率: 50Hz (显示 10Hz)
+
+
+
+
+
CPU 使用率 (%)
+
+
{{ currentCpu }}%
+
+
+
内存使用率 (%)
+
+
{{ currentMem }}%
+
+
+
+
+
+
+
+
diff --git a/frontend/src/stores/simulation.js b/frontend/src/stores/simulation.js
new file mode 100644
index 0000000..f797a5f
--- /dev/null
+++ b/frontend/src/stores/simulation.js
@@ -0,0 +1,76 @@
+import { defineStore } from "pinia";
+import { ref, reactive } from "vue";
+
+export const useSimulationStore = defineStore("simulation", () => {
+ const ws = ref(null);
+ const connected = ref(false);
+ const simulationId = ref("");
+ const simulationStatus = ref("stopped");
+ const latestSignal = reactive({});
+ const serverMetrics = ref(null); // 新增服务器监控状态
+
+ function connect() {
+ if (ws.value) return;
+
+ ws.value = new WebSocket("ws://127.0.0.1:5000/ws");
+
+ ws.value.onopen = () => {
+ connected.value = true;
+ console.log("WS connected");
+ };
+
+ ws.value.onclose = () => {
+ connected.value = false;
+ ws.value = null;
+ console.log("WS disconnected");
+ // Simple reconnect logic could be added here
+ setTimeout(connect, 3000);
+ };
+
+ ws.value.onmessage = (evt) => {
+ try {
+ const msg = JSON.parse(evt.data);
+ handleMessage(msg);
+ } catch (e) {
+ console.error("WS parse error", e);
+ }
+ };
+ }
+
+ function handleMessage(msg) {
+ if (msg.type === "vehicle.signal") {
+ if (msg.simulation_id) {
+ simulationId.value = msg.simulation_id;
+ }
+ if (msg.payload) {
+ Object.assign(latestSignal, msg.payload);
+ }
+ } else if (msg.type === "simulation.status") {
+ if (msg.simulation_id) {
+ simulationId.value = msg.simulation_id;
+ }
+ if (msg.payload && msg.payload.status) {
+ simulationStatus.value = msg.payload.status;
+ }
+ } else if (msg.type === "server.metrics") {
+ serverMetrics.value = msg.payload;
+ }
+ }
+
+ function disconnect() {
+ if (ws.value) {
+ ws.value.close();
+ ws.value = null;
+ }
+ }
+
+ return {
+ connected,
+ simulationId,
+ simulationStatus,
+ latestSignal,
+ serverMetrics,
+ connect,
+ disconnect
+ };
+});
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..06dbc0a
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,16 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+
+export default defineConfig({
+ base: "./",
+ plugins: [vue()],
+ server: {
+ host: "127.0.0.1",
+ port: 5173
+ },
+ build: {
+ outDir: "dist",
+ emptyOutDir: true
+ }
+});
+
diff --git a/unity3D/unity开发的代码 b/unity3D/unity开发的代码
new file mode 100644
index 0000000..7a23fe8
--- /dev/null
+++ b/unity3D/unity开发的代码
@@ -0,0 +1,2 @@
+此目录放置unity开发的代码
+增了电池电量显示
\ No newline at end of file
diff --git a/智能电动车数字孪生系统功能规划2026.pdf b/智能电动车数字孪生系统功能规划2026.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..fa47fbe5c097dd5322db0b5ab16e9e1250324e74
GIT binary patch
literal 681208
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