From f182e873797baf0dd131fff51c785c37237b7895 Mon Sep 17 00:00:00 2001 From: DavidWangW <540097031@qq.com> Date: Tue, 30 Jun 2026 15:27:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9F=E5=BA=A6?= =?UTF-8?q?=E4=BC=B0=E8=AE=A1=E6=A0=A1=E5=87=86=E5=B7=A5=E5=85=B7=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E6=9B=B4=E6=8D=A2=E5=88=86=E8=BE=A8=E7=8E=87?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E9=87=8D=E6=96=B0=E6=A0=A1=E5=AF=B9=E9=80=9F?= =?UTF-8?q?=E5=BA=A6=E8=AE=A1=E7=AE=97SOURCE=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HIGHWAY_VISION_DEV_GUIDE.md | 802 ++++++++++++++++++ examples/speed_estimation/calibrate_source.py | 140 +++ 2 files changed, 942 insertions(+) create mode 100644 HIGHWAY_VISION_DEV_GUIDE.md create mode 100644 examples/speed_estimation/calibrate_source.py diff --git a/HIGHWAY_VISION_DEV_GUIDE.md b/HIGHWAY_VISION_DEV_GUIDE.md new file mode 100644 index 0000000000..30ece9a424 --- /dev/null +++ b/HIGHWAY_VISION_DEV_GUIDE.md @@ -0,0 +1,802 @@ +# Supervision 项目介绍与高速公路视觉分析系统开发指南 + +## 一、项目介绍 + +### 1.1 项目概述 + +**supervision** 是由 Roboflow 维护的开源计算机视觉工具库,被誉为"你的计算机视觉必备工具包"。它是一个模型无关(model-agnostic)的 Python 库,核心设计理念是将来自不同检测、分割、分类和关键点模型的输出标准化为统一的数据容器,然后在此基础上提供丰富的标注、跟踪、区域计数、数据集管理和指标评估工具。 + +- **版本**: `0.29.0.dev`(开发版) +- **许可证**: MIT +- **Python**: `>=3.9`(支持 3.9 ~ 3.14) +- **上游仓库**: https://github.com/roboflow/supervision +- **Fork 仓库**: https://github.com/DavidWangW/supervision +- **官方文档**: https://supervision.roboflow.com/latest/ + +### 1.2 核心特性 + +| 特性 | 说明 | +|------|------| +| 模型无关 | 支持 10+ 模型家族的标准化输出转换(YOLO、Transformers、Detectron2、MMDetection 等) | +| 统一数据容器 | `Detections`、`KeyPoints`、`Classifications` 三大核心数据类 | +| 丰富的标注器 | 22+ 可组合的可视化标注器(框、掩码、标签、热力图、轨迹等) | +| 多目标跟踪 | 内置 ByteTrack 跟踪器(已废弃,推荐使用外部 `trackers` 包) | +| 区域计数 | `LineZone`(线穿越计数)和 `PolygonZone`(多边形区域计数) | +| 数据集管理 | 支持 COCO / YOLO / Pascal VOC 格式的加载、保存、拆分与合并 | +| 评估指标 | mAP、mAR、混淆矩阵、精确率、召回率、F1 | +| 视频处理 | 帧生成器、视频写入、FPS 监控等视频工具 | + +### 1.3 项目结构 + +``` +d:\MyGithubRepo\supervision\ +├── .github/ # CI、CONTRIBUTING.md、工作流 +├── docs/ # MkDocs 文档源码 +├── examples/ # 端到端示例脚本 +│ ├── tracking/ # 目标跟踪示例 +│ ├── count_people_in_zone/ # 区域计数示例 +│ ├── traffic_analysis/ # 交通流分析示例 ⭐ +│ ├── speed_estimation/ # 速度估算示例 ⭐ +│ ├── time_in_zone/ # 区域停留时间示例 +│ ├── heatmap_and_track/ # 热力图与轨迹示例 +│ └── compact_mask/ # 紧凑掩码示例 +├── src/ +│ └── supervision/ # 主包源码 +│ ├── detection/ # 核心检测容器 (Detections)、VLM、LineZone、PolygonZone、Sinks、Slicer +│ ├── annotators/ # 22+ 可视化标注器 +│ ├── dataset/ # 数据集管理 (COCO/YOLO/Pascal VOC) +│ ├── draw/ # 颜色系统与底层绘图原语 +│ ├── geometry/ # 几何原语: Point, Vector, Rect, Position +│ ├── tracker/ # ByteTrack 多目标跟踪器(已废弃) +│ ├── key_points/ # 关键点检测容器与标注器 +│ ├── classification/ # 分类容器 +│ ├── metrics/ # 评估指标: mAP, ConfusionMatrix, Precision, Recall, F1 +│ ├── utils/ # 视频/图像/文件/转换等工具 +│ └── validators/ # 内部验证函数 +├── tests/ # pytest 测试套件 +├── pyproject.toml # 构建/依赖/lint 配置 +├── mkdocs.yml # 文档构建配置 +└── uv.lock # uv 锁文件 +``` + +### 1.4 核心依赖 + +| 依赖 | 版本要求 | 用途 | +|------|---------|------| +| numpy | >=1.21.2 | 数组运算 | +| opencv-python | >=4.5.5.64 | 图像处理 | +| scipy | >=1.10 | 科学计算 | +| matplotlib | >=3.6 | 绘图/调色板 | +| pillow | >=9.4 | 图像处理 | +| tqdm | >=4.62.3 | 进度条 | +| pyyaml | >=5.3 | YAML 文件处理 | +| requests | >=2.26 | HTTP 请求(资源下载) | + +--- + +## 二、开发环境配置 + +### 2.1 前置条件 + +- Python >= 3.9 +- [uv](https://docs.astral.sh/uv/) 包管理器(已安装) +- Git + +### 2.2 环境搭建(已完成) + +你已经通过 `git clone` 和 `uv` 完成了环境配置,以下是完整步骤回顾: + +```bash +# 1. 克隆 Fork 仓库 +git clone https://github.com/DavidWangW/supervision.git +cd supervision + +# 2. 使用 uv 创建虚拟环境并安装依赖 +uv sync + +# 3. 激活虚拟环境 (Windows PowerShell) +.venv\Scripts\Activate.ps1 + +# 4. 安装 pre-commit 钩子 +uv run pre-commit install +``` + +### 2.3 验证安装 + +```bash +# 运行测试套件,确认环境正常 +uv run pytest --cov=supervision + +# 运行 pre-commit 检查 +uv run pre-commit run --all-files +``` + +### 2.4 关键开发工具 + +| 工具 | 用途 | 命令 | +|------|------|------| +| uv | 包管理与虚拟环境 | `uv sync`, `uv run`, `uv add` | +| pytest | 测试运行 | `uv run pytest --cov=supervision` | +| pre-commit | 提交前自动检查 | `uv run pre-commit run --all-files` | +| ruff | 代码格式化与 lint | `uv run ruff check .`, `uv run ruff format .` | +| mypy | 类型检查 | `uv run mypy src/supervision` | + +--- + +## 三、代码规范与提交约定 + +### 3.1 分支策略 + +- 从 `develop` 分支创建新分支,使用前缀:`feat/`、`fix/`、`docs/`、`refactor/`、`test/`、`chore/` +- PR 必须指向 `develop` 分支 + +```bash +# 示例:创建功能分支 +git checkout develop +git pull origin develop +git checkout -b feat/highway-speed-display +``` + +### 3.2 提交信息规范(Conventional Commits) + +``` +feat: 新功能 +fix: 修复 bug +docs: 文档更新 +refactor: 重构(不改变行为) +perf: 性能优化 +test: 测试相关 +chore: 构建/工具/杂项 +``` + +### 3.3 代码风格 + +- **格式化与 Lint**: 由 **ruff** 强制执行(目标: py39, 行宽 88, 双引号) +- **类型注解**: 所有新代码必须添加类型注解,鼓励使用 mypy 严格模式 +- **文档字符串**: Google Python 风格,所有新函数和类必须包含,应包含可运行的示例 + +```python +def calculate_headway(distance: float, speed: float) -> float: + """Calculate time headway between two vehicles. + + Args: + distance: Distance between vehicles in meters. + speed: Speed of the following vehicle in m/s. + + Returns: + Time headway in seconds. + + Example: + >>> calculate_headway(50.0, 25.0) + 2.0 + """ + if speed <= 0: + return float("inf") + return distance / speed +``` + +### 3.4 提交前必做检查 + +```bash +uv run pytest --cov=supervision +uv run pre-commit run --all-files +``` + +--- + +## 四、核心 API 速查 + +### 4.1 Detections — 检测数据容器 + +`Detections` 是整个库最核心的数据结构,统一封装所有模型的输出: + +```python +import supervision as sv + +# 从 Ultralytics YOLO 模型创建 +detections = sv.Detections.from_ultralytics(results) + +# 字段说明 +detections.xyxy # 边界框 (N, 4) — [x1, y1, x2, y2] +detections.mask # 分割掩码 (N, H, W) 或 CompactMask +detections.confidence # 置信度 (N,) +detections.class_id # 类别 ID (N,) +detections.tracker_id # 跟踪 ID (N,) +detections.data # 自定义数据字典 (如 class_name) +detections.metadata # 集合级元数据 + +# 切片与过滤 +detections[0:5] # 前 5 个检测 +detections[detections.confidence > 0.5] # 高置信度过滤 +detections[detections.class_id == 2] # 按类别过滤 + +# 锚点坐标(用于标注定位) +points = detections.get_anchors_coordinates(sv.Position.BOTTOM_CENTER) +``` + +### 4.2 Annotators — 可组合标注器 + +标注器遵循**可组合模式**,每个标注器独立,通过链式调用叠加效果: + +```python +box_annotator = sv.BoxAnnotator(thickness=2) +label_annotator = sv.LabelAnnotator(text_scale=0.5) +trace_annotator = sv.TraceAnnotator(trace_length=30) + +# 链式标注 +annotated = frame.copy() +annotated = trace_annotator.annotate(scene=annotated, detections=detections) +annotated = box_annotator.annotate(scene=annotated, detections=detections) +annotated = label_annotator.annotate( + scene=annotated, detections=detections, labels=labels +) +``` + +**常用标注器一览:** + +| 标注器 | 用途 | 高速公路场景应用 | +|--------|------|-----------------| +| `BoxAnnotator` | 矩形框 | 车辆检测框 | +| `RoundBoxAnnotator` | 圆角矩形框 | 美化检测框 | +| `LabelAnnotator` | 文本标签 | 显示车速、ID | +| `TraceAnnotator` | 运动轨迹 | 车辆行驶轨迹 | +| `HeatMapAnnotator` | 热力图 | 交通密度可视化 | +| `PolygonAnnotator` | 多边形标注 | 标注检测区域 | +| `BlurAnnotator` | 模糊处理 | 隐私保护 | +| `PercentageBarAnnotator` | 百分比条 | 置信度/速度比例条 | + +### 4.3 Zone — 区域计数 + +#### LineZone — 线穿越计数 + +```python +# 定义计数线 +start = sv.Point(x=0, y=1080) +end = sv.Point(x=1920, y=1080) +line_zone = sv.LineZone(start=start, end=end) + +# 每帧更新 +crossed_in, crossed_out = line_zone.trigger(detections) + +# 可视化 +line_annotator = sv.LineZoneAnnotator(thickness=2) +annotated = line_annotator.annotate(scene=annotated, line_counter=line_zone) +``` + +#### PolygonZone — 多边形区域计数 + +```python +polygon = np.array([[0, 0], [1920, 0], [1920, 500], [0, 500]]) +zone = sv.PolygonZone(polygon=polygon) +inside = zone.trigger(detections) # 返回布尔数组 +detections_in_zone = detections[inside] +``` + +### 4.4 Tracker — 多目标跟踪 + +```python +# 内置 ByteTrack(已废弃但仍可用) +tracker = sv.ByteTrack(frame_rate=30) +detections = tracker.update_with_detections(detections) + +# 推荐:外部 trackers 包 +# pip install trackers +from trackers import ByteTrackTracker +tracker = ByteTrackTracker() +detections = tracker.update(detections) +``` + +### 4.5 Video — 视频处理工具 + +```python +# 视频信息 +video_info = sv.VideoInfo.from_video_path("input.mp4") +# video_info.fps, video_info.width, video_info.height, video_info.total_frames + +# 帧生成器 +frame_generator = sv.get_video_frames_generator(source_path="input.mp4") + +# 视频写入 +with sv.VideoSink("output.mp4", video_info) as sink: + for frame in frame_generator: + # 处理帧... + sink.write_frame(annotated_frame) +``` + +### 4.6 Sinks — 数据导出 + +```python +# CSV 导出 +with sv.CSVSink("detections.csv") as sink: + for frame in frame_generator: + # ...检测... + sink.append(detections, custom_data={"frame_id": i}) + +# JSON 导出 +with sv.JSONSink("detections.json") as sink: + # ...同上... + sink.append(detections) +``` + +### 4.7 Color — 颜色系统 + +```python +# 预定义颜色 +sv.Color.RED, sv.Color.GREEN, sv.Color.BLUE, sv.Color.WHITE, sv.Color.BLACK + +# 自定义颜色 +color = sv.Color.from_hex("#FF5733") + +# 调色板 +palette = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"]) +# 默认调色板: sv.ColorPalette.DEFAULT +``` + +--- + +## 五、高速公路视觉分析系统开发指南 + +### 5.1 系统目标 + +基于 supervision 库,开发一款面向**高速公路场景**的智能视觉分析系统,实现以下核心功能: + +| 功能模块 | 描述 | 对应 supervision 能力 | +|----------|------|----------------------| +| 车辆检测 | 实时检测画面中的车辆 | `Detections` + `from_ultralytics()` | +| 多目标跟踪 | 对检测到的车辆持续跟踪 | `ByteTrack` / `trackers` 包 | +| 车速显示 | 估算并标注每辆车的行驶速度 | `ViewTransformer` + `TraceAnnotator` + `LabelAnnotator` | +| 车流量统计 | 统计通过某截面的车辆数量 | `LineZone` + `LineZoneAnnotator` | +| 车头间距测量 | 计算同车道前后车辆之间的距离/时间间距 | 自定义逻辑 + 几何计算 | + +### 5.2 推荐技术栈 + +``` +检测模型: Ultralytics YOLO (yolo11x.pt 或自定义训练的高速公路模型) +跟踪器: ByteTrack (sv.ByteTrack 或 trackers 包) +标注: supervision annotators (BoxAnnotator, LabelAnnotator, TraceAnnotator, ...) +区域分析: LineZone, PolygonZone +透视变换: cv2.getPerspectiveTransform (像素→真实坐标映射) +视频 I/O: supervision VideoInfo + VideoSink + get_video_frames_generator +数据导出: CSVSink, JSONSink +``` + +### 5.3 系统架构设计 + +``` +┌──────────────────────────────────────────────────────┐ +│ 高速公路视觉分析系统 │ +├──────────────┬───────────────┬───────────────────────┤ +│ 输入层 │ 处理层 │ 输出层 │ +├──────────────┼───────────────┼───────────────────────┤ +│ 视频流/摄像头 │ 车辆检测 │ 可视化视频输出 │ +│ RTSP/本地文件 │ 多目标跟踪 │ 车速标注显示 │ +│ │ 透视坐标变换 │ 车流量统计面板 │ +│ │ 速度估算 │ 车头间距标注 │ +│ │ 区域计数 │ 数据导出(CSV/JSON) │ +│ │ 间距计算 │ 实时预警 │ +└──────────────┴───────────────┴───────────────────────┘ +``` + +### 5.4 核心模块实现思路 + +#### 5.4.1 车辆检测 + +```python +from ultralytics import YOLO +import supervision as sv + +model = YOLO("yolo11x.pt") # 或自定义训练模型 + +def detect_vehicles(frame: np.ndarray) -> sv.Detections: + """检测帧中的车辆(car=2, motorcycle=3, bus=5, truck=7)。""" + results = model(frame, conf=0.3, iou=0.7)[0] + detections = sv.Detections.from_ultralytics(results) + # 过滤车辆类别 (COCO 类别 ID) + vehicle_classes = [2, 3, 5, 7] + if detections.class_id is not None: + vehicle_mask = np.isin(detections.class_id, vehicle_classes) + detections = detections[vehicle_mask] + return detections +``` + +#### 5.4.2 多目标跟踪 + +```python +tracker = sv.ByteTrack(frame_rate=30, track_activation_threshold=0.3) + +# 在每帧中更新跟踪 +detections = tracker.update_with_detections(detections) +# 之后可通过 detections.tracker_id 访问每辆车的唯一 ID +``` + +#### 5.4.3 车速估算 + +参考 `examples/speed_estimation/` 的实现思路,核心是**透视变换**将像素坐标映射到真实世界坐标: + +```python +class ViewTransformer: + """透视变换器:将图像像素坐标映射到真实世界坐标。""" + + def __init__(self, source: np.ndarray, target: np.ndarray) -> None: + source = source.astype(np.float32) + target = target.astype(np.float32) + self.m = cv2.getPerspectiveTransform(source, target) + + def transform_points(self, points: np.ndarray) -> np.ndarray: + if points.size == 0: + return points + reshaped_points = points.reshape(-1, 1, 2).astype(np.float32) + transformed_points = cv2.perspectiveTransform(reshaped_points, self.m) + return transformed_points.reshape(-1, 2) + + +# 使用方式: +# 1. SOURCE: 图像中 4 个标定点(如车道线的 4 个角点) +# 2. TARGET: 对应的真实世界坐标(米) +SOURCE = np.array([[1252, 787], [2298, 803], [5039, 2159], [-550, 2159]]) +TARGET_WIDTH = 25 # 米 +TARGET_HEIGHT = 250 # 米 +TARGET = np.array([ + [0, 0], [TARGET_WIDTH - 1, 0], + [TARGET_WIDTH - 1, TARGET_HEIGHT - 1], [0, TARGET_HEIGHT - 1], +]) + +view_transformer = ViewTransformer(source=SOURCE, target=TARGET) + +# 3. 速度计算:连续帧间跟踪点的位移 / 时间 +from collections import defaultdict, deque + +coordinates = defaultdict(lambda: deque(maxlen=int(fps))) + +# 每帧更新坐标 +points = detections.get_anchors_coordinates(sv.Position.BOTTOM_CENTER) +points = view_transformer.transform_points(points).astype(int) +for tracker_id, [_, y] in zip(detections.tracker_id, points): + coordinates[tracker_id].append(y) + +# 计算速度 +for tracker_id in detections.tracker_id: + if len(coordinates[tracker_id]) >= fps / 2: + distance = abs(coordinates[tracker_id][-1] - coordinates[tracker_id][0]) # 米 + time = len(coordinates[tracker_id]) / fps # 秒 + speed = distance / time * 3.6 # km/h + labels.append(f"#{tracker_id} {int(speed)} km/h") +``` + +> **重要提示**: `SOURCE` 和 `TARGET` 必须根据实际摄像头的视角单独标定,不同机位需要不同配置。 + +#### 5.4.4 车流量统计 + +使用 `LineZone` 在画面中设置虚拟检测线: + +```python +# 设置检测线(如高速公路横截面) +line_start = sv.Point(x=500, y=600) +line_end = sv.Point(x=1400, y=600) +line_zone = sv.LineZone(start=line_start, end=line_end) + +# 每帧触发 +crossed_in, crossed_out = line_zone.trigger(detections) + +# 获取累计计数 +in_count = line_zone.in_count +out_count = line_zone.out_count + +# 可视化 +line_annotator = sv.LineZoneAnnotator(thickness=2, text_thickness=2, text_scale=0.5) +annotated = line_annotator.annotate(scene=annotated, line_counter=line_zone) +``` + +多车道场景可使用多条 `LineZone`: + +```python +# 为每条车道创建独立的计数线 +lane_lines = [ + sv.LineZone(start=sv.Point(200, 600), end=sv.Point(600, 600)), + sv.LineZone(start=sv.Point(700, 600), end=sv.Point(1100, 600)), + sv.LineZone(start=sv.Point(1200, 600), end=sv.Point(1600, 600)), +] +``` + +#### 5.4.5 车头间距测量 + +此功能需要自定义开发,supervision 不直接提供。实现思路: + +```python +def calculate_headway( + detections: sv.Detections, + view_transformer: ViewTransformer, + lane_boundaries: list[np.ndarray], +) -> dict[int, float]: + """计算同车道前后车辆的车头时距(秒)。 + + Args: + detections: 包含 tracker_id 的检测数据。 + view_transformer: 透视变换器。 + lane_boundaries: 各车道的边界多边形。 + + Returns: + tracker_id 到车头时距的映射。 + """ + # 1. 将检测点转换到真实世界坐标 + points = detections.get_anchors_coordinates(sv.Position.BOTTOM_CENTER) + real_points = view_transformer.transform_points(points) + + # 2. 按车道分组 + lane_groups: dict[int, list[tuple[int, float]]] = defaultdict(list) + for i, point in enumerate(real_points): + for lane_id, boundary in enumerate(lane_boundaries): + if _point_in_polygon(point, boundary): + lane_groups[lane_id].append( + (detections.tracker_id[i], point[1]) # (id, y坐标) + ) + + # 3. 在每条车道内按 y 坐标排序,计算相邻车头时距 + headways: dict[int, float] = {} + for lane_id, vehicles in lane_groups.items(): + vehicles.sort(key=lambda v: v[1]) # 按行驶方向排序 + for i in range(1, len(vehicles)): + tracker_id, y_rear = vehicles[i - 1] + _, y_front = vehicles[i] + distance = abs(y_front - y_rear) # 米 + # 假设后车速度已知(从速度估算模块获取) + speed = get_speed(tracker_id) # m/s + if speed > 0: + headways[tracker_id] = distance / speed # 秒 + + return headways + + +def _point_in_polygon(point: np.ndarray, polygon: np.ndarray) -> bool: + """判断点是否在多边形内。""" + result = cv2.pointPolygonTest(polygon.astype(np.float32), point, False) + return result >= 0 +``` + +### 5.5 完整系统示例框架 + +以下是一个整合所有功能的基础框架: + +```python +from collections import defaultdict, deque + +import cv2 +import numpy as np +from ultralytics import YOLO + +import supervision as sv + + +# ============ 配置 ============ + +# 透视变换标定点(需根据实际摄像头标定) +SOURCE = np.array([[1252, 787], [2298, 803], [5039, 2159], [-550, 2159]]) +TARGET_WIDTH = 25 # 米 +TARGET_HEIGHT = 250 # 米 +TARGET = np.array([ + [0, 0], [TARGET_WIDTH - 1, 0], + [TARGET_WIDTH - 1, TARGET_HEIGHT - 1], [0, TARGET_HEIGHT - 1], +]) + +# 车流量统计检测线 +LINE_START = sv.Point(x=500, y=600) +LINE_END = sv.Point(x=1400, y=600) + +# 车辆类别 (COCO) +VEHICLE_CLASSES = [2, 3, 5, 7] # car, motorcycle, bus, truck + + +# ============ 透视变换器 ============ + +class ViewTransformer: + def __init__(self, source: np.ndarray, target: np.ndarray) -> None: + source = source.astype(np.float32) + target = target.astype(np.float32) + self.m = cv2.getPerspectiveTransform(source, target) + + def transform_points(self, points: np.ndarray) -> np.ndarray: + if points.size == 0: + return points + reshaped_points = points.reshape(-1, 1, 2).astype(np.float32) + transformed_points = cv2.perspectiveTransform(reshaped_points, self.m) + return transformed_points.reshape(-1, 2) + + +# ============ 主处理逻辑 ============ + +def main( + source_video_path: str, + target_video_path: str, + confidence_threshold: float = 0.3, + iou_threshold: float = 0.7, +) -> None: + """高速公路车辆检测、跟踪、测速、流量统计与间距分析。""" + # 初始化模型和工具 + video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) + model = YOLO("yolo11x.pt") + tracker = sv.ByteTrack( + frame_rate=video_info.fps, + track_activation_threshold=confidence_threshold, + ) + view_transformer = ViewTransformer(source=SOURCE, target=TARGET) + + # 初始化标注器 + thickness = sv.calculate_optimal_line_thickness( + resolution_wh=video_info.resolution_wh + ) + text_scale = sv.calculate_optimal_text_scale( + resolution_wh=video_info.resolution_wh + ) + box_annotator = sv.BoxAnnotator(thickness=thickness) + label_annotator = sv.LabelAnnotator( + text_scale=text_scale, + text_thickness=thickness, + text_position=sv.Position.BOTTOM_CENTER, + ) + trace_annotator = sv.TraceAnnotator( + thickness=thickness, + trace_length=int(video_info.fps * 2), + position=sv.Position.BOTTOM_CENTER, + ) + + # 初始化车流量统计 + line_zone = sv.LineZone(start=LINE_START, end=LINE_END) + line_annotator = sv.LineZoneAnnotator(thickness=thickness) + + # 速度追踪 + coordinates = defaultdict(lambda: deque(maxlen=int(video_info.fps))) + speeds: dict[int, float] = {} + + # 视频处理循环 + frame_generator = sv.get_video_frames_generator(source_path=source_video_path) + with sv.VideoSink(target_video_path, video_info) as sink: + for frame in frame_generator: + # 1. 检测 + results = model(frame, conf=confidence_threshold, iou=iou_threshold)[0] + detections = sv.Detections.from_ultralytics(results) + + # 2. 过滤车辆类别 + if detections.class_id is not None: + vehicle_mask = np.isin(detections.class_id, VEHICLE_CLASSES) + detections = detections[vehicle_mask] + + # 3. 多目标跟踪 + detections = tracker.update_with_detections(detections=detections) + + # 4. 速度估算 + points = detections.get_anchors_coordinates( + anchor=sv.Position.BOTTOM_CENTER + ) + points = view_transformer.transform_points(points=points).astype(int) + + for tracker_id, [_, y] in zip(detections.tracker_id, points): + coordinates[tracker_id].append(y) + + labels = [] + for tracker_id in detections.tracker_id: + if len(coordinates[tracker_id]) < video_info.fps / 2: + labels.append(f"#{tracker_id}") + speeds[tracker_id] = 0.0 + else: + distance = abs( + coordinates[tracker_id][-1] - coordinates[tracker_id][0] + ) + time = len(coordinates[tracker_id]) / video_info.fps + speed = distance / time * 3.6 # km/h + speeds[tracker_id] = speed + labels.append(f"#{tracker_id} {int(speed)} km/h") + + # 5. 车流量统计 + line_zone.trigger(detections=detections) + + # 6. 可视化标注 + annotated_frame = frame.copy() + annotated_frame = trace_annotator.annotate( + scene=annotated_frame, detections=detections + ) + annotated_frame = box_annotator.annotate( + scene=annotated_frame, detections=detections + ) + annotated_frame = label_annotator.annotate( + scene=annotated_frame, detections=detections, labels=labels + ) + annotated_frame = line_annotator.annotate( + scene=annotated_frame, line_counter=line_zone + ) + + sink.write_frame(annotated_frame) + + +if __name__ == "__main__": + main( + source_video_path="data/highway.mp4", + target_video_path="data/highway_result.mp4", + ) +``` + +### 5.6 开发路线图 + +建议按以下阶段推进: + +**阶段一:基础检测与跟踪** +- [ ] 搭建项目脚手架,创建 `highway_vision/` 模块目录 +- [ ] 集成 YOLO 模型,实现车辆检测 +- [ ] 集成 ByteTrack,实现多目标跟踪 +- [ ] 基础可视化(检测框 + ID 标签 + 轨迹) + +**阶段二:速度估算** +- [ ] 实现透视变换标定工具(交互式选点) +- [ ] 实现 `ViewTransformer` 坐标映射 +- [ ] 实现速度估算算法 +- [ ] 车速标签可视化 + +**阶段三:流量统计** +- [ ] 实现 `LineZone` 车道检测线配置 +- [ ] 单车道流量计数 +- [ ] 多车道独立计数 +- [ ] 流量统计面板可视化 + +**阶段四:车头间距** +- [ ] 车道区域定义与车辆分组 +- [ ] 基于透视坐标的间距计算 +- [ ] 车头时距(THW)计算 +- [ ] 危险间距预警 + +**阶段五:系统集成与优化** +- [ ] 实时视频流支持(RTSP) +- [ ] 数据导出(CSV/JSON) +- [ ] 性能优化(GPU 加速、帧采样) +- [ ] Web/API 接口(可选) + +### 5.7 推荐的项目目录结构 + +``` +highway_vision/ +├── __init__.py +├── config.py # 全局配置(模型路径、标定点、阈值等) +├── detector.py # 车辆检测模块 +├── tracker.py # 多目标跟踪模块 +├── speed_estimator.py # 速度估算模块 +├── traffic_counter.py # 车流量统计模块 +├── headway_calculator.py # 车头间距计算模块 +├── view_transform.py # 透视变换模块 +├── visualizer.py # 可视化渲染模块 +├── pipeline.py # 主处理流水线 +└── utils.py # 工具函数 +``` + +--- + +## 六、参考资源 + +### 6.1 项目内示例 + +| 示例 | 路径 | 与高速公路系统的关联 | +|------|------|---------------------| +| 速度估算 | `examples/speed_estimation/` | 直接参考 — 车速显示功能 | +| 交通流分析 | `examples/traffic_analysis/` | 直接参考 — 车流量统计功能 | +| 目标跟踪 | `examples/tracking/` | 基础参考 — 多目标跟踪流程 | +| 区域计数 | `examples/count_people_in_zone/` | 基础参考 — PolygonZone 用法 | +| 热力图与轨迹 | `examples/heatmap_and_track/` | 扩展参考 — 密度可视化 | + +### 6.2 关键文件速查 + +| 需求 | 文件路径 | +|------|---------| +| Detections 数据类 | `src/supervision/detection/core.py` | +| LineZone / LineZoneAnnotator | `src/supervision/detection/line_zone.py` | +| PolygonZone / PolygonZoneAnnotator | `src/supervision/detection/tools/polygon_zone.py` | +| ByteTrack 跟踪器 | `src/supervision/tracker/byte_tracker/core.py` | +| 所有标注器 | `src/supervision/annotators/core.py` | +| 颜色系统 | `src/supervision/draw/color.py` | +| 绘图原语 | `src/supervision/draw/utils.py` | +| 视频工具 | `src/supervision/utils/video.py` | +| 几何原语 | `src/supervision/geometry/core.py` | +| CSV/JSON 导出 | `src/supervision/detection/tools/csv_sink.py`, `json_sink.py` | + +### 6.3 外部资源 + +- [supervision 官方文档](https://supervision.roboflow.com/latest/) +- [Ultralytics YOLO 文档](https://docs.ultralytics.com/) +- [ByteTrack 论文](https://arxiv.org/abs/2110.06864) +- [透视变换教程](https://docs.opencv.org/4.x/da/d6e/tutorial_py_geometric_transformations.html) diff --git a/examples/speed_estimation/calibrate_source.py b/examples/speed_estimation/calibrate_source.py new file mode 100644 index 0000000000..ff25e52966 --- /dev/null +++ b/examples/speed_estimation/calibrate_source.py @@ -0,0 +1,140 @@ +""" +Interactive SOURCE calibration tool for speed_estimation. + +Usage: + python calibrate_source.py --source_video_path + +Instructions: + 1. A window will open showing the first frame of the video. + 2. Click 4 points in order: top-left, top-right, bottom-right, bottom-left + to define the road region for perspective transformation. + 3. The points should form a quadrilateral on the road surface, + with the top edge being farther from the camera and the bottom edge closer. + 4. Press 'q' to quit, 'r' to reset points. + 5. After selecting 4 points, the result will be printed to console. + + Then update the SOURCE variable in ultralytics_example.py with the output. +""" + +import argparse + +import cv2 +import numpy as np + + +def calibrate(video_path: str) -> None: + """Open interactive window to pick 4 SOURCE points from video.""" + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + print(f"Error: Cannot open video {video_path}") + return + + ret, frame = cap.read() + cap.release() + if not ret: + print("Error: Cannot read first frame") + return + + h, w = frame.shape[:2] + print(f"Video resolution: {w}x{h}") + + points: list[tuple[int, int]] = [] + + def redraw() -> None: + """Redraw all points and lines on the frame.""" + display = frame.copy() + for i, pt in enumerate(points): + cv2.circle(display, pt, 8, (0, 255, 0), -1) + cv2.putText( + display, + f"P{i+1}", + (pt[0] + 10, pt[1] - 10), + cv2.FONT_HERSHEY_SIMPLEX, + 0.7, + (0, 255, 0), + 2, + ) + if len(points) > 1: + cv2.polylines( + display, + [np.array(points, dtype=np.int32)], + isClosed=False, + color=(0, 255, 0), + thickness=2, + ) + if len(points) == 4: + cv2.polylines( + display, + [np.array(points, dtype=np.int32)], + isClosed=True, + color=(0, 0, 255), + thickness=2, + ) + cv2.imshow("Calibrate SOURCE - Click 4 points", display) + + def mouse_callback(event: int, x: int, y: int, flags: int, param: None) -> None: + if event == cv2.EVENT_LBUTTONDOWN and len(points) < 4: + points.append((x, y)) + print(f" P{len(points)}: ({x}, {y})") + redraw() + + cv2.namedWindow("Calibrate SOURCE - Click 4 points", cv2.WINDOW_NORMAL) + cv2.resizeWindow("Calibrate SOURCE - Click 4 points", w, h) + cv2.setMouseCallback("Calibrate SOURCE - Click 4 points", mouse_callback) + cv2.imshow("Calibrate SOURCE - Click 4 points", frame) + + print("\nClick 4 points in order:") + print(" P1: top-left (far left of road)") + print(" P2: top-right (far right of road)") + print(" P3: bottom-right (near right of road)") + print(" P4: bottom-left (near left of road)") + print("\nPress 'r' to reset, 'q' to quit\n") + + while True: + key = cv2.waitKey(100) & 0xFF + if key == ord("q"): + break + elif key == ord("r"): + points.clear() + cv2.imshow("Calibrate SOURCE - Click 4 points", frame) + print("\nPoints reset. Click again.\nClick 4 points in order:") + print(" P1: top-left (far left of road)") + print(" P2: top-right (far right of road)") + print(" P3: bottom-right (near right of road)") + print(" P4: bottom-left (near left of road)") + + cv2.destroyAllWindows() + + if len(points) == 4: + source_array = np.array(points) + print("\n" + "=" * 60) + print("Selected points summary:") + for i, pt in enumerate(points): + print(f" P{i+1}: ({pt[0]}, {pt[1]})") + print("=" * 60) + print("Copy the following line to your ultralytics_example.py:") + print("=" * 60) + coords_str = ", ".join( + [f"[{pt[0]}, {pt[1]}]" for pt in points] + ) + print(f"SOURCE = np.array([{coords_str}])") + print("=" * 60) + + # Also suggest TARGET_WIDTH and TARGET_HEIGHT + # Calculate approximate real-world distance if user provides it + print("\nNote: Also adjust TARGET_WIDTH and TARGET_HEIGHT") + print(" TARGET_WIDTH = real-world width of the road section (meters)") + print(" TARGET_HEIGHT = real-world length of the road section (meters)") + else: + print(f"\nOnly {len(points)} points selected. Need exactly 4.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Calibrate SOURCE points for speed estimation" + ) + parser.add_argument( + "--source_video_path", required=True, help="Path to the source video" + ) + args = parser.parse_args() + calibrate(args.source_video_path) From 393d6f42473bbba4e8a6fb5df9dab3a10979a448 Mon Sep 17 00:00:00 2001 From: DavidWangW <540097031@qq.com> Date: Tue, 30 Jun 2026 15:27:43 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=EF=BC=9A=E5=8D=87=E7=BA=A7=20`pydeprecate`=20=E5=88=B0=200.9.0?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=AF=B9=20ppc64le=20=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uv.lock | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/uv.lock b/uv.lock index 297fb88992..af5fe02689 100644 --- a/uv.lock +++ b/uv.lock @@ -959,33 +959,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, @@ -3261,11 +3255,11 @@ wheels = [ [[package]] name = "pydeprecate" -version = "0.7.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/be/0f746a6cc399dc437d494c4240d25fa3151314dbd42cb1e89a5543fc1801/pydeprecate-0.7.0.tar.gz", hash = "sha256:925ee2e39f266bcd923c1abb76256da240e51ac04fc1ebb5490e32683770f9c4", size = 98242, upload-time = "2026-03-31T17:31:36.498Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/1e/b069ea704e42fefeb523835ba3d712347ff672743e5fa9428f4201525328/pydeprecate-0.9.0.tar.gz", hash = "sha256:ed49a36506f63a8d4ae84ffedff1e2879733d536c7a6aaafdf8bfcd39520059b", size = 151667, upload-time = "2026-06-05T19:51:17.43Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e6/42ece1260c287c5b376932d0d0fb776e5fe2a55ee58cf85d01ef97ca6c15/pydeprecate-0.7.0-py3-none-any.whl", hash = "sha256:b2baf35f82c06a22824029dd54ed763c7e8226f133893d15af4f9cf950a3b4d3", size = 63749, upload-time = "2026-03-31T17:31:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/47/87/a155f16c1c17adccaa2ed2256484fe1c6ad37b925e8e39695ed8860bc0b5/pydeprecate-0.9.0-py3-none-any.whl", hash = "sha256:c9501dae1a2567a3c7f7b04221ad5a97d1ad972a02ddb729a04299b435fedd7c", size = 102725, upload-time = "2026-06-05T19:51:16.235Z" }, ] [[package]] @@ -4122,7 +4116,7 @@ requires-dist = [ { name = "opencv-python", specifier = ">=4.5.5.64" }, { name = "pandas", marker = "extra == 'metrics'", specifier = ">=2" }, { name = "pillow", specifier = ">=9.4" }, - { name = "pydeprecate", specifier = ">=0.7,<0.9" }, + { name = "pydeprecate", specifier = ">=0.9,<0.10" }, { name = "pyyaml", specifier = ">=5.3" }, { name = "requests", specifier = ">=2.26" }, { name = "scipy", specifier = ">=1.10" }, From 81a242fca2cd33f492c7a594bad2e93b64f8ac26 Mon Sep 17 00:00:00 2001 From: cwj Date: Fri, 3 Jul 2026 11:10:38 +0800 Subject: [PATCH 3/3] feat(examples): add supervision-service web app with FastAPI and Vue Add a full-stack example for video tracking and speed estimation, including async job processing, SQLite history, and a Vue 3 dashboard. Co-authored-by: Cursor --- .gitignore | 3 + examples/README.md | 1 + examples/supervision-service/.gitignore | 11 + examples/supervision-service/README.md | 185 + examples/supervision-service/app/__init__.py | 1 + examples/supervision-service/app/config.py | 18 + .../supervision-service/app/db/__init__.py | 1 + .../supervision-service/app/db/database.py | 83 + .../supervision-service/app/db/repository.py | 273 + examples/supervision-service/app/main.py | 62 + .../app/routers/__init__.py | 1 + .../supervision-service/app/routers/health.py | 11 + .../app/routers/records.py | 102 + .../supervision-service/app/routers/speed.py | 85 + .../supervision-service/app/routers/videos.py | 45 + .../app/schemas/__init__.py | 3 + .../supervision-service/app/schemas/common.py | 15 + .../app/schemas/records.py | 55 + .../app/services/__init__.py | 3 + .../app/services/job_runner.py | 73 + .../app/services/speed_processor.py | 207 + .../app/services/upload_service.py | 40 + .../app/services/video_encoding.py | 47 + .../app/services/video_processor.py | 74 + examples/supervision-service/pyproject.toml | 22 + .../supervision-service/webapp/.editorconfig | 8 + .../supervision-service/webapp/.gitattributes | 1 + .../supervision-service/webapp/.gitignore | 39 + .../supervision-service/webapp/.oxlintrc.json | 10 + .../webapp/.prettierrc.json | 6 + examples/supervision-service/webapp/README.md | 48 + examples/supervision-service/webapp/env.d.ts | 1 + .../webapp/eslint.config.ts | 26 + .../supervision-service/webapp/index.html | 19 + .../webapp/package-lock.json | 5062 +++++++++++++++++ .../supervision-service/webapp/package.json | 44 + .../webapp/public/favicon.ico | Bin 0 -> 4286 bytes .../supervision-service/webapp/src/App.vue | 3 + .../webapp/src/api/jobs.ts | 51 + .../webapp/src/api/records.ts | 59 + .../webapp/src/api/speed.ts | 75 + .../webapp/src/api/video.ts | 73 + .../webapp/src/assets/base.css | 86 + .../webapp/src/assets/global.css | 63 + .../webapp/src/assets/logo.svg | 1 + .../webapp/src/assets/main.css | 35 + .../webapp/src/components/ApiStatusBadge.vue | 51 + .../src/components/CalibrationCanvas.vue | 268 + .../webapp/src/components/HelloWorld.vue | 41 + .../webapp/src/components/ProcessingState.vue | 101 + .../webapp/src/components/ResultPanel.vue | 66 + .../webapp/src/components/TheWelcome.vue | 95 + .../webapp/src/components/VideoDropzone.vue | 80 + .../webapp/src/components/WelcomeItem.vue | 87 + .../src/components/icons/IconCommunity.vue | 7 + .../components/icons/IconDocumentation.vue | 7 + .../src/components/icons/IconEcosystem.vue | 7 + .../src/components/icons/IconSupport.vue | 7 + .../src/components/icons/IconTooling.vue | 19 + .../webapp/src/composables/useApiHealth.ts | 31 + .../webapp/src/composables/useVideoFile.ts | 42 + .../webapp/src/layouts/AppLayout.vue | 180 + .../supervision-service/webapp/src/main.ts | 8 + .../webapp/src/router/index.ts | 25 + .../webapp/src/views/DashboardView.vue | 188 + .../webapp/src/views/HistoryView.vue | 469 ++ .../webapp/src/views/SpeedView.vue | 395 ++ .../webapp/src/views/TrackView.vue | 267 + .../webapp/tsconfig.app.json | 18 + .../supervision-service/webapp/tsconfig.json | 11 + .../webapp/tsconfig.node.json | 27 + .../supervision-service/webapp/vite.config.ts | 23 + 72 files changed, 9651 insertions(+) create mode 100644 examples/supervision-service/.gitignore create mode 100644 examples/supervision-service/README.md create mode 100644 examples/supervision-service/app/__init__.py create mode 100644 examples/supervision-service/app/config.py create mode 100644 examples/supervision-service/app/db/__init__.py create mode 100644 examples/supervision-service/app/db/database.py create mode 100644 examples/supervision-service/app/db/repository.py create mode 100644 examples/supervision-service/app/main.py create mode 100644 examples/supervision-service/app/routers/__init__.py create mode 100644 examples/supervision-service/app/routers/health.py create mode 100644 examples/supervision-service/app/routers/records.py create mode 100644 examples/supervision-service/app/routers/speed.py create mode 100644 examples/supervision-service/app/routers/videos.py create mode 100644 examples/supervision-service/app/schemas/__init__.py create mode 100644 examples/supervision-service/app/schemas/common.py create mode 100644 examples/supervision-service/app/schemas/records.py create mode 100644 examples/supervision-service/app/services/__init__.py create mode 100644 examples/supervision-service/app/services/job_runner.py create mode 100644 examples/supervision-service/app/services/speed_processor.py create mode 100644 examples/supervision-service/app/services/upload_service.py create mode 100644 examples/supervision-service/app/services/video_encoding.py create mode 100644 examples/supervision-service/app/services/video_processor.py create mode 100644 examples/supervision-service/pyproject.toml create mode 100644 examples/supervision-service/webapp/.editorconfig create mode 100644 examples/supervision-service/webapp/.gitattributes create mode 100644 examples/supervision-service/webapp/.gitignore create mode 100644 examples/supervision-service/webapp/.oxlintrc.json create mode 100644 examples/supervision-service/webapp/.prettierrc.json create mode 100644 examples/supervision-service/webapp/README.md create mode 100644 examples/supervision-service/webapp/env.d.ts create mode 100644 examples/supervision-service/webapp/eslint.config.ts create mode 100644 examples/supervision-service/webapp/index.html create mode 100644 examples/supervision-service/webapp/package-lock.json create mode 100644 examples/supervision-service/webapp/package.json create mode 100644 examples/supervision-service/webapp/public/favicon.ico create mode 100644 examples/supervision-service/webapp/src/App.vue create mode 100644 examples/supervision-service/webapp/src/api/jobs.ts create mode 100644 examples/supervision-service/webapp/src/api/records.ts create mode 100644 examples/supervision-service/webapp/src/api/speed.ts create mode 100644 examples/supervision-service/webapp/src/api/video.ts create mode 100644 examples/supervision-service/webapp/src/assets/base.css create mode 100644 examples/supervision-service/webapp/src/assets/global.css create mode 100644 examples/supervision-service/webapp/src/assets/logo.svg create mode 100644 examples/supervision-service/webapp/src/assets/main.css create mode 100644 examples/supervision-service/webapp/src/components/ApiStatusBadge.vue create mode 100644 examples/supervision-service/webapp/src/components/CalibrationCanvas.vue create mode 100644 examples/supervision-service/webapp/src/components/HelloWorld.vue create mode 100644 examples/supervision-service/webapp/src/components/ProcessingState.vue create mode 100644 examples/supervision-service/webapp/src/components/ResultPanel.vue create mode 100644 examples/supervision-service/webapp/src/components/TheWelcome.vue create mode 100644 examples/supervision-service/webapp/src/components/VideoDropzone.vue create mode 100644 examples/supervision-service/webapp/src/components/WelcomeItem.vue create mode 100644 examples/supervision-service/webapp/src/components/icons/IconCommunity.vue create mode 100644 examples/supervision-service/webapp/src/components/icons/IconDocumentation.vue create mode 100644 examples/supervision-service/webapp/src/components/icons/IconEcosystem.vue create mode 100644 examples/supervision-service/webapp/src/components/icons/IconSupport.vue create mode 100644 examples/supervision-service/webapp/src/components/icons/IconTooling.vue create mode 100644 examples/supervision-service/webapp/src/composables/useApiHealth.ts create mode 100644 examples/supervision-service/webapp/src/composables/useVideoFile.ts create mode 100644 examples/supervision-service/webapp/src/layouts/AppLayout.vue create mode 100644 examples/supervision-service/webapp/src/main.ts create mode 100644 examples/supervision-service/webapp/src/router/index.ts create mode 100644 examples/supervision-service/webapp/src/views/DashboardView.vue create mode 100644 examples/supervision-service/webapp/src/views/HistoryView.vue create mode 100644 examples/supervision-service/webapp/src/views/SpeedView.vue create mode 100644 examples/supervision-service/webapp/src/views/TrackView.vue create mode 100644 examples/supervision-service/webapp/tsconfig.app.json create mode 100644 examples/supervision-service/webapp/tsconfig.json create mode 100644 examples/supervision-service/webapp/tsconfig.node.json create mode 100644 examples/supervision-service/webapp/vite.config.ts diff --git a/.gitignore b/.gitignore index 5f9910ba79..d72e4ff0cc 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ _resolutions/ _reviews/ tasks/ *.local.md + +*.mp4 +*.pt \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index d29b485193..4e2d0c3499 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,3 +10,4 @@ For more information and examples, visit our [documentation](https://supervision - [speed estimation](./speed_estimation) by [@SkalskiP](https://github.com/SkalskiP) - [time in zone](./time_in_zone) by [@SkalskiP](https://github.com/SkalskiP) - [heatmap and track](./heatmap_and_track/) by [@HinePo](https://github.com/HinePo) +- [App](./App) — FastAPI video analysis API diff --git a/examples/supervision-service/.gitignore b/examples/supervision-service/.gitignore new file mode 100644 index 0000000000..ea1614b0c7 --- /dev/null +++ b/examples/supervision-service/.gitignore @@ -0,0 +1,11 @@ +uploads/ +outputs/ +models/ +data/ +__pycache__/ +*.pyc +.venv/ +.ruff_cache/ +*.pt +webapp/node_modules/ +webapp/dist/ diff --git a/examples/supervision-service/README.md b/examples/supervision-service/README.md new file mode 100644 index 0000000000..e60d5b488b --- /dev/null +++ b/examples/supervision-service/README.md @@ -0,0 +1,185 @@ +# Supervision Service + +基于 [supervision](https://github.com/roboflow/supervision) 的视频分析 Web 服务,包含 **FastAPI 后端** 与 **Vue 3 前端**。 + +支持在浏览器中上传视频、在线处理、预览并下载结果。 + +## 功能 + +| 模块 | 路径 | 说明 | +|------|------|------| +| 概览 | `/` | 控制台首页 | +| 检测跟踪 | `/track` | YOLO 检测 + ByteTrack 跟踪 | +| 速度估算 | `/speed` | 路面四点标定 + 透视变换测速 | + +## 环境要求 + +- **Python** >= 3.10 +- **Node.js** >= 22.18(前端开发/构建) +- **NVIDIA GPU**(可选,建议;CPU 也可运行但较慢) +- **Conda** 或 **uv**(推荐用于 Python 环境管理) + +## 安装 + +### 1. Python 后端依赖 + +```powershell +conda create -n supervision python=3.10 -y +conda activate supervision + +cd examples/supervision-service +pip install -e . +# 或手动安装: +# pip install "fastapi[standard]>=0.115.0" supervision ultralytics "jsonargparse[signatures]" imageio-ffmpeg +``` + +> **RTX 50 系列(5070 Ti 等)**:需安装 CUDA 12.8 版 PyTorch,否则无法使用 GPU: +> +> ```powershell +> pip uninstall torch torchvision torchaudio -y +> pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu128 --no-cache-dir +> ``` + +### 2. 前端依赖 + +```powershell +cd examples/supervision-service/webapp +npm install +``` + +## 启动方式 + +### 方式一:开发模式(推荐) + +前后端分开运行,前端支持热更新。 + +**终端 1 — 启动 API** + +```powershell +cd examples/supervision-service +fastapi dev app/main.py +``` + +API 地址:http://127.0.0.1:8000 +API 文档:http://127.0.0.1:8000/docs + +**终端 2 — 启动 Web 前端** + +```powershell +cd examples/supervision-service/webapp +npm run dev +``` + +Web 控制台:http://127.0.0.1:5173 + +> 开发模式下,Vite 会将 `/api`、`/health`、`/docs` 代理到 `8000` 端口,无需额外配置 CORS。 + +### 方式二:生产模式(单端口) + +先构建前端,再由 FastAPI 托管静态页面与 API。 + +```powershell +# 构建前端 +cd examples/supervision-service/webapp +npm run build + +# 启动服务 +cd .. +conda activate supervision +fastapi run app/main.py +``` + +访问:http://127.0.0.1:8000(Web UI + API + `/docs` 同一端口) + +## 项目结构 + +``` +supervision-service/ +├── app/ # FastAPI 后端 +│ ├── main.py # 应用入口 +│ ├── routers/ # API 路由 +│ └── services/ # 视频处理逻辑 +├── webapp/ # Vue 3 前端 +│ ├── src/ +│ └── dist/ # 构建产物(npm run build 后生成) +├── uploads/ # 上传原始视频(持久保存) +├── outputs/ # 处理结果视频(自动创建) +├── data/ # SQLite 数据库(supervision.db) +└── models/ # YOLO 权重(首次运行自动下载) +``` + +## API 接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/health` | 健康检查 | +| GET | `/api/info` | API 元信息 | +| POST | `/api/v1/videos/track` | 检测 + 跟踪,返回 MP4 | +| POST | `/api/v1/videos/speed-estimate` | 速度估算,返回 MP4 | +| GET | `/api/v1/records/uploads` | 上传记录列表 | +| GET | `/api/v1/records/uploads/{id}` | 单条上传记录 | +| GET | `/api/v1/records/uploads/{id}/file` | 下载原始上传文件 | +| GET | `/api/v1/records/jobs` | 解析任务记录列表 | +| GET | `/api/v1/records/jobs/{id}` | 单条解析任务 | +| GET | `/api/v1/records/jobs/{id}/file` | 下载解析结果视频 | + +## 数据存储 + +上传的视频保存在 `uploads/` 目录,元数据与解析记录写入 SQLite: + +- 数据库文件:`data/supervision.db` +- 表 `uploads`:原始文件名、存储路径、大小、上传时间 +- 表 `processing_jobs`:任务类型(`track` / `speed`)、状态、参数、输出路径、错误信息 + +服务启动时会自动建表。处理接口响应头中包含: + +- `X-Upload-Id`:上传记录 ID +- `X-Job-Id`:解析任务 ID + +## 使用说明 + +### 检测跟踪 + +1. 打开 `/track` +2. 上传视频 +3. 调整置信度 / IOU 阈值 +4. 点击「开始处理」 +5. 在线预览或下载结果 + +### 速度估算 + +1. 打开 `/speed` +2. 上传视频,在首帧上依次标记四个路面角点(远端左 → 远端右 → 近端右 → 近端左) +3. 填写路面真实宽度、长度(米) +4. 点击「开始测速」 + +> 标定精度直接影响 km/h 读数是否准确,详见 [`examples/speed_estimation`](../speed_estimation)。 + +## 常见问题 + +**前端显示 API 离线** +确认后端已启动:`fastapi dev app/main.py`,且终端无报错。 + +**处理很慢** +检查是否在使用 GPU:`python -c "import torch; print(torch.cuda.is_available())"` +若为 `False`,需安装带 CUDA 的 PyTorch(见上方安装说明)。 + +**浏览器无法播放结果视频** +后端会在处理完成后自动将视频转码为 H.264。若仍无法播放,可点击「下载视频」用本地播放器查看。修改代码后需**重启 FastAPI** 并**重新处理视频**。 + +**首次运行额外下载** +- 跟踪模块:`models/yolov8s.pt` +- 测速模块:`models/yolo11s.pt` + +## 开发命令 + +```powershell +# 后端 +fastapi dev app/main.py + +# 前端 +cd webapp +npm run dev # 开发 +npm run build # 构建 +npm run type-check # 类型检查 +``` diff --git a/examples/supervision-service/app/__init__.py b/examples/supervision-service/app/__init__.py new file mode 100644 index 0000000000..647fd0b29d --- /dev/null +++ b/examples/supervision-service/app/__init__.py @@ -0,0 +1 @@ +"""Supervision video analysis API.""" diff --git a/examples/supervision-service/app/config.py b/examples/supervision-service/app/config.py new file mode 100644 index 0000000000..dc4dca620d --- /dev/null +++ b/examples/supervision-service/app/config.py @@ -0,0 +1,18 @@ +from pathlib import Path + +APP_DIR = Path(__file__).resolve().parent +BASE_DIR = APP_DIR.parent + +UPLOAD_DIR = BASE_DIR / "uploads" +OUTPUT_DIR = BASE_DIR / "outputs" +MODELS_DIR = BASE_DIR / "models" +DATA_DIR = BASE_DIR / "data" +DATABASE_PATH = DATA_DIR / "supervision.db" + +DEFAULT_WEIGHTS = MODELS_DIR / "yolov8s.pt" +DEFAULT_SPEED_WEIGHTS = MODELS_DIR / "yolo11s.pt" +DEFAULT_CONFIDENCE = 0.3 +DEFAULT_IOU = 0.7 + +for directory in (UPLOAD_DIR, OUTPUT_DIR, MODELS_DIR, DATA_DIR): + directory.mkdir(parents=True, exist_ok=True) diff --git a/examples/supervision-service/app/db/__init__.py b/examples/supervision-service/app/db/__init__.py new file mode 100644 index 0000000000..6239d11968 --- /dev/null +++ b/examples/supervision-service/app/db/__init__.py @@ -0,0 +1 @@ +"""SQLite persistence layer.""" diff --git a/examples/supervision-service/app/db/database.py b/examples/supervision-service/app/db/database.py new file mode 100644 index 0000000000..19115f687b --- /dev/null +++ b/examples/supervision-service/app/db/database.py @@ -0,0 +1,83 @@ +import sqlite3 +from collections.abc import Iterator +from contextlib import contextmanager +from datetime import datetime, timezone + +from app.config import DATABASE_PATH + + +def _now_iso() -> str: + return datetime.now(timezone.utc).replace(tzinfo=None).isoformat(timespec="seconds") + + +def _migrate(connection: sqlite3.Connection) -> None: + columns = { + row[1] + for row in connection.execute("PRAGMA table_info(processing_jobs)").fetchall() + } + if "progress" not in columns: + connection.execute( + "ALTER TABLE processing_jobs ADD COLUMN progress INTEGER NOT NULL DEFAULT 0" + ) + if "current_frame" not in columns: + connection.execute( + "ALTER TABLE processing_jobs ADD COLUMN current_frame INTEGER NOT NULL DEFAULT 0" + ) + if "total_frames" not in columns: + connection.execute( + "ALTER TABLE processing_jobs ADD COLUMN total_frames INTEGER NOT NULL DEFAULT 0" + ) + + +def init_db() -> None: + """Create database tables if they do not exist.""" + with get_connection() as connection: + connection.executescript( + """ + CREATE TABLE IF NOT EXISTS uploads ( + id TEXT PRIMARY KEY, + original_filename TEXT NOT NULL, + stored_filename TEXT NOT NULL, + file_path TEXT NOT NULL, + file_size INTEGER NOT NULL, + content_type TEXT, + created_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS processing_jobs ( + id TEXT PRIMARY KEY, + upload_id TEXT NOT NULL, + job_type TEXT NOT NULL, + status TEXT NOT NULL, + output_path TEXT, + parameters TEXT NOT NULL, + error_message TEXT, + progress INTEGER NOT NULL DEFAULT 0, + current_frame INTEGER NOT NULL DEFAULT 0, + total_frames INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + completed_at TEXT, + FOREIGN KEY (upload_id) REFERENCES uploads(id) + ); + + CREATE INDEX IF NOT EXISTS idx_uploads_created_at + ON uploads(created_at DESC); + CREATE INDEX IF NOT EXISTS idx_jobs_upload_id + ON processing_jobs(upload_id); + CREATE INDEX IF NOT EXISTS idx_jobs_created_at + ON processing_jobs(created_at DESC); + """ + ) + _migrate(connection) + connection.commit() + + +@contextmanager +def get_connection() -> Iterator[sqlite3.Connection]: + """Yield a SQLite connection with row factory enabled.""" + connection = sqlite3.connect(DATABASE_PATH) + connection.row_factory = sqlite3.Row + try: + yield connection + finally: + connection.close() diff --git a/examples/supervision-service/app/db/repository.py b/examples/supervision-service/app/db/repository.py new file mode 100644 index 0000000000..aeec97d65f --- /dev/null +++ b/examples/supervision-service/app/db/repository.py @@ -0,0 +1,273 @@ +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any +from uuid import uuid4 + +from app.db.database import _now_iso, get_connection + + +@dataclass +class UploadRecord: + id: str + original_filename: str + stored_filename: str + file_path: str + file_size: int + content_type: str | None + created_at: str + + +@dataclass +class ProcessingJobRecord: + id: str + upload_id: str + job_type: str + status: str + output_path: str | None + parameters: dict[str, Any] + error_message: str | None + progress: int + current_frame: int + total_frames: int + created_at: str + completed_at: str | None + original_filename: str | None = None + + +def create_upload( + *, + original_filename: str, + stored_filename: str, + file_path: Path, + file_size: int, + content_type: str | None, +) -> UploadRecord: + """Insert an upload record and return it.""" + upload_id = uuid4().hex + created_at = _now_iso() + record = UploadRecord( + id=upload_id, + original_filename=original_filename, + stored_filename=stored_filename, + file_path=str(file_path), + file_size=file_size, + content_type=content_type, + created_at=created_at, + ) + with get_connection() as connection: + connection.execute( + """ + INSERT INTO uploads ( + id, original_filename, stored_filename, file_path, + file_size, content_type, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + record.id, + record.original_filename, + record.stored_filename, + record.file_path, + record.file_size, + record.content_type, + record.created_at, + ), + ) + connection.commit() + return record + + +def create_processing_job( + *, + upload_id: str, + job_type: str, + parameters: dict[str, Any], +) -> ProcessingJobRecord: + """Create a pending processing job.""" + job_id = uuid4().hex + created_at = _now_iso() + with get_connection() as connection: + connection.execute( + """ + INSERT INTO processing_jobs ( + id, upload_id, job_type, status, output_path, + parameters, error_message, progress, current_frame, + total_frames, created_at, completed_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + job_id, + upload_id, + job_type, + "processing", + None, + json.dumps(parameters, ensure_ascii=False), + None, + 0, + 0, + 0, + created_at, + None, + ), + ) + connection.commit() + return ProcessingJobRecord( + id=job_id, + upload_id=upload_id, + job_type=job_type, + status="processing", + output_path=None, + parameters=parameters, + error_message=None, + progress=0, + current_frame=0, + total_frames=0, + created_at=created_at, + completed_at=None, + ) + + +def update_job_progress( + job_id: str, + *, + progress: int, + current_frame: int, + total_frames: int, +) -> None: + """Update frame progress for a running job.""" + with get_connection() as connection: + connection.execute( + """ + UPDATE processing_jobs + SET progress = ?, current_frame = ?, total_frames = ? + WHERE id = ? + """, + ( + max(0, min(progress, 100)), + current_frame, + total_frames, + job_id, + ), + ) + connection.commit() + + +def mark_job_completed(job_id: str, output_path: Path) -> None: + """Mark a job as completed with its output file path.""" + with get_connection() as connection: + connection.execute( + """ + UPDATE processing_jobs + SET status = ?, output_path = ?, completed_at = ?, + error_message = NULL, progress = 100 + WHERE id = ? + """, + ("completed", str(output_path), _now_iso(), job_id), + ) + connection.commit() + + +def mark_job_failed(job_id: str, error_message: str) -> None: + """Mark a job as failed with an error message.""" + with get_connection() as connection: + connection.execute( + """ + UPDATE processing_jobs + SET status = ?, error_message = ?, completed_at = ? + WHERE id = ? + """, + ("failed", error_message, _now_iso(), job_id), + ) + connection.commit() + + +def list_uploads(limit: int = 50) -> list[UploadRecord]: + """Return recent upload records.""" + with get_connection() as connection: + rows = connection.execute( + """ + SELECT id, original_filename, stored_filename, file_path, + file_size, content_type, created_at + FROM uploads + ORDER BY created_at DESC + LIMIT ? + """, + (limit,), + ).fetchall() + return [UploadRecord(**dict(row)) for row in rows] + + +def get_upload(upload_id: str) -> UploadRecord | None: + """Return a single upload record by id.""" + with get_connection() as connection: + row = connection.execute( + """ + SELECT id, original_filename, stored_filename, file_path, + file_size, content_type, created_at + FROM uploads + WHERE id = ? + """, + (upload_id,), + ).fetchone() + if row is None: + return None + return UploadRecord(**dict(row)) + + +def list_processing_jobs(limit: int = 50) -> list[ProcessingJobRecord]: + """Return recent processing jobs with original filename.""" + with get_connection() as connection: + rows = connection.execute( + """ + SELECT + j.id, j.upload_id, j.job_type, j.status, j.output_path, + j.parameters, j.error_message, j.progress, j.current_frame, + j.total_frames, j.created_at, j.completed_at, + u.original_filename + FROM processing_jobs j + LEFT JOIN uploads u ON u.id = j.upload_id + ORDER BY j.created_at DESC + LIMIT ? + """, + (limit,), + ).fetchall() + return [_row_to_job(row) for row in rows] + + +def get_processing_job(job_id: str) -> ProcessingJobRecord | None: + """Return a single processing job by id.""" + with get_connection() as connection: + row = connection.execute( + """ + SELECT + j.id, j.upload_id, j.job_type, j.status, j.output_path, + j.parameters, j.error_message, j.progress, j.current_frame, + j.total_frames, j.created_at, j.completed_at, + u.original_filename + FROM processing_jobs j + LEFT JOIN uploads u ON u.id = j.upload_id + WHERE j.id = ? + """, + (job_id,), + ).fetchone() + if row is None: + return None + return _row_to_job(row) + + +def _row_to_job(row: Any) -> ProcessingJobRecord: + return ProcessingJobRecord( + id=row["id"], + upload_id=row["upload_id"], + job_type=row["job_type"], + status=row["status"], + output_path=row["output_path"], + parameters=json.loads(row["parameters"]), + error_message=row["error_message"], + progress=row["progress"], + current_frame=row["current_frame"], + total_frames=row["total_frames"], + created_at=row["created_at"], + completed_at=row["completed_at"], + original_filename=row["original_filename"], + ) diff --git a/examples/supervision-service/app/main.py b/examples/supervision-service/app/main.py new file mode 100644 index 0000000000..8b21af8d92 --- /dev/null +++ b/examples/supervision-service/app/main.py @@ -0,0 +1,62 @@ +from contextlib import asynccontextmanager +from pathlib import Path + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles + +from app.config import BASE_DIR +from app.db.database import init_db +from app.routers import health, records, speed, videos +from app.schemas.common import ApiInfo + + +@asynccontextmanager +async def lifespan(_: FastAPI): + init_db() + yield + + +app = FastAPI( + title="Supervision Video API", + description="Upload videos for detection and tracking powered by supervision.", + version="0.1.0", + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://127.0.0.1:5173", + "http://localhost:5173", + "http://127.0.0.1:8000", + "http://localhost:8000", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(health.router) +app.include_router(videos.router) +app.include_router(speed.router) +app.include_router(records.router) + + +@app.get("/api/info", response_model=ApiInfo) +def api_info() -> ApiInfo: + """Return API metadata and documentation links.""" + return ApiInfo( + message="Supervision Video API", + docs="/docs", + health="/health", + ) + + +WEBAPP_DIST = BASE_DIR / "webapp" / "dist" +if WEBAPP_DIST.is_dir(): + app.mount( + "/", + StaticFiles(directory=str(WEBAPP_DIST), html=True), + name="frontend", + ) diff --git a/examples/supervision-service/app/routers/__init__.py b/examples/supervision-service/app/routers/__init__.py new file mode 100644 index 0000000000..fb0a2f8082 --- /dev/null +++ b/examples/supervision-service/app/routers/__init__.py @@ -0,0 +1 @@ +"""API route modules.""" diff --git a/examples/supervision-service/app/routers/health.py b/examples/supervision-service/app/routers/health.py new file mode 100644 index 0000000000..283750a5a7 --- /dev/null +++ b/examples/supervision-service/app/routers/health.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from app.schemas.common import HealthResponse + +router = APIRouter(tags=["health"]) + + +@router.get("/health", response_model=HealthResponse) +def health_check() -> HealthResponse: + """Return service health status.""" + return HealthResponse(status="ok") diff --git a/examples/supervision-service/app/routers/records.py b/examples/supervision-service/app/routers/records.py new file mode 100644 index 0000000000..0b40301e87 --- /dev/null +++ b/examples/supervision-service/app/routers/records.py @@ -0,0 +1,102 @@ +from pathlib import Path +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query +from fastapi.responses import FileResponse + +from app.db.repository import ( + get_processing_job, + get_upload, + list_processing_jobs, + list_uploads, +) +from app.schemas.records import ( + ProcessingJobListResponse, + ProcessingJobResponse, + UploadListResponse, + UploadResponse, +) + +router = APIRouter(prefix="/api/v1/records", tags=["records"]) + + +def _to_upload_response(record: object) -> UploadResponse: + return UploadResponse.model_validate(record.__dict__) + + +def _to_job_response(record: object) -> ProcessingJobResponse: + return ProcessingJobResponse.model_validate(record.__dict__) + + +@router.get("/uploads", response_model=UploadListResponse) +def get_upload_records( + limit: Annotated[int, Query(ge=1, le=200)] = 50, +) -> UploadListResponse: + """List stored upload records.""" + items = [_to_upload_response(record) for record in list_uploads(limit=limit)] + return UploadListResponse(items=items, total=len(items)) + + +@router.get("/uploads/{upload_id}", response_model=UploadResponse) +def get_upload_record(upload_id: str) -> UploadResponse: + """Return a single upload record.""" + record = get_upload(upload_id) + if record is None: + raise HTTPException(status_code=404, detail="Upload record not found.") + return _to_upload_response(record) + + +@router.get("/uploads/{upload_id}/file") +def download_upload_file(upload_id: str) -> FileResponse: + """Download the original uploaded video file.""" + record = get_upload(upload_id) + if record is None: + raise HTTPException(status_code=404, detail="Upload record not found.") + + file_path = Path(record.file_path) + if not file_path.is_file(): + raise HTTPException(status_code=404, detail="Upload file not found on disk.") + + return FileResponse( + path=file_path, + media_type=record.content_type or "video/mp4", + filename=record.original_filename, + ) + + +@router.get("/jobs", response_model=ProcessingJobListResponse) +def get_processing_jobs( + limit: Annotated[int, Query(ge=1, le=200)] = 50, +) -> ProcessingJobListResponse: + """List video processing job records.""" + items = [_to_job_response(record) for record in list_processing_jobs(limit=limit)] + return ProcessingJobListResponse(items=items, total=len(items)) + + +@router.get("/jobs/{job_id}", response_model=ProcessingJobResponse) +def get_processing_job_record(job_id: str) -> ProcessingJobResponse: + """Return a single processing job record.""" + record = get_processing_job(job_id) + if record is None: + raise HTTPException(status_code=404, detail="Processing job not found.") + return _to_job_response(record) + + +@router.get("/jobs/{job_id}/file") +def download_job_result(job_id: str) -> FileResponse: + """Download the processed output video for a job.""" + record = get_processing_job(job_id) + if record is None: + raise HTTPException(status_code=404, detail="Processing job not found.") + if record.status != "completed" or not record.output_path: + raise HTTPException(status_code=404, detail="Processed file is not available.") + + file_path = Path(record.output_path) + if not file_path.is_file(): + raise HTTPException(status_code=404, detail="Processed file not found on disk.") + + return FileResponse( + path=file_path, + media_type="video/mp4", + filename=file_path.name, + ) diff --git a/examples/supervision-service/app/routers/speed.py b/examples/supervision-service/app/routers/speed.py new file mode 100644 index 0000000000..a8b55ed23e --- /dev/null +++ b/examples/supervision-service/app/routers/speed.py @@ -0,0 +1,85 @@ +import json +from pathlib import Path +from typing import Annotated + +from fastapi import APIRouter, BackgroundTasks, File, Form, HTTPException, UploadFile +from pydantic import BaseModel, Field, ValidationError + +from app.config import DEFAULT_CONFIDENCE, DEFAULT_IOU +from app.db.repository import create_processing_job +from app.schemas.records import JobCreatedResponse +from app.services.job_runner import run_speed_job +from app.services.upload_service import save_upload_file + +router = APIRouter(prefix="/api/v1/videos", tags=["speed"]) + + +class SourcePoint(BaseModel): + """A single calibration point in video pixel coordinates.""" + + x: int = Field(ge=0) + y: int = Field(ge=0) + + +class SourcePointsPayload(BaseModel): + """Four-point road surface calibration payload.""" + + points: list[SourcePoint] = Field(min_length=4, max_length=4) + + +def _parse_source_points(raw_points: str) -> list[list[int]]: + try: + payload = SourcePointsPayload.model_validate({"points": json.loads(raw_points)}) + except (json.JSONDecodeError, ValidationError, TypeError) as exc: + raise HTTPException( + status_code=400, + detail="source_points must be JSON with exactly four {x, y} objects.", + ) from exc + + return [[point.x, point.y] for point in payload.points] + + +@router.post("/speed-estimate", response_model=JobCreatedResponse, status_code=202) +def speed_estimate_video( + background_tasks: BackgroundTasks, + file: Annotated[UploadFile, File(description="Input video file")], + source_points: Annotated[ + str, + Form(description='JSON array of four points, e.g. [{"x":1,"y":2}, ...]'), + ], + target_width: Annotated[float, Form(gt=0, description="Road width in meters")], + target_height: Annotated[float, Form(gt=0, description="Road length in meters")], + confidence_threshold: Annotated[float, Form(ge=0.0, le=1.0)] = DEFAULT_CONFIDENCE, + iou_threshold: Annotated[float, Form(ge=0.0, le=1.0)] = DEFAULT_IOU, +) -> JobCreatedResponse: + """Submit a speed estimation job and process it in the background.""" + try: + upload_record = save_upload_file(file) + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + parsed_points = _parse_source_points(source_points) + job = create_processing_job( + upload_id=upload_record.id, + job_type="speed", + parameters={ + "source_points": parsed_points, + "target_width": target_width, + "target_height": target_height, + "confidence_threshold": confidence_threshold, + "iou_threshold": iou_threshold, + }, + ) + + background_tasks.add_task( + run_speed_job, + job.id, + Path(upload_record.file_path), + parsed_points, + target_width, + target_height, + confidence_threshold, + iou_threshold, + ) + + return JobCreatedResponse(job_id=job.id, upload_id=upload_record.id) diff --git a/examples/supervision-service/app/routers/videos.py b/examples/supervision-service/app/routers/videos.py new file mode 100644 index 0000000000..691053eba8 --- /dev/null +++ b/examples/supervision-service/app/routers/videos.py @@ -0,0 +1,45 @@ +from pathlib import Path +from typing import Annotated + +from fastapi import APIRouter, BackgroundTasks, File, Form, HTTPException, UploadFile + +from app.config import DEFAULT_CONFIDENCE, DEFAULT_IOU +from app.db.repository import create_processing_job +from app.schemas.records import JobCreatedResponse +from app.services.job_runner import run_track_job +from app.services.upload_service import save_upload_file + +router = APIRouter(prefix="/api/v1/videos", tags=["videos"]) + + +@router.post("/track", response_model=JobCreatedResponse, status_code=202) +def track_uploaded_video( + background_tasks: BackgroundTasks, + file: Annotated[UploadFile, File(description="Input video file (mp4, mov, avi, etc.)")], + confidence_threshold: Annotated[float, Form(ge=0.0, le=1.0)] = DEFAULT_CONFIDENCE, + iou_threshold: Annotated[float, Form(ge=0.0, le=1.0)] = DEFAULT_IOU, +) -> JobCreatedResponse: + """Submit a tracking job and process it in the background.""" + try: + upload_record = save_upload_file(file) + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + job = create_processing_job( + upload_id=upload_record.id, + job_type="track", + parameters={ + "confidence_threshold": confidence_threshold, + "iou_threshold": iou_threshold, + }, + ) + + background_tasks.add_task( + run_track_job, + job.id, + Path(upload_record.file_path), + confidence_threshold, + iou_threshold, + ) + + return JobCreatedResponse(job_id=job.id, upload_id=upload_record.id) diff --git a/examples/supervision-service/app/schemas/__init__.py b/examples/supervision-service/app/schemas/__init__.py new file mode 100644 index 0000000000..9635ebc9c8 --- /dev/null +++ b/examples/supervision-service/app/schemas/__init__.py @@ -0,0 +1,3 @@ +from app.schemas.common import ApiInfo, HealthResponse + +__all__ = ["ApiInfo", "HealthResponse"] diff --git a/examples/supervision-service/app/schemas/common.py b/examples/supervision-service/app/schemas/common.py new file mode 100644 index 0000000000..9ed21685e3 --- /dev/null +++ b/examples/supervision-service/app/schemas/common.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, Field + + +class HealthResponse(BaseModel): + """Health check response.""" + + status: str = Field(examples=["ok"]) + + +class ApiInfo(BaseModel): + """Root endpoint metadata.""" + + message: str + docs: str + health: str diff --git a/examples/supervision-service/app/schemas/records.py b/examples/supervision-service/app/schemas/records.py new file mode 100644 index 0000000000..9bf384d99d --- /dev/null +++ b/examples/supervision-service/app/schemas/records.py @@ -0,0 +1,55 @@ +from typing import Any + +from pydantic import BaseModel, Field + + +class UploadResponse(BaseModel): + """Stored upload record.""" + + id: str + original_filename: str + stored_filename: str + file_path: str + file_size: int + content_type: str | None + created_at: str + + +class ProcessingJobResponse(BaseModel): + """Processing job record.""" + + id: str + upload_id: str + job_type: str + status: str + output_path: str | None + parameters: dict[str, Any] + error_message: str | None + progress: int = Field(ge=0, le=100) + current_frame: int = Field(ge=0) + total_frames: int = Field(ge=0) + created_at: str + completed_at: str | None + original_filename: str | None = None + + +class JobCreatedResponse(BaseModel): + """Response after submitting an async processing job.""" + + job_id: str + upload_id: str + status: str = "processing" + + +class UploadListResponse(BaseModel): + """Paginated upload list.""" + + items: list[UploadResponse] + total: int = Field(description="Number of items returned") + + +class ProcessingJobListResponse(BaseModel): + """Paginated processing job list.""" + + items: list[ProcessingJobResponse] + total: int = Field(description="Number of items returned") diff --git a/examples/supervision-service/app/services/__init__.py b/examples/supervision-service/app/services/__init__.py new file mode 100644 index 0000000000..d093179777 --- /dev/null +++ b/examples/supervision-service/app/services/__init__.py @@ -0,0 +1,3 @@ +from app.services.video_processor import build_output_path, track_video + +__all__ = ["build_output_path", "track_video"] diff --git a/examples/supervision-service/app/services/job_runner.py b/examples/supervision-service/app/services/job_runner.py new file mode 100644 index 0000000000..cf3b2c457f --- /dev/null +++ b/examples/supervision-service/app/services/job_runner.py @@ -0,0 +1,73 @@ +from pathlib import Path + +from app.config import DEFAULT_CONFIDENCE, DEFAULT_IOU, DEFAULT_SPEED_WEIGHTS, DEFAULT_WEIGHTS +from app.db.repository import mark_job_completed, mark_job_failed, update_job_progress +from app.services.speed_processor import build_speed_output_path, estimate_speed_video +from app.services.video_processor import build_output_path, track_video + + +def _make_progress_callback(job_id: str): + def callback(current_frame: int, total_frames: int) -> None: + if total_frames <= 0: + return + frame_progress = int(current_frame / total_frames * 95) + update_job_progress( + job_id, + progress=min(frame_progress, 95), + current_frame=current_frame, + total_frames=total_frames, + ) + + return callback + + +def run_track_job( + job_id: str, + source_video_path: Path, + confidence_threshold: float = DEFAULT_CONFIDENCE, + iou_threshold: float = DEFAULT_IOU, +) -> None: + """Run tracking in background and update job progress.""" + output_path = build_output_path("tracked") + try: + track_video( + source_video_path=source_video_path, + target_video_path=output_path, + weights_path=DEFAULT_WEIGHTS, + confidence_threshold=confidence_threshold, + iou_threshold=iou_threshold, + on_progress=_make_progress_callback(job_id), + ) + update_job_progress(job_id, progress=99, current_frame=0, total_frames=0) + mark_job_completed(job_id, output_path) + except Exception as exc: + mark_job_failed(job_id, str(exc)) + + +def run_speed_job( + job_id: str, + source_video_path: Path, + source_points: list[list[int]], + target_width: float, + target_height: float, + confidence_threshold: float = DEFAULT_CONFIDENCE, + iou_threshold: float = DEFAULT_IOU, +) -> None: + """Run speed estimation in background and update job progress.""" + output_path = build_speed_output_path() + try: + estimate_speed_video( + source_video_path=source_video_path, + target_video_path=output_path, + source_points=source_points, + target_width=target_width, + target_height=target_height, + weights_path=DEFAULT_SPEED_WEIGHTS, + confidence_threshold=confidence_threshold, + iou_threshold=iou_threshold, + on_progress=_make_progress_callback(job_id), + ) + update_job_progress(job_id, progress=99, current_frame=0, total_frames=0) + mark_job_completed(job_id, output_path) + except Exception as exc: + mark_job_failed(job_id, str(exc)) diff --git a/examples/supervision-service/app/services/speed_processor.py b/examples/supervision-service/app/services/speed_processor.py new file mode 100644 index 0000000000..eb99ad1e4a --- /dev/null +++ b/examples/supervision-service/app/services/speed_processor.py @@ -0,0 +1,207 @@ +from collections import defaultdict, deque +from collections.abc import Callable +from pathlib import Path +from uuid import uuid4 + +import cv2 +import numpy as np +from ultralytics import YOLO + +import supervision as sv + +from app.config import DEFAULT_CONFIDENCE, DEFAULT_IOU, DEFAULT_SPEED_WEIGHTS, OUTPUT_DIR +from app.services.video_encoding import ensure_browser_playable + +ProgressCallback = Callable[[int, int], None] + + +class ViewTransformer: + def __init__(self, source: np.ndarray, target: np.ndarray) -> None: + source = source.astype(np.float32) + target = target.astype(np.float32) + self.m = cv2.getPerspectiveTransform(source, target) + + def transform_points(self, points: np.ndarray) -> np.ndarray: + if points.size == 0: + return points + + reshaped_points = points.reshape(-1, 1, 2).astype(np.float32) + transformed_points = cv2.perspectiveTransform(reshaped_points, self.m) + return transformed_points.reshape(-1, 2) + + +def build_target_array(target_width: float, target_height: float) -> np.ndarray: + """Build the bird's-eye target quadrilateral for perspective transform. + + Args: + target_width: Real-world width of the calibrated road section in meters. + target_height: Real-world length of the calibrated road section in meters. + + Returns: + Target corner coordinates for OpenCV perspective transform. + + Examples: + >>> arr = build_target_array(25, 250) + >>> arr.shape + (4, 2) + """ + width = max(target_width, 1.0) + height = max(target_height, 1.0) + return np.array( + [ + [0, 0], + [width - 1, 0], + [width - 1, height - 1], + [0, height - 1], + ], + dtype=np.float32, + ) + + +def estimate_speed_video( + source_video_path: Path, + target_video_path: Path, + source_points: list[list[int]], + target_width: float, + target_height: float, + weights_path: Path = DEFAULT_SPEED_WEIGHTS, + confidence_threshold: float = DEFAULT_CONFIDENCE, + iou_threshold: float = DEFAULT_IOU, + on_progress: ProgressCallback | None = None, +) -> Path: + """Estimate vehicle speeds and write an annotated output video. + + Args: + source_video_path: Input video path. + target_video_path: Output annotated video path. + source_points: Four [x, y] points defining the road surface quadrilateral. + target_width: Real-world width of the calibrated section in meters. + target_height: Real-world length of the calibrated section in meters. + weights_path: YOLO weights path. + confidence_threshold: Detection confidence threshold. + iou_threshold: Detection IOU threshold. + + Returns: + Path to the annotated output video. + + Examples: + >>> points = [[0, 0], [100, 0], [100, 100], [0, 100]] + >>> estimate_speed_video(Path("in.mp4"), Path("out.mp4"), points, 25, 250) # doctest: +SKIP + PosixPath('out.mp4') + """ + if len(source_points) != 4: + raise ValueError("Exactly four source points are required.") + + source = np.array(source_points, dtype=np.float32) + target = build_target_array(target_width, target_height) + + video_info = sv.VideoInfo.from_video_path(str(source_video_path)) + model = YOLO(str(weights_path)) + byte_track = sv.ByteTrack( + frame_rate=video_info.fps, + track_activation_threshold=confidence_threshold, + ) + + thickness = sv.calculate_optimal_line_thickness( + resolution_wh=video_info.resolution_wh + ) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh) + box_annotator = sv.BoxAnnotator(thickness=thickness) + label_annotator = sv.LabelAnnotator( + text_scale=text_scale, + text_thickness=thickness, + text_position=sv.Position.BOTTOM_CENTER, + ) + trace_annotator = sv.TraceAnnotator( + thickness=thickness, + trace_length=int(video_info.fps * 2), + position=sv.Position.BOTTOM_CENTER, + ) + + frame_generator = sv.get_video_frames_generator(str(source_video_path)) + polygon_zone = sv.PolygonZone(polygon=source) + view_transformer = ViewTransformer(source=source, target=target) + total_frames = video_info.total_frames + + coordinates: defaultdict[int, deque[int]] = defaultdict( + lambda: deque(maxlen=int(video_info.fps)) + ) + + target_video_path.parent.mkdir(parents=True, exist_ok=True) + + if on_progress and total_frames: + on_progress(0, total_frames) + + with sv.VideoSink(str(target_video_path), video_info) as sink: + for frame_index, frame in enumerate(frame_generator, start=1): + result = model( + frame, + verbose=False, + conf=confidence_threshold, + iou=iou_threshold, + )[0] + detections = sv.Detections.from_ultralytics(result) + detections = detections[polygon_zone.trigger(detections)] + detections = byte_track.update_with_detections(detections=detections) + + points = detections.get_anchors_coordinates( + anchor=sv.Position.BOTTOM_CENTER + ) + points = view_transformer.transform_points(points=points).astype(int) + + for tracker_id, (_, y) in zip(detections.tracker_id, points): + coordinates[tracker_id].append(y) + + labels = [] + for tracker_id in detections.tracker_id: + if len(coordinates[tracker_id]) < video_info.fps / 2: + labels.append(f"#{tracker_id}") + else: + coordinate_start = coordinates[tracker_id][-1] + coordinate_end = coordinates[tracker_id][0] + distance = abs(coordinate_start - coordinate_end) + time = len(coordinates[tracker_id]) / video_info.fps + speed = distance / time * 3.6 + labels.append(f"#{tracker_id} {int(speed)} km/h") + + annotated_frame = frame.copy() + annotated_frame = trace_annotator.annotate( + scene=annotated_frame, + detections=detections, + ) + annotated_frame = box_annotator.annotate( + scene=annotated_frame, + detections=detections, + ) + annotated_frame = label_annotator.annotate( + scene=annotated_frame, + detections=detections, + labels=labels, + ) + sink.write_frame(annotated_frame) + + if on_progress and total_frames: + on_progress(frame_index, total_frames) + + if on_progress and total_frames: + on_progress(total_frames, total_frames) + + ensure_browser_playable(target_video_path) + return target_video_path + + +def build_speed_output_path(prefix: str = "speed") -> Path: + """Create a unique output path for speed estimation results. + + Args: + prefix: Filename prefix for the output file. + + Returns: + Path under the configured output directory. + + Examples: + >>> path = build_speed_output_path() + >>> path.name.startswith("speed_") + True + """ + return OUTPUT_DIR / f"{prefix}_{uuid4().hex[:8]}.mp4" diff --git a/examples/supervision-service/app/services/upload_service.py b/examples/supervision-service/app/services/upload_service.py new file mode 100644 index 0000000000..48ab9c4cef --- /dev/null +++ b/examples/supervision-service/app/services/upload_service.py @@ -0,0 +1,40 @@ +from pathlib import Path +from uuid import uuid4 + +from fastapi import UploadFile + +from app.config import UPLOAD_DIR +from app.db.repository import UploadRecord, create_upload + + +def save_upload_file(file: UploadFile) -> UploadRecord: + """Persist an uploaded video under ``uploads/`` and store metadata. + + Args: + file: Incoming multipart upload. + + Returns: + Created upload database record. + + Raises: + ValueError: If filename is missing or file is empty. + """ + if not file.filename: + raise ValueError("Filename is required.") + + content = file.file.read() + if not content: + raise ValueError("Uploaded file is empty.") + + suffix = Path(file.filename).suffix or ".mp4" + stored_filename = f"{uuid4().hex}{suffix}" + upload_path = UPLOAD_DIR / stored_filename + upload_path.write_bytes(content) + + return create_upload( + original_filename=file.filename, + stored_filename=stored_filename, + file_path=upload_path, + file_size=len(content), + content_type=file.content_type, + ) diff --git a/examples/supervision-service/app/services/video_encoding.py b/examples/supervision-service/app/services/video_encoding.py new file mode 100644 index 0000000000..ec6c9d9ffd --- /dev/null +++ b/examples/supervision-service/app/services/video_encoding.py @@ -0,0 +1,47 @@ +import subprocess +from pathlib import Path + +import imageio_ffmpeg + + +def ensure_browser_playable(video_path: Path) -> Path: + """Transcode a video to H.264 so browsers can play it in HTML5 video. + + OpenCV VideoSink defaults to ``mp4v``, which most browsers cannot decode. + This re-encodes the file in place using the bundled ffmpeg from imageio-ffmpeg. + + Args: + video_path: Path to the video file to transcode. + + Returns: + The same path, now containing a browser-compatible MP4. + + Examples: + >>> ensure_browser_playable(Path("output.mp4")) # doctest: +SKIP + PosixPath('output.mp4') + """ + temp_path = video_path.with_name(f"{video_path.stem}_web{video_path.suffix}") + ffmpeg = imageio_ffmpeg.get_ffmpeg_exe() + + subprocess.run( + [ + ffmpeg, + "-y", + "-i", + str(video_path), + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + "-an", + str(temp_path), + ], + check=True, + capture_output=True, + ) + + video_path.unlink(missing_ok=True) + temp_path.rename(video_path) + return video_path diff --git a/examples/supervision-service/app/services/video_processor.py b/examples/supervision-service/app/services/video_processor.py new file mode 100644 index 0000000000..f2adefc476 --- /dev/null +++ b/examples/supervision-service/app/services/video_processor.py @@ -0,0 +1,74 @@ +from collections.abc import Callable +from pathlib import Path + +from ultralytics import YOLO + +import supervision as sv + +from app.config import DEFAULT_CONFIDENCE, DEFAULT_IOU, DEFAULT_WEIGHTS +from app.services.video_encoding import ensure_browser_playable + +ProgressCallback = Callable[[int, int], None] + + +def track_video( + source_video_path: Path, + target_video_path: Path, + weights_path: Path = DEFAULT_WEIGHTS, + confidence_threshold: float = DEFAULT_CONFIDENCE, + iou_threshold: float = DEFAULT_IOU, + on_progress: ProgressCallback | None = None, +) -> Path: + """Run YOLO detection and ByteTrack on a video file.""" + model = YOLO(str(weights_path)) + tracker = sv.ByteTrack() + box_annotator = sv.BoxAnnotator() + label_annotator = sv.LabelAnnotator() + + video_info = sv.VideoInfo.from_video_path(str(source_video_path)) + frame_generator = sv.get_video_frames_generator(str(source_video_path)) + total_frames = video_info.total_frames + + if on_progress and total_frames: + on_progress(0, total_frames) + + target_video_path.parent.mkdir(parents=True, exist_ok=True) + + with sv.VideoSink(str(target_video_path), video_info) as sink: + for frame_index, frame in enumerate(frame_generator, start=1): + results = model( + frame, + verbose=False, + conf=confidence_threshold, + iou=iou_threshold, + )[0] + detections = sv.Detections.from_ultralytics(results) + detections = tracker.update_with_detections(detections) + + annotated_frame = box_annotator.annotate( + scene=frame.copy(), + detections=detections, + ) + annotated_frame = label_annotator.annotate( + scene=annotated_frame, + detections=detections, + ) + sink.write_frame(frame=annotated_frame) + + if on_progress and total_frames: + on_progress(frame_index, total_frames) + + if on_progress and total_frames: + on_progress(total_frames, total_frames) + + ensure_browser_playable(target_video_path) + return target_video_path + + +def build_output_path(prefix: str = "tracked") -> Path: + """Create a unique output video path.""" + from uuid import uuid4 + + from app.config import OUTPUT_DIR + + return OUTPUT_DIR / f"{prefix}_{uuid4().hex[:8]}.mp4" diff --git a/examples/supervision-service/pyproject.toml b/examples/supervision-service/pyproject.toml new file mode 100644 index 0000000000..d2b2af8fc5 --- /dev/null +++ b/examples/supervision-service/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "supervision-api" +version = "0.1.0" +description = "Video analysis API powered by supervision" +requires-python = ">=3.10" +dependencies = [ + "fastapi[standard]>=0.115.0", + "supervision", + "ultralytics", + "jsonargparse[signatures]", + "imageio-ffmpeg>=0.5", +] + +[tool.fastapi] +entrypoint = "app.main:app" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["app"] diff --git a/examples/supervision-service/webapp/.editorconfig b/examples/supervision-service/webapp/.editorconfig new file mode 100644 index 0000000000..3b510aa687 --- /dev/null +++ b/examples/supervision-service/webapp/.editorconfig @@ -0,0 +1,8 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +max_line_length = 100 diff --git a/examples/supervision-service/webapp/.gitattributes b/examples/supervision-service/webapp/.gitattributes new file mode 100644 index 0000000000..6313b56c57 --- /dev/null +++ b/examples/supervision-service/webapp/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/examples/supervision-service/webapp/.gitignore b/examples/supervision-service/webapp/.gitignore new file mode 100644 index 0000000000..cd68f14023 --- /dev/null +++ b/examples/supervision-service/webapp/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/examples/supervision-service/webapp/.oxlintrc.json b/examples/supervision-service/webapp/.oxlintrc.json new file mode 100644 index 0000000000..d5648b966b --- /dev/null +++ b/examples/supervision-service/webapp/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"], + "env": { + "browser": true + }, + "categories": { + "correctness": "error" + } +} diff --git a/examples/supervision-service/webapp/.prettierrc.json b/examples/supervision-service/webapp/.prettierrc.json new file mode 100644 index 0000000000..29a2402ef0 --- /dev/null +++ b/examples/supervision-service/webapp/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/examples/supervision-service/webapp/README.md b/examples/supervision-service/webapp/README.md new file mode 100644 index 0000000000..382f7d60d9 --- /dev/null +++ b/examples/supervision-service/webapp/README.md @@ -0,0 +1,48 @@ +# webapp + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/examples/supervision-service/webapp/env.d.ts b/examples/supervision-service/webapp/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/supervision-service/webapp/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/supervision-service/webapp/eslint.config.ts b/examples/supervision-service/webapp/eslint.config.ts new file mode 100644 index 0000000000..89fdc89a24 --- /dev/null +++ b/examples/supervision-service/webapp/eslint.config.ts @@ -0,0 +1,26 @@ +import { globalIgnores } from 'eslint/config' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' +import pluginOxlint from 'eslint-plugin-oxlint' +import skipFormatting from 'eslint-config-prettier/flat' + +// To allow more languages other than `ts` in `.vue` files, uncomment the following lines: +// import { configureVueProject } from '@vue/eslint-config-typescript' +// configureVueProject({ scriptLangs: ['ts', 'tsx'] }) +// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{vue,ts,mts,tsx}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + ...pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + + ...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'), + + skipFormatting, +) diff --git a/examples/supervision-service/webapp/index.html b/examples/supervision-service/webapp/index.html new file mode 100644 index 0000000000..9e32d407d0 --- /dev/null +++ b/examples/supervision-service/webapp/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + Supervision Service + + +
+ + + diff --git a/examples/supervision-service/webapp/package-lock.json b/examples/supervision-service/webapp/package-lock.json new file mode 100644 index 0000000000..7bef1bad4c --- /dev/null +++ b/examples/supervision-service/webapp/package-lock.json @@ -0,0 +1,5062 @@ +{ + "name": "webapp", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webapp", + "version": "0.0.0", + "dependencies": { + "vue": "^3.5.38", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.2", + "@vitejs/plugin-vue": "^6.0.7", + "@vue/eslint-config-typescript": "^14.8.0", + "@vue/tsconfig": "^0.9.1", + "eslint": "^10.5.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-oxlint": "~1.69.0", + "eslint-plugin-vue": "~10.9.2", + "jiti": "^2.7.0", + "npm-run-all2": "^9.0.2", + "oxlint": "~1.69.0", + "prettier": "3.8.4", + "typescript": "~6.0.0", + "vite": "^8.0.16", + "vite-plugin-vue-devtools": "^8.1.2", + "vue-eslint-parser": "^10.4.1", + "vue-tsc": "^3.3.5" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.69.0.tgz", + "integrity": "sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.69.0.tgz", + "integrity": "sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.69.0.tgz", + "integrity": "sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.69.0.tgz", + "integrity": "sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.69.0.tgz", + "integrity": "sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.69.0.tgz", + "integrity": "sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.69.0.tgz", + "integrity": "sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.69.0.tgz", + "integrity": "sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.69.0.tgz", + "integrity": "sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.69.0.tgz", + "integrity": "sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.69.0.tgz", + "integrity": "sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.69.0.tgz", + "integrity": "sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.69.0.tgz", + "integrity": "sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.69.0.tgz", + "integrity": "sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.69.0.tgz", + "integrity": "sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.69.0.tgz", + "integrity": "sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.69.0.tgz", + "integrity": "sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.69.0.tgz", + "integrity": "sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.69.0.tgz", + "integrity": "sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node24": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node24/-/node24-24.0.4.tgz", + "integrity": "sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", + "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.5.tgz", + "integrity": "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.1.5.tgz", + "integrity": "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5", + "@vue/devtools-shared": "^8.1.5" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "license": "MIT" + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.9.0.tgz", + "integrity": "sha512-E3j9hDlfVf10F30MRcLTPY2IIhWIx1nsvkVukk14kTcuA+oBVot9zsP1hzsO+PAMDxV3Fd9FimBJtUBNBL5KFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.60.0", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.60.0", + "vue-eslint-parser": "^10.4.0" + }, + "bin": { + "vue-eslint-config-typescript": "dist/bin.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0 || ^10.0.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.6.tgz", + "integrity": "sha512-LgBMZAy2sR3cQWknpyaxnI6yBkqDfLBPkbdhwRhQCvzfNJRQXPilgQIrdI/v4ytJ0sAq9bWhaPsjqBqneomJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.384", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.384.tgz", + "integrity": "sha512-g6KAKY1vkYsADvSPWvdJsuYT0ixdcu6lUtD9P/wJKGBEDlZVXh2AX42j1mPqqaQPDluWjara9ziQ7xqAeXCt5A==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "dev": true, + "license": "MIT", + "peer": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-oxlint": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-oxlint/-/eslint-plugin-oxlint-1.69.0.tgz", + "integrity": "sha512-ryJT8Pqb3jgWhmQcKA/D98K6UckthAR70wPTBI4rOjcaKJ9nmQkysTLbTVVEcdzfT9mznV/2MKspBsCCpXm36w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.3.1" + }, + "peerDependencies": { + "oxlint": "~1.69.0" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.9.2.tgz", + "integrity": "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^7.1.0", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "vue-eslint-parser": "^10.3.0" + }, + "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", + "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", + "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-all2": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-9.0.2.tgz", + "integrity": "sha512-+dd4SO2jAlLE06OzmJKzIe6QvvjXezcbmobnh8usR0a8BzQCABTdqTXqVPji0ICOhSQpIIrkGd7IzNl5iDaRSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^1.0.0", + "read-package-json-fast": "^6.0.0", + "shell-quote": "^1.8.4", + "which": "^7.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", + "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/oxlint": { + "version": "1.69.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.69.0.tgz", + "integrity": "sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.69.0", + "@oxlint/binding-android-arm64": "1.69.0", + "@oxlint/binding-darwin-arm64": "1.69.0", + "@oxlint/binding-darwin-x64": "1.69.0", + "@oxlint/binding-freebsd-x64": "1.69.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.69.0", + "@oxlint/binding-linux-arm-musleabihf": "1.69.0", + "@oxlint/binding-linux-arm64-gnu": "1.69.0", + "@oxlint/binding-linux-arm64-musl": "1.69.0", + "@oxlint/binding-linux-ppc64-gnu": "1.69.0", + "@oxlint/binding-linux-riscv64-gnu": "1.69.0", + "@oxlint/binding-linux-riscv64-musl": "1.69.0", + "@oxlint/binding-linux-s390x-gnu": "1.69.0", + "@oxlint/binding-linux-x64-gnu": "1.69.0", + "@oxlint/binding-linux-x64-musl": "1.69.0", + "@oxlint/binding-openharmony-arm64": "1.69.0", + "@oxlint/binding-win32-arm64-msvc": "1.69.0", + "@oxlint/binding-win32-ia32-msvc": "1.69.0", + "@oxlint/binding-win32-x64-msvc": "1.69.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-1.0.0.tgz", + "integrity": "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", + "integrity": "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-dev-rpc/node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vite-hot-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.0", + "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", + "ohash": "^2.0.11", + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.1.5.tgz", + "integrity": "sha512-QSVntSN0sbVV08CZntMWOfF8McyPyYyYd19sChLjxYyFCC7Fcpey74ztw1uCCU23wTmR2yWPb92uaxVD4Lw0Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.1.5", + "@vue/devtools-kit": "^8.1.5", + "@vue/devtools-shared": "^8.1.5", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^6.0.0" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz", + "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz", + "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0 || ^9.0.0", + "eslint-visitor-keys": "^4.2.0 || ^5.0.0", + "espree": "^10.3.0 || ^11.0.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-router": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", + "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0-rc.4", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.1.2", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34", + "pinia": "^3.0.4", + "vite": "^7.0.0 || ^8.0.0", + "vue": "^3.5.34" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-router/node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz", + "integrity": "sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0.tgz", + "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/types": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0.tgz", + "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vue-tsc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.6.tgz", + "integrity": "sha512-ERXGgbKSBGFUkavrJ1Iwj0ZVxKqB/5UOx65IXy7fPf2UsoI21n3ssQLwdvx8xyUGgJe9PvSZTM/FYzIdwUDPFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.6" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/supervision-service/webapp/package.json b/examples/supervision-service/webapp/package.json new file mode 100644 index 0000000000..26d905ab78 --- /dev/null +++ b/examples/supervision-service/webapp/package.json @@ -0,0 +1,44 @@ +{ + "name": "webapp", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint": "run-s \"lint:*\"", + "lint:oxlint": "oxlint . --fix", + "lint:eslint": "eslint . --fix --cache", + "format": "prettier --write --experimental-cli src/" + }, + "dependencies": { + "vue": "^3.5.38", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.2", + "@vitejs/plugin-vue": "^6.0.7", + "@vue/eslint-config-typescript": "^14.8.0", + "@vue/tsconfig": "^0.9.1", + "eslint": "^10.5.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-oxlint": "~1.69.0", + "eslint-plugin-vue": "~10.9.2", + "jiti": "^2.7.0", + "npm-run-all2": "^9.0.2", + "oxlint": "~1.69.0", + "prettier": "3.8.4", + "typescript": "~6.0.0", + "vite": "^8.0.16", + "vite-plugin-vue-devtools": "^8.1.2", + "vue-eslint-parser": "^10.4.1", + "vue-tsc": "^3.3.5" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } +} diff --git a/examples/supervision-service/webapp/public/favicon.ico b/examples/supervision-service/webapp/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/examples/supervision-service/webapp/src/App.vue b/examples/supervision-service/webapp/src/App.vue new file mode 100644 index 0000000000..7c2aa3f3e1 --- /dev/null +++ b/examples/supervision-service/webapp/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/examples/supervision-service/webapp/src/api/jobs.ts b/examples/supervision-service/webapp/src/api/jobs.ts new file mode 100644 index 0000000000..ece1aba7d9 --- /dev/null +++ b/examples/supervision-service/webapp/src/api/jobs.ts @@ -0,0 +1,51 @@ +import type { ProcessingJobRecord } from '@/api/records' + +export type { ProcessingJobRecord } + +export interface JobCreatedResponse { + job_id: string + upload_id: string + status: string +} + +export function jobResultUrl(jobId: string): string { + return `/api/v1/records/jobs/${jobId}/file` +} + +export async function fetchJobRecord(jobId: string): Promise { + const response = await fetch(`/api/v1/records/jobs/${jobId}`) + if (!response.ok) { + throw new Error(`Failed to load job: ${response.status}`) + } + return response.json() as Promise +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => { + window.setTimeout(resolve, ms) + }) +} + +export async function pollJobUntilComplete( + jobId: string, + onProgress: (job: ProcessingJobRecord) => void, + signal?: AbortSignal, +): Promise { + while (true) { + if (signal?.aborted) { + throw new Error('任务已取消。') + } + + const job = await fetchJobRecord(jobId) + onProgress(job) + + if (job.status === 'completed') { + return job + } + if (job.status === 'failed') { + throw new Error(job.error_message ?? '处理失败。') + } + + await sleep(600) + } +} diff --git a/examples/supervision-service/webapp/src/api/records.ts b/examples/supervision-service/webapp/src/api/records.ts new file mode 100644 index 0000000000..e23bbbdb5c --- /dev/null +++ b/examples/supervision-service/webapp/src/api/records.ts @@ -0,0 +1,59 @@ +export interface UploadRecord { + id: string + original_filename: string + stored_filename: string + file_path: string + file_size: number + content_type: string | null + created_at: string +} + +export interface ProcessingJobRecord { + id: string + upload_id: string + job_type: string + status: string + output_path: string | null + parameters: Record + error_message: string | null + progress: number + current_frame: number + total_frames: number + created_at: string + completed_at: string | null + original_filename: string | null +} + +export interface UploadListResponse { + items: UploadRecord[] + total: number +} + +export interface ProcessingJobListResponse { + items: ProcessingJobRecord[] + total: number +} + +export async function fetchUploadRecords(limit = 50): Promise { + const response = await fetch(`/api/v1/records/uploads?limit=${limit}`) + if (!response.ok) { + throw new Error(`Failed to load upload records: ${response.status}`) + } + return response.json() as Promise +} + +export async function fetchJobRecords(limit = 50): Promise { + const response = await fetch(`/api/v1/records/jobs?limit=${limit}`) + if (!response.ok) { + throw new Error(`Failed to load job records: ${response.status}`) + } + return response.json() as Promise +} + +export function uploadFileUrl(uploadId: string): string { + return `/api/v1/records/uploads/${uploadId}/file` +} + +export function jobResultUrl(jobId: string): string { + return `/api/v1/records/jobs/${jobId}/file` +} diff --git a/examples/supervision-service/webapp/src/api/speed.ts b/examples/supervision-service/webapp/src/api/speed.ts new file mode 100644 index 0000000000..d940e5c8ae --- /dev/null +++ b/examples/supervision-service/webapp/src/api/speed.ts @@ -0,0 +1,75 @@ +import { jobResultUrl, pollJobUntilComplete } from '@/api/jobs' + +export interface SourcePoint { + x: number + y: number +} + +export interface SpeedEstimateOptions { + file: File + sourcePoints: SourcePoint[] + targetWidth: number + targetHeight: number + confidenceThreshold?: number + iouThreshold?: number + onProgress?: (progress: number, currentFrame: number, totalFrames: number) => void + signal?: AbortSignal +} + +export interface SpeedEstimateResult { + jobId: string + resultUrl: string +} + +export async function estimateSpeed({ + file, + sourcePoints, + targetWidth, + targetHeight, + confidenceThreshold = 0.3, + iouThreshold = 0.7, + onProgress, + signal, +}: SpeedEstimateOptions): Promise { + const formData = new FormData() + formData.append('file', file) + formData.append('source_points', JSON.stringify(sourcePoints)) + formData.append('target_width', String(targetWidth)) + formData.append('target_height', String(targetHeight)) + formData.append('confidence_threshold', String(confidenceThreshold)) + formData.append('iou_threshold', String(iouThreshold)) + + const response = await fetch('/api/v1/videos/speed-estimate', { + method: 'POST', + body: formData, + signal, + }) + + if (!response.ok) { + let detail = `Request failed: ${response.status}` + try { + const payload = (await response.json()) as { detail?: string } + if (payload.detail) { + detail = payload.detail + } + } catch { + // ignore JSON parse errors + } + throw new Error(detail) + } + + const created = (await response.json()) as { job_id: string } + + await pollJobUntilComplete( + created.job_id, + (job) => { + onProgress?.(job.progress, job.current_frame, job.total_frames) + }, + signal, + ) + + return { + jobId: created.job_id, + resultUrl: jobResultUrl(created.job_id), + } +} diff --git a/examples/supervision-service/webapp/src/api/video.ts b/examples/supervision-service/webapp/src/api/video.ts new file mode 100644 index 0000000000..bdbda6e27a --- /dev/null +++ b/examples/supervision-service/webapp/src/api/video.ts @@ -0,0 +1,73 @@ +import { jobResultUrl, pollJobUntilComplete } from '@/api/jobs' + +export interface HealthResponse { + status: string +} + +export interface TrackVideoOptions { + file: File + confidenceThreshold?: number + iouThreshold?: number + onProgress?: (progress: number, currentFrame: number, totalFrames: number) => void + signal?: AbortSignal +} + +export interface TrackVideoResult { + jobId: string + resultUrl: string +} + +export async function fetchHealth(): Promise { + const response = await fetch('/health') + if (!response.ok) { + throw new Error(`Health check failed: ${response.status}`) + } + return response.json() as Promise +} + +export async function trackVideo({ + file, + confidenceThreshold = 0.3, + iouThreshold = 0.7, + onProgress, + signal, +}: TrackVideoOptions): Promise { + const formData = new FormData() + formData.append('file', file) + formData.append('confidence_threshold', String(confidenceThreshold)) + formData.append('iou_threshold', String(iouThreshold)) + + const response = await fetch('/api/v1/videos/track', { + method: 'POST', + body: formData, + signal, + }) + + if (!response.ok) { + let detail = `Request failed: ${response.status}` + try { + const payload = (await response.json()) as { detail?: string } + if (payload.detail) { + detail = payload.detail + } + } catch { + // ignore JSON parse errors + } + throw new Error(detail) + } + + const created = (await response.json()) as { job_id: string } + + await pollJobUntilComplete( + created.job_id, + (job) => { + onProgress?.(job.progress, job.current_frame, job.total_frames) + }, + signal, + ) + + return { + jobId: created.job_id, + resultUrl: jobResultUrl(created.job_id), + } +} diff --git a/examples/supervision-service/webapp/src/assets/base.css b/examples/supervision-service/webapp/src/assets/base.css new file mode 100644 index 0000000000..8816868a41 --- /dev/null +++ b/examples/supervision-service/webapp/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/examples/supervision-service/webapp/src/assets/global.css b/examples/supervision-service/webapp/src/assets/global.css new file mode 100644 index 0000000000..4c5dc550bc --- /dev/null +++ b/examples/supervision-service/webapp/src/assets/global.css @@ -0,0 +1,63 @@ +:root { + --bg-base: #0d1014; + --bg-panel: #151a22; + --bg-elevated: #1c2330; + --line: rgba(245, 197, 24, 0.16); + --marking: #f5c518; + --accent: #ff6b2c; + --text: #e6edf3; + --text-muted: #8b949e; + --success: #3dd68c; + --danger: #ff8a80; + --sidebar-width: 240px; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#app { + min-height: 100%; +} + +body { + margin: 0; + color: var(--text); + background: var(--bg-base); + font-family: 'IBM Plex Sans', sans-serif; + font-size: 15px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +a { + color: inherit; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button:focus-visible, +a:focus-visible, +input:focus-visible { + outline: 2px solid var(--marking); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/examples/supervision-service/webapp/src/assets/logo.svg b/examples/supervision-service/webapp/src/assets/logo.svg new file mode 100644 index 0000000000..7565660356 --- /dev/null +++ b/examples/supervision-service/webapp/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/examples/supervision-service/webapp/src/assets/main.css b/examples/supervision-service/webapp/src/assets/main.css new file mode 100644 index 0000000000..36fb845b52 --- /dev/null +++ b/examples/supervision-service/webapp/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/examples/supervision-service/webapp/src/components/ApiStatusBadge.vue b/examples/supervision-service/webapp/src/components/ApiStatusBadge.vue new file mode 100644 index 0000000000..ffdf5a9f3d --- /dev/null +++ b/examples/supervision-service/webapp/src/components/ApiStatusBadge.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/CalibrationCanvas.vue b/examples/supervision-service/webapp/src/components/CalibrationCanvas.vue new file mode 100644 index 0000000000..78ceace705 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/CalibrationCanvas.vue @@ -0,0 +1,268 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/HelloWorld.vue b/examples/supervision-service/webapp/src/components/HelloWorld.vue new file mode 100644 index 0000000000..d174cf8e1c --- /dev/null +++ b/examples/supervision-service/webapp/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/ProcessingState.vue b/examples/supervision-service/webapp/src/components/ProcessingState.vue new file mode 100644 index 0000000000..805441f8f9 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/ProcessingState.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/ResultPanel.vue b/examples/supervision-service/webapp/src/components/ResultPanel.vue new file mode 100644 index 0000000000..2c56773ba9 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/ResultPanel.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/TheWelcome.vue b/examples/supervision-service/webapp/src/components/TheWelcome.vue new file mode 100644 index 0000000000..8b731d918b --- /dev/null +++ b/examples/supervision-service/webapp/src/components/TheWelcome.vue @@ -0,0 +1,95 @@ + + + diff --git a/examples/supervision-service/webapp/src/components/VideoDropzone.vue b/examples/supervision-service/webapp/src/components/VideoDropzone.vue new file mode 100644 index 0000000000..c23630ea90 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/VideoDropzone.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/components/WelcomeItem.vue b/examples/supervision-service/webapp/src/components/WelcomeItem.vue new file mode 100644 index 0000000000..6d7086aea0 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/examples/supervision-service/webapp/src/components/icons/IconCommunity.vue b/examples/supervision-service/webapp/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000000..2dc8b05525 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/examples/supervision-service/webapp/src/components/icons/IconDocumentation.vue b/examples/supervision-service/webapp/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000000..6d4791cfbc --- /dev/null +++ b/examples/supervision-service/webapp/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/examples/supervision-service/webapp/src/components/icons/IconEcosystem.vue b/examples/supervision-service/webapp/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000000..c3a4f078c0 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/examples/supervision-service/webapp/src/components/icons/IconSupport.vue b/examples/supervision-service/webapp/src/components/icons/IconSupport.vue new file mode 100644 index 0000000000..7452834d3e --- /dev/null +++ b/examples/supervision-service/webapp/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/examples/supervision-service/webapp/src/components/icons/IconTooling.vue b/examples/supervision-service/webapp/src/components/icons/IconTooling.vue new file mode 100644 index 0000000000..660598d7c7 --- /dev/null +++ b/examples/supervision-service/webapp/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/examples/supervision-service/webapp/src/composables/useApiHealth.ts b/examples/supervision-service/webapp/src/composables/useApiHealth.ts new file mode 100644 index 0000000000..1d957d67c9 --- /dev/null +++ b/examples/supervision-service/webapp/src/composables/useApiHealth.ts @@ -0,0 +1,31 @@ +import { onMounted, onUnmounted, ref } from 'vue' + +import { fetchHealth } from '@/api/video' + +export function useApiHealth(pollMs = 15000) { + const status = ref<'loading' | 'online' | 'offline'>('loading') + let timer: ReturnType | undefined + + async function refresh() { + status.value = 'loading' + try { + const health = await fetchHealth() + status.value = health.status === 'ok' ? 'online' : 'offline' + } catch { + status.value = 'offline' + } + } + + onMounted(() => { + void refresh() + timer = setInterval(() => void refresh(), pollMs) + }) + + onUnmounted(() => { + if (timer) { + clearInterval(timer) + } + }) + + return { status, refresh } +} diff --git a/examples/supervision-service/webapp/src/composables/useVideoFile.ts b/examples/supervision-service/webapp/src/composables/useVideoFile.ts new file mode 100644 index 0000000000..4c17d2d062 --- /dev/null +++ b/examples/supervision-service/webapp/src/composables/useVideoFile.ts @@ -0,0 +1,42 @@ +import { computed, ref } from 'vue' + +export function useVideoFile() { + const file = ref(null) + const previewUrl = ref('') + + const fileMeta = computed(() => { + if (!file.value) { + return null + } + const sizeMb = (file.value.size / (1024 * 1024)).toFixed(2) + return { + name: file.value.name, + sizeMb, + type: file.value.type || 'video/*', + } + }) + + function setFile(next: File | null) { + if (previewUrl.value) { + URL.revokeObjectURL(previewUrl.value) + previewUrl.value = '' + } + file.value = next + if (next) { + previewUrl.value = URL.createObjectURL(next) + } + } + + function clear() { + setFile(null) + } + + function revoke() { + if (previewUrl.value) { + URL.revokeObjectURL(previewUrl.value) + previewUrl.value = '' + } + } + + return { file, previewUrl, fileMeta, setFile, clear, revoke } +} diff --git a/examples/supervision-service/webapp/src/layouts/AppLayout.vue b/examples/supervision-service/webapp/src/layouts/AppLayout.vue new file mode 100644 index 0000000000..813fc0b4dc --- /dev/null +++ b/examples/supervision-service/webapp/src/layouts/AppLayout.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/main.ts b/examples/supervision-service/webapp/src/main.ts new file mode 100644 index 0000000000..4a1e9e6585 --- /dev/null +++ b/examples/supervision-service/webapp/src/main.ts @@ -0,0 +1,8 @@ +import './assets/global.css' + +import { createApp } from 'vue' + +import App from './App.vue' +import router from './router' + +createApp(App).use(router).mount('#app') diff --git a/examples/supervision-service/webapp/src/router/index.ts b/examples/supervision-service/webapp/src/router/index.ts new file mode 100644 index 0000000000..82e9f747af --- /dev/null +++ b/examples/supervision-service/webapp/src/router/index.ts @@ -0,0 +1,25 @@ +import { createRouter, createWebHistory } from 'vue-router' + +import AppLayout from '@/layouts/AppLayout.vue' +import DashboardView from '@/views/DashboardView.vue' +import HistoryView from '@/views/HistoryView.vue' +import SpeedView from '@/views/SpeedView.vue' +import TrackView from '@/views/TrackView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + component: AppLayout, + children: [ + { path: '', name: 'dashboard', component: DashboardView }, + { path: 'track', name: 'track', component: TrackView }, + { path: 'speed', name: 'speed', component: SpeedView }, + { path: 'history', name: 'history', component: HistoryView }, + ], + }, + ], +}) + +export default router diff --git a/examples/supervision-service/webapp/src/views/DashboardView.vue b/examples/supervision-service/webapp/src/views/DashboardView.vue new file mode 100644 index 0000000000..b11df1a430 --- /dev/null +++ b/examples/supervision-service/webapp/src/views/DashboardView.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/views/HistoryView.vue b/examples/supervision-service/webapp/src/views/HistoryView.vue new file mode 100644 index 0000000000..3a1fa3c4b5 --- /dev/null +++ b/examples/supervision-service/webapp/src/views/HistoryView.vue @@ -0,0 +1,469 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/views/SpeedView.vue b/examples/supervision-service/webapp/src/views/SpeedView.vue new file mode 100644 index 0000000000..d9a3117467 --- /dev/null +++ b/examples/supervision-service/webapp/src/views/SpeedView.vue @@ -0,0 +1,395 @@ + + + + + diff --git a/examples/supervision-service/webapp/src/views/TrackView.vue b/examples/supervision-service/webapp/src/views/TrackView.vue new file mode 100644 index 0000000000..67543bd0bb --- /dev/null +++ b/examples/supervision-service/webapp/src/views/TrackView.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/examples/supervision-service/webapp/tsconfig.app.json b/examples/supervision-service/webapp/tsconfig.app.json new file mode 100644 index 0000000000..c0f2d86707 --- /dev/null +++ b/examples/supervision-service/webapp/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/examples/supervision-service/webapp/tsconfig.json b/examples/supervision-service/webapp/tsconfig.json new file mode 100644 index 0000000000..66b5e5703e --- /dev/null +++ b/examples/supervision-service/webapp/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/examples/supervision-service/webapp/tsconfig.node.json b/examples/supervision-service/webapp/tsconfig.node.json new file mode 100644 index 0000000000..c9b2badc95 --- /dev/null +++ b/examples/supervision-service/webapp/tsconfig.node.json @@ -0,0 +1,27 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/examples/supervision-service/webapp/vite.config.ts b/examples/supervision-service/webapp/vite.config.ts new file mode 100644 index 0000000000..4173f648a3 --- /dev/null +++ b/examples/supervision-service/webapp/vite.config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue(), vueDevTools()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + proxy: { + '/api': 'http://127.0.0.1:8000', + '/health': 'http://127.0.0.1:8000', + '/docs': 'http://127.0.0.1:8000', + '/openapi.json': 'http://127.0.0.1:8000', + }, + }, +})