From 00151e466c7f84d1a6726c02d69af16076a7a536 Mon Sep 17 00:00:00 2001 From: Wu Qing <3184394176@qq.com> Date: Fri, 7 Aug 2026 23:01:29 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E8=A7=A3=E5=86=B3=20CDC=20?= =?UTF-8?q?=E5=8E=BB=E9=87=8D=E3=80=81=E9=9B=86=E4=B8=AD=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E4=B8=8E=E9=A6=96=E6=AC=A1=E5=88=9D=E5=A7=8B=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现 CDC 内容寻址仓库、远程 Agent 中央中转备份与首次初始化体验,并补充安全校验、测试及双语文档。 Closes #94 Closes #101 Closes #104 --- README.md | 6 +- README.zh-CN.md | 6 +- deploy/install.sh | 53 +- docs-site/docs/deployment/bare-metal.md | 24 +- docs-site/docs/features/backup-types.md | 14 +- docs-site/docs/features/multi-node.md | 23 +- docs-site/docs/features/storage-backends.md | 4 +- .../docs/getting-started/installation.md | 14 +- .../current/deployment/bare-metal.md | 24 +- .../current/features/backup-types.md | 14 +- .../current/features/multi-node.md | 23 +- .../current/features/storage-backends.md | 4 +- .../current/getting-started/installation.md | 14 +- server/internal/agent/client.go | 66 +- server/internal/agent/executor.go | 91 +- server/internal/agent/executor_test.go | 147 ++ server/internal/app/app.go | 4 +- server/internal/backup/repository.go | 1356 +++++++++++++++++ server/internal/backup/repository_chunker.go | 131 ++ server/internal/backup/repository_test.go | 414 +++++ server/internal/backup/retention/service.go | 125 +- .../internal/backup/retention/service_test.go | 63 + server/internal/backup/temp_files.go | 11 + server/internal/http/agent_handler.go | 66 + server/internal/http/router.go | 2 + .../installscript/deploy_install_test.go | 20 + server/internal/model/backup_record.go | 24 +- server/internal/model/backup_task.go | 7 +- server/internal/service/agent_service.go | 250 ++- server/internal/service/agent_service_test.go | 194 ++- .../service/backup_execution_service.go | 313 +++- .../service/backup_execution_service_test.go | 153 ++ .../internal/service/backup_record_service.go | 66 +- .../internal/service/backup_task_service.go | 73 +- server/internal/service/execution_helpers.go | 5 + server/internal/service/restore_service.go | 92 +- .../internal/service/restore_service_test.go | 46 +- .../internal/service/verification_service.go | 14 + server/internal/storage/rclone/provider.go | 47 + server/internal/storage/types.go | 19 +- .../backup-records/BackupRecordLogDrawer.tsx | 10 +- .../backup-tasks/BackupTaskFormDrawer.tsx | 94 +- .../backup-tasks/SourceServerSelector.test.ts | 35 + .../backup-tasks/SourceServerSelector.tsx | 57 + web/src/components/common/DirectoryPicker.tsx | 2 +- web/src/components/common/EventCenter.tsx | 2 +- web/src/components/common/GlobalSearch.tsx | 2 +- .../components/common/LanguageSwitcher.tsx | 18 + .../icons/BackupServerIllustration.tsx | 50 + web/src/components/icons/index.ts | 38 + .../StorageTargetFormDrawer.tsx | 12 +- .../StorageTargetName.test.tsx | 14 + .../storage-targets/StorageTargetName.tsx | 18 + .../storage-targets/field-config.test.ts | 3 +- .../storage-targets/field-config.ts | 1 + web/src/i18n.test.ts | 21 + web/src/i18n.ts | 27 +- web/src/layouts/AppLayout.tsx | 2 +- web/src/layouts/protected-layout.tsx | 2 +- web/src/locales/en-US.json | 57 +- web/src/locales/zh-CN.json | 57 +- .../backup-records/BackupRecordsPage.tsx | 1 + web/src/pages/dashboard/DashboardPage.tsx | 2 +- web/src/pages/login/LoginPage.test.tsx | 70 + web/src/pages/login/LoginPage.tsx | 230 +-- web/src/pages/nodes/BatchCommandTable.tsx | 2 +- web/src/pages/nodes/NodesPage.tsx | 2 +- .../nodes/wizard/Step3CommandPreview.tsx | 2 +- web/src/pages/reports/ReportsPage.tsx | 2 +- .../storage-targets/StorageTargetsPage.tsx | 3 +- web/src/styles/global.css | 29 +- web/src/test/setup.ts | 14 + web/src/types/backup-records.ts | 4 +- web/src/types/backup-tasks.ts | 2 +- 74 files changed, 4507 insertions(+), 400 deletions(-) create mode 100644 server/internal/backup/repository.go create mode 100644 server/internal/backup/repository_chunker.go create mode 100644 server/internal/backup/repository_test.go create mode 100644 web/src/components/backup-tasks/SourceServerSelector.test.ts create mode 100644 web/src/components/backup-tasks/SourceServerSelector.tsx create mode 100644 web/src/components/common/LanguageSwitcher.tsx create mode 100644 web/src/components/icons/BackupServerIllustration.tsx create mode 100644 web/src/components/icons/index.ts create mode 100644 web/src/components/storage-targets/StorageTargetName.test.tsx create mode 100644 web/src/components/storage-targets/StorageTargetName.tsx create mode 100644 web/src/i18n.test.ts create mode 100644 web/src/pages/login/LoginPage.test.tsx diff --git a/README.md b/README.md index 5db127d..3e391ab 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,15 @@ docker run -d --name backupx -p 8340:8340 -v backupx-data:/app/data awuqing/back # Or prebuilt archive curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz tar xzf backupx-*.tar.gz && cd backupx-* && sudo ./install.sh + +# Or build and install on bare metal without Docker +git clone https://github.com/Awuqing/BackupX.git && cd BackupX +make build && sudo ./deploy/install.sh ``` For ARM64 hosts, use `backupx-linux-arm64.tar.gz`. The archive contains `backupx`, `web/`, `config.example.yaml`, and `install.sh`; run `install.sh` from the extracted directory. -Open `http://your-server:8340`, create the admin account, then follow the [5-minute Quick Start](https://awuqing.github.io/BackupX/docs/getting-started/quick-start). +Open `http://your-server:8340`, choose English or Chinese on the setup screen, create the first administrator account, then follow the [5-minute Quick Start](https://awuqing.github.io/BackupX/docs/getting-started/quick-start). ## Documentation diff --git a/README.zh-CN.md b/README.zh-CN.md index 661a9b0..e6a0b27 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -60,11 +60,15 @@ docker run -d --name backupx -p 8340:8340 -v backupx-data:/app/data awuqing/back # 或使用预编译包 curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz tar xzf backupx-*.tar.gz && cd backupx-* && sudo ./install.sh + +# 或从源码构建并裸机安装(无需 Docker) +git clone https://github.com/Awuqing/BackupX.git && cd BackupX +make build && sudo ./deploy/install.sh ``` ARM64 主机请下载 `backupx-linux-arm64.tar.gz`。预编译包内包含 `backupx`、`web/`、`config.example.yaml` 和 `install.sh`,请在解压后的目录内执行 `install.sh`。 -打开 `http://your-server:8340`,创建管理员账户,按 [5 分钟快速开始](https://awuqing.github.io/BackupX/zh-Hans/docs/getting-started/quick-start) 完成首次备份。 +打开 `http://your-server:8340`,在初始化页选择中文或 English 并创建首个管理员账户,按 [5 分钟快速开始](https://awuqing.github.io/BackupX/zh-Hans/docs/getting-started/quick-start) 完成首次备份。 ## 文档 diff --git a/deploy/install.sh b/deploy/install.sh index d451ae7..da068a1 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -14,7 +14,13 @@ if [ -f "$SCRIPT_DIR/backupx" ] && [ -d "$SCRIPT_DIR/web" ]; then CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$SCRIPT_DIR/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$SCRIPT_DIR/nginx.conf}" else - BIN_SOURCE="${BIN_SOURCE:-$PROJECT_ROOT/server/backupx}" + SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/bin/backupx" + # Keep compatibility with contributors who built the historical path by + # hand, while matching the canonical `make build` output first. + if [ ! -f "$SOURCE_BIN_DEFAULT" ] && [ -f "$PROJECT_ROOT/server/backupx" ]; then + SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/backupx" + fi + BIN_SOURCE="${BIN_SOURCE:-$SOURCE_BIN_DEFAULT}" WEB_SOURCE="${WEB_SOURCE:-$PROJECT_ROOT/web/dist}" CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$PROJECT_ROOT/server/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$PROJECT_ROOT/deploy/nginx.conf}" @@ -27,8 +33,9 @@ if [ "$(id -u)" -ne 0 ]; then fi if [ ! -f "$BIN_SOURCE" ]; then - echo "未找到后端二进制:$BIN_SOURCE" >&2 - echo "源码树安装请先执行:cd \"$PROJECT_ROOT/server\" && go build -o backupx ./cmd/backupx" >&2 + echo "Backend binary not found / 未找到后端二进制:$BIN_SOURCE" >&2 + echo "源码树安装请先在仓库根目录执行 make build(产物:server/bin/backupx)。" >&2 + echo "For a source install, run 'make build' in the repository root first." >&2 echo "发布包安装请确认当前目录包含 ./backupx、./web 和 ./install.sh。" >&2 exit 1 fi @@ -92,6 +99,41 @@ fi systemctl daemon-reload systemctl enable --now "$SERVICE_NAME" +# systemctl may return before the process has opened its HTTP listener. Verify +# the same unauthenticated endpoint used by the first-administrator screen so a +# broken bare-metal install cannot print a false success message. +HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8340/api/auth/setup/status}" +READY=0 +ATTEMPT=1 +while [ "$ATTEMPT" -le 30 ]; do + if systemctl is-active --quiet "$SERVICE_NAME"; then + if command -v curl >/dev/null 2>&1; then + if curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then + READY=1 + break + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q -T 2 -O /dev/null "$HEALTH_URL"; then + READY=1 + break + fi + else + echo "Warning / 警告:未找到 curl 或 wget,仅验证 systemd 服务状态。" >&2 + READY=1 + break + fi + fi + ATTEMPT=$((ATTEMPT + 1)) + sleep 1 +done + +if [ "$READY" -ne 1 ]; then + echo "BackupX did not become ready at $HEALTH_URL / 服务未通过就绪检查。" >&2 + systemctl status "$SERVICE_NAME" --no-pager >&2 || true + journalctl -u "$SERVICE_NAME" -n 50 --no-pager >&2 || true + exit 1 +fi + if [ -d "/etc/nginx/conf.d" ] && [ -f "$NGINX_SOURCE" ]; then install -m 0644 "$NGINX_SOURCE" "/etc/nginx/conf.d/$SERVICE_NAME.conf" if command -v nginx >/dev/null 2>&1; then @@ -111,6 +153,11 @@ cat <:8340 +首次访问 / First sign-in: + 1. 打开上面的地址,并可在登录页右上角选择 中文 或 English。 + 2. 页面显示“系统初始化 / System setup”时,创建首个管理员用户名和密码。 + 3. 如果未显示初始化表单,请先检查:$HEALTH_URL + (如已安装 nginx,脚本会自动写入反向代理配置,可继续用 80 端口访问。) 排查:若服务未监听端口,请查看日志: diff --git a/docs-site/docs/deployment/bare-metal.md b/docs-site/docs/deployment/bare-metal.md index 2200f56..ee81c82 100644 --- a/docs-site/docs/deployment/bare-metal.md +++ b/docs-site/docs/deployment/bare-metal.md @@ -10,20 +10,21 @@ description: systemd + Nginx deployment from the prebuilt release tarball or sou ```bash # Download the matching tarball -curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-v1.6.0-linux-amd64.tar.gz +curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz # Extract and install -tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* +tar xzf backupx-linux-amd64.tar.gz && cd backupx-*-linux-amd64 sudo ./install.sh ``` The installer performs these steps automatically: 1. Creates a system user `backupx` -2. Copies the binary to `/opt/backupx/` -3. Generates a default `config.yaml` with safe JWT/encryption secrets +2. Copies the binary to `/opt/backupx/bin/backupx` and the web console to `/opt/backupx/web` +3. Installs the default configuration at `/etc/backupx/config.yaml` 4. Installs `backupx.service` (systemd), enabled at boot 5. (Optional) installs an Nginx site file — see [Nginx Reverse Proxy](./nginx) +6. Verifies the first-setup API before reporting success For multi-node clusters, edit `/etc/backupx/config.yaml` after installation and set the Master URL that remote Agents can reach: @@ -63,11 +64,13 @@ After=network.target [Service] Type=simple User=backupx +Group=backupx WorkingDirectory=/opt/backupx -ExecStart=/opt/backupx/backupx --config /opt/backupx/config.yaml +ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure -RestartSec=5s -LimitNOFILE=65536 +RestartSec=5 +NoNewPrivileges=true +LimitNOFILE=65535 [Install] WantedBy=multi-user.target @@ -79,17 +82,20 @@ Typical operations: sudo systemctl status backupx sudo journalctl -u backupx -f # live logs sudo systemctl restart backupx +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` +Open `http://your-server:8340`, switch to English if desired, and create the first administrator on the **System setup** screen. For a custom listen port, run the installer with a matching `HEALTH_URL`. + ## Password reset If the admin password is lost: ```bash -/opt/backupx/backupx reset-password \ +/opt/backupx/bin/backupx reset-password \ --username admin \ --password 'newpass123' \ - --config /opt/backupx/config.yaml + --config /etc/backupx/config.yaml ``` Docker equivalent: diff --git a/docs-site/docs/features/backup-types.md b/docs-site/docs/features/backup-types.md index 5285c20..f41b946 100644 --- a/docs-site/docs/features/backup-types.md +++ b/docs-site/docs/features/backup-types.md @@ -12,12 +12,22 @@ When a task is routed to a remote Agent, the source tools and paths are resolved ## File / Directory -Tars (and optionally gzips) one or more filesystem paths. +File tasks offer three backup modes: + +- **Full archive** — writes a self-contained tar artifact on every run +- **Differential archive** — writes only changes since the current full baseline and periodically refreshes that baseline +- **CDC repository** — splits content with stable 512 KiB / 1 MiB / 4 MiB boundaries, stores new chunks in immutable 32 MiB packs, and writes a small snapshot manifest for each run + +The CDC repository deduplicates identical content across files and snapshots. Restore, selective restore, verification, download-as-tar, retention, and garbage collection all resolve data through the repository index. Compression and encryption are applied per chunk; encrypted repositories use keyed chunk IDs so plaintext hashes are not exposed. + +Repository mode currently uses a single-writer index and therefore runs on the Master only. To keep repository copies on multiple backends, select multiple primary storage targets on the task. Object-level replication is intentionally disabled because a snapshot manifest without its shared packs and indexes is not a complete backup. + +Common file-task options: - **Source** accepts multiple paths — one per line in the UI - **Exclude patterns** accept gitignore-style globs - Supports following symlinks, preserving permissions -- Output is a single `.tar` or `.tar.gz` artifact +- Full and differential modes output `.tar`, `.tar.gz`, or `.tar.zst` artifacts ## MySQL diff --git a/docs-site/docs/features/multi-node.md b/docs-site/docs/features/multi-node.md index 0cfc477..b47346b 100644 --- a/docs-site/docs/features/multi-node.md +++ b/docs-site/docs/features/multi-node.md @@ -26,6 +26,27 @@ BackupX supports Master-Agent mode: backup tasks can be routed to specific nodes - **Execution** — Agent reuses the same BackupRunner (file / mysql / postgresql / sqlite / saphana) and uploads directly to storage - **Security** — Each node has its own token; the Agent never holds the Master's JWT secret or AES-256 key +## Centralize backups from servers B/C/D into storage M + +Use the Master as the control plane and register every source server as an Agent. A task's **Source server** determines where paths and database tools are resolved; its **Storage targets** determine where the resulting artifact is retained. + +BackupX chooses the data path per target: + +| Destination | Data path | +| --- | --- | +| S3, WebDAV, FTP, cloud drive, or another network backend | Agent streams directly to the destination | +| `local_disk` with **Relay remote backups through Master** enabled (for example storage server M mounted through NFS) | Agent streams through the authenticated Master API; Master writes to its configured local path | + +The relay is streaming: the Master does not create a second temporary copy of the entire artifact. The reverse path is used when restoring a Master-local artifact back to its source Agent. Use HTTPS whenever Agent traffic crosses an untrusted network. + +To configure the common `A → {B,C,D} → M` topology: + +1. Run BackupX Master on A and mount M on A if M is exposed as NFS or another filesystem. +2. Create a `local_disk` target for that mount and keep **Relay remote backups through Master** enabled, or create an S3/WebDAV target exposed by M. Existing local-disk targets keep their prior Agent-local behavior until this switch is enabled. +3. Install one Agent on B, C, and D from **Node Management**. +4. Create a backup task for each source, choose B/C/D under **Source server**, browse that server's paths, and select M as the storage target. A source-server pool label can route identical tasks dynamically. +5. Verify the per-target result in the backup record. For a Master-local target, the record reports transfer mode `master_relay`; network backends remain `direct`. + ## Walkthrough ### 0. Set the Master URL for production clusters @@ -78,7 +99,7 @@ In Step 1 choose "Batch" and paste node names (one per line, max 50). Step 3 sho ### 5. Route a task to the node -In the **Backup Tasks** page, pick the target node when creating the task. When the task runs: +In the **Backup Tasks** page, pick the source server when creating the task. When the task runs: - Local (`nodeId=0`) → Master executes in-process - Remote node → Master enqueues the command → Agent claims → Agent runs locally → uploads → reports back diff --git a/docs-site/docs/features/storage-backends.md b/docs-site/docs/features/storage-backends.md index 731526b..6e977cb 100644 --- a/docs-site/docs/features/storage-backends.md +++ b/docs-site/docs/features/storage-backends.md @@ -19,7 +19,9 @@ BackupX aims to accept any place you'd want to drop a backup file. | **Google Drive** | Client ID/Secret + OAuth authorization | | **WebDAV** | URL + username/password | | **FTP / FTPS** | Host + port + username/password | -| **Local disk** | Target directory (absolute path) | +| **Local disk** | Target directory (absolute path) + optional Master relay for remote Agents | + +New local-disk targets enable **Relay remote backups through Master** by default. This makes the configured path belong to the Master, so a storage server mounted there can collect backups from many source Agents. Turn the switch off when the path intentionally belongs to each Agent. Existing targets retain their previous Agent-local behavior until explicitly changed. ## Rclone backends diff --git a/docs-site/docs/getting-started/installation.md b/docs-site/docs/getting-started/installation.md index 32f0c85..42bec64 100644 --- a/docs-site/docs/getting-started/installation.md +++ b/docs-site/docs/getting-started/installation.md @@ -55,10 +55,11 @@ sudo ./install.sh # creates system user, installs to /opt/backupx, sets u The installer: 1. Creates a `backupx` system user -2. Installs binary to `/opt/backupx/backupx` -3. Creates `/opt/backupx/config.yaml` with safe defaults +2. Installs the binary to `/opt/backupx/bin/backupx` and the web console to `/opt/backupx/web` +3. Creates `/etc/backupx/config.yaml` with safe defaults 4. Installs and enables the `backupx.service` systemd unit 5. (Optional) Configures an Nginx reverse proxy +6. Waits for `/api/auth/setup/status`; if startup fails, prints systemd diagnostics and exits non-zero ## From source @@ -67,16 +68,17 @@ Requires Go ≥ 1.25 and Node.js ≥ 20. ```bash git clone https://github.com/Awuqing/BackupX.git && cd BackupX make build -# or, for builds behind the great firewall -make docker-cn +sudo ./deploy/install.sh ``` After `make build`, the binary is at `server/bin/backupx` and the built web UI is at `web/dist/`. +The installer consumes those exact paths, so no Docker runtime is required. If an existing configuration uses a non-default port, set `HEALTH_URL` for the readiness check, for example `sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`. ## Verify the install ```bash -backupx --version # e.g. v1.6.0 +/opt/backupx/bin/backupx --version +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` -Then open `http://your-server:8340` to see the initial admin setup screen. +Then open `http://your-server:8340`. Choose **English** or **中文** in the upper-right corner. A fresh database shows **System setup**, where you create the first administrator username and password. If that form does not appear, retry the status request above before attempting to sign in. diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md index 60e49ba..f58191e 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md @@ -10,20 +10,21 @@ description: 从预编译包或源码部署 BackupX(systemd + Nginx)。 ```bash # 下载对应平台的压缩包 -curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-v1.6.0-linux-amd64.tar.gz +curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz # 解压并安装 -tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* +tar xzf backupx-linux-amd64.tar.gz && cd backupx-*-linux-amd64 sudo ./install.sh ``` 安装脚本自动完成以下步骤: 1. 创建系统用户 `backupx` -2. 复制二进制到 `/opt/backupx/` -3. 生成默认 `config.yaml`(含安全的 JWT/加密密钥) +2. 复制二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台复制到 `/opt/backupx/web` +3. 把默认配置安装到 `/etc/backupx/config.yaml` 4. 安装并启用 `backupx.service` systemd 单元 5. (可选)生成 Nginx 站点配置 — 参见 [Nginx 反向代理](./nginx) +6. 验证首次初始化接口就绪后才报告安装成功 如果要部署多节点集群,安装后请编辑 `/etc/backupx/config.yaml`,设置远程 Agent 可访问到的 Master URL: @@ -63,11 +64,13 @@ After=network.target [Service] Type=simple User=backupx +Group=backupx WorkingDirectory=/opt/backupx -ExecStart=/opt/backupx/backupx --config /opt/backupx/config.yaml +ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure -RestartSec=5s -LimitNOFILE=65536 +RestartSec=5 +NoNewPrivileges=true +LimitNOFILE=65535 [Install] WantedBy=multi-user.target @@ -79,17 +82,20 @@ WantedBy=multi-user.target sudo systemctl status backupx sudo journalctl -u backupx -f # 实时日志 sudo systemctl restart backupx +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` +访问 `http://your-server:8340`,可按需切换到 English,然后在“系统初始化 / System setup”页面创建首个管理员。若监听端口不是默认值,请为安装脚本传入对应的 `HEALTH_URL`。 + ## 密码重置 忘记管理员密码时: ```bash -/opt/backupx/backupx reset-password \ +/opt/backupx/bin/backupx reset-password \ --username admin \ --password 'newpass123' \ - --config /opt/backupx/config.yaml + --config /etc/backupx/config.yaml ``` Docker 等效命令: diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/backup-types.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/backup-types.md index df97237..bd6042e 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/backup-types.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/backup-types.md @@ -12,12 +12,22 @@ BackupX 支持五种内置备份类型,类型决定了用哪个 runner 执行 ## 文件 / 目录 -打包(可选 gzip)一个或多个文件系统路径。 +文件任务提供三种备份模式: + +- **全量归档** — 每次生成一份可独立使用的 tar 产物 +- **差异归档** — 只保存相对当前全量基线的变化,并按周期刷新全量基线 +- **CDC 去重仓库** — 按稳定的 512 KiB / 1 MiB / 4 MiB 内容边界切块,将新块合并到不可变的 32 MiB pack,每次运行只新增一份小型快照清单 + +CDC 仓库会在不同文件、不同快照之间复用相同内容。完整恢复、选择性恢复、完整性校验、下载为 tar、保留策略和垃圾回收都通过仓库索引定位分块。压缩与加密按块执行;启用加密时使用带密钥的块 ID,不暴露明文哈希。 + +当前仓库索引采用单写者模型,因此 CDC 模式仅在 Master 本机执行。如需保存多份完整仓库,请在任务中直接多选主存储目标。对象级副本复制会被禁用,因为只有快照清单、没有共享 pack 与索引并不是完整备份。 + +文件任务的通用选项: - **源路径** 支持多个(UI 中每行一个) - **排除模式** 支持 gitignore 风格的通配符 - 可选跟随符号链接、保留权限 -- 输出单个 `.tar` 或 `.tar.gz` +- 全量与差异模式输出 `.tar`、`.tar.gz` 或 `.tar.zst` ## MySQL diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md index 9b5a540..7ad1032 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md @@ -26,6 +26,27 @@ BackupX 支持 Master-Agent 模式:备份任务可以指定在哪个节点执 - **执行** — Agent 复用 BackupRunner(file / mysql / postgresql / sqlite / saphana)并直接上传到存储 - **安全** — 每个节点独立 Token;Agent 不持有 Master 的 JWT 密钥或 AES-256 加密密钥 +## 把 B/C/D 服务器集中备份到 M + +Master 作为控制面,每台源服务器安装一个 Agent。任务里的 **源服务器** 决定源路径和数据库工具在哪台机器解析,**存储目标** 决定备份产物最终保留在哪里。 + +BackupX 会根据目标类型选择数据路径: + +| 目标 | 数据路径 | +| --- | --- | +| S3、WebDAV、FTP、云盘或其他网络后端 | Agent 直接流式上传到目标 | +| 启用 **远程备份经 Master 中转** 的 `local_disk`(例如通过 NFS 挂载的存储服务器 M) | Agent 通过认证后的 Master API 流式中转,由 Master 写入配置目录 | + +中转过程不会在 Master 上额外落一份完整临时文件。把 Master 本地磁盘中的备份恢复到源 Agent 时会走反向流式通道。Agent 与 Master 之间跨越不可信网络时必须配置 HTTPS。 + +典型的 `A → {B,C,D} → M` 拓扑按以下步骤配置: + +1. 在 A 运行 BackupX Master;如果 M 以 NFS 等文件系统提供存储,先把 M 挂载到 A。 +2. 为该挂载点创建 `local_disk` 目标并保持 **远程备份经 Master 中转** 开启;如果 M 提供 S3/WebDAV,也可直接创建对应网络目标。升级前已有的本地磁盘目标会继续沿用 Agent 本机落盘,手动开启该选项后才切换到中央目录。 +3. 从 **节点管理** 分别在 B、C、D 安装 Agent。 +4. 为每台源服务器创建任务,在 **源服务器** 选择 B/C/D,浏览该服务器的路径,再把 M 选为存储目标。相同任务也可用源服务器池标签动态调度。 +5. 在备份记录中检查逐目标结果。Master 本地磁盘目标会记录 `master_relay` 中转模式,网络后端仍为 `direct` 直传。 + ## 一键部署步骤 ### 0. 为生产集群设置 Master 对外 URL @@ -78,7 +99,7 @@ Docker 模式使用同一组环境变量约定:`BACKUPX_AGENT_MASTER`、`BACKU ### 5. 把任务路由到该节点 -在 **备份任务** 页面新建任务时选择对应节点。任务触发时: +在 **备份任务** 页面新建任务时选择对应源服务器。任务触发时: - 本机 / 未指定(`nodeId=0`):Master 进程内直接执行 - 远程节点:Master 写入命令队列 → Agent 拉取 → Agent 本地执行 → 上传 → 回报 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/storage-backends.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/storage-backends.md index cdd25ac..b3f7895 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/storage-backends.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/storage-backends.md @@ -19,7 +19,9 @@ BackupX 的目标是接入任何你想放置备份文件的地方。 | **Google Drive** | Client ID/Secret + OAuth 授权 | | **WebDAV** | 地址 + 用户名/密码 | | **FTP / FTPS** | 主机 + 端口 + 用户名/密码 | -| **本地磁盘** | 目标目录(绝对路径) | +| **本地磁盘** | 目标目录(绝对路径)+ 可选的远程 Agent 经 Master 中转 | + +新建本地磁盘目标默认开启 **远程备份经 Master 中转**。开启时,配置目录属于 Master,挂载到 Master 的存储服务器可集中接收多台源 Agent 的备份;如果该路径本就属于各 Agent,请关闭此选项。升级前已有目标保持原来的 Agent 本机落盘行为,只有显式开启后才会切换。 ## Rclone 后端 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md index 3ac0208..3fbac14 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md @@ -55,10 +55,11 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 安装脚本会自动: 1. 创建 `backupx` 系统用户 -2. 安装二进制到 `/opt/backupx/backupx` -3. 生成 `/opt/backupx/config.yaml`(含安全默认值) +2. 安装二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台安装到 `/opt/backupx/web` +3. 生成 `/etc/backupx/config.yaml`(含安全默认值) 4. 注册并启用 `backupx.service` systemd 单元 5. (可选)配置 Nginx 反向代理 +6. 等待 `/api/auth/setup/status` 就绪;启动失败时输出 systemd 诊断并返回非零状态 ## 从源码构建 @@ -67,16 +68,17 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 ```bash git clone https://github.com/Awuqing/BackupX.git && cd BackupX make build -# 或使用国内镜像加速构建 Docker -make docker-cn +sudo ./deploy/install.sh ``` `make build` 完成后,二进制位于 `server/bin/backupx`,构建好的 Web UI 位于 `web/dist/`。 +安装脚本会直接使用这两个路径,不需要 Docker 运行时。如果已有配置修改了默认端口,可覆盖就绪检查地址,例如:`sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`。 ## 验证安装 ```bash -backupx --version # 输出如 v1.6.0 +/opt/backupx/bin/backupx --version +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` -打开浏览器访问 `http://your-server:8340`,会进入初始化管理员账户页面。 +打开浏览器访问 `http://your-server:8340`,可在右上角选择 **中文** 或 **English**。全新数据库会显示“系统初始化 / System setup”,在这里创建首个管理员用户名和密码。如果没有出现初始化表单,请先重试上面的状态接口,不要直接尝试登录。 diff --git a/server/internal/agent/client.go b/server/internal/agent/client.go index 08751ea..64ba166 100644 --- a/server/internal/agent/client.go +++ b/server/internal/agent/client.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -125,10 +126,11 @@ type TaskSpec struct { // StorageTargetConfig 与 service.AgentStorageTargetConfig 对齐 type StorageTargetConfig struct { - ID uint `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Config json.RawMessage `json:"config"` + ID uint `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Config json.RawMessage `json:"config"` + TransferMode string `json:"transferMode"` } // GetTaskSpec 拉取任务规格 @@ -149,6 +151,7 @@ type RecordUpdate struct { Checksum string `json:"checksum,omitempty"` StoragePath string `json:"storagePath,omitempty"` StorageTargetID uint `json:"storageTargetId,omitempty"` + StorageTransferMode string `json:"storageTransferMode,omitempty"` StorageUploadResults []StorageResultItem `json:"storageUploadResults,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` LogAppend string `json:"logAppend,omitempty"` @@ -160,6 +163,7 @@ type StorageResultItem struct { Status string `json:"status"` StoragePath string `json:"storagePath,omitempty"` FileSize int64 `json:"fileSize,omitempty"` + TransferMode string `json:"transferMode,omitempty"` Error string `json:"error,omitempty"` } @@ -169,6 +173,39 @@ func (c *MasterClient) UpdateRecord(ctx context.Context, recordID uint, update R return c.do(ctx, http.MethodPost, path, update, nil) } +// UploadArtifact streams an artifact through the Master for storage targets +// that are not directly reachable from the Agent. +func (c *MasterClient) UploadArtifact(ctx context.Context, recordID, targetID uint, objectKey string, size int64, checksum string, reader io.Reader) error { + path := fmt.Sprintf("/api/agent/records/%d/artifacts/%d", recordID, targetID) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.baseURL+path, reader) + if err != nil { + return err + } + // The executor owns and closes the artifact file. Prevent net/http from + // closing that underlying reader when it finishes the request body. + req.Body = io.NopCloser(reader) + req.ContentLength = size + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("X-Agent-Token", c.token) + req.Header.Set("X-BackupX-Object-Key", objectKey) + req.Header.Set("X-BackupX-SHA256", checksum) + client := *c.httpClient + client.Timeout = 0 + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("relay artifact to Master: %w", err) + } + data, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + closeErr := resp.Body.Close() + if readErr != nil || closeErr != nil { + return errors.Join(readErr, closeErr) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("relay artifact to Master: http %d: %s", resp.StatusCode, string(data)) + } + return nil +} + // RestoreSpec 与 service.AgentRestoreSpec 对齐 type RestoreSpec struct { RestoreRecordID uint `json:"restoreRecordId"` @@ -210,6 +247,27 @@ func (c *MasterClient) GetRestoreSpec(ctx context.Context, restoreRecordID uint) return &spec, nil } +func (c *MasterClient) DownloadRestoreArtifact(ctx context.Context, restoreRecordID uint) (io.ReadCloser, error) { + path := fmt.Sprintf("/api/agent/restores/%d/artifact", restoreRecordID) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil) + if err != nil { + return nil, err + } + req.Header.Set("X-Agent-Token", c.token) + client := *c.httpClient + client.Timeout = 0 + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("download relayed artifact from Master: %w", err) + } + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return resp.Body, nil + } + data, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + closeErr := resp.Body.Close() + return nil, errors.Join(fmt.Errorf("download relayed artifact from Master: http %d: %s", resp.StatusCode, string(data)), readErr, closeErr) +} + // UpdateRestore 上报恢复记录的状态/日志 func (c *MasterClient) UpdateRestore(ctx context.Context, restoreRecordID uint, update RestoreUpdate) error { path := fmt.Sprintf("/api/agent/restores/%d", restoreRecordID) diff --git a/server/internal/agent/executor.go b/server/internal/agent/executor.go index b1163b6..e58ac7d 100644 --- a/server/internal/agent/executor.go +++ b/server/internal/agent/executor.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "os" @@ -123,7 +124,7 @@ func (e *Executor) ExecuteRunTask(ctx context.Context, taskID, recordID uint) er } fileName := filepath.Base(finalPath) fileSize := info.Size() - storagePath := backup.BuildStorageKey(spec.Type, startedAt, fileName) + storagePath := backup.BuildRecordStorageKey(spec.Type, startedAt, recordID, fileName) // 5) 计算 checksum(一次读一次)并上传到所有目标 checksum, err := computeFileSHA256(finalPath) @@ -137,13 +138,15 @@ func (e *Executor) ExecuteRunTask(ctx context.Context, taskID, recordID uint) er } uploadResults := make([]StorageResultItem, 0, len(spec.StorageTargets)) selectedStorageTargetID := uint(0) + selectedStorageTransferMode := "" var uploadErrors []string for _, target := range spec.StorageTargets { - if err := e.uploadToTarget(ctx, recordID, target, finalPath, storagePath, fileSize, spec.TaskID); err != nil { + if err := e.uploadToTarget(ctx, recordID, target, finalPath, storagePath, fileSize, checksum, spec.TaskID); err != nil { uploadResults = append(uploadResults, StorageResultItem{ StorageTargetID: target.ID, StorageTargetName: target.Name, Status: "failed", + TransferMode: target.TransferMode, Error: err.Error(), }) uploadErrors = append(uploadErrors, fmt.Sprintf("%s: %v", target.Name, err)) @@ -152,6 +155,7 @@ func (e *Executor) ExecuteRunTask(ctx context.Context, taskID, recordID uint) er } if selectedStorageTargetID == 0 { selectedStorageTargetID = target.ID + selectedStorageTransferMode = target.TransferMode } uploadResults = append(uploadResults, StorageResultItem{ StorageTargetID: target.ID, @@ -159,6 +163,7 @@ func (e *Executor) ExecuteRunTask(ctx context.Context, taskID, recordID uint) er Status: "success", StoragePath: storagePath, FileSize: fileSize, + TransferMode: target.TransferMode, }) e.appendLog(ctx, recordID, fmt.Sprintf("[agent] 已上传到存储目标 %s\n", target.Name)) } @@ -179,34 +184,40 @@ func (e *Executor) ExecuteRunTask(ctx context.Context, taskID, recordID uint) er Checksum: checksum, StoragePath: storagePath, StorageTargetID: selectedStorageTargetID, + StorageTransferMode: selectedStorageTransferMode, StorageUploadResults: uploadResults, LogAppend: fmt.Sprintf("[agent] 任务完成,总计 %d 字节\n", fileSize), }) } // uploadToTarget 上传单个目标。为保持简化不做上传级重试(rclone 本身已有 low-level 重试)。 -func (e *Executor) uploadToTarget(ctx context.Context, recordID uint, target StorageTargetConfig, filePath, objectKey string, fileSize int64, taskID uint) error { - var rawConfig map[string]any - if len(target.Config) > 0 { - // DecodeRawConfig 通过 json 解析 - if err := jsonUnmarshalMap(target.Config, &rawConfig); err != nil { - return fmt.Errorf("parse storage config: %w", err) - } - } - provider, err := e.storageRegistry.Create(ctx, target.Type, rawConfig) - if err != nil { - return fmt.Errorf("create provider: %w", err) - } +func (e *Executor) uploadToTarget(ctx context.Context, recordID uint, target StorageTargetConfig, filePath, objectKey string, fileSize int64, checksum string, taskID uint) error { f, err := os.Open(filePath) if err != nil { return fmt.Errorf("open artifact: %w", err) } - defer f.Close() + if target.TransferMode == storage.TransferModeMasterRelay { + uploadErr := e.client.UploadArtifact(ctx, recordID, target.ID, objectKey, fileSize, checksum, f) + return errors.Join(uploadErr, f.Close()) + } + var rawConfig map[string]any + if len(target.Config) > 0 { + // DecodeRawConfig 通过 json 解析 + if err := jsonUnmarshalMap(target.Config, &rawConfig); err != nil { + return errors.Join(fmt.Errorf("parse storage config: %w", err), f.Close()) + } + } + provider, err := e.storageRegistry.Create(ctx, target.Type, rawConfig) + if err != nil { + closeErr := f.Close() + return errors.Join(fmt.Errorf("create provider: %w", err), closeErr) + } meta := map[string]string{ "taskId": fmt.Sprintf("%d", taskID), "recordId": fmt.Sprintf("%d", recordID), } - return provider.Upload(ctx, objectKey, f, fileSize, meta) + uploadErr := provider.Upload(ctx, objectKey, f, fileSize, meta) + return errors.Join(uploadErr, f.Close()) } // appendLog 追加日志到 Master 记录(尽力而为,失败不中断主流程) @@ -328,7 +339,7 @@ func (e *Executor) DeleteStorageObject(ctx context.Context, targetType string, t // ExecuteRestore 处理 restore_record 命令:拉规格 → 下载 → 解压 → 执行 runner.Restore → 上报结果。 // // 与 ExecuteRunTask 对称,但方向相反: -// - 下载:通过 spec.Storage 创建 provider → Download(spec.StoragePath) +// - 下载:直连共享存储,或通过 Master 中转其本地磁盘对象 // - 解密:当前 Agent 不支持加密恢复(密钥未下发),spec.Encrypt=true 会直接失败 // - 执行:backup.Registry.Runner(spec.Type).Restore // - 上报:通过 UpdateRestore(status/logAppend) @@ -357,28 +368,31 @@ func (e *Executor) ExecuteRestore(ctx context.Context, restoreRecordID uint) err } defer os.RemoveAll(tmpDir) - // 1) 创建 storage provider - var rawConfig map[string]any - if len(spec.Storage.Config) > 0 { - if err := jsonUnmarshalMap(spec.Storage.Config, &rawConfig); err != nil { - e.reportRestoreFailure(ctx, restoreRecordID, fmt.Sprintf("解析存储配置失败: %v", err)) - return err - } - } - provider, err := e.storageRegistry.Create(ctx, spec.Storage.Type, rawConfig) - if err != nil { - e.reportRestoreFailure(ctx, restoreRecordID, fmt.Sprintf("创建存储客户端失败: %v", err)) - return err - } - - // 2) 下载 + // 1) 下载 fileName := spec.FileName if strings.TrimSpace(fileName) == "" { fileName = filepath.Base(spec.StoragePath) } artifactPath := filepath.Join(tmpDir, filepath.Base(fileName)) e.appendRestoreLog(ctx, restoreRecordID, fmt.Sprintf("[agent] 下载备份文件 %s\n", spec.StoragePath)) - reader, err := provider.Download(ctx, spec.StoragePath) + var reader io.ReadCloser + if spec.Storage.TransferMode == storage.TransferModeMasterRelay { + reader, err = e.client.DownloadRestoreArtifact(ctx, restoreRecordID) + } else { + var rawConfig map[string]any + if len(spec.Storage.Config) > 0 { + if err := jsonUnmarshalMap(spec.Storage.Config, &rawConfig); err != nil { + e.reportRestoreFailure(ctx, restoreRecordID, fmt.Sprintf("解析存储配置失败: %v", err)) + return err + } + } + provider, providerErr := e.storageRegistry.Create(ctx, spec.Storage.Type, rawConfig) + if providerErr != nil { + e.reportRestoreFailure(ctx, restoreRecordID, fmt.Sprintf("创建存储客户端失败: %v", providerErr)) + return providerErr + } + reader, err = provider.Download(ctx, spec.StoragePath) + } if err != nil { e.reportRestoreFailure(ctx, restoreRecordID, fmt.Sprintf("下载备份失败: %v", err)) return err @@ -489,8 +503,10 @@ func buildRestoreBackupTaskSpec(spec *RestoreSpec, startedAt time.Time, tempDir } // writeReaderToLocal 把 reader 写到本地文件(Agent 侧工具函数)。 -func writeReaderToLocal(targetPath string, reader io.ReadCloser) error { - defer reader.Close() +func writeReaderToLocal(targetPath string, reader io.ReadCloser) (err error) { + defer func() { + err = errors.Join(err, reader.Close()) + }() if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { return err } @@ -498,9 +514,8 @@ func writeReaderToLocal(targetPath string, reader io.ReadCloser) error { if err != nil { return err } - defer file.Close() - _, err = io.Copy(file, reader) - return err + _, copyErr := io.Copy(file, reader) + return errors.Join(copyErr, file.Close()) } // 辅助函数 diff --git a/server/internal/agent/executor_test.go b/server/internal/agent/executor_test.go index d4f35ff..b197418 100644 --- a/server/internal/agent/executor_test.go +++ b/server/internal/agent/executor_test.go @@ -1,7 +1,10 @@ package agent import ( + "archive/tar" + "bytes" "context" + "crypto/sha256" "encoding/json" "fmt" "io" @@ -109,6 +112,150 @@ func TestExecuteRunTaskRecordsPerTargetUploadResults(t *testing.T) { } } +func TestExecuteRunTaskRelaysMasterLocalDiskTarget(t *testing.T) { + sourceDir := t.TempDir() + if err := os.WriteFile(filepath.Join(sourceDir, "index.html"), []byte("centralize me"), 0o644); err != nil { + t.Fatalf("WriteFile returned error: %v", err) + } + var relayed []byte + var finalUpdate RecordUpdate + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/api/agent/tasks/1": + writeAgentEnvelope(t, w, TaskSpec{ + TaskID: 1, + Name: "remote-source", + Type: "file", + SourcePath: sourceDir, + Compression: "gzip", + StorageTargets: []StorageTargetConfig{{ + ID: 11, Name: "master-disk", Type: storage.TypeLocalDisk, TransferMode: storage.TransferModeMasterRelay, + }}, + }) + case r.Method == http.MethodPut && r.URL.Path == "/api/agent/records/99/artifacts/11": + body, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("ReadAll relayed body: %v", err) + } + digest := sha256.Sum256(body) + if got := r.Header.Get("X-BackupX-SHA256"); got != fmt.Sprintf("%x", digest[:]) { + t.Fatalf("relay checksum header = %q", got) + } + objectKey := r.Header.Get("X-BackupX-Object-Key") + if !strings.Contains(objectKey, "/records/99/") || r.ContentLength != int64(len(body)) { + t.Fatalf("invalid relay metadata: key=%q length=%d body=%d", objectKey, r.ContentLength, len(body)) + } + relayed = append([]byte(nil), body...) + writeAgentEnvelope(t, w, map[string]string{"status": "ok"}) + case r.Method == http.MethodPost && r.URL.Path == "/api/agent/records/99": + var update RecordUpdate + if err := json.NewDecoder(r.Body).Decode(&update); err != nil { + t.Fatalf("Decode update returned error: %v", err) + } + if update.Status != "" { + finalUpdate = update + } + writeAgentEnvelope(t, w, map[string]string{"status": "ok"}) + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + executor := NewExecutor(NewMasterClient(server.URL, "token", false), filepath.Join(t.TempDir(), "tmp")) + if err := executor.ExecuteRunTask(context.Background(), 1, 99); err != nil { + t.Fatalf("ExecuteRunTask returned error: %v", err) + } + if len(relayed) == 0 { + t.Fatal("expected artifact bytes to be streamed through Master") + } + if finalUpdate.Status != "success" || finalUpdate.StorageTransferMode != storage.TransferModeMasterRelay { + t.Fatalf("unexpected final relay update: %#v", finalUpdate) + } + if len(finalUpdate.StorageUploadResults) != 1 || finalUpdate.StorageUploadResults[0].TransferMode != storage.TransferModeMasterRelay { + t.Fatalf("unexpected relay target result: %#v", finalUpdate.StorageUploadResults) + } +} + +func TestExecuteRestoreDownloadsMasterRelayedArtifact(t *testing.T) { + var archive bytes.Buffer + tarWriter := tar.NewWriter(&archive) + content := []byte("restored through Master") + header := &tar.Header{Name: "site/index.html", Mode: 0o644, Size: int64(len(content)), Typeflag: tar.TypeReg} + if err := tarWriter.WriteHeader(header); err != nil { + t.Fatalf("WriteHeader returned error: %v", err) + } + if _, err := tarWriter.Write(content); err != nil { + t.Fatalf("Write returned error: %v", err) + } + if err := tarWriter.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + artifact := append([]byte(nil), archive.Bytes()...) + digest := sha256.Sum256(artifact) + restoreRoot := t.TempDir() + restoreSource := filepath.Join(restoreRoot, "site") + artifactRequests := 0 + var finalUpdate RestoreUpdate + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/api/agent/restores/77/spec": + writeAgentEnvelope(t, w, RestoreSpec{ + RestoreRecordID: 77, + BackupRecordID: 99, + TaskID: 1, + TaskName: "remote-source", + Type: "file", + SourcePath: restoreSource, + Storage: StorageTargetConfig{ + ID: 11, Name: "master-disk", Type: storage.TypeLocalDisk, TransferMode: storage.TransferModeMasterRelay, + }, + StoragePath: "BackupX/file/site.tar", + FileName: "site.tar", + Checksum: fmt.Sprintf("%x", digest[:]), + }) + case r.Method == http.MethodGet && r.URL.Path == "/api/agent/restores/77/artifact": + artifactRequests++ + if r.Header.Get("X-Agent-Token") != "token" { + t.Fatalf("missing Agent token on relay download") + } + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(artifact))) + _, _ = w.Write(artifact) + case r.Method == http.MethodPost && r.URL.Path == "/api/agent/restores/77": + var update RestoreUpdate + if err := json.NewDecoder(r.Body).Decode(&update); err != nil { + t.Fatalf("Decode update returned error: %v", err) + } + if update.Status != "" { + finalUpdate = update + } + writeAgentEnvelope(t, w, map[string]string{"status": "ok"}) + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + executor := NewExecutor(NewMasterClient(server.URL, "token", false), filepath.Join(t.TempDir(), "tmp")) + if err := executor.ExecuteRestore(context.Background(), 77); err != nil { + t.Fatalf("ExecuteRestore returned error: %v", err) + } + if artifactRequests != 1 { + t.Fatalf("expected one relay artifact request, got %d", artifactRequests) + } + restored, err := os.ReadFile(filepath.Join(restoreRoot, "site", "index.html")) + if err != nil { + t.Fatalf("ReadFile returned error: %v", err) + } + if string(restored) != string(content) { + t.Fatalf("restored content = %q, want %q", restored, content) + } + if finalUpdate.Status != "success" { + t.Fatalf("unexpected final restore update: %#v", finalUpdate) + } +} + func TestExecuteRunTaskReportsPerTargetUploadResultsWhenAllTargetsFail(t *testing.T) { sourceDir := t.TempDir() if err := os.WriteFile(filepath.Join(sourceDir, "index.html"), []byte("hello"), 0o644); err != nil { diff --git a/server/internal/app/app.go b/server/internal/app/app.go index 88deb5f..2cfac10 100644 --- a/server/internal/app/app.go +++ b/server/internal/app/app.go @@ -84,7 +84,7 @@ func New(ctx context.Context, cfg config.Config, version string) (*Application, // nodeRepo 在下方 Cluster 节点管理区块才实例化,这里延后注入 backupRunnerRegistry := backup.NewRegistry(backup.NewFileRunner(), backup.NewSQLiteRunner(), backup.NewMySQLRunner(nil), backup.NewPostgreSQLRunner(nil), backup.NewSAPHANARunner(nil), backup.NewMongoDBRunner(nil)) logHub := backup.NewLogHub() - retentionService := backupretention.NewService(backupRecordRepo) + retentionService := backupretention.NewService(backupRecordRepo, configCipher.Key()) notifyRegistry := notify.NewRegistry(notify.NewEmailNotifier(), notify.NewWebhookNotifier(), notify.NewTelegramNotifier()) notificationService := service.NewNotificationService(notificationRepo, notifyRegistry, configCipher) authService.SetNotificationService(notificationService) @@ -135,7 +135,7 @@ func New(ctx context.Context, cfg config.Config, version string) (*Application, // Agent 协议服务:命令队列 + 任务下发 + 记录上报 agentCmdRepo := repository.NewAgentCommandRepository(db) nodeService.SetAgentCommandRepository(agentCmdRepo) - agentService := service.NewAgentService(nodeRepo, backupTaskRepo, backupRecordRepo, storageTargetRepo, agentCmdRepo, configCipher) + agentService := service.NewAgentService(nodeRepo, backupTaskRepo, backupRecordRepo, storageTargetRepo, agentCmdRepo, configCipher, storageRegistry) agentService.SetRestoreRepository(restoreRecordRepo) agentService.StartCommandTimeoutMonitor(ctx, 30*time.Second, 10*time.Minute) diff --git a/server/internal/backup/repository.go b/server/internal/backup/repository.go new file mode 100644 index 0000000..da0659c --- /dev/null +++ b/server/internal/backup/repository.go @@ -0,0 +1,1356 @@ +package backup + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" + + "backupx/server/internal/storage" + "github.com/klauspost/compress/zstd" +) + +const ( + repositoryFormatVersion = 1 + repositoryRoot = ".backupx/repository/v1" + repositoryIndexPrefix = repositoryRoot + "/indexes" + repositoryPackPrefix = repositoryRoot + "/packs" + repositorySnapshotRoot = repositoryRoot + "/snapshots" + repositoryDefaultPack = int64(32 << 20) + repositoryMaxIndexSize = int64(64 << 20) + repositoryMaxSnapshot = int64(256 << 20) + repositoryMaxEncoded = int64(repositoryChunkMax + (256 << 10)) +) + +type RepositoryStore struct { + key []byte + chunker *contentDefinedChunker + packSize int64 +} + +type RepositoryPlan struct { + tempDir string + spoolPath string + chunks map[string]repositoryPlanChunk + chunkOrder []string + snapshot repositorySnapshot + Manifest Manifest + LogicalSize int64 + UniqueSize int64 +} + +type repositoryPlanChunk struct { + Offset int64 + Size int64 +} + +type repositorySnapshot struct { + Version int `json:"version"` + TaskID uint `json:"taskId"` + CreatedAt time.Time `json:"createdAt"` + Compression string `json:"compression"` + Encrypted bool `json:"encrypted"` + SourcePaths []string `json:"sourcePaths"` + Entries []repositoryEntry `json:"entries"` +} + +type repositoryEntry struct { + Path string `json:"path"` + Kind string `json:"kind"` + Mode uint32 `json:"mode"` + ModTime int64 `json:"modTime"` + Size int64 `json:"size"` + LinkTarget string `json:"linkTarget,omitempty"` + Chunks []string `json:"chunks,omitempty"` +} + +type repositorySnapshotEnvelope struct { + Version int `json:"version"` + Encrypted bool `json:"encrypted"` + Data json.RawMessage `json:"data,omitempty"` + Ciphertext string `json:"ciphertext,omitempty"` +} + +type repositoryChunkLocation struct { + Pack string `json:"pack"` + Offset int64 `json:"offset"` + Length int64 `json:"length"` + PlainSize int64 `json:"plainSize"` + Compression string `json:"compression"` + Encrypted bool `json:"encrypted"` +} + +type repositoryIndexSegment struct { + Version int `json:"version"` + CreatedAt time.Time `json:"createdAt"` + Pack string `json:"pack"` + Chunks map[string]repositoryChunkLocation `json:"chunks"` +} + +type RepositoryUploadResult struct { + SnapshotKey string + SnapshotSize int64 + LogicalSize int64 + UploadedBytes int64 + ReusedBytes int64 + UniqueChunks int + NewChunks int + Checksum string +} + +type RepositoryVerifyResult struct { + Entries int + Chunks int + Bytes int64 +} + +type RepositoryPruneResult struct { + DeletedIndexes int + DeletedPacks int + ReclaimedBytes int64 +} + +func NewRepositoryStore(encryptionKey []byte) *RepositoryStore { + keyCopy := make([]byte, len(encryptionKey)) + copy(keyCopy, encryptionKey) + return &RepositoryStore{ + key: keyCopy, + chunker: newContentDefinedChunker(), + packSize: repositoryDefaultPack, + } +} + +func (s *RepositoryStore) SnapshotKey(taskID, recordID uint, startedAt time.Time) string { + stamp := startedAt.UTC().Format("20060102T150405.000000000Z") + return fmt.Sprintf("%s/%d/%s-%d.bxrs", repositorySnapshotRoot, taskID, stamp, recordID) +} + +func (p *RepositoryPlan) Close() error { + if p == nil || strings.TrimSpace(p.tempDir) == "" { + return nil + } + err := os.RemoveAll(p.tempDir) + p.tempDir = "" + p.spoolPath = "" + return err +} + +func (s *RepositoryStore) BuildPlan(ctx context.Context, task TaskSpec, writer LogWriter) (plan *RepositoryPlan, err error) { + if writer == nil { + writer = NopLogWriter{} + } + sourcePaths := compactPaths(task.SourcePaths) + if len(sourcePaths) == 0 && strings.TrimSpace(task.SourcePath) != "" { + sourcePaths = []string{filepath.Clean(strings.TrimSpace(task.SourcePath))} + } + if len(sourcePaths) == 0 { + return nil, fmt.Errorf("source path is required") + } + compression, err := s.normalizeCompression(task.Compression) + if err != nil { + return nil, err + } + if task.Encrypt && len(s.key) != 32 { + return nil, fmt.Errorf("repository encryption requires a 256-bit key") + } + if err := os.MkdirAll(task.TempDir, 0o755); err != nil { + return nil, fmt.Errorf("create repository temp root: %w", err) + } + tempDir, err := os.MkdirTemp(task.TempDir, "repository-plan-*") + if err != nil { + return nil, fmt.Errorf("create repository plan directory: %w", err) + } + defer func() { + if err == nil { + return + } + if cleanupErr := os.RemoveAll(tempDir); cleanupErr != nil { + err = errors.Join(err, fmt.Errorf("clean repository plan: %w", cleanupErr)) + } + }() + + spoolPath := filepath.Join(tempDir, "chunks.spool") + spool, err := os.Create(spoolPath) + if err != nil { + return nil, fmt.Errorf("create repository chunk spool: %w", err) + } + spoolClosed := false + defer func() { + if spoolClosed { + return + } + if closeErr := spool.Close(); closeErr != nil { + err = errors.Join(err, fmt.Errorf("close repository chunk spool: %w", closeErr)) + } + }() + + plan = &RepositoryPlan{ + tempDir: tempDir, + spoolPath: spoolPath, + chunks: make(map[string]repositoryPlanChunk), + snapshot: repositorySnapshot{ + Version: repositoryFormatVersion, + TaskID: task.ID, + CreatedAt: task.StartedAt.UTC(), + Compression: compression, + Encrypted: task.Encrypt, + SourcePaths: sourcePaths, + Entries: make([]repositoryEntry, 0), + }, + Manifest: Manifest{Entries: make([]ManifestEntry, 0)}, + } + excludes := normalizeExcludePatterns(task.ExcludePatterns) + seenPaths := make(map[string]struct{}) + spoolOffset := int64(0) + writer.WriteLine(fmt.Sprintf("CDC 仓库模式:FastCDC %d KiB/%d KiB/%d KiB,pack %d MiB", repositoryChunkMin>>10, repositoryChunkAvg>>10, repositoryChunkMax>>10, s.packSize>>20)) + + for sourceIndex, rawSource := range sourcePaths { + sourcePath := filepath.Clean(strings.TrimSpace(rawSource)) + if _, statErr := os.Lstat(sourcePath); statErr != nil { + return nil, fmt.Errorf("stat source path %s: %w", sourcePath, statErr) + } + baseParent := filepath.Dir(sourcePath) + writer.WriteLine(fmt.Sprintf("扫描源路径 [%d/%d]:%s", sourceIndex+1, len(sourcePaths), sourcePath)) + walkErr := filepath.Walk(sourcePath, func(currentPath string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + if ctxErr := ctx.Err(); ctxErr != nil { + return ctxErr + } + relative, relErr := filepath.Rel(baseParent, currentPath) + if relErr != nil { + return relErr + } + archiveName := path.Clean(filepath.ToSlash(relative)) + if archiveName == "." || strings.HasPrefix(archiveName, "../") { + return fmt.Errorf("invalid repository entry path %q", archiveName) + } + if shouldExcludeEntry(archiveName, info.IsDir(), excludes) { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + if _, duplicated := seenPaths[archiveName]; duplicated { + return fmt.Errorf("duplicate repository entry %q from overlapping source paths", archiveName) + } + seenPaths[archiveName] = struct{}{} + + entry := repositoryEntry{ + Path: archiveName, + Mode: uint32(info.Mode().Perm()), + ModTime: info.ModTime().UTC().UnixNano(), + Size: info.Size(), + } + manifestEntry := entryFromInfo(archiveName, info) + switch { + case info.IsDir(): + entry.Kind = "directory" + case info.Mode()&os.ModeSymlink != 0: + entry.Kind = "symlink" + linkTarget, linkErr := os.Readlink(currentPath) + if linkErr != nil { + return fmt.Errorf("read symlink %s: %w", currentPath, linkErr) + } + entry.LinkTarget = linkTarget + case info.Mode().IsRegular(): + entry.Kind = "file" + plan.LogicalSize += info.Size() + file, openErr := os.Open(currentPath) + if openErr != nil { + return fmt.Errorf("open source file %s: %w", currentPath, openErr) + } + splitErr := s.chunker.Split(ctx, file, func(raw []byte) error { + chunkID := s.chunkID(raw, compression, task.Encrypt) + entry.Chunks = append(entry.Chunks, chunkID) + if _, exists := plan.chunks[chunkID]; exists { + return nil + } + written, writeErr := spool.Write(raw) + if writeErr != nil { + return fmt.Errorf("write repository chunk spool: %w", writeErr) + } + if written != len(raw) { + return io.ErrShortWrite + } + plan.chunks[chunkID] = repositoryPlanChunk{Offset: spoolOffset, Size: int64(len(raw))} + plan.chunkOrder = append(plan.chunkOrder, chunkID) + plan.UniqueSize += int64(len(raw)) + spoolOffset += int64(len(raw)) + return nil + }) + closeErr := file.Close() + if splitErr != nil { + return errors.Join(fmt.Errorf("chunk source file %s: %w", currentPath, splitErr), closeErr) + } + if closeErr != nil { + return fmt.Errorf("close source file %s: %w", currentPath, closeErr) + } + default: + writer.WriteLine(fmt.Sprintf("跳过不支持的特殊文件:%s", currentPath)) + delete(seenPaths, archiveName) + return nil + } + plan.snapshot.Entries = append(plan.snapshot.Entries, entry) + plan.Manifest.Entries = append(plan.Manifest.Entries, manifestEntry) + return nil + }) + if walkErr != nil { + return nil, fmt.Errorf("scan source path %s: %w", sourcePath, walkErr) + } + } + if err := spool.Sync(); err != nil { + return nil, fmt.Errorf("sync repository chunk spool: %w", err) + } + if err := spool.Close(); err != nil { + return nil, fmt.Errorf("close repository chunk spool: %w", err) + } + spoolClosed = true + if err := s.validateSnapshot(&plan.snapshot); err != nil { + return nil, err + } + writer.WriteLine(fmt.Sprintf("CDC 扫描完成:%d 个条目,逻辑数据 %d bytes,任务内唯一数据 %d bytes", len(plan.snapshot.Entries), plan.LogicalSize, plan.UniqueSize)) + return plan, nil +} + +func (s *RepositoryStore) Upload(ctx context.Context, provider storage.StorageProvider, plan *RepositoryPlan, snapshotKey string) (*RepositoryUploadResult, error) { + if provider == nil || plan == nil || strings.TrimSpace(snapshotKey) == "" { + return nil, fmt.Errorf("repository provider, plan and snapshot key are required") + } + locations, err := s.loadIndex(ctx, provider) + if err != nil { + return nil, err + } + spool, err := os.Open(plan.spoolPath) + if err != nil { + return nil, fmt.Errorf("open repository chunk spool: %w", err) + } + uploadedBytes, newChunks, err := s.uploadMissingChunks(ctx, provider, plan, spool, locations) + closeErr := spool.Close() + if err != nil { + return nil, errors.Join(err, closeErr) + } + if closeErr != nil { + return nil, fmt.Errorf("close repository chunk spool: %w", closeErr) + } + + snapshotBytes, err := s.encodeSnapshot(plan.snapshot) + if err != nil { + return nil, err + } + if err := provider.Upload(ctx, snapshotKey, bytes.NewReader(snapshotBytes), int64(len(snapshotBytes)), map[string]string{"format": "backupx-repository-v1"}); err != nil { + return nil, fmt.Errorf("upload repository snapshot %s: %w", snapshotKey, err) + } + digest := sha256.Sum256(snapshotBytes) + reusedBytes := int64(0) + for _, chunkID := range plan.chunkOrder { + if _, wasNew := newChunks[chunkID]; !wasNew { + reusedBytes += plan.chunks[chunkID].Size + } + } + return &RepositoryUploadResult{ + SnapshotKey: snapshotKey, + SnapshotSize: int64(len(snapshotBytes)), + LogicalSize: plan.LogicalSize, + UploadedBytes: uploadedBytes + int64(len(snapshotBytes)), + ReusedBytes: reusedBytes, + UniqueChunks: len(plan.chunkOrder), + NewChunks: len(newChunks), + Checksum: hex.EncodeToString(digest[:]), + }, nil +} + +// EstimateUploadSize returns a target-aware soft-quota estimate. Existing +// chunks are excluded so a nearly full repository can still accept a snapshot +// that is almost entirely deduplicated. +func (s *RepositoryStore) EstimateUploadSize(ctx context.Context, provider storage.StorageProvider, plan *RepositoryPlan) (int64, error) { + if provider == nil || plan == nil { + return 0, fmt.Errorf("repository provider and plan are required") + } + locations, err := s.loadIndex(ctx, provider) + if err != nil { + return 0, err + } + snapshotBytes, err := s.encodeSnapshot(plan.snapshot) + if err != nil { + return 0, err + } + estimate := int64(len(snapshotBytes)) + for _, chunkID := range plan.chunkOrder { + if _, exists := locations[chunkID]; exists { + continue + } + // Compression generally lowers this value. The small allowance covers + // encryption tags and index metadata while keeping the check conservative. + estimate += plan.chunks[chunkID].Size + 256 + } + return estimate, nil +} + +func (s *RepositoryStore) Restore(ctx context.Context, provider storage.StorageProvider, snapshotKey, expectedChecksum string, task TaskSpec, writer LogWriter) (err error) { + if writer == nil { + writer = NopLogWriter{} + } + if strings.TrimSpace(expectedChecksum) == "" { + return fmt.Errorf("repository snapshot checksum is required for restore") + } + snapshot, _, err := s.loadSnapshot(ctx, provider, snapshotKey, expectedChecksum) + if err != nil { + return err + } + locations, err := s.loadIndex(ctx, provider) + if err != nil { + return err + } + restoreSource := strings.TrimSpace(task.SourcePath) + if len(task.SourcePaths) > 0 { + restoreSource = strings.TrimSpace(task.SourcePaths[0]) + } + targetRoot := strings.TrimSpace(task.RestoreTargetPath) + if targetRoot == "" { + if restoreSource == "" { + return fmt.Errorf("repository restore source path is required when no restore target is provided") + } + targetRoot = filepath.Dir(filepath.Clean(restoreSource)) + } else { + targetRoot = filepath.Clean(targetRoot) + } + if !filepath.IsAbs(targetRoot) { + return fmt.Errorf("repository restore target must be absolute: %s", targetRoot) + } + restoreRoot, err := s.openRestoreRoot(targetRoot) + if err != nil { + return fmt.Errorf("open repository restore root: %w", err) + } + defer func() { + err = errors.Join(err, restoreRoot.Close()) + }() + + restored := 0 + directories := make([]repositoryEntry, 0) + for _, entry := range snapshot.Entries { + if err := ctx.Err(); err != nil { + return err + } + if len(task.SelectedPaths) > 0 && !pathSelected(entry.Path, task.SelectedPaths) { + continue + } + entryPath, localizeErr := filepath.Localize(entry.Path) + if localizeErr != nil || !filepath.IsLocal(entryPath) { + return fmt.Errorf("unsafe repository restore path %q", entry.Path) + } + if parent := filepath.Dir(entryPath); parent != "." { + if err := restoreRoot.MkdirAll(parent, 0o755); err != nil { + return fmt.Errorf("create restore parent for %s: %w", entry.Path, err) + } + } + switch entry.Kind { + case "directory": + if info, statErr := restoreRoot.Lstat(entryPath); statErr == nil { + if info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("restore directory crosses existing symlink: %s", entry.Path) + } + } else if !errors.Is(statErr, os.ErrNotExist) { + return fmt.Errorf("inspect restore directory %s: %w", entry.Path, statErr) + } + if err := restoreRoot.MkdirAll(entryPath, os.FileMode(entry.Mode)); err != nil { + return fmt.Errorf("create restore directory %s: %w", entry.Path, err) + } + directories = append(directories, entry) + case "symlink": + resolvedLinkTarget := path.Clean(path.Join(path.Dir(entry.Path), strings.ReplaceAll(entry.LinkTarget, "\\", "/"))) + localizedLinkTarget, localizeTargetErr := filepath.Localize(resolvedLinkTarget) + if localizeTargetErr != nil || !filepath.IsLocal(localizedLinkTarget) { + return fmt.Errorf("unsafe repository symlink target %q", entry.LinkTarget) + } + linkTarget, relativeTargetErr := filepath.Rel(filepath.Dir(entryPath), localizedLinkTarget) + if relativeTargetErr != nil { + return fmt.Errorf("resolve restore symlink target %s: %w", entry.Path, relativeTargetErr) + } + if info, statErr := restoreRoot.Lstat(entryPath); statErr == nil { + if info.IsDir() { + return fmt.Errorf("refuse to replace restore directory with symlink: %s", entry.Path) + } + if err := restoreRoot.Remove(entryPath); err != nil { + return fmt.Errorf("replace restore symlink %s: %w", entry.Path, err) + } + } else if !errors.Is(statErr, os.ErrNotExist) { + return fmt.Errorf("inspect restore symlink %s: %w", entry.Path, statErr) + } + if err := restoreRoot.Symlink(linkTarget, entryPath); err != nil { + return fmt.Errorf("create restore symlink %s: %w", entry.Path, err) + } + case "file": + if info, statErr := restoreRoot.Lstat(entryPath); statErr == nil { + if info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("restore file would overwrite existing symlink: %s", entry.Path) + } + } else if !errors.Is(statErr, os.ErrNotExist) { + return fmt.Errorf("inspect restore file %s: %w", entry.Path, statErr) + } + file, openErr := restoreRoot.OpenFile(entryPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(entry.Mode)) + if openErr != nil { + return fmt.Errorf("create restore file %s: %w", entry.Path, openErr) + } + var written int64 + for _, chunkID := range entry.Chunks { + raw, readErr := s.readChunk(ctx, provider, chunkID, locations) + if readErr != nil { + return errors.Join(fmt.Errorf("restore %s: %w", entry.Path, readErr), file.Close()) + } + count, writeErr := file.Write(raw) + written += int64(count) + if writeErr != nil { + return errors.Join(fmt.Errorf("write restore file %s: %w", entry.Path, writeErr), file.Close()) + } + if count != len(raw) { + return errors.Join(io.ErrShortWrite, file.Close()) + } + } + if written != entry.Size { + return errors.Join(fmt.Errorf("restored size mismatch for %s: expected %d, got %d", entry.Path, entry.Size, written), file.Close()) + } + if err := file.Chmod(os.FileMode(entry.Mode)); err != nil { + return errors.Join(fmt.Errorf("restore mode for %s: %w", entry.Path, err), file.Close()) + } + if closeErr := file.Close(); closeErr != nil { + return fmt.Errorf("close restore file %s: %w", entry.Path, closeErr) + } + modTime := time.Unix(0, entry.ModTime) + if err := restoreRoot.Chtimes(entryPath, modTime, modTime); err != nil { + return fmt.Errorf("restore timestamp for %s: %w", entry.Path, err) + } + default: + return fmt.Errorf("unsupported repository entry kind %q", entry.Kind) + } + restored++ + } + // Children modify their parent directory timestamps, so directory metadata + // is restored only after every selected file has been written. + for index := len(directories) - 1; index >= 0; index-- { + entry := directories[index] + entryPath, localizeErr := filepath.Localize(entry.Path) + if localizeErr != nil || !filepath.IsLocal(entryPath) { + return fmt.Errorf("unsafe repository restore path %q", entry.Path) + } + if err := restoreRoot.Chmod(entryPath, os.FileMode(entry.Mode)); err != nil { + return fmt.Errorf("restore mode for %s: %w", entry.Path, err) + } + modTime := time.Unix(0, entry.ModTime) + if err := restoreRoot.Chtimes(entryPath, modTime, modTime); err != nil { + return fmt.Errorf("restore timestamp for %s: %w", entry.Path, err) + } + } + writer.WriteLine(fmt.Sprintf("CDC 仓库恢复完成:%d 个条目", restored)) + return nil +} + +func (s *RepositoryStore) ExportTar(ctx context.Context, provider storage.StorageProvider, snapshotKey, expectedChecksum, destination string) (err error) { + if strings.TrimSpace(expectedChecksum) == "" { + return fmt.Errorf("repository snapshot checksum is required for export") + } + snapshot, _, err := s.loadSnapshot(ctx, provider, snapshotKey, expectedChecksum) + if err != nil { + return err + } + locations, err := s.loadIndex(ctx, provider) + if err != nil { + return err + } + file, err := os.Create(destination) + if err != nil { + return fmt.Errorf("create repository export: %w", err) + } + tw := tar.NewWriter(file) + tarClosed := false + fileClosed := false + defer func() { + if !tarClosed { + err = errors.Join(err, tw.Close()) + } + if !fileClosed { + err = errors.Join(err, file.Close()) + } + }() + for _, entry := range snapshot.Entries { + if err := ctx.Err(); err != nil { + return err + } + header := &tar.Header{ + Name: entry.Path, + Mode: int64(entry.Mode), + ModTime: time.Unix(0, entry.ModTime), + Size: entry.Size, + } + switch entry.Kind { + case "directory": + header.Typeflag = tar.TypeDir + header.Size = 0 + header.Name = strings.TrimSuffix(entry.Path, "/") + "/" + case "symlink": + header.Typeflag = tar.TypeSymlink + header.Size = 0 + header.Linkname = entry.LinkTarget + case "file": + header.Typeflag = tar.TypeReg + default: + return fmt.Errorf("unsupported repository entry kind %q", entry.Kind) + } + if err := tw.WriteHeader(header); err != nil { + return fmt.Errorf("write repository tar header: %w", err) + } + if entry.Kind == "file" { + for _, chunkID := range entry.Chunks { + raw, readErr := s.readChunk(ctx, provider, chunkID, locations) + if readErr != nil { + return readErr + } + if _, writeErr := tw.Write(raw); writeErr != nil { + return fmt.Errorf("write repository tar data: %w", writeErr) + } + } + } + } + closeErr := tw.Close() + tarClosed = true + if closeErr != nil { + return fmt.Errorf("close repository tar: %w", closeErr) + } + closeErr = file.Close() + fileClosed = true + if closeErr != nil { + return fmt.Errorf("close repository export: %w", closeErr) + } + return nil +} + +func (s *RepositoryStore) Verify(ctx context.Context, provider storage.StorageProvider, snapshotKey, expectedChecksum string) (*RepositoryVerifyResult, error) { + if strings.TrimSpace(expectedChecksum) == "" { + return nil, fmt.Errorf("repository snapshot checksum is required for verification") + } + snapshot, _, err := s.loadSnapshot(ctx, provider, snapshotKey, expectedChecksum) + if err != nil { + return nil, err + } + locations, err := s.loadIndex(ctx, provider) + if err != nil { + return nil, err + } + seen := make(map[string]struct{}) + result := &RepositoryVerifyResult{Entries: len(snapshot.Entries)} + for _, entry := range snapshot.Entries { + for _, chunkID := range entry.Chunks { + if _, checked := seen[chunkID]; checked { + continue + } + raw, readErr := s.readChunk(ctx, provider, chunkID, locations) + if readErr != nil { + return nil, readErr + } + seen[chunkID] = struct{}{} + result.Chunks++ + result.Bytes += int64(len(raw)) + } + } + return result, nil +} + +func (s *RepositoryStore) Prune(ctx context.Context, provider storage.StorageProvider) (*RepositoryPruneResult, error) { + if provider == nil { + return nil, fmt.Errorf("repository provider is required") + } + liveChunks := make(map[string]struct{}) + snapshots, err := provider.List(ctx, repositorySnapshotRoot) + if err != nil { + return nil, fmt.Errorf("list repository snapshots: %w", err) + } + sort.Slice(snapshots, func(i, j int) bool { return snapshots[i].Key < snapshots[j].Key }) + for _, object := range snapshots { + snapshot, _, loadErr := s.loadSnapshot(ctx, provider, object.Key, "") + if loadErr != nil { + return nil, fmt.Errorf("refuse to prune with unreadable snapshot %s: %w", object.Key, loadErr) + } + for _, entry := range snapshot.Entries { + for _, chunkID := range entry.Chunks { + liveChunks[chunkID] = struct{}{} + } + } + } + + indexObjects, err := provider.List(ctx, repositoryIndexPrefix) + if err != nil { + return nil, fmt.Errorf("list repository indexes: %w", err) + } + packSizes := make(map[string]int64) + packObjects, err := provider.List(ctx, repositoryPackPrefix) + if err != nil { + return nil, fmt.Errorf("list repository packs: %w", err) + } + for _, object := range packObjects { + packSizes[object.Key] = object.Size + } + keptPacks := make(map[string]struct{}) + result := &RepositoryPruneResult{} + for _, object := range indexObjects { + segment, readErr := s.readIndexSegment(ctx, provider, object.Key) + if readErr != nil { + return nil, fmt.Errorf("refuse to prune with unreadable index %s: %w", object.Key, readErr) + } + keep := false + for chunkID := range segment.Chunks { + if _, live := liveChunks[chunkID]; live { + keep = true + break + } + } + if keep { + keptPacks[segment.Pack] = struct{}{} + continue + } + if err := provider.Delete(ctx, object.Key); err != nil { + return nil, fmt.Errorf("delete unused repository index %s: %w", object.Key, err) + } + result.DeletedIndexes++ + if err := provider.Delete(ctx, segment.Pack); err != nil { + return nil, fmt.Errorf("delete unused repository pack %s: %w", segment.Pack, err) + } + result.DeletedPacks++ + result.ReclaimedBytes += packSizes[segment.Pack] + delete(packSizes, segment.Pack) + } + for packKey, size := range packSizes { + if _, keep := keptPacks[packKey]; keep { + continue + } + if err := provider.Delete(ctx, packKey); err != nil { + return nil, fmt.Errorf("delete orphaned repository pack %s: %w", packKey, err) + } + result.DeletedPacks++ + result.ReclaimedBytes += size + } + return result, nil +} + +func (s *RepositoryStore) uploadMissingChunks(ctx context.Context, provider storage.StorageProvider, plan *RepositoryPlan, spool *os.File, locations map[string]repositoryChunkLocation) (uploadedBytes int64, newChunkIDs map[string]struct{}, err error) { + newChunkIDs = make(map[string]struct{}) + packPath := "" + var packFile *os.File + packSize := int64(0) + packChunks := make(map[string]repositoryChunkLocation) + + closeAndRemove := func() error { + var cleanupErr error + if packFile != nil { + cleanupErr = packFile.Close() + packFile = nil + } + if packPath != "" { + cleanupErr = errors.Join(cleanupErr, os.Remove(packPath)) + packPath = "" + } + return cleanupErr + } + flush := func() error { + if packFile == nil || len(packChunks) == 0 { + return nil + } + if err := packFile.Sync(); err != nil { + return fmt.Errorf("sync repository pack: %w", err) + } + if err := packFile.Close(); err != nil { + return fmt.Errorf("close repository pack: %w", err) + } + packFile = nil + packKey, indexBytes, packBytes, err := s.uploadPack(ctx, provider, packPath, packChunks) + if err != nil { + return err + } + for chunkID, location := range packChunks { + location.Pack = packKey + locations[chunkID] = location + newChunkIDs[chunkID] = struct{}{} + } + uploadedBytes += packBytes + indexBytes + if err := os.Remove(packPath); err != nil { + return fmt.Errorf("remove temporary repository pack: %w", err) + } + packPath = "" + packSize = 0 + packChunks = make(map[string]repositoryChunkLocation) + return nil + } + defer func() { + if cleanupErr := closeAndRemove(); cleanupErr != nil { + err = errors.Join(err, fmt.Errorf("clean temporary repository pack: %w", cleanupErr)) + } + }() + + for _, chunkID := range plan.chunkOrder { + if _, exists := locations[chunkID]; exists { + continue + } + planChunk := plan.chunks[chunkID] + raw := make([]byte, planChunk.Size) + readCount, readErr := spool.ReadAt(raw, planChunk.Offset) + if readErr != nil && readErr != io.EOF { + return 0, nil, fmt.Errorf("read repository chunk spool: %w", readErr) + } + if int64(readCount) != planChunk.Size { + return 0, nil, io.ErrUnexpectedEOF + } + encoded, encodeErr := s.encodeChunk(raw, plan.snapshot.Compression, plan.snapshot.Encrypted, chunkID) + if encodeErr != nil { + return 0, nil, encodeErr + } + if packFile != nil && packSize > 0 && packSize+int64(len(encoded)) > s.packSize { + if err := flush(); err != nil { + return 0, nil, err + } + } + if packFile == nil { + created, createErr := os.CreateTemp(plan.tempDir, "repository-pack-*") + if createErr != nil { + return 0, nil, fmt.Errorf("create temporary repository pack: %w", createErr) + } + packFile = created + packPath = created.Name() + } + written, writeErr := packFile.Write(encoded) + if writeErr != nil { + return 0, nil, fmt.Errorf("write repository pack: %w", writeErr) + } + if written != len(encoded) { + return 0, nil, io.ErrShortWrite + } + packChunks[chunkID] = repositoryChunkLocation{ + Offset: packSize, + Length: int64(len(encoded)), + PlainSize: planChunk.Size, + Compression: plan.snapshot.Compression, + Encrypted: plan.snapshot.Encrypted, + } + packSize += int64(len(encoded)) + } + if err := flush(); err != nil { + return 0, nil, err + } + return uploadedBytes, newChunkIDs, nil +} + +func (s *RepositoryStore) uploadPack(ctx context.Context, provider storage.StorageProvider, packPath string, chunks map[string]repositoryChunkLocation) (packKey string, indexSize int64, packSize int64, err error) { + pack, err := os.Open(packPath) + if err != nil { + return "", 0, 0, fmt.Errorf("open repository pack: %w", err) + } + packClosed := false + defer func() { + if !packClosed { + err = errors.Join(err, pack.Close()) + } + }() + hasher := sha256.New() + packBytes, err := io.Copy(hasher, pack) + if err != nil { + return "", 0, 0, fmt.Errorf("hash repository pack: %w", err) + } + packID := hex.EncodeToString(hasher.Sum(nil)) + packKey = fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, packID[:2], packID) + for chunkID, location := range chunks { + location.Pack = packKey + chunks[chunkID] = location + } + segment := repositoryIndexSegment{Version: repositoryFormatVersion, CreatedAt: time.Now().UTC(), Pack: packKey, Chunks: chunks} + indexBytes, err := json.Marshal(segment) + if err != nil { + return "", 0, 0, fmt.Errorf("encode repository index: %w", err) + } + if _, err := pack.Seek(0, io.SeekStart); err != nil { + return "", 0, 0, fmt.Errorf("rewind repository pack: %w", err) + } + if err := provider.Upload(ctx, packKey, pack, packBytes, map[string]string{"format": "backupx-pack-v1"}); err != nil { + return "", 0, 0, fmt.Errorf("upload repository pack %s: %w", packKey, err) + } + closeErr := pack.Close() + packClosed = true + if closeErr != nil { + return "", 0, 0, fmt.Errorf("close repository pack: %w", closeErr) + } + indexKey := fmt.Sprintf("%s/%s.json", repositoryIndexPrefix, packID) + if err := provider.Upload(ctx, indexKey, bytes.NewReader(indexBytes), int64(len(indexBytes)), map[string]string{"format": "backupx-index-v1"}); err != nil { + deleteErr := provider.Delete(ctx, packKey) + return "", 0, 0, errors.Join(fmt.Errorf("upload repository index %s: %w", indexKey, err), deleteErr) + } + return packKey, int64(len(indexBytes)), packBytes, nil +} + +func (s *RepositoryStore) loadIndex(ctx context.Context, provider storage.StorageProvider) (map[string]repositoryChunkLocation, error) { + objects, err := provider.List(ctx, repositoryIndexPrefix) + if err != nil { + return nil, fmt.Errorf("list repository indexes: %w", err) + } + sort.Slice(objects, func(i, j int) bool { return objects[i].Key < objects[j].Key }) + locations := make(map[string]repositoryChunkLocation) + for _, object := range objects { + segment, readErr := s.readIndexSegment(ctx, provider, object.Key) + if readErr != nil { + return nil, readErr + } + for chunkID, location := range segment.Chunks { + if _, exists := locations[chunkID]; !exists { + locations[chunkID] = location + } + } + } + return locations, nil +} + +func (s *RepositoryStore) readIndexSegment(ctx context.Context, provider storage.StorageProvider, key string) (*repositoryIndexSegment, error) { + indexPrefix := repositoryIndexPrefix + "/" + indexName := strings.TrimPrefix(key, indexPrefix) + indexID := strings.TrimSuffix(indexName, ".json") + decodedIndexID, decodeIndexErr := hex.DecodeString(indexID) + if indexName == key || indexID == indexName || indexID != strings.ToLower(indexID) || strings.Contains(indexName, "/") || strings.Contains(indexName, "\\") || decodeIndexErr != nil || len(decodedIndexID) != sha256.Size { + return nil, fmt.Errorf("invalid repository index key %q", key) + } + expectedPack := fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, indexID[:2], indexID) + reader, err := provider.Download(ctx, key) + if err != nil { + return nil, fmt.Errorf("download repository index %s: %w", key, err) + } + data, readErr := io.ReadAll(io.LimitReader(reader, repositoryMaxIndexSize+1)) + closeErr := reader.Close() + if readErr != nil { + return nil, fmt.Errorf("read repository index %s: %w", key, readErr) + } + if closeErr != nil { + return nil, fmt.Errorf("close repository index %s: %w", key, closeErr) + } + if int64(len(data)) > repositoryMaxIndexSize { + return nil, fmt.Errorf("repository index %s exceeds %d bytes", key, repositoryMaxIndexSize) + } + var segment repositoryIndexSegment + if err := json.Unmarshal(data, &segment); err != nil { + return nil, fmt.Errorf("decode repository index %s: %w", key, err) + } + if segment.Version != repositoryFormatVersion || segment.Pack != expectedPack || len(segment.Chunks) == 0 { + return nil, fmt.Errorf("unsupported repository index %s", key) + } + packLimit := s.packSize + if packLimit <= 0 { + packLimit = repositoryDefaultPack + } + if packLimit < repositoryMaxEncoded { + packLimit = repositoryMaxEncoded + } + for chunkID, location := range segment.Chunks { + expectedPrefix := "p-" + if location.Encrypted { + expectedPrefix = "e-" + } + encodedID := strings.TrimPrefix(chunkID, expectedPrefix) + decodedID, decodeErr := hex.DecodeString(encodedID) + validPrefix := encodedID != chunkID + validCompression := location.Compression == "none" || location.Compression == "gzip" || location.Compression == "zstd" + if len(decodedID) != sha256.Size || decodeErr != nil || encodedID != strings.ToLower(encodedID) || !validPrefix || !validCompression || location.Pack != expectedPack || location.Offset < 0 || location.Length <= 0 || location.Length > repositoryMaxEncoded || location.Length > packLimit || location.PlainSize <= 0 || location.PlainSize > repositoryChunkMax || location.Offset > packLimit-location.Length { + return nil, fmt.Errorf("invalid chunk location in repository index %s", key) + } + } + return &segment, nil +} + +func (s *RepositoryStore) loadSnapshot(ctx context.Context, provider storage.StorageProvider, key, expectedChecksum string) (*repositorySnapshot, []byte, error) { + reader, err := provider.Download(ctx, key) + if err != nil { + return nil, nil, fmt.Errorf("download repository snapshot %s: %w", key, err) + } + data, readErr := io.ReadAll(io.LimitReader(reader, repositoryMaxSnapshot+1)) + closeErr := reader.Close() + if readErr != nil { + return nil, nil, fmt.Errorf("read repository snapshot %s: %w", key, readErr) + } + if closeErr != nil { + return nil, nil, fmt.Errorf("close repository snapshot %s: %w", key, closeErr) + } + if int64(len(data)) > repositoryMaxSnapshot { + return nil, nil, fmt.Errorf("repository snapshot %s exceeds %d bytes", key, repositoryMaxSnapshot) + } + if expected := strings.TrimSpace(expectedChecksum); expected != "" { + expectedBytes, decodeErr := hex.DecodeString(expected) + if decodeErr != nil || len(expectedBytes) != sha256.Size { + return nil, nil, fmt.Errorf("repository snapshot checksum is invalid") + } + digest := sha256.Sum256(data) + if !strings.EqualFold(hex.EncodeToString(digest[:]), expected) { + return nil, nil, fmt.Errorf("repository snapshot checksum mismatch") + } + } + var envelope repositorySnapshotEnvelope + if err := json.Unmarshal(data, &envelope); err != nil { + return nil, nil, fmt.Errorf("decode repository snapshot envelope %s: %w", key, err) + } + if envelope.Version != repositoryFormatVersion { + return nil, nil, fmt.Errorf("unsupported repository snapshot version %d", envelope.Version) + } + payload := []byte(envelope.Data) + if envelope.Encrypted { + ciphertext, decodeErr := base64.RawURLEncoding.DecodeString(envelope.Ciphertext) + if decodeErr != nil { + return nil, nil, fmt.Errorf("decode repository snapshot ciphertext: %w", decodeErr) + } + payload, err = s.decrypt(ciphertext, []byte("backupx-repository-snapshot-v1")) + if err != nil { + return nil, nil, fmt.Errorf("decrypt repository snapshot: %w", err) + } + } + var snapshot repositorySnapshot + if err := json.Unmarshal(payload, &snapshot); err != nil { + return nil, nil, fmt.Errorf("decode repository snapshot: %w", err) + } + if snapshot.Version != repositoryFormatVersion { + return nil, nil, fmt.Errorf("unsupported repository snapshot payload version %d", snapshot.Version) + } + if snapshot.Encrypted != envelope.Encrypted { + return nil, nil, fmt.Errorf("repository snapshot encryption metadata mismatch") + } + if err := s.validateSnapshot(&snapshot); err != nil { + return nil, nil, err + } + return &snapshot, data, nil +} + +func (*RepositoryStore) validateSnapshot(snapshot *repositorySnapshot) error { + if snapshot == nil { + return fmt.Errorf("repository snapshot is required") + } + if snapshot.Version != repositoryFormatVersion { + return fmt.Errorf("unsupported repository snapshot payload version %d", snapshot.Version) + } + if snapshot.Compression != "none" && snapshot.Compression != "gzip" && snapshot.Compression != "zstd" { + return fmt.Errorf("unsupported repository snapshot compression %q", snapshot.Compression) + } + seenPaths := make(map[string]struct{}, len(snapshot.Entries)) + symlinkPaths := make(map[string]struct{}) + for _, entry := range snapshot.Entries { + if entry.Path == "." || !fs.ValidPath(entry.Path) || path.Clean(entry.Path) != entry.Path || strings.Contains(entry.Path, "\\") || entry.Mode & ^uint32(0o777) != 0 || entry.Size < 0 { + return fmt.Errorf("invalid repository snapshot entry %q", entry.Path) + } + if _, exists := seenPaths[entry.Path]; exists { + return fmt.Errorf("duplicate repository snapshot entry %q", entry.Path) + } + seenPaths[entry.Path] = struct{}{} + switch entry.Kind { + case "directory": + if len(entry.Chunks) != 0 || entry.LinkTarget != "" { + return fmt.Errorf("invalid repository directory entry %q", entry.Path) + } + case "symlink": + if entry.LinkTarget == "" || len(entry.Chunks) != 0 { + return fmt.Errorf("invalid repository symlink entry %q", entry.Path) + } + normalizedTarget := strings.ReplaceAll(entry.LinkTarget, "\\", "/") + resolvedTarget := path.Clean(path.Join(path.Dir(entry.Path), normalizedTarget)) + if strings.ContainsRune(entry.LinkTarget, 0) || path.IsAbs(normalizedTarget) || filepath.IsAbs(entry.LinkTarget) || filepath.VolumeName(entry.LinkTarget) != "" || resolvedTarget == ".." || strings.HasPrefix(resolvedTarget, "../") { + return fmt.Errorf("repository symlink %q escapes the restore root", entry.Path) + } + symlinkPaths[entry.Path] = struct{}{} + case "file": + if entry.LinkTarget != "" || (entry.Size == 0 && len(entry.Chunks) != 0) || (entry.Size > 0 && len(entry.Chunks) == 0) { + return fmt.Errorf("invalid repository file entry %q", entry.Path) + } + expectedPrefix := "p-" + if snapshot.Encrypted { + expectedPrefix = "e-" + } + for _, chunkID := range entry.Chunks { + encodedID := strings.TrimPrefix(chunkID, expectedPrefix) + decodedID, decodeErr := hex.DecodeString(encodedID) + if decodeErr != nil || len(decodedID) != sha256.Size || encodedID == chunkID || encodedID != strings.ToLower(encodedID) { + return fmt.Errorf("invalid chunk id in repository entry %q", entry.Path) + } + } + default: + return fmt.Errorf("unsupported repository entry kind %q", entry.Kind) + } + } + for entryPath := range seenPaths { + for parent := path.Dir(entryPath); parent != "."; parent = path.Dir(parent) { + if _, crossesSymlink := symlinkPaths[parent]; crossesSymlink { + return fmt.Errorf("repository entry %q crosses symlink %q", entryPath, parent) + } + } + } + return nil +} + +func (s *RepositoryStore) encodeSnapshot(snapshot repositorySnapshot) ([]byte, error) { + payload, err := json.Marshal(snapshot) + if err != nil { + return nil, fmt.Errorf("encode repository snapshot: %w", err) + } + envelope := repositorySnapshotEnvelope{Version: repositoryFormatVersion, Encrypted: snapshot.Encrypted} + if snapshot.Encrypted { + ciphertext, encryptErr := s.encrypt(payload, []byte("backupx-repository-snapshot-v1")) + if encryptErr != nil { + return nil, fmt.Errorf("encrypt repository snapshot: %w", encryptErr) + } + envelope.Ciphertext = base64.RawURLEncoding.EncodeToString(ciphertext) + } else { + envelope.Data = payload + } + data, err := json.Marshal(envelope) + if err != nil { + return nil, fmt.Errorf("encode repository snapshot envelope: %w", err) + } + return data, nil +} + +func (s *RepositoryStore) readChunk(ctx context.Context, provider storage.StorageProvider, chunkID string, locations map[string]repositoryChunkLocation) ([]byte, error) { + location, exists := locations[chunkID] + if !exists { + return nil, fmt.Errorf("repository chunk %s is missing from the index", chunkID) + } + var reader io.ReadCloser + var err error + if ranged, ok := provider.(storage.StorageRangeDownloader); ok { + reader, err = ranged.DownloadRange(ctx, location.Pack, location.Offset, location.Length) + } else { + reader, err = provider.Download(ctx, location.Pack) + if err == nil && location.Offset > 0 { + if _, copyErr := io.CopyN(io.Discard, reader, location.Offset); copyErr != nil { + return nil, errors.Join(fmt.Errorf("seek repository pack %s: %w", location.Pack, copyErr), reader.Close()) + } + } + } + if err != nil { + return nil, fmt.Errorf("read repository pack %s: %w", location.Pack, err) + } + encoded := make([]byte, int(location.Length)) + _, readErr := io.ReadFull(reader, encoded) + closeErr := reader.Close() + if readErr != nil { + return nil, fmt.Errorf("read repository chunk %s: %w", chunkID, readErr) + } + if closeErr != nil { + return nil, fmt.Errorf("close repository pack %s: %w", location.Pack, closeErr) + } + raw, err := s.decodeChunk(encoded, location, chunkID) + if err != nil { + return nil, err + } + if int64(len(raw)) != location.PlainSize { + return nil, fmt.Errorf("repository chunk %s size mismatch", chunkID) + } + if actual := s.chunkID(raw, location.Compression, location.Encrypted); actual != chunkID { + return nil, fmt.Errorf("repository chunk %s failed content verification", chunkID) + } + return raw, nil +} + +func (s *RepositoryStore) encodeChunk(raw []byte, compression string, encrypted bool, chunkID string) ([]byte, error) { + var encoded []byte + switch compression { + case "none": + encoded = append([]byte(nil), raw...) + case "gzip": + var buffer bytes.Buffer + writer := gzip.NewWriter(&buffer) + if _, err := writer.Write(raw); err != nil { + return nil, errors.Join(fmt.Errorf("gzip repository chunk: %w", err), writer.Close()) + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close repository gzip chunk: %w", err) + } + encoded = buffer.Bytes() + case "zstd": + writer, err := zstd.NewWriter(nil) + if err != nil { + return nil, fmt.Errorf("create repository zstd encoder: %w", err) + } + encoded = writer.EncodeAll(raw, nil) + writer.Close() + default: + return nil, fmt.Errorf("unsupported repository compression %q", compression) + } + if !encrypted { + return encoded, nil + } + ciphertext, err := s.encrypt(encoded, []byte(chunkID)) + if err != nil { + return nil, fmt.Errorf("encrypt repository chunk: %w", err) + } + return ciphertext, nil +} + +func (s *RepositoryStore) decodeChunk(encoded []byte, location repositoryChunkLocation, chunkID string) ([]byte, error) { + payload := encoded + var err error + if location.Encrypted { + payload, err = s.decrypt(encoded, []byte(chunkID)) + if err != nil { + return nil, fmt.Errorf("decrypt repository chunk %s: %w", chunkID, err) + } + } + switch location.Compression { + case "none": + return payload, nil + case "gzip": + reader, err := gzip.NewReader(bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("open repository gzip chunk: %w", err) + } + raw, readErr := io.ReadAll(io.LimitReader(reader, location.PlainSize+1)) + closeErr := reader.Close() + if readErr != nil { + return nil, fmt.Errorf("decompress repository gzip chunk: %w", readErr) + } + if closeErr != nil { + return nil, fmt.Errorf("close repository gzip chunk: %w", closeErr) + } + return raw, nil + case "zstd": + reader, err := zstd.NewReader(bytes.NewReader(payload), zstd.WithDecoderMaxMemory(uint64(repositoryChunkMax+(1<<20)))) + if err != nil { + return nil, fmt.Errorf("create repository zstd decoder: %w", err) + } + raw, readErr := io.ReadAll(io.LimitReader(reader, location.PlainSize+1)) + reader.Close() + if readErr != nil { + return nil, fmt.Errorf("decompress repository zstd chunk: %w", readErr) + } + return raw, nil + default: + return nil, fmt.Errorf("unsupported repository compression %q", location.Compression) + } +} + +func (s *RepositoryStore) chunkID(raw []byte, compression string, encrypted bool) string { + domain := fmt.Sprintf("backupx-repository-v1/%s/plain\x00", compression) + prefix := "p-" + if encrypted { + domain = fmt.Sprintf("backupx-repository-v1/%s/encrypted\x00", compression) + mac := hmac.New(sha256.New, s.key) + mac.Write([]byte(domain)) + mac.Write(raw) + return "e-" + hex.EncodeToString(mac.Sum(nil)) + } + digest := sha256.New() + digest.Write([]byte(domain)) + digest.Write(raw) + return prefix + hex.EncodeToString(digest.Sum(nil)) +} + +func (s *RepositoryStore) encrypt(plain, additionalData []byte) ([]byte, error) { + if len(s.key) != 32 { + return nil, fmt.Errorf("repository encryption key is unavailable") + } + block, err := aes.NewCipher(s.key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + return gcm.Seal(nonce, nonce, plain, additionalData), nil +} + +func (s *RepositoryStore) decrypt(ciphertext, additionalData []byte) ([]byte, error) { + if len(s.key) != 32 { + return nil, fmt.Errorf("repository encryption key is unavailable") + } + block, err := aes.NewCipher(s.key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + if len(ciphertext) < gcm.NonceSize() { + return nil, fmt.Errorf("repository ciphertext is too short") + } + nonce := ciphertext[:gcm.NonceSize()] + return gcm.Open(nil, nonce, ciphertext[gcm.NonceSize():], additionalData) +} + +func (s *RepositoryStore) normalizeCompression(value string) (string, error) { + switch strings.ToLower(strings.TrimSpace(value)) { + case "", "zstd": + return "zstd", nil + case "gzip": + return "gzip", nil + case "none": + return "none", nil + default: + return "", fmt.Errorf("unsupported repository compression %q", value) + } +} + +func (s *RepositoryStore) openRestoreRoot(target string) (*os.Root, error) { + cleanTarget := filepath.Clean(target) + if !filepath.IsAbs(cleanTarget) { + return nil, fmt.Errorf("restore target must be absolute: %s", target) + } + volume := filepath.VolumeName(cleanTarget) + if strings.Contains(volume, "..") || strings.ContainsRune(volume, 0) { + return nil, fmt.Errorf("invalid restore target volume: %s", target) + } + volumeRootPath := string(filepath.Separator) + relativeTarget := strings.TrimLeft(cleanTarget, string(filepath.Separator)) + if volume != "" { + volumeRootPath = volume + string(filepath.Separator) + relativeTarget = strings.TrimLeft(strings.TrimPrefix(cleanTarget, volume), string(filepath.Separator)) + } + if relativeTarget != "" && !filepath.IsLocal(relativeTarget) { + return nil, fmt.Errorf("restore target is not local to its volume: %s", target) + } + volumeRoot, err := os.OpenRoot(volumeRootPath) + if err != nil { + return nil, fmt.Errorf("open restore volume: %w", err) + } + if relativeTarget == "" { + return volumeRoot, nil + } + if err := volumeRoot.MkdirAll(relativeTarget, 0o755); err != nil { + return nil, errors.Join(fmt.Errorf("create repository restore root: %w", err), volumeRoot.Close()) + } + restoreRoot, openErr := volumeRoot.OpenRoot(relativeTarget) + closeErr := volumeRoot.Close() + if openErr != nil || closeErr != nil { + return nil, errors.Join(openErr, closeErr) + } + return restoreRoot, nil +} + +func compactPaths(items []string) []string { + result := make([]string, 0, len(items)) + for _, item := range items { + if value := strings.TrimSpace(item); value != "" { + result = append(result, filepath.Clean(value)) + } + } + return result +} diff --git a/server/internal/backup/repository_chunker.go b/server/internal/backup/repository_chunker.go new file mode 100644 index 0000000..1dabf21 --- /dev/null +++ b/server/internal/backup/repository_chunker.go @@ -0,0 +1,131 @@ +package backup + +import ( + "context" + "fmt" + "io" +) + +const ( + repositoryChunkMin = 512 << 10 + repositoryChunkAvg = 1 << 20 + repositoryChunkMax = 4 << 20 +) + +// contentDefinedChunker implements the normalized FastCDC cut-point strategy. +// The rolling Gear hash only retains the latest 64 bytes through uint64 +// overflow, so chunk boundaries re-synchronize after insertions or deletions. +type contentDefinedChunker struct { + minSize int + avgSize int + maxSize int + smallMask uint64 + largeMask uint64 + gear [256]uint64 +} + +func newContentDefinedChunker() *contentDefinedChunker { + chunker := &contentDefinedChunker{ + minSize: repositoryChunkMin, + avgSize: repositoryChunkAvg, + maxSize: repositoryChunkMax, + smallMask: (1 << 21) - 1, + largeMask: (1 << 19) - 1, + } + + // SplitMix64 produces a stable, well-distributed Gear table. The seed and + // generation algorithm are part of repository format v1 and must not change. + seed := uint64(0x6a09e667f3bcc909) + for i := range chunker.gear { + seed += 0x9e3779b97f4a7c15 + value := seed + value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9 + value = (value ^ (value >> 27)) * 0x94d049bb133111eb + chunker.gear[i] = value ^ (value >> 31) + } + return chunker +} + +func (c *contentDefinedChunker) Split(ctx context.Context, reader io.Reader, emit func([]byte) error) error { + if reader == nil || emit == nil { + return fmt.Errorf("chunk reader and emitter are required") + } + + pending := make([]byte, 0, c.maxSize+(256<<10)) + readBuffer := make([]byte, 256<<10) + eof := false + for { + if !eof { + readCount, readErr := reader.Read(readBuffer) + if readCount > 0 { + pending = append(pending, readBuffer[:readCount]...) + } + switch readErr { + case nil: + case io.EOF: + eof = true + default: + return fmt.Errorf("read source for chunking: %w", readErr) + } + if readCount == 0 && readErr == nil { + continue + } + } + + for len(pending) > 0 { + if err := ctx.Err(); err != nil { + return err + } + cut := c.findCutPoint(pending, eof) + if cut == 0 { + break + } + chunk := make([]byte, cut) + copy(chunk, pending[:cut]) + if err := emit(chunk); err != nil { + return err + } + copy(pending, pending[cut:]) + pending = pending[:len(pending)-cut] + } + + if eof { + if len(pending) != 0 { + return fmt.Errorf("chunker stopped with %d buffered bytes", len(pending)) + } + return nil + } + } +} + +func (c *contentDefinedChunker) findCutPoint(data []byte, eof bool) int { + if len(data) < c.minSize { + if eof { + return len(data) + } + return 0 + } + + limit := len(data) + if limit > c.maxSize { + limit = c.maxSize + } + var hash uint64 + for index := c.minSize; index < limit; index++ { + hash = (hash << 1) + c.gear[data[index]] + mask := c.largeMask + if index < c.avgSize { + mask = c.smallMask + } + if hash&mask == 0 { + return index + 1 + } + } + if len(data) >= c.maxSize { + return c.maxSize + } + if eof { + return len(data) + } + return 0 +} diff --git a/server/internal/backup/repository_test.go b/server/internal/backup/repository_test.go new file mode 100644 index 0000000..282e39d --- /dev/null +++ b/server/internal/backup/repository_test.go @@ -0,0 +1,414 @@ +package backup + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "math/rand" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "testing" + "time" + + "backupx/server/internal/storage" +) + +func TestContentDefinedChunkerResynchronizesAfterInsertion(t *testing.T) { + source := make([]byte, 8<<20) + if _, err := rand.New(rand.NewSource(42)).Read(source); err != nil { + t.Fatalf("generate source: %v", err) + } + modified := make([]byte, 0, len(source)+4096) + modified = append(modified, source[:2<<20]...) + modified = append(modified, bytes.Repeat([]byte("inserted"), 512)...) + modified = append(modified, source[2<<20:]...) + + chunker := newContentDefinedChunker() + collect := func(data []byte) map[string]struct{} { + t.Helper() + ids := make(map[string]struct{}) + err := chunker.Split(context.Background(), bytes.NewReader(data), func(chunk []byte) error { + digest := sha256.Sum256(chunk) + ids[fmt.Sprintf("%x", digest[:])] = struct{}{} + if len(chunk) > repositoryChunkMax { + return fmt.Errorf("chunk exceeds maximum: %d", len(chunk)) + } + return nil + }) + if err != nil { + t.Fatalf("split chunks: %v", err) + } + return ids + } + originalChunks := collect(source) + modifiedChunks := collect(modified) + shared := 0 + for chunkID := range originalChunks { + if _, ok := modifiedChunks[chunkID]; ok { + shared++ + } + } + if shared < len(originalChunks)/2 { + t.Fatalf("content-defined boundaries did not resynchronize: shared=%d original=%d", shared, len(originalChunks)) + } +} + +func TestRepositoryRoundTripDedupAndPrune(t *testing.T) { + ctx := context.Background() + tempDir := t.TempDir() + sourceDir := filepath.Join(tempDir, "dataset") + if err := os.MkdirAll(filepath.Join(sourceDir, "empty"), 0o755); err != nil { + t.Fatalf("create source: %v", err) + } + original := make([]byte, 6<<20) + if _, err := rand.New(rand.NewSource(7)).Read(original); err != nil { + t.Fatalf("generate fixture: %v", err) + } + primaryPath := filepath.Join(sourceDir, "primary.bin") + duplicatePath := filepath.Join(sourceDir, "duplicate.bin") + if err := os.WriteFile(primaryPath, original, 0o640); err != nil { + t.Fatalf("write primary: %v", err) + } + if err := os.WriteFile(duplicatePath, original, 0o640); err != nil { + t.Fatalf("write duplicate: %v", err) + } + + key := sha256.Sum256([]byte("repository-test-key")) + store := NewRepositoryStore(key[:]) + provider := newMemoryRepositoryProvider() + task := TaskSpec{ + ID: 12, + Name: "repository-test", + Type: "file", + SourcePaths: []string{sourceDir}, + Compression: "zstd", + Encrypt: true, + StartedAt: time.Date(2026, 8, 6, 1, 2, 3, 0, time.UTC), + TempDir: tempDir, + } + + firstPlan, err := store.BuildPlan(ctx, task, NopLogWriter{}) + if err != nil { + t.Fatalf("build first plan: %v", err) + } + firstKey := store.SnapshotKey(task.ID, 1, task.StartedAt) + firstResult, err := store.Upload(ctx, provider, firstPlan, firstKey) + if closeErr := firstPlan.Close(); closeErr != nil { + t.Fatalf("close first plan: %v", closeErr) + } + if err != nil { + t.Fatalf("upload first snapshot: %v", err) + } + if firstResult.NewChunks == 0 || firstResult.UniqueChunks == 0 { + t.Fatalf("first upload did not create chunks: %+v", firstResult) + } + if firstPlan.UniqueSize >= firstPlan.LogicalSize { + t.Fatalf("duplicate file was not deduplicated within plan: unique=%d logical=%d", firstPlan.UniqueSize, firstPlan.LogicalSize) + } + + modified := make([]byte, 0, len(original)+4096) + modified = append(modified, original[:2<<20]...) + modified = append(modified, bytes.Repeat([]byte("changed!"), 512)...) + modified = append(modified, original[2<<20:]...) + if err := os.WriteFile(primaryPath, modified, 0o640); err != nil { + t.Fatalf("modify primary: %v", err) + } + task.StartedAt = task.StartedAt.Add(time.Hour) + secondPlan, err := store.BuildPlan(ctx, task, NopLogWriter{}) + if err != nil { + t.Fatalf("build second plan: %v", err) + } + secondKey := store.SnapshotKey(task.ID, 2, task.StartedAt) + secondResult, err := store.Upload(ctx, provider, secondPlan, secondKey) + if closeErr := secondPlan.Close(); closeErr != nil { + t.Fatalf("close second plan: %v", closeErr) + } + if err != nil { + t.Fatalf("upload second snapshot: %v", err) + } + if secondResult.ReusedBytes <= secondResult.LogicalSize/3 { + t.Fatalf("second snapshot reused too little data: %+v", secondResult) + } + if secondResult.UploadedBytes >= secondResult.LogicalSize { + t.Fatalf("incremental upload was not smaller than logical data: %+v", secondResult) + } + + verify, err := store.Verify(ctx, provider, secondKey, secondResult.Checksum) + if err != nil { + t.Fatalf("verify repository: %v", err) + } + if verify.Chunks == 0 || verify.Bytes == 0 { + t.Fatalf("empty verification result: %+v", verify) + } + + restoreRoot := filepath.Join(tempDir, "restore") + restoreTask := task + restoreTask.RestoreTargetPath = restoreRoot + if err := store.Restore(ctx, provider, secondKey, strings.Repeat("0", sha256.Size*2), restoreTask, NopLogWriter{}); err == nil { + t.Fatal("restore accepted a mismatched snapshot checksum") + } + if err := store.Restore(ctx, provider, secondKey, "", restoreTask, NopLogWriter{}); err == nil { + t.Fatal("restore accepted a missing snapshot checksum") + } + if err := store.Restore(ctx, provider, secondKey, secondResult.Checksum, restoreTask, NopLogWriter{}); err != nil { + t.Fatalf("restore snapshot: %v", err) + } + restored, err := os.ReadFile(filepath.Join(restoreRoot, filepath.Base(sourceDir), "primary.bin")) + if err != nil { + t.Fatalf("read restored primary: %v", err) + } + if !bytes.Equal(restored, modified) { + t.Fatalf("restored primary differs from source") + } + if info, err := os.Stat(filepath.Join(restoreRoot, filepath.Base(sourceDir), "empty")); err != nil || !info.IsDir() { + t.Fatalf("empty directory was not restored: info=%v err=%v", info, err) + } + + if err := provider.Delete(ctx, firstKey); err != nil { + t.Fatalf("delete first snapshot: %v", err) + } + if _, err := store.Prune(ctx, provider); err != nil { + t.Fatalf("prune with live snapshot: %v", err) + } + if err := provider.Delete(ctx, secondKey); err != nil { + t.Fatalf("delete second snapshot: %v", err) + } + pruned, err := store.Prune(ctx, provider) + if err != nil { + t.Fatalf("prune empty repository: %v", err) + } + if pruned.DeletedPacks == 0 || pruned.DeletedIndexes == 0 { + t.Fatalf("prune did not reclaim repository data: %+v", pruned) + } + if objects, err := provider.List(ctx, repositoryPackPrefix); err != nil || len(objects) != 0 { + t.Fatalf("packs remain after prune: objects=%v err=%v", objects, err) + } +} + +func TestRepositoryRestoreRejectsUnsafeSnapshotMetadata(t *testing.T) { + cases := []struct { + name string + entries []repositoryEntry + }{ + { + name: "path traversal", + entries: []repositoryEntry{{Path: "../escape", Kind: "directory", Mode: 0o755}}, + }, + { + name: "entry below symlink", + entries: []repositoryEntry{ + {Path: "link", Kind: "symlink", Mode: 0o777, LinkTarget: "inside"}, + {Path: "link/payload", Kind: "file", Mode: 0o600, Size: 1, Chunks: []string{"p-" + strings.Repeat("0", sha256.Size*2)}}, + }, + }, + { + name: "escaping symlink target", + entries: []repositoryEntry{{Path: "escape", Kind: "symlink", Mode: 0o777, LinkTarget: "../outside"}}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + store := NewRepositoryStore(nil) + provider := newMemoryRepositoryProvider() + snapshot := repositorySnapshot{ + Version: repositoryFormatVersion, + TaskID: 1, + CreatedAt: time.Now().UTC(), + Compression: "none", + Entries: tc.entries, + } + data, err := store.encodeSnapshot(snapshot) + if err != nil { + t.Fatalf("encodeSnapshot returned error: %v", err) + } + key := store.SnapshotKey(1, 1, snapshot.CreatedAt) + if err := provider.Upload(ctx, key, bytes.NewReader(data), int64(len(data)), nil); err != nil { + t.Fatalf("Upload snapshot returned error: %v", err) + } + digest := sha256.Sum256(data) + task := TaskSpec{SourcePath: filepath.Join(t.TempDir(), "source"), RestoreTargetPath: filepath.Join(t.TempDir(), "restore")} + if err := store.Restore(ctx, provider, key, fmt.Sprintf("%x", digest[:]), task, NopLogWriter{}); err == nil { + t.Fatal("restore accepted unsafe snapshot metadata") + } + }) + } +} + +func TestRepositoryRestorePreservesDirectoryWhenSnapshotContainsSymlink(t *testing.T) { + ctx := context.Background() + store := NewRepositoryStore(nil) + provider := newMemoryRepositoryProvider() + snapshot := repositorySnapshot{ + Version: repositoryFormatVersion, + TaskID: 1, + CreatedAt: time.Now().UTC(), + Compression: "none", + Entries: []repositoryEntry{{Path: "link", Kind: "symlink", Mode: 0o777, LinkTarget: "inside"}}, + } + data, err := store.encodeSnapshot(snapshot) + if err != nil { + t.Fatalf("encodeSnapshot returned error: %v", err) + } + key := store.SnapshotKey(1, 1, snapshot.CreatedAt) + if err := provider.Upload(ctx, key, bytes.NewReader(data), int64(len(data)), nil); err != nil { + t.Fatalf("Upload snapshot returned error: %v", err) + } + digest := sha256.Sum256(data) + restoreRoot := filepath.Join(t.TempDir(), "restore") + markerPath := filepath.Join(restoreRoot, "link", "keep.txt") + if err := os.MkdirAll(filepath.Dir(markerPath), 0o755); err != nil { + t.Fatalf("MkdirAll marker parent: %v", err) + } + if err := os.WriteFile(markerPath, []byte("keep"), 0o600); err != nil { + t.Fatalf("WriteFile marker: %v", err) + } + task := TaskSpec{SourcePath: filepath.Join(t.TempDir(), "source"), RestoreTargetPath: restoreRoot} + if err := store.Restore(ctx, provider, key, fmt.Sprintf("%x", digest[:]), task, NopLogWriter{}); err == nil { + t.Fatal("restore replaced an existing directory with a symlink") + } + if data, err := os.ReadFile(markerPath); err != nil || string(data) != "keep" { + t.Fatalf("existing directory content changed: data=%q err=%v", data, err) + } +} + +func TestRepositoryRejectsOversizedChunkLocation(t *testing.T) { + ctx := context.Background() + store := NewRepositoryStore(nil) + provider := newMemoryRepositoryProvider() + chunkID := "p-" + strings.Repeat("0", sha256.Size*2) + packID := strings.Repeat("a", sha256.Size*2) + segment := repositoryIndexSegment{ + Version: repositoryFormatVersion, + Pack: fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, packID[:2], packID), + Chunks: map[string]repositoryChunkLocation{ + chunkID: {Pack: fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, packID[:2], packID), Offset: 0, Length: repositoryMaxEncoded + 1, PlainSize: 1, Compression: "none"}, + }, + } + data, err := json.Marshal(segment) + if err != nil { + t.Fatalf("Marshal index returned error: %v", err) + } + indexKey := fmt.Sprintf("%s/%s.json", repositoryIndexPrefix, packID) + if err := provider.Upload(ctx, indexKey, bytes.NewReader(data), int64(len(data)), nil); err != nil { + t.Fatalf("Upload index returned error: %v", err) + } + if _, err := store.loadIndex(ctx, provider); err == nil { + t.Fatal("loadIndex accepted an oversized encoded chunk") + } +} + +func TestRepositoryRejectsIndexPackMismatch(t *testing.T) { + ctx := context.Background() + store := NewRepositoryStore(nil) + provider := newMemoryRepositoryProvider() + chunkID := "p-" + strings.Repeat("0", sha256.Size*2) + indexID := strings.Repeat("a", sha256.Size*2) + otherPackID := strings.Repeat("b", sha256.Size*2) + expectedPack := fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, indexID[:2], indexID) + segment := repositoryIndexSegment{ + Version: repositoryFormatVersion, + Pack: expectedPack, + Chunks: map[string]repositoryChunkLocation{ + chunkID: { + Pack: fmt.Sprintf("%s/%s/%s.pack", repositoryPackPrefix, otherPackID[:2], otherPackID), + Offset: 0, + Length: 1, + PlainSize: 1, + Compression: "none", + }, + }, + } + data, err := json.Marshal(segment) + if err != nil { + t.Fatalf("Marshal index returned error: %v", err) + } + indexKey := fmt.Sprintf("%s/%s.json", repositoryIndexPrefix, indexID) + if err := provider.Upload(ctx, indexKey, bytes.NewReader(data), int64(len(data)), nil); err != nil { + t.Fatalf("Upload index returned error: %v", err) + } + if _, err := store.loadIndex(ctx, provider); err == nil { + t.Fatal("loadIndex accepted a chunk location pointing to a different pack") + } +} + +type memoryRepositoryProvider struct { + mu sync.RWMutex + objects map[string][]byte + times map[string]time.Time +} + +func newMemoryRepositoryProvider() *memoryRepositoryProvider { + return &memoryRepositoryProvider{objects: make(map[string][]byte), times: make(map[string]time.Time)} +} + +func (p *memoryRepositoryProvider) Type() storage.ProviderType { return "memory" } +func (p *memoryRepositoryProvider) TestConnection(context.Context) error { return nil } + +func (p *memoryRepositoryProvider) Upload(_ context.Context, key string, reader io.Reader, size int64, _ map[string]string) error { + data, err := io.ReadAll(reader) + if err != nil { + return err + } + if int64(len(data)) != size { + return fmt.Errorf("size mismatch for %s: %d != %d", key, len(data), size) + } + p.mu.Lock() + defer p.mu.Unlock() + p.objects[key] = append([]byte(nil), data...) + p.times[key] = time.Now().UTC() + return nil +} + +func (p *memoryRepositoryProvider) Download(_ context.Context, key string) (io.ReadCloser, error) { + p.mu.RLock() + defer p.mu.RUnlock() + data, ok := p.objects[key] + if !ok { + return nil, fmt.Errorf("object %s not found", key) + } + return io.NopCloser(bytes.NewReader(append([]byte(nil), data...))), nil +} + +func (p *memoryRepositoryProvider) DownloadRange(_ context.Context, key string, offset, length int64) (io.ReadCloser, error) { + p.mu.RLock() + defer p.mu.RUnlock() + data, ok := p.objects[key] + if !ok { + return nil, fmt.Errorf("object %s not found", key) + } + if offset < 0 || length <= 0 || offset+length > int64(len(data)) { + return nil, fmt.Errorf("invalid range %d:%d for %s", offset, length, key) + } + return io.NopCloser(bytes.NewReader(append([]byte(nil), data[offset:offset+length]...))), nil +} + +func (p *memoryRepositoryProvider) Delete(_ context.Context, key string) error { + p.mu.Lock() + defer p.mu.Unlock() + if _, ok := p.objects[key]; !ok { + return fmt.Errorf("object %s not found", key) + } + delete(p.objects, key) + delete(p.times, key) + return nil +} + +func (p *memoryRepositoryProvider) List(_ context.Context, prefix string) ([]storage.ObjectInfo, error) { + p.mu.RLock() + defer p.mu.RUnlock() + result := make([]storage.ObjectInfo, 0) + for key, data := range p.objects { + if strings.HasPrefix(key, prefix) { + result = append(result, storage.ObjectInfo{Key: key, Size: int64(len(data)), UpdatedAt: p.times[key]}) + } + } + sort.Slice(result, func(i, j int) bool { return result[i].Key < result[j].Key }) + return result, nil +} diff --git a/server/internal/backup/retention/service.go b/server/internal/backup/retention/service.go index 70dda96..6e632c9 100644 --- a/server/internal/backup/retention/service.go +++ b/server/internal/backup/retention/service.go @@ -2,11 +2,13 @@ package retention import ( "context" + "encoding/json" "fmt" "sort" "strings" "time" + "backupx/server/internal/backup" "backupx/server/internal/model" "backupx/server/internal/repository" "backupx/server/internal/storage" @@ -40,16 +42,48 @@ type CleanupResult struct { Warnings []string } -type Service struct { - records repository.BackupRecordRepository - now func() time.Time +type cleanupObject struct { + targetID uint + path string } -func NewService(records repository.BackupRecordRepository) *Service { - return &Service{records: records, now: func() time.Time { return time.Now().UTC() }} +type storedUploadResult struct { + StorageTargetID uint `json:"storageTargetId"` + Status string `json:"status"` + StoragePath string `json:"storagePath"` +} + +type Service struct { + records repository.BackupRecordRepository + repositoryKey []byte + now func() time.Time +} + +func NewService(records repository.BackupRecordRepository, repositoryKey ...[]byte) *Service { + var key []byte + if len(repositoryKey) > 0 { + key = append([]byte(nil), repositoryKey[0]...) + } + return &Service{records: records, repositoryKey: key, now: func() time.Time { return time.Now().UTC() }} } func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider storage.StorageProvider) (*CleanupResult, error) { + return s.cleanup(ctx, task, func(uint) (storage.StorageProvider, bool) { + return provider, provider != nil + }) +} + +// CleanupProviders applies one retention decision to every successful copy of +// a record before deleting its database row. This prevents multi-target tasks +// from leaving stale objects after the first target removes the shared record. +func (s *Service) CleanupProviders(ctx context.Context, task *model.BackupTask, providers map[uint]storage.StorageProvider) (*CleanupResult, error) { + return s.cleanup(ctx, task, func(targetID uint) (storage.StorageProvider, bool) { + provider, ok := providers[targetID] + return provider, ok && provider != nil + }) +} + +func (s *Service) cleanup(ctx context.Context, task *model.BackupTask, resolveProvider func(uint) (storage.StorageProvider, bool)) (*CleanupResult, error) { if task == nil { return nil, fmt.Errorf("backup task is required") } @@ -67,17 +101,35 @@ func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider // 差异链保护:保留仍被存活差异依赖的全量,避免删除基线后差异无法恢复。 candidates = protectDifferentialBases(records, candidates) result := &CleanupResult{} + repositoryProviders := make(map[uint]storage.StorageProvider) + touchedProviders := make(map[uint]storage.StorageProvider) for _, record := range candidates { - if strings.TrimSpace(record.StoragePath) != "" { - if provider == nil { - result.Warnings = append(result.Warnings, fmt.Sprintf("record %d missing storage provider for cleanup", record.ID)) + objects, objectErr := cleanupObjectsForRecord(record) + if objectErr != nil { + result.Warnings = append(result.Warnings, fmt.Sprintf("decode storage copies for record %d failed: %v", record.ID, objectErr)) + continue + } + allObjectsDeleted := true + for _, object := range objects { + provider, ok := resolveProvider(object.targetID) + if !ok { + result.Warnings = append(result.Warnings, fmt.Sprintf("record %d missing storage provider %d for cleanup", record.ID, object.targetID)) + allObjectsDeleted = false continue } - if err := provider.Delete(ctx, record.StoragePath); err != nil { - result.Warnings = append(result.Warnings, fmt.Sprintf("delete storage object %s failed: %v", record.StoragePath, err)) + if err := provider.Delete(ctx, object.path); err != nil { + result.Warnings = append(result.Warnings, fmt.Sprintf("delete storage object %s from target %d failed: %v", object.path, object.targetID, err)) + allObjectsDeleted = false continue } result.DeletedObjects++ + touchedProviders[object.targetID] = provider + if record.BackupKind == model.BackupKindRepository { + repositoryProviders[object.targetID] = provider + } + } + if !allObjectsDeleted { + continue } if err := s.records.Delete(ctx, record.ID); err != nil { result.Warnings = append(result.Warnings, fmt.Sprintf("delete backup record %d failed: %v", record.ID, err)) @@ -85,13 +137,25 @@ func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider } result.DeletedRecords++ } + for targetID, provider := range repositoryProviders { + pruned, pruneErr := backup.NewRepositoryStore(s.repositoryKey).Prune(ctx, provider) + if pruneErr != nil { + result.Warnings = append(result.Warnings, fmt.Sprintf("prune CDC repository on target %d failed: %v", targetID, pruneErr)) + } else { + result.DeletedObjects += pruned.DeletedPacks + pruned.DeletedIndexes + } + } // 清理空目录:收集被删除文件的父目录,尝试移除空目录 - if dirCleaner, ok := provider.(storage.StorageDirCleaner); ok && result.DeletedObjects > 0 { + for targetID, provider := range touchedProviders { + dirCleaner, ok := provider.(storage.StorageDirCleaner) + if !ok { + continue + } prefixes := collectDirPrefixes(candidates) for _, prefix := range prefixes { if err := dirCleaner.RemoveEmptyDirs(ctx, prefix); err != nil { - result.Warnings = append(result.Warnings, fmt.Sprintf("cleanup empty dirs for %s: %v", prefix, err)) + result.Warnings = append(result.Warnings, fmt.Sprintf("cleanup empty dirs for %s on target %d: %v", prefix, targetID, err)) } } } @@ -99,6 +163,43 @@ func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider return result, nil } +func cleanupObjectsForRecord(record model.BackupRecord) ([]cleanupObject, error) { + defaultPath := strings.TrimSpace(record.StoragePath) + if strings.TrimSpace(record.StorageUploadResults) == "" { + if defaultPath == "" { + return nil, nil + } + return []cleanupObject{{targetID: record.StorageTargetID, path: defaultPath}}, nil + } + var results []storedUploadResult + if err := json.Unmarshal([]byte(record.StorageUploadResults), &results); err != nil { + return nil, err + } + objects := make([]cleanupObject, 0, len(results)) + seen := make(map[uint]struct{}, len(results)) + for _, result := range results { + if !strings.EqualFold(strings.TrimSpace(result.Status), model.BackupRecordStatusSuccess) { + continue + } + objectPath := strings.TrimSpace(result.StoragePath) + if objectPath == "" { + objectPath = defaultPath + } + if objectPath == "" { + continue + } + if _, exists := seen[result.StorageTargetID]; exists { + continue + } + seen[result.StorageTargetID] = struct{}{} + objects = append(objects, cleanupObject{targetID: result.StorageTargetID, path: objectPath}) + } + if len(objects) == 0 && defaultPath != "" { + return nil, fmt.Errorf("successful record has no successful storage copy") + } + return objects, nil +} + // protectDifferentialBases 从删除候选中剔除「仍被存活差异依赖的全量」, // 避免删除基线后其差异备份失去依据、无法恢复。全量仅当其全部差异都已过期/删除时才会被清理。 func protectDifferentialBases(all []model.BackupRecord, candidates []model.BackupRecord) []model.BackupRecord { diff --git a/server/internal/backup/retention/service_test.go b/server/internal/backup/retention/service_test.go index a1e8124..8cb8108 100644 --- a/server/internal/backup/retention/service_test.go +++ b/server/internal/backup/retention/service_test.go @@ -221,3 +221,66 @@ func TestCleanupDeletesExpiredRecords(t *testing.T) { t.Fatalf("unexpected deleted objects: %#v", provider.deleted) } } + +func TestCleanupProvidersDeletesEverySuccessfulCopyBeforeRecord(t *testing.T) { + now := time.Date(2026, 3, 7, 16, 0, 0, 0, time.UTC) + completedNew := now.Add(-time.Hour) + completedOld := now.Add(-24 * time.Hour) + repo := &fakeRecordRepository{records: []model.BackupRecord{ + {ID: 2, TaskID: 1, StoragePath: "records/2", Status: model.BackupRecordStatusSuccess, CompletedAt: &completedNew}, + { + ID: 1, TaskID: 1, StoragePath: "records/1", Status: model.BackupRecordStatusSuccess, CompletedAt: &completedOld, + StorageUploadResults: `[{"storageTargetId":11,"status":"success","storagePath":"first/1"},{"storageTargetId":12,"status":"success","storagePath":"second/1"},{"storageTargetId":13,"status":"failed"}]`, + }, + }} + first := &fakeProvider{} + second := &fakeProvider{} + service := NewService(repo) + service.now = func() time.Time { return now } + + result, err := service.CleanupProviders(context.Background(), &model.BackupTask{ID: 1, MaxBackups: 1}, map[uint]storage.StorageProvider{ + 11: first, + 12: second, + }) + if err != nil { + t.Fatalf("CleanupProviders returned error: %v", err) + } + if result.DeletedRecords != 1 || result.DeletedObjects != 2 || len(result.Warnings) != 0 { + t.Fatalf("unexpected cleanup result: %#v", result) + } + if len(repo.deleted) != 1 || repo.deleted[0] != 1 { + t.Fatalf("unexpected deleted records: %#v", repo.deleted) + } + if len(first.deleted) != 1 || first.deleted[0] != "first/1" { + t.Fatalf("unexpected first-target deletes: %#v", first.deleted) + } + if len(second.deleted) != 1 || second.deleted[0] != "second/1" { + t.Fatalf("unexpected second-target deletes: %#v", second.deleted) + } +} + +func TestCleanupProvidersKeepsRecordWhenCopyProviderIsUnavailable(t *testing.T) { + now := time.Date(2026, 3, 7, 16, 0, 0, 0, time.UTC) + completedNew := now.Add(-time.Hour) + completedOld := now.Add(-24 * time.Hour) + repo := &fakeRecordRepository{records: []model.BackupRecord{ + {ID: 2, TaskID: 1, StoragePath: "records/2", Status: model.BackupRecordStatusSuccess, CompletedAt: &completedNew}, + { + ID: 1, TaskID: 1, StoragePath: "records/1", Status: model.BackupRecordStatusSuccess, CompletedAt: &completedOld, + StorageUploadResults: `[{"storageTargetId":11,"status":"success","storagePath":"first/1"},{"storageTargetId":12,"status":"success","storagePath":"second/1"}]`, + }, + }} + first := &fakeProvider{} + service := NewService(repo) + + result, err := service.CleanupProviders(context.Background(), &model.BackupTask{ID: 1, MaxBackups: 1}, map[uint]storage.StorageProvider{11: first}) + if err != nil { + t.Fatalf("CleanupProviders returned error: %v", err) + } + if result.DeletedRecords != 0 || result.DeletedObjects != 1 || len(result.Warnings) != 1 { + t.Fatalf("unexpected safe partial-cleanup result: %#v", result) + } + if len(repo.deleted) != 0 { + t.Fatalf("record must remain until all copies are deleted: %#v", repo.deleted) + } +} diff --git a/server/internal/backup/temp_files.go b/server/internal/backup/temp_files.go index db77fc2..cce68c5 100644 --- a/server/internal/backup/temp_files.go +++ b/server/internal/backup/temp_files.go @@ -55,6 +55,17 @@ func BuildStorageKey(backupType string, startedAt time.Time, fileName string) st return filepath.ToSlash(filepath.Join("BackupX", typeName, startedAt.UTC().Format("060102"), fileName)) } +// BuildRecordStorageKey gives remote-Agent artifacts a record-owned namespace. +// The Master validates this namespace before accepting a relayed upload, so one +// Agent cannot overwrite another record's object on centrally mounted storage. +func BuildRecordStorageKey(backupType string, startedAt time.Time, recordID uint, fileName string) string { + typeName := strings.TrimSpace(strings.ToLower(backupType)) + if typeName == "" { + typeName = "file" + } + return filepath.ToSlash(filepath.Join("BackupX", typeName, startedAt.UTC().Format("060102"), "records", fmt.Sprintf("%d", recordID), fileName)) +} + func sanitizeTaskName(value string) string { trimmed := strings.TrimSpace(strings.ToLower(value)) trimmed = strings.ReplaceAll(trimmed, " ", "-") diff --git a/server/internal/http/agent_handler.go b/server/internal/http/agent_handler.go index 8750c8f..23d28c1 100644 --- a/server/internal/http/agent_handler.go +++ b/server/internal/http/agent_handler.go @@ -156,6 +156,44 @@ func (h *AgentHandler) UpdateRecord(c *gin.Context) { response.Success(c, gin.H{"status": "ok"}) } +// UploadArtifact streams a remote source artifact into storage mounted only on +// the Master. The request body is never buffered as a whole in memory or disk. +func (h *AgentHandler) UploadArtifact(c *gin.Context) { + node, err := h.agentService.AuthenticatedNode(c.Request.Context(), extractToken(c)) + if err != nil { + response.Error(c, err) + return + } + recordID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + response.Error(c, err) + return + } + targetID, err := strconv.ParseUint(c.Param("targetId"), 10, 32) + if err != nil { + response.Error(c, err) + return + } + if c.Request.ContentLength < 0 { + c.JSON(stdhttp.StatusLengthRequired, gin.H{"code": "CONTENT_LENGTH_REQUIRED", "message": "artifact content length is required"}) + return + } + if err := h.agentService.UploadArtifact( + c.Request.Context(), + node, + uint(recordID), + uint(targetID), + c.GetHeader("X-BackupX-Object-Key"), + c.Request.ContentLength, + c.GetHeader("X-BackupX-SHA256"), + c.Request.Body, + ); err != nil { + response.Error(c, err) + return + } + response.Success(c, gin.H{"status": "ok"}) +} + // GetRestoreSpec Agent 拉取恢复规格。 func (h *AgentHandler) GetRestoreSpec(c *gin.Context) { if h.restoreService == nil { @@ -208,6 +246,34 @@ func (h *AgentHandler) UpdateRestore(c *gin.Context) { response.Success(c, gin.H{"status": "ok"}) } +// DownloadRestoreArtifact streams a Master-local backup back to its source +// Agent for restore without exposing the local storage configuration. +func (h *AgentHandler) DownloadRestoreArtifact(c *gin.Context) { + if h.restoreService == nil { + c.JSON(stdhttp.StatusServiceUnavailable, gin.H{"code": "RESTORE_SERVICE_DISABLED", "message": "restore service is not enabled"}) + return + } + node, err := h.agentService.AuthenticatedNode(c.Request.Context(), extractToken(c)) + if err != nil { + response.Error(c, err) + return + } + restoreID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + response.Error(c, err) + return + } + artifact, err := h.restoreService.DownloadAgentArtifact(c.Request.Context(), node, uint(restoreID)) + if err != nil { + response.Error(c, err) + return + } + c.DataFromReader(stdhttp.StatusOK, artifact.Size, "application/octet-stream", artifact.Reader, nil) + if err := artifact.Reader.Close(); err != nil { + _ = c.Error(err) + } +} + // Self 返回当前 Agent token 所属节点的状态,供安装脚本末尾探活。 func (h *AgentHandler) Self(c *gin.Context) { node, err := h.agentService.AuthenticatedNode(c.Request.Context(), extractToken(c)) diff --git a/server/internal/http/router.go b/server/internal/http/router.go index ba5bf9d..f003fb2 100644 --- a/server/internal/http/router.go +++ b/server/internal/http/router.go @@ -322,7 +322,9 @@ func NewRouter(deps RouterDependencies) *gin.Engine { agent.POST("/commands/:id/result", agentHandler.SubmitCommandResult) agent.GET("/tasks/:id", agentHandler.GetTaskSpec) agent.POST("/records/:id", agentHandler.UpdateRecord) + agent.PUT("/records/:id/artifacts/:targetId", agentHandler.UploadArtifact) agent.GET("/restores/:id/spec", agentHandler.GetRestoreSpec) + agent.GET("/restores/:id/artifact", agentHandler.DownloadRestoreArtifact) agent.POST("/restores/:id", agentHandler.UpdateRestore) // Agent v1(安装脚本探活用),仅 Self 端点 diff --git a/server/internal/installscript/deploy_install_test.go b/server/internal/installscript/deploy_install_test.go index 4c0507a..c001e23 100644 --- a/server/internal/installscript/deploy_install_test.go +++ b/server/internal/installscript/deploy_install_test.go @@ -39,3 +39,23 @@ func TestDeployInstallScriptSupportsReleasePackageLayout(t *testing.T) { } } } + +func TestDeployInstallScriptSupportsSourceBuildAndVerifiesFirstSetup(t *testing.T) { + scriptPath := filepath.Join("..", "..", "..", "deploy", "install.sh") + data, err := os.ReadFile(scriptPath) + if err != nil { + t.Fatal(err) + } + script := string(data) + for _, want := range []string{ + `SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/bin/backupx"`, + `For a source install, run 'make build' in the repository root first.`, + `HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8340/api/auth/setup/status}"`, + `systemctl is-active --quiet "$SERVICE_NAME"`, + `System setup`, + } { + if !strings.Contains(script, want) { + t.Fatalf("install.sh missing %q", want) + } + } +} diff --git a/server/internal/model/backup_record.go b/server/internal/model/backup_record.go index 9f8f9c6..8734c19 100644 --- a/server/internal/model/backup_record.go +++ b/server/internal/model/backup_record.go @@ -9,9 +9,11 @@ const ( ) const ( - // BackupKindFull 全量备份;BackupKindDifferential 差异备份(仅含自基线全量以来的变更)。 + // BackupKindFull 全量归档;BackupKindDifferential 差异归档; + // BackupKindRepository 为可独立恢复的 CDC 内容寻址快照。 BackupKindFull = "full" BackupKindDifferential = "differential" + BackupKindRepository = "repository" ) type BackupRecord struct { @@ -20,20 +22,22 @@ type BackupRecord struct { Task BackupTask `json:"task,omitempty"` StorageTargetID uint `gorm:"column:storage_target_id;index;not null" json:"storageTargetId"` StorageTarget StorageTarget `json:"storageTarget,omitempty"` - // NodeID 执行该次备份的节点(0 = 本机 Master)。用于集群中识别 local_disk 类型 - // 存储的归属节点,避免 Master 端试图跨节点访问远程 Agent 的本地存储。 - NodeID uint `gorm:"column:node_id;index;default:0" json:"nodeId"` - Status string `gorm:"size:20;index;not null" json:"status"` - FileName string `gorm:"column:file_name;size:255" json:"fileName"` - FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"` - Checksum string `gorm:"column:checksum;size:64" json:"checksum"` - StoragePath string `gorm:"column:storage_path;size:500" json:"storagePath"` + // NodeID 执行该次备份的节点(0 = 本机 Master)。StorageTransferMode 进一步 + // 区分远程 Agent 直写与 Master 中转,避免在错误节点访问 local_disk。 + NodeID uint `gorm:"column:node_id;index;default:0" json:"nodeId"` + Status string `gorm:"size:20;index;not null" json:"status"` + FileName string `gorm:"column:file_name;size:255" json:"fileName"` + FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"` + Checksum string `gorm:"column:checksum;size:64" json:"checksum"` + StoragePath string `gorm:"column:storage_path;size:500" json:"storagePath"` + // 空值表示旧版 Agent 直写;direct / master_relay 记录新协议的实际数据路径。 + StorageTransferMode string `gorm:"column:storage_transfer_mode;size:20" json:"storageTransferMode,omitempty"` StorageUploadResults string `gorm:"column:storage_upload_results;type:text" json:"-"` DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"` // Locked 保留锁定(法律保留):为 true 时该备份不参与保留期/数量自动清理, // 且禁止手动删除,直到显式解锁。用于保护合规快照、迁移前基线等关键备份。 Locked bool `gorm:"column:locked;not null;default:false;index" json:"locked"` - // BackupKind 备份类型:full(全量)/ differential(差异)。 + // BackupKind 备份类型:full(全量)/ differential(差异)/ repository(CDC 快照)。 BackupKind string `gorm:"column:backup_kind;size:16;not null;default:'full';index" json:"backupKind"` // BaseRecordID 差异备份所基于的全量备份记录 ID(全量记录为 0)。 BaseRecordID uint `gorm:"column:base_record_id;index;not null;default:0" json:"baseRecordId"` diff --git a/server/internal/model/backup_task.go b/server/internal/model/backup_task.go index a3409d7..b077f68 100644 --- a/server/internal/model/backup_task.go +++ b/server/internal/model/backup_task.go @@ -12,9 +12,11 @@ const ( ) const ( - // BackupModeFull 全量模式(默认);BackupModeDifferential 差异模式(仅文件类型本机任务)。 + // BackupModeFull 全量模式(默认);BackupModeDifferential 差异归档; + // BackupModeRepository 为 CDC 内容寻址仓库模式(仅文件类型本机任务)。 BackupModeFull = "full" BackupModeDifferential = "differential" + BackupModeRepository = "repository" ) const ( @@ -55,7 +57,8 @@ type BackupTask struct { Compression string `gorm:"size:10;not null;default:'gzip'" json:"compression"` Encrypt bool `gorm:"not null;default:false" json:"encrypt"` MaxBackups int `gorm:"column:max_backups;not null;default:10" json:"maxBackups"` - // BackupMode 备份模式:full(全量,默认)/ differential(差异)。差异仅支持本机文件任务。 + // BackupMode 备份模式:full(全量,默认)/ differential(差异归档)/ + // repository(FastCDC 分块、全局去重快照)。后两者仅支持本机文件任务。 BackupMode string `gorm:"column:backup_mode;size:16;not null;default:'full'" json:"backupMode"` // DiffFullIntervalDays 差异模式下强制全量的间隔(天):最近全量超过该天数则本次自动改为全量, // 限制差异链跨度与单个差异体积。默认 7。 diff --git a/server/internal/service/agent_service.go b/server/internal/service/agent_service.go index 7723325..9a32e14 100644 --- a/server/internal/service/agent_service.go +++ b/server/internal/service/agent_service.go @@ -2,15 +2,22 @@ package service import ( "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" + "io" + "math" + "path" "strings" "time" "backupx/server/internal/apperror" + "backupx/server/internal/backup" "backupx/server/internal/model" "backupx/server/internal/repository" + "backupx/server/internal/storage" "backupx/server/internal/storage/codec" ) @@ -23,6 +30,7 @@ type AgentService struct { storageRepo repository.StorageTargetRepository cmdRepo repository.AgentCommandRepository restoreRepo repository.RestoreRecordRepository + registry *storage.Registry cipher *codec.ConfigCipher } @@ -33,6 +41,7 @@ func NewAgentService( storageRepo repository.StorageTargetRepository, cmdRepo repository.AgentCommandRepository, cipher *codec.ConfigCipher, + registry *storage.Registry, ) *AgentService { return &AgentService{ nodeRepo: nodeRepo, @@ -40,6 +49,7 @@ func NewAgentService( recordRepo: recordRepo, storageRepo: storageRepo, cmdRepo: cmdRepo, + registry: registry, cipher: cipher, } } @@ -145,10 +155,11 @@ type AgentTaskSpec struct { // AgentStorageTargetConfig 存储目标配置(已解密) type AgentStorageTargetConfig struct { - ID uint `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Config json.RawMessage `json:"config"` + ID uint `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Config json.RawMessage `json:"config"` + TransferMode string `json:"transferMode"` } // GetTaskSpec 返回 Agent 执行任务所需的完整规格。 @@ -187,11 +198,22 @@ func (s *AgentService) GetTaskSpec(ctx context.Context, node *model.Node, taskID if err != nil { return nil, fmt.Errorf("decrypt storage config: %w", err) } + transferMode := storage.TransferModeDirect + if strings.EqualFold(target.Type, storage.TypeLocalDisk) { + var localConfig storage.LocalDiskConfig + if err := json.Unmarshal(configRaw, &localConfig); err != nil { + return nil, fmt.Errorf("decode local disk config: %w", err) + } + if localConfig.MasterRelay { + transferMode = storage.TransferModeMasterRelay + } + } storageTargets = append(storageTargets, AgentStorageTargetConfig{ - ID: target.ID, - Type: target.Type, - Name: target.Name, - Config: json.RawMessage(configRaw), + ID: target.ID, + Type: target.Type, + Name: target.Name, + Config: json.RawMessage(configRaw), + TransferMode: transferMode, }) } return &AgentTaskSpec{ @@ -214,6 +236,102 @@ func (s *AgentService) GetTaskSpec(ctx context.Context, node *model.Node, taskID }, nil } +// UploadArtifact receives a remote Agent artifact as a stream and writes it +// with a provider created on the Master. The first supported use is local_disk, +// whose configured path belongs to the Master rather than the source Agent. +func (s *AgentService) UploadArtifact(ctx context.Context, node *model.Node, recordID, targetID uint, objectKey string, size int64, checksum string, reader io.Reader) error { + if node == nil || reader == nil || s.registry == nil { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID", "中转上传参数不完整", nil) + } + record, err := s.recordRepo.FindByID(ctx, recordID) + if err != nil { + return err + } + if record == nil { + return apperror.New(404, "BACKUP_RECORD_NOT_FOUND", "记录不存在", nil) + } + task, err := s.taskRepo.FindByID(ctx, record.TaskID) + if err != nil { + return err + } + if task == nil || !recordBelongsToNode(record, task, node.ID) { + return apperror.Unauthorized("BACKUP_RECORD_FORBIDDEN", "记录不属于当前节点", nil) + } + if isBackupRecordTerminal(record.Status) { + return apperror.BadRequest("BACKUP_RECORD_TERMINAL", "备份记录已结束,不能继续上传产物", nil) + } + allowedTarget := false + for _, configuredTargetID := range collectTargetIDs(task) { + if configuredTargetID == targetID { + allowedTarget = true + break + } + } + if !allowedTarget { + return apperror.Unauthorized("BACKUP_STORAGE_TARGET_FORBIDDEN", "存储目标不属于该任务", nil) + } + target, err := s.storageRepo.FindByID(ctx, targetID) + if err != nil { + return err + } + if target == nil || !strings.EqualFold(target.Type, storage.TypeLocalDisk) { + return apperror.BadRequest("AGENT_ARTIFACT_RELAY_UNSUPPORTED", "仅 Master 本地磁盘目标需要中转上传", nil) + } + configMap := map[string]any{} + if err := s.cipher.DecryptJSON(target.ConfigCiphertext, &configMap); err != nil { + return fmt.Errorf("decrypt storage config: %w", err) + } + masterRelay, _ := configMap["masterRelay"].(bool) + if !masterRelay { + return apperror.BadRequest("AGENT_ARTIFACT_RELAY_UNSUPPORTED", "该本地磁盘目标配置为 Agent 直接写入", nil) + } + cleanKey, keyErr := s.validateArtifactKey(record, task, objectKey, true) + if keyErr != nil { + return keyErr + } + checksum = strings.TrimSpace(checksum) + checksumBytes, checksumErr := hex.DecodeString(checksum) + if size < 0 || size == math.MaxInt64 || checksumErr != nil || len(checksumBytes) != sha256.Size { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID", "中转上传需要有效的大小和 SHA-256", checksumErr) + } + if target.QuotaBytes > 0 { + usage, usageErr := s.recordRepo.StorageUsage(ctx) + if usageErr != nil { + return fmt.Errorf("read storage usage: %w", usageErr) + } + currentUsed := int64(0) + for _, item := range usage { + if item.StorageTargetID == targetID { + currentUsed = item.TotalSize + break + } + } + if currentUsed >= target.QuotaBytes || size > target.QuotaBytes-currentUsed { + return apperror.BadRequest("BACKUP_STORAGE_QUOTA_EXCEEDED", fmt.Sprintf("超出存储目标配额(当前 %d,新增 %d,配额 %d)", currentUsed, size, target.QuotaBytes), nil) + } + } + provider, err := s.registry.Create(ctx, target.Type, configMap) + if err != nil { + return fmt.Errorf("create master relay provider: %w", err) + } + limited := io.LimitReader(reader, size+1) + hashed := newHashingReader(limited) + metadata := map[string]string{ + "taskId": fmt.Sprintf("%d", task.ID), + "recordId": fmt.Sprintf("%d", record.ID), + "sourceNodeId": fmt.Sprintf("%d", node.ID), + "transferMode": storage.TransferModeMasterRelay, + } + if err := provider.Upload(ctx, cleanKey, hashed, size, metadata); err != nil { + return errors.Join(fmt.Errorf("relay artifact to master storage: %w", err), provider.Delete(ctx, cleanKey)) + } + if hashed.n != size || !strings.EqualFold(hashed.Sum(), checksum) { + deleteErr := provider.Delete(ctx, cleanKey) + return errors.Join(fmt.Errorf("relayed artifact integrity mismatch: received %d of %d bytes", hashed.n, size), deleteErr) + } + return nil +} + func (s *AgentService) ensureTaskSpecAccess(ctx context.Context, node *model.Node, task *model.BackupTask) error { if task.NodeID == node.ID { return nil @@ -236,6 +354,7 @@ type AgentRecordUpdate struct { Checksum string `json:"checksum,omitempty"` StoragePath string `json:"storagePath,omitempty"` StorageTargetID uint `json:"storageTargetId,omitempty"` + StorageTransferMode string `json:"storageTransferMode,omitempty"` StorageUploadResults []StorageUploadResultItem `json:"storageUploadResults,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` LogAppend string `json:"logAppend,omitempty"` // 增量日志,追加到 record.log_content @@ -260,6 +379,99 @@ func (s *AgentService) UpdateRecord(ctx context.Context, node *model.Node, recor if isBackupRecordTerminal(record.Status) { return nil } + allowedTargets := make(map[uint]struct{}) + for _, targetID := range collectTargetIDs(task) { + allowedTargets[targetID] = struct{}{} + } + targetCache := make(map[uint]*model.StorageTarget) + validateTransferMode := func(targetID uint, transferMode string) (string, error) { + if _, ok := allowedTargets[targetID]; !ok { + return "", apperror.Unauthorized("BACKUP_STORAGE_TARGET_FORBIDDEN", "存储目标不属于该任务", nil) + } + target := targetCache[targetID] + if target == nil { + var findErr error + target, findErr = s.storageRepo.FindByID(ctx, targetID) + if findErr != nil { + return "", findErr + } + if target == nil { + return "", apperror.BadRequest("BACKUP_STORAGE_TARGET_INVALID", "存储目标不存在", nil) + } + targetCache[targetID] = target + } + expectedMode := storage.TransferModeDirect + if strings.EqualFold(target.Type, storage.TypeLocalDisk) { + var localConfig storage.LocalDiskConfig + if err := s.cipher.DecryptJSON(target.ConfigCiphertext, &localConfig); err != nil { + return "", fmt.Errorf("decrypt storage config: %w", err) + } + if localConfig.MasterRelay { + expectedMode = storage.TransferModeMasterRelay + } + } + if transferMode != "" && transferMode != expectedMode { + return "", apperror.BadRequest("AGENT_STORAGE_TRANSFER_MODE_INVALID", "Agent 上报的存储传输模式与目标配置不一致", nil) + } + return expectedMode, nil + } + selectedTransferMode := "" + if update.StorageTargetID > 0 { + if _, ok := allowedTargets[update.StorageTargetID]; !ok { + return apperror.Unauthorized("BACKUP_STORAGE_TARGET_FORBIDDEN", "存储目标不属于该任务", nil) + } + var modeErr error + selectedTransferMode, modeErr = validateTransferMode(update.StorageTargetID, update.StorageTransferMode) + if modeErr != nil { + return modeErr + } + } else if update.StorageTransferMode != "" { + return apperror.BadRequest("AGENT_STORAGE_TRANSFER_MODE_INVALID", "传输模式缺少对应的存储目标", nil) + } + for index := range update.StorageUploadResults { + result := &update.StorageUploadResults[index] + expectedMode, err := validateTransferMode(result.StorageTargetID, result.TransferMode) + if err != nil { + return err + } + if result.FileSize < 0 || (result.Status != "" && result.Status != "success" && result.Status != "failed") { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID", "Agent 上报的存储结果无效", nil) + } + if result.StoragePath != "" { + normalizedPath, err := s.validateArtifactKey(record, task, result.StoragePath, expectedMode == storage.TransferModeMasterRelay) + if err != nil { + return err + } + result.StoragePath = normalizedPath + } + result.TransferMode = expectedMode + } + if update.StoragePath != "" { + if update.StorageTargetID == 0 { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID_PATH", "存储路径缺少对应的存储目标", nil) + } + cleanStoragePath, err := s.validateArtifactKey(record, task, update.StoragePath, selectedTransferMode == storage.TransferModeMasterRelay) + if err != nil { + return err + } + if update.FileName != "" && path.Base(cleanStoragePath) != update.FileName { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID_PATH", "Agent 上报的文件名与存储路径不一致", nil) + } + update.StoragePath = cleanStoragePath + } + if update.Status != "" && update.Status != model.BackupRecordStatusRunning && update.Status != model.BackupRecordStatusSuccess && update.Status != model.BackupRecordStatusFailed { + return apperror.BadRequest("BACKUP_RECORD_STATUS_INVALID", "Agent 上报的备份状态无效", nil) + } + if update.FileSize < 0 || (update.FileName != "" && (path.Base(update.FileName) != update.FileName || strings.Contains(update.FileName, "\\"))) { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID", "Agent 上报的备份文件信息无效", nil) + } + if update.Checksum != "" { + checksumBytes, checksumErr := hex.DecodeString(strings.TrimSpace(update.Checksum)) + if checksumErr != nil || len(checksumBytes) != sha256.Size { + return apperror.BadRequest("AGENT_ARTIFACT_INVALID", "Agent 上报的 SHA-256 无效", checksumErr) + } + update.Checksum = strings.ToLower(strings.TrimSpace(update.Checksum)) + } if update.Status != "" { record.Status = update.Status } @@ -277,6 +489,7 @@ func (s *AgentService) UpdateRecord(ctx context.Context, node *model.Node, recor } if update.StorageTargetID > 0 { record.StorageTargetID = update.StorageTargetID + record.StorageTransferMode = selectedTransferMode } if len(update.StorageUploadResults) > 0 { if resultsJSON, marshalErr := json.Marshal(update.StorageUploadResults); marshalErr == nil { @@ -312,6 +525,27 @@ func (s *AgentService) UpdateRecord(ctx context.Context, node *model.Node, recor return nil } +func (s *AgentService) validateArtifactKey(record *model.BackupRecord, task *model.BackupTask, objectKey string, requireRecordNamespace bool) (string, error) { + if record == nil || task == nil { + return "", apperror.BadRequest("AGENT_ARTIFACT_INVALID_PATH", "无法确认中转对象归属", nil) + } + rawKey := objectKey + cleanKey := path.Clean(rawKey) + fileName := path.Base(cleanKey) + if rawKey == "" || strings.TrimSpace(rawKey) != rawKey || cleanKey == "." || path.IsAbs(cleanKey) || strings.HasPrefix(cleanKey, "../") || cleanKey != rawKey || strings.Contains(rawKey, "\\") || fileName == "." || fileName == "/" { + return "", apperror.BadRequest("AGENT_ARTIFACT_INVALID_PATH", "中转上传对象路径不安全", nil) + } + expectedKey := backup.BuildRecordStorageKey(task.Type, record.StartedAt, record.ID, fileName) + if requireRecordNamespace { + legacyKey := backup.BuildStorageKey(task.Type, record.StartedAt, fileName) + if cleanKey != expectedKey && cleanKey != legacyKey { + return "", apperror.BadRequest("AGENT_ARTIFACT_INVALID_PATH", "中转上传对象不属于当前备份记录", nil) + } + return expectedKey, nil + } + return cleanKey, nil +} + func recordBelongsToNode(record *model.BackupRecord, task *model.BackupTask, nodeID uint) bool { if record.NodeID != 0 { return record.NodeID == nodeID diff --git a/server/internal/service/agent_service_test.go b/server/internal/service/agent_service_test.go index ccda6f4..4a66dfd 100644 --- a/server/internal/service/agent_service_test.go +++ b/server/internal/service/agent_service_test.go @@ -1,19 +1,26 @@ package service import ( + "bytes" "context" + "crypto/sha256" "errors" + "fmt" + "os" "path/filepath" "strings" "testing" "time" + "backupx/server/internal/backup" "backupx/server/internal/config" "backupx/server/internal/database" "backupx/server/internal/logger" "backupx/server/internal/model" "backupx/server/internal/repository" + "backupx/server/internal/storage" "backupx/server/internal/storage/codec" + storageRclone "backupx/server/internal/storage/rclone" "gorm.io/gorm" ) @@ -42,7 +49,7 @@ func newAgentServicePoolTestHarness(t *testing.T) (*AgentService, *gorm.DB, repo if err := nodeRepo.Create(context.Background(), other); err != nil { t.Fatalf("create other node: %v", err) } - targetConfig, err := cipher.EncryptJSON(map[string]any{"basePath": t.TempDir()}) + targetConfig, err := cipher.EncryptJSON(map[string]any{"basePath": t.TempDir(), "masterRelay": true}) if err != nil { t.Fatalf("EncryptJSON returned error: %v", err) } @@ -76,7 +83,8 @@ func newAgentServicePoolTestHarness(t *testing.T) (*AgentService, *gorm.DB, repo if err := recordRepo.Create(context.Background(), record); err != nil { t.Fatalf("create record: %v", err) } - return NewAgentService(nodeRepo, taskRepo, recordRepo, storageRepo, cmdRepo, cipher), db, recordRepo, cmdRepo, owner, other + storageRegistry := storage.NewRegistry(storageRclone.NewLocalDiskFactory()) + return NewAgentService(nodeRepo, taskRepo, recordRepo, storageRepo, cmdRepo, cipher, storageRegistry), db, recordRepo, cmdRepo, owner, other } func TestAgentServicePooledTaskUsesRecordNodeForSpecAndRecordUpdates(t *testing.T) { @@ -90,19 +98,27 @@ func TestAgentServicePooledTaskUsesRecordNodeForSpecAndRecordUpdates(t *testing. if spec.TaskID != 1 || len(spec.StorageTargets) != 1 { t.Fatalf("unexpected spec: %#v", spec) } + if spec.StorageTargets[0].TransferMode != storage.TransferModeMasterRelay { + t.Fatalf("expected local disk to use Master relay, got %#v", spec.StorageTargets[0]) + } if _, err := svc.GetTaskSpec(ctx, other, 1); err == nil { t.Fatal("expected non-owner node to be forbidden from pooled task spec") } + record, err := records.FindByID(ctx, 1) + if err != nil { + t.Fatalf("FindByID record returned error: %v", err) + } + storagePath := backup.BuildRecordStorageKey("file", record.StartedAt, record.ID, "backup.tar.gz") if err := svc.UpdateRecord(ctx, owner, 1, AgentRecordUpdate{ - Status: model.BackupRecordStatusSuccess, - FileName: "backup.tar.gz", - FileSize: 123, - StoragePath: "tasks/1/backup.tar.gz", - StorageTargetID: 2, + Status: model.BackupRecordStatusSuccess, + FileName: "backup.tar.gz", + FileSize: 123, + StoragePath: storagePath, + StorageTargetID: 1, + StorageTransferMode: storage.TransferModeMasterRelay, StorageUploadResults: []StorageUploadResultItem{ - {StorageTargetID: 1, StorageTargetName: "first", Status: "failed", Error: "boom"}, - {StorageTargetID: 2, StorageTargetName: "second", Status: "success", StoragePath: "tasks/1/backup.tar.gz", FileSize: 123}, + {StorageTargetID: 1, StorageTargetName: "local", Status: "success", StoragePath: storagePath, FileSize: 123, TransferMode: storage.TransferModeMasterRelay}, }, }); err != nil { t.Fatalf("owner UpdateRecord returned error: %v", err) @@ -114,10 +130,13 @@ func TestAgentServicePooledTaskUsesRecordNodeForSpecAndRecordUpdates(t *testing. if updated.Status != model.BackupRecordStatusSuccess || updated.NodeID != owner.ID { t.Fatalf("unexpected updated record: %#v", updated) } - if updated.StorageTargetID != 2 { - t.Fatalf("expected successful storage target id 2, got %d", updated.StorageTargetID) + if updated.StorageTargetID != 1 { + t.Fatalf("expected successful storage target id 1, got %d", updated.StorageTargetID) } - if !strings.Contains(updated.StorageUploadResults, `"storageTargetName":"second"`) { + if updated.StorageTransferMode != storage.TransferModeMasterRelay { + t.Fatalf("expected Master relay transfer mode, got %q", updated.StorageTransferMode) + } + if !strings.Contains(updated.StorageUploadResults, `"storageTargetName":"local"`) { t.Fatalf("expected upload results to be persisted, got %q", updated.StorageUploadResults) } if err := svc.UpdateRecord(ctx, other, 1, AgentRecordUpdate{LogAppend: "bad"}); err == nil { @@ -125,6 +144,157 @@ func TestAgentServicePooledTaskUsesRecordNodeForSpecAndRecordUpdates(t *testing. } } +func TestAgentServiceRelaysRemoteArtifactToMasterLocalDisk(t *testing.T) { + svc, _, records, _, owner, other := newAgentServicePoolTestHarness(t) + ctx := context.Background() + payload := []byte("artifact from remote source server") + digest := sha256.Sum256(payload) + checksum := fmt.Sprintf("%x", digest[:]) + record, err := records.FindByID(ctx, 1) + if err != nil { + t.Fatalf("FindByID record returned error: %v", err) + } + objectKey := backup.BuildRecordStorageKey("file", record.StartedAt, record.ID, "remote-source.tar") + + if err := svc.UploadArtifact(ctx, owner, 1, 1, objectKey, int64(len(payload)), checksum, bytes.NewReader(payload)); err != nil { + t.Fatalf("UploadArtifact returned error: %v", err) + } + target, err := svc.storageRepo.FindByID(ctx, 1) + if err != nil || target == nil { + t.Fatalf("FindByID target: target=%#v err=%v", target, err) + } + config := map[string]any{} + if err := svc.cipher.DecryptJSON(target.ConfigCiphertext, &config); err != nil { + t.Fatalf("DecryptJSON target config: %v", err) + } + basePath, _ := config["basePath"].(string) + stored, err := os.ReadFile(filepath.Join(basePath, filepath.FromSlash(objectKey))) + if err != nil { + t.Fatalf("read relayed artifact: %v", err) + } + if !bytes.Equal(stored, payload) { + t.Fatalf("relayed artifact differs: got %q", stored) + } + if err := svc.UploadArtifact(ctx, other, 1, 1, objectKey, int64(len(payload)), checksum, bytes.NewReader(payload)); err == nil { + t.Fatal("expected a different node to be forbidden from relaying the artifact") + } + + legacyPayload := []byte("artifact from an older Agent") + legacyDigest := sha256.Sum256(legacyPayload) + legacyKey := backup.BuildStorageKey("file", record.StartedAt, "legacy-agent.tar") + canonicalKey := backup.BuildRecordStorageKey("file", record.StartedAt, record.ID, "legacy-agent.tar") + if err := svc.UploadArtifact(ctx, owner, record.ID, target.ID, legacyKey, int64(len(legacyPayload)), fmt.Sprintf("%x", legacyDigest[:]), bytes.NewReader(legacyPayload)); err != nil { + t.Fatalf("UploadArtifact legacy key returned error: %v", err) + } + stored, err = os.ReadFile(filepath.Join(basePath, filepath.FromSlash(canonicalKey))) + if err != nil || !bytes.Equal(stored, legacyPayload) { + t.Fatalf("legacy Agent artifact was not normalized: data=%q err=%v", stored, err) + } + if _, err := os.Stat(filepath.Join(basePath, filepath.FromSlash(legacyKey))); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("legacy object key should not be written directly: %v", err) + } + if err := svc.UpdateRecord(ctx, owner, record.ID, AgentRecordUpdate{ + Status: model.BackupRecordStatusSuccess, + FileName: "legacy-agent.tar", + FileSize: int64(len(legacyPayload)), + Checksum: fmt.Sprintf("%x", legacyDigest[:]), + StoragePath: legacyKey, + StorageTargetID: target.ID, + StorageUploadResults: []StorageUploadResultItem{{ + StorageTargetID: target.ID, + Status: "success", + StoragePath: legacyKey, + FileSize: int64(len(legacyPayload)), + }}, + }); err != nil { + t.Fatalf("UpdateRecord legacy key returned error: %v", err) + } + updated, err := records.FindByID(ctx, record.ID) + if err != nil { + t.Fatalf("FindByID updated record returned error: %v", err) + } + if updated.StoragePath != canonicalKey || updated.StorageTransferMode != storage.TransferModeMasterRelay || !strings.Contains(updated.StorageUploadResults, canonicalKey) { + t.Fatalf("legacy Agent record was not normalized: %#v", updated) + } +} + +func TestAgentServiceRejectsArtifactOutsideRecordNamespace(t *testing.T) { + svc, _, records, _, owner, _ := newAgentServicePoolTestHarness(t) + ctx := context.Background() + record, err := records.FindByID(ctx, 1) + if err != nil { + t.Fatalf("FindByID record returned error: %v", err) + } + target, err := svc.storageRepo.FindByID(ctx, 1) + if err != nil || target == nil { + t.Fatalf("FindByID target: target=%#v err=%v", target, err) + } + config := map[string]any{} + if err := svc.cipher.DecryptJSON(target.ConfigCiphertext, &config); err != nil { + t.Fatalf("DecryptJSON target config: %v", err) + } + basePath, _ := config["basePath"].(string) + victimKey := backup.BuildRecordStorageKey("file", record.StartedAt, record.ID+1, "victim.tar") + victimPath := filepath.Join(basePath, filepath.FromSlash(victimKey)) + if err := os.MkdirAll(filepath.Dir(victimPath), 0o755); err != nil { + t.Fatalf("MkdirAll victim parent: %v", err) + } + if err := os.WriteFile(victimPath, []byte("keep me"), 0o600); err != nil { + t.Fatalf("WriteFile victim: %v", err) + } + payload := []byte("overwrite") + digest := sha256.Sum256(payload) + if err := svc.UploadArtifact(ctx, owner, record.ID, target.ID, victimKey, int64(len(payload)), fmt.Sprintf("%x", digest[:]), bytes.NewReader(payload)); err == nil { + t.Fatal("expected another record namespace to be rejected") + } + stored, err := os.ReadFile(victimPath) + if err != nil { + t.Fatalf("ReadFile victim: %v", err) + } + if string(stored) != "keep me" { + t.Fatalf("victim object changed: %q", stored) + } + if err := svc.UpdateRecord(ctx, owner, record.ID, AgentRecordUpdate{StoragePath: victimKey, StorageTargetID: target.ID, StorageTransferMode: storage.TransferModeMasterRelay}); err == nil { + t.Fatal("expected another record namespace in status update to be rejected") + } +} + +func TestAgentServiceKeepsExistingLocalDiskTargetsAgentLocal(t *testing.T) { + svc, _, records, _, owner, _ := newAgentServicePoolTestHarness(t) + ctx := context.Background() + target, err := svc.storageRepo.FindByID(ctx, 1) + if err != nil || target == nil { + t.Fatalf("FindByID target: target=%#v err=%v", target, err) + } + legacyConfig, err := svc.cipher.EncryptJSON(map[string]any{"basePath": t.TempDir()}) + if err != nil { + t.Fatalf("EncryptJSON legacy target: %v", err) + } + target.ConfigCiphertext = legacyConfig + if err := svc.storageRepo.Update(ctx, target); err != nil { + t.Fatalf("Update legacy target: %v", err) + } + + spec, err := svc.GetTaskSpec(ctx, owner, 1) + if err != nil { + t.Fatalf("GetTaskSpec returned error: %v", err) + } + if len(spec.StorageTargets) != 1 || spec.StorageTargets[0].TransferMode != storage.TransferModeDirect { + t.Fatalf("expected legacy local disk to stay Agent-local, got %#v", spec.StorageTargets) + } + payload := []byte("must not be relayed") + digest := sha256.Sum256(payload) + record, findErr := records.FindByID(ctx, 1) + if findErr != nil { + t.Fatalf("FindByID record returned error: %v", findErr) + } + objectKey := backup.BuildRecordStorageKey("file", record.StartedAt, record.ID, "legacy.tar") + err = svc.UploadArtifact(ctx, owner, 1, 1, objectKey, int64(len(payload)), fmt.Sprintf("%x", digest[:]), bytes.NewReader(payload)) + if err == nil { + t.Fatal("expected relay upload to be rejected for an Agent-local target") + } +} + func TestAgentServiceUpdateRecordRefreshesTaskSummaryOnTerminalStatus(t *testing.T) { for _, status := range []string{model.BackupRecordStatusSuccess, model.BackupRecordStatusFailed} { t.Run(status, func(t *testing.T) { diff --git a/server/internal/service/backup_execution_service.go b/server/internal/service/backup_execution_service.go index 76066b2..f880682 100644 --- a/server/internal/service/backup_execution_service.go +++ b/server/internal/service/backup_execution_service.go @@ -5,11 +5,13 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "hash" "io" "os" "path/filepath" + "sort" "strings" "sync" "time" @@ -49,6 +51,7 @@ type StorageUploadResultItem struct { Status string `json:"status"` StoragePath string `json:"storagePath,omitempty"` FileSize int64 `json:"fileSize,omitempty"` + TransferMode string `json:"transferMode,omitempty"` Error string `json:"error,omitempty"` } @@ -62,6 +65,17 @@ type DownloadedArtifact struct { Reader io.ReadCloser } +type temporaryArtifactReader struct { + *os.File + directory string +} + +func (r *temporaryArtifactReader) Close() error { + closeErr := r.File.Close() + removeErr := os.RemoveAll(r.directory) + return errors.Join(closeErr, removeErr) +} + // collectTargetIDs 获取任务关联的所有存储目标 ID func collectTargetIDs(task *model.BackupTask) []uint { if len(task.StorageTargets) > 0 { @@ -102,6 +116,10 @@ type BackupExecutionService struct { bandwidthLimit string // rclone 带宽限制(全局默认,节点配置可覆盖) metrics *metrics.Metrics taskLocks sync.Map + // repositoryLocks serializes immutable index updates per storage target. + // Repository mode is intentionally single-writer in v1 to avoid orphaned + // duplicate packs when two local tasks discover the same missing chunk. + repositoryLocks sync.Map } // SetMetrics 注入 Prometheus 采集器。nil 时所有埋点退化为 no-op。 @@ -211,6 +229,25 @@ func (s *BackupExecutionService) DownloadRecord(ctx context.Context, recordID ui if err != nil { return nil, err } + if record.BackupKind == model.BackupKindRepository { + tempDir, err := os.MkdirTemp(s.tempDir, "repository-download-*") + if err != nil { + return nil, apperror.Internal("BACKUP_RECORD_DOWNLOAD_FAILED", "无法创建 CDC 导出目录", err) + } + exportName := fmt.Sprintf("backupx-record-%d.tar", record.ID) + exportPath := filepath.Join(tempDir, exportName) + store := backup.NewRepositoryStore(s.cipher.Key()) + if err := store.ExportTar(ctx, provider, record.StoragePath, record.Checksum, exportPath); err != nil { + cleanupErr := os.RemoveAll(tempDir) + return nil, apperror.Internal("BACKUP_RECORD_DOWNLOAD_FAILED", "无法从 CDC 仓库导出归档", errors.Join(err, cleanupErr)) + } + file, err := os.Open(exportPath) + if err != nil { + cleanupErr := os.RemoveAll(tempDir) + return nil, apperror.Internal("BACKUP_RECORD_DOWNLOAD_FAILED", "无法打开 CDC 导出归档", errors.Join(err, cleanupErr)) + } + return &DownloadedArtifact{FileName: exportName, Reader: &temporaryArtifactReader{File: file, directory: tempDir}}, nil + } reader, err := provider.Download(ctx, record.StoragePath) if err != nil { return nil, apperror.Internal("BACKUP_RECORD_DOWNLOAD_FAILED", "无法下载备份文件", err) @@ -234,6 +271,16 @@ func (s *BackupExecutionService) RestoreRecord(ctx context.Context, recordID uin if task == nil { return apperror.New(404, "BACKUP_TASK_NOT_FOUND", "关联的备份任务不存在,无法执行恢复", fmt.Errorf("backup task %d not found", record.TaskID)) } + if record.BackupKind == model.BackupKindRepository { + spec, specErr := s.buildTaskSpec(task, record.StartedAt) + if specErr != nil { + return specErr + } + if err := backup.NewRepositoryStore(s.cipher.Key()).Restore(ctx, provider, record.StoragePath, record.Checksum, spec, backup.NopLogWriter{}); err != nil { + return apperror.Internal("BACKUP_RECORD_RESTORE_FAILED", "从 CDC 仓库恢复备份失败", err) + } + return nil + } tempDir, err := os.MkdirTemp("", "backupx-restore-*") if err != nil { return apperror.Internal("BACKUP_RECORD_RESTORE_FAILED", "无法创建恢复目录", err) @@ -291,6 +338,58 @@ func (s *BackupExecutionService) DeleteRecord(ctx context.Context, recordID uint fmt.Sprintf("该全量备份仍有 %d 个差异备份依赖它,删除会导致这些差异无法恢复。请先删除相关差异备份或等待其过期。", deps), nil) } } + if record.BackupKind == model.BackupKindRepository { + copies := []StorageUploadResultItem{{ + StorageTargetID: record.StorageTargetID, + Status: model.BackupRecordStatusSuccess, + StoragePath: record.StoragePath, + }} + if strings.TrimSpace(record.StorageUploadResults) != "" { + if err := json.Unmarshal([]byte(record.StorageUploadResults), &copies); err != nil { + return apperror.Internal("BACKUP_RECORD_DELETE_FAILED", "无法解析 CDC 仓库副本信息,已停止删除以避免遗留数据", err) + } + } + copyPaths := make(map[uint]string, len(copies)) + for _, copy := range copies { + if strings.EqualFold(copy.Status, model.BackupRecordStatusSuccess) && strings.TrimSpace(copy.StoragePath) != "" { + copyPaths[copy.StorageTargetID] = copy.StoragePath + } + } + targetIDs := make([]uint, 0, len(copyPaths)) + for targetID := range copyPaths { + targetIDs = append(targetIDs, targetID) + } + sort.Slice(targetIDs, func(i, j int) bool { return targetIDs[i] < targetIDs[j] }) + unlocks := make([]func(), 0, len(targetIDs)) + for _, targetID := range targetIDs { + unlocks = append(unlocks, s.acquireRepositoryLock(targetID)) + } + defer func() { + for index := len(unlocks) - 1; index >= 0; index-- { + unlocks[index]() + } + }() + providers := make(map[uint]storage.StorageProvider, len(targetIDs)) + for _, targetID := range targetIDs { + provider, resolveErr := s.resolveProvider(ctx, targetID) + if resolveErr != nil { + return resolveErr + } + if deleteErr := provider.Delete(ctx, copyPaths[targetID]); deleteErr != nil { + return apperror.Internal("BACKUP_RECORD_DELETE_FAILED", "无法删除 CDC 仓库快照", deleteErr) + } + providers[targetID] = provider + } + for targetID, provider := range providers { + if _, pruneErr := backup.NewRepositoryStore(s.cipher.Key()).Prune(ctx, provider); pruneErr != nil { + return apperror.Internal("BACKUP_REPOSITORY_PRUNE_FAILED", fmt.Sprintf("无法清理存储目标 %d 的 CDC 仓库;记录暂时保留以便重试", targetID), pruneErr) + } + } + if err := s.records.Delete(ctx, recordID); err != nil { + return apperror.Internal("BACKUP_RECORD_DELETE_FAILED", "无法删除备份记录", err) + } + return nil + } if remote, err := s.deleteRemoteLocalDiskObject(ctx, record); err != nil { return err } else if !remote && strings.TrimSpace(record.StoragePath) != "" { @@ -312,6 +411,9 @@ func (s *BackupExecutionService) deleteRemoteLocalDiskObject(ctx context.Context if strings.TrimSpace(record.StoragePath) == "" || s.nodeRepo == nil { return false, nil } + if record.StorageTransferMode == storage.TransferModeMasterRelay { + return false, nil + } node, err := s.nodeRepo.FindByID(ctx, record.NodeID) if err != nil || node == nil || node.IsLocal { return false, nil @@ -384,6 +486,9 @@ func (s *BackupExecutionService) startTask(ctx context.Context, id uint, async b return nil, perr } } + if strings.EqualFold(task.BackupMode, model.BackupModeRepository) && s.resolveRemoteNode(ctx, resolvedNodeID) != nil { + return nil, apperror.BadRequest("BACKUP_TASK_REPOSITORY_REMOTE_UNSUPPORTED", "CDC 仓库模式当前仅支持 Master 本机单写者执行", nil) + } startedAt := s.now() // 取第一个存储目标 ID 做兼容 primaryTargetID := task.StorageTargetID @@ -630,6 +735,141 @@ func (s *BackupExecutionService) resolveDifferentialBase(ctx context.Context, ta return 0, backup.Manifest{}, false } +type repositoryTaskResult struct { + fileName string + logicalSize int64 + checksum string + storagePath string + storageTargetID uint + manifestJSON string + uploadResults []StorageUploadResultItem + providers map[uint]storage.StorageProvider +} + +func (s *BackupExecutionService) executeRepositoryTask(ctx context.Context, task *model.BackupTask, recordID uint, startedAt time.Time, spec backup.TaskSpec, logger *backup.ExecutionLogger) (*repositoryTaskResult, error) { + store := backup.NewRepositoryStore(s.cipher.Key()) + plan, err := store.BuildPlan(ctx, spec, logger) + if err != nil { + return nil, err + } + defer func() { + if closeErr := plan.Close(); closeErr != nil { + logger.Warnf("清理 CDC 临时计划失败:%v", closeErr) + } + }() + manifestBytes, err := backup.EncodeManifest(plan.Manifest) + if err != nil { + return nil, fmt.Errorf("encode repository manifest: %w", err) + } + targetIDs := collectTargetIDs(task) + if len(targetIDs) == 0 { + return nil, fmt.Errorf("没有关联的存储目标") + } + storageUsage, usageErr := s.storageUsageSnapshot(ctx) + if usageErr != nil { + logger.Warnf("读取存储目标用量失败,跳过本次软配额校验:%v", usageErr) + storageUsage = map[uint]int64{} + } + + snapshotKey := store.SnapshotKey(task.ID, recordID, startedAt) + result := &repositoryTaskResult{ + fileName: filepath.Base(snapshotKey), + logicalSize: plan.LogicalSize, + storagePath: snapshotKey, + manifestJSON: string(manifestBytes), + uploadResults: make([]StorageUploadResultItem, 0, len(targetIDs)), + providers: make(map[uint]storage.StorageProvider), + } + var failures []string + for _, targetID := range targetIDs { + target, findErr := s.targets.FindByID(ctx, targetID) + targetName := fmt.Sprintf("target-%d", targetID) + if findErr == nil && target != nil { + targetName = target.Name + } + if findErr != nil || target == nil { + message := "存储目标不存在" + if findErr != nil { + message = findErr.Error() + } + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{StorageTargetID: targetID, StorageTargetName: targetName, Status: "failed", Error: message}) + failures = append(failures, fmt.Sprintf("%s: %s", targetName, message)) + continue + } + provider, resolveErr := s.resolveProviderForNode(ctx, targetID, task.NodeID) + if resolveErr != nil { + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{StorageTargetID: targetID, StorageTargetName: targetName, Status: "failed", Error: resolveErr.Error()}) + failures = append(failures, fmt.Sprintf("%s: %v", targetName, resolveErr)) + continue + } + logger.Infof("同步 CDC 仓库到存储目标:%s", targetName) + unlock := s.acquireRepositoryLock(targetID) + estimatedSize, estimateErr := store.EstimateUploadSize(ctx, provider, plan) + if estimateErr != nil { + unlock() + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{StorageTargetID: targetID, StorageTargetName: targetName, Status: "failed", Error: estimateErr.Error()}) + failures = append(failures, fmt.Sprintf("%s: %v", targetName, estimateErr)) + continue + } + if target.QuotaBytes > 0 && storageUsage[targetID]+estimatedSize > target.QuotaBytes { + unlock() + message := fmt.Sprintf("超出存储目标配额(%d + 预计 %d > %d)", storageUsage[targetID], estimatedSize, target.QuotaBytes) + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{StorageTargetID: targetID, StorageTargetName: targetName, Status: "failed", Error: message}) + failures = append(failures, fmt.Sprintf("%s: %s", targetName, message)) + continue + } + upload, uploadErr := store.Upload(ctx, provider, plan, snapshotKey) + unlock() + if uploadErr != nil { + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{StorageTargetID: targetID, StorageTargetName: targetName, Status: "failed", Error: uploadErr.Error()}) + failures = append(failures, fmt.Sprintf("%s: %v", targetName, uploadErr)) + logger.Warnf("存储目标 %s CDC 仓库同步失败:%v", targetName, uploadErr) + continue + } + result.uploadResults = append(result.uploadResults, StorageUploadResultItem{ + StorageTargetID: targetID, StorageTargetName: targetName, Status: "success", + StoragePath: upload.SnapshotKey, FileSize: upload.UploadedBytes, + }) + result.providers[targetID] = provider + if result.storageTargetID == 0 { + result.storageTargetID = targetID + result.checksum = upload.Checksum + } + logger.Infof("存储目标 %s CDC 同步完成:新块 %d/%d,复用 %d bytes,实际上传 %d bytes", targetName, upload.NewChunks, upload.UniqueChunks, upload.ReusedBytes, upload.UploadedBytes) + } + if result.storageTargetID == 0 { + return nil, fmt.Errorf("所有存储目标 CDC 仓库同步均失败:%s", strings.Join(failures, "; ")) + } + if len(failures) > 0 { + logger.Warnf("部分存储目标 CDC 仓库同步失败:%s", strings.Join(failures, "; ")) + } + if s.dependentsResolver != nil { + go func(upstreamID uint, upstreamName string) { + dependents, resolveErr := s.dependentsResolver.TriggerDependents(context.Background(), upstreamID) + if resolveErr != nil { + logger.Warnf("解析任务 %s 的下游依赖失败:%v", upstreamName, resolveErr) + return + } + for _, dependentID := range dependents { + if _, runErr := s.RunTaskByID(context.Background(), dependentID); runErr != nil { + logger.Warnf("触发下游任务 #%d 失败(上游: %s):%v", dependentID, upstreamName, runErr) + } else { + logger.Infof("已触发下游任务 #%d(上游: %s)", dependentID, upstreamName) + } + } + }(task.ID, task.Name) + } + return result, nil +} + +func (s *BackupExecutionService) acquireRepositoryLock(targetID uint) func() { + created := &sync.Mutex{} + actual, _ := s.repositoryLocks.LoadOrStore(targetID, created) + lock := actual.(*sync.Mutex) + lock.Lock() + return lock.Unlock +} + func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.BackupTask, recordID uint, startedAt time.Time) { // 节点级并发限流:当任务绑定节点且节点配置了 MaxConcurrent>0, // 该节点上所有任务共享一个节点专属 semaphore,互相排队 @@ -658,18 +898,33 @@ func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.Ba backupKind := model.BackupKindFull var baseRecordID uint var manifestJSON string + var repositoryProviders map[uint]storage.StorageProvider completeRecord := func() { + readyForRepositoryRetention := status == model.BackupRecordStatusSuccess if finalizeErr := s.finalizeRecord(ctx, task, recordID, startedAt, status, errMessage, logger.String(), fileName, fileSize, checksum, storagePath, selectedStorageTargetID); finalizeErr != nil { logger.Errorf("写回备份记录失败:%v", finalizeErr) + readyForRepositoryRetention = false } // 采集任务执行结果到 Prometheus(耗时 + 产出字节 + 状态计数) s.metrics.ObserveTaskRun(task.Type, status, time.Since(startedAt).Seconds(), fileSize) // 写入多目标上传结果 if len(uploadResults) > 0 { - if resultsJSON, marshalErr := json.Marshal(uploadResults); marshalErr == nil { - if record, findErr := s.records.FindByID(ctx, recordID); findErr == nil && record != nil { - record.StorageUploadResults = string(resultsJSON) - _ = s.records.Update(ctx, record) + resultsJSON, marshalErr := json.Marshal(uploadResults) + if marshalErr != nil { + logger.Warnf("序列化多目标上传结果失败:%v", marshalErr) + readyForRepositoryRetention = false + } else if record, findErr := s.records.FindByID(ctx, recordID); findErr != nil || record == nil { + if findErr != nil { + logger.Warnf("读取备份记录以写回多目标结果失败:%v", findErr) + } else { + logger.Warnf("备份记录 #%d 不存在,无法写回多目标结果", recordID) + } + readyForRepositoryRetention = false + } else { + record.StorageUploadResults = string(resultsJSON) + if updateErr := s.records.Update(ctx, record); updateErr != nil { + logger.Warnf("写回多目标上传结果失败:%v", updateErr) + readyForRepositoryRetention = false } } } @@ -681,6 +936,36 @@ func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.Ba record.Manifest = manifestJSON if updErr := s.records.Update(ctx, record); updErr != nil { logger.Warnf("写回差异链信息失败:%v", updErr) + readyForRepositoryRetention = false + } + } else { + if findErr != nil { + logger.Warnf("读取备份记录以写回备份类型失败:%v", findErr) + } else { + logger.Warnf("备份记录 #%d 不存在,无法写回备份类型", recordID) + } + readyForRepositoryRetention = false + } + } + if readyForRepositoryRetention && backupKind == model.BackupKindRepository && s.retention != nil && len(repositoryProviders) > 0 { + targetIDs := make([]uint, 0, len(repositoryProviders)) + for targetID := range repositoryProviders { + targetIDs = append(targetIDs, targetID) + } + sort.Slice(targetIDs, func(i, j int) bool { return targetIDs[i] < targetIDs[j] }) + unlocks := make([]func(), 0, len(targetIDs)) + for _, targetID := range targetIDs { + unlocks = append(unlocks, s.acquireRepositoryLock(targetID)) + } + cleanupResult, cleanupErr := s.retention.CleanupProviders(ctx, task, repositoryProviders) + for index := len(unlocks) - 1; index >= 0; index-- { + unlocks[index]() + } + if cleanupErr != nil { + logger.Warnf("执行 CDC 仓库保留策略失败:%v", cleanupErr) + } else { + for _, warning := range cleanupResult.Warnings { + logger.Warnf("CDC 仓库保留策略警告:%s", warning) } } } @@ -701,6 +986,26 @@ func (s *BackupExecutionService) executeTask(ctx context.Context, task *model.Ba logger.Errorf("构建任务运行时配置失败:%v", err) return } + if task.Type == model.BackupTaskTypeFile && strings.EqualFold(task.BackupMode, model.BackupModeRepository) { + backupKind = model.BackupKindRepository + repositoryResult, repositoryErr := s.executeRepositoryTask(ctx, task, recordID, startedAt, spec, logger) + if repositoryErr != nil { + errMessage = repositoryErr.Error() + logger.Errorf("执行 CDC 仓库备份失败:%v", repositoryErr) + return + } + fileName = repositoryResult.fileName + fileSize = repositoryResult.logicalSize + checksum = repositoryResult.checksum + storagePath = repositoryResult.storagePath + selectedStorageTargetID = repositoryResult.storageTargetID + uploadResults = repositoryResult.uploadResults + repositoryProviders = repositoryResult.providers + manifestJSON = repositoryResult.manifestJSON + status = model.BackupRecordStatusSuccess + logger.Infof("CDC 仓库备份执行完成") + return + } // 差异备份:解析基线全量,命中则切换为差异模式(仅本机文件任务)。 if baseID, baseManifest, ok := s.resolveDifferentialBase(ctx, task); ok { spec.Differential = true diff --git a/server/internal/service/backup_execution_service_test.go b/server/internal/service/backup_execution_service_test.go index 5f9ec15..f9eb3b5 100644 --- a/server/internal/service/backup_execution_service_test.go +++ b/server/internal/service/backup_execution_service_test.go @@ -1,6 +1,7 @@ package service import ( + "bytes" "context" "errors" "fmt" @@ -155,6 +156,108 @@ func TestBackupExecutionServiceRunTaskByIDSync(t *testing.T) { } } +func TestBackupExecutionServiceRepositoryModeRoundTrip(t *testing.T) { + executionService, recordService, tasks, _, records, sourceDir, storageDir := newExecutionTestServices(t) + ctx := context.Background() + task, err := tasks.FindByID(ctx, 1) + if err != nil { + t.Fatalf("FindByID task returned error: %v", err) + } + task.BackupMode = model.BackupModeRepository + task.Compression = "zstd" + if err := tasks.Update(ctx, task); err != nil { + t.Fatalf("Update repository task returned error: %v", err) + } + large := make([]byte, 4<<20) + for index := range large { + large[index] = byte((index * 31) % 251) + } + largePath := filepath.Join(sourceDir, "large.bin") + if err := os.WriteFile(largePath, large, 0o640); err != nil { + t.Fatalf("write large fixture: %v", err) + } + + first, err := executionService.RunTaskByIDSync(ctx, task.ID) + if err != nil { + t.Fatalf("first repository backup returned error: %v", err) + } + if first.Status != model.BackupRecordStatusSuccess || first.BackupKind != model.BackupKindRepository { + t.Fatalf("unexpected first repository record: %#v", first) + } + if !strings.HasPrefix(first.StoragePath, ".backupx/repository/v1/snapshots/") { + t.Fatalf("unexpected repository snapshot path: %s", first.StoragePath) + } + + large[2<<20] ^= 0xff + if err := os.WriteFile(largePath, large, 0o640); err != nil { + t.Fatalf("modify large fixture: %v", err) + } + second, err := executionService.RunTaskByIDSync(ctx, task.ID) + if err != nil { + t.Fatalf("second repository backup returned error: %v", err) + } + stored, err := records.FindByID(ctx, second.ID) + if err != nil { + t.Fatalf("FindByID repository record returned error: %v", err) + } + if stored == nil || stored.BackupKind != model.BackupKindRepository || stored.Manifest == "" { + t.Fatalf("repository metadata was not persisted: %#v", stored) + } + + download, err := recordService.Download(ctx, second.ID) + if err != nil { + t.Fatalf("export repository snapshot returned error: %v", err) + } + exported, readErr := io.ReadAll(download.Reader) + closeErr := download.Reader.Close() + if readErr != nil || closeErr != nil { + t.Fatalf("read repository export: read=%v close=%v", readErr, closeErr) + } + if len(exported) == 0 || !strings.HasSuffix(download.FileName, ".tar") { + t.Fatalf("unexpected repository export: name=%s size=%d", download.FileName, len(exported)) + } + + if err := os.WriteFile(largePath, bytes.Repeat([]byte{0}, len(large)), 0o640); err != nil { + t.Fatalf("damage source before restore: %v", err) + } + if err := executionService.RestoreRecord(ctx, second.ID); err != nil { + t.Fatalf("restore repository record returned error: %v", err) + } + restored, err := os.ReadFile(largePath) + if err != nil { + t.Fatalf("read restored source: %v", err) + } + if !bytes.Equal(restored, large) { + t.Fatalf("repository restore did not reproduce the source") + } + + if err := recordService.Delete(ctx, first.ID); err != nil { + t.Fatalf("delete first repository record: %v", err) + } + if err := recordService.Delete(ctx, second.ID); err != nil { + t.Fatalf("delete second repository record: %v", err) + } + packRoot := filepath.Join(storageDir, filepath.FromSlash(".backupx/repository/v1/packs")) + remainingPacks := 0 + if err := filepath.Walk(packRoot, func(_ string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + if os.IsNotExist(walkErr) { + return nil + } + return walkErr + } + if info != nil && !info.IsDir() { + remainingPacks++ + } + return nil + }); err != nil { + t.Fatalf("inspect repository packs: %v", err) + } + if remainingPacks != 0 { + t.Fatalf("repository prune left %d packs after deleting all snapshots", remainingPacks) + } +} + func TestBackupExecutionServiceNodePoolSelectionDoesNotPersistTaskNodeID(t *testing.T) { executionService, _, tasks, _, records, _, _ := newExecutionTestServices(t) ctx := context.Background() @@ -326,6 +429,56 @@ func TestBackupExecutionServiceRestoreRecordRejectsRemoteLocalDisk(t *testing.T) } } +func TestBackupExecutionServiceDownloadsMasterRelayedLocalDiskRecord(t *testing.T) { + executionService, _, tasks, _, records, _, storageDir := newExecutionTestServices(t) + ctx := context.Background() + executionService.SetClusterDependencies(&nodeRepoStub{nodes: []model.Node{ + {ID: 10, Name: "edge-a", Token: "edge-a-token", Status: model.NodeStatusOnline}, + }}, &fakeDispatcher{}) + task, err := tasks.FindByID(ctx, 1) + if err != nil { + t.Fatalf("FindByID task returned error: %v", err) + } + storagePath := "file/2026/05/09/relayed.tar" + artifactPath := filepath.Join(storageDir, filepath.FromSlash(storagePath)) + if err := os.MkdirAll(filepath.Dir(artifactPath), 0o755); err != nil { + t.Fatalf("MkdirAll artifact parent returned error: %v", err) + } + content := []byte("stored on Master") + if err := os.WriteFile(artifactPath, content, 0o600); err != nil { + t.Fatalf("WriteFile artifact returned error: %v", err) + } + completedAt := time.Now().UTC() + record := &model.BackupRecord{ + TaskID: task.ID, + StorageTargetID: task.StorageTargetID, + NodeID: 10, + Status: model.BackupRecordStatusSuccess, + FileName: "relayed.tar", + FileSize: int64(len(content)), + StoragePath: storagePath, + StorageTransferMode: storage.TransferModeMasterRelay, + StartedAt: completedAt.Add(-time.Second), + CompletedAt: &completedAt, + } + if err := records.Create(ctx, record); err != nil { + t.Fatalf("Create record returned error: %v", err) + } + + download, err := executionService.DownloadRecord(ctx, record.ID) + if err != nil { + t.Fatalf("DownloadRecord returned error: %v", err) + } + got, readErr := io.ReadAll(download.Reader) + closeErr := download.Reader.Close() + if readErr != nil || closeErr != nil { + t.Fatalf("read relayed artifact: read=%v close=%v", readErr, closeErr) + } + if !bytes.Equal(got, content) { + t.Fatalf("downloaded content = %q, want %q", got, content) + } +} + func TestBackupExecutionServiceRecordsFirstSuccessfulStorageTarget(t *testing.T) { executionService, _, tasks, targets, records, _, _ := newExecutionTestServices(t) ctx := context.Background() diff --git a/server/internal/service/backup_record_service.go b/server/internal/service/backup_record_service.go index e4d7246..3b2461a 100644 --- a/server/internal/service/backup_record_service.go +++ b/server/internal/service/backup_record_service.go @@ -23,22 +23,23 @@ type BackupRecordListInput struct { } type BackupRecordSummary struct { - ID uint `json:"id"` - TaskID uint `json:"taskId"` - TaskName string `json:"taskName"` - StorageTargetID uint `json:"storageTargetId"` - StorageTargetName string `json:"storageTargetName"` - Status string `json:"status"` - FileName string `json:"fileName"` - FileSize int64 `json:"fileSize"` - Checksum string `json:"checksum"` - StoragePath string `json:"storagePath"` - DurationSeconds int `json:"durationSeconds"` - ErrorMessage string `json:"errorMessage"` - StartedAt time.Time `json:"startedAt"` - CompletedAt *time.Time `json:"completedAt,omitempty"` - Locked bool `json:"locked"` - BackupKind string `json:"backupKind"` + ID uint `json:"id"` + TaskID uint `json:"taskId"` + TaskName string `json:"taskName"` + StorageTargetID uint `json:"storageTargetId"` + StorageTargetName string `json:"storageTargetName"` + Status string `json:"status"` + FileName string `json:"fileName"` + FileSize int64 `json:"fileSize"` + Checksum string `json:"checksum"` + StoragePath string `json:"storagePath"` + StorageTransferMode string `json:"storageTransferMode,omitempty"` + DurationSeconds int `json:"durationSeconds"` + ErrorMessage string `json:"errorMessage"` + StartedAt time.Time `json:"startedAt"` + CompletedAt *time.Time `json:"completedAt,omitempty"` + Locked bool `json:"locked"` + BackupKind string `json:"backupKind"` } type BackupRecordDetail struct { @@ -184,22 +185,23 @@ func (s *BackupRecordService) SetLock(ctx context.Context, id uint, locked bool) func toBackupRecordSummary(item *model.BackupRecord) BackupRecordSummary { return BackupRecordSummary{ - ID: item.ID, - TaskID: item.TaskID, - TaskName: item.Task.Name, - StorageTargetID: item.StorageTargetID, - StorageTargetName: item.StorageTarget.Name, - Status: item.Status, - FileName: item.FileName, - FileSize: item.FileSize, - Checksum: item.Checksum, - StoragePath: item.StoragePath, - DurationSeconds: item.DurationSeconds, - ErrorMessage: item.ErrorMessage, - StartedAt: item.StartedAt, - CompletedAt: item.CompletedAt, - Locked: item.Locked, - BackupKind: item.BackupKind, + ID: item.ID, + TaskID: item.TaskID, + TaskName: item.Task.Name, + StorageTargetID: item.StorageTargetID, + StorageTargetName: item.StorageTarget.Name, + Status: item.Status, + FileName: item.FileName, + FileSize: item.FileSize, + Checksum: item.Checksum, + StoragePath: item.StoragePath, + StorageTransferMode: item.StorageTransferMode, + DurationSeconds: item.DurationSeconds, + ErrorMessage: item.ErrorMessage, + StartedAt: item.StartedAt, + CompletedAt: item.CompletedAt, + Locked: item.Locked, + BackupKind: item.BackupKind, } } diff --git a/server/internal/service/backup_task_service.go b/server/internal/service/backup_task_service.go index 998f327..87eee42 100644 --- a/server/internal/service/backup_task_service.go +++ b/server/internal/service/backup_task_service.go @@ -57,8 +57,8 @@ type BackupTaskUpsertInput struct { KeepWeekly int `json:"keepWeekly"` KeepMonthly int `json:"keepMonthly"` KeepYearly int `json:"keepYearly"` - // BackupMode 备份模式:full(默认)/ differential(差异,仅文件类型本机任务) - BackupMode string `json:"backupMode" binding:"omitempty,oneof=full differential"` + // BackupMode 备份模式:full(默认)/ differential(差异归档)/ repository(CDC 去重仓库) + BackupMode string `json:"backupMode" binding:"omitempty,oneof=full differential repository"` DiffFullIntervalDays int `json:"diffFullIntervalDays"` // 备份复制目标存储 ID 列表(3-2-1 规则) ReplicationTargetIDs []uint `json:"replicationTargetIds"` @@ -414,21 +414,50 @@ func (s *BackupTaskService) cleanupRemoteFiles(ctx context.Context, taskID uint) recordCount = len(records) // 缓存 provider 避免同一存储目标重复创建连接 providerCache := make(map[uint]storage.StorageProvider) + repositoryProviders := make(map[uint]storage.StorageProvider) for _, record := range records { - if strings.TrimSpace(record.StoragePath) == "" { - continue + copies := []StorageUploadResultItem{{ + StorageTargetID: record.StorageTargetID, + Status: model.BackupRecordStatusSuccess, + StoragePath: record.StoragePath, + }} + if strings.TrimSpace(record.StorageUploadResults) != "" { + var storedCopies []StorageUploadResultItem + if unmarshalErr := json.Unmarshal([]byte(record.StorageUploadResults), &storedCopies); unmarshalErr == nil { + copies = storedCopies + } } - provider, ok := providerCache[record.StorageTargetID] - if !ok { - provider, err = s.resolveStorageProvider(ctx, record.StorageTargetID) - if err != nil { + seenTargets := make(map[uint]struct{}, len(copies)) + for _, copy := range copies { + if !strings.EqualFold(copy.Status, model.BackupRecordStatusSuccess) || strings.TrimSpace(copy.StoragePath) == "" { continue } - providerCache[record.StorageTargetID] = provider + if _, seen := seenTargets[copy.StorageTargetID]; seen { + continue + } + seenTargets[copy.StorageTargetID] = struct{}{} + provider, ok := providerCache[copy.StorageTargetID] + if !ok { + provider, err = s.resolveStorageProvider(ctx, copy.StorageTargetID) + if err != nil { + continue + } + providerCache[copy.StorageTargetID] = provider + } + if err := provider.Delete(ctx, copy.StoragePath); err == nil { + cleanedFiles++ + if record.BackupKind == model.BackupKindRepository { + repositoryProviders[copy.StorageTargetID] = provider + } + } } - if err := provider.Delete(ctx, record.StoragePath); err == nil { - cleanedFiles++ + } + for _, provider := range repositoryProviders { + pruned, pruneErr := backup.NewRepositoryStore(s.cipher.Key()).Prune(ctx, provider) + if pruneErr != nil { + continue } + cleanedFiles += pruned.DeletedIndexes + pruned.DeletedPacks } return recordCount, cleanedFiles } @@ -530,6 +559,17 @@ func (s *BackupTaskService) validateInput(ctx context.Context, existing *model.B return apperror.BadRequest("BACKUP_TASK_DIFF_REMOTE_UNSUPPORTED", "差异备份当前仅支持本机 Master 执行,请将任务固定在本机或改用全量备份。", nil) } } + if strings.EqualFold(strings.TrimSpace(input.BackupMode), model.BackupModeRepository) { + if input.Type != model.BackupTaskTypeFile { + return apperror.BadRequest("BACKUP_TASK_REPOSITORY_UNSUPPORTED", "CDC 仓库模式仅支持文件目录类型任务", nil) + } + if strings.TrimSpace(input.NodePoolTag) != "" || (fixedNode != nil && !fixedNode.IsLocal) { + return apperror.BadRequest("BACKUP_TASK_REPOSITORY_REMOTE_UNSUPPORTED", "CDC 仓库模式当前采用单写者索引,仅支持 Master 本机执行。远程服务器备份请暂用全量模式。", nil) + } + if len(input.ReplicationTargetIDs) > 0 { + return apperror.BadRequest("BACKUP_TASK_REPOSITORY_REPLICATION_UNSUPPORTED", "CDC 仓库快照不能使用对象级复制;请直接为任务选择多个存储目标以生成完整仓库副本。", nil) + } + } if input.RetentionDays < 0 { return apperror.BadRequest("BACKUP_TASK_INVALID", "保留天数不能小于 0", nil) } @@ -935,10 +975,17 @@ func decodeExtraConfig(value string) (map[string]any, error) { return result, nil } -// normalizeBackupMode 归一化备份模式:仅文件类型可启用差异,其余一律全量(双保险,防绕过校验)。 +// normalizeBackupMode 归一化备份模式:仅文件类型可启用差异或 CDC 仓库, +// 其余一律全量(双保险,防绕过校验)。 func normalizeBackupMode(mode, taskType string) string { - if strings.EqualFold(strings.TrimSpace(mode), model.BackupModeDifferential) && normalizeBackupTaskType(taskType) == model.BackupTaskTypeFile { + if normalizeBackupTaskType(taskType) != model.BackupTaskTypeFile { + return model.BackupModeFull + } + switch strings.ToLower(strings.TrimSpace(mode)) { + case model.BackupModeDifferential: return model.BackupModeDifferential + case model.BackupModeRepository: + return model.BackupModeRepository } return model.BackupModeFull } diff --git a/server/internal/service/execution_helpers.go b/server/internal/service/execution_helpers.go index 4c04de5..2042a2c 100644 --- a/server/internal/service/execution_helpers.go +++ b/server/internal/service/execution_helpers.go @@ -140,6 +140,11 @@ func validateCrossNodeLocalDisk(ctx context.Context, nodeRepo repository.NodeRep if record == nil || record.NodeID == 0 || nodeRepo == nil { return nil } + // 中转模式的对象实际落在 Master 配置的本地磁盘,Master 可以安全访问。 + // 空值和 direct 均按旧版 Agent 本地落盘处理,保持升级兼容。 + if record.StorageTransferMode == storage.TransferModeMasterRelay { + return nil + } node, err := nodeRepo.FindByID(ctx, record.NodeID) if err != nil || node == nil || node.IsLocal { return nil diff --git a/server/internal/service/restore_service.go b/server/internal/service/restore_service.go index ba1c0db..5d67531 100644 --- a/server/internal/service/restore_service.go +++ b/server/internal/service/restore_service.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "os" "path/filepath" "strings" @@ -321,6 +322,13 @@ func (s *RestoreService) restoreArtifact(ctx context.Context, record *model.Back if err != nil { return fmt.Errorf("创建存储客户端失败:%w", err) } + if record.BackupKind == model.BackupKindRepository { + logger.Infof("读取 CDC 仓库快照:%s", record.StoragePath) + if err := backup.NewRepositoryStore(s.cipher.Key()).Restore(ctx, provider, record.StoragePath, record.Checksum, spec, logger); err != nil { + return fmt.Errorf("恢复 CDC 仓库快照失败:%w", err) + } + return nil + } recDir, err := os.MkdirTemp(parentTempDir, fmt.Sprintf("rec-%d-*", record.ID)) if err != nil { return fmt.Errorf("创建恢复子目录失败:%w", err) @@ -368,6 +376,9 @@ func (s *RestoreService) buildRestoreChain(ctx context.Context, record *model.Ba } func backupKindLabel(kind string) string { + if kind == model.BackupKindRepository { + return "CDC 仓库快照" + } if kind == model.BackupKindDifferential { return "差异" } @@ -591,15 +602,22 @@ func (s *RestoreService) GetAgentRestoreSpec(ctx context.Context, node *model.No if target == nil { return nil, apperror.BadRequest("BACKUP_STORAGE_TARGET_INVALID", "存储目标不存在", nil) } - configRaw, err := s.cipher.Decrypt(target.ConfigCiphertext) - if err != nil { - return nil, fmt.Errorf("decrypt storage config: %w", err) - } // 拆开 sourcePaths sourcePaths := []string{} if strings.TrimSpace(task.SourcePaths) != "" { _ = json.Unmarshal([]byte(task.SourcePaths), &sourcePaths) } + transferMode := storage.TransferModeDirect + if backupRecord.StorageTransferMode == storage.TransferModeMasterRelay { + transferMode = storage.TransferModeMasterRelay + } + var configRaw []byte + if transferMode == storage.TransferModeDirect { + configRaw, err = s.cipher.Decrypt(target.ConfigCiphertext) + if err != nil { + return nil, fmt.Errorf("decrypt storage config: %w", err) + } + } return &AgentRestoreSpec{ RestoreRecordID: restore.ID, BackupRecordID: backupRecord.ID, @@ -618,10 +636,11 @@ func (s *RestoreService) GetAgentRestoreSpec(ctx context.Context, node *model.No Compression: task.Compression, Encrypt: task.Encrypt, Storage: AgentStorageTargetConfig{ - ID: target.ID, - Type: target.Type, - Name: target.Name, - Config: json.RawMessage(configRaw), + ID: target.ID, + Type: target.Type, + Name: target.Name, + Config: json.RawMessage(configRaw), + TransferMode: transferMode, }, StoragePath: backupRecord.StoragePath, FileName: backupRecord.FileName, @@ -629,6 +648,63 @@ func (s *RestoreService) GetAgentRestoreSpec(ctx context.Context, node *model.No }, nil } +type AgentArtifactDownload struct { + Reader io.ReadCloser + Size int64 +} + +// DownloadAgentArtifact opens a Master-local object for authenticated streaming +// back to the Agent that owns the restore record. +func (s *RestoreService) DownloadAgentArtifact(ctx context.Context, node *model.Node, restoreID uint) (*AgentArtifactDownload, error) { + if node == nil { + return nil, apperror.Unauthorized("RESTORE_RECORD_FORBIDDEN", "恢复记录不属于当前节点", nil) + } + restore, err := s.restores.FindByID(ctx, restoreID) + if err != nil { + return nil, err + } + if restore == nil { + return nil, apperror.New(404, "RESTORE_RECORD_NOT_FOUND", "恢复记录不存在", nil) + } + if restore.NodeID != node.ID { + return nil, apperror.Unauthorized("RESTORE_RECORD_FORBIDDEN", "恢复记录不属于当前节点", nil) + } + if isRestoreRecordTerminal(restore.Status) { + return nil, apperror.BadRequest("RESTORE_RECORD_TERMINAL", "恢复记录已结束,不能继续下载产物", nil) + } + record, err := s.records.FindByID(ctx, restore.BackupRecordID) + if err != nil { + return nil, err + } + if record == nil { + return nil, apperror.New(404, "BACKUP_RECORD_NOT_FOUND", "源备份记录不存在", nil) + } + target, err := s.targets.FindByID(ctx, record.StorageTargetID) + if err != nil { + return nil, err + } + if target == nil || !strings.EqualFold(target.Type, storage.TypeLocalDisk) || record.StorageTransferMode != storage.TransferModeMasterRelay { + return nil, apperror.BadRequest("AGENT_ARTIFACT_RELAY_UNSUPPORTED", "该存储目标应由 Agent 直接下载", nil) + } + configMap := map[string]any{} + if err := s.cipher.DecryptJSON(target.ConfigCiphertext, &configMap); err != nil { + return nil, fmt.Errorf("decrypt storage config: %w", err) + } + provider, err := s.storageRegistry.Create(ctx, target.Type, configMap) + if err != nil { + return nil, fmt.Errorf("create master relay provider: %w", err) + } + reader, err := provider.Download(ctx, record.StoragePath) + if err != nil { + return nil, fmt.Errorf("open master relay artifact: %w", err) + } + size := record.FileSize + if size <= 0 { + size = -1 + } + return &AgentArtifactDownload{Reader: reader, Size: size}, nil +} + // UpdateAgentRestore Agent 回传状态/日志。 func (s *RestoreService) UpdateAgentRestore(ctx context.Context, node *model.Node, restoreID uint, update AgentRestoreUpdate) error { restore, err := s.restores.FindByID(ctx, restoreID) diff --git a/server/internal/service/restore_service_test.go b/server/internal/service/restore_service_test.go index 5991c9c..a411728 100644 --- a/server/internal/service/restore_service_test.go +++ b/server/internal/service/restore_service_test.go @@ -1,8 +1,10 @@ package service import ( + "bytes" "context" "encoding/json" + "io" "os" "path/filepath" "strings" @@ -427,16 +429,27 @@ func TestRestoreServiceAgentRestoreAccessUsesRestoreRecordNode(t *testing.T) { } startedAt := time.Now().UTC() completedAt := startedAt.Add(time.Second) + artifact := []byte("central backup artifact") + storagePath := "file/2026/05/09/remote.tar.gz" + artifactPath := filepath.Join(h.storageDir, filepath.FromSlash(storagePath)) + if err := os.MkdirAll(filepath.Dir(artifactPath), 0o755); err != nil { + t.Fatalf("MkdirAll artifact parent: %v", err) + } + if err := os.WriteFile(artifactPath, artifact, 0o600); err != nil { + t.Fatalf("WriteFile artifact: %v", err) + } backupRecord := &model.BackupRecord{ - TaskID: task.ID, - StorageTargetID: task.StorageTargetID, - NodeID: owner.ID, - Status: model.BackupRecordStatusSuccess, - FileName: "remote.tar.gz", - StoragePath: "file/2026/05/09/remote.tar.gz", - Checksum: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - StartedAt: startedAt, - CompletedAt: &completedAt, + TaskID: task.ID, + StorageTargetID: task.StorageTargetID, + NodeID: owner.ID, + Status: model.BackupRecordStatusSuccess, + FileName: "remote.tar.gz", + StoragePath: storagePath, + FileSize: int64(len(artifact)), + StorageTransferMode: storage.TransferModeMasterRelay, + Checksum: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + StartedAt: startedAt, + CompletedAt: &completedAt, } if err := h.records.Create(ctx, backupRecord); err != nil { t.Fatalf("Create backup record: %v", err) @@ -464,6 +477,21 @@ func TestRestoreServiceAgentRestoreAccessUsesRestoreRecordNode(t *testing.T) { if spec.Checksum != backupRecord.Checksum { t.Fatalf("expected spec.Checksum=%q, got %q", backupRecord.Checksum, spec.Checksum) } + if spec.Storage.TransferMode != storage.TransferModeMasterRelay { + t.Fatalf("expected Master relay restore, got %#v", spec.Storage) + } + download, err := h.service.DownloadAgentArtifact(ctx, owner, restore.ID) + if err != nil { + t.Fatalf("DownloadAgentArtifact returned error: %v", err) + } + downloaded, readErr := io.ReadAll(download.Reader) + closeErr := download.Reader.Close() + if readErr != nil || closeErr != nil { + t.Fatalf("read relayed restore artifact: read=%v close=%v", readErr, closeErr) + } + if !bytes.Equal(downloaded, artifact) { + t.Fatalf("relayed restore artifact differs: %q", downloaded) + } if _, err := h.service.GetAgentRestoreSpec(ctx, other, restore.ID); err == nil { t.Fatal("expected non-owner node to be forbidden from restore spec") } diff --git a/server/internal/service/verification_service.go b/server/internal/service/verification_service.go index 4350d8c..45e7f40 100644 --- a/server/internal/service/verification_service.go +++ b/server/internal/service/verification_service.go @@ -299,6 +299,20 @@ func (s *VerificationService) executeLocally(ctx context.Context, verID uint, ta logger.Errorf("创建存储客户端失败:%v", err) return } + if backupRecord.BackupKind == model.BackupKindRepository { + logger.Infof("验证 CDC 仓库快照及全部引用块:%s", backupRecord.StoragePath) + report, verifyErr := backup.NewRepositoryStore(s.cipher.Key()).Verify(ctx, provider, backupRecord.StoragePath, backupRecord.Checksum) + if verifyErr != nil { + errMessage = verifyErr.Error() + summary = "CDC 仓库完整性校验失败" + logger.Errorf("验证未通过:%v", verifyErr) + return + } + status = model.VerificationRecordStatusSuccess + summary = fmt.Sprintf("CDC 仓库完整性校验通过:%d 个条目、%d 个唯一块、%d bytes", report.Entries, report.Chunks, report.Bytes) + logger.Infof("%s", summary) + return + } fileName := backupRecord.FileName if strings.TrimSpace(fileName) == "" { fileName = filepath.Base(backupRecord.StoragePath) diff --git a/server/internal/storage/rclone/provider.go b/server/internal/storage/rclone/provider.go index 60f1257..ff33147 100644 --- a/server/internal/storage/rclone/provider.go +++ b/server/internal/storage/rclone/provider.go @@ -2,6 +2,7 @@ package rclone import ( "context" + "errors" "fmt" "io" "sort" @@ -68,13 +69,56 @@ func (p *Provider) Download(ctx context.Context, objectKey string) (io.ReadClose return reader, nil } +// DownloadRange reads one slice from an object. Most object-storage backends +// map this to a native HTTP Range request. Backends that reject ranged reads +// fall back to a full stream while preserving the same interface contract. +func (p *Provider) DownloadRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) { + if offset < 0 || length <= 0 { + return nil, fmt.Errorf("rclone download range %s: invalid offset=%d length=%d", objectKey, offset, length) + } + obj, err := p.rfs.NewObject(ctx, objectKey) + if err != nil { + return nil, fmt.Errorf("rclone find object %s: %w", objectKey, err) + } + reader, rangeErr := obj.Open(ctx, &fs.RangeOption{Start: offset, End: offset + length - 1}) + if rangeErr == nil { + return reader, nil + } + reader, err = obj.Open(ctx) + if err != nil { + return nil, fmt.Errorf("rclone download range %s (range: %v; fallback: %w)", objectKey, rangeErr, err) + } + if offset > 0 { + if _, err := io.CopyN(io.Discard, reader, offset); err != nil { + closeErr := reader.Close() + return nil, errors.Join(fmt.Errorf("rclone seek object %s: %w", objectKey, err), closeErr) + } + } + return &limitedReadCloser{Reader: io.LimitReader(reader, length), closer: reader}, nil +} + +type limitedReadCloser struct { + io.Reader + closer io.Closer +} + +func (r *limitedReadCloser) Close() error { + return r.closer.Close() +} + // Delete 通过 rclone 删除远端对象。 func (p *Provider) Delete(ctx context.Context, objectKey string) error { obj, err := p.rfs.NewObject(ctx, objectKey) if err != nil { + if errors.Is(err, fs.ErrorObjectNotFound) || errors.Is(err, fs.ErrorDirNotFound) { + return nil + } return fmt.Errorf("rclone find object %s: %w", objectKey, err) } if err := obj.Remove(ctx); err != nil { + if errors.Is(err, fs.ErrorObjectNotFound) || errors.Is(err, fs.ErrorDirNotFound) { + return nil + } return fmt.Errorf("rclone delete %s: %w", objectKey, err) } return nil @@ -102,6 +146,9 @@ func (p *Provider) List(ctx context.Context, prefix string) ([]storage.ObjectInf return nil }) if err != nil { + if errors.Is(err, fs.ErrorDirNotFound) || errors.Is(err, fs.ErrorObjectNotFound) { + return []storage.ObjectInfo{}, nil + } return nil, fmt.Errorf("rclone list %s: %w", prefix, err) } return items, nil diff --git a/server/internal/storage/types.go b/server/internal/storage/types.go index 92672e7..aae226a 100644 --- a/server/internal/storage/types.go +++ b/server/internal/storage/types.go @@ -34,6 +34,14 @@ const ( TypeFTP = string(ProviderTypeFTP) ) +const ( + // TransferModeDirect lets an Agent write to a network-accessible backend. + TransferModeDirect = "direct" + // TransferModeMasterRelay streams an artifact through the authenticated + // Agent API so a remote source can use storage mounted only on the Master. + TransferModeMasterRelay = "master_relay" +) + type ObjectInfo struct { Key string `json:"key"` Size int64 `json:"size"` @@ -49,6 +57,13 @@ type StorageProvider interface { List(ctx context.Context, prefix string) ([]ObjectInfo, error) } +// StorageRangeDownloader is an optional capability used by packed repository +// backups. Implementations return exactly the requested byte range when the +// backend supports ranged reads and may transparently fall back to a full read. +type StorageRangeDownloader interface { + DownloadRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) +} + type ProviderFactory interface { Type() ProviderType } @@ -92,7 +107,8 @@ func ParseProviderType(value string) ProviderType { } type LocalDiskConfig struct { - BasePath string `json:"basePath"` + BasePath string `json:"basePath"` + MasterRelay bool `json:"masterRelay"` } type S3Config struct { @@ -151,4 +167,3 @@ type FTPConfig struct { type StorageDirCleaner interface { RemoveEmptyDirs(ctx context.Context, prefix string) error } - diff --git a/web/src/components/backup-records/BackupRecordLogDrawer.tsx b/web/src/components/backup-records/BackupRecordLogDrawer.tsx index b0e35c6..9380ef6 100644 --- a/web/src/components/backup-records/BackupRecordLogDrawer.tsx +++ b/web/src/components/backup-records/BackupRecordLogDrawer.tsx @@ -280,6 +280,10 @@ export function BackupRecordLogDrawer({ visible, recordId, onCancel, onChanged } { label: '文件名', value: record.fileName || '-' }, { label: '文件大小', value: formatBytes(record.fileSize) }, { label: '存储路径', value: record.storagePath || '-' }, + ...(record.storageTransferMode ? [{ + label: '传输路径', + value: record.storageTransferMode === 'master_relay' ? 'Master 流式中转' : 'Agent 直传', + }] : []), { label: '开始时间', value: formatDateTime(record.startedAt) }, { label: '完成时间', value: formatDateTime(record.completedAt) }, { label: '耗时', value: formatDuration(record.durationSeconds) }, @@ -316,14 +320,16 @@ export function BackupRecordLogDrawer({ visible, recordId, onCancel, onChanged } )} - {record.storageUploadResults && record.storageUploadResults.length > 1 && ( + {record.storageUploadResults && (record.storageUploadResults.length > 1 || record.storageUploadResults.some((result) => result.transferMode)) && (
存储目标上传结果 ({ label: r.storageTargetName, - value: r.status === 'success' ? '上传成功' : `上传失败: ${r.error || '未知错误'}`, + value: r.status === 'success' + ? `上传成功${r.transferMode === 'master_relay' ? ' · Master 流式中转' : r.transferMode === 'direct' ? ' · Agent 直传' : ''}` + : `上传失败: ${r.error || '未知错误'}`, }))} />
diff --git a/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx b/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx index 7a24079..71ee7f4 100644 --- a/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx +++ b/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx @@ -1,5 +1,5 @@ import { Alert, Button, Divider, Drawer, Input, InputNumber, Select, Space, Steps, Switch, Typography, Grid } from '@arco-design/web-react' -import { IconDelete, IconPlus } from '@arco-design/web-react/icon' +import { IconDelete, IconPlus } from '../icons' import { useEffect, useMemo, useState } from 'react' import { CronInput } from '../CronInput' import type { StorageTargetDetail, StorageTargetPayload, StorageTargetSummary } from '../../types/storage-targets' @@ -9,6 +9,8 @@ import type { NodeSummary } from '../../types/nodes' import { DatabasePicker } from '../common/DatabasePicker' import { DirectoryPicker } from '../common/DirectoryPicker' import { StorageTargetFormDrawer } from '../storage-targets/StorageTargetFormDrawer' +import { StorageTargetName } from '../storage-targets/StorageTargetName' +import { SourceServerSelector } from './SourceServerSelector' import { backupCompressionOptions, backupTaskTypeOptions, @@ -168,7 +170,7 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa return 0 }) return sorted.map((item) => ({ - label: item.starred ? `★ ${item.name}` : item.name, + label: , value: item.id, disabled: !item.enabled, })) @@ -176,21 +178,6 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa [storageTargets], ) - // 执行节点选项:本地节点显示 "本机 (local)",远程节点带状态后缀 - const nodeOptions = useMemo(() => { - const list = nodes ?? [] - return [ - { label: '本机 (Master)', value: 0 }, - ...list - .filter((item) => !item.isLocal) - .map((item) => ({ - label: `${item.name}${item.status === 'online' ? '' : '(离线)'}`, - value: item.id, - disabled: item.status !== 'online', - })), - ] - }, [nodes]) - function updateDraft(patch: Partial) { setDraft((current) => ({ ...current, ...patch })) } @@ -251,6 +238,12 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa if (validPaths.length === 0 && !value.sourcePath.trim()) { return '请输入至少一个源路径' } + if (value.backupMode === 'repository' && (((value.nodeId ?? 0) > 0 && value.nodeId !== localNodeId) || value.nodePoolTag?.trim())) { + return 'CDC 仓库模式当前仅支持 Master 本机执行' + } + if (value.backupMode === 'repository' && value.replicationTargetIds.length > 0) { + return 'CDC 仓库模式请直接多选存储目标,不能使用对象级副本复制' + } } if (isSQLiteBackupTask(value.type) && !value.dbPath.trim()) { return '请输入 SQLite 数据库路径' @@ -306,33 +299,26 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa 备份类型 { - const nodeId = Number(value ?? 0) - // 固定节点与节点池互斥:切到固定节点时清空 NodePoolTag - updateDraft(nodeId > 0 ? { nodeId, nodePoolTag: '' } : { nodeId }) - }} - /> - - 任务在所选节点上执行备份与恢复;源路径/数据库以该节点视角解析。远程节点需先在"节点管理"中安装 Agent。 - - -
- 节点池标签(可选) - 0} - onChange={(value) => updateDraft({ nodePoolTag: value })} - /> - - 执行节点选"本机 / 未指定"时可启用;从节点 Labels 命中此 tag 的在线节点中按当前运行任务数最少的挑选一台执行。 - -
+ { + // 固定源服务器与服务器池互斥;CDC 仓库仍固定在 Master 单写者。 + updateDraft(nodeId > 0 + ? { + nodeId, + nodePoolTag: '', + backupMode: nodeId !== localNodeId && draft.backupMode === 'repository' ? 'full' : draft.backupMode, + } + : { nodeId }) + }} + onNodePoolTagChange={(value) => updateDraft({ + nodePoolTag: value, + backupMode: value.trim() && draft.backupMode === 'repository' ? 'full' : draft.backupMode, + })} + />
Cron 表达式 updateDraft({ cronExpr: value })} /> @@ -587,6 +573,11 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa )} + {((draft.nodeId ?? 0) > 0 && draft.nodeId !== localNodeId) || draft.nodePoolTag?.trim() ? ( + + 远程源服务器会直传 S3、WebDAV 等网络存储;本地磁盘目标启用 Master 中转后,文件经 Agent 认证 API 流式写入中央目录。跨公网部署请为 Master 配置 HTTPS。 + + ) : null}
压缩策略 @@ -600,8 +591,11 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa options={[ { label: '全量备份', value: 'full' }, { label: '差异备份(仅文件、本机)', value: 'differential' }, + { label: 'CDC 去重仓库(仅文件、本机)', value: 'repository' }, ]} - onChange={(value) => updateDraft({ backupMode: value as BackupMode })} + onChange={(value) => updateDraft(value === 'repository' + ? { backupMode: value as BackupMode, nodeId: 0, nodePoolTag: '', replicationTargetIds: [] } + : { backupMode: value as BackupMode })} /> {draft.backupMode === 'differential' && (
@@ -618,6 +612,11 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa />
)} + {draft.backupMode === 'repository' && ( + + 文件按内容边界切块并写入全局分块池;相同数据跨文件、跨快照只上传一次。新块会合并为 pack,恢复时通过索引按需读取。当前版本采用单写者索引,因此固定在 Master 本机执行;需要多副本时请直接多选上方存储目标。 + + )}
)}
@@ -736,10 +735,13 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa value={draft.replicationTargetIds} placeholder="选择副本目标(不选 = 不启用复制)" options={storageTargetOptions.filter((opt) => !(draft.storageTargetIds ?? []).includes(opt.value as number))} + disabled={draft.backupMode === 'repository'} onChange={(values: number[]) => updateDraft({ replicationTargetIds: values })} /> - 备份成功后自动镜像到副本存储。满足 3-2-1 规则:至少 2 份副本、至少 1 份异地。建议选不同 provider 的目标。 + {draft.backupMode === 'repository' + ? 'CDC 仓库包含共享 pack 与索引,不能只复制单个快照对象;请在“存储目标”中直接多选以生成完整仓库副本。' + : '备份成功后自动镜像到副本存储。满足 3-2-1 规则:至少 2 份副本、至少 1 份异地。建议选不同 provider 的目标。'}
diff --git a/web/src/components/backup-tasks/SourceServerSelector.test.ts b/web/src/components/backup-tasks/SourceServerSelector.test.ts new file mode 100644 index 0000000..23baa10 --- /dev/null +++ b/web/src/components/backup-tasks/SourceServerSelector.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest' +import type { NodeSummary } from '../../types/nodes' +import { buildSourceServerOptions } from './SourceServerSelector' + +function node(id: number, name: string, status: NodeSummary['status'], isLocal = false): NodeSummary { + return { + id, + name, + status, + isLocal, + hostname: '', + ipAddress: '', + os: '', + arch: '', + agentVersion: '', + lastSeen: '', + createdAt: '', + } +} + +describe('buildSourceServerOptions', () => { + it('keeps Master first and disables offline remote sources', () => { + const options = buildSourceServerOptions([ + node(1, 'local', 'online', true), + node(2, 'source-b', 'online'), + node(3, 'source-c', 'offline'), + ]) + + expect(options).toEqual([ + { label: 'Master 本机', value: 0, disabled: false }, + { label: 'source-b', value: 2, disabled: false }, + { label: 'source-c(离线)', value: 3, disabled: true }, + ]) + }) +}) diff --git a/web/src/components/backup-tasks/SourceServerSelector.tsx b/web/src/components/backup-tasks/SourceServerSelector.tsx new file mode 100644 index 0000000..86b5396 --- /dev/null +++ b/web/src/components/backup-tasks/SourceServerSelector.tsx @@ -0,0 +1,57 @@ +import { Input, Select, Typography } from '@arco-design/web-react' +import { useMemo } from 'react' +import type { NodeSummary } from '../../types/nodes' + +interface SourceServerSelectorProps { + nodeId: number + nodePoolTag: string + localNodeId?: number + nodes?: NodeSummary[] + onNodeChange: (nodeId: number) => void + onNodePoolTagChange: (tag: string) => void +} + +export function buildSourceServerOptions(nodes: NodeSummary[] = []) { + return [ + { label: 'Master 本机', value: 0, disabled: false }, + ...nodes + .filter((node) => !node.isLocal) + .map((node) => ({ + label: `${node.name}${node.status === 'online' ? '' : '(离线)'}`, + value: node.id, + disabled: node.status !== 'online', + })), + ] +} + +export function SourceServerSelector({ nodeId, nodePoolTag, localNodeId, nodes, onNodeChange, onNodePoolTagChange }: SourceServerSelectorProps) { + const options = useMemo(() => buildSourceServerOptions(nodes), [nodes]) + const selectedNode = nodes?.find((node) => node.id === nodeId) + const isRemote = nodeId > 0 && nodeId !== localNodeId + + return ( + <> +
+ 源服务器 + 0} + onChange={onNodePoolTagChange} + /> + + 仅在选择 Master 本机时可填写;系统从 Labels 命中该标签的在线 Agent 中选择当前运行任务最少的一台。 + +
+ + ) +} diff --git a/web/src/components/common/DirectoryPicker.tsx b/web/src/components/common/DirectoryPicker.tsx index c24b15a..5129868 100644 --- a/web/src/components/common/DirectoryPicker.tsx +++ b/web/src/components/common/DirectoryPicker.tsx @@ -1,5 +1,5 @@ import { Button, Input, Message, Modal, Space, Spin, Tree, Typography, Empty } from '@arco-design/web-react' -import { IconFolder, IconFile, IconFolderAdd } from '@arco-design/web-react/icon' +import { IconFolder, IconFile, IconFolderAdd } from '../icons' import { useCallback, useState } from 'react' import { listNodeDirectory } from '../../services/nodes' import type { DirEntry } from '../../types/nodes' diff --git a/web/src/components/common/EventCenter.tsx b/web/src/components/common/EventCenter.tsx index 42561cf..fa20f93 100644 --- a/web/src/components/common/EventCenter.tsx +++ b/web/src/components/common/EventCenter.tsx @@ -1,5 +1,5 @@ import { Badge, Button, Drawer, Empty, Notification, Space, Tag, Typography } from '@arco-design/web-react' -import { IconNotification } from '@arco-design/web-react/icon' +import { IconNotification } from '../icons' import { useEffect, useState } from 'react' import { useEventStream, type SystemEvent } from '../../hooks/useEventStream' import { useEventStore } from '../../stores/events' diff --git a/web/src/components/common/GlobalSearch.tsx b/web/src/components/common/GlobalSearch.tsx index 3a319a2..dcfc2bf 100644 --- a/web/src/components/common/GlobalSearch.tsx +++ b/web/src/components/common/GlobalSearch.tsx @@ -1,5 +1,5 @@ import { Empty, Input, Modal, Space, Spin, Tag, Typography } from '@arco-design/web-react' -import { IconSearch } from '@arco-design/web-react/icon' +import { IconSearch } from '../icons' import { useCallback, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { globalSearch, type SearchKind, type SearchResult, type SearchResultItem } from '../../services/search' diff --git a/web/src/components/common/LanguageSwitcher.tsx b/web/src/components/common/LanguageSwitcher.tsx new file mode 100644 index 0000000..10a89f8 --- /dev/null +++ b/web/src/components/common/LanguageSwitcher.tsx @@ -0,0 +1,18 @@ +import { Select } from '@arco-design/web-react' +import { useTranslation } from 'react-i18next' +import { languageOptions, normalizeLanguage, setApplicationLanguage, type SupportedLanguage } from '../../i18n' + +export function LanguageSwitcher() { + const { t, i18n } = useTranslation() + const currentLanguage = normalizeLanguage(i18n.resolvedLanguage) + + return ( + } size="large" /> + + } size="large" /> - - } size="large" /> + + } size="large" /> - - } size="large" /> + + } size="large" /> - ) : ( form={loginForm} layout="vertical" onSubmit={handleLogin}> - - } size="large" onChange={resetTwoFactorPrompt} /> + + } size="large" onChange={resetTwoFactorPrompt} /> - - } size="large" onChange={resetTwoFactorPrompt} /> + + } size="large" onChange={resetTwoFactorPrompt} /> {twoFactorRequired && ( <> - - } size="large" maxLength={32} /> + + } size="large" maxLength={32} /> - - - + + + - 信任此设备 30 天 + {t('auth.trustDevice')} )} - )} diff --git a/web/src/pages/nodes/BatchCommandTable.tsx b/web/src/pages/nodes/BatchCommandTable.tsx index b96f82d..c724e45 100644 --- a/web/src/pages/nodes/BatchCommandTable.tsx +++ b/web/src/pages/nodes/BatchCommandTable.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Table, Button, Space, Message, Typography, Tag } from '@arco-design/web-react' -import { IconCopy, IconDownload, IconRefresh } from '@arco-design/web-react/icon' +import { IconCopy, IconDownload, IconRefresh } from '../../components/icons' const { Text } = Typography diff --git a/web/src/pages/nodes/NodesPage.tsx b/web/src/pages/nodes/NodesPage.tsx index 9587078..bdcd079 100644 --- a/web/src/pages/nodes/NodesPage.tsx +++ b/web/src/pages/nodes/NodesPage.tsx @@ -5,7 +5,7 @@ import { } from '@arco-design/web-react' import { IconPlus, IconDelete, IconDesktop, IconCloudDownload, IconEdit, IconMore, -} from '@arco-design/web-react/icon' +} from '../../components/icons' import type { NodeSummary } from '../../types/nodes' import { listNodes, deleteNode, updateNode, rotateNodeToken } from '../../services/nodes' import { fetchSystemInfo } from '../../services/system' diff --git a/web/src/pages/nodes/wizard/Step3CommandPreview.tsx b/web/src/pages/nodes/wizard/Step3CommandPreview.tsx index 9000060..455119f 100644 --- a/web/src/pages/nodes/wizard/Step3CommandPreview.tsx +++ b/web/src/pages/nodes/wizard/Step3CommandPreview.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Typography, Button, Space, Collapse, Spin, Message, Tag } from '@arco-design/web-react' -import { IconCopy, IconRefresh } from '@arco-design/web-react/icon' +import { IconCopy, IconRefresh } from '../../../components/icons' import { fetchScriptPreview } from '../../../services/nodes' import type { InstallTokenResult, InstallMode } from '../../../types/nodes' import { buildAgentDownloadCommand, buildAgentInstallCommand, buildEmbeddedAgentInstallCommand } from '../installCommands' diff --git a/web/src/pages/reports/ReportsPage.tsx b/web/src/pages/reports/ReportsPage.tsx index d26d6d7..d6fec7b 100644 --- a/web/src/pages/reports/ReportsPage.tsx +++ b/web/src/pages/reports/ReportsPage.tsx @@ -1,5 +1,5 @@ import { Button, Card, Grid, Message, Select, Space, Statistic, Table, Tag, Typography } from '@arco-design/web-react' -import { IconDownload, IconRefresh } from '@arco-design/web-react/icon' +import { IconDownload, IconRefresh } from '../../components/icons' import { useCallback, useEffect, useState } from 'react' import { downloadComplianceCSV, fetchComplianceReport } from '../../services/reports' import type { ComplianceReport, ComplianceRisk, ComplianceTaskRow } from '../../types/reports' diff --git a/web/src/pages/storage-targets/StorageTargetsPage.tsx b/web/src/pages/storage-targets/StorageTargetsPage.tsx index c987f0a..7e062d1 100644 --- a/web/src/pages/storage-targets/StorageTargetsPage.tsx +++ b/web/src/pages/storage-targets/StorageTargetsPage.tsx @@ -18,6 +18,7 @@ import { formatBytes } from '../../utils/format' import type { StorageConnectionTestResult, StorageTargetDetail, StorageTargetPayload, StorageTargetSummary } from '../../types/storage-targets' import { getStorageTargetTypeLabel } from '../../components/storage-targets/field-config' import { StorageTargetFormDrawer } from '../../components/storage-targets/StorageTargetFormDrawer' +import { StorageTargetName } from '../../components/storage-targets/StorageTargetName' function resolveErrorMessage(error: unknown) { if (axios.isAxiosError(error)) { @@ -224,7 +225,7 @@ export function StorageTargetsPage() {
- {target.starred ? '★ ' : ''}{target.name} + {getStorageTargetTypeLabel(target.type) && {getStorageTargetTypeLabel(target.type)}} diff --git a/web/src/styles/global.css b/web/src/styles/global.css index 3b58508..cf28e30 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -42,48 +42,25 @@ body { .login-bg { position: fixed; inset: 0; - background: linear-gradient(135deg, #111a2c 0%, #1f2d47 100%); + background: #111a2c; z-index: 0; } -.login-bg::before { - content: ''; - position: absolute; - width: 800px; - height: 800px; - border-radius: 50%; - background: radial-gradient(circle, rgba(52,145,250,0.08) 0%, transparent 70%); - top: -300px; - right: -200px; -} - -.login-bg::after { - content: ''; - position: absolute; - width: 600px; - height: 600px; - border-radius: 50%; - background: radial-gradient(circle, rgba(114,46,209,0.06) 0%, transparent 70%); - bottom: -200px; - left: -100px; -} - .login-container { display: flex; width: 1000px; max-width: 90vw; min-height: 560px; background: var(--color-bg-2); - border-radius: 20px; + border-radius: 4px; overflow: hidden; - box-shadow: 0 20px 60px rgba(0,0,0,0.4); z-index: 1; animation: slideUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } .login-banner { flex: 1; - background: linear-gradient(135deg, var(--color-primary-6, #165dff) 0%, var(--color-primary-8, #0e42d2) 100%); + background: var(--color-primary-6, #165dff); position: relative; display: flex; align-items: center; diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts index a866f69..a9a4ac5 100644 --- a/web/src/test/setup.ts +++ b/web/src/test/setup.ts @@ -24,3 +24,17 @@ Object.defineProperty(window, 'localStorage', { value: storage, configurable: true, }) + +Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addEventListener: () => undefined, + removeEventListener: () => undefined, + addListener: () => undefined, + removeListener: () => undefined, + dispatchEvent: () => false, + }), +}) diff --git a/web/src/types/backup-records.ts b/web/src/types/backup-records.ts index 01c980f..a6debfc 100644 --- a/web/src/types/backup-records.ts +++ b/web/src/types/backup-records.ts @@ -21,12 +21,13 @@ export interface BackupRecordSummary { fileSize: number checksum: string storagePath: string + storageTransferMode?: 'direct' | 'master_relay' durationSeconds: number errorMessage: string startedAt: string completedAt?: string locked: boolean - backupKind: 'full' | 'differential' + backupKind: 'full' | 'differential' | 'repository' } export interface BackupRecordContentEntry { @@ -49,6 +50,7 @@ export interface StorageUploadResultItem { status: 'success' | 'failed' storagePath?: string fileSize?: number + transferMode?: 'direct' | 'master_relay' error?: string } diff --git a/web/src/types/backup-tasks.ts b/web/src/types/backup-tasks.ts index b254ad1..eeeeb20 100644 --- a/web/src/types/backup-tasks.ts +++ b/web/src/types/backup-tasks.ts @@ -1,7 +1,7 @@ export type BackupTaskType = 'file' | 'mysql' | 'sqlite' | 'postgresql' | 'saphana' | 'mongodb' export type BackupTaskStatus = 'idle' | 'running' | 'success' | 'failed' export type BackupCompression = 'gzip' | 'zstd' | 'none' -export type BackupMode = 'full' | 'differential' +export type BackupMode = 'full' | 'differential' | 'repository' export interface BackupTaskSummary { id: number From 5827074334b1963db746605875b331e8a5adc93c Mon Sep 17 00:00:00 2001 From: Wu Qing <3184394176@qq.com> Date: Sun, 9 Aug 2026 02:45:17 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=9B=86?= =?UTF-8?q?=E7=BE=A4=E9=83=A8=E7=BD=B2=E4=B8=8E=E5=A0=A1=E5=9E=92=E6=9C=BA?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=20(#106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持受限网络、正向代理、私有 CA 与 SSH 堡垒机部署 Agent。 加固 Docker、systemd、Nginx、安装器、Release 校验与可信代理边界,并完善命令队列索引、前端安装向导及中英文运维文档。 --- .github/workflows/release.yml | 5 + Dockerfile | 27 +- README.md | 2 +- README.zh-CN.md | 2 +- deploy/backupx.service | 1 + deploy/docker/entrypoint.sh | 37 +- deploy/docker/nginx.conf | 51 - deploy/install.sh | 87 +- deploy/nginx.conf | 10 +- docker-compose.yml | 38 +- docs-site/docs/deployment/bare-metal.md | 17 +- docs-site/docs/deployment/configuration.md | 22 +- docs-site/docs/deployment/docker.md | 81 +- docs-site/docs/deployment/nginx.md | 9 +- docs-site/docs/features/multi-node.md | 276 +- .../docs/getting-started/installation.md | 7 +- docs-site/docs/intro.md | 2 +- docs-site/docs/reference/cli.md | 5 +- docs-site/i18n/zh-CN/code.json | 2 +- .../current/deployment/bare-metal.md | 17 +- .../current/deployment/configuration.md | 22 +- .../current/deployment/docker.md | 77 +- .../current/deployment/nginx.md | 9 +- .../current/features/multi-node.md | 274 +- .../current/getting-started/installation.md | 7 +- .../current/intro.md | 2 +- .../current/reference/cli.md | 5 +- docs-site/package-lock.json | 4250 ++++++++--------- docs-site/package.json | 12 +- .../src/components/HomepageFeatures/index.tsx | 2 +- server/cmd/backupx/agent.go | 16 +- server/config.example.yaml | 3 + server/internal/agent/agent.go | 7 +- server/internal/agent/client.go | 52 +- server/internal/agent/client_test.go | 93 + server/internal/agent/config.go | 109 +- server/internal/agent/config_test.go | 50 +- server/internal/backup/file_runner.go | 4 +- server/internal/config/config.go | 25 + server/internal/config/config_test.go | 44 + server/internal/database/database.go | 10 +- server/internal/database/database_test.go | 40 + server/internal/http/agent_handler.go | 10 +- .../internal/http/forwarded_headers_test.go | 49 + server/internal/http/install_flow_test.go | 44 +- server/internal/http/install_handler.go | 19 +- server/internal/http/middleware.go | 41 + server/internal/http/node_handler.go | 42 +- server/internal/http/router.go | 4 + .../installscript/deploy_install_test.go | 77 +- server/internal/installscript/issue46_test.go | 2 +- server/internal/installscript/renderer.go | 85 + .../internal/installscript/renderer_test.go | 153 +- .../templates/agent-compose.yml.tmpl | 14 +- .../templates/agent-install.sh.tmpl | 170 +- server/internal/model/agent_command.go | 32 +- server/internal/model/agent_install_token.go | 26 +- .../agent_command_repository_test.go | 18 + .../agent_install_token_repository_test.go | 5 + .../backup_record_repository_test.go | 5 + .../repository/backup_task_repository_test.go | 5 + .../repository/node_repository_test.go | 5 + .../notification_repository_test.go | 5 + .../oauth_session_repository_test.go | 5 + .../restore_record_repository_test.go | 5 + .../storage_target_repository_test.go | 5 + .../internal/service/install_token_service.go | 79 +- .../service/install_token_service_test.go | 76 +- web/package-lock.json | 491 +- web/package.json | 10 +- web/src/pages/nodes/AgentInstallWizard.tsx | 48 +- web/src/pages/nodes/BatchCommandTable.tsx | 2 +- web/src/pages/nodes/NodesPage.tsx | 4 +- web/src/pages/nodes/installCommands.test.ts | 19 +- web/src/pages/nodes/installCommands.ts | 39 +- .../pages/nodes/useAgentDeployFlow.test.ts | 18 + web/src/pages/nodes/useAgentDeployFlow.ts | 9 +- .../wizard/AgentConnectionOptions.test.ts | 34 + .../nodes/wizard/AgentConnectionOptions.tsx | 110 + .../nodes/wizard/InstallCommandBlock.tsx | 30 + web/src/pages/nodes/wizard/Step1NodeName.tsx | 6 +- .../nodes/wizard/Step2DeployOptions.test.ts | 12 + .../pages/nodes/wizard/Step2DeployOptions.tsx | 22 +- .../nodes/wizard/Step3CommandPreview.tsx | 91 +- web/src/services/nodes.ts | 10 +- web/src/types/nodes.ts | 3 + 86 files changed, 4668 insertions(+), 3082 deletions(-) delete mode 100644 deploy/docker/nginx.conf create mode 100644 server/internal/agent/client_test.go create mode 100644 server/internal/database/database_test.go create mode 100644 server/internal/http/forwarded_headers_test.go create mode 100644 web/src/pages/nodes/wizard/AgentConnectionOptions.test.ts create mode 100644 web/src/pages/nodes/wizard/AgentConnectionOptions.tsx create mode 100644 web/src/pages/nodes/wizard/InstallCommandBlock.tsx create mode 100644 web/src/pages/nodes/wizard/Step2DeployOptions.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 373eb75..342203a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,6 +110,7 @@ jobs: cp -r web/dist "${ARCHIVE_NAME}/web" cp server/config.example.yaml "${ARCHIVE_NAME}/" cp deploy/install.sh "${ARCHIVE_NAME}/" 2>/dev/null || true + cp deploy/backupx.service "${ARCHIVE_NAME}/" 2>/dev/null || true # v2.2+: 随发布包提供 Grafana dashboard 与 nginx.conf 模板 if [ -d deploy/grafana ]; then cp -r deploy/grafana "${ARCHIVE_NAME}/grafana" @@ -117,6 +118,8 @@ jobs: cp deploy/nginx.conf "${ARCHIVE_NAME}/nginx.conf" 2>/dev/null || true tar czf "${ARCHIVE_NAME}.tar.gz" "${ARCHIVE_NAME}" cp "${ARCHIVE_NAME}.tar.gz" "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" + sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256" + sha256sum "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" > "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256" - name: Upload to GitHub Release uses: softprops/action-gh-release@v2 @@ -124,7 +127,9 @@ jobs: tag_name: ${{ env.VERSION }} files: | backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz + backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256 backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz + backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256 generate_release_notes: true # ─── Job 3: Docker 多架构 → Docker Hub ─── diff --git a/Dockerfile b/Dockerfile index 02e4695..88299cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,12 +51,11 @@ RUN if [ "$USE_CHINA_MIRROR" = "true" ]; then \ sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories; \ fi +# Database client binaries are required by MySQL and PostgreSQL backup tasks. RUN apk add --no-cache \ - nginx \ tzdata \ ca-certificates \ - docker-cli docker-cli-compose \ - # Required by mysql/postgresql backup tasks + su-exec \ mysql-client \ postgresql16-client \ && rm -rf /var/cache/apk/* @@ -70,24 +69,30 @@ COPY --from=server-builder /build/server/backupx /app/bin/backupx # Copy frontend static files COPY --from=web-builder /build/web/dist /app/web -# Copy nginx config -COPY deploy/docker/nginx.conf /etc/nginx/http.d/default.conf - # Copy entrypoint COPY deploy/docker/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh # Create data directories RUN mkdir -p /app/data /tmp/backupx && \ - chown -R backupx:backupx /app /tmp/backupx - -# Nginx needs to write to these dirs -RUN mkdir -p /var/lib/nginx/tmp /var/log/nginx && \ - chown -R backupx:backupx /var/lib/nginx /var/log/nginx /run/nginx + touch /app/data/.backupx-owner-v2 && \ + chown -R backupx:backupx /app/data /tmp/backupx && \ + chmod 0750 /app/data /tmp/backupx WORKDIR /app EXPOSE 8340 VOLUME ["/app/data"] +ENV BACKUPX_SERVER_HOST=0.0.0.0 \ + BACKUPX_SERVER_PORT=8340 \ + BACKUPX_SERVER_WEB_ROOT=/app/web + +USER root + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD su-exec backupx:backupx wget -q -T 3 -O /dev/null http://127.0.0.1:8340/ready || exit 1 + +STOPSIGNAL SIGTERM + ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/README.md b/README.md index 3e391ab..62bac76 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ | **SAP HANA Backint Agent** | Built-in Backint protocol — HANA's native interface routes data directly to any BackupX storage backend | | **70+ Storage Backends** | Alibaba OSS, Tencent COS, Qiniu, S3, Google Drive, WebDAV, FTP + SFTP, Azure Blob, Dropbox, OneDrive and dozens more via rclone | | **Scheduling** | Cron + visual editor + auto-retention (by days/count + empty-directory cleanup) | -| **Multi-Node Cluster** | Master-Agent mode via HTTP long-polling — Agents run tasks locally, upload straight to storage, no reverse connectivity required | +| **Multi-Node Cluster** | Outbound-only Master-Agent polling with proxy, private-CA, and SSH-bastion support; Agents run tasks locally with no reverse connectivity required | | **Security** | JWT + bcrypt + AES-256-GCM encrypted config + optional backup encryption + full audit log | | **Notifications** | Email / Webhook / Telegram on success or failure | | **Observability** | Prometheus `/metrics` endpoint + `/health` + `/ready` probes + SLA breach gauge | diff --git a/README.zh-CN.md b/README.zh-CN.md index e6a0b27..fbda82a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -43,7 +43,7 @@ | **SAP HANA Backint 代理** | 内置 SAP HANA Backint 协议代理,HANA 原生备份接口可直接把数据路由到 BackupX 支持的任意存储后端 | | **70+ 存储后端** | 内置阿里云 OSS / 腾讯云 COS / 七牛云 / S3 / Google Drive / WebDAV / FTP + 通过 rclone 集成 SFTP、Azure Blob、Dropbox、OneDrive 等 70+ 后端 | | **自动调度** | Cron 定时 + 可视化编辑器 + 自动保留策略(按天数/份数清理,自动回收空目录) | -| **多节点集群** | Master-Agent 模式,基于 HTTP 长轮询跨多台服务器管理备份。Agent 本地执行任务并直接上传到存储,无需反向连通性 | +| **多节点集群** | Master-Agent 模式,基于 Agent 主动出站的 HTTP 轮询跨服务器管理备份,支持代理、私有 CA 与 SSH 堡垒机,无需反向连通性 | | **安全** | JWT + bcrypt + AES-256-GCM 加密配置 + 可选备份文件加密 + 完整审计日志 | | **通知** | 邮件 / Webhook / Telegram,备份成功或失败时自动推送 | | **可观测性** | Prometheus `/metrics` 端点 + `/health` + `/ready` 探针 + SLA 违约监控 | diff --git a/deploy/backupx.service b/deploy/backupx.service index 1d89354..7330e13 100644 --- a/deploy/backupx.service +++ b/deploy/backupx.service @@ -12,6 +12,7 @@ ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure RestartSec=5 NoNewPrivileges=true +UMask=0027 LimitNOFILE=65535 [Install] diff --git a/deploy/docker/entrypoint.sh b/deploy/docker/entrypoint.sh index 35c7114..f6cc02f 100644 --- a/deploy/docker/entrypoint.sh +++ b/deploy/docker/entrypoint.sh @@ -1,27 +1,18 @@ #!/bin/sh -set -e +set -eu -if [ "${1:-}" = "agent" ]; then - exec /app/bin/backupx "$@" +# 旧镜像曾以 root 写入数据卷。Master 启动时做一次所有权迁移,随后 +# 降权运行;Agent 模式由部署命令显式决定用户,以访问宿主机备份路径。 +if [ "$(id -u)" -eq 0 ] && [ "${1:-}" != "agent" ]; then + chown backupx:backupx /app/data /tmp/backupx + if [ ! -f /app/data/.backupx-owner-v2 ]; then + chown -R backupx:backupx /app/data + su-exec backupx:backupx touch /app/data/.backupx-owner-v2 + fi + export HOME=/app + exec su-exec backupx:backupx /app/bin/backupx "$@" fi -# Backend listens on internal port 8341, Nginx exposes 8340 -export BACKUPX_SERVER_PORT="${BACKUPX_SERVER_PORT_INTERNAL:-8341}" - -# Start Nginx in background -nginx -g "daemon off;" & -NGINX_PID=$! - -# Start BackupX backend -/app/bin/backupx & -APP_PID=$! - -# Trap signals for graceful shutdown -trap 'kill $APP_PID $NGINX_PID 2>/dev/null; wait $APP_PID $NGINX_PID 2>/dev/null' SIGTERM SIGINT - -echo "BackupX started — Nginx :8340 -> Backend :8341" - -# Wait for either process to exit -wait -n $APP_PID $NGINX_PID 2>/dev/null || true -kill $APP_PID $NGINX_PID 2>/dev/null || true -wait $APP_PID $NGINX_PID 2>/dev/null || true +# Web 静态文件由 BackupX 后端直接托管。容器只运行一个前台进程, +# 让 Docker 准确传递信号、收集退出码并执行健康检查。 +exec /app/bin/backupx "$@" diff --git a/deploy/docker/nginx.conf b/deploy/docker/nginx.conf deleted file mode 100644 index ff4fb65..0000000 --- a/deploy/docker/nginx.conf +++ /dev/null @@ -1,51 +0,0 @@ -server { - listen 8340; - server_name _; - - root /app/web; - index index.html; - - # API reverse proxy to backend - location /api/ { - proxy_pass http://127.0.0.1:8341/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Connection ""; - proxy_buffering off; - proxy_cache off; - proxy_read_timeout 3600s; - } - - # Agent one-click install endpoints. - # Some external reverse proxies strip the /api prefix before reaching this - # container, so /install/ must be proxied here instead of falling through to - # the SPA index.html. - location /install/ { - proxy_pass http://127.0.0.1:8341/install/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_buffering off; - proxy_cache off; - } - - location = /health { proxy_pass http://127.0.0.1:8341/health; } - location = /ready { proxy_pass http://127.0.0.1:8341/ready; } - location = /metrics { proxy_pass http://127.0.0.1:8341/metrics; } - - # SPA fallback - location / { - try_files $uri $uri/ /index.html; - } - - # Static assets cache - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { - expires 30d; - add_header Cache-Control "public, immutable"; - } -} diff --git a/deploy/install.sh b/deploy/install.sh index da068a1..1c5cfbb 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -13,6 +13,7 @@ if [ -f "$SCRIPT_DIR/backupx" ] && [ -d "$SCRIPT_DIR/web" ]; then WEB_SOURCE="${WEB_SOURCE:-$SCRIPT_DIR/web}" CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$SCRIPT_DIR/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$SCRIPT_DIR/nginx.conf}" + SERVICE_SOURCE_DEFAULT="$SCRIPT_DIR/backupx.service" else SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/bin/backupx" # Keep compatibility with contributors who built the historical path by @@ -24,14 +25,38 @@ else WEB_SOURCE="${WEB_SOURCE:-$PROJECT_ROOT/web/dist}" CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$PROJECT_ROOT/server/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$PROJECT_ROOT/deploy/nginx.conf}" + SERVICE_SOURCE_DEFAULT="$PROJECT_ROOT/deploy/backupx.service" fi -SERVICE_SOURCE="${SERVICE_SOURCE:-$PROJECT_ROOT/deploy/backupx.service}" +SERVICE_SOURCE_EXPLICIT=0 +if [ -n "${SERVICE_SOURCE:-}" ]; then + SERVICE_SOURCE_EXPLICIT=1 +fi +SERVICE_SOURCE="${SERVICE_SOURCE:-$SERVICE_SOURCE_DEFAULT}" +INSTALL_NGINX="${INSTALL_NGINX:-0}" if [ "$(id -u)" -ne 0 ]; then echo "请使用 root 或 sudo 执行安装脚本。" >&2 exit 1 fi +validate_install_path() { + path_name="$1" + path_value="$2" + case "$path_value" in + /*) ;; + *) echo "$path_name 必须是绝对路径: $path_value" >&2; exit 1 ;; + esac + case "$path_value" in + /|*"//"*|*"/./"*|*"/."|*"/../"*|*"/.."|*[!A-Za-z0-9_./+-]*) + echo "$path_name 必须是规范、安全且非根目录的绝对路径: $path_value" >&2 + exit 1 + ;; + esac +} + +validate_install_path PREFIX "$PREFIX" +validate_install_path ETC_DIR "$ETC_DIR" + if [ ! -f "$BIN_SOURCE" ]; then echo "Backend binary not found / 未找到后端二进制:$BIN_SOURCE" >&2 echo "源码树安装请先在仓库根目录执行 make build(产物:server/bin/backupx)。" >&2 @@ -52,6 +77,18 @@ if [ ! -f "$CONFIG_TEMPLATE" ]; then exit 1 fi +if [ "$SERVICE_SOURCE_EXPLICIT" = "1" ] && [ ! -f "$SERVICE_SOURCE" ]; then + echo "指定的 systemd unit 不存在:$SERVICE_SOURCE" >&2 + exit 1 +fi + +for managed_path in "$PREFIX" "$PREFIX/bin" "$PREFIX/web" "$PREFIX/data" "$ETC_DIR"; do + if [ -L "$managed_path" ]; then + echo "拒绝通过符号链接写入受管目录:$managed_path" >&2 + exit 1 + fi +done + if ! getent group "$APP_GROUP" >/dev/null 2>&1; then groupadd --system "$APP_GROUP" fi @@ -60,19 +97,27 @@ if ! id "$APP_USER" >/dev/null 2>&1; then useradd --system --gid "$APP_GROUP" --home-dir "$PREFIX" --shell /usr/sbin/nologin "$APP_USER" fi -install -d -o "$APP_USER" -g "$APP_GROUP" "$PREFIX" "$PREFIX/bin" "$PREFIX/web" "$PREFIX/data" "$ETC_DIR" -install -m 0755 "$BIN_SOURCE" "$PREFIX/bin/backupx" +install -d -o root -g root -m 0755 "$PREFIX" "$PREFIX/bin" "$PREFIX/web" +install -d -o "$APP_USER" -g "$APP_GROUP" -m 0750 "$PREFIX/data" +install -d -o root -g "$APP_GROUP" -m 0750 "$ETC_DIR" +install -o root -g root -m 0755 "$BIN_SOURCE" "$PREFIX/bin/backupx.new" +mv -f "$PREFIX/bin/backupx.new" "$PREFIX/bin/backupx" cp -R "$WEB_SOURCE/." "$PREFIX/web/" -chown -R "$APP_USER:$APP_GROUP" "$PREFIX" +chown -R root:root "$PREFIX/bin" "$PREFIX/web" +find "$PREFIX/web" -type d -exec chmod 0755 {} \; +find "$PREFIX/web" -type f -exec chmod 0644 {} \; +chown -R "$APP_USER:$APP_GROUP" "$PREFIX/data" if [ ! -f "$ETC_DIR/config.yaml" ]; then - install -o "$APP_USER" -g "$APP_GROUP" -m 0640 "$CONFIG_TEMPLATE" "$ETC_DIR/config.yaml" + install -o root -g "$APP_GROUP" -m 0640 "$CONFIG_TEMPLATE" "$ETC_DIR/config.yaml" fi -# 确保服务账户能读取配置:历史版本曾以 root:root 0640 安装配置, -# 导致以 backupx 身份运行的服务因无权读取配置而启动失败(exit 1)。 -chown "$APP_USER:$APP_GROUP" "$ETC_DIR/config.yaml" +# 服务账户只需读取配置,不应拥有修改 /etc 配置或可执行文件的权限。 +chown root:"$APP_GROUP" "$ETC_DIR/config.yaml" +chmod 0640 "$ETC_DIR/config.yaml" -if [ -f "$SERVICE_SOURCE" ]; then +# 仓库 unit 使用标准路径;自定义 PREFIX/ETC_DIR 时动态生成以保持路径一致。 +# 显式传入 SERVICE_SOURCE 表示调用方已经审核其中的路径,始终优先使用。 +if [ -f "$SERVICE_SOURCE" ] && { [ "$SERVICE_SOURCE_EXPLICIT" = "1" ] || { [ "$PREFIX" = "/opt/backupx" ] && [ "$ETC_DIR" = "/etc/backupx" ]; }; }; then install -m 0644 "$SERVICE_SOURCE" "/etc/systemd/system/$SERVICE_NAME.service" else cat > "/etc/systemd/system/$SERVICE_NAME.service" <&2 + systemctl status "$SERVICE_NAME" --no-pager >&2 || true + journalctl -u "$SERVICE_NAME" -n 50 --no-pager >&2 || true + exit 1 +fi # systemctl may return before the process has opened its HTTP listener. Verify # the same unauthenticated endpoint used by the first-administrator screen so a @@ -134,12 +185,15 @@ if [ "$READY" -ne 1 ]; then exit 1 fi -if [ -d "/etc/nginx/conf.d" ] && [ -f "$NGINX_SOURCE" ]; then - install -m 0644 "$NGINX_SOURCE" "/etc/nginx/conf.d/$SERVICE_NAME.conf" - if command -v nginx >/dev/null 2>&1; then - nginx -t - systemctl reload nginx || true +if [ "$INSTALL_NGINX" = "1" ]; then + if [ ! -d "/etc/nginx/conf.d" ] || [ ! -f "$NGINX_SOURCE" ]; then + echo "已请求安装 Nginx 配置,但未找到 /etc/nginx/conf.d 或配置模板。" >&2 + exit 1 fi + install -o root -g root -m 0644 "$NGINX_SOURCE" "/etc/nginx/conf.d/$SERVICE_NAME.conf" + command -v nginx >/dev/null 2>&1 || { echo "未找到 nginx 命令。" >&2; exit 1; } + nginx -t + systemctl reload nginx fi cat < [Active Master + SQLite] + ^ + | outbound HTTP(S) polling + +---------+---------+ + | | | + [Agent B] [Agent C] [Agent D] + | | | + +----> storage targets ``` -- **Protocol** — HTTP long-polling; the Agent initiates every connection -- **Heartbeat** — Agent reports every 15s; Master marks nodes offline after 45s of silence -- **Dispatch** — Master persists `run_task` commands to a queue; Agent polls and claims them -- **Execution** — Agent reuses the same BackupRunner (file / mysql / postgresql / sqlite / saphana) and uploads directly to storage -- **Security** — Each node has its own token; the Agent never holds the Master's JWT secret or AES-256 key +- Each node has an independent Agent Token. The Agent never receives the Master's JWT or encryption key. +- A node is marked offline after 45 seconds without a heartbeat. +- The Master persists commands; an Agent claims and executes them locally. +- Network storage is normally written directly by the Agent. A Master-local target can opt into authenticated streaming relay. -## Centralize backups from servers B/C/D into storage M +:::warning Single-active Master +The embedded SQLite database is not a shared multi-writer database. Run exactly one active Master against a data directory. For control-plane recovery, use an active/passive host, persistent-volume snapshots, and a stable DNS name or virtual IP. Never scale multiple Master replicas over the same `/app/data` or `backupx.db`. +::: -Use the Master as the control plane and register every source server as an Agent. A task's **Source server** determines where paths and database tools are resolved; its **Storage targets** determine where the resulting artifact is retained. +BackupX applies a five-second SQLite busy timeout and command-queue indexes to reduce contention from concurrent Agent polls and task updates. Keep the database on a local or block-backed filesystem. For a file-level control-plane backup, stop the Master before copying the whole data directory; do not copy only `backupx.db` while it is running. -BackupX chooses the data path per target: +## Choose a network path -| Destination | Data path | -| --- | --- | -| S3, WebDAV, FTP, cloud drive, or another network backend | Agent streams directly to the destination | -| `local_disk` with **Relay remote backups through Master** enabled (for example storage server M mounted through NFS) | Agent streams through the authenticated Master API; Master writes to its configured local path | +| Scenario | Agent Master URL | Agent proxy URL | Notes | +| --- | --- | --- | --- | +| Routed network or public service | `https://backup.example.com` | empty | Recommended; allow only outbound TCP 443 | +| Corporate forward proxy | `https://backup.example.com` | `http://proxy.internal:3128` | HTTP(S) and SOCKS5(H) are supported | +| SSH dynamic tunnel through a bastion | `https://backup.internal` | `socks5h://127.0.0.1:1080` | Preserves TLS hostname and resolves internal DNS through the tunnel | +| SSH fixed local forward | `http://127.0.0.1:18340` | empty | The HTTP hop is protected by SSH; bind the forward to loopback only | -The relay is streaming: the Master does not create a second temporary copy of the entire artifact. The reverse path is used when restoring a Master-local artifact back to its source Agent. Use HTTPS whenever Agent traffic crosses an untrusted network. +For private PKI, provide the absolute path of a pre-provisioned PEM CA certificate. Do not use `--insecure-tls` in production. -To configure the common `A → {B,C,D} → M` topology: +When no explicit proxy is configured, Agent-to-Master HTTP traffic follows `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. A system service does not normally inherit an interactive shell's environment, so set the proxy in the install wizard or Agent YAML for systemd deployments. -1. Run BackupX Master on A and mount M on A if M is exposed as NFS or another filesystem. -2. Create a `local_disk` target for that mount and keep **Relay remote backups through Master** enabled, or create an S3/WebDAV target exposed by M. Existing local-disk targets keep their prior Agent-local behavior until this switch is enabled. -3. Install one Agent on B, C, and D from **Node Management**. -4. Create a backup task for each source, choose B/C/D under **Source server**, browse that server's paths, and select M as the storage target. A source-server pool label can route identical tasks dynamically. -5. Verify the per-target result in the backup record. For a Master-local target, the record reports transfer mode `master_relay`; network backends remain `direct`. +## Prepare the Master -## Walkthrough +Set a stable URL before generating commands: -### 0. Set the Master URL for production clusters - -Before generating Agent install commands, make sure the Master URL shown to Agents is stable and reachable from every target host. - -If BackupX runs behind Docker, Nginx, a load balancer, or an outer reverse proxy, configure `server.external_url` or `BACKUPX_SERVER_EXTERNAL_URL` on the Master: - -```yaml title="config.yaml" +```yaml title="/etc/backupx/config.yaml" server: external_url: "https://backup.example.com" + trusted_proxies: + - "127.0.0.1" + - "::1" + # Add the exact reverse-proxy IP or subnet when it is not local. + # - "172.18.0.0/16" ``` -This URL is baked into systemd units, foreground commands, and docker-compose snippets. If it is wrong, Agents will install successfully but stay offline because they keep polling an internal or browser-only address. +`external_url` is the default install and Agent runtime address. A restricted node can override both generated target-side URLs with its tunnel or internal address while the browser continues to use the public address. -### 1. Open the install wizard +Use HTTPS across untrusted networks. For Master-relay uploads, configure the reverse proxy with unlimited request body size and request buffering disabled; see [Nginx Reverse Proxy](../deployment/nginx). -In the Web Console → **Node Management** → **Add Node**. You'll see a three-step wizard. +Configure the Agent with the final API URL, not an HTTP-to-HTTPS redirect. The Agent deliberately does not follow redirects so its authentication Token cannot be forwarded to an unintended host. -- **Step 1 — Node info.** Give the node a name, or switch to batch mode and paste multiple names (one per line, max 50). -- **Step 2 — Deploy options.** Pick install mode (`systemd` recommended, `docker`, or `foreground` for debugging), architecture (auto-detect by default), agent version (defaults to the master's version), TTL for the install link (5 min / 15 min / 1 h / 24 h), and download source (`github` direct, or the `ghproxy` mirror for mainland China). -- **Step 3 — Copy the command.** A one-line install command is shown with a live countdown. Click copy, paste into the target machine, and run with root privileges. The default command embeds the rendered installer, so the target host does not need to fetch `/api/install/:token` through your reverse proxy. The public install URL is still available as a fallback. +## Deploy an Agent -### 2. One-line install on the target host +Open **Node Management → Add Node**: -Use the command generated by the Web Console. It writes the installer to a temporary file, validates the `BACKUPX_AGENT_INSTALL_V1` marker, then runs it with root privileges. +1. Enter one node name, or up to 50 names in batch mode. +2. Select systemd, Docker, or foreground mode; architecture; Agent release; command TTL; and download source. +3. Select **Direct** or **Proxy or bastion**. For the restricted path, set an Agent-specific Master URL, proxy URL, or private CA path. +4. Copy the generated command to the target host and run it with root privileges. -The script runs automatically and: +Systemd is recommended for host-file backup and restore because the Agent needs access to arbitrary local paths. A Docker Agent sees only explicitly mounted paths; recreate it with read-only backup-source mounts and separately scoped writable restore destinations before assigning file tasks. -1. Detects OS and architecture (`uname -m`) -2. Downloads the matching `backupx` binary from GitHub Release (or the ghproxy mirror) -3. Installs to `/opt/backupx-agent` and creates a `backupx` system user -4. Writes `/etc/systemd/system/backupx-agent.service` with the token baked into environment variables -5. Runs `systemctl enable --now backupx-agent` -6. Polls `/api/v1/agent/self` until the master confirms `status: online` (up to 30 s) +The URL-based command downloads a one-time installer and verifies its marker before execution. The wizard binds the selected Agent URL, explicit proxy, and private CA to that download command as well as to the installed Agent configuration. If the install endpoint is still unreachable, use the separately displayed embedded command. The embedded command contains the long-lived node Token and must be handled as a secret. -Docker mode uses the same `BACKUPX_AGENT_MASTER`, `BACKUPX_AGENT_TOKEN`, and `BACKUPX_AGENT_TEMP_DIR=/var/lib/backupx-agent/tmp` environment contract. After starting the container, the installer also probes `/api/v1/agent/self`; if the node does not come online, it prints `docker ps` and `docker logs --tail=100 backupx-agent` diagnostics before exiting non-zero. +The installer: -If you choose the URL-based fallback command and `curl` prints HTML or the shell reports `Syntax error: newline unexpected`, the install URL is being served by the web console instead of the backend. Ensure either `/api/install/` or `/install/` is forwarded to the BackupX backend, or use the embedded command generated by the console. +1. Detects `linux/amd64` or `linux/arm64`. +2. Downloads the selected Release archive through the explicit proxy when configured, otherwise using the host's normal direct/environment-proxy route, and verifies its SHA-256 sidecar when the release provides one. +3. Writes `/etc/backupx-agent/config.yaml` and `/etc/backupx-agent/agent.token` with mode `0600`. +4. Keeps the Token out of the systemd unit and Docker environment metadata. +5. Starts the Agent and checks `/api/v1/agent/self` for up to 30 seconds. +6. Returns non-zero with systemd or Docker diagnostics when the node does not become online. -Reruns are idempotent — to upgrade or re-provision, simply generate a new install command and run it again. The one-time install link expires after its TTL or after first consumption, whichever is sooner. +Older releases without checksum sidecars remain installable with a warning. New releases should always publish and verify the sidecar. -### 3. Rotate agent tokens at any time - -Go to the node's action menu (︙) → **Rotate Token**. The new token is shown once and the old token remains valid for 24 h, allowing rolling restarts without downtime. After 24 h, the old token is rejected. - -### 4. Batch deployment - -In Step 1 choose "Batch" and paste node names (one per line, max 50). Step 3 shows a table with one command per node plus a **Download .sh** button that bundles all commands into a shell script, convenient for SSH loops or Ansible tasks. - -### 5. Route a task to the node - -In the **Backup Tasks** page, pick the source server when creating the task. When the task runs: - -- Local (`nodeId=0`) → Master executes in-process -- Remote node → Master enqueues the command → Agent claims → Agent runs locally → uploads → reports back - -The node table shows the Agent health and command queue state: pending/dispatched depth, running long commands, timeouts, oldest active command age, and the latest Agent-side error. The same queue depth, running-command, and timeout snapshots are exported as Prometheus metrics: - -- `backupx_agent_command_queue_depth` -- `backupx_agent_command_running` -- `backupx_agent_command_timeout_total` - -## Known limitations - -- **Encrypted backups are Master-only** — the Agent doesn't hold Master's AES-256 key. Creating or updating a task with `encrypt: true` and a remote node or node pool is rejected up front -- **Directory browser timeout** — remote dir listing is a synchronous RPC through the queue (15s default) -- **Dispatched command timeout** — claimed-but-unfinished commands are marked `timeout` after 10 minutes - -## CLI reference +### Installed systemd configuration +```yaml title="/etc/backupx-agent/config.yaml" +master: "https://backup.example.com" +tokenFile: "/etc/backupx-agent/agent.token" +heartbeatInterval: "15s" +pollInterval: "5s" +tempDir: "/var/lib/backupx-agent/tmp" +proxyUrl: "" +caCertFile: "" ``` -backupx agent --help - -master string Master URL - -token string Agent auth token - -config string YAML config path (takes precedence over env) - -temp-dir string Local temp directory (default /tmp/backupx-agent) - -insecure-tls Skip TLS verification (testing only) -``` - -## systemd unit ```ini title="/etc/systemd/system/backupx-agent.service" [Unit] Description=BackupX Agent -After=network.target +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=300 +StartLimitBurst=10 [Service] Type=simple -User=backupx -Environment="BACKUPX_AGENT_MASTER=https://master.example.com" -Environment="BACKUPX_AGENT_TOKEN=your-token" -ExecStart=/opt/backupx/backupx agent +ExecStart=/opt/backupx-agent/backupx agent --config /etc/backupx-agent/config.yaml Restart=on-failure RestartSec=10s +TimeoutStopSec=30s +UMask=0077 +LimitNOFILE=65535 [Install] WantedBy=multi-user.target ``` -Enable and start: +The Agent runs as root because file backup and restore paths may belong to arbitrary system users. Restrict who can create tasks and who can modify the root-owned Agent configuration. + +## SSH bastion example + +Prefer a SOCKS tunnel when the internal Master uses HTTPS: its hostname and certificate validation remain unchanged. + +Create a dedicated SSH account and pre-provision its private key plus a verified `known_hosts` file. Then create: + +```sshconfig title="/etc/backupx-agent/ssh_config" +Host backupx-bastion + HostName bastion.example.com + User backupx-tunnel + IdentityFile /etc/backupx-agent/tunnel_ed25519 + IdentitiesOnly yes + BatchMode yes + UserKnownHostsFile /etc/backupx-agent/known_hosts + StrictHostKeyChecking yes + DynamicForward 127.0.0.1:1080 + ExitOnForwardFailure yes + ServerAliveInterval 30 + ServerAliveCountMax 3 +``` + +```ini title="/etc/systemd/system/backupx-agent-tunnel.service" +[Unit] +Description=BackupX Agent SSH tunnel +After=network-online.target +Wants=network-online.target +Before=backupx-agent.service + +[Service] +Type=simple +ExecStart=/usr/bin/ssh -NT -F /etc/backupx-agent/ssh_config backupx-bastion +Restart=always +RestartSec=5s + +[Install] +WantedBy=multi-user.target +``` + +Add a drop-in so the Agent fails closed when the tunnel is unavailable: + +```ini title="/etc/systemd/system/backupx-agent.service.d/tunnel.conf" +[Unit] +Requires=backupx-agent-tunnel.service +After=backupx-agent-tunnel.service +``` + +Reload and start both units: ```bash -sudo systemctl enable --now backupx-agent -sudo journalctl -u backupx-agent -f +sudo systemctl daemon-reload +sudo systemctl enable --now backupx-agent-tunnel backupx-agent ``` + +In the wizard, keep the internal HTTPS Master URL and set the proxy to `socks5h://127.0.0.1:1080`. Verify the bastion host key out-of-band before enabling the service. + +## Central storage data paths + +| Destination | Data path | +| --- | --- | +| S3, WebDAV, FTP, cloud drive, or another network backend | Agent streams directly to the destination | +| `local_disk` with **Relay remote backups through Master** enabled | Agent streams through the authenticated Master API; Master writes to its local mount | + +The relay does not create a second complete temporary copy on the Master. Restore uses the reverse streaming path. Nginx request buffering must be disabled for this behavior to remain streaming. + +## Operations + +```bash +sudo systemctl status backupx-agent +sudo journalctl -u backupx-agent -n 100 --no-pager +sudo /opt/backupx-agent/backupx agent --config /etc/backupx-agent/config.yaml +``` + +Rotate a node Token from its action menu. Update `/etc/backupx-agent/agent.token` on the node and restart the service during the 24-hour overlap window. + +Monitor these Prometheus metrics: + +- `backupx_agent_command_queue_depth` +- `backupx_agent_command_running` +- `backupx_agent_command_timeout_total` +- `backupx_node_online` + +## CLI reference + +```text +backupx agent --help + -master string Master URL + -token string Agent authentication token + -token-file string Read the Agent Token from a file + -config string YAML configuration path + -temp-dir string Local temporary directory + -proxy-url string HTTP(S) or SOCKS5(H) proxy + -ca-cert string PEM CA certificate used to verify the Master + -insecure-tls Skip TLS verification (testing only) +``` + +Environment variables: `BACKUPX_AGENT_MASTER`, `BACKUPX_AGENT_TOKEN`, `BACKUPX_AGENT_TOKEN_FILE`, `BACKUPX_AGENT_HEARTBEAT`, `BACKUPX_AGENT_POLL`, `BACKUPX_AGENT_TEMP_DIR`, `BACKUPX_AGENT_PROXY_URL`, `BACKUPX_AGENT_CA_CERT_FILE`, and `BACKUPX_AGENT_INSECURE_TLS`. + +## Known limitations + +- The Master is single-active because it uses embedded SQLite. +- Encrypted backups are Master-only because Agents do not hold the Master encryption key. +- Remote directory browsing is a synchronous queue RPC with a 15-second timeout. +- Claimed commands that stop reporting progress are timed out according to the Master command monitor. diff --git a/docs-site/docs/getting-started/installation.md b/docs-site/docs/getting-started/installation.md index 42bec64..dc31820 100644 --- a/docs-site/docs/getting-started/installation.md +++ b/docs-site/docs/getting-started/installation.md @@ -48,8 +48,9 @@ Images: [`awuqing/backupx`](https://hub.docker.com/r/awuqing/backupx) — suppor Download from the [Releases page](https://github.com/Awuqing/BackupX/releases) and run the installer: ```bash +sha256sum -c backupx-v*-linux-amd64.tar.gz.sha256 tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* -sudo ./install.sh # creates system user, installs to /opt/backupx, sets up systemd + nginx +sudo ./install.sh # creates system user, installs to /opt/backupx, sets up systemd ``` The installer: @@ -58,7 +59,7 @@ The installer: 2. Installs the binary to `/opt/backupx/bin/backupx` and the web console to `/opt/backupx/web` 3. Creates `/etc/backupx/config.yaml` with safe defaults 4. Installs and enables the `backupx.service` systemd unit -5. (Optional) Configures an Nginx reverse proxy +5. Leaves Nginx unchanged unless `INSTALL_NGINX=1` is explicitly requested 6. Waits for `/api/auth/setup/status`; if startup fails, prints systemd diagnostics and exits non-zero ## From source @@ -74,6 +75,8 @@ sudo ./deploy/install.sh After `make build`, the binary is at `server/bin/backupx` and the built web UI is at `web/dist/`. The installer consumes those exact paths, so no Docker runtime is required. If an existing configuration uses a non-default port, set `HEALTH_URL` for the readiness check, for example `sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`. +The Nginx template is opt-in because automatically installing a catch-all virtual host can intercept existing sites. Review `deploy/nginx.conf`, then use `sudo INSTALL_NGINX=1 ./deploy/install.sh` only when it matches the host. + ## Verify the install ```bash diff --git a/docs-site/docs/intro.md b/docs-site/docs/intro.md index 75df0cf..da1638e 100644 --- a/docs-site/docs/intro.md +++ b/docs-site/docs/intro.md @@ -21,7 +21,7 @@ description: Overview of BackupX — a self-hosted server backup management plat ``` [Web Console] ─── JWT ──→ [Master (backupx)] │ - │ HTTP long-poll (token auth) + │ outbound HTTP polling (token auth) ▼ [Agent (backupx agent)] │ diff --git a/docs-site/docs/reference/cli.md b/docs-site/docs/reference/cli.md index ad19727..8fc33d4 100644 --- a/docs-site/docs/reference/cli.md +++ b/docs-site/docs/reference/cli.md @@ -32,11 +32,14 @@ backupx agent --master http://master:8340 --token |------|-------------| | `--master ` | Master URL | | `--token ` | Agent auth token | +| `--token-file ` | Read the Agent Token from a file; preferred for services and containers | | `--config ` | YAML config (takes precedence over env) | | `--temp-dir ` | Local temp directory (default `/tmp/backupx-agent`) | +| `--proxy-url ` | Explicit HTTP(S) or SOCKS5(H) proxy | +| `--ca-cert ` | PEM CA certificate used to verify the Master | | `--insecure-tls` | Skip TLS verification (testing only) | -Environment variables: `BACKUPX_AGENT_MASTER`, `BACKUPX_AGENT_TOKEN`, `BACKUPX_AGENT_HEARTBEAT`, `BACKUPX_AGENT_POLL`, `BACKUPX_AGENT_TEMP_DIR`, `BACKUPX_AGENT_INSECURE_TLS`. +Environment variables: `BACKUPX_AGENT_MASTER`, `BACKUPX_AGENT_TOKEN`, `BACKUPX_AGENT_TOKEN_FILE`, `BACKUPX_AGENT_HEARTBEAT`, `BACKUPX_AGENT_POLL`, `BACKUPX_AGENT_TEMP_DIR`, `BACKUPX_AGENT_PROXY_URL`, `BACKUPX_AGENT_CA_CERT_FILE`, `BACKUPX_AGENT_INSECURE_TLS`. When no explicit proxy URL is set, the Agent also honors `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. ## `backupx backint` diff --git a/docs-site/i18n/zh-CN/code.json b/docs-site/i18n/zh-CN/code.json index e95c02f..e22bb85 100644 --- a/docs-site/i18n/zh-CN/code.json +++ b/docs-site/i18n/zh-CN/code.json @@ -69,7 +69,7 @@ "feat.scheduling.title": {"message": "调度与保留策略"}, "feat.scheduling.desc": {"message": "基于 Cron 的可视化调度编辑器,支持按天数/份数自动保留和空目录清理。"}, "feat.cluster.title": {"message": "多节点集群"}, - "feat.cluster.desc": {"message": "Master-Agent 基于 HTTP 长轮询。Agent 在本地执行任务并直接上传到存储 — 无需反向连通性。"}, + "feat.cluster.desc": {"message": "Master-Agent 采用 Agent 主动出站轮询。支持代理、私有 CA 与 SSH 堡垒机,无需反向连通性。"}, "feat.security.title": {"message": "默认安全"}, "feat.security.desc": {"message": "JWT 认证、bcrypt、AES-256-GCM 加密配置、可选备份加密、完整审计日志。"}, "feat.deploy.title": {"message": "部署轻量"}, diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md index f58191e..c988443 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md @@ -1,7 +1,7 @@ --- sidebar_position: 2 title: 裸机部署 -description: 从预编译包或源码部署 BackupX(systemd + Nginx)。 +description: 从预编译包或源码加固部署 BackupX,Nginx 改为显式启用。 --- # 裸机部署 @@ -11,6 +11,8 @@ description: 从预编译包或源码部署 BackupX(systemd + Nginx)。 ```bash # 下载对应平台的压缩包 curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz +curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz.sha256 +sha256sum -c backupx-linux-amd64.tar.gz.sha256 # 解压并安装 tar xzf backupx-linux-amd64.tar.gz && cd backupx-*-linux-amd64 @@ -23,9 +25,17 @@ sudo ./install.sh 2. 复制二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台复制到 `/opt/backupx/web` 3. 把默认配置安装到 `/etc/backupx/config.yaml` 4. 安装并启用 `backupx.service` systemd 单元 -5. (可选)生成 Nginx 站点配置 — 参见 [Nginx 反向代理](./nginx) +5. 默认不修改 Nginx;只有显式设置 `INSTALL_NGINX=1` 时才安装模板 6. 验证首次初始化接口就绪后才报告安装成功 +可执行文件与前端资源由 root 所有,只有 `/opt/backupx/data` 允许 `backupx` 服务账户写入。`/etc/backupx/config.yaml` 以 `root:backupx`、`0640` 权限安装。 + +仓库提供的 Nginx 模板只是起点,可能与现有默认站点冲突。先审核域名与 TLS 策略,再显式启用: + +```bash +sudo INSTALL_NGINX=1 ./install.sh +``` + 如果要部署多节点集群,安装后请编辑 `/etc/backupx/config.yaml`,设置远程 Agent 可访问到的 Master URL: ```yaml @@ -70,6 +80,7 @@ ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure RestartSec=5 NoNewPrivileges=true +UMask=0027 LimitNOFILE=65535 [Install] @@ -87,6 +98,8 @@ curl -fsS http://127.0.0.1:8340/api/auth/setup/status 访问 `http://your-server:8340`,可按需切换到 English,然后在“系统初始化 / System setup”页面创建首个管理员。若监听端口不是默认值,请为安装脚本传入对应的 `HEALTH_URL`。 +生产环境应通过 HTTPS 暴露 BackupX,或在防火墙限制 `8340` 端口。安装器不会自动修改防火墙。 + ## 密码重置 忘记管理员密码时: diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md index 7b35326..f628929 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md @@ -15,7 +15,11 @@ server: host: "0.0.0.0" # BACKUPX_SERVER_HOST port: 8340 # BACKUPX_SERVER_PORT mode: "release" # release | debug - external_url: "" # BACKUPX_SERVER_EXTERNAL_URL — Agent 安装脚本使用的 Master 对外 URL + external_url: "" # BACKUPX_SERVER_EXTERNAL_URL — 稳定的 Master 对外 URL + trusted_proxies: # BACKUPX_SERVER_TRUSTED_PROXIES — 准确的代理 IP/CIDR + - "127.0.0.1" + - "::1" + web_root: "" # BACKUPX_SERVER_WEB_ROOT — 前端构建目录 database: path: "./data/backupx.db" # BACKUPX_DATABASE_PATH — 内嵌 SQLite @@ -48,6 +52,7 @@ log: |--------|----------| | `server.port` | `BACKUPX_SERVER_PORT` | | `server.external_url` | `BACKUPX_SERVER_EXTERNAL_URL` | +| `server.trusted_proxies` | `BACKUPX_SERVER_TRUSTED_PROXIES`(环境变量使用逗号分隔) | | `security.jwt_expire` | `BACKUPX_SECURITY_JWT_EXPIRE` | | `log.level` | `BACKUPX_LOG_LEVEL` | | `backup.max_concurrent` | `BACKUPX_BACKUP_MAX_CONCURRENT` | @@ -64,3 +69,18 @@ server: ``` BackupX 会用这个地址渲染一键 Agent 安装脚本和 docker-compose 片段。该地址必须能被所有 Agent 主机访问。只有在 `X-Forwarded-Proto` / `X-Forwarded-Host` 可靠且正好指向 Agent 可访问地址时,才建议留空。 + +代理或 SSH 堡垒机场景可在安装向导中为单个 Agent 设置运行地址。公开安装链接仍使用 `server.external_url`,生成的 Agent 配置则使用该覆盖地址。 + +## 可信反向代理 + +BackupX 只接受 `server.trusted_proxies` 中来源提供的客户端转发头。默认仅允许本机 Nginx。代理运行在其他容器或主机时,加入准确 IP 或网段: + +```yaml +server: + trusted_proxies: + - "127.0.0.1" + - "172.18.0.0/16" +``` + +不要配置 `0.0.0.0/0`,因为登录限流、安装令牌限流和审计日志都依赖客户端地址。BackupX 直接暴露且不应信任任何转发头时可设置空列表。 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md index 0e9a9b4..9c54ae8 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md @@ -1,81 +1,96 @@ --- sidebar_position: 1 title: Docker 部署 -description: 生产级 Docker 部署方案,含 compose 配置、宿主目录挂载、环境变量覆盖。 +description: 带健康检查和持久化数据的加固单进程 Docker 部署。 --- # Docker 部署 -BackupX 官方 Docker 镜像 [`awuqing/backupx`](https://hub.docker.com/r/awuqing/backupx) 支持多架构(linux/amd64 + linux/arm64)。 +官方镜像 [`awuqing/backupx`](https://hub.docker.com/r/awuqing/backupx) 支持 `linux/amd64` 和 `linux/arm64`。 ## Compose 文件 ```yaml title="docker-compose.yml" services: backupx: - image: awuqing/backupx:latest + image: ${BACKUPX_IMAGE:-awuqing/backupx:latest} container_name: backupx restart: unless-stopped + init: true + stop_grace_period: 30s ports: - - "8340:8340" + - "${BACKUPX_BIND_ADDRESS:-0.0.0.0}:${BACKUPX_PORT:-8340}:8340" volumes: - backupx-data:/app/data - # 挂载需要备份的宿主机目录: - - /var/www:/mnt/www:ro - - /etc/nginx:/mnt/nginx-conf:ro + # - /var/www:/mnt/www:ro + # - /etc/nginx:/mnt/nginx-conf:ro + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - CHOWN + - DAC_OVERRIDE + - SETGID + - SETUID environment: - - TZ=Asia/Shanghai - # 远程 Agent 需要通过公网或可路由地址连接 Master 时必须配置: - # - BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com - - BACKUPX_LOG_LEVEL=info - - BACKUPX_BACKUP_MAX_CONCURRENT=2 + TZ: Asia/Shanghai + # BACKUPX_SERVER_EXTERNAL_URL: https://backup.example.com + BACKUPX_LOG_LEVEL: info + BACKUPX_BACKUP_MAX_CONCURRENT: "2" + healthcheck: + test: ["CMD", "su-exec", "backupx:backupx", "wget", "-q", "-T", "3", "-O", "/dev/null", "http://127.0.0.1:8340/ready"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s volumes: backupx-data: ``` -启动: - ```bash docker compose up -d +docker compose ps ``` +入口脚本仅以 root 完成旧镜像数据的所有权迁移,随后只运行一个非 root `backupx` 进程;Compose 仅保留初始化所需的所有权与 UID/GID 切换能力。后端同时提供 API 与前端静态文件,默认不挂载 Docker Socket,也不打包 Docker CLI。生产环境应把 `BACKUPX_IMAGE` 固定到明确 Release 标签。 + ## 备份宿主机目录 -想备份宿主机上的文件,需要将对应路径挂载进容器。在 Web UI 创建文件类型任务时,把源路径指向挂载后的容器内路径(如 `/mnt/www`)。 +按需挂载源目录,并在任务中使用容器内路径。容器中的 `backupx` 用户必须拥有读取权限;恢复目标应使用单独且范围受限的可写挂载。特权路径优先通过远程 Agent 处理;确实需要 Master 以 root 读取时,应显式设置 `user: "0:0"` 并审核每一个挂载。 ## 多节点集群 -如果要在其他机器部署 Agent,请在 Master 容器上设置 `BACKUPX_SERVER_EXTERNAL_URL`,值为所有 Agent 都能访问到的 URL: +设置所有 Agent 可达的稳定地址: ```yaml environment: - - BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com + BACKUPX_SERVER_EXTERNAL_URL: https://backup.example.com ``` -Agent 跨不可信网络访问时建议使用 HTTPS。控制台生成的一键安装脚本和 docker-compose 片段会把这个值写成 `BACKUPX_AGENT_MASTER`。 +跨不可信网络必须使用 HTTPS。代理、私有 CA 和 SSH 堡垒机场景见 [多节点集群](../features/multi-node)。 -## 环境变量 +外部反向代理运行在其他容器时,只把准确的 Docker 网桥网段加入 `BACKUPX_SERVER_TRUSTED_PROXIES`,例如 `172.18.0.0/16`,不要信任所有地址。 -所有配置项都可以通过 `BACKUPX_` 前缀环境变量覆盖: +## 环境变量覆盖 ```yaml environment: - - TZ=Asia/Shanghai - - BACKUPX_SERVER_PORT=8340 - - BACKUPX_LOG_LEVEL=debug - - BACKUPX_BACKUP_MAX_CONCURRENT=4 - - BACKUPX_BACKUP_TEMP_DIR=/tmp/backupx + TZ: Asia/Shanghai + BACKUPX_LOG_LEVEL: debug + BACKUPX_BACKUP_MAX_CONCURRENT: "4" + BACKUPX_BACKUP_TEMP_DIR: /tmp/backupx ``` -完整列表见 [配置参考](./configuration)。 +镜像内部端口固定为 `8340`,只通过 `BACKUPX_PORT` 修改宿主机发布端口。 -## 升级 - -在 UI **系统设置 → 检查更新** 页面查看是否有新版,然后在宿主机上: +## 升级与回退准备 ```bash -docker compose pull && docker compose up -d +docker compose pull +docker compose up -d +docker compose ps ``` -无需手工迁移:BackupX 启动时自动迁移 SQLite schema。 +等待状态变为 `healthy` 后再切换流量或移除旧部署。升级前应停止 Master 后做文件级复制,或对整个 `backupx-data` 卷创建原子快照。同一个数据卷必须只运行一个活动 Master;SQLite 不支持多个 Master 容器共享 `/app/data`。 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/nginx.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/nginx.md index b15c80e..f9e19c6 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/nginx.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/nginx.md @@ -23,13 +23,16 @@ server { location /api/ { proxy_pass http://127.0.0.1:8340; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Port $server_port; # 大文件上传(用于恢复流程) client_max_body_size 0; + proxy_request_buffering off; # 实时日志使用 SSE,必须关闭缓冲 proxy_buffering off; @@ -39,6 +42,10 @@ server { } ``` +集群使用 Master 中转备份时必须保留 `proxy_request_buffering off`。否则 Nginx 会先把 Agent 上传的完整备份写入代理临时目录,再交给 BackupX,既失去流式传输优势,也可能占满代理磁盘。 + +如果 Nginx 运行在另一台主机或另一个容器,只把该代理的 IP 或网段加入 `server.trusted_proxies`,不要配置 `0.0.0.0/0`。登录限流、安装令牌限流和审计日志都依赖可信的客户端地址。 + ## certbot 配置 HTTPS ```bash diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md index 7ad1032..532ff43 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md @@ -1,155 +1,227 @@ --- sidebar_position: 4 title: 多节点集群 -description: Master-Agent 模式 — 通过 HTTP 长轮询把备份路由到远程服务器。 +description: 通过直连 HTTPS、正向代理或 SSH 堡垒机部署 BackupX Agent。 --- # 多节点集群 -BackupX 支持 Master-Agent 模式:备份任务可以指定在哪个节点执行,Agent 在本地完成备份并直接上传到存储。所有连接都由 Agent 主动发起,所以远程服务器只需要出站 HTTP 访问权限。 +BackupX 使用一个单活 Master 作为控制面,在每台源服务器运行 Agent。所有连接都由 Agent 主动发起:每 15 秒上报心跳,每 5 秒轮询命令,不需要为 Agent 开放入站端口。 -## 架构 +## 架构与边界 -``` -[Web 控制台] ─── JWT ──→ [Master (backupx)] - ↑ ↓ - │ │ HTTP 长轮询(Token 认证) - │ ↓ - [Agent (backupx agent)] ← 运行在远程服务器 - ↓ - [70+ 存储后端] +```text +[Web 控制台] ────────> [单活 Master + SQLite] + ^ + | Agent 主动 HTTP(S) 轮询 + +---------+---------+ + | | | + [Agent B] [Agent C] [Agent D] + | | | + +----> 存储目标 ``` -- **协议** — HTTP 长轮询,Agent 主动发起所有连接 -- **心跳** — Agent 每 15s 上报一次;Master 超过 45s 未收到心跳即判为离线 -- **下发** — Master 把 `run_task` 命令写入队列,Agent 轮询拉取 -- **执行** — Agent 复用 BackupRunner(file / mysql / postgresql / sqlite / saphana)并直接上传到存储 -- **安全** — 每个节点独立 Token;Agent 不持有 Master 的 JWT 密钥或 AES-256 加密密钥 +- 每个节点有独立 Agent Token,Agent 不持有 Master 的 JWT 密钥或配置加密密钥。 +- Master 超过 45 秒未收到心跳即把节点标记为离线。 +- Master 持久化命令,Agent 领取后在本机执行。 +- 网络存储通常由 Agent 直传;Master 本地存储可显式启用认证流式中转。 -## 把 B/C/D 服务器集中备份到 M +:::warning Master 只能单活 +内置 SQLite 不是共享多写数据库。同一个数据目录只能运行一个 Master。控制面高可用应采用主备主机、持久卷快照以及稳定 DNS 或虚拟 IP,故障时确保旧 Master 停止后再启动备用实例。不要让多个 Master 副本同时挂载 `/app/data` 或同一个 `backupx.db`。 +::: -Master 作为控制面,每台源服务器安装一个 Agent。任务里的 **源服务器** 决定源路径和数据库工具在哪台机器解析,**存储目标** 决定备份产物最终保留在哪里。 +BackupX 会设置 5 秒 SQLite busy timeout,并为命令队列建立查询索引,降低 Agent 并发轮询及任务更新时的锁竞争。数据库应位于本地文件系统或块存储。采用文件复制备份控制面时,先停止 Master 再复制整个数据目录;运行期间不要只复制 `backupx.db`。 -BackupX 会根据目标类型选择数据路径: +## 选择网络路径 -| 目标 | 数据路径 | -| --- | --- | -| S3、WebDAV、FTP、云盘或其他网络后端 | Agent 直接流式上传到目标 | -| 启用 **远程备份经 Master 中转** 的 `local_disk`(例如通过 NFS 挂载的存储服务器 M) | Agent 通过认证后的 Master API 流式中转,由 Master 写入配置目录 | +| 场景 | Agent Master 地址 | Agent 代理 URL | 说明 | +| --- | --- | --- | --- | +| 可路由内网或公网服务 | `https://backup.example.com` | 留空 | 推荐,只需放行出站 TCP 443 | +| 企业正向代理 | `https://backup.example.com` | `http://proxy.internal:3128` | 支持 HTTP(S) 与 SOCKS5(H) | +| 通过堡垒机建立 SSH 动态转发 | `https://backup.internal` | `socks5h://127.0.0.1:1080` | 保留 TLS 主机名,并通过隧道解析内网 DNS | +| SSH 固定本地转发 | `http://127.0.0.1:18340` | 留空 | HTTP 链路位于 SSH 内,只能绑定回环地址 | -中转过程不会在 Master 上额外落一份完整临时文件。把 Master 本地磁盘中的备份恢复到源 Agent 时会走反向流式通道。Agent 与 Master 之间跨越不可信网络时必须配置 HTTPS。 +私有 PKI 场景请填写目标节点上预置的 PEM CA 证书绝对路径。生产环境不要使用 `--insecure-tls`。 -典型的 `A → {B,C,D} → M` 拓扑按以下步骤配置: +未配置显式代理时,Agent 到 Master 的 HTTP 流量会遵循 `HTTP_PROXY`、`HTTPS_PROXY` 和 `NO_PROXY`。systemd 服务通常不会继承交互式 Shell 环境,因此 systemd 部署应在安装向导或 Agent YAML 中明确配置代理。 -1. 在 A 运行 BackupX Master;如果 M 以 NFS 等文件系统提供存储,先把 M 挂载到 A。 -2. 为该挂载点创建 `local_disk` 目标并保持 **远程备份经 Master 中转** 开启;如果 M 提供 S3/WebDAV,也可直接创建对应网络目标。升级前已有的本地磁盘目标会继续沿用 Agent 本机落盘,手动开启该选项后才切换到中央目录。 -3. 从 **节点管理** 分别在 B、C、D 安装 Agent。 -4. 为每台源服务器创建任务,在 **源服务器** 选择 B/C/D,浏览该服务器的路径,再把 M 选为存储目标。相同任务也可用源服务器池标签动态调度。 -5. 在备份记录中检查逐目标结果。Master 本地磁盘目标会记录 `master_relay` 中转模式,网络后端仍为 `direct` 直传。 +## 准备 Master -## 一键部署步骤 +生成命令前先设置稳定地址: -### 0. 为生产集群设置 Master 对外 URL - -生成 Agent 安装命令前,请先确认 Master URL 对所有目标主机稳定可达。 - -如果 BackupX 部署在 Docker、Nginx、负载均衡或外层反向代理后面,请在 Master 配置 `server.external_url` 或环境变量 `BACKUPX_SERVER_EXTERNAL_URL`: - -```yaml title="config.yaml" +```yaml title="/etc/backupx/config.yaml" server: external_url: "https://backup.example.com" + trusted_proxies: + - "127.0.0.1" + - "::1" + # 代理不在本机时,只加入准确的代理 IP 或网段。 + # - "172.18.0.0/16" ``` -该 URL 会写入 systemd 单元、前台运行命令和 docker-compose 片段。如果地址不正确,Agent 可能安装成功但始终离线,因为它会持续轮询一个内网地址或仅浏览器可访问的地址。 +`external_url` 是默认安装入口和 Agent 运行地址。受限节点可以让目标机侧生成的安装 URL 与 Agent 运行地址同时改用隧道或内网地址,浏览器仍继续访问公网地址。 -### 1. 打开安装向导 +跨不可信网络必须使用 HTTPS。Master 中转上传还要求反向代理关闭请求缓冲并允许大请求体,详见 [Nginx 反向代理](../deployment/nginx)。 -Web 控制台 → **节点管理** → **添加节点**,打开三步向导: +Agent 必须直接配置最终 API 地址,不能依赖 HTTP 跳转到 HTTPS。Agent 会主动拒绝重定向,避免认证 Token 被转发到非预期主机。 -- **第一步 · 节点信息**:填写节点名称;或切换"批量创建"粘贴多行名称(每行一个,最多 50 个) -- **第二步 · 部署参数**:选择安装模式(`systemd` 推荐、`Docker`、`前台运行` 调试用)、架构(默认自动检测)、Agent 版本(默认跟随 Master 版本)、有效期(5 分钟 / 15 分钟 / 1 小时 / 24 小时)、下载源(`GitHub` 直连或 `ghproxy` 镜像,国内服务器建议后者) -- **第三步 · 安装命令**:一条一键安装命令 + 实时倒计时。点击复制,粘贴到目标机以 root 权限执行。默认命令会嵌入已渲染的安装脚本,目标机无需再通过反向代理访问 `/api/install/:token`;公开安装 URL 仍作为备用路径保留。 +## 部署 Agent -### 2. 目标机一条命令完成 +打开 **节点管理 → 添加节点**: -请直接使用 Web 控制台生成的命令。该命令会把安装脚本写入临时文件,校验 `BACKUPX_AGENT_INSTALL_V1` 魔数,再以 root 权限执行。 +1. 输入单个节点名,或在批量模式输入最多 50 个名称。 +2. 选择 systemd、Docker 或前台模式,以及架构、Agent Release、命令有效期和下载源。 +3. 选择 **直连** 或 **代理或堡垒机**。受限网络可填写节点专用 Master 地址、代理 URL 或私有 CA 路径。 +4. 把生成的命令复制到目标机,以 root 权限执行。 -脚本会自动: +备份和恢复宿主机文件时推荐 systemd,因为 Agent 需要访问任意本地路径。Docker Agent 只能看到显式挂载的目录;分配文件任务前,应使用只读备份源 volume,并为恢复目标单独配置范围受限的可写挂载。 -1. 检测操作系统与架构(`uname -m`) -2. 从 GitHub Release(或 ghproxy 镜像)下载匹配的 `backupx` 二进制 -3. 安装到 `/opt/backupx-agent`,创建系统用户 `backupx` -4. 写入 `/etc/systemd/system/backupx-agent.service`(token 已烧入环境变量) -5. 执行 `systemctl enable --now backupx-agent` -6. 轮询 `/api/v1/agent/self`,直到 Master 确认 `status: online`(最多 30 秒) +主命令通过一次性入口下载安装器,并在执行前校验脚本标记。向导会把所选 Agent 地址、显式代理和私有 CA 同时绑定到下载命令与安装后的 Agent 配置。如果目标网络仍无法访问安装入口,使用页面单独展示的嵌入式备用命令。嵌入式命令包含长期节点 Token,必须按密钥管理。 -Docker 模式使用同一组环境变量约定:`BACKUPX_AGENT_MASTER`、`BACKUPX_AGENT_TOKEN` 和 `BACKUPX_AGENT_TEMP_DIR=/var/lib/backupx-agent/tmp`。容器启动后,安装脚本同样会探测 `/api/v1/agent/self`;如果节点没有上线,会输出 `docker ps` 与 `docker logs --tail=100 backupx-agent` 排查命令,并以非零状态退出。 +安装器会: -如果使用 URL 备用命令时 `curl` 输出 HTML,或 shell 报 `Syntax error: newline unexpected`,说明安装 URL 被 Web 控制台接管而不是转发到后端。需要确保 `/api/install/` 或 `/install/` 至少一个路径能转发到 BackupX 后端,或改用控制台生成的嵌入式命令。 +1. 检测 `linux/amd64` 或 `linux/arm64`。 +2. 配置显式代理时始终通过该代理下载 Release;否则使用主机的正常直连或环境代理路径,并在该版本提供 SHA-256 旁车文件时进行校验。 +3. 以 `0600` 权限写入 `/etc/backupx-agent/config.yaml` 和 `/etc/backupx-agent/agent.token`。 +4. 不把 Token 写入 systemd unit 或 Docker 环境元数据。 +5. 启动 Agent,并在 30 秒内轮询 `/api/v1/agent/self`。 +6. 节点未上线时返回非零状态,并输出 systemd 或 Docker 排查命令。 -脚本是幂等的:升级或重装只需重新生成一条安装命令再跑一次。一次性安装链接在 TTL 到期或被首次消费后立即作废。 +旧版本如果没有校验文件,会显示兼容性警告后继续安装;新版本应始终发布并校验该文件。 -### 3. 随时轮换 Agent Token - -节点操作列(︙)→ **重新生成 Token**。新 Token 一次性显示,旧 Token 24 小时内仍有效,便于滚动替换无需停机。24 小时后旧 Token 被拒绝。 - -### 4. 批量部署 - -第一步选"批量创建"粘贴节点名(每行一个,最多 50 个)。第三步显示每个节点对应的命令表格,底部「导出 .sh」可打包为单个 shell 文件,方便 SSH 循环或 Ansible 任务。 - -### 5. 把任务路由到该节点 - -在 **备份任务** 页面新建任务时选择对应源服务器。任务触发时: - -- 本机 / 未指定(`nodeId=0`):Master 进程内直接执行 -- 远程节点:Master 写入命令队列 → Agent 拉取 → Agent 本地执行 → 上传 → 回报 - -节点列表会展示 Agent 健康与命令队列状态:pending/dispatched 深度、运行中的长任务、超时数、最旧活跃命令年龄和最近 Agent 错误。同样的队列深度、运行中命令数和超时快照会导出为 Prometheus 指标: - -- `backupx_agent_command_queue_depth` -- `backupx_agent_command_running` -- `backupx_agent_command_timeout_total` - -## 已知限制 - -- **加密备份仅支持 Master 本机执行**:Agent 不持有 Master 的 AES-256 密钥。创建或更新任务时,如果 `encrypt: true` 且选择了远程节点或节点池,会在入口直接拒绝 -- **目录浏览超时**:远程目录浏览通过命令队列做同步 RPC,默认 15s 超时 -- **派发命令超时**:Agent 领取但未完成的命令超过 10 分钟会被置 `timeout` - -## CLI 参考 +### systemd 安装结果 +```yaml title="/etc/backupx-agent/config.yaml" +master: "https://backup.example.com" +tokenFile: "/etc/backupx-agent/agent.token" +heartbeatInterval: "15s" +pollInterval: "5s" +tempDir: "/var/lib/backupx-agent/tmp" +proxyUrl: "" +caCertFile: "" ``` -backupx agent --help - -master string Master URL - -token string Agent 认证令牌 - -config string YAML 配置文件路径(优先级高于环境变量) - -temp-dir string 本地临时目录(默认 /tmp/backupx-agent) - -insecure-tls 跳过 TLS 证书校验(仅测试用) -``` - -## systemd 单元 ```ini title="/etc/systemd/system/backupx-agent.service" [Unit] Description=BackupX Agent -After=network.target +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=300 +StartLimitBurst=10 [Service] Type=simple -User=backupx -Environment="BACKUPX_AGENT_MASTER=https://master.example.com" -Environment="BACKUPX_AGENT_TOKEN=your-token" -ExecStart=/opt/backupx/backupx agent +ExecStart=/opt/backupx-agent/backupx agent --config /etc/backupx-agent/config.yaml Restart=on-failure RestartSec=10s +TimeoutStopSec=30s +UMask=0077 +LimitNOFILE=65535 [Install] WantedBy=multi-user.target ``` -启用并启动: +Agent 以 root 运行,因为文件备份和恢复路径可能属于任意系统用户。应严格限制谁能创建任务,以及谁能修改 root 所有的 Agent 配置。 + +## SSH 堡垒机示例 + +内网 Master 使用 HTTPS 时优先采用 SOCKS 隧道,这样 Master 主机名与证书校验保持不变。 + +先创建专用 SSH 账户,预置私钥和已经人工核对指纹的 `known_hosts`,再创建: + +```sshconfig title="/etc/backupx-agent/ssh_config" +Host backupx-bastion + HostName bastion.example.com + User backupx-tunnel + IdentityFile /etc/backupx-agent/tunnel_ed25519 + IdentitiesOnly yes + BatchMode yes + UserKnownHostsFile /etc/backupx-agent/known_hosts + StrictHostKeyChecking yes + DynamicForward 127.0.0.1:1080 + ExitOnForwardFailure yes + ServerAliveInterval 30 + ServerAliveCountMax 3 +``` + +```ini title="/etc/systemd/system/backupx-agent-tunnel.service" +[Unit] +Description=BackupX Agent SSH tunnel +After=network-online.target +Wants=network-online.target +Before=backupx-agent.service + +[Service] +Type=simple +ExecStart=/usr/bin/ssh -NT -F /etc/backupx-agent/ssh_config backupx-bastion +Restart=always +RestartSec=5s + +[Install] +WantedBy=multi-user.target +``` + +再添加依赖覆写,让隧道不可用时 Agent 关闭失败而不是绕过堡垒机: + +```ini title="/etc/systemd/system/backupx-agent.service.d/tunnel.conf" +[Unit] +Requires=backupx-agent-tunnel.service +After=backupx-agent-tunnel.service +``` ```bash -sudo systemctl enable --now backupx-agent -sudo journalctl -u backupx-agent -f +sudo systemctl daemon-reload +sudo systemctl enable --now backupx-agent-tunnel backupx-agent ``` + +在安装向导中保留内网 HTTPS Master 地址,把代理填写为 `socks5h://127.0.0.1:1080`。启用服务前必须通过独立渠道核对堡垒机 Host Key。 + +## 集中存储数据路径 + +| 目标 | 数据路径 | +| --- | --- | +| S3、WebDAV、FTP、云盘或其他网络后端 | Agent 直接流式上传到目标 | +| 启用 **远程备份经 Master 中转** 的 `local_disk` | Agent 通过认证 Master API 流式上传,Master 写入本地挂载 | + +中转不会在 Master 上额外创建一份完整临时副本,恢复时走反向流式通道。Nginx 必须关闭请求缓冲,才能保持该特性。 + +## 运维 + +```bash +sudo systemctl status backupx-agent +sudo journalctl -u backupx-agent -n 100 --no-pager +sudo /opt/backupx-agent/backupx agent --config /etc/backupx-agent/config.yaml +``` + +从节点操作菜单轮换 Token 后,在 24 小时重叠窗口内更新 `/etc/backupx-agent/agent.token` 并重启服务。 + +建议监控: + +- `backupx_agent_command_queue_depth` +- `backupx_agent_command_running` +- `backupx_agent_command_timeout_total` +- `backupx_node_online` + +## CLI 参考 + +```text +backupx agent --help + -master string Master 地址 + -token string Agent Token + -token-file string 从文件读取 Agent Token + -config string YAML 配置文件路径 + -temp-dir string 本地临时目录 + -proxy-url string HTTP(S) 或 SOCKS5(H) 代理 + -ca-cert string 用于校验 Master 的 PEM CA 证书 + -insecure-tls 跳过 TLS 校验(仅测试) +``` + +环境变量:`BACKUPX_AGENT_MASTER`、`BACKUPX_AGENT_TOKEN`、`BACKUPX_AGENT_TOKEN_FILE`、`BACKUPX_AGENT_HEARTBEAT`、`BACKUPX_AGENT_POLL`、`BACKUPX_AGENT_TEMP_DIR`、`BACKUPX_AGENT_PROXY_URL`、`BACKUPX_AGENT_CA_CERT_FILE`、`BACKUPX_AGENT_INSECURE_TLS`。 + +## 已知限制 + +- Master 使用内置 SQLite,只支持单活。 +- 加密备份仅支持 Master 本机执行,因为 Agent 不持有 Master 加密密钥。 +- 远程目录浏览是同步队列 RPC,默认超时 15 秒。 +- Agent 领取后长期不更新的命令会由 Master 超时监控处理。 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md index 3fbac14..80044db 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md @@ -48,8 +48,9 @@ Docker Hub:[`awuqing/backupx`](https://hub.docker.com/r/awuqing/backupx),支 从 [Releases 页面](https://github.com/Awuqing/BackupX/releases) 下载对应平台的压缩包,执行安装脚本: ```bash +sha256sum -c backupx-v*-linux-amd64.tar.gz.sha256 tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* -sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 systemd + Nginx +sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 systemd ``` 安装脚本会自动: @@ -58,7 +59,7 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 2. 安装二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台安装到 `/opt/backupx/web` 3. 生成 `/etc/backupx/config.yaml`(含安全默认值) 4. 注册并启用 `backupx.service` systemd 单元 -5. (可选)配置 Nginx 反向代理 +5. 默认不修改 Nginx;只有显式设置 `INSTALL_NGINX=1` 时才安装模板 6. 等待 `/api/auth/setup/status` 就绪;启动失败时输出 systemd 诊断并返回非零状态 ## 从源码构建 @@ -74,6 +75,8 @@ sudo ./deploy/install.sh `make build` 完成后,二进制位于 `server/bin/backupx`,构建好的 Web UI 位于 `web/dist/`。 安装脚本会直接使用这两个路径,不需要 Docker 运行时。如果已有配置修改了默认端口,可覆盖就绪检查地址,例如:`sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`。 +自动安装兜底虚拟主机可能接管现有站点,因此 Nginx 模板改为显式启用。请先审核 `deploy/nginx.conf`,确认适合当前主机后再执行 `sudo INSTALL_NGINX=1 ./deploy/install.sh`。 + ## 验证安装 ```bash diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md index 19ef0ea..6a6a10a 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md @@ -21,7 +21,7 @@ description: BackupX——自托管服务器备份管理平台概览。 ``` [Web 控制台] ─── JWT ──→ [Master (backupx)] │ - │ HTTP 长轮询(Token 认证) + │ Agent 主动 HTTP 轮询(Token 认证) ▼ [Agent (backupx agent)] │ diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/reference/cli.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/reference/cli.md index b32874a..849065a 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/reference/cli.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/reference/cli.md @@ -32,11 +32,14 @@ backupx agent --master http://master:8340 --token |------|------| | `--master ` | Master URL | | `--token ` | Agent 认证令牌 | +| `--token-file ` | 从文件读取 Agent Token,服务与容器部署推荐使用 | | `--config ` | YAML 配置文件(优先级高于环境变量) | | `--temp-dir ` | 本地临时目录(默认 `/tmp/backupx-agent`) | +| `--proxy-url ` | 显式 HTTP(S) 或 SOCKS5(H) 代理 | +| `--ca-cert ` | 用于校验 Master 的 PEM CA 证书 | | `--insecure-tls` | 跳过 TLS 校验(仅测试用) | -环境变量:`BACKUPX_AGENT_MASTER`、`BACKUPX_AGENT_TOKEN`、`BACKUPX_AGENT_HEARTBEAT`、`BACKUPX_AGENT_POLL`、`BACKUPX_AGENT_TEMP_DIR`、`BACKUPX_AGENT_INSECURE_TLS`。 +环境变量:`BACKUPX_AGENT_MASTER`、`BACKUPX_AGENT_TOKEN`、`BACKUPX_AGENT_TOKEN_FILE`、`BACKUPX_AGENT_HEARTBEAT`、`BACKUPX_AGENT_POLL`、`BACKUPX_AGENT_TEMP_DIR`、`BACKUPX_AGENT_PROXY_URL`、`BACKUPX_AGENT_CA_CERT_FILE`、`BACKUPX_AGENT_INSECURE_TLS`。未设置显式代理时,Agent 同样遵循 `HTTP_PROXY`、`HTTPS_PROXY` 和 `NO_PROXY`。 ## `backupx backint` diff --git a/docs-site/package-lock.json b/docs-site/package-lock.json index d9ca95b..ca8ea04 100644 --- a/docs-site/package-lock.json +++ b/docs-site/package-lock.json @@ -1,16 +1,16 @@ { - "name": "docs-site-tmp", + "name": "backupx-docs", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "docs-site-tmp", + "name": "backupx-docs", "version": "1.0.0", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/faster": "3.10.0", - "@docusaurus/preset-classic": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/faster": "3.10.2", + "@docusaurus/preset-classic": "3.10.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", @@ -18,9 +18,9 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/tsconfig": "3.10.0", - "@docusaurus/types": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/tsconfig": "3.10.2", + "@docusaurus/types": "3.10.2", "@types/react": "^19.0.0", "typescript": "~6.0.2" }, @@ -28,47 +28,62 @@ "node": ">=20.0" } }, - "node_modules/@algolia/abtesting": { - "version": "1.16.2", - "resolved": "https://registry.npmmirror.com/@algolia/abtesting/-/abtesting-1.16.2.tgz", - "integrity": "sha512-n9s6bEV6imdtIEd+BGP7WkA4pEZ5YTdgQ05JQhHwWawHg3hyjpNwC0TShGz6zWhv+jfLDGA/6FFNbySFS0P9cw==", + "node_modules/@11ty/gray-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/gray-matter/-/gray-matter-1.0.0.tgz", + "integrity": "sha512-7mJJl+wf1AByoT0PknQiQfOPnVNT4fevGrUBVWO4HXsnYn1aQPyRyrELYrNUFleUBM++KzMKN6QaxHPk0t/6/g==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "js-yaml": "^4.1.0", + "kind-of": "^6.0.3", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.22.0.tgz", + "integrity": "sha512-BFR6zNowNKcY7Ou7TaJc9QWexES4YKPbmf/OTFofpdsdhz4x6q0lbxp3duO0EHnyrN7rE4ba/TSXuY+BDGu4+g==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.19.8", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.8.tgz", - "integrity": "sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.9.tgz", + "integrity": "sha512-4U2JKLMWlDu0CotYyUkWakDxr8AIav3QtIUXXRpfavYN29aVWfzlwJp9T0rPKEf/dO2QCPAUc0Kq1Tj1GJxo2A==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.8", - "@algolia/autocomplete-shared": "1.19.8" + "@algolia/autocomplete-plugin-algolia-insights": "1.19.9", + "@algolia/autocomplete-shared": "1.19.9" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.8", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.8.tgz", - "integrity": "sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.9.tgz", + "integrity": "sha512-6mExC6X7762s2SV3eJy3QOkB8bdMmnUhQ2agvGVDuzwoGyr3PquGSY/0vPQXCfiAiCaXUz1rXn+lwghgSi0l0w==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.19.8" + "@algolia/autocomplete-shared": "1.19.9" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.19.8", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz", - "integrity": "sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.9.tgz", + "integrity": "sha512-YosP9Uoek6y/Ur1r1qeogk4biMe/hzkyNcgMCciw0//3XpCM7VlYLSHnyt/vOnEOGhCCc0+3v+unEiH6zz+Z1A==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -76,99 +91,99 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.50.2.tgz", - "integrity": "sha512-52iq0vHy1sphgnwoZyx5PmbEt8hsh+m7jD123LmBs6qy4GK7LbYZIeKd+nSnSipN2zvKRZ2zScS6h9PW3J7SXg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.56.0.tgz", + "integrity": "sha512-7r4Z3NC7yU1oAQVWJNA2HX7tX481F3pJvCGyLIXiTdBcthz4Q/o21jwcMYDFkuI92UWTNBQQmHYgwHo1zS5dzg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.50.2.tgz", - "integrity": "sha512-WpPIUg+cSG2aPUG0gS8Ko9DwRgbRPUZxJkolhL2aCsmSlcEEZT65dILrfg5ovcxtx0Kvr+xtBVsTMtsQWRtPDQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.56.0.tgz", + "integrity": "sha512-avmjXQSq+jadFO8Xl2em05/uQdQnEmHsJyOAdVbZkmVgpMfxL12aJwVVfGNwYr9nulcpuJN1X0lTaQ5wxuNGcA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.50.2.tgz", - "integrity": "sha512-Gj2MgtArGcsr82kIqRlo6/dCAFjrs2gLByEqyRENuT7ugrSMFuqg1vDzeBjRL1t3EJEJCFtT0PLX3gB8A6Hq4Q==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.56.0.tgz", + "integrity": "sha512-v2TPStUhY//ripPjIVclZ8AWc7DEGooXULZGFlFu37zNatgHjw34oZZ+OSbbc/YHO+xZwPl62I1k8xH1m4S2eg==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.50.2.tgz", - "integrity": "sha512-CUqoid5jDpmrc0oK3/xuZXFt6kwT0P9Lw7/nsM14YTr6puvmi+OUKmURpmebQF22S2vCG8L1DAoXXujxQUi/ug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.56.0.tgz", + "integrity": "sha512-P0ehROpM4Sem3Sqo5x2cKPgj67D3G3jy0rh1Amwkcvsfr6tkvIcdCmerieanqTF7NxUMPNFLkpIFeMO8Rpa50w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.50.2.tgz", - "integrity": "sha512-AndZWFoc0gbP5901OeQJ73BazgGgSGiBEba4ohdoJuZwHTO2Gio8Q4L1VLmytMBYcviVigB0iICToMvEJxI4ug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.56.0.tgz", + "integrity": "sha512-SXK3Vn3WVxyzbm31oePZBJkp1wpOyuWdd4B/Pv7n0aXDxmeSWhC1R1FC1517mMrFAIaPH4Rt0x6RUe7ZNjz8FA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.2.tgz", - "integrity": "sha512-NWoL+psEkz5dIzweaByVXuEB45wS8/rk0E0AhMMnaVJdVs7TcACPH2/OURm+N0xRDITkTHqCna823rd6Uqntdg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.56.0.tgz", + "integrity": "sha512-5+ZdX8garFnmycnZgKhtXHePEaLj5zqDxI/0lkhhluzCcvTn0/PvvTirTg8hHYetQHvn7GDyeAiqTAieMvMW4A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.50.2.tgz", - "integrity": "sha512-ypSboUJ3XJoQz5DeDo82hCnrRuwq3q9ZdFhVKAik9TnZh1DvLqoQsrbBjXg7C7zQOtV/Qbge/HmyoV6V5L7MhQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.56.0.tgz", + "integrity": "sha512-+mKUdYvqOi0BcvpAEyCEw49vSBptufIcfibtHz2bdr1pI789M46Yt0uQEk/sxtK3teh71OQvVFHaTDzShUWewQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" @@ -176,86 +191,86 @@ }, "node_modules/@algolia/events": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/@algolia/events/-/events-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.50.2.tgz", - "integrity": "sha512-VlR2FRXLw2bCB94SQo6zxg/Qi+547aOji6Pb+dKE7h1DMCCY317St+OpjpmgzE+bT2O9ALIc0V4nVIBOd7Gy+Q==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.56.0.tgz", + "integrity": "sha512-9g/zj+AZx5moFcdFIrYQoVrueXivjUcc3MQHtCYT8WhIuk1lUh1AyEhvJCS0XBZld09cLvd1AZ3BvDBpVpX2UA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.50.2.tgz", - "integrity": "sha512-Cmvfp2+qopzQt8OilU97rhLhosq7ZrB6uieok3EwFUqG/aalPg6DgfCmu0yJMrYe+KMC1qRVt1MTRAUwLknUMQ==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.56.0.tgz", + "integrity": "sha512-Qf3Sr6f9A9uxCZUf3MXS0d2b877uYzEB5yxqpVGXAhcJnBCQjrRRon0KvefpGkxy+BshrIJs96OUoMtGqXTFDA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.50.2.tgz", - "integrity": "sha512-jrkuyKoOM7dFWQ/6Y4hQAse2SC3L/RldG6GnPjMvAj65h+7Ubb51S0pKk4ofSStF0xm4LCNe0C4T6XX4nOFDiQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.56.0.tgz", + "integrity": "sha512-GXWG1rWc5wu8hY4N33Y3b6ernY6sAdAvmKWN/zHAiACOx40WnpG0TVX5YazCAr/9gOYGInSiM2A0y2jy2xbiDA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.2.tgz", - "integrity": "sha512-4107YLJqCudPiBUlwnk6oTSUVwU7ab+qL1SfQGEDYI8DZH5gsf1ekPt9JykXRKYXf2IfouFL5GiCY/PHTFIjYw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.56.0.tgz", + "integrity": "sha512-7t24cBxaInS3mZb7ddEaZT/tp6q+/aR4YttsQVyP1/i+LmwPR34atO35KjaLFCcRVrlP7sYOAqkCfg6lIRB+ew==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.50.2.tgz", - "integrity": "sha512-vOrd3MQpLgmf6wXAueTuZ/cA0W4uRwIHHaxNy3h+a6YcNn6bCV/gFdZuv3F13v593zRU2k5R75NmvRWLenvMrw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.56.0.tgz", + "integrity": "sha512-R7ePHgVYmDFjZpvrsVAfbDz/d4RxKAYZ5/vgLfIsCVRZRryjWl/3INOxpOICzitehQ5FjNtNjcLQTrmHPTcHBQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.50.2.tgz", - "integrity": "sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.56.0.tgz", + "integrity": "sha512-PIOUXlSnrqM0S+WOgDRb4RzotydJH7ZoT6tOyL7tAO7qJOfvX5wsEW8Pe+PMKMwvuI4/gIyK9cg2H7lJXqnc4Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" @@ -276,29 +291,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@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", @@ -316,7 +331,7 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -324,13 +339,13 @@ } }, "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==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -340,25 +355,25 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "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==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "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==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@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" @@ -369,7 +384,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -377,17 +392,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "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==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@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": { @@ -399,7 +414,7 @@ }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -407,12 +422,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, @@ -425,7 +440,7 @@ }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -434,7 +449,7 @@ }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.8", - "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { @@ -458,13 +473,13 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "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==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -501,12 +516,12 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "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==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -522,14 +537,14 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -539,14 +554,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@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" @@ -556,13 +571,13 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "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==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -587,48 +602,48 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "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==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@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==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -638,13 +653,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -654,12 +669,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -669,12 +684,28 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -684,14 +715,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -701,13 +732,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -718,7 +749,7 @@ }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", "engines": { @@ -730,7 +761,7 @@ }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "license": "MIT", "dependencies": { @@ -741,12 +772,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -756,12 +787,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "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==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -771,12 +802,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "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==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -786,12 +817,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "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==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -802,7 +833,7 @@ }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", "dependencies": { @@ -817,12 +848,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -832,395 +863,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", - "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "engines": { @@ -1230,14 +879,396 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "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-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "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-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "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-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "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-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "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-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "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-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "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-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "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-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "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-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "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-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1247,13 +1278,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1263,12 +1294,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1278,12 +1309,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1293,12 +1324,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1308,16 +1339,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1327,13 +1358,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1343,12 +1374,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1358,13 +1389,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1374,12 +1405,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1389,13 +1420,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1405,14 +1436,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1422,12 +1453,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1437,12 +1468,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", + "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1452,12 +1483,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1467,16 +1498,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", - "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-syntax-jsx": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1486,12 +1517,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" + "@babel/plugin-transform-react-jsx": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1501,13 +1532,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1517,12 +1548,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1532,13 +1563,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1548,12 +1579,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1563,13 +1594,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", - "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1584,7 +1615,7 @@ }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -1592,12 +1623,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1607,13 +1638,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1623,12 +1654,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1638,12 +1669,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1653,12 +1684,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1668,16 +1699,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@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" @@ -1687,12 +1718,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1702,13 +1733,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1718,13 +1749,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1734,13 +1765,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1750,75 +1781,76 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", @@ -1835,7 +1867,7 @@ }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { "version": "0.14.2", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { @@ -1848,7 +1880,7 @@ }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -1857,7 +1889,7 @@ }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", "dependencies": { @@ -1870,17 +1902,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.28.5.tgz", - "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.28.0", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1890,16 +1922,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1932,17 +1964,17 @@ } }, "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==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -1950,9 +1982,9 @@ } }, "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==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -1974,7 +2006,7 @@ }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", "funding": [ { @@ -1997,7 +2029,7 @@ }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "funding": [ { @@ -2016,7 +2048,7 @@ }, "node_modules/@csstools/css-calc": { "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "funding": [ { @@ -2039,7 +2071,7 @@ }, "node_modules/@csstools/css-color-parser": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "funding": [ { @@ -2066,7 +2098,7 @@ }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "funding": [ { @@ -2088,7 +2120,7 @@ }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "funding": [ { @@ -2107,7 +2139,7 @@ }, "node_modules/@csstools/media-query-list-parser": { "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", "funding": [ { @@ -2130,7 +2162,7 @@ }, "node_modules/@csstools/postcss-alpha-function": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", "funding": [ { @@ -2159,7 +2191,7 @@ }, "node_modules/@csstools/postcss-cascade-layers": { "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", "funding": [ { @@ -2185,7 +2217,7 @@ }, "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { @@ -2206,9 +2238,9 @@ } }, "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2220,7 +2252,7 @@ }, "node_modules/@csstools/postcss-color-function": { "version": "4.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", "funding": [ { @@ -2249,7 +2281,7 @@ }, "node_modules/@csstools/postcss-color-function-display-p3-linear": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", "funding": [ { @@ -2278,7 +2310,7 @@ }, "node_modules/@csstools/postcss-color-mix-function": { "version": "3.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", "funding": [ { @@ -2307,7 +2339,7 @@ }, "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", "funding": [ { @@ -2336,7 +2368,7 @@ }, "node_modules/@csstools/postcss-content-alt-text": { "version": "2.0.8", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", "funding": [ { @@ -2364,7 +2396,7 @@ }, "node_modules/@csstools/postcss-contrast-color-function": { "version": "2.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", "funding": [ { @@ -2393,7 +2425,7 @@ }, "node_modules/@csstools/postcss-exponential-functions": { "version": "2.0.9", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", "funding": [ { @@ -2420,7 +2452,7 @@ }, "node_modules/@csstools/postcss-font-format-keywords": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", "funding": [ { @@ -2446,7 +2478,7 @@ }, "node_modules/@csstools/postcss-gamut-mapping": { "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", "funding": [ { @@ -2473,7 +2505,7 @@ }, "node_modules/@csstools/postcss-gradients-interpolation-method": { "version": "5.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", "funding": [ { @@ -2502,7 +2534,7 @@ }, "node_modules/@csstools/postcss-hwb-function": { "version": "4.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", "funding": [ { @@ -2531,7 +2563,7 @@ }, "node_modules/@csstools/postcss-ic-unit": { "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", "funding": [ { @@ -2558,7 +2590,7 @@ }, "node_modules/@csstools/postcss-initial": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", "funding": [ { @@ -2580,7 +2612,7 @@ }, "node_modules/@csstools/postcss-is-pseudo-class": { "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", "funding": [ { @@ -2606,7 +2638,7 @@ }, "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { @@ -2627,9 +2659,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2641,7 +2673,7 @@ }, "node_modules/@csstools/postcss-light-dark-function": { "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", "funding": [ { @@ -2669,7 +2701,7 @@ }, "node_modules/@csstools/postcss-logical-float-and-clear": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", "funding": [ { @@ -2691,7 +2723,7 @@ }, "node_modules/@csstools/postcss-logical-overflow": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", "funding": [ { @@ -2713,7 +2745,7 @@ }, "node_modules/@csstools/postcss-logical-overscroll-behavior": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", "funding": [ { @@ -2735,7 +2767,7 @@ }, "node_modules/@csstools/postcss-logical-resize": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", "funding": [ { @@ -2760,7 +2792,7 @@ }, "node_modules/@csstools/postcss-logical-viewport-units": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", "funding": [ { @@ -2786,7 +2818,7 @@ }, "node_modules/@csstools/postcss-media-minmax": { "version": "2.0.9", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", "funding": [ { @@ -2814,7 +2846,7 @@ }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", "funding": [ { @@ -2841,7 +2873,7 @@ }, "node_modules/@csstools/postcss-nested-calc": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", "funding": [ { @@ -2867,7 +2899,7 @@ }, "node_modules/@csstools/postcss-normalize-display-values": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", "integrity": "sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==", "funding": [ { @@ -2892,7 +2924,7 @@ }, "node_modules/@csstools/postcss-oklab-function": { "version": "4.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", "funding": [ { @@ -2921,7 +2953,7 @@ }, "node_modules/@csstools/postcss-position-area-property": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", "integrity": "sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==", "funding": [ { @@ -2943,7 +2975,7 @@ }, "node_modules/@csstools/postcss-progressive-custom-properties": { "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", "funding": [ { @@ -2968,7 +3000,7 @@ }, "node_modules/@csstools/postcss-property-rule-prelude-list": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", "funding": [ { @@ -2994,7 +3026,7 @@ }, "node_modules/@csstools/postcss-random-function": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", "funding": [ { @@ -3021,7 +3053,7 @@ }, "node_modules/@csstools/postcss-relative-color-syntax": { "version": "3.0.12", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", "funding": [ { @@ -3050,7 +3082,7 @@ }, "node_modules/@csstools/postcss-scope-pseudo-class": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", "funding": [ { @@ -3074,9 +3106,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -3088,7 +3120,7 @@ }, "node_modules/@csstools/postcss-sign-functions": { "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", "funding": [ { @@ -3115,7 +3147,7 @@ }, "node_modules/@csstools/postcss-stepped-value-functions": { "version": "4.0.9", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", "funding": [ { @@ -3142,7 +3174,7 @@ }, "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", "funding": [ { @@ -3167,7 +3199,7 @@ }, "node_modules/@csstools/postcss-system-ui-font-family": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", "integrity": "sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==", "funding": [ { @@ -3193,7 +3225,7 @@ }, "node_modules/@csstools/postcss-text-decoration-shorthand": { "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", "funding": [ { @@ -3219,7 +3251,7 @@ }, "node_modules/@csstools/postcss-trigonometric-functions": { "version": "4.0.9", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", "funding": [ { @@ -3246,7 +3278,7 @@ }, "node_modules/@csstools/postcss-unset-value": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", "funding": [ { @@ -3268,7 +3300,7 @@ }, "node_modules/@csstools/utilities": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/utilities/-/utilities-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", "funding": [ { @@ -3298,9 +3330,9 @@ } }, "node_modules/@docsearch/core": { - "version": "4.6.2", - "resolved": "https://registry.npmmirror.com/@docsearch/core/-/core-4.6.2.tgz", - "integrity": "sha512-/S0e6Dj7Zcm8m9Rru49YEX49dhU11be68c+S/BCyN8zQsTTgkKzXlhRbVL5mV6lOLC2+ZRRryaTdcm070Ug2oA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.7.0.tgz", + "integrity": "sha512-p/9xVKmPDj3FPvMfPf5naVO3Ej8SCbcUugGvx1+8GgkuBNbqxqN2Irx3WLBv8VY0jH7XpRwKWdlmjXLZsmTLsg==", "license": "MIT", "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3320,20 +3352,20 @@ } }, "node_modules/@docsearch/css": { - "version": "4.6.2", - "resolved": "https://registry.npmmirror.com/@docsearch/css/-/css-4.6.2.tgz", - "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.7.0.tgz", + "integrity": "sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw==", "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "4.6.2", - "resolved": "https://registry.npmmirror.com/@docsearch/react/-/react-4.6.2.tgz", - "integrity": "sha512-/BbtGFtqVOGwZx0dw/UfhN/0/DmMQYnulY4iv0tPRhC2JCXv0ka/+izwt3Jzo1ZxXS/2eMvv9zHsBJOK1I9f/w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.7.0.tgz", + "integrity": "sha512-x6oedjJ8O8/pIDBsMo5Orca3/6cQCz616/CwthVe68l43mqnj2lrJ9kFQITBqy8hMsS3nWeBWFoVO5dJ1DCFKA==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.6.2", - "@docsearch/css": "4.6.2" + "@docsearch/core": "4.7.0", + "@docsearch/css": "4.7.0" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3358,7 +3390,7 @@ }, "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { "version": "1.19.2", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", "license": "MIT", "dependencies": { @@ -3368,7 +3400,7 @@ }, "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { "version": "1.19.2", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", "license": "MIT", "dependencies": { @@ -3380,7 +3412,7 @@ }, "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { "version": "1.19.2", - "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", "license": "MIT", "peerDependencies": { @@ -3389,9 +3421,9 @@ } }, "node_modules/@docusaurus/babel": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/babel/-/babel-3.10.0.tgz", - "integrity": "sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.2.tgz", + "integrity": "sha512-aJ1hpGyvfkte3dDAfNbWM4biW4yWZBVz7TIGLZP+v+tWOBgxX3e0N5ZIXHIvmfNNXTI77pcHUx3KmtOk05Ze3Q==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", @@ -3403,8 +3435,8 @@ "@babel/preset-typescript": "^7.25.9", "@babel/runtime": "^7.25.9", "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", "babel-plugin-dynamic-import-node": "^2.3.3", "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -3414,17 +3446,17 @@ } }, "node_modules/@docusaurus/bundler": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/bundler/-/bundler-3.10.0.tgz", - "integrity": "sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.2.tgz", + "integrity": "sha512-i0ZNcy0f0WhaOlYVgzLsWhIoEXO9kS3HRoKPtgE6vQtZUq7arKZaYdNBudr3mqCmd+TyOkwtwfHgs1ENj07r5g==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.10.0", - "@docusaurus/cssnano-preset": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", + "@docusaurus/babel": "3.10.2", + "@docusaurus/cssnano-preset": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", "babel-loader": "^9.2.1", "clean-css": "^5.3.3", "copy-webpack-plugin": "^11.0.0", @@ -3442,7 +3474,7 @@ "tslib": "^2.6.0", "url-loader": "^4.1.1", "webpack": "^5.95.0", - "webpackbar": "^6.0.1" + "webpackbar": "^7.0.0" }, "engines": { "node": ">=20.0" @@ -3457,18 +3489,18 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/core/-/core-3.10.0.tgz", - "integrity": "sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.2.tgz", + "integrity": "sha512-EYByj6nk+aD9KeVxV6Hmo2/nAAT79P21Y82ycTBOBtrmqilloIbIEhgL2/8Xpt2Jz/pgNqHAwyusOGwmbKeJmA==", "license": "MIT", "dependencies": { - "@docusaurus/babel": "3.10.0", - "@docusaurus/bundler": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/babel": "3.10.2", + "@docusaurus/bundler": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "boxen": "^6.2.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", @@ -3476,7 +3508,7 @@ "combine-promises": "^1.1.0", "commander": "^5.1.0", "core-js": "^3.31.1", - "detect-port": "^1.5.1", + "detect-port": "^2.1.0", "escape-html": "^1.0.3", "eta": "^2.2.0", "eval": "^0.1.8", @@ -3524,9 +3556,9 @@ } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz", - "integrity": "sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.2.tgz", + "integrity": "sha512-4gCnHRbJLTloiwfvFAa92tgb2gI4KYhvjfQVYnEaiMO/EgvWfCo1LwytHXen+1oZAN0VAlS0JAPxp3MsvKDa3A==", "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", @@ -3539,15 +3571,15 @@ } }, "node_modules/@docusaurus/faster": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/faster/-/faster-3.10.0.tgz", - "integrity": "sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/faster/-/faster-3.10.2.tgz", + "integrity": "sha512-p/5E5/RyHv+QWusJMPN5i3OMJTqTgkhuwzVbB1AReDWTUHXQCmf5mlTFzGiDrWeQWIDOKsuOPn1jJh0s9LUOHA==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.10.0", + "@docusaurus/types": "3.10.2", "@rspack/core": "^1.7.10", - "@swc/core": "^1.7.39", - "@swc/html": "^1.13.5", + "@swc/core": "^1.15.40", + "@swc/html": "^1.15.40", "browserslist": "^4.24.2", "lightningcss": "^1.27.0", "semver": "^7.5.4", @@ -3563,9 +3595,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/logger/-/logger-3.10.0.tgz", - "integrity": "sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.2.tgz", + "integrity": "sha512-gSEwqtPfCAnC3ZSJY6xL7tcIfgg0vFD39jbv93eakuweyvO2864xR0K+kmKwBhkTCtWRNjuGGnb5rdmkD/ndqw==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -3576,14 +3608,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz", - "integrity": "sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.2.tgz", + "integrity": "sha512-9Fd4V/SFjfrVQ0JH5EN0+iPWyFunvTeQE3gfyFeetqPaXMP0OylIjOw16dCuXG4NZJrYdBqwzjh18/h3gRi47w==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -3615,12 +3647,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz", - "integrity": "sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.2.tgz", + "integrity": "sha512-h/I5e4jaAhDHW4vaLENi1i2hnOEnXY1t9R+nnRTbgUl7ymVRzN/HF7dDfj8rKYGj8gfIge+Ef+iYRAMtbGvsrQ==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.10.0", + "@docusaurus/types": "3.10.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3634,19 +3666,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz", - "integrity": "sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.2.tgz", + "integrity": "sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "cheerio": "1.0.0-rc.12", "combine-promises": "^1.1.0", "feed": "^4.2.2", @@ -3669,20 +3701,20 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz", - "integrity": "sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.2.tgz", + "integrity": "sha512-Sqwl4FPoZBDrlY8I2VU2H8O0M91CHp9T8ToMSkTZmjvHCif+1laqfXi6sTk8IfyVS/trN5yNjcWd1bFsGB6W5Q==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -3702,16 +3734,16 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz", - "integrity": "sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.2.tgz", + "integrity": "sha512-h5R12sZ/vV9EPiVjvIl9YFCOwkpwXes7dQMYt3EvP6Pphu4amHxxTqWxf08Fl5DR8h+oZMbWpFTNw5vKEYfvzQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -3725,15 +3757,15 @@ } }, "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz", - "integrity": "sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.2.tgz", + "integrity": "sha512-UkdvQby5OQUKWrw3lLnSTJXQ6VETaUVTuPQX9AABtmFm5h+ifEBx1OQ+LN726Q4byuwBf2ElHkf4qU4hTxdvRg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "tslib": "^2.6.0" }, "engines": { @@ -3741,14 +3773,14 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz", - "integrity": "sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.2.tgz", + "integrity": "sha512-8vbZNOSCpnsT57EY6CgN7sgRVmx3KTYwO8Uvo2pbxOyb8tbqAwtT9SslqaQ41HbA1v1hpn5RP7u5s2KvRwAFpQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^2.3.0", "tslib": "^2.6.0" @@ -3762,14 +3794,14 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz", - "integrity": "sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.2.tgz", + "integrity": "sha512-kMHMBK9j4VAtgd5owwrRLRIi0EjkrpXlX7ePj1+y68XfVZV9I1T4S+koPDm+Hfw2TtnyHvh0uNrDvjz+DjQGVA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "tslib": "^2.6.0" }, "engines": { @@ -3781,15 +3813,14 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz", - "integrity": "sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.2.tgz", + "integrity": "sha512-Vt90nNFhtAChRe9+it1hcHFgFvETdSnOkL5Bma+p6E/yU2tAYrvvyk+gv+LJGM2ZUkyKuKXLRsZ2Lb0bO7+Vog==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@types/gtag.js": "^0.0.20", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "tslib": "^2.6.0" }, "engines": { @@ -3801,14 +3832,14 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz", - "integrity": "sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.2.tgz", + "integrity": "sha512-MLCffCldysi/R0nzJQP7ZWd0xAoGNnSTiVOo6TTR6mKVGFhE+/XArGe67ZcaZv1uytgQXoXs92VJrgVDrz80rQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "tslib": "^2.6.0" }, "engines": { @@ -3820,17 +3851,17 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz", - "integrity": "sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.2.tgz", + "integrity": "sha512-PODkwg5XetLML3hU/3xpCKJUZ9cqExLaBnD/Fzzwj2VHogLeqnDisLIujae87zuze7T4mCm2A6KEqZkyiz07EQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -3844,15 +3875,15 @@ } }, "node_modules/@docusaurus/plugin-svgr": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz", - "integrity": "sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.2.tgz", + "integrity": "sha512-JgfT3jWM0TJ8Uw0cEcqxHpybngQY1vlBYpuuNO+gEh5iPh5Ar+vxq/u9CFrYsWeXy48BN7Db76Pzp2edNXUQ8A==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "@svgr/core": "8.1.0", "@svgr/webpack": "^8.1.0", "tslib": "^2.6.0", @@ -3867,26 +3898,26 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz", - "integrity": "sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.2.tgz", + "integrity": "sha512-a4B3VczmDl99zK0EufDQYomdJ186WDingjmDXxhN2PNPS9Ty/Y2M5CLFX1KQMRKqRTLiRDKfutzG5IY1FC/ceg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/plugin-content-blog": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/plugin-content-pages": "3.10.0", - "@docusaurus/plugin-css-cascade-layers": "3.10.0", - "@docusaurus/plugin-debug": "3.10.0", - "@docusaurus/plugin-google-analytics": "3.10.0", - "@docusaurus/plugin-google-gtag": "3.10.0", - "@docusaurus/plugin-google-tag-manager": "3.10.0", - "@docusaurus/plugin-sitemap": "3.10.0", - "@docusaurus/plugin-svgr": "3.10.0", - "@docusaurus/theme-classic": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-search-algolia": "3.10.0", - "@docusaurus/types": "3.10.0" + "@docusaurus/core": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/plugin-css-cascade-layers": "3.10.2", + "@docusaurus/plugin-debug": "3.10.2", + "@docusaurus/plugin-google-analytics": "3.10.2", + "@docusaurus/plugin-google-gtag": "3.10.2", + "@docusaurus/plugin-google-tag-manager": "3.10.2", + "@docusaurus/plugin-sitemap": "3.10.2", + "@docusaurus/plugin-svgr": "3.10.2", + "@docusaurus/theme-classic": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-search-algolia": "3.10.2", + "@docusaurus/types": "3.10.2" }, "engines": { "node": ">=20.0" @@ -3897,24 +3928,24 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz", - "integrity": "sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.2.tgz", + "integrity": "sha512-JqTSLQmqmA9uKWZsD5iwBGJ4JyKB4/yTw6PsSXVPRJG/6GAm/u+add9Iip+hvwP12/AnPNztrdxsI14NJW4KeA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/plugin-content-blog": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/plugin-content-pages": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-translations": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", @@ -3938,15 +3969,15 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/theme-common/-/theme-common-3.10.0.tgz", - "integrity": "sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.2.tgz", + "integrity": "sha512-R9b/vMpK1yye6hNZTA6x/ivRv+at6GhxnXcxkpzCGzO1R1RwiquqiFg2wMFh6aqlJTpWRFKpFD2TzCDQcyOU0A==", "license": "MIT", "dependencies": { - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3966,20 +3997,20 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz", - "integrity": "sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.2.tgz", + "integrity": "sha512-1msxllyhi/5m77JukXtp5UFnUAriwZIC1oJ7MTnpQpCwLTbclJi5BK5n28CTZuSXpQN2ewbbnqRgAhMM6c6ihg==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "^1.19.2", "@docsearch/react": "^3.9.0 || ^4.3.2", - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-translations": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", "algoliasearch": "^5.37.0", "algoliasearch-helper": "^3.26.0", "clsx": "^2.0.0", @@ -3998,9 +4029,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz", - "integrity": "sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.2.tgz", + "integrity": "sha512-iv20wrxnyXkY89LM3TzRlzGlt5fIGO5UnaR6UL1ZVfB9RRFjxQFQ6awDrwAc6Km8Y5gD8pInuwYPF+6/TiCxXA==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -4011,16 +4042,16 @@ } }, "node_modules/@docusaurus/tsconfig": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/tsconfig/-/tsconfig-3.10.0.tgz", - "integrity": "sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.10.2.tgz", + "integrity": "sha512-5GiB7h/nFsMFPO9mCqcRNE1yA5TSXXNCshNIgHPL6fCPOjcTDixs6qjQBu8ddkgPcicwCvOA7n3jeK2rGdJk6g==", "dev": true, "license": "MIT" }, "node_modules/@docusaurus/types": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/types/-/types-3.10.0.tgz", - "integrity": "sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.2.tgz", + "integrity": "sha512-B6rvfwIFSapUqUJjMriZswX13K8l5Z7AcmVE6uTEJpYddQieSTR12DsGaFtcZAIDsQd4p+0WTl0Vc6jmZK0Trw==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -4041,7 +4072,7 @@ }, "node_modules/@docusaurus/types/node_modules/webpack-merge": { "version": "5.10.0", - "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.10.0.tgz", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "license": "MIT", "dependencies": { @@ -4054,21 +4085,21 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/utils/-/utils-3.10.0.tgz", - "integrity": "sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.2.tgz", + "integrity": "sha512-xx0W3eav2uW1NRIpuHJWNwLTC15xPNjU4Uxi9NSnd3swYC96BE3vFiT93SD8s24kmAAWNwgZwfZ2fghGZ01Lcw==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-common": "3.10.0", + "@11ty/gray-matter": "^1.0.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-common": "3.10.2", "escape-string-regexp": "^4.0.0", "execa": "^5.1.1", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", "github-slugger": "^1.5.0", "globby": "^11.1.0", - "gray-matter": "^4.0.3", "jiti": "^1.20.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", @@ -4086,12 +4117,12 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/utils-common/-/utils-common-3.10.0.tgz", - "integrity": "sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.2.tgz", + "integrity": "sha512-x3Dz6jv6iQKBNjBmVTu8p57abMp/VNTUgKBMgRVXJc5444orBTsArv0+cdfrXTiz/VMmHfDRVkPbL7GH2B7T7w==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.10.0", + "@docusaurus/types": "3.10.2", "tslib": "^2.6.0" }, "engines": { @@ -4099,14 +4130,14 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz", - "integrity": "sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.2.tgz", + "integrity": "sha512-sn8unbDfUL585NtR3cwHefPicOyaHvPaX7VD0aOg/siIxUBoKyKKaGEqzJZDS64mM43TnxurkYDtmB1wsJlZsw==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -4150,13 +4181,13 @@ }, "node_modules/@hapi/hoek": { "version": "9.3.0", - "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/@hapi/topo/-/topo-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", "license": "BSD-3-Clause", "dependencies": { @@ -4165,7 +4196,7 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "license": "MIT", "dependencies": { @@ -4177,7 +4208,7 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.3.tgz", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", "dependencies": { @@ -4204,7 +4235,7 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { @@ -4664,7 +4695,7 @@ }, "node_modules/@mdx-js/mdx": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", "license": "MIT", "dependencies": { @@ -4795,7 +4826,7 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", "dependencies": { @@ -4808,7 +4839,7 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "license": "MIT", "engines": { @@ -4817,7 +4848,7 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", "dependencies": { @@ -5201,7 +5232,7 @@ }, "node_modules/@sideway/address": { "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "license": "BSD-3-Clause", "dependencies": { @@ -5210,25 +5241,25 @@ }, "node_modules/@sideway/formula": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/@sideway/formula/-/formula-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", - "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "license": "MIT", "engines": { @@ -5240,7 +5271,7 @@ }, "node_modules/@slorber/remark-comment": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", "license": "MIT", "dependencies": { @@ -5251,7 +5282,7 @@ }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "license": "MIT", "engines": { @@ -5267,7 +5298,7 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", "license": "MIT", "engines": { @@ -5283,7 +5314,7 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", "license": "MIT", "engines": { @@ -5299,7 +5330,7 @@ }, "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "license": "MIT", "engines": { @@ -5315,7 +5346,7 @@ }, "node_modules/@svgr/babel-plugin-svg-dynamic-title": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "license": "MIT", "engines": { @@ -5331,7 +5362,7 @@ }, "node_modules/@svgr/babel-plugin-svg-em-dimensions": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "license": "MIT", "engines": { @@ -5347,7 +5378,7 @@ }, "node_modules/@svgr/babel-plugin-transform-react-native-svg": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "license": "MIT", "engines": { @@ -5363,7 +5394,7 @@ }, "node_modules/@svgr/babel-plugin-transform-svg-component": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "license": "MIT", "engines": { @@ -5379,7 +5410,7 @@ }, "node_modules/@svgr/babel-preset": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "license": "MIT", "dependencies": { @@ -5405,7 +5436,7 @@ }, "node_modules/@svgr/core": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/core/-/core-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", "dependencies": { @@ -5425,7 +5456,7 @@ }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "license": "MIT", "dependencies": { @@ -5442,7 +5473,7 @@ }, "node_modules/@svgr/plugin-jsx": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "license": "MIT", "dependencies": { @@ -5464,7 +5495,7 @@ }, "node_modules/@svgr/plugin-svgo": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", "license": "MIT", "dependencies": { @@ -5485,7 +5516,7 @@ }, "node_modules/@svgr/webpack": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/@svgr/webpack/-/webpack-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", "license": "MIT", "dependencies": { @@ -5507,14 +5538,14 @@ } }, "node_modules/@swc/core": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core/-/core-1.15.26.tgz", - "integrity": "sha512-tglZGyx8N5PC+x1Nd/JrZxqpqlcZoSuG9gTDKO6AuFToFiVB3uS8HvbKFuO7g3lJzvFf9riAb94xs9HU2UhAHQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.47.tgz", + "integrity": "sha512-FbsO5JcfOjfH38W/rohBRBweJeERsAuIP4f377lmkmxTcq9exjtx4SkRuZY5CdfhR2CBVwDIJegBpJDffwNsOg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.26" + "@swc/types": "^0.1.27" }, "engines": { "node": ">=10" @@ -5524,18 +5555,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.26", - "@swc/core-darwin-x64": "1.15.26", - "@swc/core-linux-arm-gnueabihf": "1.15.26", - "@swc/core-linux-arm64-gnu": "1.15.26", - "@swc/core-linux-arm64-musl": "1.15.26", - "@swc/core-linux-ppc64-gnu": "1.15.26", - "@swc/core-linux-s390x-gnu": "1.15.26", - "@swc/core-linux-x64-gnu": "1.15.26", - "@swc/core-linux-x64-musl": "1.15.26", - "@swc/core-win32-arm64-msvc": "1.15.26", - "@swc/core-win32-ia32-msvc": "1.15.26", - "@swc/core-win32-x64-msvc": "1.15.26" + "@swc/core-darwin-arm64": "1.15.47", + "@swc/core-darwin-x64": "1.15.47", + "@swc/core-linux-arm-gnueabihf": "1.15.47", + "@swc/core-linux-arm64-gnu": "1.15.47", + "@swc/core-linux-arm64-musl": "1.15.47", + "@swc/core-linux-ppc64-gnu": "1.15.47", + "@swc/core-linux-s390x-gnu": "1.15.47", + "@swc/core-linux-x64-gnu": "1.15.47", + "@swc/core-linux-x64-musl": "1.15.47", + "@swc/core-win32-arm64-msvc": "1.15.47", + "@swc/core-win32-ia32-msvc": "1.15.47", + "@swc/core-win32-x64-msvc": "1.15.47" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -5547,9 +5578,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.26.tgz", - "integrity": "sha512-OmcP96CFsNOwa65tamQayRcfqhNlcQ3YCWOq+0Wb+CAM4uB7kOMrXY41Gj4atthxrGhLQ9pg7Vk26iApb88idA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.47.tgz", + "integrity": "sha512-GsoMtan3ojGGMGFbl31mmRu5ctZ56re8grGE8mO/OHJ8O+JRkzod02fe7X6ZQ8JvamA3imkEkx/h3u+vsOgPgA==", "cpu": [ "arm64" ], @@ -5563,9 +5594,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.26.tgz", - "integrity": "sha512-liTTTpKSv89ivIxcZ+iU1cRige9Y7JkOjVnJ2Ystzl+DsWNHqt7wLTTgm/u7gEqmmAS2JKryODLQn3q1UtFNPQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.47.tgz", + "integrity": "sha512-leTi7Rx3KF4zcC637iqWgk9SoV8VXAD8ppQYXsep63px5A/UftOcxLN1pmr8Z1si/YvX90ompP/rHgpYkgwXWg==", "cpu": [ "x64" ], @@ -5579,9 +5610,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.26.tgz", - "integrity": "sha512-Y/g+m3I8CeBof5A3kWWOS6QA2HOIUytF5EeTgfwcAK+GKT/tGe7Xqo5svBtaqflU5od2zzbMTWqkinPXgRWGgA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.47.tgz", + "integrity": "sha512-hBqHuoWKKIsKmDBn9qVeWqj5GWZhtlcczVaqQmNRXsDfq+voR5CxKRfamA367QjJXtceYuliLFfEL8QsskRM2g==", "cpu": [ "arm" ], @@ -5595,12 +5626,15 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.26.tgz", - "integrity": "sha512-19IvwyPfBN/rz9s7qXhOTQmW0922+pjpRUUvIebu+CMM75nX6YuDzHsGx8hSmn5dS89SNaMCh1lgUuXqm++6jg==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.47.tgz", + "integrity": "sha512-TBxvRz+B4K205TWHHZxWVxkC2RFNP/Mz3PNcECBos5PsKwxjg3QSJzdoebr0VCf0Bfh8HOPldKxAP/8XkFe9gA==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5611,12 +5645,15 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.26.tgz", - "integrity": "sha512-iNlbvTIo425rkKzDLLWFJGnFXr3myETUdIDHcjuiPNZE8b0ogmcAuilC4yEJX7FSHGbnlsoJcCT2xf4b3VJmmQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.47.tgz", + "integrity": "sha512-3Yu3Uq/VgytqsPjTMbkPU1ExADytbdWbruJYhA584E9jrpE2Ki+R6VVPoZCeAVk1Cb7QxcRTgblw6bSa6a/R+w==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5627,12 +5664,15 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.26.tgz", - "integrity": "sha512-AuuEOtG+YXKIjIUup4RsxYNklx6XVB3WKWfhxG6hnfPrn7vp89RNOLbbyyprgj6Sk7k9ulwGVTJElEvmBNPSCA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.47.tgz", + "integrity": "sha512-wfdMi5IaOaNtmh2/6geRoxIdNfqylUZFdtzTKS655y1axWfIWyx7As74vv0wVdjeCIZ3WmCI9odDd4rUttXOSQ==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5643,12 +5683,15 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.26.tgz", - "integrity": "sha512-JcMDWQvW1BchUyRg8E0jHiTx7CQYpUr5uDEL1dnPDECrEjBEGG2ynmJ3XX70sWXql0JagqR1t3VpANYFWdUnqA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.47.tgz", + "integrity": "sha512-3hHYBY0yx8Ez7GMRrkhXHQzMdR5IZA6Wq5Ee4svlgwvSECLpnAJ9+0AimEGUFDvuLwE7nV/2+PYe8+Nm4rvNcQ==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5659,12 +5702,15 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.26.tgz", - "integrity": "sha512-FW7V7Mbpq4+PA7BiAq76LJs8MdNuUSylyuRVfQRkhIyeWadFroZ+KOPgjku8Z/fXzngxBRvsk+PGGB0t8mGcjA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.47.tgz", + "integrity": "sha512-TjfhjgP/jGCfFHYC3JQPhJA1HwErbIJ9JfREDc1KNkvY6P0LodCgKVIlQ5deeTbkG7ih3bF5PHJLuLpaZjdRyQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5675,12 +5721,15 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.26.tgz", - "integrity": "sha512-w8erqMHsVcdGwUfJxF6LaiTuPoKnyLOcUbhLcxiXrlLt5MLjtlgcIeUY/NWK/oPoyqkgH+/i8pOJnMTxvl83ZQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.47.tgz", + "integrity": "sha512-CQpS8Ge/avfjZd0UEwG/sds83Uu32deQXcV1Jo3jD0mmvQQqtYAjpsDZXugmheeAwmt+YIuoVtVHro8LMYHqsQ==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5691,9 +5740,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.26.tgz", - "integrity": "sha512-uDCWCNpUiqkbvPmsuPUTn/P7ag9SqNXD2JT/W3dUu7yZ2krzN+nmmoQ2xRX63/J6RYiHI7aT4jo7Z++lsljlPA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.47.tgz", + "integrity": "sha512-0W8IKHsUTYiT7G2RqtOoVWk+89yzZikIiDUb/sCK6BmQDBhN91hQSfyUtW12jhEWLzYgcfmisfsZrmZE+84U1A==", "cpu": [ "arm64" ], @@ -5707,9 +5756,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.26.tgz", - "integrity": "sha512-2k1ax1QmmqLEnpC0uRCw7OXhBfyvdPqERBXupDasjYbChT6ZSO/uha28Bp38cw0viKIG79L27aTDkbkABsMW3w==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.47.tgz", + "integrity": "sha512-ZIp49d2Z4/ka2jO9otOg4hDvTdPmp86kVOgS2M5FCPI7eKKZ1W0boxWn+8XeZrfERtFGW0AlMRm4JhlJa7l3NA==", "cpu": [ "ia32" ], @@ -5723,9 +5772,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.26.tgz", - "integrity": "sha512-aUuYecSEGa4SUSdyCWaI/vk8jdseifYnsF1GZQx2+piL8GIuT/5QrVcFfmes4Iwy7FIVXxtzD063z/FfpZ7K7w==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.47.tgz", + "integrity": "sha512-2h8Iek95vnixkBRCo+H8p09+Q5ll2NgSMFrWTy0iKt7+/t+8/T5mBpiT6c0ZxSS7wcWjwZ9sGZkK70tTSYHdDw==", "cpu": [ "x64" ], @@ -5740,14 +5789,14 @@ }, "node_modules/@swc/counter": { "version": "0.1.3", - "resolved": "https://registry.npmmirror.com/@swc/counter/-/counter-0.1.3.tgz", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "license": "Apache-2.0" }, "node_modules/@swc/html": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html/-/html-1.15.26.tgz", - "integrity": "sha512-Km1TYhlsSchi5HTx7qvs7j7PilxoLNH9KFd6iwC9soXYif5x41x63vXFogDw2sgRUtYI8h6zWqGySQPGz2XdtQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html/-/html-1.15.47.tgz", + "integrity": "sha512-Olu/8OORvYB+43v2/85nHe1EA8WSvWp8D3kctEAput/o4F+EWCK0nr4yGTqvWB7Z+ODz0k+vnxpvHQYzsKP2LA==", "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -5756,24 +5805,24 @@ "node": ">=14" }, "optionalDependencies": { - "@swc/html-darwin-arm64": "1.15.26", - "@swc/html-darwin-x64": "1.15.26", - "@swc/html-linux-arm-gnueabihf": "1.15.26", - "@swc/html-linux-arm64-gnu": "1.15.26", - "@swc/html-linux-arm64-musl": "1.15.26", - "@swc/html-linux-ppc64-gnu": "1.15.26", - "@swc/html-linux-s390x-gnu": "1.15.26", - "@swc/html-linux-x64-gnu": "1.15.26", - "@swc/html-linux-x64-musl": "1.15.26", - "@swc/html-win32-arm64-msvc": "1.15.26", - "@swc/html-win32-ia32-msvc": "1.15.26", - "@swc/html-win32-x64-msvc": "1.15.26" + "@swc/html-darwin-arm64": "1.15.47", + "@swc/html-darwin-x64": "1.15.47", + "@swc/html-linux-arm-gnueabihf": "1.15.47", + "@swc/html-linux-arm64-gnu": "1.15.47", + "@swc/html-linux-arm64-musl": "1.15.47", + "@swc/html-linux-ppc64-gnu": "1.15.47", + "@swc/html-linux-s390x-gnu": "1.15.47", + "@swc/html-linux-x64-gnu": "1.15.47", + "@swc/html-linux-x64-musl": "1.15.47", + "@swc/html-win32-arm64-msvc": "1.15.47", + "@swc/html-win32-ia32-msvc": "1.15.47", + "@swc/html-win32-x64-msvc": "1.15.47" } }, "node_modules/@swc/html-darwin-arm64": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-darwin-arm64/-/html-darwin-arm64-1.15.26.tgz", - "integrity": "sha512-V8xZxwEWXoQUQ+rrOdfP7xQbeBYfiIaxgKpSEGiGU7brR64iIh8PqISH877Xwt5GNVMiFBlcKGw2SJJkiqgffg==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.15.47.tgz", + "integrity": "sha512-Qe3FXNLO/k2WN7xmBP8SzBrtLvlIbyqa8cceUOI0opE4gk+myk5qIk022MiGJOgiF1B21/wMXgA08gYjQ2lP2Q==", "cpu": [ "arm64" ], @@ -5787,9 +5836,9 @@ } }, "node_modules/@swc/html-darwin-x64": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-darwin-x64/-/html-darwin-x64-1.15.26.tgz", - "integrity": "sha512-P3auk5yNL+3yvxYbVWSGeFy1T55vFVE6MOygYSVlR70s1vZo69Ce8seA5MSuOsXlxOBVGKzMRAUpbLFc8V5E4w==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-darwin-x64/-/html-darwin-x64-1.15.47.tgz", + "integrity": "sha512-khCOiWmqlqi8yz+F/ePExhht/+3/cz1XzKV7raPKVynzd6sPxYtFQlNubj305s2soEXgkYYIH0zVVLwiMOVn6Q==", "cpu": [ "x64" ], @@ -5803,9 +5852,9 @@ } }, "node_modules/@swc/html-linux-arm-gnueabihf": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-arm-gnueabihf/-/html-linux-arm-gnueabihf-1.15.26.tgz", - "integrity": "sha512-bggrfNPwOO3NLChUG9GD5bDlS02wyWqGDcpS9T8W1Yr8V4C/a6ejJs+0G7q+BiThvnrqcCv9nmguz6lu+AvIrw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm-gnueabihf/-/html-linux-arm-gnueabihf-1.15.47.tgz", + "integrity": "sha512-7VRqaOEQEFPHSbUcnobmI59WbFK9M35Sse8TFN3hHxOlK9fINJ9yN5FuE9XEeRQAk+PFdR6WrIcF1Xwx1Cpi8A==", "cpu": [ "arm" ], @@ -5819,12 +5868,15 @@ } }, "node_modules/@swc/html-linux-arm64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.15.26.tgz", - "integrity": "sha512-B70y65D0u/08cA3WOfxWY/rfgoWCSw+6MG0nOLF/WIVrc95BZVFVEOvvdtS1VKHWRM/ozOn5EEwpw7nMVeKHbQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.15.47.tgz", + "integrity": "sha512-Ri9MmQqVRiEWuRfpkSTqz0bguvejXQcqQAkoW1Hke6+DColfzETcgNWa/Kw/ii5A/SS9Nb7acKaYdIw1vMBPwA==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5835,12 +5887,15 @@ } }, "node_modules/@swc/html-linux-arm64-musl": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-arm64-musl/-/html-linux-arm64-musl-1.15.26.tgz", - "integrity": "sha512-9klXrd8mrrV05L8BDQkKzE280o/KLUrxktGFrapjp4gLMzQjYkHlfYEbixqSQmG7LuQ2l5HEkNRI5luXDZUisA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-musl/-/html-linux-arm64-musl-1.15.47.tgz", + "integrity": "sha512-NyXRQiVBkeutgCwCxUUaFqZdDmpZONs7Zz3AZGoY35s9GwXF412Nt22fK/e7lO/sM6G/+S3rOom/0xTmUQSK6A==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5851,12 +5906,15 @@ } }, "node_modules/@swc/html-linux-ppc64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-ppc64-gnu/-/html-linux-ppc64-gnu-1.15.26.tgz", - "integrity": "sha512-bNBP5fSFmz+gIA+0IDmbcweALcUoVS8mBC0lVfteZDD4LzyntyryTfM1etv858RMYPmh1eHapmzchzdgo5Hhmw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-ppc64-gnu/-/html-linux-ppc64-gnu-1.15.47.tgz", + "integrity": "sha512-rQ+XLJHFzu0e4M5p1+8kF2FKzI7WO9KPPji87OuicHZVrDCEqg7/jSGu3hys9CjIQIYVfR+tAFuZz3Q1/jOoNA==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5867,12 +5925,15 @@ } }, "node_modules/@swc/html-linux-s390x-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-s390x-gnu/-/html-linux-s390x-gnu-1.15.26.tgz", - "integrity": "sha512-V6sgxVXQdyff4ejhQmRGyfajThzzx5uLjyZr7O+1+Sno5eSyK0zKpn5HAPi5pzydplRK8U+85kS41btC1wf1hQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-s390x-gnu/-/html-linux-s390x-gnu-1.15.47.tgz", + "integrity": "sha512-w6bitHrllrE3lqmf14cT3spmWhZHGts1O08O4adzxztSPto7O5GM3IerqZm/NtsqlxNsfbVHnhaNXxXWe/8Q+Q==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5883,12 +5944,15 @@ } }, "node_modules/@swc/html-linux-x64-gnu": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.15.26.tgz", - "integrity": "sha512-TCvXVgzB5VglIBe+JXPa1EEFXh1L7qpXID41pb3bJ8p7nghE8fT+Rpf9mCjIDVkUiOlfUnw1jUOxMwLWYpgVxg==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.15.47.tgz", + "integrity": "sha512-FTA7E29gcyadd71prg8sJUVacYrIhAXS8L7p48yCfgcNOKquofN1TDOrHVOyYMoxplL3FUwwbN+AZxWO7QfWPQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5899,12 +5963,15 @@ } }, "node_modules/@swc/html-linux-x64-musl": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-linux-x64-musl/-/html-linux-x64-musl-1.15.26.tgz", - "integrity": "sha512-eqZcDYpRguDApsXmKf5aTpzSfgd1dVjXSpMKsk8HyR4Veqp/P4dy/vVHpjbABNtZz8JIHQlR8gkXEh0i4uG6KQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-linux-x64-musl/-/html-linux-x64-musl-1.15.47.tgz", + "integrity": "sha512-g+K1GKUrj+S9o8Qsgii2g7ooMzZ750R4IAqH5CVElIA5FTMAx7KGnu9ga6aEnRwwLYxY3s1k/nN+ls0NEk240g==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5915,9 +5982,9 @@ } }, "node_modules/@swc/html-win32-arm64-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-win32-arm64-msvc/-/html-win32-arm64-msvc-1.15.26.tgz", - "integrity": "sha512-RWwC26Qw0dHD78LP2gZMAb5qgDtR7iQ9GZUw3c3VpfylnLBajQhx+dkJ1yCul/ZBVFhyB0w4xa5iWiRCvgvdng==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-win32-arm64-msvc/-/html-win32-arm64-msvc-1.15.47.tgz", + "integrity": "sha512-5Iw3i00JdTySi+ccc2CM5bxY+8TZivQmcTqUC6mUdAY0/KYBgSe1/L294UJQ4OTLQqNDOYXY9jdb070zZUX7jg==", "cpu": [ "arm64" ], @@ -5931,9 +5998,9 @@ } }, "node_modules/@swc/html-win32-ia32-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-win32-ia32-msvc/-/html-win32-ia32-msvc-1.15.26.tgz", - "integrity": "sha512-rkUy12bgZL3CSvSxUPrhJDQaRGpbILjiAew0goicQT2a7MBiHpnf/YUgT6R2QgX9D/sWZB9UOP+uoqqprvsgBQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-win32-ia32-msvc/-/html-win32-ia32-msvc-1.15.47.tgz", + "integrity": "sha512-YkKd0hbIOeuFC1Sg7uGKkU2JhL+c9vAJlT6P0nRbWCAL71n01IPbdtbW6PmvgVmI7pxRYucXpr4pg839nX5+9Q==", "cpu": [ "ia32" ], @@ -5947,9 +6014,9 @@ } }, "node_modules/@swc/html-win32-x64-msvc": { - "version": "1.15.26", - "resolved": "https://registry.npmmirror.com/@swc/html-win32-x64-msvc/-/html-win32-x64-msvc-1.15.26.tgz", - "integrity": "sha512-1ubd0JSCcqNhDj6kafae0Oy+PMrmziiT5vX1gwkxoWPLPtI8fAbLmZPL532tUXitAZznTPbasjkO+RY52WuMtw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/html-win32-x64-msvc/-/html-win32-x64-msvc-1.15.47.tgz", + "integrity": "sha512-w9Ug+VB9hlHT4hTWIJSB40AdF+qs5w07CYq/+ef1vQRjT5naN6/SKuXmr9/CKLn3T7A9z3lRMPKE4+lmtUkBOQ==", "cpu": [ "x64" ], @@ -5963,9 +6030,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.26", - "resolved": "https://registry.npmmirror.com/@swc/types/-/types-0.1.26.tgz", - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -6033,7 +6100,7 @@ }, "node_modules/@types/debug": { "version": "4.1.13", - "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { @@ -6068,7 +6135,7 @@ }, "node_modules/@types/estree-jsx": { "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { @@ -6099,16 +6166,10 @@ "@types/send": "*" } }, - "node_modules/@types/gtag.js": { - "version": "0.0.20", - "resolved": "https://registry.npmmirror.com/@types/gtag.js/-/gtag.js-0.0.20.tgz", - "integrity": "sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==", - "license": "MIT" - }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -6149,13 +6210,13 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "license": "MIT", "dependencies": { @@ -6164,7 +6225,7 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", "dependencies": { @@ -6179,7 +6240,7 @@ }, "node_modules/@types/mdast": { "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { @@ -6200,7 +6261,7 @@ }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, @@ -6280,7 +6341,7 @@ }, "node_modules/@types/sax": { "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/@types/sax/-/sax-1.2.7.tgz", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "license": "MIT", "dependencies": { @@ -6337,7 +6398,7 @@ }, "node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, @@ -6352,7 +6413,7 @@ }, "node_modules/@types/yargs": { "version": "17.0.35", - "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.35.tgz", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { @@ -6361,14 +6422,14 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "license": "ISC" }, "node_modules/@webassemblyjs/ast": { @@ -6598,7 +6659,7 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", "peerDependencies": { @@ -6618,12 +6679,12 @@ } }, "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", + "integrity": "sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==", "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">= 16.0.0" } }, "node_modules/aggregate-error": { @@ -6685,34 +6746,34 @@ } }, "node_modules/algoliasearch": { - "version": "5.50.2", - "resolved": "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.50.2.tgz", - "integrity": "sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.56.0.tgz", + "integrity": "sha512-PrqppUmhT4ENdas2pH9caE7efUcxy6EcSFhWzosiVuQBzu2tQ5yLTI6jwomT/1cuBnivzGfxiJCqDNN9FRRh+Q==", "license": "MIT", "dependencies": { - "@algolia/abtesting": "1.16.2", - "@algolia/client-abtesting": "5.50.2", - "@algolia/client-analytics": "5.50.2", - "@algolia/client-common": "5.50.2", - "@algolia/client-insights": "5.50.2", - "@algolia/client-personalization": "5.50.2", - "@algolia/client-query-suggestions": "5.50.2", - "@algolia/client-search": "5.50.2", - "@algolia/ingestion": "1.50.2", - "@algolia/monitoring": "1.50.2", - "@algolia/recommend": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/abtesting": "1.22.0", + "@algolia/client-abtesting": "5.56.0", + "@algolia/client-analytics": "5.56.0", + "@algolia/client-common": "5.56.0", + "@algolia/client-insights": "5.56.0", + "@algolia/client-personalization": "5.56.0", + "@algolia/client-query-suggestions": "5.56.0", + "@algolia/client-search": "5.56.0", + "@algolia/ingestion": "1.56.0", + "@algolia/monitoring": "1.56.0", + "@algolia/recommend": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.28.1", - "resolved": "https://registry.npmmirror.com/algoliasearch-helper/-/algoliasearch-helper-3.28.1.tgz", - "integrity": "sha512-6iXpbkkrAI5HFpCWXlNmIDSBuoN/U1XnEvb2yJAoWfqrZ+DrybI7MQ5P5mthFaprmocq+zbi6HxnR28xnZAYBw==", + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.29.3.tgz", + "integrity": "sha512-gVOMbbPVrCO3Xs+B+BLAIGFqIQow6qHMTYCEeBqLQB9m4ZmKUqMwkEumlal2iyyezHEd4Y0FvFTLbCRutCutIQ==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -6750,33 +6811,6 @@ "node": ">=8" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -6813,6 +6847,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", @@ -6828,13 +6871,13 @@ }, "node_modules/arg": { "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, @@ -6846,7 +6889,7 @@ }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "license": "MIT", "engines": { @@ -6869,7 +6912,7 @@ }, "node_modules/astring": { "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/astring/-/astring-1.9.0.tgz", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "license": "MIT", "bin": { @@ -6877,9 +6920,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.5.0", - "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.0.tgz", - "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "funding": [ { "type": "opencollective", @@ -6896,8 +6939,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.2", - "caniuse-lite": "^1.0.30001787", + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -6914,7 +6957,7 @@ }, "node_modules/babel-loader": { "version": "9.2.1", - "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-9.2.1.tgz", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "license": "MIT", "dependencies": { @@ -6931,7 +6974,7 @@ }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "license": "MIT", "dependencies": { @@ -6940,7 +6983,7 @@ }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.17", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { @@ -6954,7 +6997,7 @@ }, "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { @@ -6963,7 +7006,7 @@ }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.13.0", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", "dependencies": { @@ -6976,7 +7019,7 @@ }, "node_modules/babel-plugin-polyfill-regenerator": { "version": "0.6.8", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { @@ -6988,7 +7031,7 @@ }, "node_modules/bail": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", "funding": { @@ -7003,9 +7046,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.19", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", - "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -7022,7 +7065,7 @@ }, "node_modules/big.js": { "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "license": "MIT", "engines": { @@ -7042,9 +7085,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -7128,9 +7171,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7150,9 +7193,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "funding": [ { "type": "opencollective", @@ -7169,10 +7212,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -7250,7 +7293,7 @@ }, "node_modules/call-bind": { "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.9.tgz", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { @@ -7297,7 +7340,7 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", "engines": { @@ -7328,7 +7371,7 @@ }, "node_modules/caniuse-api": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "license": "MIT", "dependencies": { @@ -7339,9 +7382,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001788", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", - "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "funding": [ { "type": "opencollective", @@ -7360,7 +7403,7 @@ }, "node_modules/ccount": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", "funding": { @@ -7386,7 +7429,7 @@ }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "license": "MIT", "engines": { @@ -7395,7 +7438,7 @@ }, "node_modules/character-entities": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { @@ -7405,7 +7448,7 @@ }, "node_modules/character-entities-html4": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", "funding": { @@ -7415,7 +7458,7 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { @@ -7425,7 +7468,7 @@ }, "node_modules/character-reference-invalid": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", "funding": { @@ -7435,7 +7478,7 @@ }, "node_modules/cheerio": { "version": "1.0.0-rc.12", - "resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "license": "MIT", "dependencies": { @@ -7456,7 +7499,7 @@ }, "node_modules/cheerio-select": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "license": "BSD-2-Clause", "dependencies": { @@ -7621,7 +7664,7 @@ }, "node_modules/collapse-white-space": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "license": "MIT", "funding": { @@ -7649,7 +7692,7 @@ }, "node_modules/colord": { "version": "2.9.3", - "resolved": "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "license": "MIT" }, @@ -7670,7 +7713,7 @@ }, "node_modules/comma-separated-tokens": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { @@ -7689,7 +7732,7 @@ }, "node_modules/common-path-prefix": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "license": "ISC" }, @@ -7808,7 +7851,7 @@ }, "node_modules/consola": { "version": "3.4.2", - "resolved": "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "license": "MIT", "engines": { @@ -7835,7 +7878,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, @@ -7856,7 +7899,7 @@ }, "node_modules/copy-text-to-clipboard": { "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", "license": "MIT", "engines": { @@ -7868,7 +7911,7 @@ }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", - "resolved": "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "license": "MIT", "dependencies": { @@ -7892,7 +7935,7 @@ }, "node_modules/copy-webpack-plugin/node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { @@ -7904,7 +7947,7 @@ }, "node_modules/copy-webpack-plugin/node_modules/globby": { "version": "13.2.2", - "resolved": "https://registry.npmmirror.com/globby/-/globby-13.2.2.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "license": "MIT", "dependencies": { @@ -7923,7 +7966,7 @@ }, "node_modules/copy-webpack-plugin/node_modules/slash": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/slash/-/slash-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "license": "MIT", "engines": { @@ -7945,12 +7988,15 @@ } }, "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", "license": "MIT", "dependencies": { - "browserslist": "^4.28.1" + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" }, "funding": { "type": "opencollective", @@ -7965,7 +8011,7 @@ }, "node_modules/cosmiconfig": { "version": "8.3.6", - "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "license": "MIT", "dependencies": { @@ -8032,7 +8078,7 @@ }, "node_modules/css-blank-pseudo": { "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", "funding": [ { @@ -8056,9 +8102,9 @@ } }, "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -8070,7 +8116,7 @@ }, "node_modules/css-declaration-sorter": { "version": "7.4.0", - "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", "license": "ISC", "engines": { @@ -8082,7 +8128,7 @@ }, "node_modules/css-has-pseudo": { "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", "funding": [ { @@ -8109,7 +8155,7 @@ }, "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { @@ -8130,9 +8176,9 @@ } }, "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -8144,7 +8190,7 @@ }, "node_modules/css-loader": { "version": "6.11.0", - "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "license": "MIT", "dependencies": { @@ -8179,7 +8225,7 @@ }, "node_modules/css-minimizer-webpack-plugin": { "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", "license": "MIT", "dependencies": { @@ -8223,7 +8269,7 @@ }, "node_modules/css-prefers-color-scheme": { "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", "funding": [ { @@ -8245,7 +8291,7 @@ }, "node_modules/css-select": { "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { @@ -8261,7 +8307,7 @@ }, "node_modules/css-tree": { "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "license": "MIT", "dependencies": { @@ -8285,9 +8331,9 @@ } }, "node_modules/cssdb": { - "version": "8.8.0", - "resolved": "https://registry.npmmirror.com/cssdb/-/cssdb-8.8.0.tgz", - "integrity": "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.9.0.tgz", + "integrity": "sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==", "funding": [ { "type": "opencollective", @@ -8302,7 +8348,7 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", "bin": { @@ -8314,7 +8360,7 @@ }, "node_modules/cssnano": { "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/cssnano/-/cssnano-6.1.2.tgz", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "license": "MIT", "dependencies": { @@ -8334,7 +8380,7 @@ }, "node_modules/cssnano-preset-advanced": { "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", "license": "MIT", "dependencies": { @@ -8355,7 +8401,7 @@ }, "node_modules/cssnano-preset-default": { "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", "license": "MIT", "dependencies": { @@ -8399,7 +8445,7 @@ }, "node_modules/cssnano-utils": { "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "license": "MIT", "engines": { @@ -8411,7 +8457,7 @@ }, "node_modules/csso": { "version": "5.0.5", - "resolved": "https://registry.npmmirror.com/csso/-/csso-5.0.5.tgz", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", "dependencies": { @@ -8424,7 +8470,7 @@ }, "node_modules/csso/node_modules/css-tree": { "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { @@ -8438,7 +8484,7 @@ }, "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", - "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.28.tgz", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, @@ -8473,7 +8519,7 @@ }, "node_modules/decode-named-character-reference": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { @@ -8522,7 +8568,7 @@ }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "license": "MIT", "engines": { @@ -8568,7 +8614,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { @@ -8594,7 +8640,7 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "dependencies": { @@ -8620,7 +8666,7 @@ }, "node_modules/dequal": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", "engines": { @@ -8639,7 +8685,7 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { @@ -8653,25 +8699,24 @@ "license": "MIT" }, "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-2.1.0.tgz", + "integrity": "sha512-epZuWb/6Q62L+nDHJc/hQAqf8pylsqgk3BpZXVBx1CDnr3nkrVNn73Uu1rXcFzkNcc+hkP3whuOg7JZYaQB65Q==", "license": "MIT", "dependencies": { - "address": "^1.0.1", - "debug": "4" + "address": "^2.0.1" }, "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" + "detect": "dist/commonjs/bin/detect-port.js", + "detect-port": "dist/commonjs/bin/detect-port.js" }, "engines": { - "node": ">= 4.0.0" + "node": ">= 16.0.0" } }, "node_modules/devlop": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { @@ -8684,7 +8729,7 @@ }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "license": "MIT", "dependencies": { @@ -8717,7 +8762,7 @@ }, "node_modules/dom-serializer": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { @@ -8743,7 +8788,7 @@ }, "node_modules/domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", "dependencies": { @@ -8758,7 +8803,7 @@ }, "node_modules/domutils": { "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { @@ -8837,9 +8882,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.339", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.339.tgz", - "integrity": "sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==", + "version": "1.5.402", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz", + "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -8850,13 +8895,13 @@ }, "node_modules/emojilib": { "version": "2.4.0", - "resolved": "https://registry.npmmirror.com/emojilib/-/emojilib-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "license": "MIT", "engines": { @@ -8865,7 +8910,7 @@ }, "node_modules/emoticon": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/emoticon/-/emoticon-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "license": "MIT", "funding": { @@ -8897,7 +8942,7 @@ }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { @@ -8909,7 +8954,7 @@ }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", "dependencies": { @@ -8954,7 +8999,7 @@ }, "node_modules/esast-util-from-estree": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", "license": "MIT", "dependencies": { @@ -8970,7 +9015,7 @@ }, "node_modules/esast-util-from-js": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", "license": "MIT", "dependencies": { @@ -9036,19 +9081,6 @@ "node": ">=8.0.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", @@ -9081,7 +9113,7 @@ }, "node_modules/estree-util-attach-comments": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", "license": "MIT", "dependencies": { @@ -9094,7 +9126,7 @@ }, "node_modules/estree-util-build-jsx": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", "license": "MIT", "dependencies": { @@ -9110,7 +9142,7 @@ }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "license": "MIT", "funding": { @@ -9120,7 +9152,7 @@ }, "node_modules/estree-util-scope": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", "license": "MIT", "dependencies": { @@ -9134,7 +9166,7 @@ }, "node_modules/estree-util-to-js": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", "license": "MIT", "dependencies": { @@ -9149,7 +9181,7 @@ }, "node_modules/estree-util-value-to-estree": { "version": "3.5.0", - "resolved": "https://registry.npmmirror.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", "license": "MIT", "dependencies": { @@ -9161,7 +9193,7 @@ }, "node_modules/estree-util-visit": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", "license": "MIT", "dependencies": { @@ -9175,7 +9207,7 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "license": "MIT", "dependencies": { @@ -9184,7 +9216,7 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { @@ -9352,13 +9384,13 @@ }, "node_modules/extend": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "license": "MIT", "dependencies": { @@ -9376,7 +9408,7 @@ }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { @@ -9392,14 +9424,14 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -9414,7 +9446,7 @@ }, "node_modules/fastq": { "version": "1.20.1", - "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { @@ -9423,7 +9455,7 @@ }, "node_modules/fault": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/fault/-/fault-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", "license": "MIT", "dependencies": { @@ -9448,7 +9480,7 @@ }, "node_modules/feed": { "version": "4.2.2", - "resolved": "https://registry.npmmirror.com/feed/-/feed-4.2.2.tgz", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", "license": "MIT", "dependencies": { @@ -9458,33 +9490,9 @@ "node": ">=0.4.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-loader": { "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/file-loader/-/file-loader-6.2.0.tgz", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", "license": "MIT", "dependencies": { @@ -9503,9 +9511,9 @@ } }, "node_modules/file-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -9520,7 +9528,7 @@ }, "node_modules/file-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { @@ -9529,13 +9537,13 @@ }, "node_modules/file-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { @@ -9598,7 +9606,7 @@ }, "node_modules/find-cache-dir": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", "license": "MIT", "dependencies": { @@ -9614,7 +9622,7 @@ }, "node_modules/find-up": { "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-6.3.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "license": "MIT", "dependencies": { @@ -9668,7 +9676,7 @@ }, "node_modules/format": { "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", "engines": { "node": ">=0.4.x" @@ -9685,7 +9693,7 @@ }, "node_modules/fraction.js": { "version": "5.3.4", - "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "license": "MIT", "engines": { @@ -9744,7 +9752,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", "engines": { @@ -9777,7 +9785,7 @@ }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "license": "ISC" }, @@ -9808,7 +9816,7 @@ }, "node_modules/github-slugger": { "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/github-slugger/-/github-slugger-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", "license": "ISC" }, @@ -9863,7 +9871,7 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "license": "MIT", "dependencies": { @@ -9936,43 +9944,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", @@ -10005,7 +9976,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { @@ -10040,9 +10011,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -10053,7 +10024,7 @@ }, "node_modules/hast-util-from-parse5": { "version": "8.0.3", - "resolved": "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", "license": "MIT", "dependencies": { @@ -10073,7 +10044,7 @@ }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", "dependencies": { @@ -10086,7 +10057,7 @@ }, "node_modules/hast-util-raw": { "version": "9.1.0", - "resolved": "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "license": "MIT", "dependencies": { @@ -10111,7 +10082,7 @@ }, "node_modules/hast-util-to-estree": { "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", "license": "MIT", "dependencies": { @@ -10139,7 +10110,7 @@ }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", - "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", "license": "MIT", "dependencies": { @@ -10166,7 +10137,7 @@ }, "node_modules/hast-util-to-parse5": { "version": "8.0.1", - "resolved": "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", "license": "MIT", "dependencies": { @@ -10185,7 +10156,7 @@ }, "node_modules/hast-util-whitespace": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { @@ -10198,7 +10169,7 @@ }, "node_modules/hastscript": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { @@ -10301,7 +10272,7 @@ }, "node_modules/html-minifier-terser": { "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", "license": "MIT", "dependencies": { @@ -10322,7 +10293,7 @@ }, "node_modules/html-minifier-terser/node_modules/commander": { "version": "10.0.1", - "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "license": "MIT", "engines": { @@ -10343,7 +10314,7 @@ }, "node_modules/html-void-elements": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", "funding": { @@ -10415,7 +10386,7 @@ }, "node_modules/htmlparser2": { "version": "8.0.2", - "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -10485,9 +10456,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", @@ -10565,7 +10536,7 @@ }, "node_modules/icss-utils": { "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "license": "ISC", "engines": { @@ -10577,7 +10548,7 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", "engines": { @@ -10586,7 +10557,7 @@ }, "node_modules/image-size": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/image-size/-/image-size-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", "license": "MIT", "bin": { @@ -10598,7 +10569,7 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { @@ -10641,7 +10612,7 @@ }, "node_modules/infima": { "version": "0.2.0-alpha.45", - "resolved": "https://registry.npmmirror.com/infima/-/infima-0.2.0-alpha.45.tgz", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "license": "MIT", "engines": { @@ -10665,7 +10636,7 @@ }, "node_modules/inline-style-parser": { "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, @@ -10689,7 +10660,7 @@ }, "node_modules/is-alphabetical": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "license": "MIT", "funding": { @@ -10699,7 +10670,7 @@ }, "node_modules/is-alphanumerical": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { @@ -10713,7 +10684,7 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, @@ -10742,12 +10713,12 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -10758,7 +10729,7 @@ }, "node_modules/is-decimal": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", "funding": { @@ -10783,7 +10754,7 @@ }, "node_modules/is-extendable": { "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "license": "MIT", "engines": { @@ -10822,7 +10793,7 @@ }, "node_modules/is-hexadecimal": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", "funding": { @@ -10914,7 +10885,7 @@ }, "node_modules/is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "license": "MIT", "engines": { @@ -10932,7 +10903,7 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { @@ -10956,7 +10927,7 @@ }, "node_modules/is-regexp": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/is-regexp/-/is-regexp-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "license": "MIT", "engines": { @@ -11025,7 +10996,7 @@ }, "node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", "dependencies": { @@ -11042,7 +11013,7 @@ }, "node_modules/jest-worker": { "version": "29.7.0", - "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", "dependencies": { @@ -11057,7 +11028,7 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { @@ -11072,7 +11043,7 @@ }, "node_modules/jiti": { "version": "1.21.7", - "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { @@ -11080,9 +11051,9 @@ } }, "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmmirror.com/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", @@ -11099,9 +11070,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11112,7 +11093,7 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { @@ -11130,7 +11111,7 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, @@ -11142,7 +11123,7 @@ }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", "bin": { @@ -11207,13 +11188,13 @@ } }, "node_modules/launch-editor": { - "version": "2.13.2", - "resolved": "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.13.2.tgz", - "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "license": "MIT", "dependencies": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "node_modules/leven": { @@ -11226,9 +11207,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -11241,23 +11222,23 @@ "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" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -11275,9 +11256,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -11295,9 +11276,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -11315,9 +11296,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -11335,9 +11316,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -11355,12 +11336,15 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11375,12 +11359,15 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11395,12 +11382,15 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11415,12 +11405,15 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11435,9 +11428,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -11455,9 +11448,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -11476,7 +11469,7 @@ }, "node_modules/lilconfig": { "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { @@ -11488,7 +11481,7 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, @@ -11507,7 +11500,7 @@ }, "node_modules/loader-utils": { "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "license": "MIT", "dependencies": { @@ -11521,7 +11514,7 @@ }, "node_modules/locate-path": { "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-7.2.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "license": "MIT", "dependencies": { @@ -11542,25 +11535,25 @@ }, "node_modules/lodash.debounce": { "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", "funding": { @@ -11603,7 +11596,7 @@ }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "license": "ISC", "dependencies": { @@ -11612,7 +11605,7 @@ }, "node_modules/markdown-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", "license": "MIT", "engines": { @@ -11624,7 +11617,7 @@ }, "node_modules/markdown-table": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { @@ -11643,7 +11636,7 @@ }, "node_modules/mdast-util-directive": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { @@ -11664,7 +11657,7 @@ }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { @@ -11680,7 +11673,7 @@ }, "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { @@ -11692,7 +11685,7 @@ }, "node_modules/mdast-util-from-markdown": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { @@ -11716,7 +11709,7 @@ }, "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -11732,7 +11725,7 @@ }, "node_modules/mdast-util-frontmatter": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", "license": "MIT", "dependencies": { @@ -11750,7 +11743,7 @@ }, "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { @@ -11762,7 +11755,7 @@ }, "node_modules/mdast-util-gfm": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { @@ -11781,7 +11774,7 @@ }, "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { @@ -11798,7 +11791,7 @@ }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -11818,7 +11811,7 @@ }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -11834,7 +11827,7 @@ }, "node_modules/mdast-util-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { @@ -11851,7 +11844,7 @@ }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", "dependencies": { @@ -11866,7 +11859,7 @@ }, "node_modules/mdast-util-gfm-table": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", "dependencies": { @@ -11883,7 +11876,7 @@ }, "node_modules/mdast-util-gfm-task-list-item": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "license": "MIT", "dependencies": { @@ -11899,7 +11892,7 @@ }, "node_modules/mdast-util-mdx": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", "license": "MIT", "dependencies": { @@ -11916,7 +11909,7 @@ }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { @@ -11934,7 +11927,7 @@ }, "node_modules/mdast-util-mdx-jsx": { "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", "dependencies": { @@ -11958,7 +11951,7 @@ }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", "dependencies": { @@ -11976,7 +11969,7 @@ }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { @@ -11990,7 +11983,7 @@ }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", - "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { @@ -12011,7 +12004,7 @@ }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { @@ -12032,7 +12025,7 @@ }, "node_modules/mdast-util-to-string": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { @@ -12045,7 +12038,7 @@ }, "node_modules/mdn-data": { "version": "2.0.30", - "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "license": "CC0-1.0" }, @@ -12104,7 +12097,7 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", "engines": { @@ -12122,7 +12115,7 @@ }, "node_modules/micromark": { "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { @@ -12157,7 +12150,7 @@ }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { @@ -12191,7 +12184,7 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12211,7 +12204,7 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12231,7 +12224,7 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12247,7 +12240,7 @@ }, "node_modules/micromark-extension-directive": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "license": "MIT", "dependencies": { @@ -12266,7 +12259,7 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12286,7 +12279,7 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12306,7 +12299,7 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12322,7 +12315,7 @@ }, "node_modules/micromark-extension-frontmatter": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", "license": "MIT", "dependencies": { @@ -12338,7 +12331,7 @@ }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12358,7 +12351,7 @@ }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12374,7 +12367,7 @@ }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "license": "MIT", "dependencies": { @@ -12394,7 +12387,7 @@ }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { @@ -12410,7 +12403,7 @@ }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12430,7 +12423,7 @@ }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12446,7 +12439,7 @@ }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { @@ -12466,7 +12459,7 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12486,7 +12479,7 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12506,7 +12499,7 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12522,7 +12515,7 @@ }, "node_modules/micromark-extension-gfm-strikethrough": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "license": "MIT", "dependencies": { @@ -12540,7 +12533,7 @@ }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12556,7 +12549,7 @@ }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { @@ -12573,7 +12566,7 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12593,7 +12586,7 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12613,7 +12606,7 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12629,7 +12622,7 @@ }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "license": "MIT", "dependencies": { @@ -12642,7 +12635,7 @@ }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { @@ -12659,7 +12652,7 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12679,7 +12672,7 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12699,7 +12692,7 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12715,7 +12708,7 @@ }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", "funding": [ { @@ -12741,7 +12734,7 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12761,7 +12754,7 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12781,7 +12774,7 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12797,7 +12790,7 @@ }, "node_modules/micromark-extension-mdx-jsx": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", "license": "MIT", "dependencies": { @@ -12819,7 +12812,7 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -12839,7 +12832,7 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12859,7 +12852,7 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12875,7 +12868,7 @@ }, "node_modules/micromark-extension-mdx-md": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", "license": "MIT", "dependencies": { @@ -12888,7 +12881,7 @@ }, "node_modules/micromark-extension-mdxjs": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", "license": "MIT", "dependencies": { @@ -12908,7 +12901,7 @@ }, "node_modules/micromark-extension-mdxjs-esm": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", "license": "MIT", "dependencies": { @@ -12929,7 +12922,7 @@ }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -12949,7 +12942,7 @@ }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -12965,7 +12958,7 @@ }, "node_modules/micromark-factory-destination": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { @@ -12986,7 +12979,7 @@ }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13006,7 +12999,7 @@ }, "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13022,7 +13015,7 @@ }, "node_modules/micromark-factory-label": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { @@ -13044,7 +13037,7 @@ }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13064,7 +13057,7 @@ }, "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13080,7 +13073,7 @@ }, "node_modules/micromark-factory-mdx-expression": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", "funding": [ { @@ -13107,7 +13100,7 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -13127,7 +13120,7 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13147,7 +13140,7 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13163,7 +13156,7 @@ }, "node_modules/micromark-factory-space": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", "funding": [ { @@ -13183,7 +13176,7 @@ }, "node_modules/micromark-factory-space/node_modules/micromark-util-types": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { @@ -13199,7 +13192,7 @@ }, "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { @@ -13221,7 +13214,7 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -13241,7 +13234,7 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13261,7 +13254,7 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13277,7 +13270,7 @@ }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { @@ -13299,7 +13292,7 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -13319,7 +13312,7 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13339,7 +13332,7 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13355,7 +13348,7 @@ }, "node_modules/micromark-util-character": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "funding": [ { @@ -13375,7 +13368,7 @@ }, "node_modules/micromark-util-character/node_modules/micromark-util-types": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { @@ -13391,7 +13384,7 @@ }, "node_modules/micromark-util-chunked": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { @@ -13410,7 +13403,7 @@ }, "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13426,7 +13419,7 @@ }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { @@ -13447,7 +13440,7 @@ }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13467,7 +13460,7 @@ }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13483,7 +13476,7 @@ }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { @@ -13503,7 +13496,7 @@ }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { @@ -13522,7 +13515,7 @@ }, "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13538,7 +13531,7 @@ }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { @@ -13560,7 +13553,7 @@ }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13580,7 +13573,7 @@ }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13596,7 +13589,7 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { @@ -13612,7 +13605,7 @@ }, "node_modules/micromark-util-events-to-acorn": { "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", "funding": [ { @@ -13637,7 +13630,7 @@ }, "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13653,7 +13646,7 @@ }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { @@ -13669,7 +13662,7 @@ }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { @@ -13688,7 +13681,7 @@ }, "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13704,7 +13697,7 @@ }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { @@ -13723,7 +13716,7 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { @@ -13744,7 +13737,7 @@ }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13764,7 +13757,7 @@ }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13780,7 +13773,7 @@ }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "funding": [ { @@ -13802,7 +13795,7 @@ }, "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13818,7 +13811,7 @@ }, "node_modules/micromark-util-symbol": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { @@ -13834,7 +13827,7 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { @@ -13850,7 +13843,7 @@ }, "node_modules/micromark/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { @@ -13870,7 +13863,7 @@ }, "node_modules/micromark/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { @@ -13890,7 +13883,7 @@ }, "node_modules/micromark/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { @@ -13973,7 +13966,7 @@ }, "node_modules/mini-css-extract-plugin": { "version": "2.10.2", - "resolved": "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", "license": "MIT", "dependencies": { @@ -14047,9 +14040,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -14091,7 +14084,7 @@ }, "node_modules/node-emoji": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/node-emoji/-/node-emoji-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "license": "MIT", "dependencies": { @@ -14105,10 +14098,13 @@ } }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", - "license": "MIT" + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -14145,7 +14141,7 @@ }, "node_modules/nprogress": { "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", "license": "MIT" }, @@ -14163,7 +14159,7 @@ }, "node_modules/null-loader": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/null-loader/-/null-loader-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", "license": "MIT", "dependencies": { @@ -14182,9 +14178,9 @@ } }, "node_modules/null-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -14199,7 +14195,7 @@ }, "node_modules/null-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { @@ -14208,13 +14204,13 @@ }, "node_modules/null-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/null-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { @@ -14253,7 +14249,7 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", "engines": { @@ -14262,7 +14258,7 @@ }, "node_modules/object.assign": { "version": "4.1.7", - "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "license": "MIT", "dependencies": { @@ -14359,7 +14355,7 @@ }, "node_modules/p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "license": "MIT", "engines": { @@ -14368,7 +14364,7 @@ }, "node_modules/p-limit": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "license": "MIT", "dependencies": { @@ -14383,7 +14379,7 @@ }, "node_modules/p-locate": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "license": "MIT", "dependencies": { @@ -14413,7 +14409,7 @@ }, "node_modules/p-queue": { "version": "6.6.2", - "resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-6.6.2.tgz", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "license": "MIT", "dependencies": { @@ -14446,7 +14442,7 @@ }, "node_modules/p-timeout": { "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "license": "MIT", "dependencies": { @@ -14486,7 +14482,7 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", "dependencies": { @@ -14498,7 +14494,7 @@ }, "node_modules/parse-entities": { "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { @@ -14517,13 +14513,13 @@ }, "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "license": "MIT", "dependencies": { @@ -14541,13 +14537,13 @@ }, "node_modules/parse-numeric-range": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", "license": "ISC" }, "node_modules/parse5": { "version": "7.3.0", - "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { @@ -14559,7 +14555,7 @@ }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "license": "MIT", "dependencies": { @@ -14572,7 +14568,7 @@ }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { @@ -14603,7 +14599,7 @@ }, "node_modules/path-exists": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "license": "MIT", "engines": { @@ -14627,7 +14623,7 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, @@ -14642,7 +14638,7 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "license": "MIT", "engines": { @@ -14669,7 +14665,7 @@ }, "node_modules/pkg-dir": { "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "license": "MIT", "dependencies": { @@ -14700,9 +14696,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -14719,7 +14715,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -14729,7 +14725,7 @@ }, "node_modules/postcss-attribute-case-insensitive": { "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", "funding": [ { @@ -14753,9 +14749,9 @@ } }, "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14767,7 +14763,7 @@ }, "node_modules/postcss-calc": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", "license": "MIT", "dependencies": { @@ -14783,7 +14779,7 @@ }, "node_modules/postcss-clamp": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", "license": "MIT", "dependencies": { @@ -14798,7 +14794,7 @@ }, "node_modules/postcss-color-functional-notation": { "version": "7.0.12", - "resolved": "https://registry.npmmirror.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", "funding": [ { @@ -14827,7 +14823,7 @@ }, "node_modules/postcss-color-hex-alpha": { "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", "funding": [ { @@ -14853,7 +14849,7 @@ }, "node_modules/postcss-color-rebeccapurple": { "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", "funding": [ { @@ -14879,7 +14875,7 @@ }, "node_modules/postcss-colormin": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "license": "MIT", "dependencies": { @@ -14897,7 +14893,7 @@ }, "node_modules/postcss-convert-values": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "license": "MIT", "dependencies": { @@ -14913,7 +14909,7 @@ }, "node_modules/postcss-custom-media": { "version": "11.0.6", - "resolved": "https://registry.npmmirror.com/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", "funding": [ { @@ -14941,7 +14937,7 @@ }, "node_modules/postcss-custom-properties": { "version": "14.0.6", - "resolved": "https://registry.npmmirror.com/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", "funding": [ { @@ -14970,7 +14966,7 @@ }, "node_modules/postcss-custom-selectors": { "version": "8.0.5", - "resolved": "https://registry.npmmirror.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", "funding": [ { @@ -14997,9 +14993,9 @@ } }, "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15011,7 +15007,7 @@ }, "node_modules/postcss-dir-pseudo-class": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", "funding": [ { @@ -15035,9 +15031,9 @@ } }, "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15049,7 +15045,7 @@ }, "node_modules/postcss-discard-comments": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "license": "MIT", "engines": { @@ -15061,7 +15057,7 @@ }, "node_modules/postcss-discard-duplicates": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "license": "MIT", "engines": { @@ -15073,7 +15069,7 @@ }, "node_modules/postcss-discard-empty": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "license": "MIT", "engines": { @@ -15085,7 +15081,7 @@ }, "node_modules/postcss-discard-overridden": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "license": "MIT", "engines": { @@ -15097,7 +15093,7 @@ }, "node_modules/postcss-discard-unused": { "version": "6.0.5", - "resolved": "https://registry.npmmirror.com/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", "license": "MIT", "dependencies": { @@ -15112,7 +15108,7 @@ }, "node_modules/postcss-double-position-gradients": { "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", "funding": [ { @@ -15139,7 +15135,7 @@ }, "node_modules/postcss-focus-visible": { "version": "10.0.1", - "resolved": "https://registry.npmmirror.com/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", "funding": [ { @@ -15163,9 +15159,9 @@ } }, "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15177,7 +15173,7 @@ }, "node_modules/postcss-focus-within": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", "funding": [ { @@ -15201,9 +15197,9 @@ } }, "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15215,7 +15211,7 @@ }, "node_modules/postcss-font-variant": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", "license": "MIT", "peerDependencies": { @@ -15224,7 +15220,7 @@ }, "node_modules/postcss-gap-properties": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", "funding": [ { @@ -15246,7 +15242,7 @@ }, "node_modules/postcss-image-set-function": { "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", "funding": [ { @@ -15272,7 +15268,7 @@ }, "node_modules/postcss-lab-function": { "version": "7.0.12", - "resolved": "https://registry.npmmirror.com/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", "funding": [ { @@ -15301,7 +15297,7 @@ }, "node_modules/postcss-loader": { "version": "7.3.4", - "resolved": "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-7.3.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "license": "MIT", "dependencies": { @@ -15323,7 +15319,7 @@ }, "node_modules/postcss-logical": { "version": "8.1.0", - "resolved": "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-8.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", "funding": [ { @@ -15348,7 +15344,7 @@ }, "node_modules/postcss-merge-idents": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", "license": "MIT", "dependencies": { @@ -15364,7 +15360,7 @@ }, "node_modules/postcss-merge-longhand": { "version": "6.0.5", - "resolved": "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", "license": "MIT", "dependencies": { @@ -15380,7 +15376,7 @@ }, "node_modules/postcss-merge-rules": { "version": "6.1.1", - "resolved": "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", "license": "MIT", "dependencies": { @@ -15398,7 +15394,7 @@ }, "node_modules/postcss-minify-font-values": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", "license": "MIT", "dependencies": { @@ -15413,7 +15409,7 @@ }, "node_modules/postcss-minify-gradients": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "license": "MIT", "dependencies": { @@ -15430,7 +15426,7 @@ }, "node_modules/postcss-minify-params": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "license": "MIT", "dependencies": { @@ -15447,7 +15443,7 @@ }, "node_modules/postcss-minify-selectors": { "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", "license": "MIT", "dependencies": { @@ -15462,7 +15458,7 @@ }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "license": "ISC", "engines": { @@ -15474,7 +15470,7 @@ }, "node_modules/postcss-modules-local-by-default": { "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "license": "MIT", "dependencies": { @@ -15490,9 +15486,9 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15504,7 +15500,7 @@ }, "node_modules/postcss-modules-scope": { "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "license": "ISC", "dependencies": { @@ -15518,9 +15514,9 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15532,7 +15528,7 @@ }, "node_modules/postcss-modules-values": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "license": "ISC", "dependencies": { @@ -15547,7 +15543,7 @@ }, "node_modules/postcss-nesting": { "version": "13.0.2", - "resolved": "https://registry.npmmirror.com/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", "funding": [ { @@ -15574,7 +15570,7 @@ }, "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", "funding": [ { @@ -15596,7 +15592,7 @@ }, "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { @@ -15617,9 +15613,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15631,7 +15627,7 @@ }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "license": "MIT", "engines": { @@ -15643,7 +15639,7 @@ }, "node_modules/postcss-normalize-display-values": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "license": "MIT", "dependencies": { @@ -15658,7 +15654,7 @@ }, "node_modules/postcss-normalize-positions": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "license": "MIT", "dependencies": { @@ -15673,7 +15669,7 @@ }, "node_modules/postcss-normalize-repeat-style": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "license": "MIT", "dependencies": { @@ -15688,7 +15684,7 @@ }, "node_modules/postcss-normalize-string": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "license": "MIT", "dependencies": { @@ -15703,7 +15699,7 @@ }, "node_modules/postcss-normalize-timing-functions": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "license": "MIT", "dependencies": { @@ -15718,7 +15714,7 @@ }, "node_modules/postcss-normalize-unicode": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "license": "MIT", "dependencies": { @@ -15734,7 +15730,7 @@ }, "node_modules/postcss-normalize-url": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "license": "MIT", "dependencies": { @@ -15749,7 +15745,7 @@ }, "node_modules/postcss-normalize-whitespace": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "license": "MIT", "dependencies": { @@ -15764,7 +15760,7 @@ }, "node_modules/postcss-opacity-percentage": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", "funding": [ { @@ -15786,7 +15782,7 @@ }, "node_modules/postcss-ordered-values": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "license": "MIT", "dependencies": { @@ -15802,7 +15798,7 @@ }, "node_modules/postcss-overflow-shorthand": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", "funding": [ { @@ -15827,7 +15823,7 @@ }, "node_modules/postcss-page-break": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", "license": "MIT", "peerDependencies": { @@ -15836,7 +15832,7 @@ }, "node_modules/postcss-place": { "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/postcss-place/-/postcss-place-10.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", "funding": [ { @@ -15861,7 +15857,7 @@ }, "node_modules/postcss-preset-env": { "version": "10.6.1", - "resolved": "https://registry.npmmirror.com/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", "integrity": "sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==", "funding": [ { @@ -15956,7 +15952,7 @@ }, "node_modules/postcss-pseudo-class-any-link": { "version": "10.0.1", - "resolved": "https://registry.npmmirror.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", "funding": [ { @@ -15980,9 +15976,9 @@ } }, "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15994,7 +15990,7 @@ }, "node_modules/postcss-reduce-idents": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", "license": "MIT", "dependencies": { @@ -16009,7 +16005,7 @@ }, "node_modules/postcss-reduce-initial": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "license": "MIT", "dependencies": { @@ -16025,7 +16021,7 @@ }, "node_modules/postcss-reduce-transforms": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "license": "MIT", "dependencies": { @@ -16040,7 +16036,7 @@ }, "node_modules/postcss-replace-overflow-wrap": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", "license": "MIT", "peerDependencies": { @@ -16049,7 +16045,7 @@ }, "node_modules/postcss-selector-not": { "version": "8.0.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", "funding": [ { @@ -16073,9 +16069,9 @@ } }, "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16086,9 +16082,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16100,7 +16096,7 @@ }, "node_modules/postcss-sort-media-queries": { "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", "license": "MIT", "dependencies": { @@ -16115,7 +16111,7 @@ }, "node_modules/postcss-svgo": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "license": "MIT", "dependencies": { @@ -16131,7 +16127,7 @@ }, "node_modules/postcss-unique-selectors": { "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", "license": "MIT", "dependencies": { @@ -16146,13 +16142,13 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, "node_modules/postcss-zindex": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", "license": "MIT", "engines": { @@ -16174,7 +16170,7 @@ }, "node_modules/pretty-time": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/pretty-time/-/pretty-time-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", "license": "MIT", "engines": { @@ -16196,7 +16192,7 @@ }, "node_modules/prismjs": { "version": "1.30.0", - "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { @@ -16234,9 +16230,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -16273,7 +16269,7 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { @@ -16330,7 +16326,7 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { @@ -16362,7 +16358,7 @@ }, "node_modules/randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "license": "MIT", "dependencies": { @@ -16485,7 +16481,7 @@ }, "node_modules/react-json-view-lite": { "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", "license": "MIT", "engines": { @@ -16603,7 +16599,7 @@ }, "node_modules/recma-build-jsx": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", "license": "MIT", "dependencies": { @@ -16618,7 +16614,7 @@ }, "node_modules/recma-jsx": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/recma-jsx/-/recma-jsx-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", "license": "MIT", "dependencies": { @@ -16638,7 +16634,7 @@ }, "node_modules/recma-parse": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/recma-parse/-/recma-parse-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", "license": "MIT", "dependencies": { @@ -16654,7 +16650,7 @@ }, "node_modules/recma-stringify": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/recma-stringify/-/recma-stringify-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", "license": "MIT", "dependencies": { @@ -16676,13 +16672,13 @@ }, "node_modules/regenerate": { "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.2", - "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", "dependencies": { @@ -16694,7 +16690,7 @@ }, "node_modules/regexpu-core": { "version": "6.4.0", - "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.4.0.tgz", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", "dependencies": { @@ -16738,14 +16734,14 @@ }, "node_modules/regjsgen": { "version": "0.8.0", - "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.1", - "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.1.tgz", - "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -16756,7 +16752,7 @@ }, "node_modules/rehype-raw": { "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "license": "MIT", "dependencies": { @@ -16771,7 +16767,7 @@ }, "node_modules/rehype-recma": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/rehype-recma/-/rehype-recma-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", "license": "MIT", "dependencies": { @@ -16795,7 +16791,7 @@ }, "node_modules/remark-directive": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/remark-directive/-/remark-directive-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", "license": "MIT", "dependencies": { @@ -16811,7 +16807,7 @@ }, "node_modules/remark-emoji": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/remark-emoji/-/remark-emoji-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", "license": "MIT", "dependencies": { @@ -16827,7 +16823,7 @@ }, "node_modules/remark-frontmatter": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", "license": "MIT", "dependencies": { @@ -16843,7 +16839,7 @@ }, "node_modules/remark-gfm": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "license": "MIT", "dependencies": { @@ -16861,7 +16857,7 @@ }, "node_modules/remark-mdx": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/remark-mdx/-/remark-mdx-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", "license": "MIT", "dependencies": { @@ -16875,7 +16871,7 @@ }, "node_modules/remark-parse": { "version": "11.0.0", - "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "license": "MIT", "dependencies": { @@ -16891,7 +16887,7 @@ }, "node_modules/remark-rehype": { "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "license": "MIT", "dependencies": { @@ -16908,7 +16904,7 @@ }, "node_modules/remark-stringify": { "version": "11.0.0", - "resolved": "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", "license": "MIT", "dependencies": { @@ -17021,15 +17017,6 @@ "entities": "^2.0.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", @@ -17055,7 +17042,7 @@ }, "node_modules/resolve": { "version": "1.22.12", - "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { @@ -17082,7 +17069,7 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "license": "MIT", "engines": { @@ -17121,7 +17108,7 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { @@ -17131,7 +17118,7 @@ }, "node_modules/rtlcss": { "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/rtlcss/-/rtlcss-4.3.0.tgz", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "license": "MIT", "dependencies": { @@ -17161,7 +17148,7 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { @@ -17209,9 +17196,9 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -17225,7 +17212,7 @@ }, "node_modules/schema-dts": { "version": "1.1.5", - "resolved": "https://registry.npmmirror.com/schema-dts/-/schema-dts-1.1.5.tgz", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", "license": "Apache-2.0" }, @@ -17250,14 +17237,14 @@ }, "node_modules/search-insights": { "version": "2.17.3", - "resolved": "https://registry.npmmirror.com/search-insights/-/search-insights-2.17.3.tgz", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "license": "MIT", "peer": true }, "node_modules/section-matter": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/section-matter/-/section-matter-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", "license": "MIT", "dependencies": { @@ -17364,7 +17351,7 @@ }, "node_modules/serialize-javascript": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "license": "BSD-3-Clause", "dependencies": { @@ -17501,7 +17488,7 @@ }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "license": "MIT", "dependencies": { @@ -17562,9 +17549,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -17673,7 +17660,7 @@ }, "node_modules/sitemap": { "version": "7.1.3", - "resolved": "https://registry.npmmirror.com/sitemap/-/sitemap-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.3.tgz", "integrity": "sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==", "license": "MIT", "dependencies": { @@ -17692,13 +17679,13 @@ }, "node_modules/sitemap/node_modules/@types/node": { "version": "17.0.45", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/skin-tone/-/skin-tone-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "license": "MIT", "dependencies": { @@ -17710,7 +17697,7 @@ }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "license": "MIT", "engines": { @@ -17719,7 +17706,7 @@ }, "node_modules/snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/snake-case/-/snake-case-3.0.4.tgz", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "license": "MIT", "dependencies": { @@ -17740,7 +17727,7 @@ }, "node_modules/sort-css-media-queries": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", "license": "MIT", "engines": { @@ -17749,7 +17736,7 @@ }, "node_modules/source-map": { "version": "0.7.6", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "license": "BSD-3-Clause", "engines": { @@ -17758,7 +17745,7 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "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": { @@ -17786,7 +17773,7 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { @@ -17824,15 +17811,9 @@ "wbuf": "^1.7.3" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/srcset": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/srcset/-/srcset-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", "license": "MIT", "engines": { @@ -17853,7 +17834,7 @@ }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, @@ -17912,7 +17893,7 @@ }, "node_modules/stringify-entities": { "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { @@ -17926,7 +17907,7 @@ }, "node_modules/stringify-object": { "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "license": "BSD-2-Clause", "dependencies": { @@ -17952,7 +17933,7 @@ }, "node_modules/strip-bom-string": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", "license": "MIT", "engines": { @@ -17970,7 +17951,7 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "license": "MIT", "engines": { @@ -17982,7 +17963,7 @@ }, "node_modules/style-to-js": { "version": "1.1.21", - "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { @@ -17991,7 +17972,7 @@ }, "node_modules/style-to-object": { "version": "1.0.14", - "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { @@ -18000,7 +17981,7 @@ }, "node_modules/stylehacks": { "version": "6.1.1", - "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-6.1.1.tgz", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", "license": "MIT", "dependencies": { @@ -18028,7 +18009,7 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { @@ -18040,14 +18021,14 @@ }, "node_modules/svg-parser": { "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "license": "MIT" }, "node_modules/svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.4.tgz", + "integrity": "sha512-GsNRis4e8jxn2Y9ENz/8lbJ93CstG8svtMnuRaHbiF2LTJ5tK0/q3t/URPq9Zc7zVWBJnNnJMIp6bevK7bSmNg==", "license": "MIT", "dependencies": { "commander": "^7.2.0", @@ -18071,7 +18052,7 @@ }, "node_modules/svgo/node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { @@ -18080,7 +18061,7 @@ }, "node_modules/swc-loader": { "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/swc-loader/-/swc-loader-0.2.7.tgz", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.7.tgz", "integrity": "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==", "license": "MIT", "dependencies": { @@ -18281,7 +18262,7 @@ }, "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { @@ -18291,7 +18272,7 @@ }, "node_modules/trough": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { @@ -18400,7 +18381,7 @@ }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", "engines": { @@ -18409,7 +18390,7 @@ }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "license": "MIT", "engines": { @@ -18418,7 +18399,7 @@ }, "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", "dependencies": { @@ -18431,7 +18412,7 @@ }, "node_modules/unicode-match-property-value-ecmascript": { "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", "engines": { @@ -18440,7 +18421,7 @@ }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", "engines": { @@ -18449,7 +18430,7 @@ }, "node_modules/unified": { "version": "11.0.5", - "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { @@ -18483,7 +18464,7 @@ }, "node_modules/unist-util-is": { "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { @@ -18496,7 +18477,7 @@ }, "node_modules/unist-util-position": { "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { @@ -18509,7 +18490,7 @@ }, "node_modules/unist-util-position-from-estree": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", "license": "MIT", "dependencies": { @@ -18522,7 +18503,7 @@ }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { @@ -18535,7 +18516,7 @@ }, "node_modules/unist-util-visit": { "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { @@ -18550,7 +18531,7 @@ }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { @@ -18686,7 +18667,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { @@ -18695,7 +18676,7 @@ }, "node_modules/url-loader": { "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/url-loader/-/url-loader-4.1.1.tgz", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", "license": "MIT", "dependencies": { @@ -18721,9 +18702,9 @@ } }, "node_modules/url-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -18738,7 +18719,7 @@ }, "node_modules/url-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { @@ -18747,13 +18728,13 @@ }, "node_modules/url-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/url-loader/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { @@ -18762,7 +18743,7 @@ }, "node_modules/url-loader/node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { @@ -18774,7 +18755,7 @@ }, "node_modules/url-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { @@ -18804,7 +18785,7 @@ }, "node_modules/utility-types": { "version": "3.11.0", - "resolved": "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", "license": "MIT", "engines": { @@ -18846,7 +18827,7 @@ }, "node_modules/vfile": { "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { @@ -18860,7 +18841,7 @@ }, "node_modules/vfile-location": { "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "license": "MIT", "dependencies": { @@ -18874,7 +18855,7 @@ }, "node_modules/vfile-message": { "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { @@ -18910,7 +18891,7 @@ }, "node_modules/web-namespaces": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "license": "MIT", "funding": { @@ -19064,9 +19045,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz", - "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", + "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", @@ -19087,7 +19068,7 @@ "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", + "launch-editor": "^2.14.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", @@ -19151,9 +19132,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -19204,81 +19185,36 @@ } }, "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", + "integrity": "sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", + "ansis": "^3.2.0", "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" + "std-env": "^3.7.0" }, "engines": { "node": ">=14.21.3" }, "peerDependencies": { + "@rspack/core": "*", "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -19403,9 +19339,9 @@ } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -19467,7 +19403,7 @@ }, "node_modules/xml-js": { "version": "1.6.11", - "resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", "license": "MIT", "dependencies": { @@ -19479,13 +19415,13 @@ }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, "node_modules/yocto-queue": { "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { @@ -19497,7 +19433,7 @@ }, "node_modules/zwitch": { "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { diff --git a/docs-site/package.json b/docs-site/package.json index c8fe804..13d50d6 100644 --- a/docs-site/package.json +++ b/docs-site/package.json @@ -15,9 +15,9 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/faster": "3.10.0", - "@docusaurus/preset-classic": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/faster": "3.10.2", + "@docusaurus/preset-classic": "3.10.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", @@ -25,9 +25,9 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/tsconfig": "3.10.0", - "@docusaurus/types": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/tsconfig": "3.10.2", + "@docusaurus/types": "3.10.2", "@types/react": "^19.0.0", "typescript": "~6.0.2" }, diff --git a/docs-site/src/components/HomepageFeatures/index.tsx b/docs-site/src/components/HomepageFeatures/index.tsx index d0a4202..e4baf12 100644 --- a/docs-site/src/components/HomepageFeatures/index.tsx +++ b/docs-site/src/components/HomepageFeatures/index.tsx @@ -93,7 +93,7 @@ const FEATURES: FeatureItem[] = [ title: Multi-Node Cluster, description: ( - Master-Agent via HTTP long-polling. Agents run tasks locally and upload directly to storage — no reverse connectivity. + Outbound-only Master-Agent polling with proxy, private-CA, and SSH-bastion support. No reverse connectivity is required. ), icon: , diff --git a/server/cmd/backupx/agent.go b/server/cmd/backupx/agent.go index 2cccee9..418a48a 100644 --- a/server/cmd/backupx/agent.go +++ b/server/cmd/backupx/agent.go @@ -24,7 +24,10 @@ func runAgent(args []string) { configPath := fs.String("config", "", "path to agent config YAML (optional)") master := fs.String("master", "", "master URL, e.g. http://master.example.com:8340") token := fs.String("token", "", "agent authentication token") + tokenFile := fs.String("token-file", "", "read the agent authentication token from a file") tempDir := fs.String("temp-dir", "", "local temp directory for backup artifacts") + proxyURL := fs.String("proxy-url", "", "HTTP(S) or SOCKS5 proxy used to reach the master") + caCertFile := fs.String("ca-cert", "", "PEM CA certificate used to verify the master") insecureTLS := fs.Bool("insecure-tls", false, "skip TLS verification (testing only)") if err := fs.Parse(args); err != nil { @@ -36,10 +39,21 @@ func runAgent(args []string) { fmt.Fprintf(os.Stderr, "agent: load config: %v\n", err) os.Exit(2) } - cfg.MergeWithFlags(*master, *token, *tempDir) + cfg.ApplyOverrides(agent.Overrides{ + Master: *master, + Token: *token, + TokenFile: *tokenFile, + TempDir: *tempDir, + ProxyURL: *proxyURL, + CACertFile: *caCertFile, + }) if *insecureTLS { cfg.InsecureSkipTLSVerify = true } + if err := cfg.ResolveToken(); err != nil { + fmt.Fprintf(os.Stderr, "agent: %v\n", err) + os.Exit(2) + } if err := cfg.Validate(); err != nil { fmt.Fprintf(os.Stderr, "agent: %v\n", err) os.Exit(2) diff --git a/server/config.example.yaml b/server/config.example.yaml index 351b378..82da115 100644 --- a/server/config.example.yaml +++ b/server/config.example.yaml @@ -4,6 +4,9 @@ server: port: 8340 mode: "release" # debug | release external_url: "" # 可选:Master 对 Agent 可达的 URL,例如 https://backup.example.com + trusted_proxies: # 仅这些代理可提供 X-Forwarded-For;跨容器代理需加入其网段 + - "127.0.0.1" + - "::1" web_root: "" # 前端静态目录;留空自动探测(./web、/opt/backupx/web 等)。 # 命中后后端直接托管 Web 控制台,无需额外 nginx 反向代理。 diff --git a/server/internal/agent/agent.go b/server/internal/agent/agent.go index 93bedf3..cf5d4d7 100644 --- a/server/internal/agent/agent.go +++ b/server/internal/agent/agent.go @@ -28,10 +28,16 @@ type Agent struct { // New 构造 Agent。 func New(cfg *Config, version string) (*Agent, error) { + if err := cfg.ResolveToken(); err != nil { + return nil, err + } if err := cfg.Validate(); err != nil { return nil, err } client := NewMasterClient(cfg.Master, cfg.Token, cfg.InsecureSkipTLSVerify) + if err := client.ConfigureTransport(cfg.ProxyURL, cfg.CACertFile); err != nil { + return nil, fmt.Errorf("configure master connection: %w", err) + } executor := NewExecutor(client, cfg.TempDir) return &Agent{ cfg: cfg, @@ -93,7 +99,6 @@ func (a *Agent) heartbeatLoop(ctx context.Context, interval time.Duration) { func (a *Agent) heartbeatOnce(ctx context.Context) error { hostname, _ := os.Hostname() req := HeartbeatRequest{ - Token: a.cfg.Token, Hostname: hostname, IPAddress: detectLocalIP(), AgentVersion: a.version, diff --git a/server/internal/agent/client.go b/server/internal/agent/client.go index 64ba166..43f2b50 100644 --- a/server/internal/agent/client.go +++ b/server/internal/agent/client.go @@ -4,11 +4,14 @@ import ( "bytes" "context" "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" "io" "net/http" + "net/url" + "os" "strings" "time" ) @@ -22,23 +25,64 @@ type MasterClient struct { // NewMasterClient 构造 Master 客户端。 func NewMasterClient(baseURL, token string, insecureTLS bool) *MasterClient { - transport := &http.Transport{} - if insecureTLS { - transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + transport := http.DefaultTransport.(*http.Transport).Clone() + tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} + if transport.TLSClientConfig != nil { + tlsConfig = transport.TLSClientConfig.Clone() + tlsConfig.MinVersion = tls.VersionTLS12 } + // 仅用于用户显式开启的测试模式。生产环境应配置受信 CA。 + tlsConfig.InsecureSkipVerify = insecureTLS // #nosec G402 + transport.TLSClientConfig = tlsConfig return &MasterClient{ baseURL: strings.TrimRight(baseURL, "/"), token: token, httpClient: &http.Client{ Timeout: 120 * time.Second, Transport: transport, + // Agent Token 是自定义认证头。禁止自动重定向,避免代理或错误 + // 配置把它转发到另一个主机;Master URL 必须直接指向 API。 + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, }, } } +// ConfigureTransport 应用显式代理和私有 CA。默认 Transport 已保留 +// ProxyFromEnvironment,因此 ProxyURL 留空时 HTTP_PROXY/HTTPS_PROXY/NO_PROXY 生效。 +func (c *MasterClient) ConfigureTransport(proxyURL, caCertFile string) error { + transport, ok := c.httpClient.Transport.(*http.Transport) + if !ok { + return errors.New("agent http transport has unexpected type") + } + if strings.TrimSpace(proxyURL) != "" { + parsedProxy, err := url.Parse(strings.TrimSpace(proxyURL)) + if err != nil { + return fmt.Errorf("parse proxy URL: %w", err) + } + transport.Proxy = http.ProxyURL(parsedProxy) + } + if strings.TrimSpace(caCertFile) == "" { + return nil + } + pemData, err := os.ReadFile(strings.TrimSpace(caCertFile)) + if err != nil { + return fmt.Errorf("read CA certificate: %w", err) + } + roots, err := x509.SystemCertPool() + if err != nil || roots == nil { + roots = x509.NewCertPool() + } + if !roots.AppendCertsFromPEM(pemData) { + return errors.New("CA certificate file does not contain a valid PEM certificate") + } + transport.TLSClientConfig.RootCAs = roots + return nil +} + // HeartbeatRequest Agent 上报心跳的请求 type HeartbeatRequest struct { - Token string `json:"token"` Hostname string `json:"hostname,omitempty"` IPAddress string `json:"ipAddress,omitempty"` AgentVersion string `json:"agentVersion,omitempty"` diff --git a/server/internal/agent/client_test.go b/server/internal/agent/client_test.go new file mode 100644 index 0000000..bad09ca --- /dev/null +++ b/server/internal/agent/client_test.go @@ -0,0 +1,93 @@ +package agent + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestMasterClientKeepsEnvironmentProxySupport(t *testing.T) { + client := NewMasterClient("https://master.example.com", "token", false) + transport := client.httpClient.Transport.(*http.Transport) + if transport.Proxy == nil { + t.Fatal("default transport proxy function must be preserved") + } +} + +func TestMasterClientConfiguresExplicitProxy(t *testing.T) { + client := NewMasterClient("https://master.example.com", "token", false) + if err := client.ConfigureTransport("socks5h://127.0.0.1:1080", ""); err != nil { + t.Fatal(err) + } + transport := client.httpClient.Transport.(*http.Transport) + requestURL, _ := url.Parse("https://master.example.com") + proxyURL, err := transport.Proxy(&http.Request{URL: requestURL}) + if err != nil { + t.Fatal(err) + } + if proxyURL == nil || proxyURL.String() != "socks5h://127.0.0.1:1080" { + t.Fatalf("proxy URL = %v", proxyURL) + } +} + +func TestMasterClientRejectsInvalidCACertificate(t *testing.T) { + path := filepath.Join(t.TempDir(), "invalid.pem") + if err := os.WriteFile(path, []byte("not a certificate"), 0o600); err != nil { + t.Fatal(err) + } + client := NewMasterClient("https://master.example.com", "token", false) + if err := client.ConfigureTransport("", path); err == nil { + t.Fatal("expected invalid CA certificate error") + } +} + +func TestMasterClientDoesNotForwardTokenThroughRedirects(t *testing.T) { + receivedToken := "" + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedToken = r.Header.Get("X-Agent-Token") + w.WriteHeader(http.StatusOK) + })) + defer target.Close() + redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect) + })) + defer redirector.Close() + + client := NewMasterClient(redirector.URL, "secret-agent-token", false) + if _, err := client.Heartbeat(context.Background(), HeartbeatRequest{}); err == nil { + t.Fatal("redirect response should not be accepted as a Master API response") + } + if receivedToken != "" { + t.Fatalf("Agent token leaked through redirect: %q", receivedToken) + } +} + +func TestHeartbeatSendsTokenOnlyInAuthenticationHeader(t *testing.T) { + requestBody := "" + receivedHeader := "" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + requestBody = string(body) + receivedHeader = r.Header.Get("X-Agent-Token") + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"data":{"status":"ok","nodeId":1,"name":"node"}}`)) + })) + defer server.Close() + + client := NewMasterClient(server.URL, "secret-agent-token", false) + if _, err := client.Heartbeat(context.Background(), HeartbeatRequest{Hostname: "node"}); err != nil { + t.Fatal(err) + } + if receivedHeader != "secret-agent-token" { + t.Fatalf("authentication header = %q", receivedHeader) + } + if strings.Contains(requestBody, "secret-agent-token") || strings.Contains(requestBody, `"token"`) { + t.Fatalf("heartbeat body exposed the Agent token: %s", requestBody) + } +} diff --git a/server/internal/agent/config.go b/server/internal/agent/config.go index 03b0a34..df63d4b 100644 --- a/server/internal/agent/config.go +++ b/server/internal/agent/config.go @@ -10,8 +10,10 @@ package agent import ( "errors" "fmt" + "net/url" "os" "strings" + "time" "gopkg.in/yaml.v3" ) @@ -22,16 +24,34 @@ type Config struct { Master string `yaml:"master"` // Token 节点认证令牌(在 Master 创建节点时生成) Token string `yaml:"token"` + // TokenFile 从文件读取节点认证令牌;适合 systemd 凭据和容器 secret。 + // Token 与 TokenFile 同时设置时优先使用 Token。 + TokenFile string `yaml:"tokenFile"` // HeartbeatInterval 心跳间隔,默认 15s HeartbeatInterval string `yaml:"heartbeatInterval"` // PollInterval 命令轮询间隔,默认 5s PollInterval string `yaml:"pollInterval"` // TempDir 备份临时目录,默认 /var/lib/backupx-agent/tmp TempDir string `yaml:"tempDir"` + // ProxyURL Agent 访问 Master 使用的显式代理。留空时遵循 + // HTTP_PROXY、HTTPS_PROXY 与 NO_PROXY;支持 http(s) 和 socks5(h)。 + ProxyURL string `yaml:"proxyUrl"` + // CACertFile 私有 CA 的 PEM 文件路径,用于安全连接内网 HTTPS Master。 + CACertFile string `yaml:"caCertFile"` // InsecureSkipTLSVerify 测试环境允许跳过 TLS 证书校验 InsecureSkipTLSVerify bool `yaml:"insecureSkipTlsVerify"` } +// Overrides 表示命令行显式提供的 Agent 配置覆盖项。 +type Overrides struct { + Master string + Token string + TokenFile string + TempDir string + ProxyURL string + CACertFile string +} + // LoadConfigFile 从 YAML 文件加载 Agent 配置。 func LoadConfigFile(path string) (*Config, error) { data, err := os.ReadFile(path) @@ -50,42 +70,109 @@ func LoadConfigFile(path string) (*Config, error) { // 支持的环境变量: // - BACKUPX_AGENT_MASTER Master URL // - BACKUPX_AGENT_TOKEN 节点认证令牌 +// - BACKUPX_AGENT_TOKEN_FILE 节点认证令牌文件 // - BACKUPX_AGENT_HEARTBEAT 心跳间隔(如 15s) // - BACKUPX_AGENT_POLL 命令轮询间隔(如 5s) // - BACKUPX_AGENT_TEMP_DIR 临时目录 +// - BACKUPX_AGENT_PROXY_URL 显式 HTTP(S)/SOCKS5 代理 +// - BACKUPX_AGENT_CA_CERT_FILE 私有 CA PEM 文件 // - BACKUPX_AGENT_INSECURE_TLS true / 1 跳过 TLS 校验 func LoadConfigFromEnv() (*Config, error) { cfg := &Config{ Master: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_MASTER")), Token: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_TOKEN")), + TokenFile: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_TOKEN_FILE")), HeartbeatInterval: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_HEARTBEAT")), PollInterval: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_POLL")), TempDir: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_TEMP_DIR")), + ProxyURL: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_PROXY_URL")), + CACertFile: strings.TrimSpace(os.Getenv("BACKUPX_AGENT_CA_CERT_FILE")), InsecureSkipTLSVerify: strings.EqualFold(os.Getenv("BACKUPX_AGENT_INSECURE_TLS"), "true") || os.Getenv("BACKUPX_AGENT_INSECURE_TLS") == "1", } return applyConfigDefaults(cfg) } -// MergeWithFlags 把命令行覆盖值合并入配置(非空覆盖)。 -func (c *Config) MergeWithFlags(master, token, tempDir string) { - if strings.TrimSpace(master) != "" { - c.Master = master +// ApplyOverrides 把命令行覆盖值合并入配置(非空覆盖)。 +func (c *Config) ApplyOverrides(overrides Overrides) { + if strings.TrimSpace(overrides.Master) != "" { + c.Master = strings.TrimSpace(overrides.Master) } - if strings.TrimSpace(token) != "" { - c.Token = token + tokenProvided := strings.TrimSpace(overrides.Token) != "" + if strings.TrimSpace(overrides.TokenFile) != "" { + c.TokenFile = strings.TrimSpace(overrides.TokenFile) + if !tokenProvided { + // An explicit --token-file must override a token inherited from YAML. + c.Token = "" + } } - if strings.TrimSpace(tempDir) != "" { - c.TempDir = tempDir + if tokenProvided { + c.Token = strings.TrimSpace(overrides.Token) } + if strings.TrimSpace(overrides.TempDir) != "" { + c.TempDir = strings.TrimSpace(overrides.TempDir) + } + if strings.TrimSpace(overrides.ProxyURL) != "" { + c.ProxyURL = strings.TrimSpace(overrides.ProxyURL) + } + if strings.TrimSpace(overrides.CACertFile) != "" { + c.CACertFile = strings.TrimSpace(overrides.CACertFile) + } +} + +// ResolveToken 在所有配置源合并完成后读取 token 文件。 +func (c *Config) ResolveToken() error { + if strings.TrimSpace(c.Token) != "" || strings.TrimSpace(c.TokenFile) == "" { + c.Token = strings.TrimSpace(c.Token) + return nil + } + data, err := os.ReadFile(strings.TrimSpace(c.TokenFile)) + if err != nil { + return fmt.Errorf("read agent token file: %w", err) + } + c.Token = strings.TrimSpace(string(data)) + if c.Token == "" { + return errors.New("agent token file is empty") + } + return nil } // Validate 校验必填字段。 func (c *Config) Validate() error { + masterURL, err := url.Parse(strings.TrimSpace(c.Master)) if strings.TrimSpace(c.Master) == "" { return errors.New("master url is required (set via --master, BACKUPX_AGENT_MASTER or config file)") } + if err != nil || (masterURL.Scheme != "http" && masterURL.Scheme != "https") || masterURL.Host == "" || masterURL.User != nil || masterURL.RawQuery != "" || masterURL.Fragment != "" { + return errors.New("master url must be an absolute http(s) URL without credentials, query or fragment") + } if strings.TrimSpace(c.Token) == "" { - return errors.New("token is required (set via --token, BACKUPX_AGENT_TOKEN or config file)") + return errors.New("token is required (set via --token, --token-file, environment or config file)") + } + if c.ProxyURL != "" { + proxyURL, proxyErr := url.Parse(c.ProxyURL) + if proxyErr != nil || proxyURL.Host == "" { + return errors.New("proxy url must be an absolute URL") + } + switch proxyURL.Scheme { + case "http", "https", "socks5", "socks5h": + default: + return errors.New("proxy url scheme must be http, https, socks5 or socks5h") + } + if proxyURL.RawQuery != "" || proxyURL.Fragment != "" || (proxyURL.Path != "" && proxyURL.Path != "/") { + return errors.New("proxy url must not contain a path, query or fragment") + } + } + if c.CACertFile != "" && c.InsecureSkipTLSVerify { + return errors.New("ca cert file and insecure TLS cannot be enabled together") + } + for name, value := range map[string]string{ + "heartbeat interval": c.HeartbeatInterval, + "poll interval": c.PollInterval, + } { + duration, durationErr := time.ParseDuration(value) + if durationErr != nil || duration <= 0 { + return fmt.Errorf("%s must be a positive duration", name) + } } return nil } @@ -101,5 +188,9 @@ func applyConfigDefaults(cfg *Config) (*Config, error) { cfg.TempDir = "/var/lib/backupx-agent/tmp" } cfg.Master = strings.TrimRight(strings.TrimSpace(cfg.Master), "/") + cfg.Token = strings.TrimSpace(cfg.Token) + cfg.TokenFile = strings.TrimSpace(cfg.TokenFile) + cfg.ProxyURL = strings.TrimSpace(cfg.ProxyURL) + cfg.CACertFile = strings.TrimSpace(cfg.CACertFile) return cfg, nil } diff --git a/server/internal/agent/config_test.go b/server/internal/agent/config_test.go index f1e5292..619235f 100644 --- a/server/internal/agent/config_test.go +++ b/server/internal/agent/config_test.go @@ -11,9 +11,12 @@ func TestLoadConfigFile(t *testing.T) { path := filepath.Join(dir, "agent.yaml") content := `master: http://master.example.com:8340/ token: abc123 +tokenFile: /run/secrets/backupx_agent_token heartbeatInterval: 20s pollInterval: 3s tempDir: /var/backupx-agent +proxyUrl: socks5h://127.0.0.1:1080 +caCertFile: /etc/backupx-agent/ca.pem insecureSkipTlsVerify: true ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { @@ -35,6 +38,9 @@ insecureSkipTlsVerify: true if !cfg.InsecureSkipTLSVerify { t.Errorf("insecure should be true") } + if cfg.ProxyURL != "socks5h://127.0.0.1:1080" || cfg.CACertFile != "/etc/backupx-agent/ca.pem" { + t.Errorf("connection options not loaded: %+v", cfg) + } } func TestLoadConfigDefaults(t *testing.T) { @@ -64,8 +70,15 @@ func TestConfigValidate(t *testing.T) { {"valid", Config{Master: "http://m", Token: "t"}, false}, {"missing master", Config{Token: "t"}, true}, {"missing token", Config{Master: "http://m"}, true}, + {"invalid master scheme", Config{Master: "ssh://m", Token: "t"}, true}, + {"master credentials rejected", Config{Master: "https://user:pass@m", Token: "t"}, true}, + {"valid socks proxy", Config{Master: "https://m", Token: "t", ProxyURL: "socks5h://127.0.0.1:1080"}, false}, + {"invalid proxy", Config{Master: "https://m", Token: "t", ProxyURL: "ftp://proxy"}, true}, + {"proxy path rejected", Config{Master: "https://m", Token: "t", ProxyURL: "http://proxy/connect"}, true}, + {"invalid heartbeat", Config{Master: "https://m", Token: "t", HeartbeatInterval: "never"}, true}, } for _, c := range cases { + _, _ = applyConfigDefaults(&c.cfg) err := c.cfg.Validate() if (err != nil) != c.wantErr { t.Errorf("%s: err=%v wantErr=%v", c.name, err, c.wantErr) @@ -75,7 +88,7 @@ func TestConfigValidate(t *testing.T) { func TestMergeWithFlags(t *testing.T) { cfg := &Config{Master: "http://old", Token: "old"} - cfg.MergeWithFlags("http://new", "", "/tmp/x") + cfg.ApplyOverrides(Overrides{Master: "http://new", TempDir: "/tmp/x", ProxyURL: "http://proxy:3128"}) if cfg.Master != "http://new" { t.Errorf("master not overridden: %q", cfg.Master) } @@ -85,17 +98,50 @@ func TestMergeWithFlags(t *testing.T) { if cfg.TempDir != "/tmp/x" { t.Errorf("tempDir: %q", cfg.TempDir) } + if cfg.ProxyURL != "http://proxy:3128" { + t.Errorf("proxyUrl: %q", cfg.ProxyURL) + } +} + +func TestTokenFileOverrideReplacesConfiguredToken(t *testing.T) { + path := filepath.Join(t.TempDir(), "agent.token") + if err := os.WriteFile(path, []byte("file-token\n"), 0o600); err != nil { + t.Fatal(err) + } + cfg := &Config{Token: "yaml-token", TokenFile: "/old/token"} + cfg.ApplyOverrides(Overrides{TokenFile: " " + path + " "}) + if err := cfg.ResolveToken(); err != nil { + t.Fatal(err) + } + if cfg.Token != "file-token" || cfg.TokenFile != path { + t.Fatalf("token file override was not applied: %+v", cfg) + } } func TestLoadConfigFromEnv(t *testing.T) { t.Setenv("BACKUPX_AGENT_MASTER", "http://env-master") t.Setenv("BACKUPX_AGENT_TOKEN", "env-token") + t.Setenv("BACKUPX_AGENT_PROXY_URL", "http://env-proxy:8080") t.Setenv("BACKUPX_AGENT_INSECURE_TLS", "true") cfg, err := LoadConfigFromEnv() if err != nil { t.Fatal(err) } - if cfg.Master != "http://env-master" || cfg.Token != "env-token" || !cfg.InsecureSkipTLSVerify { + if cfg.Master != "http://env-master" || cfg.Token != "env-token" || cfg.ProxyURL != "http://env-proxy:8080" || !cfg.InsecureSkipTLSVerify { t.Errorf("env not picked up: %+v", cfg) } } + +func TestResolveTokenFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "agent.token") + if err := os.WriteFile(path, []byte(" file-token\n"), 0o600); err != nil { + t.Fatal(err) + } + cfg := &Config{TokenFile: path} + if err := cfg.ResolveToken(); err != nil { + t.Fatal(err) + } + if cfg.Token != "file-token" { + t.Fatalf("token = %q", cfg.Token) + } +} diff --git a/server/internal/backup/file_runner.go b/server/internal/backup/file_runner.go index b86e20f..726060d 100644 --- a/server/internal/backup/file_runner.go +++ b/server/internal/backup/file_runner.go @@ -84,7 +84,7 @@ func (r *FileRunner) Run(_ context.Context, task TaskSpec, writer LogWriter) (*R walkErr := filepath.Walk(sourcePath, func(currentPath string, currentInfo os.FileInfo, walkErr error) error { if walkErr != nil { - writer.WriteLine(fmt.Sprintf("⚠ 无法访问 %s: %v", currentPath, walkErr)) + writer.WriteLine(fmt.Sprintf("[WARN] 无法访问 %s: %v", currentPath, walkErr)) return nil } relPath, err := filepath.Rel(baseParent, currentPath) @@ -115,7 +115,7 @@ func (r *FileRunner) Run(_ context.Context, task TaskSpec, writer LogWriter) (*R if currentInfo.IsDir() { dirCount++ - writer.WriteLine(fmt.Sprintf("📁 进入目录 %s", archiveName)) + writer.WriteLine(fmt.Sprintf("[DIR] 进入目录 %s", archiveName)) } header, err := tar.FileInfoHeader(currentInfo, "") diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 05fcb85..33871fe 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -2,6 +2,8 @@ package config import ( "fmt" + "net" + "net/url" "strings" "time" @@ -21,6 +23,9 @@ type ServerConfig struct { Port int `mapstructure:"port"` Mode string `mapstructure:"mode"` ExternalURL string `mapstructure:"external_url"` + // TrustedProxies 限定可提供 X-Forwarded-For 等头部的反向代理地址。 + // 默认仅信任本机代理;空列表表示不信任任何代理头。 + TrustedProxies []string `mapstructure:"trusted_proxies"` // WebRoot 指向前端构建产物目录。留空时后端会按部署惯例自动探测 // (./web、./web/dist、/opt/backupx/web 等)。探测命中后后端直接托管 // 前端 SPA,无需额外的 nginx 反向代理即可访问 Web 控制台。 @@ -91,6 +96,25 @@ func Load(configPath string) (Config, error) { if cfg.Server.Mode == "" { cfg.Server.Mode = "release" } + cfg.Server.ExternalURL = strings.TrimRight(strings.TrimSpace(cfg.Server.ExternalURL), "/") + if cfg.Server.ExternalURL != "" { + externalURL, parseErr := url.Parse(cfg.Server.ExternalURL) + if parseErr != nil || (externalURL.Scheme != "http" && externalURL.Scheme != "https") || externalURL.Host == "" || externalURL.User != nil || externalURL.RawQuery != "" || externalURL.Fragment != "" { + return Config{}, fmt.Errorf("server.external_url must be an absolute http(s) URL without credentials, query or fragment") + } + } + if len(cfg.Server.TrustedProxies) == 1 && strings.Contains(cfg.Server.TrustedProxies[0], ",") { + cfg.Server.TrustedProxies = strings.Split(cfg.Server.TrustedProxies[0], ",") + } + for index := range cfg.Server.TrustedProxies { + proxy := strings.TrimSpace(cfg.Server.TrustedProxies[index]) + cfg.Server.TrustedProxies[index] = proxy + if net.ParseIP(proxy) == nil { + if _, _, parseErr := net.ParseCIDR(proxy); parseErr != nil { + return Config{}, fmt.Errorf("server.trusted_proxies contains invalid IP or CIDR %q", proxy) + } + } + } if cfg.Database.Path == "" { cfg.Database.Path = "./data/backupx.db" } @@ -142,6 +166,7 @@ func applyDefaults(v *viper.Viper) { v.SetDefault("server.port", 8340) v.SetDefault("server.mode", "release") v.SetDefault("server.external_url", "") + v.SetDefault("server.trusted_proxies", []string{"127.0.0.1", "::1"}) v.SetDefault("server.web_root", "") v.SetDefault("database.path", "./data/backupx.db") v.SetDefault("security.jwt_expire", "24h") diff --git a/server/internal/config/config_test.go b/server/internal/config/config_test.go index 7fe7d7e..3e59894 100644 --- a/server/internal/config/config_test.go +++ b/server/internal/config/config_test.go @@ -21,6 +21,25 @@ func TestLoadUsesDefaultsWithoutConfigFile(t *testing.T) { if cfg.Database.Path != "./data/backupx.db" { t.Fatalf("expected default database path, got %s", cfg.Database.Path) } + if len(cfg.Server.TrustedProxies) != 2 { + t.Fatalf("expected loopback trusted proxies, got %#v", cfg.Server.TrustedProxies) + } +} + +func TestLoadRejectsInvalidExternalURLAndTrustedProxy(t *testing.T) { + tests := []string{ + "server:\n external_url: \"ssh://master.example.com\"\n", + "server:\n trusted_proxies: [\"not-an-ip\"]\n", + } + for _, content := range tests { + configPath := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil { + t.Fatal(err) + } + if _, err := Load(configPath); err == nil { + t.Fatalf("expected invalid configuration to fail: %s", content) + } + } } func TestLoadReadsServerExternalURLFromFile(t *testing.T) { @@ -52,3 +71,28 @@ func TestLoadReadsServerExternalURLFromEnv(t *testing.T) { t.Fatalf("expected external URL from env, got %q", cfg.Server.ExternalURL) } } + +func TestLoadReadsTrustedProxiesFromEnv(t *testing.T) { + t.Setenv("BACKUPX_SERVER_TRUSTED_PROXIES", "127.0.0.1,172.18.0.0/16") + cfg, err := Load("") + if err != nil { + t.Fatal(err) + } + if len(cfg.Server.TrustedProxies) != 2 || cfg.Server.TrustedProxies[1] != "172.18.0.0/16" { + t.Fatalf("trusted proxies = %#v", cfg.Server.TrustedProxies) + } +} + +func TestLoadAllowsTrustedProxiesToBeDisabled(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.yaml") + if err := os.WriteFile(configPath, []byte("server:\n trusted_proxies: []\n"), 0o600); err != nil { + t.Fatal(err) + } + cfg, err := Load(configPath) + if err != nil { + t.Fatal(err) + } + if len(cfg.Server.TrustedProxies) != 0 { + t.Fatalf("trusted proxies should be disabled, got %#v", cfg.Server.TrustedProxies) + } +} diff --git a/server/internal/database/database.go b/server/internal/database/database.go index 74a0543..91be2e1 100644 --- a/server/internal/database/database.go +++ b/server/internal/database/database.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "backupx/server/internal/config" "backupx/server/internal/model" @@ -18,7 +19,14 @@ func Open(cfg config.DatabaseConfig, logger *zap.Logger) (*gorm.DB, error) { return nil, fmt.Errorf("create database dir: %w", err) } - db, err := gorm.Open(sqlite.Open(cfg.Path), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Silent)}) + separator := "?" + if strings.Contains(cfg.Path, "?") { + separator = "&" + } + // busy_timeout 减少 Agent 轮询、心跳和任务写入同时发生时的瞬时锁错误。 + // 维持默认回滚日志模式,保证当前嵌入式 SQLite 依赖的数据完整性。 + dsn := cfg.Path + separator + "_pragma=busy_timeout(5000)" + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Silent)}) if err != nil { return nil, fmt.Errorf("open sqlite: %w", err) } diff --git a/server/internal/database/database_test.go b/server/internal/database/database_test.go new file mode 100644 index 0000000..8cc9065 --- /dev/null +++ b/server/internal/database/database_test.go @@ -0,0 +1,40 @@ +package database + +import ( + "path/filepath" + "testing" + + "backupx/server/internal/config" + "backupx/server/internal/logger" +) + +func TestOpenConfiguresSQLiteForSingleMasterConcurrency(t *testing.T) { + log, err := logger.New(config.LogConfig{Level: "error"}) + if err != nil { + t.Fatal(err) + } + db, err := Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log) + if err != nil { + t.Fatal(err) + } + sqlDB, err := db.DB() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) + + var journalMode string + if err := db.Raw("PRAGMA journal_mode").Scan(&journalMode).Error; err != nil { + t.Fatal(err) + } + if journalMode != "delete" { + t.Fatalf("journal_mode = %q, want delete", journalMode) + } + var busyTimeout int + if err := db.Raw("PRAGMA busy_timeout").Scan(&busyTimeout).Error; err != nil { + t.Fatal(err) + } + if busyTimeout != 5000 { + t.Fatalf("busy_timeout = %d, want 5000", busyTimeout) + } +} diff --git a/server/internal/http/agent_handler.go b/server/internal/http/agent_handler.go index 23d28c1..7912390 100644 --- a/server/internal/http/agent_handler.go +++ b/server/internal/http/agent_handler.go @@ -23,7 +23,7 @@ func NewAgentHandler(agentService *service.AgentService, nodeService *service.No return &AgentHandler{agentService: agentService, nodeService: nodeService, restoreService: restoreService} } -// extractToken 从请求头或 JSON body 中提取 Agent Token。 +// extractToken 从认证请求头中提取 Agent Token。 func extractToken(c *gin.Context) string { if t := strings.TrimSpace(c.GetHeader("X-Agent-Token")); t != "" { return t @@ -46,10 +46,10 @@ func (h *AgentHandler) Heartbeat(c *gin.Context) { Arch string `json:"arch"` } _ = c.ShouldBindJSON(&input) - // token 优先走 body(向后兼容),否则从 header 读 - token := input.Token + // 新版 Agent 只通过请求头发送 Token;JSON body 仅保留旧版本兼容。 + token := extractToken(c) if token == "" { - token = extractToken(c) + token = input.Token } if token == "" { c.JSON(stdhttp.StatusBadRequest, gin.H{"code": "INVALID_INPUT", "message": "missing token"}) @@ -72,7 +72,7 @@ func (h *AgentHandler) Heartbeat(c *gin.Context) { }) } -// Poll Agent 长轮询获取下一条待执行命令。 +// Poll Agent 获取下一条待执行命令;Agent 按配置间隔主动轮询。 // 无命令时返回 {command: null}。 func (h *AgentHandler) Poll(c *gin.Context) { node, err := h.agentService.AuthenticatedNode(c.Request.Context(), extractToken(c)) diff --git a/server/internal/http/forwarded_headers_test.go b/server/internal/http/forwarded_headers_test.go new file mode 100644 index 0000000..41369b4 --- /dev/null +++ b/server/internal/http/forwarded_headers_test.go @@ -0,0 +1,49 @@ +package http + +import ( + stdhttp "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestForwardedHeadersMiddlewareRejectsUntrustedHeaders(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + engine.Use(ForwardedHeadersMiddleware([]string{"127.0.0.1", "10.0.0.0/8"})) + engine.GET("/master-url", func(c *gin.Context) { + c.String(stdhttp.StatusOK, resolveMasterURL(c, "")) + }) + + request := httptest.NewRequest(stdhttp.MethodGet, "http://master.example.com/master-url", nil) + request.RemoteAddr = "203.0.113.10:54321" + request.Header.Set("X-Forwarded-Host", "attacker.example.com") + request.Header.Set("X-Forwarded-Proto", "https") + recorder := httptest.NewRecorder() + engine.ServeHTTP(recorder, request) + + if recorder.Body.String() != "http://master.example.com" { + t.Fatalf("untrusted forwarding headers changed URL: %q", recorder.Body.String()) + } +} + +func TestForwardedHeadersMiddlewareAcceptsTrustedProxy(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + engine.Use(ForwardedHeadersMiddleware([]string{"10.0.0.0/8"})) + engine.GET("/master-url", func(c *gin.Context) { + c.String(stdhttp.StatusOK, resolveMasterURL(c, "")) + }) + + request := httptest.NewRequest(stdhttp.MethodGet, "http://backupx:8340/master-url", nil) + request.RemoteAddr = "10.10.0.5:43210" + request.Header.Set("X-Forwarded-Host", "backup.example.com") + request.Header.Set("X-Forwarded-Proto", "https") + recorder := httptest.NewRecorder() + engine.ServeHTTP(recorder, request) + + if recorder.Body.String() != "https://backup.example.com" { + t.Fatalf("trusted forwarding headers were ignored: %q", recorder.Body.String()) + } +} diff --git a/server/internal/http/install_flow_test.go b/server/internal/http/install_flow_test.go index 005a670..a14310a 100644 --- a/server/internal/http/install_flow_test.go +++ b/server/internal/http/install_flow_test.go @@ -115,7 +115,7 @@ func setupInstallFlowRouterWithExternalURL(t *testing.T, externalURL string) (ht return router, setupResp.Data.Token } -func TestInstallTokenUsesConfiguredExternalURL(t *testing.T) { +func TestInstallTokenUsesAgentSpecificURLAndConnectionSettings(t *testing.T) { const externalURL = "https://public.example.com/base" router, jwt := setupInstallFlowRouterWithExternalURL(t, externalURL) @@ -141,11 +141,14 @@ func TestInstallTokenUsesConfiguredExternalURL(t *testing.T) { } genBody, _ := json.Marshal(map[string]any{ - "mode": "systemd", - "arch": "auto", - "agentVersion": "v1.7.0", - "downloadSrc": "github", - "ttlSeconds": 900, + "mode": "systemd", + "arch": "auto", + "agentVersion": "v1.7.0", + "downloadSrc": "github", + "ttlSeconds": 900, + "agentMasterUrl": "http://127.0.0.1:18340", + "proxyUrl": "socks5h://127.0.0.1:1080", + "caCertFile": "/etc/pki/internal-ca.pem", }) genReq := httptest.NewRequest(http.MethodPost, "/api/nodes/"+formatUint(batchResp.Data[0].ID)+"/install-tokens", bytes.NewBuffer(genBody)) @@ -156,6 +159,9 @@ func TestInstallTokenUsesConfiguredExternalURL(t *testing.T) { if genRec.Code != 200 { t.Fatalf("install-tokens failed: %d %s", genRec.Code, genRec.Body.String()) } + if genRec.Header().Get("Cache-Control") != "no-store" { + t.Fatalf("install-token response must not be cached: %#v", genRec.Header()) + } var genResp struct { Data struct { InstallToken string `json:"installToken"` @@ -167,18 +173,21 @@ func TestInstallTokenUsesConfiguredExternalURL(t *testing.T) { if err := json.Unmarshal(genRec.Body.Bytes(), &genResp); err != nil { t.Fatalf("unmarshal gen: %v", err) } - if genResp.Data.URL != externalURL+"/api/install/"+genResp.Data.InstallToken { - t.Fatalf("url should use external URL, got %q", genResp.Data.URL) + if genResp.Data.URL != "http://127.0.0.1:18340/api/install/"+genResp.Data.InstallToken { + t.Fatalf("url should use Agent-specific URL, got %q", genResp.Data.URL) } - if genResp.Data.FallbackURL != externalURL+"/install/"+genResp.Data.InstallToken { - t.Fatalf("fallbackUrl should use external URL, got %q", genResp.Data.FallbackURL) + if genResp.Data.FallbackURL != "http://127.0.0.1:18340/install/"+genResp.Data.InstallToken { + t.Fatalf("fallbackUrl should use Agent-specific URL, got %q", genResp.Data.FallbackURL) } decodedScript, err := base64.StdEncoding.DecodeString(genResp.Data.ScriptBase64) if err != nil { t.Fatalf("scriptBase64 should be valid base64: %v", err) } - if !strings.Contains(string(decodedScript), `MASTER_URL="`+externalURL+`"`) { - t.Fatalf("script should use external MASTER_URL:\n%s", string(decodedScript)) + if !strings.Contains(string(decodedScript), `MASTER_URL="http://127.0.0.1:18340"`) { + t.Fatalf("script should use the Agent-specific Master URL:\n%s", string(decodedScript)) + } + if !strings.Contains(string(decodedScript), `PROXY_URL="socks5h://127.0.0.1:1080"`) || !strings.Contains(string(decodedScript), `CA_CERT_FILE="/etc/pki/internal-ca.pem"`) { + t.Fatalf("script should include restricted-network settings:\n%s", string(decodedScript)) } } @@ -258,8 +267,9 @@ func TestOneClickInstallFlow(t *testing.T) { if scriptRec.Code != 200 { t.Fatalf("script fetch failed: %d %s", scriptRec.Code, scriptRec.Body.String()) } - if !strings.Contains(scriptRec.Body.String(), "systemctl enable --now backupx-agent") { - t.Fatalf("script missing systemctl enable:\n%s", scriptRec.Body.String()) + if !strings.Contains(scriptRec.Body.String(), "systemctl enable backupx-agent") || + !strings.Contains(scriptRec.Body.String(), "systemctl restart backupx-agent") { + t.Fatalf("script missing systemctl enable/restart:\n%s", scriptRec.Body.String()) } // Issue #46 防嗅探 headers:text/plain + nosniff + no-store + Content-Disposition if ct := scriptRec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") { @@ -354,7 +364,8 @@ func TestInstallScriptAliasUnderAPI(t *testing.T) { if aliasRec.Code != 200 { t.Fatalf("/api/install alias failed: %d %s", aliasRec.Code, aliasRec.Body.String()) } - if !strings.Contains(aliasRec.Body.String(), "systemctl enable --now backupx-agent") { + if !strings.Contains(aliasRec.Body.String(), "systemctl enable backupx-agent") || + !strings.Contains(aliasRec.Body.String(), "systemctl restart backupx-agent") { t.Errorf("alias should return rendered script, got:\n%s", aliasRec.Body.String()) } if ct := aliasRec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") { @@ -557,6 +568,9 @@ func TestInstallFlowComposeSuccessConsumesToken(t *testing.T) { if !strings.Contains(composeRec.Body.String(), "BACKUPX_AGENT_TOKEN") { t.Fatalf("compose missing token env:\n%s", composeRec.Body.String()) } + if composeRec.Header().Get("Cache-Control") != "no-store" || composeRec.Header().Get("X-Content-Type-Options") != "nosniff" { + t.Fatalf("compose response missing secret-safe headers: %#v", composeRec.Header()) + } scriptReq := httptest.NewRequest(http.MethodGet, "/api/install/"+genResp.Data.InstallToken, nil) scriptRec := httptest.NewRecorder() diff --git a/server/internal/http/install_handler.go b/server/internal/http/install_handler.go index 9924d17..7371bf8 100644 --- a/server/internal/http/install_handler.go +++ b/server/internal/http/install_handler.go @@ -104,16 +104,22 @@ func (h *InstallHandler) Compose(c *gin.Context) { } h.recordConsumeAudit(c, consumed, "compose") yaml, err := installscript.RenderComposeYaml(installscript.Context{ - MasterURL: resolveMasterURL(c, h.externalURL), + MasterURL: installMasterURL(resolveMasterURL(c, h.externalURL), consumed.Record), AgentToken: consumed.Node.Token, AgentVersion: consumed.Record.AgentVer, Mode: model.InstallModeDocker, + Arch: consumed.Record.Arch, NodeID: consumed.Node.ID, + ProxyURL: consumed.Record.ProxyURL, + CACertFile: consumed.Record.CACertFile, }) if err != nil { c.String(stdhttp.StatusInternalServerError, "render error\n") return } + c.Header("X-Content-Type-Options", "nosniff") + c.Header("Cache-Control", "no-store") + c.Header("Content-Disposition", `attachment; filename="backupx-agent-compose.yml"`) c.Data(stdhttp.StatusOK, "text/yaml; charset=utf-8", []byte(yaml)) } @@ -134,7 +140,7 @@ func (h *InstallHandler) recordConsumeAudit(c *gin.Context, consumed *service.Co func renderInstallScript(masterURL string, node *model.Node, record *model.AgentInstallToken) (string, error) { return installscript.RenderScript(installscript.Context{ - MasterURL: masterURL, + MasterURL: installMasterURL(masterURL, record), AgentToken: node.Token, AgentVersion: record.AgentVer, Mode: record.Mode, @@ -142,9 +148,18 @@ func renderInstallScript(masterURL string, node *model.Node, record *model.Agent DownloadBase: installscript.DownloadBaseFor(record.DownloadSrc), InstallPrefix: "/opt/backupx-agent", NodeID: node.ID, + ProxyURL: record.ProxyURL, + CACertFile: record.CACertFile, }) } +func installMasterURL(fallback string, record *model.AgentInstallToken) string { + if record != nil && strings.TrimSpace(record.AgentMasterURL) != "" { + return strings.TrimRight(strings.TrimSpace(record.AgentMasterURL), "/") + } + return fallback +} + // resolveMasterURL 按优先级推导 Master URL:外部配置 > X-Forwarded-* > Request.Host。 // 此为包级 helper,供 install_handler 和 node_handler 共用。 func resolveMasterURL(c *gin.Context, externalURL string) string { diff --git a/server/internal/http/middleware.go b/server/internal/http/middleware.go index 7e79a0b..f9991b6 100644 --- a/server/internal/http/middleware.go +++ b/server/internal/http/middleware.go @@ -3,6 +3,7 @@ package http import ( "context" stdhttp "net/http" + "net/netip" "strings" "backupx/server/internal/apperror" @@ -11,6 +12,46 @@ import ( "github.com/gin-gonic/gin" ) +// ForwardedHeadersMiddleware 只允许配置中的反向代理提供转发头。 +// Gin 的 trusted_proxies 保护 ClientIP;这里同步保护安装命令使用的 +// X-Forwarded-Host 与 X-Forwarded-Proto,避免直连请求伪造 Agent 地址。 +func ForwardedHeadersMiddleware(trustedProxies []string) gin.HandlerFunc { + trustedPrefixes := make([]netip.Prefix, 0, len(trustedProxies)) + for _, raw := range trustedProxies { + raw = strings.TrimSpace(raw) + if prefix, err := netip.ParsePrefix(raw); err == nil { + trustedPrefixes = append(trustedPrefixes, prefix) + continue + } + if addr, err := netip.ParseAddr(raw); err == nil { + trustedPrefixes = append(trustedPrefixes, netip.PrefixFrom(addr, addr.BitLen())) + } + } + + return func(c *gin.Context) { + remote, err := netip.ParseAddrPort(c.Request.RemoteAddr) + trusted := false + if err == nil { + remoteAddr := remote.Addr().Unmap() + for _, prefix := range trustedPrefixes { + if prefix.Contains(remoteAddr) { + trusted = true + break + } + } + } + if !trusted { + for _, header := range []string{ + "Forwarded", "X-Forwarded-For", "X-Forwarded-Host", + "X-Forwarded-Port", "X-Forwarded-Proto", "X-Real-IP", + } { + c.Request.Header.Del(header) + } + } + c.Next() + } +} + // CORSMiddleware handles Cross-Origin Resource Sharing for the API. func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/server/internal/http/node_handler.go b/server/internal/http/node_handler.go index 249694b..a833b18 100644 --- a/server/internal/http/node_handler.go +++ b/server/internal/http/node_handler.go @@ -204,6 +204,7 @@ func (h *NodeHandler) RotateToken(c *gin.Context) { recordAudit(c, h.auditService, "node", "rotate_token", "node", fmt.Sprintf("%d", id), "", fmt.Sprintf("轮换节点 Token (ID: %d)", id)) + c.Header("Cache-Control", "no-store") response.Success(c, gin.H{"newToken": tok}) } @@ -220,11 +221,14 @@ func (h *NodeHandler) CreateInstallToken(c *gin.Context) { return } var input struct { - Mode string `json:"mode"` - Arch string `json:"arch"` - AgentVersion string `json:"agentVersion"` - DownloadSrc string `json:"downloadSrc"` - TTLSeconds int `json:"ttlSeconds"` + Mode string `json:"mode"` + Arch string `json:"arch"` + AgentVersion string `json:"agentVersion"` + DownloadSrc string `json:"downloadSrc"` + TTLSeconds int `json:"ttlSeconds"` + AgentMasterURL string `json:"agentMasterUrl"` + ProxyURL string `json:"proxyUrl"` + CACertFile string `json:"caCertFile"` } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(stdhttp.StatusBadRequest, gin.H{"code": "INVALID_INPUT", "message": err.Error()}) @@ -246,13 +250,16 @@ func (h *NodeHandler) CreateInstallToken(c *gin.Context) { out, err := h.installTokenSvc.CreateCommand(c.Request.Context(), service.InstallCommandInput{ InstallTokenInput: service.InstallTokenInput{ - NodeID: uint(id), - Mode: input.Mode, - Arch: input.Arch, - AgentVersion: input.AgentVersion, - DownloadSrc: input.DownloadSrc, - TTLSeconds: input.TTLSeconds, - CreatedByID: h.resolveCurrentUserID(c), + NodeID: uint(id), + Mode: input.Mode, + Arch: input.Arch, + AgentVersion: input.AgentVersion, + DownloadSrc: input.DownloadSrc, + TTLSeconds: input.TTLSeconds, + CreatedByID: h.resolveCurrentUserID(c), + AgentMasterURL: input.AgentMasterURL, + ProxyURL: input.ProxyURL, + CACertFile: input.CACertFile, }, MasterURL: resolveMasterURL(c, h.externalURL), }) @@ -278,6 +285,7 @@ func (h *NodeHandler) CreateInstallToken(c *gin.Context) { "composeUrl": out.ComposeURL, "fallbackComposeUrl": out.FallbackComposeURL, } + c.Header("Cache-Control", "no-store") response.Success(c, body) } @@ -292,18 +300,24 @@ func (h *NodeHandler) PreviewScript(c *gin.Context) { return } src := c.DefaultQuery("downloadSrc", "github") + agentMasterURL := c.Query("agentMasterUrl") + if agentMasterURL == "" { + agentMasterURL = resolveMasterURL(c, h.externalURL) + } ctx := installscript.Context{ - MasterURL: resolveMasterURL(c, h.externalURL), + MasterURL: agentMasterURL, AgentToken: "", AgentVersion: ver, Mode: mode, Arch: arch, DownloadBase: installscript.DownloadBaseFor(src), InstallPrefix: "/opt/backupx-agent", + ProxyURL: c.Query("proxyUrl"), + CACertFile: c.Query("caCertFile"), } script, err := installscript.RenderScript(ctx) if err != nil { - response.Error(c, err) + response.Error(c, apperror.BadRequest("INSTALL_TOKEN_INVALID", "Agent 连接配置无效", err)) return } c.Data(stdhttp.StatusOK, "text/x-shellscript; charset=utf-8", []byte(script)) diff --git a/server/internal/http/router.go b/server/internal/http/router.go index f003fb2..2c02949 100644 --- a/server/internal/http/router.go +++ b/server/internal/http/router.go @@ -61,7 +61,11 @@ type RouterDependencies struct { func NewRouter(deps RouterDependencies) *gin.Engine { gin.SetMode(deps.Config.Server.Mode) engine := gin.New() + if err := engine.SetTrustedProxies(deps.Config.Server.TrustedProxies); err != nil { + panic("invalid trusted proxy configuration: " + err.Error()) + } engine.Use(gin.Recovery()) + engine.Use(ForwardedHeadersMiddleware(deps.Config.Server.TrustedProxies)) engine.Use(CORSMiddleware()) engine.Use(requestLogger(deps.Logger)) diff --git a/server/internal/installscript/deploy_install_test.go b/server/internal/installscript/deploy_install_test.go index c001e23..13089a4 100644 --- a/server/internal/installscript/deploy_install_test.go +++ b/server/internal/installscript/deploy_install_test.go @@ -6,11 +6,17 @@ import ( "path/filepath" "strings" "testing" + + "gopkg.in/yaml.v3" ) func TestDeployInstallScriptSyntax(t *testing.T) { scriptPath := filepath.Join("..", "..", "..", "deploy", "install.sh") - cmd := exec.Command("sh", "-n", scriptPath) + sh, err := exec.LookPath("sh") + if err != nil { + t.Skip("POSIX sh is not available on this platform") + } + cmd := exec.Command(sh, "-n", scriptPath) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("install.sh syntax invalid: %v\n%s", err, output) @@ -30,9 +36,13 @@ func TestDeployInstallScriptSupportsReleasePackageLayout(t *testing.T) { `BIN_SOURCE="${BIN_SOURCE:-$SCRIPT_DIR/backupx}"`, `WEB_SOURCE="${WEB_SOURCE:-$SCRIPT_DIR/web}"`, `CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$SCRIPT_DIR/config.example.yaml}"`, + `SERVICE_SOURCE_DEFAULT="$SCRIPT_DIR/backupx.service"`, `发布包安装请确认当前目录包含 ./backupx、./web 和 ./install.sh。`, `cat > "/etc/systemd/system/$SERVICE_NAME.service" <= '0' && c <= '9': + case c >= 'a' && c <= 'z': + case c >= 'A' && c <= 'Z': + case c == '/' || c == '.' || c == '_' || c == '-' || c == '+': + default: + return fmt.Errorf("install prefix contains illegal character %q", c) + } + } + if err := validateDownloadBase(ctx.DownloadBase); err != nil { + return err + } + return nil +} + +func validateDownloadBase(raw string) error { + if strings.ContainsAny(raw, " \t\r\n\"'`$\\") { + return fmt.Errorf("download base contains illegal characters") + } + u, err := url.Parse(raw) + if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" { + return fmt.Errorf("download base must be an absolute http(s) URL") + } + if u.User != nil || u.RawQuery != "" || u.Fragment != "" { + return fmt.Errorf("download base must not contain credentials, query or fragment") + } return nil } @@ -114,6 +159,43 @@ func validateMasterURL(raw string) error { if u.Host == "" { return fmt.Errorf("master URL missing host") } + if u.User != nil || u.RawQuery != "" || u.Fragment != "" { + return fmt.Errorf("master URL must not contain credentials, query or fragment") + } + return nil +} + +func validateProxyURL(raw string) error { + raw = strings.TrimSpace(raw) + if raw == "" { + return nil + } + if strings.ContainsAny(raw, " \t\r\n\"'`$\\") { + return fmt.Errorf("proxy URL contains illegal characters") + } + u, err := url.Parse(raw) + if err != nil || u.Host == "" { + return fmt.Errorf("invalid proxy URL") + } + switch u.Scheme { + case "http", "https", "socks5", "socks5h": + default: + return fmt.Errorf("proxy URL scheme must be http, https, socks5 or socks5h") + } + if u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Path != "" && u.Path != "/") { + return fmt.Errorf("proxy URL must not contain credentials, path, query or fragment") + } + return nil +} + +func validateCACertFile(file string) error { + file = strings.TrimSpace(file) + if file == "" { + return nil + } + if !path.IsAbs(file) || path.Clean(file) != file || strings.ContainsAny(file, " \t\r\n\"'`$\\") { + return fmt.Errorf("CA certificate path must be a clean absolute path without shell metacharacters") + } return nil } @@ -160,6 +242,9 @@ func validateAgentVersion(v string) error { } func withDefaults(ctx Context) Context { + if ctx.Arch == "" { + ctx.Arch = model.InstallArchAuto + } if ctx.InstallPrefix == "" { ctx.InstallPrefix = "/opt/backupx-agent" } diff --git a/server/internal/installscript/renderer_test.go b/server/internal/installscript/renderer_test.go index 91f0601..209448f 100644 --- a/server/internal/installscript/renderer_test.go +++ b/server/internal/installscript/renderer_test.go @@ -2,11 +2,13 @@ package installscript import ( "os" + "os/exec" "path/filepath" "strings" "testing" "backupx/server/internal/model" + "gopkg.in/yaml.v3" ) // 使用合法 hex token(32 字节 = 64 字符)以通过 validateAgentToken 校验 @@ -27,11 +29,13 @@ func TestRenderScriptSystemd(t *testing.T) { t.Fatalf("render err: %v", err) } mustContain := []string{ - "BACKUPX_AGENT_MASTER=${MASTER_URL}", - `Environment="BACKUPX_AGENT_TOKEN=${AGENT_TOKEN}"`, + `master: "${MASTER_URL}"`, + `tokenFile: "${TOKEN_FILE}"`, + `ExecStart=${INSTALL_PREFIX}/backupx agent --config ${CONFIG_FILE}`, "/var/lib/backupx-agent/tmp", "systemctl daemon-reload", - "systemctl enable --now backupx-agent", + "systemctl enable backupx-agent", + "systemctl restart backupx-agent", "systemctl status backupx-agent", "X-Agent-Token: ${AGENT_TOKEN}", "MASTER_URL=\"https://master.example.com\"", @@ -48,6 +52,29 @@ func TestRenderScriptSystemd(t *testing.T) { t.Errorf("systemd script unexpectedly contains %q", s) } } + if strings.Contains(got, `Environment="BACKUPX_AGENT_TOKEN=`) { + t.Errorf("systemd unit must not expose the agent token in its environment:\n%s", got) + } +} + +func TestRenderedInstallScriptSyntax(t *testing.T) { + sh, err := exec.LookPath("sh") + if err != nil { + t.Skip("POSIX sh is not available on this platform") + } + for _, mode := range []string{model.InstallModeSystemd, model.InstallModeDocker, model.InstallModeForeground} { + ctx := testCtx + ctx.Mode = mode + script, renderErr := RenderScript(ctx) + if renderErr != nil { + t.Fatal(renderErr) + } + cmd := exec.Command(sh, "-n") + cmd.Stdin = strings.NewReader(script) + if output, syntaxErr := cmd.CombinedOutput(); syntaxErr != nil { + t.Fatalf("%s installer syntax invalid: %v\n%s", mode, syntaxErr, output) + } + } } func TestRenderScriptForeground(t *testing.T) { @@ -90,8 +117,11 @@ func TestRenderScriptDocker(t *testing.T) { if !strings.Contains(got, `"awuqing/backupx:${AGENT_VERSION}" agent`) { t.Errorf("docker script must start image in agent mode:\n%s", got) } - if !strings.Contains(got, `-e "BACKUPX_AGENT_TEMP_DIR=/var/lib/backupx-agent/tmp"`) { - t.Errorf("docker script missing temp dir env:\n%s", got) + if !strings.Contains(got, `-v /etc/backupx-agent:/etc/backupx-agent:ro`) { + t.Errorf("docker script missing protected config mount:\n%s", got) + } + if !strings.Contains(got, `agent --config /etc/backupx-agent/config.yaml`) { + t.Errorf("docker script must load the protected config file:\n%s", got) } if !strings.Contains(got, `docker logs --tail=100 backupx-agent`) { t.Errorf("docker script missing diagnostic log command:\n%s", got) @@ -102,6 +132,9 @@ func TestRenderScriptDocker(t *testing.T) { if strings.Contains(got, "systemctl daemon-reload") { t.Errorf("docker script should not reference systemctl:\n%s", got) } + if strings.Contains(got, `-e "BACKUPX_AGENT_TOKEN=`) { + t.Errorf("docker inspect must not expose the agent token:\n%s", got) + } } func TestDockerEntrypointForwardsAgentSubcommand(t *testing.T) { @@ -111,12 +144,18 @@ func TestDockerEntrypointForwardsAgentSubcommand(t *testing.T) { t.Fatalf("read docker entrypoint: %v", err) } script := string(got) - if !strings.Contains(script, `"${1:-}" = "agent"`) { - t.Fatalf("entrypoint must detect the agent subcommand before starting server:\n%s", script) - } if !strings.Contains(script, `exec /app/bin/backupx "$@"`) { t.Fatalf("entrypoint must exec backupx with forwarded args:\n%s", script) } + if !strings.Contains(script, `exec su-exec backupx:backupx /app/bin/backupx "$@"`) { + t.Fatalf("master entrypoint must drop privileges after data migration:\n%s", script) + } + if !strings.Contains(script, `export HOME=/app`) { + t.Fatalf("master entrypoint must set the service user's home directory:\n%s", script) + } + if strings.Contains(script, "nginx") || strings.Contains(script, "wait -n") { + t.Fatalf("entrypoint should run a single foreground process:\n%s", script) + } } func TestRenderComposeYaml(t *testing.T) { @@ -138,9 +177,40 @@ func TestRenderComposeYaml(t *testing.T) { if !strings.Contains(got, `BACKUPX_AGENT_TEMP_DIR: "/var/lib/backupx-agent/tmp"`) { t.Errorf("compose missing temp dir env:\n%s", got) } + if !strings.Contains(got, `user: "0:0"`) || !strings.Contains(got, "no-new-privileges:true") { + t.Errorf("compose missing root execution declaration or security option:\n%s", got) + } if !strings.Contains(got, "/var/lib/backupx-agent:/var/lib/backupx-agent") { t.Errorf("compose missing agent data volume:\n%s", got) } + var document map[string]any + if err := yaml.Unmarshal([]byte(got), &document); err != nil { + t.Fatalf("compose is not valid YAML: %v\n%s", err, got) + } +} + +func TestRenderComposeYamlIncludesRestrictedNetworkSettings(t *testing.T) { + ctx := testCtx + ctx.Mode = model.InstallModeDocker + ctx.ProxyURL = "socks5h://127.0.0.1:1080" + ctx.CACertFile = "/etc/backupx-agent/ca.pem" + got, err := RenderComposeYaml(ctx) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{ + `BACKUPX_AGENT_PROXY_URL: "socks5h://127.0.0.1:1080"`, + `BACKUPX_AGENT_CA_CERT_FILE: "/etc/backupx-agent/ca.pem"`, + `- /etc/backupx-agent/ca.pem:/etc/backupx-agent/ca.pem:ro`, + } { + if !strings.Contains(got, want) { + t.Errorf("compose missing %q:\n%s", want, got) + } + } + var document map[string]any + if err := yaml.Unmarshal([]byte(got), &document); err != nil { + t.Fatalf("compose is not valid YAML: %v\n%s", err, got) + } } func TestRenderScriptRejectsInjectedMasterURL(t *testing.T) { @@ -159,6 +229,73 @@ func TestRenderScriptRejectsInjectedMasterURL(t *testing.T) { } } +func TestRenderScriptIncludesRestrictedNetworkSettings(t *testing.T) { + ctx := testCtx + ctx.MasterURL = "http://127.0.0.1:18340" + ctx.ProxyURL = "socks5h://127.0.0.1:1080" + ctx.CACertFile = "/etc/pki/internal-ca.pem" + got, err := RenderScript(ctx) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{ + `PROXY_URL="socks5h://127.0.0.1:1080"`, + `CA_CERT_FILE="/etc/pki/internal-ca.pem"`, + `proxyUrl: "${PROXY_URL}"`, + `caCertFile: "${CA_CERT_FILE}"`, + `curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --proxy "$PROXY_URL"`, + } { + if !strings.Contains(got, want) { + t.Errorf("restricted network script missing %q", want) + } + } +} + +func TestRenderScriptRejectsUnsafeRestrictedNetworkSettings(t *testing.T) { + for _, mutate := range []func(*Context){ + func(ctx *Context) { ctx.ProxyURL = "ftp://proxy.example.com" }, + func(ctx *Context) { ctx.ProxyURL = "http://user:pass@proxy.example.com" }, + func(ctx *Context) { ctx.ProxyURL = "http://proxy.example.com/connect" }, + func(ctx *Context) { ctx.CACertFile = "relative-ca.pem" }, + } { + ctx := testCtx + mutate(&ctx) + if _, err := RenderScript(ctx); err == nil { + t.Fatalf("expected restricted network settings to be rejected: %+v", ctx) + } + } +} + +func TestRenderScriptRejectsUnsafeDeploymentSettings(t *testing.T) { + for _, mutate := range []func(*Context){ + func(ctx *Context) { ctx.Mode = "unknown" }, + func(ctx *Context) { ctx.Arch = "386" }, + func(ctx *Context) { ctx.InstallPrefix = "/opt/backupx;touch/tmp/pwned" }, + func(ctx *Context) { ctx.DownloadBase = "https://user:pass@example.com/releases" }, + } { + ctx := testCtx + mutate(&ctx) + if _, err := RenderScript(ctx); err == nil { + t.Fatalf("expected unsafe deployment settings to be rejected: %+v", ctx) + } + } +} + +func TestRenderScriptVerifiesReleaseChecksumWithoutEmoji(t *testing.T) { + got, err := RenderScript(testCtx) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{"sha256sum -c", `download_file "${URL}.sha256"`, "[OK] 节点已上线", "[WARN]"} { + if !strings.Contains(got, want) { + t.Errorf("script missing %q", want) + } + } + if strings.ContainsAny(got, "\u2713\u26a0") { + t.Fatal("installer output must not use emoji symbols") + } +} + func TestRenderComposeYamlRejectsInjectedMasterURL(t *testing.T) { ctx := testCtx ctx.Mode = model.InstallModeDocker diff --git a/server/internal/installscript/templates/agent-compose.yml.tmpl b/server/internal/installscript/templates/agent-compose.yml.tmpl index a49b072..6ded671 100644 --- a/server/internal/installscript/templates/agent-compose.yml.tmpl +++ b/server/internal/installscript/templates/agent-compose.yml.tmpl @@ -1,14 +1,22 @@ -# BackupX Agent docker-compose 片段 +# BackupX Agent Compose 片段 # 生成于 {{.MasterURL}} · 节点 ID {{.NodeID}} -version: "3.8" +# 文件包含长期节点 Token,请保存为 0600 权限并在部署完成后限制访问。 services: backupx-agent: image: awuqing/backupx:{{.AgentVersion}} command: ["agent"] + user: "0:0" restart: unless-stopped + security_opt: + - no-new-privileges:true environment: BACKUPX_AGENT_MASTER: "{{.MasterURL}}" BACKUPX_AGENT_TOKEN: "{{.AgentToken}}" BACKUPX_AGENT_TEMP_DIR: "/var/lib/backupx-agent/tmp" - volumes: +{{if .ProxyURL}} BACKUPX_AGENT_PROXY_URL: "{{.ProxyURL}}" +{{end}}{{if .CACertFile}} BACKUPX_AGENT_CA_CERT_FILE: "{{.CACertFile}}" +{{end}} volumes: - /var/lib/backupx-agent:/var/lib/backupx-agent +{{if .CACertFile}} - {{.CACertFile}}:{{.CACertFile}}:ro +{{end}} # 备份宿主机文件时,必须按需添加只读源目录挂载: + # - /srv/data:/srv/data:ro diff --git a/server/internal/installscript/templates/agent-install.sh.tmpl b/server/internal/installscript/templates/agent-install.sh.tmpl index 5d4adaf..6aac492 100644 --- a/server/internal/installscript/templates/agent-install.sh.tmpl +++ b/server/internal/installscript/templates/agent-install.sh.tmpl @@ -1,12 +1,11 @@ #!/bin/sh # BackupX Agent 一键安装脚本(由 Master 动态渲染) -# Magic: BACKUPX_AGENT_INSTALL_V1 —— 若 `head -3 脚本` 看不到此行,说明反向代理/CDN 改写了响应 +# Magic: BACKUPX_AGENT_INSTALL_V1 —— 若 `head -3 脚本` 看不到此行,说明反向代理或 CDN 改写了响应 # 模式: {{.Mode}} | 架构: {{.Arch}} | 版本: {{.AgentVersion}} set -eu +umask 077 -# 自举到 bash(文件执行模式下生效;管道模式 $0 不是文件,exec 会静默失败,继续用 sh)。 -# 动机:部分 Debian/Ubuntu 用户通过 `curl | sudo sh` 触发时,dash 对本脚本报语法错误; -# 若目标机装有 bash,优先切换到 bash 获得更一致的行为。 +# 文件执行模式下优先使用 bash;管道模式继续使用 POSIX sh。 if [ -z "${BASH_VERSION:-}" ] && command -v bash >/dev/null 2>&1 && [ -f "$0" ]; then exec bash "$0" "$@" fi @@ -17,14 +16,56 @@ AGENT_VERSION="{{.AgentVersion}}" DOWNLOAD_BASE="{{.DownloadBase}}" INSTALL_PREFIX="{{.InstallPrefix}}" ARCH="{{.Arch}}" +PROXY_URL="{{.ProxyURL}}" +CA_CERT_FILE="{{.CACertFile}}" +CONFIG_DIR="/etc/backupx-agent" +CONFIG_FILE="${CONFIG_DIR}/config.yaml" +TOKEN_FILE="${CONFIG_DIR}/agent.token" + +download_file() { + source_url="$1" + destination="$2" + if command -v curl >/dev/null 2>&1; then + if [ -n "$PROXY_URL" ]; then + curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --proxy "$PROXY_URL" "$source_url" -o "$destination" + else + curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 "$source_url" -o "$destination" + fi + else + wget -q -T 30 -O "$destination" "$source_url" + fi +} + +agent_is_online() { + if command -v curl >/dev/null 2>&1; then + if [ -n "$PROXY_URL" ] && [ -n "$CA_CERT_FILE" ]; then + response=$(curl -fsS --max-time 5 --proxy "$PROXY_URL" --cacert "$CA_CERT_FILE" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 + elif [ -n "$PROXY_URL" ]; then + response=$(curl -fsS --max-time 5 --proxy "$PROXY_URL" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 + elif [ -n "$CA_CERT_FILE" ]; then + response=$(curl -fsS --max-time 5 --cacert "$CA_CERT_FILE" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 + else + response=$(curl -fsS --max-time 5 -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 + fi + else + response=$(wget -q -T 5 --max-redirect=0 -O - --header="X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 + fi + printf '%s' "$response" | grep -q '"status":"online"' +} # 1. 前置检查 [ "$(id -u)" -eq 0 ] || { echo "请使用 root 或 sudo 执行" >&2; exit 1; } -command -v curl >/dev/null || command -v wget >/dev/null \ +command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 \ || { echo "需要 curl 或 wget" >&2; exit 1; } -{{if eq .Mode "systemd"}}command -v systemctl >/dev/null || { echo "不支持非 systemd 系统" >&2; exit 1; } -{{end}}{{if eq .Mode "docker"}}command -v docker >/dev/null || { echo "需要先安装 docker" >&2; exit 1; } +command -v grep >/dev/null 2>&1 || { echo "需要 grep" >&2; exit 1; } +if { [ -n "$PROXY_URL" ] || [ -n "$CA_CERT_FILE" ]; } && ! command -v curl >/dev/null 2>&1; then + echo "显式代理或自定义 CA 场景需要 curl" >&2 + exit 1 +fi +{{if eq .Mode "systemd"}}command -v systemctl >/dev/null 2>&1 || { echo "当前系统不支持 systemd" >&2; exit 1; } +{{end}}{{if eq .Mode "docker"}}command -v docker >/dev/null 2>&1 || { echo "需要先安装 Docker" >&2; exit 1; } {{end}} + # 2. 架构检测 if [ "$ARCH" = "auto" ]; then case "$(uname -m)" in @@ -34,24 +75,54 @@ if [ "$ARCH" = "auto" ]; then esac fi +# 3. 安全写入 Agent 配置。systemd unit 与容器元数据中不保存节点 Token。 +install -d -m 0700 "$CONFIG_DIR" /var/lib/backupx-agent /var/lib/backupx-agent/tmp +printf '%s\n' "$AGENT_TOKEN" > "$TOKEN_FILE" +chmod 0600 "$TOKEN_FILE" +if [ -n "$CA_CERT_FILE" ]; then + [ -r "$CA_CERT_FILE" ] || { echo "无法读取 CA 证书: $CA_CERT_FILE" >&2; exit 1; } + if [ "$CA_CERT_FILE" != "$CONFIG_DIR/ca.pem" ]; then + install -m 0644 "$CA_CERT_FILE" "$CONFIG_DIR/ca.pem" + fi + CA_CERT_FILE="$CONFIG_DIR/ca.pem" +fi +cat > "$CONFIG_FILE" </dev/null; then - curl -fsSL "$URL" -o "$TMPDIR/pkg.tar.gz" +download_file "$URL" "$TMPDIR/$ARCHIVE" +if download_file "${URL}.sha256" "$TMPDIR/$ARCHIVE.sha256" 2>/dev/null; then + if command -v sha256sum >/dev/null 2>&1; then + (cd "$TMPDIR" && sha256sum -c "$ARCHIVE.sha256") + elif command -v shasum >/dev/null 2>&1; then + (cd "$TMPDIR" && shasum -a 256 -c "$ARCHIVE.sha256") + else + echo "已下载校验文件,但系统缺少 sha256sum 或 shasum,拒绝未校验安装" >&2 + exit 1 + fi else - wget -qO "$TMPDIR/pkg.tar.gz" "$URL" + echo "[WARN] 当前版本未提供 SHA-256 校验文件,继续兼容安装" >&2 fi -tar xzf "$TMPDIR/pkg.tar.gz" -C "$TMPDIR" +tar xzf "$TMPDIR/$ARCHIVE" -C "$TMPDIR" -# 4. 安装二进制 + 数据目录 echo "[2/4] 安装到 ${INSTALL_PREFIX}" install -d -m 0755 "$INSTALL_PREFIX" -install -d -m 0700 /var/lib/backupx-agent /var/lib/backupx-agent/tmp -install -m 0755 "$TMPDIR/backupx-${AGENT_VERSION}-linux-${ARCH}/backupx" "$INSTALL_PREFIX/backupx" +install -m 0755 "$TMPDIR/backupx-${AGENT_VERSION}-linux-${ARCH}/backupx" "$INSTALL_PREFIX/backupx.new" +mv -f "$INSTALL_PREFIX/backupx.new" "$INSTALL_PREFIX/backupx" {{end}} {{if eq .Mode "systemd"}} @@ -62,69 +133,78 @@ cat > /etc/systemd/system/backupx-agent.service <&2 + systemctl status backupx-agent --no-pager >&2 || true + journalctl -u backupx-agent -n 100 --no-pager >&2 || true + exit 1 +fi # 6. 等待上线 echo "[4/4] 等待节点上线" -for i in $(seq 1 15); do +i=0 +while [ "$i" -lt 15 ]; do sleep 2 - if curl -fsSL -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null \ - | grep -q '"status":"online"'; then - echo "✓ 节点已上线" + if agent_is_online; then + echo "[OK] 节点已上线" exit 0 fi + i=$((i + 1)) done -echo "⚠ 30s 内未收到上线心跳,请检查防火墙或 journalctl -u backupx-agent" -echo "提示:systemd 服务名是 backupx-agent,可执行 systemctl status backupx-agent 查看状态。" +echo "[WARN] 30 秒内未收到上线心跳,请检查网络、代理或 SSH 隧道" >&2 +echo "排查命令: systemctl status backupx-agent" >&2 +echo "排查命令: journalctl -u backupx-agent -n 100 --no-pager" >&2 exit 2 {{end}} {{if eq .Mode "foreground"}} # 5. 前台运行 -echo "[3/3] 前台启动 agent(Ctrl+C 退出)" -export BACKUPX_AGENT_MASTER="${MASTER_URL}" -export BACKUPX_AGENT_TOKEN="${AGENT_TOKEN}" -exec "${INSTALL_PREFIX}/backupx" agent --temp-dir /var/lib/backupx-agent/tmp +echo "[3/3] 前台启动 Agent(Ctrl+C 退出)" +exec "${INSTALL_PREFIX}/backupx" agent --config "$CONFIG_FILE" {{end}} {{if eq .Mode "docker"}} -# Docker 模式:直接用镜像启动容器 +# Docker 模式:配置文件只读挂载,避免 Token 出现在 docker inspect 环境变量中。 echo "[1/2] 拉取镜像 awuqing/backupx:${AGENT_VERSION}" docker pull "awuqing/backupx:${AGENT_VERSION}" echo "[2/2] 启动容器 backupx-agent" docker rm -f backupx-agent >/dev/null 2>&1 || true -docker run -d --name backupx-agent --restart=unless-stopped \ - -e "BACKUPX_AGENT_MASTER=${MASTER_URL}" \ - -e "BACKUPX_AGENT_TOKEN=${AGENT_TOKEN}" \ - -e "BACKUPX_AGENT_TEMP_DIR=/var/lib/backupx-agent/tmp" \ +docker run -d --name backupx-agent --restart=unless-stopped --user 0:0 \ + --security-opt no-new-privileges:true \ + -v /etc/backupx-agent:/etc/backupx-agent:ro \ -v /var/lib/backupx-agent:/var/lib/backupx-agent \ - "awuqing/backupx:${AGENT_VERSION}" agent -echo "✓ 容器已启动,等待节点上线" -for i in $(seq 1 15); do + "awuqing/backupx:${AGENT_VERSION}" agent --config /etc/backupx-agent/config.yaml +echo "提示: Docker Agent 只能访问显式挂载的目录;备份宿主机路径前请按文档添加只读 -v 挂载。" +echo "容器已启动,等待节点上线" +i=0 +while [ "$i" -lt 15 ]; do sleep 2 - if curl -fsSL -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null \ - | grep -q '"status":"online"'; then - echo "✓ 节点已上线" + if agent_is_online; then + echo "[OK] 节点已上线" exit 0 fi + i=$((i + 1)) done -echo "⚠ 30s 内未收到上线心跳,请检查容器状态、网络与 Master URL。" -echo "排查命令:docker ps -a --filter name=backupx-agent" -echo "排查命令:docker logs --tail=100 backupx-agent" +echo "[WARN] 30 秒内未收到上线心跳,请检查容器、代理、隧道与 Master 地址" >&2 +echo "排查命令: docker ps -a --filter name=backupx-agent" >&2 +echo "排查命令: docker logs --tail=100 backupx-agent" >&2 exit 2 {{end}} diff --git a/server/internal/model/agent_command.go b/server/internal/model/agent_command.go index f20e45b..5476b51 100644 --- a/server/internal/model/agent_command.go +++ b/server/internal/model/agent_command.go @@ -4,11 +4,11 @@ import "time" // AgentCommand 状态常量 const ( - AgentCommandStatusPending = "pending" // 待 Agent 拉取 + AgentCommandStatusPending = "pending" // 待 Agent 拉取 AgentCommandStatusDispatched = "dispatched" // Agent 已领取,正在执行 - AgentCommandStatusSucceeded = "succeeded" // 执行成功 - AgentCommandStatusFailed = "failed" // 执行失败 - AgentCommandStatusTimeout = "timeout" // 超时未完成 + AgentCommandStatusSucceeded = "succeeded" // 执行成功 + AgentCommandStatusFailed = "failed" // 执行失败 + AgentCommandStatusTimeout = "timeout" // 超时未完成 ) // AgentCommand 类型常量 @@ -36,20 +36,20 @@ const ( ) // AgentCommand 代表 Master 发给某个 Agent 节点的待执行命令。 -// 使用简单的数据库队列实现:Agent 通过 token 长轮询拉取本节点 pending 命令, +// 使用简单的数据库队列实现:Agent 通过 token 定期轮询本节点 pending 命令, // 执行后回写状态与结果。Master 侧通过定时检查把超时的命令标记为 timeout。 type AgentCommand struct { - ID uint `gorm:"primaryKey" json:"id"` - NodeID uint `gorm:"column:node_id;index;not null" json:"nodeId"` - Type string `gorm:"size:32;index;not null" json:"type"` - Status string `gorm:"size:20;index;not null;default:'pending'" json:"status"` - Payload string `gorm:"type:text" json:"payload"` // JSON - Result string `gorm:"type:text" json:"result"` // JSON(成功结果) - ErrorMessage string `gorm:"column:error_message;type:text" json:"errorMessage"` - DispatchedAt *time.Time `gorm:"column:dispatched_at" json:"dispatchedAt,omitempty"` - CompletedAt *time.Time `gorm:"column:completed_at" json:"completedAt,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID uint `gorm:"primaryKey" json:"id"` + NodeID uint `gorm:"column:node_id;not null;index:idx_agent_commands_node_status,priority:1" json:"nodeId"` + Type string `gorm:"size:32;index;not null" json:"type"` + Status string `gorm:"size:20;not null;default:'pending';index:idx_agent_commands_node_status,priority:2;index:idx_agent_commands_status_dispatched,priority:1;index:idx_agent_commands_status_created,priority:1" json:"status"` + Payload string `gorm:"type:text" json:"payload"` // JSON + Result string `gorm:"type:text" json:"result"` // JSON(成功结果) + ErrorMessage string `gorm:"column:error_message;type:text" json:"errorMessage"` + DispatchedAt *time.Time `gorm:"column:dispatched_at;index:idx_agent_commands_status_dispatched,priority:2" json:"dispatchedAt,omitempty"` + CompletedAt *time.Time `gorm:"column:completed_at" json:"completedAt,omitempty"` + CreatedAt time.Time `gorm:"index:idx_agent_commands_status_created,priority:2" json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (AgentCommand) TableName() string { diff --git a/server/internal/model/agent_install_token.go b/server/internal/model/agent_install_token.go index f211d7e..ed6b36b 100644 --- a/server/internal/model/agent_install_token.go +++ b/server/internal/model/agent_install_token.go @@ -6,17 +6,21 @@ import "time" // // 生命周期:创建 → 消费(ConsumedAt 非空即作废)→ 超过 ExpiresAt 后被 GC 硬删除。 type AgentInstallToken struct { - ID uint `gorm:"primaryKey" json:"id"` - Token string `gorm:"size:64;uniqueIndex;not null" json:"token"` - NodeID uint `gorm:"not null;index" json:"nodeId"` - Mode string `gorm:"size:16;not null" json:"mode"` // systemd|docker|foreground - Arch string `gorm:"size:16;not null" json:"arch"` // amd64|arm64|auto - AgentVer string `gorm:"size:32;not null" json:"agentVersion"` - DownloadSrc string `gorm:"size:16;not null;default:'github'" json:"downloadSrc"` - ExpiresAt time.Time `gorm:"not null;index" json:"expiresAt"` - ConsumedAt *time.Time `json:"consumedAt,omitempty"` - CreatedByID uint `gorm:"not null" json:"createdById"` - CreatedAt time.Time `json:"createdAt"` + ID uint `gorm:"primaryKey" json:"id"` + Token string `gorm:"size:64;uniqueIndex;not null" json:"token"` + NodeID uint `gorm:"not null;index" json:"nodeId"` + Mode string `gorm:"size:16;not null" json:"mode"` // systemd|docker|foreground + Arch string `gorm:"size:16;not null" json:"arch"` // amd64|arm64|auto + AgentVer string `gorm:"size:32;not null" json:"agentVersion"` + DownloadSrc string `gorm:"size:16;not null;default:'github'" json:"downloadSrc"` + // AgentMasterURL 可覆盖公开安装地址,支持代理或 SSH 隧道后的节点专用入口。 + AgentMasterURL string `gorm:"size:2048" json:"agentMasterUrl,omitempty"` + ProxyURL string `gorm:"size:2048" json:"proxyUrl,omitempty"` + CACertFile string `gorm:"size:512" json:"caCertFile,omitempty"` + ExpiresAt time.Time `gorm:"not null;index" json:"expiresAt"` + ConsumedAt *time.Time `json:"consumedAt,omitempty"` + CreatedByID uint `gorm:"not null" json:"createdById"` + CreatedAt time.Time `json:"createdAt"` } func (AgentInstallToken) TableName() string { return "agent_install_tokens" } diff --git a/server/internal/repository/agent_command_repository_test.go b/server/internal/repository/agent_command_repository_test.go index b9689e9..eaf1c8d 100644 --- a/server/internal/repository/agent_command_repository_test.go +++ b/server/internal/repository/agent_command_repository_test.go @@ -17,12 +17,30 @@ func newTestDB(t *testing.T) *gorm.DB { if err != nil { t.Fatalf("open: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("get sql database: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) if err := db.AutoMigrate(&model.AgentCommand{}); err != nil { t.Fatalf("migrate: %v", err) } return db } +func TestAgentCommandQueueIndexes(t *testing.T) { + db := newTestDB(t) + for _, name := range []string{ + "idx_agent_commands_node_status", + "idx_agent_commands_status_dispatched", + "idx_agent_commands_status_created", + } { + if !db.Migrator().HasIndex(&model.AgentCommand{}, name) { + t.Fatalf("missing Agent command queue index %s", name) + } + } +} + func TestAgentCommandRepository_ClaimPending(t *testing.T) { db := newTestDB(t) repo := NewAgentCommandRepository(db) diff --git a/server/internal/repository/agent_install_token_repository_test.go b/server/internal/repository/agent_install_token_repository_test.go index b7c6b26..fef32ef 100644 --- a/server/internal/repository/agent_install_token_repository_test.go +++ b/server/internal/repository/agent_install_token_repository_test.go @@ -20,6 +20,11 @@ func openTestInstallTokenDB(t *testing.T) *gorm.DB { if err != nil { t.Fatalf("open: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("get sql database: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) if err := db.AutoMigrate(&model.AgentInstallToken{}); err != nil { t.Fatalf("migrate: %v", err) } diff --git a/server/internal/repository/backup_record_repository_test.go b/server/internal/repository/backup_record_repository_test.go index c7765e0..48cfb55 100644 --- a/server/internal/repository/backup_record_repository_test.go +++ b/server/internal/repository/backup_record_repository_test.go @@ -22,6 +22,11 @@ func newBackupRecordTestRepository(t *testing.T) *GormBackupRecordRepository { if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) storageTarget := &model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"} if err := db.Create(storageTarget).Error; err != nil { t.Fatalf("seed storage target error: %v", err) diff --git a/server/internal/repository/backup_task_repository_test.go b/server/internal/repository/backup_task_repository_test.go index aea21db..b528b8d 100644 --- a/server/internal/repository/backup_task_repository_test.go +++ b/server/internal/repository/backup_task_repository_test.go @@ -21,6 +21,11 @@ func newBackupTaskTestRepository(t *testing.T) *GormBackupTaskRepository { if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) if err := db.Create(&model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"}).Error; err != nil { t.Fatalf("seed storage target error: %v", err) } diff --git a/server/internal/repository/node_repository_test.go b/server/internal/repository/node_repository_test.go index 9c7ed2a..e73b14e 100644 --- a/server/internal/repository/node_repository_test.go +++ b/server/internal/repository/node_repository_test.go @@ -19,6 +19,11 @@ func openTestNodeDB(t *testing.T) *gorm.DB { if err != nil { t.Fatalf("open sqlite: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("get sql database: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) if err := db.AutoMigrate(&model.Node{}); err != nil { t.Fatalf("migrate: %v", err) } diff --git a/server/internal/repository/notification_repository_test.go b/server/internal/repository/notification_repository_test.go index 053c25d..01e615f 100644 --- a/server/internal/repository/notification_repository_test.go +++ b/server/internal/repository/notification_repository_test.go @@ -21,6 +21,11 @@ func newNotificationTestRepository(t *testing.T) *GormNotificationRepository { if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) return NewNotificationRepository(db) } diff --git a/server/internal/repository/oauth_session_repository_test.go b/server/internal/repository/oauth_session_repository_test.go index 6f1185a..43d79c4 100644 --- a/server/internal/repository/oauth_session_repository_test.go +++ b/server/internal/repository/oauth_session_repository_test.go @@ -22,6 +22,11 @@ func newOAuthSessionTestRepository(t *testing.T) *GormOAuthSessionRepository { if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) return NewOAuthSessionRepository(db) } diff --git a/server/internal/repository/restore_record_repository_test.go b/server/internal/repository/restore_record_repository_test.go index f51b237..621fedf 100644 --- a/server/internal/repository/restore_record_repository_test.go +++ b/server/internal/repository/restore_record_repository_test.go @@ -22,6 +22,11 @@ func newRestoreRecordTestRepository(t *testing.T) (*GormRestoreRecordRepository, if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) storageTarget := &model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"} if err := db.Create(storageTarget).Error; err != nil { t.Fatalf("seed storage target error: %v", err) diff --git a/server/internal/repository/storage_target_repository_test.go b/server/internal/repository/storage_target_repository_test.go index 018dfe6..139f1f3 100644 --- a/server/internal/repository/storage_target_repository_test.go +++ b/server/internal/repository/storage_target_repository_test.go @@ -26,6 +26,11 @@ func newStorageTestRepository(t *testing.T) *GormStorageTargetRepository { if err != nil { t.Fatalf("database.Open returned error: %v", err) } + sqlDB, err := db.DB() + if err != nil { + t.Fatalf("db.DB returned error: %v", err) + } + t.Cleanup(func() { _ = sqlDB.Close() }) return NewStorageTargetRepository(db) } diff --git a/server/internal/service/install_token_service.go b/server/internal/service/install_token_service.go index e2e97d9..27a5481 100644 --- a/server/internal/service/install_token_service.go +++ b/server/internal/service/install_token_service.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "encoding/hex" "fmt" + "net/url" "strings" "time" @@ -27,13 +28,16 @@ func NewInstallTokenService(repo repository.AgentInstallTokenRepository, nodeRep // InstallTokenInput 生成一次性安装令牌的输入。 type InstallTokenInput struct { - NodeID uint - Mode string - Arch string - AgentVersion string - DownloadSrc string - TTLSeconds int - CreatedByID uint + NodeID uint + Mode string + Arch string + AgentVersion string + DownloadSrc string + TTLSeconds int + CreatedByID uint + AgentMasterURL string + ProxyURL string + CACertFile string } // InstallTokenOutput 生成结果。 @@ -112,14 +116,17 @@ func (s *InstallTokenService) Create(ctx context.Context, in InstallTokenInput) } expiresAt := time.Now().UTC().Add(time.Duration(in.TTLSeconds) * time.Second) record := &model.AgentInstallToken{ - Token: token, - NodeID: in.NodeID, - Mode: in.Mode, - Arch: in.Arch, - AgentVer: in.AgentVersion, - DownloadSrc: in.DownloadSrc, - ExpiresAt: expiresAt, - CreatedByID: in.CreatedByID, + Token: token, + NodeID: in.NodeID, + Mode: in.Mode, + Arch: in.Arch, + AgentVer: in.AgentVersion, + DownloadSrc: in.DownloadSrc, + AgentMasterURL: strings.TrimRight(strings.TrimSpace(in.AgentMasterURL), "/"), + ProxyURL: strings.TrimSpace(in.ProxyURL), + CACertFile: strings.TrimSpace(in.CACertFile), + ExpiresAt: expiresAt, + CreatedByID: in.CreatedByID, } if err := s.repo.Create(ctx, record); err != nil { return nil, err @@ -130,9 +137,15 @@ func (s *InstallTokenService) Create(ctx context.Context, in InstallTokenInput) // CreateCommand 创建 install token,并返回 UI 展示安装命令所需的 URL 与嵌入式脚本。 func (s *InstallTokenService) CreateCommand(ctx context.Context, in InstallCommandInput) (*InstallCommandOutput, error) { masterURL := strings.TrimRight(strings.TrimSpace(in.MasterURL), "/") - if masterURL == "" { - return nil, apperror.BadRequest("INSTALL_TOKEN_INVALID", "masterURL 必填", nil) + deliveryURL, parseErr := url.Parse(masterURL) + if masterURL == "" || parseErr != nil || (deliveryURL.Scheme != "http" && deliveryURL.Scheme != "https") || deliveryURL.Host == "" || deliveryURL.User != nil || deliveryURL.RawQuery != "" || deliveryURL.Fragment != "" || strings.ContainsAny(masterURL, " \t\r\n\"'`$\\") { + return nil, apperror.BadRequest("INSTALL_TOKEN_INVALID", "masterURL 必须是安全的完整 HTTP(S) 地址", parseErr) } + agentMasterURL := strings.TrimRight(strings.TrimSpace(in.AgentMasterURL), "/") + if agentMasterURL == "" { + agentMasterURL = masterURL + } + in.AgentMasterURL = agentMasterURL if err := s.validate(in.InstallTokenInput); err != nil { return nil, err } @@ -144,12 +157,15 @@ func (s *InstallTokenService) CreateCommand(ctx context.Context, in InstallComma return nil, apperror.New(404, "NODE_NOT_FOUND", "节点不存在", nil) } if _, err := renderInstallCommandScript(masterURL, node, &model.AgentInstallToken{ - Mode: in.Mode, - Arch: in.Arch, - AgentVer: in.AgentVersion, - DownloadSrc: in.DownloadSrc, + Mode: in.Mode, + Arch: in.Arch, + AgentVer: in.AgentVersion, + DownloadSrc: in.DownloadSrc, + AgentMasterURL: in.AgentMasterURL, + ProxyURL: in.ProxyURL, + CACertFile: in.CACertFile, }); err != nil { - return nil, err + return nil, apperror.BadRequest("INSTALL_TOKEN_INVALID", "Agent 连接配置无效", err) } out, err := s.Create(ctx, in.InstallTokenInput) if err != nil { @@ -164,20 +180,24 @@ func (s *InstallTokenService) CreateCommand(ctx context.Context, in InstallComma ExpiresAt: out.ExpiresAt, Node: out.Node, Record: out.Record, - URL: masterURL + "/api/install/" + out.Token, - FallbackURL: masterURL + "/install/" + out.Token, + URL: agentMasterURL + "/api/install/" + out.Token, + FallbackURL: agentMasterURL + "/install/" + out.Token, ScriptBase64: base64.StdEncoding.EncodeToString([]byte(script)), } if out.Record.Mode == model.InstallModeDocker { - result.ComposeURL = masterURL + "/api/install/" + out.Token + "/compose.yml" - result.FallbackComposeURL = masterURL + "/install/" + out.Token + "/compose.yml" + result.ComposeURL = agentMasterURL + "/api/install/" + out.Token + "/compose.yml" + result.FallbackComposeURL = agentMasterURL + "/install/" + out.Token + "/compose.yml" } return result, nil } func renderInstallCommandScript(masterURL string, node *model.Node, record *model.AgentInstallToken) (string, error) { + agentMasterURL := strings.TrimRight(strings.TrimSpace(record.AgentMasterURL), "/") + if agentMasterURL == "" { + agentMasterURL = masterURL + } return installscript.RenderScript(installscript.Context{ - MasterURL: masterURL, + MasterURL: agentMasterURL, AgentToken: node.Token, AgentVersion: record.AgentVer, Mode: record.Mode, @@ -185,6 +205,8 @@ func renderInstallCommandScript(masterURL string, node *model.Node, record *mode DownloadBase: installscript.DownloadBaseFor(record.DownloadSrc), InstallPrefix: "/opt/backupx-agent", NodeID: node.ID, + ProxyURL: record.ProxyURL, + CACertFile: record.CACertFile, }) } @@ -259,6 +281,9 @@ func (s *InstallTokenService) validate(in InstallTokenInput) error { return apperror.BadRequest("INSTALL_TOKEN_INVALID", fmt.Sprintf("ttlSeconds 需在 %d-%d", InstallTokenMinTTL, InstallTokenMaxTTL), nil) } + if len(in.AgentMasterURL) > 2048 || len(in.ProxyURL) > 2048 || len(in.CACertFile) > 512 { + return apperror.BadRequest("INSTALL_TOKEN_INVALID", "连接配置过长", nil) + } return nil } diff --git a/server/internal/service/install_token_service_test.go b/server/internal/service/install_token_service_test.go index 5ac0789..e9a7bf0 100644 --- a/server/internal/service/install_token_service_test.go +++ b/server/internal/service/install_token_service_test.go @@ -2,7 +2,9 @@ package service import ( "context" + "encoding/base64" "path/filepath" + "strings" "testing" "time" @@ -177,13 +179,16 @@ func TestInstallTokenServiceCreateCommandBuildsURLsAndScript(t *testing.T) { out, err := svc.CreateCommand(context.Background(), InstallCommandInput{ InstallTokenInput: InstallTokenInput{ - NodeID: node.ID, - Mode: model.InstallModeDocker, - Arch: model.InstallArchAuto, - AgentVersion: "v1.7.0", - DownloadSrc: model.InstallSourceGitHub, - TTLSeconds: 900, - CreatedByID: 1, + NodeID: node.ID, + Mode: model.InstallModeDocker, + Arch: model.InstallArchAuto, + AgentVersion: "v1.7.0", + DownloadSrc: model.InstallSourceGitHub, + TTLSeconds: 900, + CreatedByID: 1, + AgentMasterURL: "http://127.0.0.1:18340", + ProxyURL: "socks5h://127.0.0.1:1080", + CACertFile: "/etc/pki/internal-ca.pem", }, MasterURL: "https://public.example.com/base", }) @@ -193,15 +198,66 @@ func TestInstallTokenServiceCreateCommandBuildsURLsAndScript(t *testing.T) { if out.Token == "" || out.ScriptBase64 == "" { t.Fatalf("missing token or script: %+v", out) } - if out.URL != "https://public.example.com/base/api/install/"+out.Token { + if out.URL != "http://127.0.0.1:18340/api/install/"+out.Token { t.Fatalf("bad url: %s", out.URL) } - if out.FallbackURL != "https://public.example.com/base/install/"+out.Token { + if out.FallbackURL != "http://127.0.0.1:18340/install/"+out.Token { t.Fatalf("bad fallback url: %s", out.FallbackURL) } - if out.ComposeURL != "https://public.example.com/base/api/install/"+out.Token+"/compose.yml" { + if out.ComposeURL != "http://127.0.0.1:18340/api/install/"+out.Token+"/compose.yml" { t.Fatalf("bad compose url: %s", out.ComposeURL) } + script, err := base64.StdEncoding.DecodeString(out.ScriptBase64) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{ + `MASTER_URL="http://127.0.0.1:18340"`, + `PROXY_URL="socks5h://127.0.0.1:1080"`, + `CA_CERT_FILE="/etc/pki/internal-ca.pem"`, + } { + if !strings.Contains(string(script), want) { + t.Fatalf("script missing %q", want) + } + } +} + +func TestInstallTokenServiceRejectsUnsafeConnectionBeforeCreate(t *testing.T) { + db := openInstallTokenTestDB(t) + nodeRepo := repository.NewNodeRepository(db) + node := &model.Node{Name: "restricted", Token: "deadbeefcafebabe0123456789abcdef0123456789abcdef0123456789abcdef"} + if err := nodeRepo.Create(context.Background(), node); err != nil { + t.Fatal(err) + } + tokenRepo := repository.NewAgentInstallTokenRepository(db) + svc := NewInstallTokenService(tokenRepo, nodeRepo) + _, err := svc.CreateCommand(context.Background(), InstallCommandInput{ + InstallTokenInput: InstallTokenInput{ + NodeID: node.ID, Mode: model.InstallModeSystemd, Arch: model.InstallArchAuto, + AgentVersion: "v2.4.0", DownloadSrc: model.InstallSourceGitHub, TTLSeconds: 900, + ProxyURL: "http://user:pass@proxy.example.com", + }, + MasterURL: "https://public.example.com", + }) + if err == nil { + t.Fatal("expected proxy credentials to be rejected") + } + count, countErr := tokenRepo.CountCreatedSince(context.Background(), node.ID, time.Now().UTC().Add(-time.Hour)) + if countErr != nil || count != 0 { + t.Fatalf("invalid connection created token records: count=%d err=%v", count, countErr) + } + + _, err = svc.CreateCommand(context.Background(), InstallCommandInput{ + InstallTokenInput: InstallTokenInput{ + NodeID: node.ID, Mode: model.InstallModeSystemd, Arch: model.InstallArchAuto, + AgentVersion: "v2.4.0", DownloadSrc: model.InstallSourceGitHub, TTLSeconds: 900, + AgentMasterURL: "https://master.internal", + }, + MasterURL: "http://public.example.com/$unsafe", + }) + if err == nil { + t.Fatal("expected unsafe public delivery URL to be rejected") + } } func TestInstallTokenServiceRateLimit(t *testing.T) { diff --git a/web/package-lock.json b/web/package-lock.json index 85581c7..06c1c20 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -9,14 +9,14 @@ "version": "0.1.0", "dependencies": { "@arco-design/web-react": "^2.66.0", - "axios": "^1.16.0", - "echarts": "^6.0.0", + "axios": "^1.19.0", + "echarts": "^6.1.0", "echarts-for-react": "^3.0.6", "i18next": "^25.8.14", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^16.5.6", - "react-router-dom": "^6.30.0", + "react-router-dom": "^6.30.4", "zustand": "^5.0.3" }, "devDependencies": { @@ -29,8 +29,8 @@ "@vitejs/plugin-react": "^4.3.4", "jsdom": "^26.0.0", "typescript": "^5.7.3", - "vite": "^6.4.2", - "vitest": "^3.0.8" + "vite": "^6.4.3", + "vitest": "^3.2.7" } }, "node_modules/@adobe/css-tools": { @@ -97,13 +97,13 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "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.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -112,9 +112,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "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": { @@ -122,21 +122,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "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", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@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", @@ -153,14 +153,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -170,14 +170,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "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.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@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" @@ -187,9 +187,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "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": { @@ -197,29 +197,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "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.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "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.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -239,9 +239,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -249,9 +249,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -259,9 +259,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "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": { @@ -269,27 +269,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "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.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -340,33 +340,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "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.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@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.0", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -374,14 +374,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -995,9 +995,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -1505,7 +1505,7 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", @@ -1516,7 +1516,7 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" @@ -1588,15 +1588,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -1605,13 +1605,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "3.2.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -1632,9 +1632,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1645,13 +1645,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", + "@vitest/utils": "3.2.7", "pathe": "^2.0.3", "strip-literal": "^3.0.0" }, @@ -1660,13 +1660,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.7", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -1675,9 +1675,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1688,13 +1688,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.7", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, @@ -1749,7 +1749,7 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", @@ -1759,21 +1759,47 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/b-tween": { "version": "0.3.3", "resolved": "https://registry.npmmirror.com/b-tween/-/b-tween-0.3.3.tgz", @@ -1787,9 +1813,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1800,9 +1826,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "dev": true, "funding": [ { @@ -1820,11 +1846,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -1845,7 +1871,7 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { @@ -1857,9 +1883,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001777", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", - "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -1879,7 +1905,7 @@ }, "node_modules/chai": { "version": "5.3.3", - "resolved": "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", @@ -1896,7 +1922,7 @@ }, "node_modules/check-error": { "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", @@ -1941,7 +1967,7 @@ }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { @@ -2015,7 +2041,6 @@ "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2038,7 +2063,7 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", @@ -2048,7 +2073,7 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { @@ -2091,7 +2116,7 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { @@ -2104,13 +2129,13 @@ } }, "node_modules/echarts": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", - "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "license": "Apache-2.0", "dependencies": { "tslib": "2.3.0", - "zrender": "6.0.0" + "zrender": "6.1.0" } }, "node_modules/echarts-for-react": { @@ -2134,9 +2159,9 @@ "license": "0BSD" }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.402", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz", + "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==", "dev": true, "license": "ISC" }, @@ -2155,7 +2180,7 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { @@ -2164,7 +2189,7 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { @@ -2179,9 +2204,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2192,7 +2217,7 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { @@ -2249,7 +2274,7 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", @@ -2259,7 +2284,7 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", @@ -2334,16 +2359,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -2366,7 +2391,7 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { @@ -2385,7 +2410,7 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { @@ -2409,7 +2434,7 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { @@ -2422,7 +2447,7 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { @@ -2434,7 +2459,7 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { @@ -2446,7 +2471,7 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { @@ -2460,9 +2485,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2636,7 +2661,7 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", @@ -2680,14 +2705,14 @@ }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", @@ -2708,7 +2733,7 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", @@ -2718,7 +2743,7 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { @@ -2727,7 +2752,7 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { @@ -2736,7 +2761,7 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { @@ -2760,13 +2785,12 @@ "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -2783,11 +2807,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/number-precision": { "version": "1.6.0", @@ -2833,7 +2860,7 @@ }, "node_modules/pathval": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", @@ -2862,9 +2889,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -2882,7 +2909,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3054,12 +3081,12 @@ } }, "node_modules/react-router": { - "version": "6.30.3", - "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.3.tgz", - "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2" + "@remix-run/router": "1.23.3" }, "engines": { "node": ">=14.0.0" @@ -3069,13 +3096,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.3", - "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.3.tgz", - "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2", - "react-router": "6.30.3" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" }, "engines": { "node": ">=14.0.0" @@ -3213,7 +3240,7 @@ }, "node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", @@ -3288,7 +3315,7 @@ }, "node_modules/strip-literal": { "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", @@ -3301,7 +3328,7 @@ }, "node_modules/strip-literal/node_modules/js-tokens": { "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" @@ -3356,7 +3383,7 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", @@ -3366,7 +3393,7 @@ }, "node_modules/tinyspy": { "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/tinyspy/-/tinyspy-4.0.4.tgz", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", @@ -3448,9 +3475,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", + "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==", "dev": true, "funding": [ { @@ -3531,9 +3558,9 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -3629,20 +3656,20 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -3672,8 +3699,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", "happy-dom": "*", "jsdom": "*" }, @@ -3828,15 +3855,15 @@ }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/zrender": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", - "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "license": "BSD-3-Clause", "dependencies": { "tslib": "2.3.0" @@ -3844,7 +3871,7 @@ }, "node_modules/zrender/node_modules/tslib": { "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" }, diff --git a/web/package.json b/web/package.json index 72b48b5..40b14ac 100644 --- a/web/package.json +++ b/web/package.json @@ -11,14 +11,14 @@ }, "dependencies": { "@arco-design/web-react": "^2.66.0", - "axios": "^1.16.0", - "echarts": "^6.0.0", + "axios": "^1.19.0", + "echarts": "^6.1.0", "echarts-for-react": "^3.0.6", "i18next": "^25.8.14", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^16.5.6", - "react-router-dom": "^6.30.0", + "react-router-dom": "^6.30.4", "zustand": "^5.0.3" }, "devDependencies": { @@ -31,7 +31,7 @@ "@vitejs/plugin-react": "^4.3.4", "jsdom": "^26.0.0", "typescript": "^5.7.3", - "vite": "^6.4.2", - "vitest": "^3.0.8" + "vite": "^6.4.3", + "vitest": "^3.2.7" } } diff --git a/web/src/pages/nodes/AgentInstallWizard.tsx b/web/src/pages/nodes/AgentInstallWizard.tsx index c237200..a14abfd 100644 --- a/web/src/pages/nodes/AgentInstallWizard.tsx +++ b/web/src/pages/nodes/AgentInstallWizard.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useRef, useState } from 'react' import { Modal, Steps, Button, Space, Message, Spin } from '@arco-design/web-react' import { Step1NodeName, type Mode } from './wizard/Step1NodeName' -import { Step2DeployOptions, type DeployOptions } from './wizard/Step2DeployOptions' +import { Step2DeployOptions, isReleaseVersion, type DeployOptions } from './wizard/Step2DeployOptions' import { Step3CommandPreview } from './wizard/Step3CommandPreview' import { BatchCommandTable, type BatchCommandRow } from './BatchCommandTable' -import type { InstallTokenResult } from '../../types/nodes' +import type { InstallTokenInput, InstallTokenResult } from '../../types/nodes' import { useAgentDeployFlow, type AgentDeployRow } from './useAgentDeployFlow' +import { validateAgentConnection } from './wizard/AgentConnectionOptions' const Step = Steps.Step @@ -29,15 +30,19 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, const [deploy, setDeploy] = useState({ mode: 'systemd', arch: 'auto', - agentVersion: masterVersion || '', + agentVersion: isReleaseVersion(masterVersion) ? masterVersion || '' : '', downloadSrc: 'github', ttlSeconds: 900, + connectionMode: 'direct', + agentMasterUrl: '', + proxyUrl: '', + caCertFile: '', }) // 当父组件异步拿到 masterVersion 后,同步到 deploy.agentVersion(仅初始为空时) useEffect(() => { - if (masterVersion && !deploy.agentVersion) { - setDeploy((prev) => ({ ...prev, agentVersion: masterVersion })) + if (isReleaseVersion(masterVersion) && !deploy.agentVersion) { + setDeploy((prev) => ({ ...prev, agentVersion: masterVersion as string })) } }, [masterVersion]) // eslint-disable-line react-hooks/exhaustive-deps @@ -98,17 +103,23 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, Message.warning('请填写 Agent 版本号(形如 v1.7.0)') return } + const connectionError = validateAgentConnection(deploy) + if (connectionError) { + Message.warning(connectionError) + return + } + const installInput = toInstallTokenInput(deploy) setSubmitting(true) try { if (fixedNode) { - const result = await deployFlow.submitExistingNode(fixedNode, deploy) + const result = await deployFlow.submitExistingNode(fixedNode, installInput) applySingleOrTableResult(result.rows, fixedNode) } else if (mode === 'single') { - const result = await deployFlow.submitNewNodes([singleName.trim()], deploy) + const result = await deployFlow.submitNewNodes([singleName.trim()], installInput) applySingleOrTableResult(result.rows) } else { const names = parseBatchNames() - const result = await deployFlow.submitNewNodes(names, deploy) + const result = await deployFlow.submitNewNodes(names, installInput) if (mountedRef.current) setBatchRows(toBatchRows(result.rows)) if (result.status === 'partialFailed') { Message.warning('部分节点安装命令生成失败,可在结果表中查看') @@ -127,7 +138,7 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, if (!singleNodeInfo) return setSubmitting(true) try { - const row = await deployFlow.regenerateNode(singleNodeInfo, deploy) + const row = await deployFlow.regenerateNode(singleNodeInfo, toInstallTokenInput(deploy)) if (row.status === 'ready' && row.installToken) { setSingleToken(row.installToken) } else { @@ -143,7 +154,7 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, const retryBatchNode = async (row: BatchCommandRow) => { setSubmitting(true) try { - const next = await deployFlow.regenerateNode({ id: row.nodeId, name: row.nodeName }, deploy) + const next = await deployFlow.regenerateNode({ id: row.nodeId, name: row.nodeName }, toInstallTokenInput(deploy)) setBatchRows((rows) => rows.map((item) => ( item.nodeId === row.nodeId ? toBatchRows([next])[0] : item ))) @@ -164,6 +175,9 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, arch: deploy.arch, agentVersion: deploy.agentVersion, downloadSrc: deploy.downloadSrc, + agentMasterUrl: deploy.connectionMode === 'restricted' ? deploy.agentMasterUrl.trim() : '', + proxyUrl: deploy.connectionMode === 'restricted' ? deploy.proxyUrl.trim() : '', + caCertFile: deploy.connectionMode === 'restricted' ? deploy.caCertFile.trim() : '', } // fixedNode 路径下步骤只有 2 步(部署参数 + 安装命令),step 值从 1 开始, @@ -236,7 +250,6 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, nodeId={singleNodeInfo.id} nodeName={singleNodeInfo.name} token={singleToken} - mode={deploy.mode} previewParams={previewParams} onRegenerate={regenerateSingle} /> @@ -268,6 +281,19 @@ export function AgentInstallWizard({ visible, onClose, onSuccess, masterVersion, } } +function toInstallTokenInput(deploy: DeployOptions): InstallTokenInput { + return { + mode: deploy.mode, + arch: deploy.arch, + agentVersion: deploy.agentVersion.trim(), + downloadSrc: deploy.downloadSrc, + ttlSeconds: deploy.ttlSeconds, + agentMasterUrl: deploy.connectionMode === 'restricted' ? deploy.agentMasterUrl.trim() : undefined, + proxyUrl: deploy.connectionMode === 'restricted' ? deploy.proxyUrl.trim() : undefined, + caCertFile: deploy.connectionMode === 'restricted' ? deploy.caCertFile.trim() : undefined, + } +} + function toBatchRows(rows: AgentDeployRow[]): BatchCommandRow[] { return rows.map((row) => ({ nodeId: row.nodeId, diff --git a/web/src/pages/nodes/BatchCommandTable.tsx b/web/src/pages/nodes/BatchCommandTable.tsx index c724e45..badd4fc 100644 --- a/web/src/pages/nodes/BatchCommandTable.tsx +++ b/web/src/pages/nodes/BatchCommandTable.tsx @@ -81,7 +81,7 @@ export function BatchCommandTable({ rows, onRetryNode }: Props) { } return ( {cmd as string} diff --git a/web/src/pages/nodes/NodesPage.tsx b/web/src/pages/nodes/NodesPage.tsx index bdcd079..79c4dd5 100644 --- a/web/src/pages/nodes/NodesPage.tsx +++ b/web/src/pages/nodes/NodesPage.tsx @@ -121,7 +121,7 @@ export default function NodesPage() { 新 Token(24 小时内新旧 Token 均可认证,便于滚动替换): - + {newToken}
@@ -138,7 +138,7 @@ export default function NodesPage() { render: (name: string, record: NodeSummary) => ( {record.isLocal ? : } - {name} + {name} {record.isLocal && 本机} ), diff --git a/web/src/pages/nodes/installCommands.test.ts b/web/src/pages/nodes/installCommands.test.ts index 27a0895..4b569d1 100644 --- a/web/src/pages/nodes/installCommands.test.ts +++ b/web/src/pages/nodes/installCommands.test.ts @@ -17,21 +17,32 @@ describe('install command builders', () => { 'https://master.example.com/install/abc', ) - expect(cmd).toContain('/tmp/bx-agent-install.sh') + expect(cmd).toContain('mktemp /tmp/bx-agent-install.XXXXXX') expect(cmd).toContain("'https://master.example.com/install/abc'") expect(cmd).toContain('non-script content') + expect(cmd).toContain('umask 077') + expect(cmd).toContain('rm -f "$tmp"') }) - it('keeps URL install command as primary even when embedded script is available', () => { + it('keeps the one-time URL as the primary install command', () => { const cmd = buildAgentInstallCommand( 'https://master.example.com/api/install/abc', 'https://master.example.com/install/abc', - 'IyEvYmluL3NoCg==', ) expect(cmd).toContain('https://master.example.com/api/install/abc') expect(cmd).toContain('https://master.example.com/install/abc') - expect(cmd).not.toContain('IyEvYmluL3NoCg==') + }) + + it('binds proxy and private CA settings to installer downloads', () => { + const cmd = buildAgentInstallCommand( + 'https://master.internal/api/install/abc', + undefined, + { proxyUrl: 'socks5h://127.0.0.1:1080', caCertFile: '/etc/backupx-agent/ca.pem' }, + ) + + expect(cmd).toContain("--proxy 'socks5h://127.0.0.1:1080'") + expect(cmd).toContain("--cacert '/etc/backupx-agent/ca.pem'") }) it('builds embedded fallback command explicitly', () => { diff --git a/web/src/pages/nodes/installCommands.ts b/web/src/pages/nodes/installCommands.ts index 5203c7c..1646dcb 100644 --- a/web/src/pages/nodes/installCommands.ts +++ b/web/src/pages/nodes/installCommands.ts @@ -12,16 +12,34 @@ function runScriptCommand(path: string) { return `if [ "$(id -u)" -eq 0 ]; then sh ${path}; else sudo sh ${path}; fi` } -export function buildAgentInstallCommand(url: string, fallbackUrl?: string, _scriptBase64?: string) { +export interface InstallFetchOptions { + proxyUrl?: string + caCertFile?: string +} + +function curlFetch(url: string, destination: string, options: InstallFetchOptions) { + const args = ['curl', '-fsS'] + if (options.proxyUrl?.trim()) { + args.push('--proxy', shellQuote(options.proxyUrl.trim())) + } + if (options.caCertFile?.trim()) { + args.push('--cacert', shellQuote(options.caCertFile.trim())) + } + args.push(shellQuote(url), '-o', destination) + return args.join(' ') +} + +export function buildAgentInstallCommand(url: string, fallbackUrl?: string, options: InstallFetchOptions = {}) { const primary = url.trim() const fallback = (fallbackUrl || legacyInstallUrl(primary)).trim() const urls = fallback && fallback !== primary ? [primary, fallback] : [primary] const marker = shellQuote(INSTALL_MAGIC_MARKER) const fetchScript = urls.length > 1 - ? `(curl -fsSL ${shellQuote(urls[0])} -o "$tmp" && grep -q ${marker} "$tmp" || curl -fsSL ${shellQuote(urls[1])} -o "$tmp")` - : `(curl -fsSL ${shellQuote(urls[0])} -o "$tmp" && grep -q ${marker} "$tmp")` + ? `(${curlFetch(urls[0], '"$tmp"', options)} && grep -q ${marker} "$tmp" || ${curlFetch(urls[1], '"$tmp"', options)})` + : `(${curlFetch(urls[0], '"$tmp"', options)} && grep -q ${marker} "$tmp")` return [ + 'umask 077', 'tmp=$(mktemp)', fetchScript, `{ grep -q ${marker} "$tmp" || { echo 'BackupX install endpoint returned non-script content; check reverse proxy /api/install or /install forwarding.' >&2; head -5 "$tmp" >&2; false; }; }`, @@ -29,24 +47,27 @@ export function buildAgentInstallCommand(url: string, fallbackUrl?: string, _scr ].join(' && ') + '; rc=$?; rm -f "$tmp"; test $rc -eq 0' } -export function buildAgentDownloadCommand(url: string, fallbackUrl?: string, _scriptBase64?: string) { +export function buildAgentDownloadCommand(url: string, fallbackUrl?: string, options: InstallFetchOptions = {}) { const primary = url.trim() const fallback = (fallbackUrl || legacyInstallUrl(primary)).trim() const marker = shellQuote(INSTALL_MAGIC_MARKER) const fetchScript = fallback && fallback !== primary - ? `(curl -fsSL ${shellQuote(primary)} -o /tmp/bx-agent-install.sh && grep -q ${marker} /tmp/bx-agent-install.sh || curl -fsSL ${shellQuote(fallback)} -o /tmp/bx-agent-install.sh)` - : `(curl -fsSL ${shellQuote(primary)} -o /tmp/bx-agent-install.sh && grep -q ${marker} /tmp/bx-agent-install.sh)` + ? `(${curlFetch(primary, '"$tmp"', options)} && grep -q ${marker} "$tmp" || ${curlFetch(fallback, '"$tmp"', options)})` + : `(${curlFetch(primary, '"$tmp"', options)} && grep -q ${marker} "$tmp")` return [ + 'umask 077', + 'tmp=$(mktemp /tmp/bx-agent-install.XXXXXX)', fetchScript, - `{ grep -q ${marker} /tmp/bx-agent-install.sh || { echo 'BackupX install endpoint returned non-script content; check reverse proxy /api/install or /install forwarding.' >&2; head -5 /tmp/bx-agent-install.sh >&2; false; }; }`, - runScriptCommand('/tmp/bx-agent-install.sh'), - ].join(' && ') + `{ grep -q ${marker} "$tmp" || { echo 'BackupX install endpoint returned non-script content; check reverse proxy /api/install or /install forwarding.' >&2; head -5 "$tmp" >&2; false; }; }`, + runScriptCommand('"$tmp"'), + ].join(' && ') + '; rc=$?; rm -f "$tmp"; test $rc -eq 0' } export function buildEmbeddedAgentInstallCommand(scriptBase64: string) { const marker = shellQuote(INSTALL_MAGIC_MARKER) return [ + 'umask 077', 'enc=$(mktemp)', 'tmp=$(mktemp)', `printf %s ${shellQuote(scriptBase64.trim())} > "$enc"`, diff --git a/web/src/pages/nodes/useAgentDeployFlow.test.ts b/web/src/pages/nodes/useAgentDeployFlow.test.ts index 93b0c69..5938e63 100644 --- a/web/src/pages/nodes/useAgentDeployFlow.test.ts +++ b/web/src/pages/nodes/useAgentDeployFlow.test.ts @@ -76,6 +76,24 @@ describe('createAgentDeployFlow', () => { }) }) + it('uses restricted-network options in batch install commands', async () => { + const flow = createAgentDeployFlow({ + batchCreateNodes: async () => [{ id: 1, name: 'restricted' }], + createInstallToken: async () => tokenResult({ + url: 'https://master.internal/api/install/install-token', + fallbackUrl: 'https://master.internal/install/install-token', + }), + }) + const result = await flow.submitNewNodes(['restricted'], { + ...deployOptions(), + proxyUrl: 'socks5h://127.0.0.1:1080', + caCertFile: '/etc/backupx-agent/ca.pem', + }) + + expect(result.rows[0].command).toContain("--proxy 'socks5h://127.0.0.1:1080'") + expect(result.rows[0].command).toContain("--cacert '/etc/backupx-agent/ca.pem'") + }) + it('rejects duplicate names before creating nodes', async () => { const flow = createAgentDeployFlow({ batchCreateNodes: async () => { diff --git a/web/src/pages/nodes/useAgentDeployFlow.ts b/web/src/pages/nodes/useAgentDeployFlow.ts index 8c73c3c..bfcc70a 100644 --- a/web/src/pages/nodes/useAgentDeployFlow.ts +++ b/web/src/pages/nodes/useAgentDeployFlow.ts @@ -41,7 +41,7 @@ export function createAgentDeployFlow(deps: AgentDeployFlowDeps) { const issueTokenForNode = async (node: AgentDeployNode, input: InstallTokenInput): Promise => { try { const token = await deps.createInstallToken(node.id, input) - return readyRow(node, token) + return readyRow(node, token, input) } catch (error) { return { nodeId: node.id, @@ -77,12 +77,15 @@ export function useAgentDeployFlow() { return useMemo(() => createAgentDeployFlow({ batchCreateNodes, createInstallToken }), []) } -function readyRow(node: AgentDeployNode, token: InstallTokenResult): AgentDeployRow { +function readyRow(node: AgentDeployNode, token: InstallTokenResult, input: InstallTokenInput): AgentDeployRow { return { nodeId: node.id, nodeName: node.name, status: 'ready', - command: buildAgentInstallCommand(token.url, token.fallbackUrl), + command: buildAgentInstallCommand(token.url, token.fallbackUrl, { + proxyUrl: input.proxyUrl, + caCertFile: input.caCertFile, + }), expiresAt: token.expiresAt, installToken: token, embeddedCommand: token.scriptBase64 diff --git a/web/src/pages/nodes/wizard/AgentConnectionOptions.test.ts b/web/src/pages/nodes/wizard/AgentConnectionOptions.test.ts new file mode 100644 index 0000000..780571c --- /dev/null +++ b/web/src/pages/nodes/wizard/AgentConnectionOptions.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' +import { validateAgentConnection, type AgentConnectionValue } from './AgentConnectionOptions' + +function connection(patch: Partial = {}): AgentConnectionValue { + return { + connectionMode: 'restricted', + agentMasterUrl: '', + proxyUrl: '', + caCertFile: '', + ...patch, + } +} + +describe('validateAgentConnection', () => { + it('accepts direct connectivity without overrides', () => { + expect(validateAgentConnection(connection({ connectionMode: 'direct' }))).toBe('') + }) + + it('accepts an SSH local-forward URL and SOCKS5 proxy', () => { + expect(validateAgentConnection(connection({ agentMasterUrl: 'http://127.0.0.1:18340' }))).toBe('') + expect(validateAgentConnection(connection({ proxyUrl: 'socks5h://127.0.0.1:1080' }))).toBe('') + }) + + it('rejects empty restricted settings and relative CA paths', () => { + expect(validateAgentConnection(connection())).not.toBe('') + expect(validateAgentConnection(connection({ caCertFile: 'internal-ca.pem' }))).not.toBe('') + }) + + it('rejects credentials and shell-unsafe values before submission', () => { + expect(validateAgentConnection(connection({ agentMasterUrl: 'https://user:pass@master.example.com' }))).not.toBe('') + expect(validateAgentConnection(connection({ proxyUrl: 'http://user:pass@proxy.example.com' }))).not.toBe('') + expect(validateAgentConnection(connection({ caCertFile: '/etc/pki/internal ca.pem' }))).not.toBe('') + }) +}) diff --git a/web/src/pages/nodes/wizard/AgentConnectionOptions.tsx b/web/src/pages/nodes/wizard/AgentConnectionOptions.tsx new file mode 100644 index 0000000..d549487 --- /dev/null +++ b/web/src/pages/nodes/wizard/AgentConnectionOptions.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import { Form, Input, Radio, Typography } from '@arco-design/web-react' + +const { Text } = Typography + +export type ConnectionMode = 'direct' | 'restricted' + +export interface AgentConnectionValue { + connectionMode: ConnectionMode + agentMasterUrl: string + proxyUrl: string + caCertFile: string +} + +interface Props { + value: AgentConnectionValue + onChange: (value: AgentConnectionValue) => void +} + +export function AgentConnectionOptions({ value, onChange }: Props) { + const update = (patch: Partial) => onChange({ ...value, ...patch }) + + return ( + <> + Agent 只需主动访问 Master,不需要从 Master 反向开放节点端口。} + > + update({ connectionMode: mode as ConnectionMode })} + options={[ + { label: '直连', value: 'direct' }, + { label: '代理或堡垒机', value: 'restricted' }, + ]} + /> + + + {value.connectionMode === 'restricted' && ( + <> + 可填写经 SSH 本地转发后的地址;留空则继续使用 Master 对外地址。} + > + update({ agentMasterUrl })} + /> + + 支持 http、https、socks5、socks5h;SSH 动态转发可使用 socks5h://127.0.0.1:1080。} + > + update({ proxyUrl })} + /> + + 目标节点上已存在的 PEM 文件绝对路径;安装器会复制到受保护的 Agent 配置目录。} + > + update({ caCertFile })} + /> + + + )} + + ) +} + +export function validateAgentConnection(value: AgentConnectionValue) { + if (value.connectionMode === 'direct') return '' + const agentMasterUrl = value.agentMasterUrl.trim() + const proxyUrl = value.proxyUrl.trim() + const caCertFile = value.caCertFile.trim() + if (!agentMasterUrl && !proxyUrl && !caCertFile) { + return '请至少填写 Agent 连接地址、代理 URL 或私有 CA 路径' + } + if (agentMasterUrl) { + try { + const parsed = new URL(agentMasterUrl) + if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.host || parsed.username || parsed.password || parsed.search || parsed.hash || /\s/.test(agentMasterUrl)) { + return 'Agent 连接地址必须是不含凭据、查询参数和片段的完整 HTTP(S) URL' + } + } catch { + return 'Agent 连接地址必须是完整的 HTTP 或 HTTPS URL' + } + } + if (proxyUrl) { + try { + const parsed = new URL(proxyUrl) + if (!['http:', 'https:', 'socks5:', 'socks5h:'].includes(parsed.protocol) || !parsed.host || parsed.username || parsed.password || (parsed.pathname !== '' && parsed.pathname !== '/') || parsed.search || parsed.hash || /\s/.test(proxyUrl)) { + return '代理 URL 仅支持无凭据、无路径的 http、https、socks5 或 socks5h 地址' + } + } catch { + return '代理 URL 仅支持 http、https、socks5 或 socks5h' + } + } + if (caCertFile && (!/^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/.test(caCertFile) || caCertFile.split('/').some((part) => part === '..'))) { + return '私有 CA 证书必须使用不含空格或特殊字符的绝对路径' + } + return '' +} diff --git a/web/src/pages/nodes/wizard/InstallCommandBlock.tsx b/web/src/pages/nodes/wizard/InstallCommandBlock.tsx new file mode 100644 index 0000000..0dbc0f7 --- /dev/null +++ b/web/src/pages/nodes/wizard/InstallCommandBlock.tsx @@ -0,0 +1,30 @@ +import React, { type ReactNode } from 'react' +import { Button, Space, Typography } from '@arco-design/web-react' +import { IconCopy } from '../../../components/icons' + +const { Text } = Typography + +interface Props { + label?: string + command: string + disabled?: boolean + action?: ReactNode + onCopy: (command: string) => void +} + +export function InstallCommandBlock({ label, command, disabled, action, onCopy }: Props) { + return ( +
+ {label && {label}} + + {command} + +
+ + + {action} + +
+
+ ) +} diff --git a/web/src/pages/nodes/wizard/Step1NodeName.tsx b/web/src/pages/nodes/wizard/Step1NodeName.tsx index 67c592c..13bb788 100644 --- a/web/src/pages/nodes/wizard/Step1NodeName.tsx +++ b/web/src/pages/nodes/wizard/Step1NodeName.tsx @@ -32,7 +32,7 @@ export function Step1NodeName({ {mode === 'single' ? (
- 节点名称 + 节点名称 ) : (
- 节点名称(每行一个,最多 50 个) + 节点名称(每行一个,最多 50 个)