commit aa4b14181c2612054ac6c97d1278ba95085a32b6
Author: root <13910913995@163.com>
Date: Mon Jan 19 14:27:41 2026 +0800
chore: 初始化项目并添加 .gitignore
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 0000000..fa47fbe
Binary files /dev/null and b/智能电动车数字孪生系统功能规划2026.pdf differ
diff --git a/系统开发技术方案.md b/系统开发技术方案.md
new file mode 100644
index 0000000..beefe9b
--- /dev/null
+++ b/系统开发技术方案.md
@@ -0,0 +1,416 @@
+# 智能电动汽车数字孪生系统开发技术方案(V1)
+智能电动车数字孪生系统软件通过集成高精度数据采集、实时同步控制、三维虚拟仿真、智能算法验证与系统运维管理等功能,构建了一个覆盖教学、科研与产业应用的智能电动汽车虚实融合实验平台。它不仅能实现真实驾驶环境的沉浸式再现与数据级同步,还能为科研人员提供精准可追溯的数据支持、灵活的算法验证接口和可扩展的数据分析工具,充分满足智能电动汽车领域对实验真实性、可靠性与创新性的高标准要求,推动智能驾驶与数字孪生技术的深度融合与发展。
+本文基于《智能电动车数字孪生系统功能规划(2026)》的目标,给出一套可落地的软件开发技术方案。Unity 三维驾驶仿真作为独立工程(独立代码库与发布包),通过标准接口与本系统对接。
+
+
+## 1. 建设目标与原则
+
+### 1.1 建设目标
+- 实现多源设备数据采集、统一管理与实时可视化展示
+- 构建“实体车—数字孪生体”数据交互:状态同步、数据采集、事件回放
+- 支撑多场景驾驶仿真联动(Unity 独立运行),实现同屏/多屏展示与协同操作
+- 实现实验管理与记录:仿真记录创建、过程记录、视频录制、数据持久化、历史回放
+- 本地化部署与数据安全:本地存储、最小暴露接口、路径越界防护
+
+### 1.2 设计原则
+- 分层解耦:设备接入、业务编排、数据服务、可视化展示相互独立
+- 实时优先:以事件/流式数据驱动为核心(WebSocket + HTTP)
+- 可扩展:设备类型、消息类型、算法模块、Unity 场景可插拔扩展
+- 可运维:统一配置、日志、诊断接口、打包发布与自动启动链路
+
+## 2. 总体架构
+
+### 2.1 架构分解
+系统由三类可独立发布、独立升级的可执行程序组成:
+
+1) **后端数据与控制服务(Python EXE)**
+- 负责设备采集、数据处理、仿真记录管理、持久化、文件存储、对外 API
+- 对前端提供 HTTP REST + WebSocket(实时推送)
+- 对 Unity 提供联动接口(可选双向:状态推送/指令下发)
+
+2) **桌面端可视化应用(Electron + Vue EXE)**
+- 负责 UI 展示、实验流程操作、多屏组织、外部程序(后端/Unity)启动与守护
+- 作为“应用入口”,统一呈现与统一运维
+
+3) **车辆驾驶仿真(Unity EXE,外部项目)**
+- 负责 3D 场景、动力学/视觉化仿真、互动控制
+- 与后端通过约定协议交换数据(HTTP/WebSocket/本地端口均可)
+
+### 2.2 数据流与控制流
+- **采集流**:设备/传感器 → 设备接入层 → 统一事件总线 → 处理/校准 → 入库/落盘 → WebSocket 推送
+- **控制流**:前端操作 → REST 指令 → 业务编排 → 下发到设备/控制柜,或同步给 Unity
+- **回放流**:历史仿真记录 → 查询元数据(数据库)→ 拉取文件(静态映射)→ 前端播放/曲线回放,或驱动 Unity 回放
+
+### 2.3 系统两大部分划分
+系统整体可分为两大部分(对应团队分工与发布包边界):
+
+1) **数据采集与处理(后端)**
+- 采集:方向盘/踏板/档位/手刹/车速轮速/灯光/电参/温度等
+- 处理:滤波、校准、融合、异常检测、指标计算
+- 管理:仿真记录、文件、数据库、权限(可选)
+- 服务:REST + WebSocket + 静态文件映射
+
+2) **界面显示与交互(Electron + Vue)**
+- 显示大屏:系统介绍、仿真监控、数据看板
+- 车载屏(驾驶员):驾驶视角信息与仪表盘
+- 控制屏:场景配置、系统配置、用户/权限、数据查询、数据分析
+
+### 2.4 推荐工程目录结构(本仓库)
+建议把“后端服务”和“前端桌面端”放在同一仓库便于协同开发;Unity 工程保持外部独立仓库,仅提交接口文档与发布包获取方式。
+
+```
+SmartEDT/
+ backend/ # Python:采集/处理/管理/API
+ device/ # 传感器设备接入与驱动(方向盘/踏板/灯光/电参等)
+ database/ # 数据存储(PostgreSQL/TimescaleDB)与数据访问层
+ config/ # 配置(ini/环境变量)与配置加载
+ main.py # 后端入口(启动 API/WS、初始化各模块)
+ utils.py # 通用工具(时间/路径/校验/日志等)
+ frontend/ # Electron + Vue:桌面端与多屏展示
+ src/ # Vue 渲染进程(页面/组件/路由)
+ main/ # Electron 主进程(多窗口、多屏、拉起后端/Unity)
+ (配置文件) # package.json / vite.config.* / electron-builder 配置等
+ docs/ # 方案、接口协议、运维文档
+ data/ # 默认数据目录(开发态可用)
+ release/ # 可选:打包产物临时输出目录(不提交版本库)
+```
+
+## 3. 技术选型与理由
+
+### 3.1 后端(Python)
+- **语言**:Python 3.13.1(配合硬件 SDK、算法生态与快速迭代)
+- **Web 框架**:FastAPI(异步友好、类型约束清晰、OpenAPI 自动生成)
+- **实时通讯**:WebSocket(FastAPI 原生或 Starlette WebSocket),用于状态/传感数据/事件推送
+- **进程内并发**:asyncio + 线程池/进程池(适配阻塞型硬件 SDK)
+- **持久化**:PostgreSQL18 + TimescaleDB(用于实时采集数据的时序存储)+ SQLAlchemy(ORM/迁移支持)
+- **文件存储**:本地文件系统(与数据库元数据关联)
+- **打包**:PyInstaller 打包为独立 EXE;开发环境使用 venv 隔离依赖
+
+
+### 3.2 前端(Electron + Vue)
+- **UI 框架**:Vue 3 + Vite(开发体验与生态成熟)
+- **桌面封装**:Electron(多窗口、多屏支持、可管理外部进程)
+- **本地通信**:HTTP + WebSocket 访问后端;可选 IPC(主进程与渲染进程)
+- **打包**:electron-builder 产出 Windows EXE 安装包/绿色包
+
+### 3.3 Unity(外部)
+- **三维仿真**:Unity(高效构建交互式驾驶场景与渲染)
+- **对接方式**:建议 WebSocket(低延迟实时同步)+ HTTP(配置、仿真记录、资源拉取)
+- **进程管理**:由 Electron 主进程统一拉起与关闭(可在指定显示器全屏)
+
+## 4. 后端模块设计(Python)
+
+### 4.1 模块划分
+- **配置中心(Config)**
+ - 统一读取 `config.ini`(就近优先原则:后端目录优先,其次项目根目录)
+ - 管理文件根目录、数据库路径、端口、日志等级等
+- **设备接入层(Adapters/Drivers)**
+ - 每类设备一个管理器:相机、IMU、压力/力传感、控制柜等
+ - 统一生命周期:init/connect/start/stop/disconnect
+ - 统一数据模型:采集原始数据 + 时间戳 + 设备元信息
+- **设备协调器(Coordinator)**
+ - 负责多设备协同启动/停止、状态聚合、异常重连策略
+ - 对上提供“仿真记录级操作”,对下调度各设备管理器
+- **数据处理层(Pipeline)**
+ - 校准/滤波/坐标变换/融合(按需求插拔)
+ - 统一输出“标准事件”,写入事件总线与持久化队列
+- **仿真实验管理(Simulation Experiment)**
+ - 流程:场景配置 → 仿真启动 → 仿真执行 → 仿真结束
+- **数据存储(Storage)**
+ - 元数据:PostgreSQL(仿真记录、设备、事件索引、文件索引)
+ - 实时采集数据:TimescaleDB Hypertable(高频写入、按时间范围查询与聚合)
+ - 典型能力:按时间分区、压缩、保留策略、连续聚合(按需要启用)
+ - 大文件:视频、原始采样、导出报告等存本地文件系统
+- **录制与归档(Recording/Archiving)**
+ - 仿真过程视频录制:显示大屏/车载屏/Unity 画面按需求录制(目标帧率 30fps)
+ - 录制任务与仿真记录绑定:开始/停止随仿真流程联动,支持异常中止与补偿关闭
+ - 文件索引入库:形成可回放、可导出的统一入口
+- **对外服务(API)**
+ - REST:健康检查、设备管理、仿真记录控制、数据查询、导出
+ - WebSocket:实时推送(状态、传感数据、告警、进度)
+- **静态文件映射(File Gateway)**
+ - 按配置的根目录对外只读映射
+ - 路径规范化、拒绝越界(拦截 `..`、绝对路径、UNC 路径)
+
+### 4.2 采集数据范围与精度(本期基线)
+本系统“采集与处理”侧的基线采集字段如下(用于实时显示、联动仿真与历史回放);后续可按车型/传感器扩展。
+
+- **转向与踏板**
+ - 方向盘转角:左/右(单位建议:deg)
+ - 刹车踏板行程:mm
+ - 电门踏板行程:mm
+- **车辆状态**
+ - 挡位:P / N / D / S
+ - 手刹状态(手状态):0 / 1
+ - 车速:单位建议 km/h
+ - 车轮转速:单位建议 rpm(按轮位可扩展为 FL/FR/RL/RR)
+- **灯光状态**
+ - 左转向灯 / 右转向灯
+ - 双闪
+ - 刹车灯
+- **电池与电参**
+ - 电池剩余电量:%
+ - 电压:测量误差 0.1V
+ - 电流:测量误差 0.1A
+ - 温度:测量误差 0.5℃
+
+采集来源建议按两类接入:
+- **传感器/控制柜直采**:方向盘、踏板、档位、手刹、车速/轮速、电参等(串口/CAN/以太网,取决于硬件)
+- **视觉识别采集(可选)**:当部分状态仅存在于仪表或屏幕显示时,用相机 + 识别推断(需标注数据与容错策略)
+
+
+### 4.3 API 设计建议(摘要)
+- `GET /health`:健康检查(版本、时间、依赖状态)
+- `GET /api/devices`:设备列表与状态
+- `POST /api/simulation/start`:创建并启动一次三维仿真记录(返回 simulation_id)
+- `POST /api/simulation/{simulation_id}/stop`:结束一次三维仿真并落盘
+- `GET /api/simulation/{simulation_id}`:三维仿真记录元数据
+- `GET /api/simulation/{simulation_id}/events`:事件/曲线数据查询(分页/时间窗)
+- `GET /files/`:静态文件访问映射(对标 README 的安全校验策略)
+- `WS /ws`:统一实时通道(按 topic/room 订阅)
+
+### 4.4 实时消息模型(建议)
+采用统一 JSON Envelope,便于前端与 Unity 复用:
+
+```json
+{
+ "type": "vehicle.signal",
+ "ts": 1737000000.123,
+ "simulation_id": "SIM202601160001",
+ "device_id": "controlbox_01",
+ "seq": 1024,
+ "payload": {
+ "steering_wheel_angle_deg": 12.3,
+ "brake_pedal_travel_mm": 5.2,
+ "throttle_pedal_travel_mm": 18.0,
+ "gear": "D",
+ "handbrake": 0,
+ "vehicle_speed_kmh": 36.5,
+ "wheel_speed_rpm": { "FL": 320, "FR": 319, "RL": 318, "RR": 318 },
+ "lights": { "left_turn": 0, "right_turn": 1, "hazard": 0, "brake": 0 },
+ "soc_percent": 78.2,
+ "voltage_v": 356.4,
+ "current_a": 12.7,
+ "temperature_c": 28.5
+ }
+}
+```
+
+建议补充字段:
+- `source`:device/backend/unity
+- `quality`:丢包/校准状态/信噪比等
+- `schema_version`:前后端与 Unity 协同升级
+
+### 4.5 目录与文件存储规范(建议)
+与 README 的建议一致,采用“根目录 + 仿真记录分层”的稳定结构:
+
+```
+/
+ /
+ /
+ meta.json
+ video/
+ signals/
+ exports/
+```
+
+文件根目录通过 `[FILEPATH].path` 配置;当配置为相对路径时,开发环境相对后端工作目录,打包环境相对后端 EXE 同级目录(对标 README 的路径策略)。
+
+存储类型建议(与规划大纲一致):
+- 传感器原始数据:生成数据文件(如 `signals/raw_*.dat`),按时间段或按设备分文件落盘
+- 传感器/识别后的结构化数据:实时写入 PostgreSQL/TimescaleDB(用于查询、统计、回放曲线)
+- 仿真记录视频:按窗口/视角分别落入 `video/`,并在数据库中建立索引与标签(视角、分辨率、帧率等)
+
+### 4.6 TimescaleDB 时序数据建模建议
+为支持“高频写入 + 按时间范围回放/聚合”,建议将采集数据按时序表落库,并在 TimescaleDB 中转换为 Hypertable。
+
+推荐思路:
+- 一张时序主表:按 `ts` 做 Hypertable 分区
+- 以 `simulation_id` 作为业务主维度,支持一次三维仿真记录的时间窗查询与回放
+
+字段建议(可按性能需要做列化/半结构化折中):
+- `ts`:时间戳(TimescaleDB 分区键)
+- `simulation_id`:三维仿真记录 ID(用于回放与归档)
+- `device_id`:数据来源设备
+- `signals`:结构化信号(可选 JSONB,或将高频字段拆为列以提升聚合效率)
+
+典型策略(按需启用):
+- 保留策略:只保留近 N 天原始点,长期保留用压缩或降采样结果
+- 连续聚合:生成 1s/100ms 粒度的统计序列用于大屏曲线与回放加速
+
+## 5. 前端模块设计(Electron + Vue)
+
+### 5.1 界面体系(显示大屏 / 车载屏 / 控制屏)
+界面显示功能分为三类屏幕(与规划大纲一致),由 Electron 统一创建窗口并在指定显示器呈现,Vue 负责页面实现。
+
+1) **显示大屏(面向展示/观摩)**
+- **系统介绍**
+ - 系统功能介绍:图文并茂;可扩展语音播报与 AI 问答导览
+ - 车辆三维展示:外观/内饰动态展示;关键部件结构展示(转向机构、驱动机构等)
+ - 系统原理展示:动力/转向/制动/灯光等子系统工作原理可视化
+- **数据看板**
+ - 数据主屏:中间为车辆三维动态模型;周边展示车辆概况、车辆参数(示例:秦 Plus)、历次仿真记录、设备监控(CPU/内存)、模拟报警信息
+ - 仿真回放屏:对单次仿真结果回放;三维仿真回放 + 采集数据动态图表回放;进度显示(总时长/当前进度)
+- **仿真监控**
+ - 视角切换:主视角 / 驾驶员视角
+ - 主视角:路线图(动态)+ 车辆尾随视角
+ - 驾驶员视角:三维场景动态 + 仪表盘(车速、转速、档位、手刹、车灯、电量等)
+ - 仿真视频录制:动态录制仿真视频过程屏幕(目标 30fps),并与仿真记录绑定
+
+2) **车载屏(驾驶员视角)**
+- 仿真界面:三维仿真场景;天气与时段显示;车速显示;仿真状态标识(等待/仿真中/结束)
+- 数据记录:后台记录仿真过程记录,并动态录制车载屏仿真屏幕(目标 30fps)
+
+3) **控制屏(操作与管理)**
+- 场景配置
+ - 仿真场景:城市道路/高速公路/山区道路/乡村道路/校园道路
+ - 气象:晴/雨/雪/雾等;时段:黎明/正午/黄昏/夜间
+ - 车辆颜色:红/白/黑;最高限速:100–180 km/h
+ - 仿真时长:5–60 分钟;驾驶员选择:人员/账号绑定
+ - 车辆传感器校正:自检/校正
+ - 场景推送:下发到车载屏与显示大屏
+- 仿真控制:开始仿真 / 结束仿真
+- 系统配置:功能介绍(文字)、端口与路径等基础配置入口
+- 用户配置:角色与权限配置;用户与角色选择
+- 数据查询:系统日志查询;仿真记录查询
+- 数据分析:车辆数据分析、故障模拟诊断、CAN 总线数据模拟解析
+
+### 5.2 与后端的通信
+- REST:用于仿真记录控制、配置读写、历史查询、导出触发
+- WebSocket:订阅实时 topic(设备状态、采样数据、告警、录制进度)
+- 静态文件:通过后端安全映射路径访问视频与大文件(支持 Range)
+
+### 5.3 前端路由与窗口映射(建议)
+为便于多屏稳定运行,建议把“窗口类型”与“路由入口”固化:
+- 显示大屏
+ - `/big/system-intro`
+ - `/big/dashboard`
+ - `/big/sim-monitor`
+- 车载屏
+ - `/car/sim`
+- 控制屏
+ - `/control/config`
+ - `/control/operate`
+ - `/control/query`
+ - `/control/analysis`
+ - `/control/admin`
+
+### 5.4 外部进程启动与守护(Electron 主进程)
+主进程负责统一拉起与管理:
+- 后端 EXE:启动时分配端口(固定端口或自动探测),轮询 `/health` 确认就绪
+- Unity EXE:按用户选择显示器启动(命令行传参:端口、simulation_id、模式等)
+- 异常策略:崩溃检测、重启次数限制、日志采集、用户提示
+
+推荐机制:
+- 端口管理:优先读配置,冲突时自动寻找可用端口并回写运行态配置
+- 生命周期:Electron 退出时优雅关闭后端与 Unity(超时后强制)
+
+## 6. Unity 联动方案(外部项目对接约定)
+
+### 6.1 对接目标
+- 实时同步实体车/传感器状态到 Unity(姿态、速度、踏板、方向、报警等)
+- Unity 交互事件回传(场景切换、碰撞事件、驾驶行为、训练步骤等)
+- 支持“实时模式”和“回放模式”
+
+### 6.2 建议协议
+建议使用同一套消息 Envelope:
+- **WebSocket(实时)**:订阅 `vehicle.signal`、`control.command` 等;Unity 回传 `unity.event.*`
+- **HTTP(控制/查询)**:加载仿真记录、读取配置、拉取资源、触发导出
+
+### 6.3 同步与时钟策略
+- 后端为权威时间源(服务端时间戳),Unity 按 `ts` 对齐插值
+- 允许 Unity 维护环形缓冲(例如 100–300ms)平衡抖动与延迟
+
+## 7. 配置与环境管理
+
+### 7.1 venv 管理与依赖
+- 开发环境:`python -m venv venv`,使用 `requirements.txt` 固定依赖版本
+- 构建环境:单独 `requirements_build.txt`(包含 PyInstaller 等打包依赖)
+
+### 7.2 配置文件(INI)
+建议统一使用以下段落(可按需扩展):
+
+```ini
+[SERVER]
+host = 0.0.0.0
+port = 5000
+
+[FILEPATH]
+path = data
+
+[DATABASE]
+url = postgresql+psycopg://smartedt:CHANGE_ME@127.0.0.1:5432/smartedt
+timescaledb = True
+```
+
+## 8. 打包发布与部署
+
+### 8.1 后端打包(PyInstaller)
+产物目标:
+- `Backend.exe`(单文件或目录模式)
+- `config.ini`(可外置,便于现场修改)
+- `data/`(默认数据目录,可按配置指向其他盘符)
+
+建议输出:
+- 内置 OpenAPI 文档(仅本机或受限网络可访问)
+- 日志写入到 `logs/`(按日期切分)
+
+### 8.2 前端打包(Electron)
+产物目标:
+- `SmartEDT.exe`(安装包/绿色包)
+- `resources/backend/Backend.exe`(随包携带或首次运行下载)
+- 统一版本号与升级策略(可选增量更新)
+
+### 8.3 一键启动链路
+1. 启动 `SmartEDT.exe`
+2. Electron 主进程读取配置并拉起 `Backend.exe`
+3. 后端健康检查通过后,渲染进程连接 REST/WS
+4. 用户选择“仿真模式”时,拉起 Unity EXE 并注入运行参数
+
+## 9. 多屏显示方案
+
+### 9.1 目标
+- 控制屏在主屏(操作与管理入口)
+- 显示大屏在副屏/大屏(系统介绍 / 数据看板 / 仿真监控)
+- 车载屏在驾驶员屏(或指定小屏)
+- Unity 全屏运行在指定显示器(可与显示大屏分离)
+
+### 9.2 Electron 实现要点
+- 启动时枚举显示器,建立“显示器—窗口类型”映射
+- 针对不同显示器创建多个 BrowserWindow(固定 URL/路由)
+- 提供“显示器管理”设置页:拖拽绑定、保存配置、开机恢复
+- Unity 启动参数附带目标显示器索引(或由 Unity 自行选择)
+
+建议的窗口类型:
+- BigScreenWindow:显示大屏(可切换 system-intro/dashboard/sim-monitor)
+- CarScreenWindow:车载屏(驾驶员视角)
+- ControlWindow:控制屏(配置、控制、查询、分析、管理)
+
+## 10. 安全、可靠性与合规
+
+### 10.1 本地数据安全
+- 默认仅本机访问(可通过配置开放局域网)
+- 静态文件映射做路径规范化与越界拦截(对标 README 的安全策略)
+- 敏感字段脱敏显示(subject_id 映射/匿名化)
+
+### 10.2 稳定性策略
+- 设备断连:自动重连、指数退避、状态广播
+- 写盘保护:异步队列 + 批量提交;异常时保证仿真记录可关闭并可恢复
+- 资源管理:视频录制与采样队列限速,避免磁盘/CPU 被打满
+
+## 11. 日志、诊断与监控
+- 分级日志:设备/仿真记录/API/存储
+- 诊断接口:`/health` 返回依赖与核心子系统状态(设备数量、队列长度等)
+- 现场排障:一键导出日志与仿真记录元数据(不含隐私或做脱敏)
+
+## 12. 测试与质量保证
+- 单元测试:消息模型、路径安全、数据库 CRUD、仿真流程状态机
+- 集成测试:模拟设备数据源(Mock Adapter),验证 WS 推送与落盘
+- 性能测试:高频 IMU、视频录制并发、长时仿真稳定性(内存/磁盘增长)
+
+## 13. 交付物清单(建议)
+- 后端:可执行程序、配置模板、API 文档、设备接入开发规范
+- 前端:桌面 EXE、显示器管理、实验流程 UI、日志导出
+- 对接:Unity 通讯协议文档(topic/消息体/端口/参数)、联调用例
+- 运维:安装说明、目录与数据管理说明、故障排查手册
+
diff --git a/系统框架结构评估.md b/系统框架结构评估.md
new file mode 100644
index 0000000..5a36f31
--- /dev/null
+++ b/系统框架结构评估.md
@@ -0,0 +1,66 @@
+# 系统框架结构评估与优化建议
+
+本文档基于 `d:\Trae_space\SmartEDT\README.md` 与实际代码结构,对已生成的 SmartEDT 系统框架(后端 Python 3.13.1 + FastAPI + TimescaleDB,前端 Electron + Vue)进行评估与分析,并提出优化改进点。
+
+## 1. 总体评估
+
+**结论**:系统框架整体架构合理,层次清晰,符合《系统开发技术方案.md》的要求,能够支撑后续功能开发。
+
+**亮点**:
+- **架构分层清晰**:后端按 `api`(接口)、`services`(业务逻辑)、`database`(存储)、`device`(硬件抽象)分层,职责单一。
+- **技术选型落地准确**:Python 3.13.1 + FastAPI + WebSocket + PostgreSQL/TimescaleDB 组合落地,前端 Electron + Vue 多窗口方案已具雏形。
+- **工程化细节完备**:包含 `config.ini` 配置加载、Mock 设备实现、Docker 友好的环境变量覆盖、静态文件安全映射、以及自动化的数据库 Schema 初始化。
+- **扩展性良好**:`SimulationManager` 封装了仿真生命周期,`MockVehicleDevice` 提供了设备接入的模板,便于后续替换为真实硬件驱动。
+
+## 2. 不足与改进建议
+
+尽管骨架已跑通,但在高频采集、健壮性与可维护性方面仍有优化空间:
+
+### 2.1 后端(Backend)
+
+1. **数据库连接池与异步写入优化**
+ - **现状**:`SimulationManager._run_loop` 中每次采集都调用 `_persist_signal`,内部使用 `async with self._session_factory()` 创建新 Session 并提交。
+ - **问题**:高频采集(如 50ms/20Hz)下,频繁创建/销毁 Session 会带来性能开销;且单条 Insert 效率较低。
+ - **改进**:
+ - 引入 **批量写入队列**(Batch Buffer),每 N 条或每 M 秒批量 `COPY` 或 `INSERT` 到 TimescaleDB。
+ - 确保 `engine` 配置了合理的连接池大小(`pool_size`, `max_overflow`)。
+
+2. **异常处理与优雅退出**
+ - **现状**:`main.py` 的 `shutdown` 钩子中只处理了 `simulation_manager.stop`。
+ - **问题**:如果 WebSocket 广播队列积压或数据库连接卡死,服务可能无法优雅退出。
+ - **改进**:
+ - 增加全局异常捕获(Global Exception Handler)。
+ - 在 `Broadcaster` 中增加队列满或断连的防御性逻辑。
+
+3. **配置热加载与验证**
+ - **现状**:`settings.py` 仅在启动时加载一次。
+ - **问题**:运行时无法动态调整日志级别或采集频率。
+ - **改进**:虽然不需要完全热加载,但建议增加配置项的 Pydantic 校验(当前已用 `pydantic-settings`,符合预期,但可增强校验规则)。
+
+4. **设备抽象层增强**
+ - **现状**:`MockVehicleDevice` 直接返回字典。
+ - **改进**:引入 Pydantic 模型或 TypedDict 定义设备数据协议,确保 `payload` 结构在代码层面有强类型约束。
+
+### 2.2 前端(Frontend)
+
+1. **状态管理缺失**
+ - **现状**:各 `.vue` 页面独立通过 `useWebSocket` 订阅数据。
+ - **问题**:多窗口间(如控制屏与大屏)可能需要共享某些全局状态(如当前仿真 ID、连接状态),各自维护连接可能造成资源浪费或状态不一致。
+ - **改进**:引入 Pinia 进行全局状态管理(如 `useSimulationStore`),统一管理 WS 连接与消息分发。
+
+2. **Electron 进程通信 (IPC) 封装**
+ - **现状**:`main.cjs` 启动了后端,但未建立健壮的 IPC 通道来透传后端日志或状态给渲染进程。
+ - **改进**:增加 `preload.js`,通过 `contextBridge` 暴露后端进程状态(PID、启动日志)给控制屏前端,便于运维排查。
+
+3. **多屏窗口管理增强**
+ - **现状**:`createWindows` 硬编码了 `others[0]`、`others[1]`。
+ - **改进**:增加配置文件(`window-layout.json`)记录屏幕 ID 与窗口路由的绑定关系,实现“开机自动恢复上次布局”。
+
+## 3. 优化计划
+
+基于以上分析,将对代码进行以下针对性优化:
+
+1. **后端**:优化 `SimulationManager` 的入库逻辑,改为 `Buffer` 批量写入模式。
+2. **后端**:增强 `device` 数据模型的类型定义。
+3. **前端**:引入 Pinia 并重构 WS 逻辑为 Store 模式。
+4. **工程**:补充 `docker-compose.yml` 以便快速拉起 PostgreSQL + TimescaleDB 环境。