diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ff440fd..81bbc1b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "DataPoly Dev (JDK8 + MySQL8 + PG14)", + "name": "DataPoly Dev (JDK25 + MySQL8 + PG14)", "dockerComposeFile": "docker-compose.yml", "service": "dev", "workspaceFolder": "/workspace", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 9994031..0206e41 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,14 +1,14 @@ version: '3.8' # DataPoly 开发/冒烟环境(Dev Containers): -# dev —— JDK8+Maven 开发容器(仓库挂载 /workspace,复用宿主 ~/.m2 缓存),三节点在本容器内启动 +# dev —— JDK25+Maven 开发容器(仓库挂载 /workspace,复用宿主 ~/.m2 缓存),三节点在本容器内启动 # mysql —— 8.0,mysql_native_password(免三期冒烟时的 ALTER USER 步骤) # postgres —— 14(PG 元库冒烟用) # 用法见 AGENTS.md 第六节「devcontainer 冒烟方法」。 services: dev: - image: maven:3.8.7-eclipse-temurin-8 + image: maven:3.9-eclipse-temurin-25 container_name: datapoly_dev command: sleep infinity volumes: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd8b27d..bbb294b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,21 +14,21 @@ concurrency: jobs: test: - name: Build & Test (JDK 8) + name: Build & Test (JDK 25) runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - - name: Set up JDK 8 + - name: Set up JDK 25 uses: actions/setup-java@v4 with: distribution: temurin - java-version: '8' + java-version: '25' cache: maven - name: Build and run tests run: > mvn -B -ntp test - -pl datapoly-common,datapoly-template,datapoly-core,datapoly-executor,datapoly-gateway,datapoly-manager + -pl datapoly-test -am diff --git a/AGENTS.md b/AGENTS.md index 4f66eb3..283fa1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,14 +2,22 @@ > 安全弱点上报见 SECURITY.md;内部评审细节不入库。 -Maven 多模块,BSD-3-Clause。本机构建与 CI 统一 JDK 8(temurin/zulu 均可),产物即成 Java 8 字节码(无需本机安装 JDK 25);宿主机扩展构建(build-extension.sh)JDK 8 优先,无 8 时允许 8 以上(编译目标钉在 1.8,产物仍为 Java 8 字节码)。CI(temurin 8)只跑 common/template/core/executor/gateway/manager 六模块的测试。 -- lombok 1.18.46、groovy 4.0.33(org.apache.groovy)为钉版勿回退(JDK 8 与 25 下均验证可用);注解处理器依赖必须走 `annotationProcessorPaths`。 -- 三服务经 Eureka 互联:manager(8090,Liquibase 唯一迁移执行方)、executor(8092)、gateway(8091 唯一对外入口)。前端 datapoly-manager-ui 非 Maven:manager resources 下的 `index.html` 与 `static/` 为构建产物 **不入库**(已被 .gitignore 排除,勿提交/勿 git add -f),打包前由根目录 `build-ui.sh`(node:23-alpine 容器,本机无需 Node)生成——`build.sh` 与 `docker-maven-build.sh` 已在它之前前置 `build-extension.sh`(宿主扩展装配:配置 `DATAPOLY_EXTENSION_GIT_URL` 或 `datapoly-extension` 目录存在即构建,否则无操作)及该 UI 步骤;纯 `mvn package` 的 jar 不含 UI。`build-ui.sh debug` 产出 devtools 可用的调试构建(`DATAPOLY_UI_ENV=debug`,NODE_ENV=development),仅限本机联调、勿随发行版发布;`build.sh`/`docker-maven-build.sh` 会透传首参给 build-ui.sh。 +Maven 多模块,BSD-3-Clause。本机构建与 CI 统一 JDK 25(LTS;temurin/homebrew 均可),产物即成 Java 25 字节码;宿主机扩展构建(build-extension.sh)同样要求 JDK ≥25(编译目标钉在 25,低于 25 直接中止)。CI(temurin 25)经 `mvn test -pl datapoly-test -am` 运行测试:各模块测试统一集中在 datapoly-test 模块(JUnit 4 + 手写 fake,包名与被测模块保持同包以访问 package-private 成员),其余模块不含 src/test。 +- lombok 1.18.48、groovy 4.0.33(org.apache.groovy)为钉版勿回退(JDK 25 基线验证可用);注解处理器依赖必须走 `annotationProcessorPaths`。 +- 三服务经 Eureka 互联:manager(8090,Liquibase 唯一迁移执行方)、executor(8092)、gateway(8091 唯一对外入口)。前端 datapoly-manager-ui 非 Maven:manager resources 下的 `index.html` 与 `static/` 为构建产物 **不入库**(已被 .gitignore 排除,勿提交/勿 git add -f),打包前由根目录 `build-ui.sh`(node:24-alpine 容器,本机无需 Node)生成——`build.sh` 与 `docker-maven-build.sh` 已在它之前前置 `build-extension.sh`(宿主扩展装配:配置 `DATAPOLY_EXTENSION_GIT_URL` 或 `datapoly-extension` 目录存在即构建,否则无操作)及该 UI 步骤;纯 `mvn package` 的 jar 不含 UI。`build-ui.sh debug` 产出 devtools 可用的调试构建(`DATAPOLY_UI_ENV=debug`,NODE_ENV=development),仅限本机联调、勿随发行版发布;`build.sh`/`docker-maven-build.sh` 会透传首参给 build-ui.sh。 - 许可头:新改文件只写 BSD 许可声明行,勿写个人 Copyright 头;vendored 文件(如 io.modelcontextprotocol.*)保留原版权声明。 +## 模块速览与常用命令 + +SQL/DSL → RESTful API 的数据访问中间件(Boot 3.5.x LTS + Cloud 2025.0.x,jakarta 命名空间)。Maven 模块:common(通用定义)、mcp(LLM MCP 协议)、template(SQL 内容模板)、persistence(数据库持久化)、core(接口核心实现)、cache(执行缓存)、executor/gateway/manager(三服务,见下)、test(集中全部测试)、dist(发行打包);`datapoly-manager-ui` 为前端(非 Maven);`drivers/` 装配 20+ 数据库 JDBC 驱动;`build-docker/` 镜像与 compose 一键安装。改动前按需读 docs/{zh,en}/ 下 overview.md、build-deploy.md、data-task.md(涉及 DataTask 必读)。API 文档注解用 springdoc/swagger v3(io.swagger.v3.oas.annotations),勿引入 springfox。 + +- 全量测试(CI 同款):`mvn test -pl datapoly-test -am` +- 发行构建:`./build.sh`(前置 build-extension.sh 与 build-ui.sh 再 mvn package);容器内构建:`./docker-maven-build.sh` + ## 一、网络分段(必须遵守) - 仅 gateway 对外发布端口;manager 与 executor 除 Eureka 外无 HTTP 直连,新增直连须同步更新白名单与本文件。 +- manager 内置两个 MCP Server(均走 `?token=` 查询参数鉴权,`/mcp/**` 豁免会话拦截器):数据 MCP `/mcp`·`/mcp/sse`(任意 MCP 令牌,调用数据 API tools);管理 MCP `/mcp/admin`·`/mcp/admin/sse`(仅 `mcp_client.manage_flag=1` 令牌,`dp_{entity}_{action}` 工具覆盖数据源/模块/分组/API/客户端等实体增删改查,见 `McpAdminTools`)。manage 令牌等同管理员(可读 appSecret 与 MCP 令牌明文),创建/授予须走已认证会话。 - executor `GatewaySourceFilter`(`/*`):`trusted-cidrs` 仅 IPv4 CIDR(K8s 探针须加入);`auth-token` 设置后必须带头 `X-DATAPOLY-Gateway-Token`(恒定时间比较,gateway 配 `DATAPOLY_GATEWAY_TOKEN` 注入)。gateway 未设 token 时注入哨兵 `UNSET`,勿改回空默认(SCG 启动会失败)。 - 来源判定只用 `getRemoteAddr()`,勿信任何转发头。 @@ -28,7 +36,7 @@ Groovy 沙箱默认启用但不是 JVM 隔离:勿把脚本编写权开放给 ## 五、其他约束 - 新增 JDBC 代码资源必须 try-with-resources;firewall 规则行被删时网关按"全拒绝"处理(fail-closed,属预期)。 -- DataTask 投递 Sink 仓库内置零实现,外部以 Spring Bean / `META-INF/services` 注册(SPI `com.cs.common.datatask.DataTaskSink`);宿主可自行维护本地扩展:在顶层 `datapoly-extension/`(已被 .gitignore 排除,独立 git 仓库)下用 `backend/` 放 Maven 扩展模块(依赖钉版在模块自身 pom、不进根 reactor,由入库脚本 build-extension.sh 在宿主机 JDK 8 构建后投放 lib-extra/,随发行版装配进各服务 classpath)、`front/` 放扩展 UI;API 扩展点 `ApiAssignmentPostProcessor` 注册方式相同、须同步执行且保持轻量。详见 docs/*/data-task.md。 -- 默认前端扩展目录 `datapoly-extension/front`(同被 .gitignore 排除)经 datapoly-manager-ui 编译期装配:webpack `@extension` 别名自动探测该目录 `src/index.js`(见 build/webpack.base.conf.js)、`src/extension-stub` 为缺省回退、扩展路由与 i18n 词条在 manager-ui 入口深合并——这四处钩子文件(build/webpack.base.conf.js、src/extension-stub、src/router、src/main.js)勿移除或改名;目录不存在时 CI 与普通构建不受影响。front 自带 `package.json` 可直接启动(`npm run dev`,复用宿主 webpack 链,前置为宿主 node_modules 已安装、Node 23——dev server 与生产构建均已实测)。 -- 宿主扩展 jar 经根目录 `lib-extra/` 投放点进入发行版 `lib/common/`(`package.xml` 打包该目录 `*.jar`;目录只占位入库,jar 永不入库)。扩展为独立 git 仓库(内部 GitLab,front+backend 一体):`build-extension.sh` 按环境变量 `DATAPOLY_EXTENSION_GIT_URL`(真实地址不入库,CI 注入)+ `DATAPOLY_EXTENSION_GIT_REF`(默认 master)浅克隆到 `datapoly-extension/`(仍被 .gitignore 排除),目录已存在则按本地工作区构建(宿主机 JDK 8 优先、无 8 时 8 以上可用)、`DATAPOLY_EXTENSION_FORCE_SYNC=1` 强制覆盖本地改动;`build.sh`/`docker-maven-build.sh` 会先调用该脚本,未配置且目录不存在时无操作(纯开源构建零影响)。本地环境变量注入(env.sh)与防误提交钩子集中在被忽略的 `dev-local/`。 +- DataTask 投递 Sink 仓库内置零实现,外部以 Spring Bean / `META-INF/services` 注册(SPI `com.cs.common.datatask.DataTaskSink`);宿主可自行维护本地扩展:在顶层 `datapoly-extension/`(已被 .gitignore 排除,独立 git 仓库)下用 `backend/` 放 Maven 扩展模块(依赖钉版在模块自身 pom、不进根 reactor,由入库脚本 build-extension.sh 在宿主机 JDK 25 构建后投放 lib-extra/,随发行版装配进各服务 classpath)、`front/` 放扩展 UI;API 扩展点 `ApiAssignmentPostProcessor` 注册方式相同、须同步执行且保持轻量。详见 docs/*/data-task.md。 +- 默认前端扩展目录 `datapoly-extension/front`(同被 .gitignore 排除)经 datapoly-manager-ui 编译期装配:webpack `@extension` 别名自动探测该目录 `src/index.js`(见 build/webpack.base.conf.js)、`src/extension-stub` 为缺省回退、扩展路由与 i18n 词条在 manager-ui 入口深合并——这四处钩子文件(build/webpack.base.conf.js、src/extension-stub、src/router、src/main.js)勿移除或改名;目录不存在时 CI 与普通构建不受影响。front 自带 `package.json` 可直接启动(`npm run dev`,复用宿主 webpack 链,前置为宿主 node_modules 已安装、Node 24——dev server 与生产构建均已实测)。 +- 宿主扩展 jar 经根目录 `lib-extra/` 投放点进入发行版 `lib/common/`(`package.xml` 打包该目录 `*.jar`;目录只占位入库,jar 永不入库)。扩展为独立 git 仓库(内部 GitLab,front+backend 一体):`build-extension.sh` 按环境变量 `DATAPOLY_EXTENSION_GIT_URL`(真实地址不入库,CI 注入)+ `DATAPOLY_EXTENSION_GIT_REF`(默认 master)浅克隆到 `datapoly-extension/`(仍被 .gitignore 排除),目录已存在则按本地工作区构建(宿主机 JDK 25 优先、低于 25 不可用)、`DATAPOLY_EXTENSION_FORCE_SYNC=1` 强制覆盖本地改动;`build.sh`/`docker-maven-build.sh` 会先调用该脚本,未配置且目录不存在时无操作(纯开源构建零影响)。本地环境变量注入(env.sh)与防误提交钩子集中在被忽略的 `dev-local/`。 - 一次性 token 在校验时即消费(含查库兜底路径;2026-09 修复兜底不消费导致的重放);并发首用竞态下多 executor 仍可能各放行一次(无分布式锁,已知限制)。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8844f2b..e2c0617 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Thanks for your interest in contributing! 1. Fork the repository and create your branch from `main`. 2. Set up the environment: - - JDK 8 (the project targets Java 8) + - JDK 25 (the project targets Java 25, LTS line) - Maven 3.6+ - Docker (for the database containers and the front-end build) @@ -21,15 +21,15 @@ mvn -B -ntp test -pl datapoly-common,datapoly-template,datapoly-core,datapoly-ex ## Front-end (built-in manager UI) The built-in UI (`datapoly-manager-ui`) is a Vue 2 project built with a -webpack 5 toolchain. The canonical toolchain is the pinned `node:23-alpine` -container (the production build is verified on Node 23), so build it with +webpack 5 toolchain. The canonical toolchain is the pinned `node:24-alpine` +container (the production build is verified on Node 24), so build it with the provided script (works on any host with Docker, no local Node required): ```bash sh ./build-ui.sh ``` -This builds the UI inside a `node:23-alpine` container and syncs +This builds the UI inside a `node:24-alpine` container and syncs `dist/index.html` + `dist/static/` into `datapoly-manager/src/main/resources/` (wiping the previous copies first so stale hashed files do not linger). @@ -44,7 +44,7 @@ To rebuild the UI without the wrapper script (for UI-only development), the raw container command is: ```bash -docker run --rm -v $PWD/datapoly-manager-ui:/app -w /app node:23-alpine \ +docker run --rm -v $PWD/datapoly-manager-ui:/app -w /app node:24-alpine \ sh -c "npm config set registry https://registry.npmmirror.com && \ npm install --no-audit --no-fund --no-package-lock --legacy-peer-deps && \ npm run build" @@ -55,7 +55,7 @@ If you build this way, copy `dist/index.html` and `dist/static/` into ## Local smoke testing -The `.devcontainer/` directory contains a dev container (JDK 8 + Maven, +The `.devcontainer/` directory contains a dev container (JDK 25 + Maven, MySQL 8, PostgreSQL 14) for end-to-end smoke tests: ```bash diff --git a/Dockerfile b/Dockerfile index 759095c..d59f783 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Use of this source code is governed by a BSD-style license # ============================================================================ -# 单 Dockerfile 全自动构建(多阶段):UI(node:23) → Maven 打包(temurin 8) → 运行时(temurin 8 JRE) +# 单 Dockerfile 全自动构建(多阶段):UI(node:24) → Maven 打包(temurin 25) → 运行时(temurin 25 JRE) # 产物与 build.sh / docker-maven-build.sh / build_and_push_image.sh 一致,两条链路并行可用。 # # 用法(在仓库根目录): @@ -19,9 +19,9 @@ # npmmirror / 阿里云镜像下载 npm 与 Maven 依赖,之后由层缓存与 cache mount 加速。 # ============================================================================ -# ---- Stage 1: 构建内置管理端 UI(Vue2 + webpack5,与 build-ui.sh 同为 node:23) ---- +# ---- Stage 1: 构建内置管理端 UI(Vue2 + webpack5,与 build-ui.sh 同为 node:24) ---- # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM node:23-alpine@sha256:a34e14ef1df25b58258956049ab5a71ea7f0d498e41d0b514f4b8de09af09456 AS ui +FROM node:24-alpine@sha256:a34e14ef1df25b58258956049ab5a71ea7f0d498e41d0b514f4b8de09af09456 AS ui WORKDIR /opt/app # 拷贝整个仓库(与 build-ui.sh 挂载仓库根一致):webpack 的 @extension 别名会探测 # 仓库根下 datapoly-extension/front/src,缺省回退 stub;仅生产构建,debug 变体仍走 build-ui.sh @@ -31,9 +31,9 @@ RUN npm config set registry http://mirrors.cloud.tencent.com/npm/ \ && npm install --no-audit --no-fund --no-package-lock --legacy-peer-deps \ && npm run build -# ---- Stage 2: Maven 打包(与 docker-maven-build.sh 同为 temurin 8,支持 arm64) ---- +# ---- Stage 2: Maven 打包(与 docker-maven-build.sh 同为 temurin 25,支持 arm64) ---- # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM maven:3.9-eclipse-temurin-8@sha256:d8b1b22e93012cd0257d37b3fd6d7bec3d0f7ae611730fc3ba0657b9ebe4e7ca AS build +FROM maven:3.9-eclipse-temurin-25@sha256:0e3f1c4394674505f73525188b8005f521ef3c786c651cb61432b2db3517087b AS build # CI 可传 MAVEN_ARGS=-Dmaven.test.skip=true 加速;默认与 docker-maven-build.sh 一致(跑测试) ARG MAVEN_ARGS="" WORKDIR /src @@ -51,7 +51,7 @@ RUN --mount=type=cache,target=/opt/maven/localRepository \ # ---- Stage 3: 运行时镜像(与 build-docker/datapoly/Dockerfile-* 同基线) ---- # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM eclipse-temurin:8-jre-jammy@sha256:06641b36281c1ac815c33f3f3528cfea1c6fc41ddc60d261746e4343d19cbe65 +FROM eclipse-temurin:25-jre-jammy@sha256:20a695e74d47fb29cda1cbad5d9ee6cfad4ac6e88a8e048ed6265cede1e71f5e ENV TZ=Asia/Shanghai # 非 root 运行(K8s 安全上下文友好);用户需在 COPY --chown 前存在; # curl 仅供 HEALTHCHECK 使用 diff --git a/build-docker/datapoly/Dockerfile-executor b/build-docker/datapoly/Dockerfile-executor index d4cd230..ca0467d 100644 --- a/build-docker/datapoly/Dockerfile-executor +++ b/build-docker/datapoly/Dockerfile-executor @@ -1,5 +1,5 @@ # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM eclipse-temurin:8-jre-jammy@sha256:06641b36281c1ac815c33f3f3528cfea1c6fc41ddc60d261746e4343d19cbe65 +FROM eclipse-temurin:25-jre-jammy@sha256:20a695e74d47fb29cda1cbad5d9ee6cfad4ac6e88a8e048ed6265cede1e71f5e ENV TZ=Asia/Shanghai diff --git a/build-docker/datapoly/Dockerfile-gateway b/build-docker/datapoly/Dockerfile-gateway index fd89f69..7850dae 100644 --- a/build-docker/datapoly/Dockerfile-gateway +++ b/build-docker/datapoly/Dockerfile-gateway @@ -1,5 +1,5 @@ # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM eclipse-temurin:8-jre-jammy@sha256:06641b36281c1ac815c33f3f3528cfea1c6fc41ddc60d261746e4343d19cbe65 +FROM eclipse-temurin:25-jre-jammy@sha256:20a695e74d47fb29cda1cbad5d9ee6cfad4ac6e88a8e048ed6265cede1e71f5e ENV TZ=Asia/Shanghai diff --git a/build-docker/datapoly/Dockerfile-manager b/build-docker/datapoly/Dockerfile-manager index 88c7c00..9771568 100644 --- a/build-docker/datapoly/Dockerfile-manager +++ b/build-docker/datapoly/Dockerfile-manager @@ -1,5 +1,5 @@ # 基镜像钉 digest(tag 可被上游覆盖,digest 不可变);升级基镜像时须同步刷新 -FROM eclipse-temurin:8-jre-jammy@sha256:06641b36281c1ac815c33f3f3528cfea1c6fc41ddc60d261746e4343d19cbe65 +FROM eclipse-temurin:25-jre-jammy@sha256:20a695e74d47fb29cda1cbad5d9ee6cfad4ac6e88a8e048ed6265cede1e71f5e ENV TZ=Asia/Shanghai diff --git a/build-extension.sh b/build-extension.sh index a5636c9..2548d05 100644 --- a/build-extension.sh +++ b/build-extension.sh @@ -8,7 +8,8 @@ # DATAPOLY_EXTENSION_GIT_REF 拉取的 ref(分支/标签),默认 master # DATAPOLY_EXTENSION_FORCE_SYNC=1 目录已存在时强制 fetch+reset 到 REF(丢弃本地未提交改动,慎用) # -# 后端模块构建沿用 dev-local/dev.sh 原约定:宿主机 JDK 8 优先、无 8 时允许 8 以上(编译目标钉在 1.8)、扩展模块不进根 reactor、 +# 后端模块构建:宿主机 JDK 25 优先(与 CI 的 temurin 25 对齐)、无 25 时允许 25 以上(编译目标钉在 25, +# 低于 25 无法编译,构建中止)、扩展模块不进根 reactor、 # 依赖经 解析根 pom、产物投放 lib-extra/ 后由 package.xml 打进 lib/common。 # 参数:--skip-tests 用 -DskipTests 替代默认 -Dmaven.test.skip=true(仅作用于扩展侧构建)。 @@ -65,22 +66,21 @@ if [ "$(wc -l < "$POM_LIST")" -eq 0 ]; then exit 0 fi -# ---------- ③ 宿主机 JDK:优先 JDK 8(与 CI 的 temurin 8 对齐);扩展模块编译目标为 1.8 -# (根 pom maven.compiler.source/target=1.8),8 以上 JDK 构建同样产出 Java 8 字节码, -# 故不再硬性断言 8——仅当完全无可用 JDK 时才中止 ---------- +# ---------- ③ 宿主机 JDK:优先 JDK 25(与 CI 的 temurin 25 对齐);扩展模块编译目标为 25 +# (根 pom maven.compiler.release=25),低于 25 无法编译、25 以上亦可 ---------- jdkmajor() { "$1/bin/java" -version 2>&1 | sed -n 's/.*version "\([0-9][0-9]*\).*/\1/p' | sed 's/^1$/8/' } if [ "$(uname -s)" = "Darwin" ]; then - java_home_8="" + java_home_25="" if [ -x /usr/libexec/java_home ]; then - java_home_8=$(/usr/libexec/java_home -v 8 2>/dev/null || true) + java_home_25=$(/usr/libexec/java_home -v 25 2>/dev/null || true) fi for candidate in \ "${JAVA_HOME:-}" \ - /opt/homebrew/opt/openjdk@8/libexec/openjdk.jdk/Contents/Home \ - /usr/local/opt/openjdk@8/libexec/openjdk.jdk/Contents/Home \ - "$java_home_8"; do + /opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home \ + /usr/local/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home \ + "$java_home_25"; do if [ -n "$candidate" ] && [ -n "$(jdkmajor "$candidate" 2>/dev/null)" ]; then export JAVA_HOME="$candidate" break @@ -100,8 +100,9 @@ if [ -z "${JAVA_HOME:-}" ] || [ -z "$(jdkmajor "$JAVA_HOME" 2>/dev/null)" ]; the exit 1 fi echo "[ext] JAVA_HOME=$JAVA_HOME" -if [ "$(jdkmajor "$JAVA_HOME" 2>/dev/null)" != "8" ]; then - echo "[ext] 当前为 JDK $(jdkmajor "$JAVA_HOME" 2>/dev/null)(非 8):编译目标 1.8,产物仍为 Java 8 字节码(与容器内 temurin 8 一致)" +if [ "$(jdkmajor "$JAVA_HOME" 2>/dev/null)" -lt 25 ]; then + echo "[ext] 当前为 JDK $(jdkmajor "$JAVA_HOME" 2>/dev/null)(低于 25):扩展模块编译目标为 25,无法编译,请切换 JDK 25 或以上" >&2 + exit 1 fi HOST_VERSION=$(grep -m1 '' "$ROOT/pom.xml" | sed -E 's/.*([^<]+)<\/version>.*/\1/') diff --git a/build-ui.sh b/build-ui.sh index ef6f31d..6f4cd2b 100755 --- a/build-ui.sh +++ b/build-ui.sh @@ -1,7 +1,7 @@ #!/bin/sh # 构建内置管理端 UI 并同步进 manager resources(构建产物不入库,见 .gitignore 与 AGENTS.md)。 -# 前端为 Vue2 + webpack5,经 node:23-alpine 容器构建(本机无需 Node;镜像钉 digest 防上游漂移)。 +# 前端为 Vue2 + webpack5,经 node:24-alpine 容器构建(本机无需 Node;镜像钉 digest 防上游漂移)。 # build.sh 与 docker-maven-build.sh 会在 mvn 打包前调用本脚本;纯 mvn package 的 jar 不含 UI。 # 用法:./build-ui.sh [debug] # debug —— DATAPOLY_UI_ENV=debug:NODE_ENV 保持 development,Vue 保留 devtools 钩子, @@ -29,7 +29,7 @@ docker run --rm \ -e DATAPOLY_UI_ENV="$UI_ENV" \ -v "$PWD":/opt/app \ -w /opt/app/datapoly-manager-ui \ - node:23-alpine@sha256:a34e14ef1df25b58258956049ab5a71ea7f0d498e41d0b514f4b8de09af09456 \ + node:24-alpine@sha256:a34e14ef1df25b58258956049ab5a71ea7f0d498e41d0b514f4b8de09af09456 \ sh -c "npm config set registry https://registry.npmmirror.com && \ npm install --no-audit --no-fund --no-package-lock --legacy-peer-deps && \ $BUILD_CMD" diff --git a/build.cmd b/build.cmd index 8750823..1125fc8 100644 --- a/build.cmd +++ b/build.cmd @@ -2,7 +2,7 @@ if not exist datapoly-manager\src\main\resources\index.html ( echo [WARN] Built-in UI is not built: the package will contain no management UI. - echo Build it first with Node 23 (`npm run build`) or Docker (`sh build-ui.sh`), + echo Build it first with Node 24 (`npm run build`) or Docker (`sh build-ui.sh`), echo see docs/zh/build-deploy.md. echo. ) diff --git a/build.sh b/build.sh index 070dd5d..dbf8117 100644 --- a/build.sh +++ b/build.sh @@ -1,9 +1,9 @@ #!/bin/sh -# 本机构建统一用 JDK 8(产物即成 Java 8 字节码,与 CI 的 temurin 8 一致;本机无需安装 JDK 25) -# 候选顺序:现有 JAVA_HOME → Homebrew keg → /usr/libexec/java_home -v 8。 +# 本机构建统一用 JDK 25(产物即成 Java 25 字节码,与 CI 的 temurin 25 一致) +# 候选顺序:现有 JAVA_HOME → Homebrew keg → /usr/libexec/java_home -v 25。 # 注意:java_home 在无匹配 JDK 时会回退返回唯一已装 JDK 且退出码仍为 0, -# 因此每个候选都必须用 java -version 校验主版本确为 8,防止静默落回其他版本。 +# 因此每个候选都必须用 java -version 校验主版本确为 25,防止静默落回其他版本。 # HotSpot 8 的版本串形如 "1.8.0_504",jdkmajor 会把它归一成 8 再比较。 jdkmajor() { @@ -11,28 +11,28 @@ jdkmajor() { } if [ "$(uname -s)" = "Darwin" ]; then - java_home_8="" + java_home_want="" if [ -x /usr/libexec/java_home ]; then - java_home_8=$(/usr/libexec/java_home -v 8 2>/dev/null || true) + java_home_want=$(/usr/libexec/java_home -v 25 2>/dev/null || true) fi for candidate in \ "${JAVA_HOME:-}" \ - /opt/homebrew/opt/openjdk@8/libexec/openjdk.jdk/Contents/Home \ - /usr/local/opt/openjdk@8/libexec/openjdk.jdk/Contents/Home \ - "$java_home_8"; do - if [ -n "$candidate" ] && [ "$(jdkmajor "$candidate" 2>/dev/null)" = "8" ]; then + /opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home \ + /usr/local/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home \ + "$java_home_want"; do + if [ -n "$candidate" ] && [ "$(jdkmajor "$candidate" 2>/dev/null)" = "25" ]; then export JAVA_HOME="$candidate" echo "[build.sh] JAVA_HOME=$JAVA_HOME" break fi done - if [ "$(jdkmajor "$JAVA_HOME" 2>/dev/null)" != "8" ]; then - echo "[build.sh] 未找到 JDK 8,沿用当前 JAVA_HOME/PATH 构建(JAVA_HOME=${JAVA_HOME:-<未设置>})" >&2 + if [ "$(jdkmajor "$JAVA_HOME" 2>/dev/null)" != "25" ]; then + echo "[build.sh] 未找到 JDK 25,沿用当前 JAVA_HOME/PATH 构建(JAVA_HOME=${JAVA_HOME:-<未设置>})" >&2 fi fi # 先装配宿主扩展(build-extension.sh:环境变量门控,未配置且无本地目录时无操作), -# 再构建内置 UI 产物(node:23-alpine 容器),最后 mvn 打包;mvn 本身不触发前端构建。 +# 再构建内置 UI 产物(node:24-alpine 容器),最后 mvn 打包;mvn 本身不触发前端构建。 # 传 "debug" 可构建 devtools 可用的调试版 UI(透传给 build-ui.sh),仅限本机联调。 sh "$(dirname "$0")/build-extension.sh" sh "$(dirname "$0")/build-ui.sh" "$1" diff --git a/datapoly-cache/pom.xml b/datapoly-cache/pom.xml index d975ef9..3546f82 100644 --- a/datapoly-cache/pom.xml +++ b/datapoly-cache/pom.xml @@ -31,7 +31,7 @@ com.hazelcast - hazelcast-all + hazelcast diff --git a/datapoly-cache/src/main/java/com/cs/cache/hazelcast/HazelcastCacheFactory.java b/datapoly-cache/src/main/java/com/cs/cache/hazelcast/HazelcastCacheFactory.java index 40d604b..549bf83 100644 --- a/datapoly-cache/src/main/java/com/cs/cache/hazelcast/HazelcastCacheFactory.java +++ b/datapoly-cache/src/main/java/com/cs/cache/hazelcast/HazelcastCacheFactory.java @@ -5,7 +5,7 @@ import com.cs.cache.*; import com.hazelcast.core.HazelcastInstance; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/datapoly-cache/src/main/java/com/cs/cache/redis/RedisCacheFactory.java b/datapoly-cache/src/main/java/com/cs/cache/redis/RedisCacheFactory.java index 15ef660..986b1df 100644 --- a/datapoly-cache/src/main/java/com/cs/cache/redis/RedisCacheFactory.java +++ b/datapoly-cache/src/main/java/com/cs/cache/redis/RedisCacheFactory.java @@ -4,7 +4,7 @@ import com.cs.cache.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/datapoly-common/pom.xml b/datapoly-common/pom.xml index fe08912..5d98567 100644 --- a/datapoly-common/pom.xml +++ b/datapoly-common/pom.xml @@ -42,28 +42,24 @@ spring-boot-starter-logging + - javax.servlet - javax.servlet-api - - - - io.springfox - springfox-swagger2 + org.springframework + spring-context - io.springfox - springfox-swagger-ui + com.fasterxml.jackson.core + jackson-annotations + - io.springfox - springfox-boot-starter + jakarta.servlet + jakarta.servlet-api - junit - junit - test + io.swagger.core.v3 + swagger-annotations-jakarta diff --git a/datapoly-common/src/main/java/com/cs/common/consts/Constants.java b/datapoly-common/src/main/java/com/cs/common/consts/Constants.java index 3d315cb..7786b08 100644 --- a/datapoly-common/src/main/java/com/cs/common/consts/Constants.java +++ b/datapoly-common/src/main/java/com/cs/common/consts/Constants.java @@ -48,6 +48,11 @@ public abstract class Constants { public static final String MESSAGE_ENDPOINT = "/mcp/message"; public static final String MCP_SERVER_NAME = "datapoly-mcp-server"; + public static final String ADMIN_STREAM_ENDPOINT = "/mcp/admin"; + public static final String ADMIN_SSE_ENDPOINT = "/mcp/admin/sse"; + public static final String ADMIN_MESSAGE_ENDPOINT = "/mcp/admin/message"; + public static final String MCP_ADMIN_SERVER_NAME = "datapoly-mcp-admin-server"; + public static final String getResourceName(String method, String path) { return String.format("/%s/%s[%s]", Constants.API_PATH_PREFIX, path, method); } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java b/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java index 83c3f62..5e3031d 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java @@ -3,7 +3,7 @@ package com.cs.common.dto; import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -12,22 +12,22 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("TOKEN信息") +@Schema(description = "TOKEN信息") public class AccessToken implements Serializable { - @ApiModelProperty("实际名称") + @Schema(description = "实际名称") private String realName; - @ApiModelProperty("唯一标识") + @Schema(description = "唯一标识") private String appKey; - @ApiModelProperty("token字符串") + @Schema(description = "token字符串") private String accessToken; @JsonIgnore - @ApiModelProperty("创建时的时间戳") + @Schema(description = "创建时的时间戳") private Long createTimestamp; - @ApiModelProperty("有效期(时间段,单位:秒)") + @Schema(description = "有效期(时间段,单位:秒)") private Long expireSeconds; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/ApiIdVersion.java b/datapoly-common/src/main/java/com/cs/common/dto/ApiIdVersion.java index 3197bb5..5ba872c 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/ApiIdVersion.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/ApiIdVersion.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -11,15 +11,15 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("API的ID及版本号") +@Schema(description = "API的ID及版本号") public class ApiIdVersion implements Serializable { - @ApiModelProperty("API的ID") + @Schema(description = "API的ID") private Long apiId; - @ApiModelProperty("commitId") + @Schema(description = "commitId") private Long commitId; - @ApiModelProperty("版本号") + @Schema(description = "版本号") private Integer version; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/BaseParam.java b/datapoly-common/src/main/java/com/cs/common/dto/BaseParam.java index ce99a7b..dc7527c 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/BaseParam.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/BaseParam.java @@ -3,7 +3,7 @@ package com.cs.common.dto; import com.cs.common.enums.*; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -13,27 +13,27 @@ @NoArgsConstructor public class BaseParam implements Serializable { - @ApiModelProperty("ID(前端生成并使用)") + @Schema(description = "ID(前端生成并使用)") private String id; - @ApiModelProperty("参数名") + @Schema(description = "参数名") private String name; - @ApiModelProperty("参数类型") + @Schema(description = "参数类型") private ParamTypeEnum type; - @ApiModelProperty("参数位置") + @Schema(description = "参数位置") private ParamLocationEnum location; - @ApiModelProperty("是否为数组") + @Schema(description = "是否为数组") private Boolean isArray; - @ApiModelProperty("是否必填") + @Schema(description = "是否必填") private Boolean required; - @ApiModelProperty("默认值") + @Schema(description = "默认值") private String defaultValue; - @ApiModelProperty("参数描述") + @Schema(description = "参数描述") private String remark; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/DateCount.java b/datapoly-common/src/main/java/com/cs/common/dto/DateCount.java index 865b0eb..2079b9b 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/DateCount.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/DateCount.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -11,15 +11,15 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("按照日期的统计") +@Schema(description = "按照日期的统计") public class DateCount implements Serializable { - @ApiModelProperty("日期") + @Schema(description = "日期") private String ofDate; - @ApiModelProperty("总数") + @Schema(description = "总数") private Long total; - @ApiModelProperty("成功数") + @Schema(description = "成功数") private Long success; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/HourCount.java b/datapoly-common/src/main/java/com/cs/common/dto/HourCount.java index 93da5d6..ad5af44 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/HourCount.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/HourCount.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -11,12 +11,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("按照小时的统计") +@Schema(description = "按照小时的统计") public class HourCount implements Serializable { - @ApiModelProperty("小时(0-23)") + @Schema(description = "小时(0-23)") private Integer hour; - @ApiModelProperty("总数") + @Schema(description = "总数") private Long count; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/IdWithName.java b/datapoly-common/src/main/java/com/cs/common/dto/IdWithName.java index 618f50f..f4bb491 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/IdWithName.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/IdWithName.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -11,12 +11,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("ID名称") +@Schema(description = "ID名称") public class IdWithName implements Serializable { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("名称") + @Schema(description = "名称") private String name; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/ItemParam.java b/datapoly-common/src/main/java/com/cs/common/dto/ItemParam.java index a9498ab..2064734 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/ItemParam.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/ItemParam.java @@ -4,7 +4,7 @@ import com.cs.common.enums.*; import com.cs.common.exception.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import org.apache.commons.lang3.StringUtils; @@ -13,10 +13,10 @@ @Data @AllArgsConstructor @NoArgsConstructor -@ApiModel("入参信息") +@Schema(description = "入参信息") public class ItemParam extends BaseParam { - @ApiModelProperty("Object类型的子元素") + @Schema(description = "Object类型的子元素") private List children; public void checkValid(HttpMethodEnum method) { diff --git a/datapoly-common/src/main/java/com/cs/common/dto/NameCount.java b/datapoly-common/src/main/java/com/cs/common/dto/NameCount.java index 8a59227..ad565b1 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/NameCount.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/NameCount.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; @@ -11,12 +11,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@ApiModel("统计数量") +@Schema(description = "统计数量") public class NameCount implements Serializable { - @ApiModelProperty("名称") + @Schema(description = "名称") private String name; - @ApiModelProperty("数量") + @Schema(description = "数量") private Long count; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/OutParam.java b/datapoly-common/src/main/java/com/cs/common/dto/OutParam.java index 72cdf1c..f3274a3 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/OutParam.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/OutParam.java @@ -4,7 +4,7 @@ import com.cs.common.enums.ParamTypeEnum; import com.cs.common.exception.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import org.apache.commons.lang3.StringUtils; @@ -14,25 +14,25 @@ @Data @AllArgsConstructor @NoArgsConstructor -@ApiModel("出参信息") +@Schema(description = "出参信息") public class OutParam implements Serializable { - @ApiModelProperty("ID(前端生成并使用)") + @Schema(description = "ID(前端生成并使用)") private String id; - @ApiModelProperty("参数名") + @Schema(description = "参数名") private String name; - @ApiModelProperty("参数类型") + @Schema(description = "参数类型") private ParamTypeEnum type; - @ApiModelProperty("是否为数组") + @Schema(description = "是否为数组") private Boolean isArray; - @ApiModelProperty("参数描述") + @Schema(description = "参数描述") private String remark; - @ApiModelProperty("Object类型的子元素") + @Schema(description = "Object类型的子元素") private List children; public void checkValid() { diff --git a/datapoly-common/src/main/java/com/cs/common/dto/PageResult.java b/datapoly-common/src/main/java/com/cs/common/dto/PageResult.java index 641bf80..1e8c6cf 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/PageResult.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/PageResult.java @@ -2,41 +2,41 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; import java.util.List; -@ApiModel(description = "分页结果") +@Schema(description = "分页结果") @NoArgsConstructor @Data public class PageResult implements Serializable { - @ApiModelProperty("状态码") + @Schema(description = "状态码") private Integer code = 0; - @ApiModelProperty("状态描述") + @Schema(description = "状态描述") private String message = "success"; - @ApiModelProperty("分页信息") + @Schema(description = "分页信息") private Pagination pagination; - @ApiModelProperty("数据") + @Schema(description = "数据") private List data; - @ApiModel(description = "分页结果") + @Schema(description = "分页结果") @NoArgsConstructor @Data public static class Pagination { - @ApiModelProperty("页码") + @Schema(description = "页码") private int page; - @ApiModelProperty("记录总数") + @Schema(description = "记录总数") private int total; - @ApiModelProperty("每页大小") + @Schema(description = "每页大小") private int size; } diff --git a/datapoly-common/src/main/java/com/cs/common/dto/ParamValue.java b/datapoly-common/src/main/java/com/cs/common/dto/ParamValue.java index 58792c5..e382cb1 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/ParamValue.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/ParamValue.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @@ -10,16 +10,16 @@ @Data @AllArgsConstructor @NoArgsConstructor -@ApiModel("参数信息") +@Schema(description = "参数信息") public class ParamValue extends BaseParam { - @ApiModelProperty("Object类型的子元素及值") + @Schema(description = "Object类型的子元素及值") private List children; - @ApiModelProperty("非数组参数值") + @Schema(description = "非数组参数值") private String value; - @ApiModelProperty("数组参数值") + @Schema(description = "数组参数值") private List arrayValues; @Data diff --git a/datapoly-common/src/main/java/com/cs/common/dto/ResultEntity.java b/datapoly-common/src/main/java/com/cs/common/dto/ResultEntity.java index 05ed7aa..141e920 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/ResultEntity.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/ResultEntity.java @@ -3,25 +3,25 @@ package com.cs.common.dto; import com.cs.common.exception.ResponseErrorCode; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.io.Serializable; -@ApiModel(description = "响应结果") +@Schema(description = "响应结果") @AllArgsConstructor @Data public class ResultEntity implements Serializable { private static final String SUCCESS = "success"; - @ApiModelProperty("状态码") + @Schema(description = "状态码") private Integer code; - @ApiModelProperty("状态描述") + @Schema(description = "状态描述") private String message; - @ApiModelProperty("数据") + @Schema(description = "数据") private T data; public static ResultEntity success() { diff --git a/datapoly-common/src/main/java/com/cs/common/service/FlowControlManger.java b/datapoly-common/src/main/java/com/cs/common/service/FlowControlManger.java index 7bc6f74..2b8317a 100644 --- a/datapoly-common/src/main/java/com/cs/common/service/FlowControlManger.java +++ b/datapoly-common/src/main/java/com/cs/common/service/FlowControlManger.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.common.service; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public interface FlowControlManger { diff --git a/datapoly-common/src/main/java/com/cs/common/util/ServletUtils.java b/datapoly-common/src/main/java/com/cs/common/util/ServletUtils.java index f527ca2..67b8d06 100644 --- a/datapoly-common/src/main/java/com/cs/common/util/ServletUtils.java +++ b/datapoly-common/src/main/java/com/cs/common/util/ServletUtils.java @@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; /** * Utility for reading HTTP parameters from servlet requests diff --git a/datapoly-common/src/main/java/com/cs/common/util/TokenUtils.java b/datapoly-common/src/main/java/com/cs/common/util/TokenUtils.java index c2d2031..3ed82d5 100644 --- a/datapoly-common/src/main/java/com/cs/common/util/TokenUtils.java +++ b/datapoly-common/src/main/java/com/cs/common/util/TokenUtils.java @@ -8,7 +8,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.core.env.Environment; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.security.MessageDigest; import java.util.UUID; diff --git a/datapoly-common/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java b/datapoly-common/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java deleted file mode 100644 index 795c388..0000000 --- a/datapoly-common/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// Use of this source code is governed by a BSD-style license -package com.cs.common.enums; - -import com.cs.common.util.JdbcUrlUtils; -import org.junit.Test; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; - -import static org.junit.Assert.*; - -public class ProductTypeEnumTest { - - @Test - public void testEnumUiNameInvariant() { - for (ProductTypeEnum type : ProductTypeEnum.values()) { - assertEquals("name must uppercase to its enum constant", type.name(), type.getName().toUpperCase()); - } - } - - @Test - public void testEnumIdsAreUnique() { - Set ids = new HashSet<>(); - for (ProductTypeEnum type : ProductTypeEnum.values()) { - assertTrue("duplicated id " + type.getId() + " for " + type.name(), ids.add(type.getId())); - } - } - - @Test - public void testOdpsLookup() { - assertTrue(ProductTypeEnum.exists("odps")); - assertTrue(ProductTypeEnum.exists("ODPS")); - assertEquals(ProductTypeEnum.ODPS, ProductTypeEnum.of("odps")); - assertEquals("com.aliyun.odps.jdbc.OdpsDriver", ProductTypeEnum.ODPS.getDriver()); - } - - @Test - public void testOdpsContextSwitches() { - assertFalse(ProductTypeEnum.ODPS.hasDatabaseName()); - assertFalse(ProductTypeEnum.ODPS.hasFilePath()); - assertFalse(ProductTypeEnum.ODPS.isNoViewTables()); - assertTrue(ProductTypeEnum.ODPS.offTransactional()); - assertNull(ProductTypeEnum.ODPS.getSql()); - assertNotNull(ProductTypeEnum.ODPS.getPageConsumer()); - } - - @Test - public void testOdpsUrlPatternMatchesOfficialSample() { - String httpUrl = "jdbc:odps:http://service.cn-hangzhou.maxcompute.aliyun.com/api?project=test_project&useProjectTimeZone=true"; - String httpsUrl = "jdbc:odps:https://service.cn-hangzhou.maxcompute.aliyun.com/api?project=test_project"; - assertTrue(matchesAny(ProductTypeEnum.ODPS.getUrl(), httpUrl)); - assertTrue(matchesAny(ProductTypeEnum.ODPS.getUrl(), httpsUrl)); - assertFalse(matchesAny(ProductTypeEnum.ODPS.getUrl(), "jdbc:odps:http://bad")); - assertFalse(matchesAny(ProductTypeEnum.ODPS.getUrl(), - "jdbc:odps:http://service.cn-hangzhou.maxcompute.aliyun.com/other?project=test_project")); - assertTrue(httpUrl.startsWith(ProductTypeEnum.ODPS.getUrlPrefix())); - - Matcher matcher = JdbcUrlUtils.getPattern(ProductTypeEnum.ODPS.getUrl()[0]).matcher(httpUrl); - assertTrue(matcher.matches()); - assertEquals("service.cn-hangzhou.maxcompute.aliyun.com", matcher.group("host")); - assertEquals("project=test_project&useProjectTimeZone=true", matcher.group("params")); - } - - private boolean matchesAny(String[] templates, String url) { - return Arrays.stream(templates) - .map(JdbcUrlUtils::getPattern) - .map(pattern -> pattern.matcher(url)) - .anyMatch(Matcher::matches); - } -} \ No newline at end of file diff --git a/datapoly-core/pom.xml b/datapoly-core/pom.xml index 6e8ba0a..06cf3cd 100644 --- a/datapoly-core/pom.xml +++ b/datapoly-core/pom.xml @@ -35,6 +35,18 @@ groovy + + + io.swagger.core.v3 + swagger-models-jakarta + + + + + org.apache.httpcomponents.client5 + httpclient5 + + org.springframework spring-web @@ -57,12 +69,6 @@ provided - - junit - junit - test - - \ No newline at end of file diff --git a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskJobEngine.java b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskJobEngine.java index cf5867b..4b0e624 100644 --- a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskJobEngine.java +++ b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskJobEngine.java @@ -36,7 +36,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.io.File; import java.sql.*; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskService.java b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskService.java index 01c773c..23fe742 100644 --- a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskService.java +++ b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskService.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskWorker.java b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskWorker.java index ee703fd..4ee2e92 100644 --- a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskWorker.java +++ b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskWorker.java @@ -8,8 +8,8 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import javax.annotation.PreDestroy; -import javax.annotation.Resource; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiAccessLogBasicResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiAccessLogBasicResponse.java index b7e5c8e..ba2f983 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiAccessLogBasicResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiAccessLogBasicResponse.java @@ -3,7 +3,7 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @@ -13,40 +13,40 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("API调用日志记录") +@Schema(description = "API调用日志记录") public class ApiAccessLogBasicResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("HTTP状态码") + @Schema(description = "HTTP状态码") private Integer status; - @ApiModelProperty("耗时") + @Schema(description = "耗时") private Long duration; - @ApiModelProperty("客户端地址") + @Schema(description = "客户端地址") private String ipAddr; - @ApiModelProperty("客户端UA") + @Schema(description = "客户端UA") private String userAgent; - @ApiModelProperty("应用名称") + @Schema(description = "应用名称") private String clientApp; - @ApiModelProperty("请求入参") + @Schema(description = "请求入参") private Map parameters; - @ApiModelProperty("错误异常") + @Schema(description = "错误异常") private String exception; - @ApiModelProperty("记录时间") + @Schema(description = "记录时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("执行器地址") + @Schema(description = "执行器地址") private String executorAddr; - @ApiModelProperty("网关地址") + @Schema(description = "网关地址") private String gatewayAddr; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentBaseResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentBaseResponse.java index 109a6e2..6636661 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentBaseResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentBaseResponse.java @@ -4,66 +4,66 @@ import com.cs.common.enums.*; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @Data @NoArgsConstructor -@ApiModel("API配置简单详情") +@Schema(description = "API配置简单详情") public class ApiAssignmentBaseResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("API配置名称") + @Schema(description = "API配置名称") private String name; - @ApiModelProperty("描述信息") + @Schema(description = "描述信息") private String description; - @ApiModelProperty("模块ID") + @Schema(description = "模块ID") private Long moduleId; - @ApiModelProperty("模块名称") + @Schema(description = "模块名称") private String moduleName; - @ApiModelProperty("授权分组ID") + @Schema(description = "授权分组ID") private Long groupId; - @ApiModelProperty("授权分组名称") + @Schema(description = "授权分组名称") private String groupName; - @ApiModelProperty("API请求方法:GET, HEAD, PUT, POST, DELETE") + @Schema(description = "API请求方法:GET, HEAD, PUT, POST, DELETE") private HttpMethodEnum method; - @ApiModelProperty("请求路径(不带api前缀)") + @Schema(description = "请求路径(不带api前缀)") private String path; - @ApiModelProperty("是否上线") + @Schema(description = "是否上线") private Boolean status; - @ApiModelProperty("commitId") + @Schema(description = "commitId") private Long commitId; - @ApiModelProperty("上线版本") + @Schema(description = "上线版本") private Integer version; - @ApiModelProperty("是否公开") + @Schema(description = "是否公开") private Boolean open; - @ApiModelProperty("是否告警") + @Schema(description = "是否告警") private Boolean alarm; - @ApiModelProperty("执行引擎") + @Schema(description = "执行引擎") private ExecuteEngineEnum engine; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentDetailResponse.java index 5550e06..f4c8be1 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentDetailResponse.java @@ -6,61 +6,61 @@ import com.cs.common.dto.*; import com.cs.common.enums.*; import com.cs.persistence.entity.ApiContextEntity; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @Data @NoArgsConstructor -@ApiModel("API配置详细详情") +@Schema(description = "API配置详细详情") public class ApiAssignmentDetailResponse extends ApiAssignmentBaseResponse { - @ApiModelProperty("分组ID") + @Schema(description = "分组ID") private Long groupId; - @ApiModelProperty("模块ID") + @Schema(description = "模块ID") private Long moduleId; - @ApiModelProperty("数据源ID") + @Schema(description = "数据源ID") private Long datasourceId; - @ApiModelProperty("描述") + @Schema(description = "描述") private String description; - @ApiModelProperty("接口入参") + @Schema(description = "接口入参") private List params; - @ApiModelProperty("接口出参") + @Schema(description = "接口出参") private List outputs; - @ApiModelProperty("HTTP请求的contentType") + @Schema(description = "HTTP请求的contentType") private String contentType; - @ApiModelProperty("SQL列表") + @Schema(description = "SQL列表") private List sqlList; - @ApiModelProperty("接口出参数据类型转换格式") + @Schema(description = "接口出参数据类型转换格式") private List formatMap; - @ApiModelProperty("接口出参属性命名策略") + @Schema(description = "接口出参属性命名策略") private NamingStrategyEnum namingStrategy; - @ApiModelProperty("是否开启流量控制") + @Schema(description = "是否开启流量控制") private Boolean flowStatus; - @ApiModelProperty("阈值类型") + @Schema(description = "阈值类型") private Integer flowGrade; @TableField("阈值大小") private Integer flowCount; - @ApiModelProperty("缓存键类型") + @Schema(description = "缓存键类型") private CacheKeyTypeEnum cacheKeyType; - @ApiModelProperty("缓存表达式") + @Schema(description = "缓存表达式") private String cacheKeyExpr; - @ApiModelProperty("缓存时常") + @Schema(description = "缓存时常") private Long cacheExpireSeconds; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentSaveRequest.java index b93bdb7..47a34b9 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiAssignmentSaveRequest.java @@ -5,91 +5,91 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.cs.common.dto.*; import com.cs.common.enums.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; import java.util.List; @Data @NoArgsConstructor -@ApiModel("API配置") +@Schema(description = "API配置") public class ApiAssignmentSaveRequest { - @ApiModelProperty("ID编号(保存接口使用)") + @Schema(description = "ID编号(保存接口使用)") private Long id; @NotNull(message = "groupId不能为null") - @ApiModelProperty("分组ID") + @Schema(description = "分组ID") private Long groupId; @NotNull(message = "moduleId不能为null") - @ApiModelProperty("模块ID") + @Schema(description = "模块ID") private Long moduleId; @NotNull(message = "datasourceId不能为null") - @ApiModelProperty("数据源ID") + @Schema(description = "数据源ID") private Long datasourceId; @NotBlank(message = "name不能为空") - @ApiModelProperty("API配置名称") + @Schema(description = "API配置名称") private String name; - @ApiModelProperty("描述") + @Schema(description = "描述") private String description; @NotNull(message = "method不能为null") - @ApiModelProperty("API请求方法:GET, HEAD, PUT, POST, DELETE") + @Schema(description = "API请求方法:GET, HEAD, PUT, POST, DELETE") private HttpMethodEnum method; @NotBlank(message = "contentType不能为空") - @ApiModelProperty("HTTP请求的contentType") + @Schema(description = "HTTP请求的contentType") private String contentType; @NotBlank(message = "path不能为空") - @ApiModelProperty("请求路径(不带api前缀)") + @Schema(description = "请求路径(不带api前缀)") private String path; @NotNull(message = "open不能为null") - @ApiModelProperty("是否公开") + @Schema(description = "是否公开") private Boolean open; @NotNull(message = "alarm不能为null") - @ApiModelProperty("是否告警") + @Schema(description = "是否告警") private Boolean alarm; @NotNull(message = "engine不能为null") - @ApiModelProperty("执行引擎:SQL, SCRIPT") + @Schema(description = "执行引擎:SQL, SCRIPT") private ExecuteEngineEnum engine; @NotEmpty(message = "contextList不能为空列表") - @ApiModelProperty("SQL列表") + @Schema(description = "SQL列表") private List contextList; - @ApiModelProperty("接口入参列表") + @Schema(description = "接口入参列表") private List params; - @ApiModelProperty("接口出参列表") + @Schema(description = "接口出参列表") private List outputs; - @ApiModelProperty("接口出参数据类型转换格式") + @Schema(description = "接口出参数据类型转换格式") private List formatMap; @NotNull(message = "namingStrategy不能为null") - @ApiModelProperty("接口出参属性命名策略") + @Schema(description = "接口出参属性命名策略") private NamingStrategyEnum namingStrategy; @NotNull(message = "flowStatus不能为null") - @ApiModelProperty("是否开启流量控制") + @Schema(description = "是否开启流量控制") private Boolean flowStatus; - @ApiModelProperty("阈值类型") + @Schema(description = "阈值类型") private Integer flowGrade; - @ApiModelProperty("单机阈值") + @Schema(description = "单机阈值") private Integer flowCount; - @ApiModelProperty("缓存键类型") + @Schema(description = "缓存键类型") private CacheKeyTypeEnum cacheKeyType; @TableField("缓存表达式") diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiDebugExecuteRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiDebugExecuteRequest.java index 0ff2195..3908b95 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiDebugExecuteRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiDebugExecuteRequest.java @@ -4,36 +4,36 @@ import com.cs.common.dto.ParamValue; import com.cs.common.enums.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; import java.util.List; @Data @NoArgsConstructor -@ApiModel("SQL调试执行") +@Schema(description = "SQL调试执行") public class ApiDebugExecuteRequest { @NotNull(message = "datasourceId不能为null") - @ApiModelProperty("数据源的ID") + @Schema(description = "数据源的ID") private Long dataSourceId; @NotNull(message = "engine不能为null") - @ApiModelProperty("执行引擎:SQL, SCRIPT") + @Schema(description = "执行引擎:SQL, SCRIPT") private ExecuteEngineEnum engine; - @ApiModelProperty("数据类型转换格式") + @Schema(description = "数据类型转换格式") private List formatMap; @NotNull(message = "namingStrategy不能为null") - @ApiModelProperty("属性命名策略") + @Schema(description = "属性命名策略") private NamingStrategyEnum namingStrategy; @NotEmpty(message = "contextList不能为空") - @ApiModelProperty("SQL列表") + @Schema(description = "SQL列表") private List contextList; - @ApiModelProperty("接口入参列表") + @Schema(description = "接口入参列表") private List paramValues; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiModuleAssignments.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiModuleAssignments.java index ab1bfa1..46ff34f 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiModuleAssignments.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiModuleAssignments.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @@ -10,10 +10,10 @@ @Data @NoArgsConstructor @AllArgsConstructor -@ApiModel("模块接口树") +@Schema(description = "模块接口树") public class ApiModuleAssignments extends EntityIdNameResponse { - @ApiModelProperty("接口列表") + @Schema(description = "接口列表") private List children; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ApiOnlineSearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/ApiOnlineSearchRequest.java index a8820ec..f015834 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ApiOnlineSearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ApiOnlineSearchRequest.java @@ -2,16 +2,16 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @Data @NoArgsConstructor -@ApiModel("接口搜索") +@Schema(description = "接口搜索") public class ApiOnlineSearchRequest extends EntitySearchRequest { - @ApiModelProperty("模块ID") + @Schema(description = "模块ID") private List moduleIds; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AppClientDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/AppClientDetailResponse.java index 1d7c545..4be2df8 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AppClientDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AppClientDetailResponse.java @@ -4,51 +4,51 @@ import com.cs.common.enums.*; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @Data @NoArgsConstructor -@ApiModel("客户端应用详情") +@Schema(description = "客户端应用详情") public class AppClientDetailResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("客户端应用名称") + @Schema(description = "客户端应用名称") private String name; - @ApiModelProperty("客户端应用描述") + @Schema(description = "客户端应用描述") private String description; - @ApiModelProperty("应用AppKey账号") + @Schema(description = "应用AppKey账号") private String appKey; - @ApiModelProperty("到期类型") + @Schema(description = "到期类型") private DurationTimeEnum expireDuration; - @ApiModelProperty("到期时间") + @Schema(description = "到期时间") private Long expireAt; - @ApiModelProperty("到期时间(字符串)") + @Schema(description = "到期时间(字符串)") private String expireAtStr; - @ApiModelProperty("过期类型") + @Schema(description = "过期类型") private String expireType; - @ApiModelProperty("是否过期") + @Schema(description = "是否过期") private Boolean isExpired; - @ApiModelProperty("Token生命期") + @Schema(description = "Token生命期") private AliveTimeEnum tokenAlive; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AppClientGroupRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/AppClientGroupRequest.java index c92af7c..9a25963 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AppClientGroupRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AppClientGroupRequest.java @@ -2,21 +2,21 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.List; @Data @NoArgsConstructor -@ApiModel("客户端分组关联") +@Schema(description = "客户端分组关联") public class AppClientGroupRequest { @NotNull(message = "id不能为null") - @ApiModelProperty("客户端应用ID") + @Schema(description = "客户端应用ID") private Long id; - @ApiModelProperty("分组ID列表") + @Schema(description = "分组ID列表") private List groupIds; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AppClientSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/AppClientSaveRequest.java index 2ee4715..aa290c3 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AppClientSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AppClientSaveRequest.java @@ -3,32 +3,32 @@ package com.cs.core.dto; import com.cs.common.enums.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @Data @NoArgsConstructor -@ApiModel("客户端应用信息") +@Schema(description = "客户端应用信息") public class AppClientSaveRequest { @NotBlank(message = "name不能为空") - @ApiModelProperty("客户端应用名称") + @Schema(description = "客户端应用名称") private String name; - @ApiModelProperty("客户端应用描述") + @Schema(description = "客户端应用描述") private String description; @NotBlank(message = "appKey不能为空") - @ApiModelProperty("应用AppKey账号") + @Schema(description = "应用AppKey账号") private String appKey; @NotNull(message = "expireTime不能为null") - @ApiModelProperty("到期时间") + @Schema(description = "到期时间") private ExpireTimeEnum expireTime; @NotNull(message = "tokenAlive不能为null") - @ApiModelProperty("Token存活期") + @Schema(description = "Token存活期") private AliveTimeEnum tokenAlive; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AppClientSearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/AppClientSearchRequest.java index 98a349c..7f754d8 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AppClientSearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AppClientSearchRequest.java @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("客户端应用列表搜索") +@Schema(description = "客户端应用列表搜索") public class AppClientSearchRequest extends EntitySearchRequest { - @ApiModelProperty("分组ID") + @Schema(description = "分组ID") private Long groupId; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AssignmentPublishRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/AssignmentPublishRequest.java index 474516d..1eacb87 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AssignmentPublishRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AssignmentPublishRequest.java @@ -2,20 +2,20 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Data @NoArgsConstructor -@ApiModel("版本发布请求") +@Schema(description = "版本发布请求") public class AssignmentPublishRequest { @NotNull(message = "id不能为null") - @ApiModelProperty("接口ID") + @Schema(description = "接口ID") private Long id; - @ApiModelProperty("版本描述") + @Schema(description = "版本描述") private String description; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/AssignmentSearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/AssignmentSearchRequest.java index 2f0c1ee..3646bbe 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/AssignmentSearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/AssignmentSearchRequest.java @@ -2,23 +2,23 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("列表搜索") +@Schema(description = "列表搜索") public class AssignmentSearchRequest extends EntitySearchRequest { - @ApiModelProperty("是否上线") + @Schema(description = "是否上线") private Boolean online; - @ApiModelProperty("分组ID") + @Schema(description = "分组ID") private Long groupId; - @ApiModelProperty("模块ID") + @Schema(description = "模块ID") private Long moduleId; - @ApiModelProperty("是否公开") + @Schema(description = "是否公开") private Boolean open; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataSourceBaseRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataSourceBaseRequest.java index 7606688..e3ac314 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataSourceBaseRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataSourceBaseRequest.java @@ -4,42 +4,42 @@ import com.cs.common.enums.ProductTypeEnum; import com.cs.persistence.entity.PoolConfig; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @Data @NoArgsConstructor public class DataSourceBaseRequest { @NotBlank(message = "name不能为空") - @ApiModelProperty("名称") + @Schema(description = "名称") private String name; @NotNull(message = "type不能为null") - @ApiModelProperty("类型") + @Schema(description = "类型") private ProductTypeEnum type; @NotBlank(message = "version不能为空") - @ApiModelProperty("驱动版本") + @Schema(description = "驱动版本") private String version; @NotBlank(message = "driver不能为空") - @ApiModelProperty("驱动类型") + @Schema(description = "驱动类型") private String driver; @NotBlank(message = "url不能为空") - @ApiModelProperty("连接JDBC-URL") + @Schema(description = "连接JDBC-URL") private String url; @NotBlank(message = "username不能为空") - @ApiModelProperty("账号") + @Schema(description = "账号") private String username; - @ApiModelProperty("密码") + @Schema(description = "密码") private String password; - @ApiModelProperty("连接池配置") + @Schema(description = "连接池配置") private PoolConfig poolConfig; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataSourceSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataSourceSaveRequest.java index 7260a23..e6705dd 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataSourceSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataSourceSaveRequest.java @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("数据源保存") +@Schema(description = "数据源保存") public class DataSourceSaveRequest extends DataSourceBaseRequest { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskBaseResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskBaseResponse.java index 1e08f37..0d8004a 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskBaseResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskBaseResponse.java @@ -1,7 +1,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @@ -10,30 +10,30 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("数据任务列表项") +@Schema(description = "数据任务列表项") public class DataTaskBaseResponse { - @ApiModelProperty("主键") + @Schema(description = "主键") private Long id; - @ApiModelProperty("任务名称") + @Schema(description = "任务名称") private String name; - @ApiModelProperty("任务描述") + @Schema(description = "任务描述") private String description; - @ApiModelProperty("数据源ID") + @Schema(description = "数据源ID") private Long datasourceId; - @ApiModelProperty("投递实现标识") + @Schema(description = "投递实现标识") private String sinkType; - @ApiModelProperty("是否启用") + @Schema(description = "是否启用") private Boolean enabled; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") private Timestamp createTime; - @ApiModelProperty("修改时间") + @Schema(description = "修改时间") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskDetailResponse.java index 8380380..c4a3bf3 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskDetailResponse.java @@ -4,7 +4,7 @@ import com.cs.common.dto.ItemParam; import com.cs.common.enums.DataTypeFormatEnum; import com.cs.common.enums.NamingStrategyEnum; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @@ -14,54 +14,54 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("数据任务详情") +@Schema(description = "数据任务详情") public class DataTaskDetailResponse { - @ApiModelProperty("主键") + @Schema(description = "主键") private Long id; - @ApiModelProperty("任务名称") + @Schema(description = "任务名称") private String name; - @ApiModelProperty("任务描述") + @Schema(description = "任务描述") private String description; - @ApiModelProperty("数据源ID") + @Schema(description = "数据源ID") private Long datasourceId; - @ApiModelProperty("SQL语句") + @Schema(description = "SQL语句") private String sqlText; - @ApiModelProperty("入参声明") + @Schema(description = "入参声明") private List params; - @ApiModelProperty("结果列命名策略") + @Schema(description = "结果列命名策略") private NamingStrategyEnum namingStrategy; - @ApiModelProperty("出参类型格式化配置") + @Schema(description = "出参类型格式化配置") private List formatMap; - @ApiModelProperty("列改名映射") + @Schema(description = "列改名映射") private Map columnAlias; - @ApiModelProperty("输出列顺序/子集") + @Schema(description = "输出列顺序/子集") private List columnOrder; - @ApiModelProperty("日期/小数等是否按格式化配置转字符串单元格") + @Schema(description = "日期/小数等是否按格式化配置转字符串单元格") private Boolean applyFormatToString; - @ApiModelProperty("是否允许${}原生替换") + @Schema(description = "是否允许${}原生替换") private Boolean dollarAllowed; - @ApiModelProperty("单次投递行数上限") + @Schema(description = "单次投递行数上限") private Long maxRows; - @ApiModelProperty("投递实现标识") + @Schema(description = "投递实现标识") private String sinkType; - @ApiModelProperty("投递私有配置JSON") + @Schema(description = "投递私有配置JSON") private String sinkConfig; - @ApiModelProperty("是否启用") + @Schema(description = "是否启用") private Boolean enabled; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobSearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobSearchRequest.java index 7b0303d..fc7ca42 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobSearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobSearchRequest.java @@ -2,17 +2,17 @@ package com.cs.core.dto; import com.cs.common.enums.DataTaskStatus; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("数据任务执行记录搜索") +@Schema(description = "数据任务执行记录搜索") public class DataTaskJobSearchRequest extends EntitySearchRequest { - @ApiModelProperty("任务定义ID") + @Schema(description = "任务定义ID") private Long defId; - @ApiModelProperty("执行状态") + @Schema(description = "执行状态") private DataTaskStatus status; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobView.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobView.java index d2d7454..d8193a1 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobView.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobView.java @@ -2,7 +2,7 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @@ -12,51 +12,51 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("数据任务执行记录") +@Schema(description = "数据任务执行记录") public class DataTaskJobView { - @ApiModelProperty("执行记录ID") + @Schema(description = "执行记录ID") private Long id; - @ApiModelProperty("任务定义ID") + @Schema(description = "任务定义ID") private Long defId; - @ApiModelProperty("任务名称") + @Schema(description = "任务名称") private String defName; - @ApiModelProperty("执行状态:PENDING/RUNNING/SUCCESS/FAILED/CANCELED") + @Schema(description = "执行状态:PENDING/RUNNING/SUCCESS/FAILED/CANCELED") private String status; - @ApiModelProperty("已处理行数") + @Schema(description = "已处理行数") private Long totalRows; - @ApiModelProperty("取消标记(RUNNING阶段的取消请求)") + @Schema(description = "取消标记(RUNNING阶段的取消请求)") private Boolean cancelRequested; - @ApiModelProperty("产物引用地址(由投递实现返回)") + @Schema(description = "产物引用地址(由投递实现返回)") private String artifactUri; - @ApiModelProperty("产物附加信息") + @Schema(description = "产物附加信息") private Map artifactInfo; - @ApiModelProperty("失败原因") + @Schema(description = "失败原因") private String errorMessage; - @ApiModelProperty("执行的worker地址") + @Schema(description = "执行的worker地址") private String workerAddr; - @ApiModelProperty("提交人") + @Schema(description = "提交人") private String submittedBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @ApiModelProperty("开始时间") + @Schema(description = "开始时间") private Timestamp startTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @ApiModelProperty("结束时间") + @Schema(description = "结束时间") private Timestamp finishTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") private Timestamp createTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskRunRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskRunRequest.java index f4dfe77..3af3d95 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskRunRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskRunRequest.java @@ -1,7 +1,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.Map; @@ -10,15 +10,15 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@ApiModel("数据任务运行请求") +@Schema(description = "数据任务运行请求") public class DataTaskRunRequest { - @ApiModelProperty(value = "任务定义ID", required = true) + @Schema(description = "任务定义ID", requiredMode = Schema.RequiredMode.REQUIRED) private Long defId; - @ApiModelProperty("入参值(与定义的入参声明同名匹配,嵌套对象用嵌套结构或 parent.sub 键)") + @Schema(description = "入参值(与定义的入参声明同名匹配,嵌套对象用嵌套结构或 parent.sub 键)") private Map params; - @ApiModelProperty("仅调试用:预览返回的最大行数(默认50,最大200)") + @Schema(description = "仅调试用:预览返回的最大行数(默认50,最大200)") private Integer previewSize; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSaveRequest.java index 1a2066f..f243f48 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSaveRequest.java @@ -4,7 +4,7 @@ import com.cs.common.dto.ItemParam; import com.cs.common.enums.DataTypeFormatEnum; import com.cs.common.enums.NamingStrategyEnum; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @@ -14,54 +14,54 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@ApiModel("异步数据任务定义") +@Schema(description = "异步数据任务定义") public class DataTaskSaveRequest { - @ApiModelProperty("主键(更新时必传)") + @Schema(description = "主键(更新时必传)") private Long id; - @ApiModelProperty("任务名称(唯一)") + @Schema(description = "任务名称(唯一)") private String name; - @ApiModelProperty("任务描述") + @Schema(description = "任务描述") private String description; - @ApiModelProperty(value = "数据源ID", required = true) + @Schema(description = "数据源ID", requiredMode = Schema.RequiredMode.REQUIRED) private Long datasourceId; - @ApiModelProperty(value = "SQL语句(支持MyBatis动态标签与#{}占位符)", required = true) + @Schema(description = "SQL语句(支持MyBatis动态标签与#{}占位符)", requiredMode = Schema.RequiredMode.REQUIRED) private String sqlText; - @ApiModelProperty("入参声明") + @Schema(description = "入参声明") private List params; - @ApiModelProperty("结果列命名策略") + @Schema(description = "结果列命名策略") private NamingStrategyEnum namingStrategy; - @ApiModelProperty("出参类型格式化配置") + @Schema(description = "出参类型格式化配置") private List formatMap; - @ApiModelProperty("列改名映射:key为命名策略转换后的列名,value为输出别名") + @Schema(description = "列改名映射:key为命名策略转换后的列名,value为输出别名") private Map columnAlias; - @ApiModelProperty("输出列顺序/子集:空表示保留全部原始顺序") + @Schema(description = "输出列顺序/子集:空表示保留全部原始顺序") private List columnOrder; - @ApiModelProperty("是否将日期/小数等按格式化配置转为字符串单元格") + @Schema(description = "是否将日期/小数等按格式化配置转为字符串单元格") private Boolean applyFormatToString; - @ApiModelProperty("是否允许${}原生替换(默认禁止)") + @Schema(description = "是否允许${}原生替换(默认禁止)") private Boolean dollarAllowed; - @ApiModelProperty("单次投递行数上限,<=0 使用引擎默认上限") + @Schema(description = "单次投递行数上限,<=0 使用引擎默认上限") private Long maxRows; - @ApiModelProperty(value = "投递实现标识(由扩展方提供)", required = true) + @Schema(description = "投递实现标识(由扩展方提供)", requiredMode = Schema.RequiredMode.REQUIRED) private String sinkType; - @ApiModelProperty("投递实现的私有配置JSON(原样透传;请勿存放明文口令)") + @Schema(description = "投递实现的私有配置JSON(原样透传;请勿存放明文口令)") private String sinkConfig; - @ApiModelProperty("是否启用") + @Schema(description = "是否启用") private Boolean enabled; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSearchRequest.java index f4d841e..682cad8 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DataTaskSearchRequest.java @@ -1,14 +1,14 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("数据任务列表搜索") +@Schema(description = "数据任务列表搜索") public class DataTaskSearchRequest extends EntitySearchRequest { - @ApiModelProperty("启用状态过滤(null 表示不过滤)") + @Schema(description = "启用状态过滤(null 表示不过滤)") private Boolean enabled; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDetailResponse.java index eb0a632..8aab4e5 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDetailResponse.java @@ -2,23 +2,23 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @NoArgsConstructor @Data -@ApiModel("数据库类型") +@Schema(description = "数据库类型") public class DatabaseTypeDetailResponse { - @ApiModelProperty("编号") + @Schema(description = "编号") private Integer id; - @ApiModelProperty("数据库类型") + @Schema(description = "数据库类型") private String type; - @ApiModelProperty("驱动类") + @Schema(description = "驱动类") private String driver; - @ApiModelProperty("连接串样例") + @Schema(description = "连接串样例") private String sample; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDriverResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDriverResponse.java index 2fca89a..1cb7752 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDriverResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DatabaseTypeDriverResponse.java @@ -2,25 +2,25 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @Data @Builder -@ApiModel("驱动版本") +@Schema(description = "驱动版本") public class DatabaseTypeDriverResponse { - @ApiModelProperty("驱动版本") + @Schema(description = "驱动版本") private String driverVersion; - @ApiModelProperty("驱动类名") + @Schema(description = "驱动类名") private String driverClass; - @ApiModelProperty("版本路径") + @Schema(description = "版本路径") private String driverPath; - @ApiModelProperty("驱动JAR") + @Schema(description = "驱动JAR") private List jarFiles; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/DatasourceDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/DatasourceDetailResponse.java index 322b6dc..de33075 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/DatasourceDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/DatasourceDetailResponse.java @@ -5,48 +5,48 @@ import com.cs.common.enums.ProductTypeEnum; import com.cs.persistence.entity.PoolConfig; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @NoArgsConstructor @Data -@ApiModel("数据源详情") +@Schema(description = "数据源详情") public class DatasourceDetailResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("标题") + @Schema(description = "标题") private String name; - @ApiModelProperty("数据库类型") + @Schema(description = "数据库类型") private ProductTypeEnum type; - @ApiModelProperty("驱动版本") + @Schema(description = "驱动版本") private String version; - @ApiModelProperty("驱动类") + @Schema(description = "驱动类") private String driver; - @ApiModelProperty("URL连接串") + @Schema(description = "URL连接串") private String url; - @ApiModelProperty("账号名") + @Schema(description = "账号名") private String username; - @ApiModelProperty("密码") + @Schema(description = "密码") private String password; - @ApiModelProperty("连接池配置") + @Schema(description = "连接池配置") private PoolConfig poolConfig; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/EntityIdNameResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/EntityIdNameResponse.java index 137521f..f7ba60f 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/EntityIdNameResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/EntityIdNameResponse.java @@ -2,18 +2,18 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor @AllArgsConstructor -@ApiModel("带ID的名称") +@Schema(description = "带ID的名称") public class EntityIdNameResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("名称") + @Schema(description = "名称") private String name; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/EntitySearchRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/EntitySearchRequest.java index 8f2e898..ffcb236 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/EntitySearchRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/EntitySearchRequest.java @@ -2,21 +2,21 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("列表搜索") +@Schema(description = "列表搜索") public class EntitySearchRequest { - @ApiModelProperty("页号") + @Schema(description = "页号") private Integer page; - @ApiModelProperty("页大小") + @Schema(description = "页大小") private Integer size; - @ApiModelProperty("关键词") + @Schema(description = "关键词") private String searchText; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleDetailResponse.java index 91570d6..13d11dd 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleDetailResponse.java @@ -3,33 +3,33 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @NoArgsConstructor @Data -@ApiModel("流控规则详情") +@Schema(description = "流控规则详情") public class FlowRuleDetailResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("标题") + @Schema(description = "标题") private String name; - @ApiModelProperty("阈值类型") + @Schema(description = "阈值类型") private Integer flowGrade; - @ApiModelProperty("单机阈值") + @Schema(description = "单机阈值") private Integer flowCount; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleSaveRequest.java index 6766560..7f59120 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/FlowRuleSaveRequest.java @@ -2,23 +2,23 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @NoArgsConstructor @Data -@ApiModel("流控规则详情") +@Schema(description = "流控规则详情") public class FlowRuleSaveRequest { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("标题") + @Schema(description = "标题") private String name; - @ApiModelProperty("阈值类型") + @Schema(description = "阈值类型") private Integer flowGrade; - @ApiModelProperty("单机阈值") + @Schema(description = "单机阈值") private Integer flowCount; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/McpServerAddrResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/McpServerAddrResponse.java index c1cbcae..78a7a19 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/McpServerAddrResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/McpServerAddrResponse.java @@ -2,19 +2,25 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("MCP服务端地址前缀") +@Schema(description = "MCP服务端地址前缀") public class McpServerAddrResponse { - @ApiModelProperty("SSE地址的路径") + @Schema(description = "SSE地址的路径") private String sseAddrPrefix; - @ApiModelProperty("StreamHttp地址的路径") + @Schema(description = "StreamHttp地址的路径") private String streamAddrPrefix; + + @Schema(description = "管理MCP服务SSE地址的路径(需manage权限令牌)") + private String adminSseAddrPrefix; + + @Schema(description = "管理MCP服务StreamHttp地址的路径(需manage权限令牌)") + private String adminStreamAddrPrefix; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/McpToolResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/McpToolResponse.java index 5941d01..d50a768 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/McpToolResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/McpToolResponse.java @@ -3,7 +3,7 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @@ -12,41 +12,41 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("MCP工具详情") +@Schema(description = "MCP工具详情") public class McpToolResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("工具名称") + @Schema(description = "工具名称") private String name; - @ApiModelProperty("工具描述") + @Schema(description = "工具描述") private String description; - @ApiModelProperty("接口模块ID") + @Schema(description = "接口模块ID") private Long moduleId; - @ApiModelProperty("接口模块名称") + @Schema(description = "接口模块名称") private String moduleName; - @ApiModelProperty("接口ID") + @Schema(description = "接口ID") private Long apiId; - @ApiModelProperty("接口名称") + @Schema(description = "接口名称") private String apiName; - @ApiModelProperty("接口Method") + @Schema(description = "接口Method") private String apiMethod; - @ApiModelProperty("接口Path") + @Schema(description = "接口Path") private String apiPath; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/McpToolSaveRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/McpToolSaveRequest.java index b186bae..f6e3dc2 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/McpToolSaveRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/McpToolSaveRequest.java @@ -2,28 +2,28 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @Data @NoArgsConstructor -@ApiModel("MCP工具配置") +@Schema(description = "MCP工具配置") public class McpToolSaveRequest { - @ApiModelProperty("ID编号(保存接口使用)") + @Schema(description = "ID编号(保存接口使用)") private Long id; @NotNull(message = "apiId不能为null") - @ApiModelProperty("API的ID") + @Schema(description = "API的ID") private Long apiId; @NotBlank(message = "name不能为空") - @ApiModelProperty("工具名称") + @Schema(description = "工具名称") private String name; @NotBlank(message = "description不能为空") - @ApiModelProperty("工具描述") + @Schema(description = "工具描述") private String description; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/MetadataColumnResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/MetadataColumnResponse.java index fa95d99..e61f69e 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/MetadataColumnResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/MetadataColumnResponse.java @@ -2,22 +2,22 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("列信息") +@Schema(description = "列信息") public class MetadataColumnResponse { - @ApiModelProperty("列名") + @Schema(description = "列名") private String name; - @ApiModelProperty("列类型") + @Schema(description = "列类型") private String type; - @ApiModelProperty("注释") + @Schema(description = "注释") private String remarks; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/NameValueBaseResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/NameValueBaseResponse.java index 22df949..697bc0b 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/NameValueBaseResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/NameValueBaseResponse.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import lombok.experimental.SuperBuilder; @@ -10,12 +10,12 @@ @SuperBuilder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -@ApiModel("枚举键值") +@Schema(description = "枚举键值") public class NameValueBaseResponse { - @ApiModelProperty("枚举至") + @Schema(description = "枚举至") private String key; - @ApiModelProperty("说明") + @Schema(description = "说明") private String value; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/NameValueRemarkResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/NameValueRemarkResponse.java index 095df2d..4325c14 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/NameValueRemarkResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/NameValueRemarkResponse.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import lombok.experimental.SuperBuilder; @@ -10,9 +10,9 @@ @SuperBuilder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -@ApiModel("带说明的枚举键值") +@Schema(description = "带说明的枚举键值") public class NameValueRemarkResponse extends NameValueBaseResponse { - @ApiModelProperty("注释") + @Schema(description = "注释") private String remark; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/ScriptEditorCompletion.java b/datapoly-core/src/main/java/com/cs/core/dto/ScriptEditorCompletion.java index ca31a8d..1ba03db 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/ScriptEditorCompletion.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/ScriptEditorCompletion.java @@ -2,26 +2,26 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("脚本编辑器提示列表") +@Schema(description = "脚本编辑器提示列表") public class ScriptEditorCompletion { - @ApiModelProperty("返回值类型") + @Schema(description = "返回值类型") private String meta; - @ApiModelProperty("下拉提示") + @Schema(description = "下拉提示") private String caption; - @ApiModelProperty("选择填充") + @Schema(description = "选择填充") private String value; @Builder.Default - @ApiModelProperty("分数值") + @Schema(description = "分数值") private Integer score = 1; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/SelectedEntityIdName.java b/datapoly-core/src/main/java/com/cs/core/dto/SelectedEntityIdName.java index 4bff812..63c7626 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/SelectedEntityIdName.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/SelectedEntityIdName.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @@ -11,12 +11,12 @@ @AllArgsConstructor public class SelectedEntityIdName { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("名称") + @Schema(description = "名称") private String name; - @ApiModelProperty("是否选中") + @Schema(description = "是否选中") private Boolean selected; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/SqlParamParseResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/SqlParamParseResponse.java index d213686..f02b82d 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/SqlParamParseResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/SqlParamParseResponse.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.*; @@ -10,16 +10,16 @@ @Data @NoArgsConstructor @AllArgsConstructor -@ApiModel("SQL参数解析") +@Schema(description = "SQL参数解析") public class SqlParamParseResponse { - @ApiModelProperty("参数名") + @Schema(description = "参数名") private String name; - @ApiModelProperty("是否为数组") + @Schema(description = "是否为数组") private Boolean isArray; - @ApiModelProperty("Object类型的子元素") + @Schema(description = "Object类型的子元素") private List children; public SqlParamParseResponse(String name, Boolean isArray) { diff --git a/datapoly-core/src/main/java/com/cs/core/dto/SystemUserDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/SystemUserDetailResponse.java index e1116fb..f390e73 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/SystemUserDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/SystemUserDetailResponse.java @@ -3,39 +3,39 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @Data @NoArgsConstructor -@ApiModel("系统用户") +@Schema(description = "系统用户") public class SystemUserDetailResponse { - @ApiModelProperty("ID编号") + @Schema(description = "ID编号") private Long id; - @ApiModelProperty("登陆名") + @Schema(description = "登陆名") private String username; - @ApiModelProperty("实际名") + @Schema(description = "实际名") private String realName; - @ApiModelProperty("电子邮箱") + @Schema(description = "电子邮箱") private String email; - @ApiModelProperty("地址") + @Schema(description = "地址") private String address; - @ApiModelProperty("是否锁定") + @Schema(description = "是否锁定") private Boolean locked; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; - @ApiModelProperty("更新时间") + @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp updateTime; diff --git a/datapoly-core/src/main/java/com/cs/core/dto/TestAlarmConfigRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/TestAlarmConfigRequest.java index 42489e5..af31d2e 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/TestAlarmConfigRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/TestAlarmConfigRequest.java @@ -2,33 +2,33 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; import java.util.List; @Data @NoArgsConstructor -@ApiModel("统一告警配置") +@Schema(description = "统一告警配置") public class TestAlarmConfigRequest { @NotBlank(message = "endpoint不能为空") @Pattern(regexp = "^https?://\\S+$", message = "endpoint必须是http/https URL") - @ApiModelProperty("接口端点") + @Schema(description = "接口端点") private String endpoint; @NotBlank(message = "contentType不能为空") @Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*$", message = "contentType必须是合法的 type/subtype(不允许携带参数)") - @ApiModelProperty("入参格式类型") + @Schema(description = "入参格式类型") private String contentType; @NotBlank(message = "inputTemplate不能为空") - @ApiModelProperty("入参模板") + @Schema(description = "入参模板") private String inputTemplate; @NotEmpty(message = "dataModel不能为空") - @ApiModelProperty("示例数据") + @Schema(description = "示例数据") private List dataModel; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/TopologyNodeResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/TopologyNodeResponse.java index f52e04d..a93ef4b 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/TopologyNodeResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/TopologyNodeResponse.java @@ -3,38 +3,38 @@ package com.cs.core.dto; import com.cs.common.enums.NodeStatusEnum; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor -@ApiModel("拓扑节点信息") +@Schema(description = "拓扑节点信息") public class TopologyNodeResponse { - @ApiModelProperty("服务ID") + @Schema(description = "服务ID") private String serviceId; - @ApiModelProperty("实例ID") + @Schema(description = "实例ID") private String instanceId; - @ApiModelProperty("主机地址") + @Schema(description = "主机地址") private String host; - @ApiModelProperty("端口号") + @Schema(description = "端口号") private Integer port; - @ApiModelProperty("内存使用") + @Schema(description = "内存使用") private Integer memory; - @ApiModelProperty("CPU使用") + @Schema(description = "CPU使用") private Integer cpu; - @ApiModelProperty("存储使用") + @Schema(description = "存储使用") private Integer disk; - @ApiModelProperty("节点状态") + @Schema(description = "节点状态") private NodeStatusEnum status; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/UpdateAlarmConfigRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/UpdateAlarmConfigRequest.java index bff2e07..c3e0383 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/UpdateAlarmConfigRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/UpdateAlarmConfigRequest.java @@ -3,32 +3,32 @@ package com.cs.core.dto; import com.cs.common.enums.OnOffEnum; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @Data @NoArgsConstructor -@ApiModel("统一告警配置") +@Schema(description = "统一告警配置") public class UpdateAlarmConfigRequest { @NotNull(message = "status不能为null") - @ApiModelProperty("开启状态") + @Schema(description = "开启状态") private OnOffEnum status; @NotBlank(message = "endpoint不能为空") @Pattern(regexp = "^https?://\\S+$", message = "endpoint必须是http/https URL") - @ApiModelProperty("接口端点") + @Schema(description = "接口端点") private String endpoint; @NotBlank(message = "contentType不能为空") @Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*$", message = "contentType必须是合法的 type/subtype(不允许携带参数)") - @ApiModelProperty("入参格式类型") + @Schema(description = "入参格式类型") private String contentType; @NotBlank(message = "inputTemplate不能为空") - @ApiModelProperty("入参模板") + @Schema(description = "入参模板") private String inputTemplate; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/UpdateFirewallRulesRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/UpdateFirewallRulesRequest.java index 27f97f8..35e0f8c 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/UpdateFirewallRulesRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/UpdateFirewallRulesRequest.java @@ -3,23 +3,23 @@ package com.cs.core.dto; import com.cs.common.enums.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Data @NoArgsConstructor -@ApiModel("防火墙规则") +@Schema(description = "防火墙规则") public class UpdateFirewallRulesRequest { @NotNull(message = "status不能为null") - @ApiModelProperty("开启状态") + @Schema(description = "开启状态") private OnOffEnum status; - @ApiModelProperty("黑白名单选项") + @Schema(description = "黑白名单选项") private WhiteBlackEnum mode; - @ApiModelProperty("地址列表") + @Schema(description = "地址列表") private String addresses; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/UserLoginRequest.java b/datapoly-core/src/main/java/com/cs/core/dto/UserLoginRequest.java index 694bb42..7e0f248 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/UserLoginRequest.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/UserLoginRequest.java @@ -2,21 +2,21 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @Data @NoArgsConstructor -@ApiModel("账号登陆请求") +@Schema(description = "账号登陆请求") public class UserLoginRequest { @NotBlank(message = "username不能为空") - @ApiModelProperty("账号") + @Schema(description = "账号") private String username; @NotBlank(message = "password不能为空") - @ApiModelProperty("密码") + @Schema(description = "密码") private String password; } diff --git a/datapoly-core/src/main/java/com/cs/core/dto/VersionCommitResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/VersionCommitResponse.java index 523342a..248b9d0 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/VersionCommitResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/VersionCommitResponse.java @@ -3,32 +3,32 @@ package com.cs.core.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.sql.Timestamp; @Data @NoArgsConstructor -@ApiModel("版本记录详情") +@Schema(description = "版本记录详情") public class VersionCommitResponse { - @ApiModelProperty("提交记录ID") + @Schema(description = "提交记录ID") private Long commitId; - @ApiModelProperty("版本号") + @Schema(description = "版本号") private Integer version; - @ApiModelProperty("版本描述") + @Schema(description = "版本描述") private String description; - @ApiModelProperty("接口ID") + @Schema(description = "接口ID") private Long apiId; - @ApiModelProperty("是否在线") + @Schema(description = "是否在线") private Boolean online; - @ApiModelProperty("创建时间") + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Timestamp createTime; diff --git a/datapoly-core/src/main/java/com/cs/core/dto/VersionDetailResponse.java b/datapoly-core/src/main/java/com/cs/core/dto/VersionDetailResponse.java index 620c147..f258f2e 100644 --- a/datapoly-core/src/main/java/com/cs/core/dto/VersionDetailResponse.java +++ b/datapoly-core/src/main/java/com/cs/core/dto/VersionDetailResponse.java @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license package com.cs.core.dto; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @Data @NoArgsConstructor -@ApiModel("版本记录详情") +@Schema(description = "版本记录详情") public class VersionDetailResponse extends VersionCommitResponse { - @ApiModelProperty("版本详情") + @Schema(description = "版本详情") private ApiAssignmentDetailResponse detail; } diff --git a/datapoly-core/src/main/java/com/cs/core/exec/ApiExecuteService.java b/datapoly-core/src/main/java/com/cs/core/exec/ApiExecuteService.java index 1a0d158..5a3e3f2 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/ApiExecuteService.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/ApiExecuteService.java @@ -25,8 +25,8 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.io.*; import java.nio.charset.*; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/exec/ExecutorMetadataCache.java b/datapoly-core/src/main/java/com/cs/core/exec/ExecutorMetadataCache.java index d8aa92c..9d32268 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/ExecutorMetadataCache.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/ExecutorMetadataCache.java @@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; diff --git a/datapoly-core/src/main/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractor.java b/datapoly-core/src/main/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractor.java index 47e86c5..6c64d9c 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractor.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractor.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.*; -import javax.servlet.ServletInputStream; +import jakarta.servlet.ServletInputStream; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/exec/extractor/HttpRequestBodyExtractor.java b/datapoly-core/src/main/java/com/cs/core/exec/extractor/HttpRequestBodyExtractor.java index 6537ba0..7b4fb78 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/extractor/HttpRequestBodyExtractor.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/extractor/HttpRequestBodyExtractor.java @@ -4,7 +4,7 @@ import org.springframework.http.MediaType; -import javax.servlet.ServletInputStream; +import jakarta.servlet.ServletInputStream; import java.nio.charset.Charset; import java.util.Map; diff --git a/datapoly-core/src/main/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractor.java b/datapoly-core/src/main/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractor.java index 60ef55f..5c27b3f 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractor.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractor.java @@ -9,7 +9,7 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import javax.servlet.ServletInputStream; +import jakarta.servlet.ServletInputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/exec/module/CacheVarModule.java b/datapoly-core/src/main/java/com/cs/core/exec/module/CacheVarModule.java index d3f6f70..5ebb889 100644 --- a/datapoly-core/src/main/java/com/cs/core/exec/module/CacheVarModule.java +++ b/datapoly-core/src/main/java/com/cs/core/exec/module/CacheVarModule.java @@ -9,7 +9,7 @@ import com.cs.core.exec.annotation.Module; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.concurrent.TimeUnit; @Service diff --git a/datapoly-core/src/main/java/com/cs/core/executor/AlarmHttpRequestFactory.java b/datapoly-core/src/main/java/com/cs/core/executor/AlarmHttpRequestFactory.java index 54be451..851eb7d 100644 --- a/datapoly-core/src/main/java/com/cs/core/executor/AlarmHttpRequestFactory.java +++ b/datapoly-core/src/main/java/com/cs/core/executor/AlarmHttpRequestFactory.java @@ -3,15 +3,17 @@ package com.cs.core.executor; import lombok.extern.slf4j.Slf4j; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.impl.client.*; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.core5.util.Timeout; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; /** * HTTP client for the alarm webhook. Uses the JVM default TLS trust store and hostname * verification — alarm payloads carry business data and must not be interceptable via a - * trust-all socket factory. + * trust-all socket factory. Built on Apache HttpClient 5 (Spring 6 requires client5). */ @Slf4j public class AlarmHttpRequestFactory extends HttpComponentsClientHttpRequestFactory { @@ -28,17 +30,17 @@ public AlarmHttpRequestFactory() { } private void init() { - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(); - manager.setMaxTotal(maxConnectionSize); - manager.setDefaultMaxPerRoute(maxPerRoute); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)) + .setResponseTimeout(Timeout.ofMilliseconds(socketTimeout)) + .setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout)) + .build(); CloseableHttpClient httpClient = HttpClients.custom() - .setConnectionManager(manager) - .setDefaultRequestConfig( - RequestConfig.custom() - .setConnectTimeout(connectTimeout) - .setSocketTimeout(socketTimeout) - .setConnectionRequestTimeout(connectionRequestTimeout) - .build()) + .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() + .setMaxConnTotal(maxConnectionSize) + .setMaxConnPerRoute(maxPerRoute) + .build()) + .setDefaultRequestConfig(requestConfig) .build(); setHttpClient(httpClient); } diff --git a/datapoly-core/src/main/java/com/cs/core/executor/DataSourceCleanService.java b/datapoly-core/src/main/java/com/cs/core/executor/DataSourceCleanService.java index 0e48a3d..3a2416e 100644 --- a/datapoly-core/src/main/java/com/cs/core/executor/DataSourceCleanService.java +++ b/datapoly-core/src/main/java/com/cs/core/executor/DataSourceCleanService.java @@ -10,7 +10,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.Set; @Slf4j diff --git a/datapoly-core/src/main/java/com/cs/core/executor/UnifyAlarmOpsService.java b/datapoly-core/src/main/java/com/cs/core/executor/UnifyAlarmOpsService.java index 52e7cf8..1bf9e59 100644 --- a/datapoly-core/src/main/java/com/cs/core/executor/UnifyAlarmOpsService.java +++ b/datapoly-core/src/main/java/com/cs/core/executor/UnifyAlarmOpsService.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.net.URI; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/gateway/FirewallFilterService.java b/datapoly-core/src/main/java/com/cs/core/gateway/FirewallFilterService.java index ea0c7bb..498313f 100644 --- a/datapoly-core/src/main/java/com/cs/core/gateway/FirewallFilterService.java +++ b/datapoly-core/src/main/java/com/cs/core/gateway/FirewallFilterService.java @@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import javax.annotation.*; +import jakarta.annotation.*; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/service/ApiAssignmentService.java b/datapoly-core/src/main/java/com/cs/core/service/ApiAssignmentService.java index ad2b405..b3ea568 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/ApiAssignmentService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/ApiAssignmentService.java @@ -32,8 +32,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import java.io.*; import java.lang.reflect.Method; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/service/ApiGroupService.java b/datapoly-core/src/main/java/com/cs/core/service/ApiGroupService.java index 5c048bd..595e073 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/ApiGroupService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/ApiGroupService.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Service public class ApiGroupService { diff --git a/datapoly-core/src/main/java/com/cs/core/service/ApiModuleService.java b/datapoly-core/src/main/java/com/cs/core/service/ApiModuleService.java index a136884..ee76fec 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/ApiModuleService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/ApiModuleService.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/service/AppClientService.java b/datapoly-core/src/main/java/com/cs/core/service/AppClientService.java index ebb24ef..392c683 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/AppClientService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/AppClientService.java @@ -17,7 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/service/DataSourceService.java b/datapoly-core/src/main/java/com/cs/core/service/DataSourceService.java index 8c90a74..a4f8f4e 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/DataSourceService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/DataSourceService.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.io.File; import java.sql.*; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/service/ExportImportService.java b/datapoly-core/src/main/java/com/cs/core/service/ExportImportService.java index f2e65cb..8788c1c 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/ExportImportService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/ExportImportService.java @@ -15,8 +15,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; diff --git a/datapoly-core/src/main/java/com/cs/core/service/OverviewService.java b/datapoly-core/src/main/java/com/cs/core/service/OverviewService.java index ab68d3b..c57024d 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/OverviewService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/OverviewService.java @@ -13,7 +13,7 @@ import com.cs.persistence.util.PageUtils; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/service/SystemParamService.java b/datapoly-core/src/main/java/com/cs/core/service/SystemParamService.java index e4ccdd6..1bb146a 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/SystemParamService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/SystemParamService.java @@ -9,7 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Slf4j @Service diff --git a/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java b/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java index 4dc52a4..dcbbc7a 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java @@ -12,8 +12,8 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Objects; diff --git a/datapoly-core/src/main/java/com/cs/core/servlet/ApiSwaggerService.java b/datapoly-core/src/main/java/com/cs/core/servlet/ApiSwaggerService.java index 74c7608..edbf7c4 100644 --- a/datapoly-core/src/main/java/com/cs/core/servlet/ApiSwaggerService.java +++ b/datapoly-core/src/main/java/com/cs/core/servlet/ApiSwaggerService.java @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/datapoly-core/src/main/java/com/cs/core/servlet/AuthenticationFilter.java b/datapoly-core/src/main/java/com/cs/core/servlet/AuthenticationFilter.java index 2ae37ef..ded43dd 100644 --- a/datapoly-core/src/main/java/com/cs/core/servlet/AuthenticationFilter.java +++ b/datapoly-core/src/main/java/com/cs/core/servlet/AuthenticationFilter.java @@ -23,9 +23,9 @@ import org.springframework.http.*; import org.springframework.stereotype.Component; -import javax.annotation.Resource; -import javax.servlet.*; -import javax.servlet.http.*; +import jakarta.annotation.Resource; +import jakarta.servlet.*; +import jakarta.servlet.http.*; import java.io.IOException; import java.util.Map; import java.util.concurrent.ExecutorService; diff --git a/datapoly-core/src/main/java/com/cs/core/servlet/ClientTokenService.java b/datapoly-core/src/main/java/com/cs/core/servlet/ClientTokenService.java index 00d5524..e2ea3ef 100644 --- a/datapoly-core/src/main/java/com/cs/core/servlet/ClientTokenService.java +++ b/datapoly-core/src/main/java/com/cs/core/servlet/ClientTokenService.java @@ -21,7 +21,7 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.sql.Timestamp; diff --git a/datapoly-core/src/main/java/com/cs/core/util/ResponseWriteUtils.java b/datapoly-core/src/main/java/com/cs/core/util/ResponseWriteUtils.java index c624c4e..3e38d4d 100644 --- a/datapoly-core/src/main/java/com/cs/core/util/ResponseWriteUtils.java +++ b/datapoly-core/src/main/java/com/cs/core/util/ResponseWriteUtils.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** diff --git a/datapoly-core/src/main/java/com/cs/core/util/ServletUtils.java b/datapoly-core/src/main/java/com/cs/core/util/ServletUtils.java index 10ab01b..c1de31b 100644 --- a/datapoly-core/src/main/java/com/cs/core/util/ServletUtils.java +++ b/datapoly-core/src/main/java/com/cs/core/util/ServletUtils.java @@ -6,7 +6,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.request.*; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; /** * Utilities for reading HTTP parameters from the servlet container. diff --git a/datapoly-core/src/main/java/com/cs/core/util/SpelUtils.java b/datapoly-core/src/main/java/com/cs/core/util/SpelUtils.java index 5911f49..26aa6cc 100644 --- a/datapoly-core/src/main/java/com/cs/core/util/SpelUtils.java +++ b/datapoly-core/src/main/java/com/cs/core/util/SpelUtils.java @@ -21,8 +21,9 @@ public final class SpelUtils { * on evaluation failure the original expression text is returned as a fallback (same as the existing behavior). */ public static String getExpressionValue(String expr, Map paramValues) { - EvaluationContext context = new SimpleEvaluationContext.Builder( - DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor()).build(); + EvaluationContext context = SimpleEvaluationContext + .forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor()) + .build(); for (Map.Entry entry : paramValues.entrySet()) { context.setVariable(entry.getKey(), entry.getValue()); } diff --git a/datapoly-core/src/test/java/com/cs/core/JacksonUtilsTest.java b/datapoly-core/src/test/java/com/cs/core/JacksonUtilsTest.java deleted file mode 100644 index e3a52c8..0000000 --- a/datapoly-core/src/test/java/com/cs/core/JacksonUtilsTest.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright tang. All rights reserved. -// Use of this source code is governed by a BSD-style license -package com.cs.core; - -import com.cs.common.enums.DataTypeFormatEnum; -import com.cs.core.util.JacksonUtils; -import com.fasterxml.jackson.databind.*; -import org.junit.*; - -import java.time.LocalDateTime; -import java.util.*; - -public class JacksonUtilsTest { - - /** - * Without a Spring container (plain unit test), falls back to the default timezone and serializes normally - */ - @Test - public void testToJsonStrWithoutFormatMap() { - Map result = new LinkedHashMap<>(); - result.put("user_name", "test"); - result.put("user_age", 23); - result.put("user_sex", 1); - String json = JacksonUtils.toJsonStr(result, Collections.emptyMap()); - Assert.assertEquals("{\"user_name\":\"test\",\"user_age\":23,\"user_sex\":1}", json); - } - - @Test - public void testToJsonStrWithDatetimeFormat() { - Map formatMap = new HashMap<>(); - formatMap.put(DataTypeFormatEnum.LOCAL_DATE_TIME, "yyyy/MM/dd HH:mm:ss"); - Map result = new LinkedHashMap<>(); - result.put("created", LocalDateTime.of(2026, 8, 25, 10, 15, 30)); - String json = JacksonUtils.toJsonStr(result, formatMap); - Assert.assertEquals("{\"created\":\"2026/08/25 10:15:30\"}", json); - } - - @Test - public void testJsonStrToMap() throws Exception { - String jsonString = "{\"user_sex\":1,\"user_name\":\"test\",\"user_age\":23}"; - - ObjectMapper mapper = new ObjectMapper(); - JsonNode rootNode = mapper.readTree(jsonString); - Map resultMap = new HashMap<>(); - rootNode.fields().forEachRemaining(entry -> resultMap.put(entry.getKey(), - mapper.convertValue(entry.getValue(), Object.class))); - Assert.assertEquals(3, resultMap.size()); - Assert.assertEquals("test", resultMap.get("user_name")); - Assert.assertEquals(23, resultMap.get("user_age")); - Assert.assertEquals(1, resultMap.get("user_sex")); - } -} diff --git a/datapoly-dist/pom.xml b/datapoly-dist/pom.xml index be95f4a..258ea76 100644 --- a/datapoly-dist/pom.xml +++ b/datapoly-dist/pom.xml @@ -17,7 +17,7 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.2.0 true @@ -25,7 +25,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.2.0 true @@ -33,7 +33,7 @@ org.apache.maven.plugins maven-assembly-plugin - 2.6 + 3.8.0 package diff --git a/datapoly-executor/pom.xml b/datapoly-executor/pom.xml index 8b42731..6319084 100644 --- a/datapoly-executor/pom.xml +++ b/datapoly-executor/pom.xml @@ -33,15 +33,25 @@ spring-cloud-starter-netflix-eureka-client + + + com.netflix.eureka + eureka-client-jersey3 + + com.alibaba.cloud spring-cloud-starter-alibaba-sentinel + + + io.swagger.core.v3 + swagger-models-jakarta + - junit - junit - test + io.swagger.core.v3 + swagger-annotations-jakarta diff --git a/datapoly-executor/src/main/java/com/cs/executor/config/EurekaTransportConfiguration.java b/datapoly-executor/src/main/java/com/cs/executor/config/EurekaTransportConfiguration.java new file mode 100644 index 0000000..9cd2b24 --- /dev/null +++ b/datapoly-executor/src/main/java/com/cs/executor/config/EurekaTransportConfiguration.java @@ -0,0 +1,27 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.executor.config; + +import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; +import com.netflix.discovery.shared.transport.jersey3.Jersey3TransportClientFactories; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Cloud 2025 的 eureka-client 自动装配在 jersey 在场时不再自带 TransportClientFactories + * (仅 eureka-server 侧的 EurekaServerJerseyClientAutoConfiguration 提供),纯客户端服务 + * 须自备该 bean,否则 RefreshableEurekaClientConfiguration 因缺 bean 启动失败。 + * manager 无需此类(eureka-server 依赖自带装配)。 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Jersey3TransportClientFactories.class) +public class EurekaTransportConfiguration { + + @Bean + @ConditionalOnMissingBean(TransportClientFactories.class) + public TransportClientFactories jersey3TransportClientFactories() { + return Jersey3TransportClientFactories.getInstance(); + } +} diff --git a/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorServletConfig.java b/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorServletConfig.java index 5286e93..18761fb 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorServletConfig.java +++ b/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorServletConfig.java @@ -13,8 +13,8 @@ import org.springframework.context.annotation.*; import org.springframework.scheduling.annotation.EnableScheduling; -import javax.annotation.Resource; -import javax.servlet.http.HttpServlet; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServlet; @Slf4j @EnableScheduling diff --git a/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorWebConfig.java b/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorWebConfig.java index 4e3b955..4b562d6 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorWebConfig.java +++ b/datapoly-executor/src/main/java/com/cs/executor/config/ExecutorWebConfig.java @@ -8,7 +8,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Configuration public class ExecutorWebConfig implements WebMvcConfigurer { diff --git a/datapoly-executor/src/main/java/com/cs/executor/controller/ApiSwaggerController.java b/datapoly-executor/src/main/java/com/cs/executor/controller/ApiSwaggerController.java index 1af4e9c..6f5cdfa 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/controller/ApiSwaggerController.java +++ b/datapoly-executor/src/main/java/com/cs/executor/controller/ApiSwaggerController.java @@ -4,42 +4,50 @@ import com.cs.common.consts.Constants; import com.cs.core.servlet.ApiSwaggerService; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.models.OpenAPI; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; -import springfox.documentation.spring.web.json.*; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.util.*; -@Api(tags = {"Swagger接口文档"}) +@Tag(name = "Swagger接口文档") @CrossOrigin @RestController @RequestMapping(value = Constants.API_DOC_PATH_PREFIX) public class ApiSwaggerController { + /** 与原 springfox JsonSerializer 行为对齐:序列化时排除 null 字段 */ + private static final ObjectMapper JSON_MAPPER = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + @Resource private ApiSwaggerService apiSwaggerService; - @Resource - private JsonSerializer jsonSerializer; @GetMapping(value = {"/swagger.json", "/knife4j/swagger.json"}, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getSwaggerJson(HttpServletRequest request) { + public ResponseEntity getSwaggerJson(HttpServletRequest request) { OpenAPI oas = apiSwaggerService.getSwaggerJson(request); - return new ResponseEntity(this.jsonSerializer.toJson(oas), HttpStatus.OK); + try { + return new ResponseEntity<>(JSON_MAPPER.writeValueAsString(oas), HttpStatus.OK); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize OpenAPI document", e); + } } @GetMapping(value = {"/knife4j/swagger-resources"}, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getSwaggerResource(HttpServletRequest request) { + public ResponseEntity>> getSwaggerResource(HttpServletRequest request) { Map resources = new HashMap<>(); resources.put("name", "DataPoly在线接口文档"); resources.put("url", "/swagger.json"); resources.put("swaggerVersion", "3.0"); resources.put("location", "/swagger.json"); - return new ResponseEntity(this.jsonSerializer.toJson(Lists.newArrayList(resources)), HttpStatus.OK); + return new ResponseEntity<>(Lists.newArrayList(resources), HttpStatus.OK); } } diff --git a/datapoly-executor/src/main/java/com/cs/executor/controller/ClientTokenController.java b/datapoly-executor/src/main/java/com/cs/executor/controller/ClientTokenController.java index cfa87a0..b3ae804 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/controller/ClientTokenController.java +++ b/datapoly-executor/src/main/java/com/cs/executor/controller/ClientTokenController.java @@ -9,8 +9,8 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.*; +import jakarta.annotation.Resource; +import jakarta.servlet.http.*; import java.util.Map; @Slf4j diff --git a/datapoly-executor/src/main/java/com/cs/executor/filter/GatewaySourceFilter.java b/datapoly-executor/src/main/java/com/cs/executor/filter/GatewaySourceFilter.java index b330db0..1119a80 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/filter/GatewaySourceFilter.java +++ b/datapoly-executor/src/main/java/com/cs/executor/filter/GatewaySourceFilter.java @@ -10,8 +10,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.servlet.*; -import javax.servlet.http.*; +import jakarta.servlet.*; +import jakarta.servlet.http.*; import java.io.IOException; import java.security.MessageDigest; import java.util.Objects; diff --git a/datapoly-executor/src/main/java/com/cs/executor/flowcontrol/SentinelFlowControlManager.java b/datapoly-executor/src/main/java/com/cs/executor/flowcontrol/SentinelFlowControlManager.java index f0374de..e39abda 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/flowcontrol/SentinelFlowControlManager.java +++ b/datapoly-executor/src/main/java/com/cs/executor/flowcontrol/SentinelFlowControlManager.java @@ -19,8 +19,8 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; diff --git a/datapoly-executor/src/main/java/com/cs/executor/interceptor/ExecutorInterceptor.java b/datapoly-executor/src/main/java/com/cs/executor/interceptor/ExecutorInterceptor.java index 4af6a49..1a625f1 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/interceptor/ExecutorInterceptor.java +++ b/datapoly-executor/src/main/java/com/cs/executor/interceptor/ExecutorInterceptor.java @@ -9,7 +9,7 @@ import com.cs.persistence.entity.SystemParamEntity; import org.springframework.web.servlet.HandlerInterceptor; -import javax.servlet.http.*; +import jakarta.servlet.http.*; import java.io.IOException; public class ExecutorInterceptor implements HandlerInterceptor { diff --git a/datapoly-executor/src/main/java/com/cs/executor/model/HttpApiServlet.java b/datapoly-executor/src/main/java/com/cs/executor/model/HttpApiServlet.java index 8f21859..d3c4e18 100644 --- a/datapoly-executor/src/main/java/com/cs/executor/model/HttpApiServlet.java +++ b/datapoly-executor/src/main/java/com/cs/executor/model/HttpApiServlet.java @@ -8,8 +8,8 @@ import com.cs.core.util.JacksonUtils; import com.cs.persistence.entity.ApiAssignmentEntity; -import javax.servlet.ServletException; -import javax.servlet.http.*; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.*; import java.io.IOException; public class HttpApiServlet extends HttpServlet { diff --git a/datapoly-executor/src/main/resources/application-mysql.yaml b/datapoly-executor/src/main/resources/application-mysql.yaml index b198948..afdb8c1 100644 --- a/datapoly-executor/src/main/resources/application-mysql.yaml +++ b/datapoly-executor/src/main/resources/application-mysql.yaml @@ -4,5 +4,5 @@ spring: url: jdbc:mysql://${MYSQLDB_HOST}:${MYSQLDB_PORT}/${MYSQLDB_NAME}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&allowMultiQueries=true&failOverReadOnly=false&connectTimeout=30000&serverTimezone=Asia/Shanghai username: ${MYSQLDB_USERNAME} password: ${MYSQLDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true \ No newline at end of file + hikari: + connection-test-query: SELECT 1 diff --git a/datapoly-executor/src/main/resources/application-postgres.yaml b/datapoly-executor/src/main/resources/application-postgres.yaml index 6ccd4bf..9a9be20 100644 --- a/datapoly-executor/src/main/resources/application-postgres.yaml +++ b/datapoly-executor/src/main/resources/application-postgres.yaml @@ -4,5 +4,5 @@ spring: url: jdbc:postgresql://${PGDB_HOST}:${PGDB_PORT}/${PGDB_NAME}?currentSchema=public username: ${PGDB_USERNAME} password: ${PGDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true \ No newline at end of file + hikari: + connection-test-query: SELECT 1 diff --git a/datapoly-executor/src/main/resources/application.yaml b/datapoly-executor/src/main/resources/application.yaml index 20b2b7a..8510e83 100644 --- a/datapoly-executor/src/main/resources/application.yaml +++ b/datapoly-executor/src/main/resources/application.yaml @@ -113,14 +113,6 @@ management: exposure: include: health,info -springfox: - documentation: - enabled: true - auto-startup: false - swagger-ui: - enabled: false - - datasource: driver: base-path: ${APP_DRIVERS_PATH} \ No newline at end of file diff --git a/datapoly-gateway/pom.xml b/datapoly-gateway/pom.xml index 79c31e7..e8c33a8 100644 --- a/datapoly-gateway/pom.xml +++ b/datapoly-gateway/pom.xml @@ -42,10 +42,10 @@ spring-cloud-starter-netflix-eureka-client + - junit - junit - test + com.netflix.eureka + eureka-client-jersey3 diff --git a/datapoly-gateway/src/main/java/com/cs/gateway/config/EurekaTransportConfiguration.java b/datapoly-gateway/src/main/java/com/cs/gateway/config/EurekaTransportConfiguration.java new file mode 100644 index 0000000..6318698 --- /dev/null +++ b/datapoly-gateway/src/main/java/com/cs/gateway/config/EurekaTransportConfiguration.java @@ -0,0 +1,27 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.gateway.config; + +import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; +import com.netflix.discovery.shared.transport.jersey3.Jersey3TransportClientFactories; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Cloud 2025 的 eureka-client 自动装配在 jersey 在场时不再自带 TransportClientFactories + * (仅 eureka-server 侧的 EurekaServerJerseyClientAutoConfiguration 提供),纯客户端服务 + * 须自备该 bean,否则 RefreshableEurekaClientConfiguration 因缺 bean 启动失败。 + * manager 无需此类(eureka-server 依赖自带装配)。 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Jersey3TransportClientFactories.class) +public class EurekaTransportConfiguration { + + @Bean + @ConditionalOnMissingBean(TransportClientFactories.class) + public TransportClientFactories jersey3TransportClientFactories() { + return Jersey3TransportClientFactories.getInstance(); + } +} diff --git a/datapoly-gateway/src/main/java/com/cs/gateway/filter/ClientAddressFilter.java b/datapoly-gateway/src/main/java/com/cs/gateway/filter/ClientAddressFilter.java index f77d748..97cc59f 100644 --- a/datapoly-gateway/src/main/java/com/cs/gateway/filter/ClientAddressFilter.java +++ b/datapoly-gateway/src/main/java/com/cs/gateway/filter/ClientAddressFilter.java @@ -24,7 +24,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; diff --git a/datapoly-gateway/src/main/resources/application-mysql.yaml b/datapoly-gateway/src/main/resources/application-mysql.yaml index b198948..afdb8c1 100644 --- a/datapoly-gateway/src/main/resources/application-mysql.yaml +++ b/datapoly-gateway/src/main/resources/application-mysql.yaml @@ -4,5 +4,5 @@ spring: url: jdbc:mysql://${MYSQLDB_HOST}:${MYSQLDB_PORT}/${MYSQLDB_NAME}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&allowMultiQueries=true&failOverReadOnly=false&connectTimeout=30000&serverTimezone=Asia/Shanghai username: ${MYSQLDB_USERNAME} password: ${MYSQLDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true \ No newline at end of file + hikari: + connection-test-query: SELECT 1 diff --git a/datapoly-gateway/src/main/resources/application-postgres.yaml b/datapoly-gateway/src/main/resources/application-postgres.yaml index 6ccd4bf..9a9be20 100644 --- a/datapoly-gateway/src/main/resources/application-postgres.yaml +++ b/datapoly-gateway/src/main/resources/application-postgres.yaml @@ -4,5 +4,5 @@ spring: url: jdbc:postgresql://${PGDB_HOST}:${PGDB_PORT}/${PGDB_NAME}?currentSchema=public username: ${PGDB_USERNAME} password: ${PGDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true \ No newline at end of file + hikari: + connection-test-query: SELECT 1 diff --git a/datapoly-gateway/src/main/resources/application.yaml b/datapoly-gateway/src/main/resources/application.yaml index ace4741..93f8385 100644 --- a/datapoly-gateway/src/main/resources/application.yaml +++ b/datapoly-gateway/src/main/resources/application.yaml @@ -108,9 +108,3 @@ management: web: exposure: include: health,info - -# 网关无 swagger 端点(/apidoc/** 由路由转发给 executor);整体关闭 springfox, -# 避免 Boot 2.6+ PathPattern 与 springfox 3.0 webflux 扫描的兼容性问题 -springfox: - documentation: - enabled: false diff --git a/datapoly-manager-ui/build/webpack.base.conf.js b/datapoly-manager-ui/build/webpack.base.conf.js index 15447fb..fe7b36c 100644 --- a/datapoly-manager-ui/build/webpack.base.conf.js +++ b/datapoly-manager-ui/build/webpack.base.conf.js @@ -36,9 +36,11 @@ module.exports = { resolve: { extensions: ['.js', '.vue', '.json'], // webpack 5 dropped the automatic node polyfills this app relied on through - // urlencode -> iconv-lite; buffer is the only one actually needed + // urlencode -> iconv-lite; buffer and string_decoder are the ones actually needed + // (iconv-lite >=0.5 additionally pulls string_decoder for its codecs) fallback: { - buffer: require.resolve('buffer/') + buffer: require.resolve('buffer/'), + 'string_decoder': require.resolve('string_decoder/') }, // Extension sources sit outside the manager-ui tree, so its npm imports cannot // rely on the classic upward node_modules lookup. Its own node_modules (if any, the diff --git a/datapoly-manager-ui/package-lock.json b/datapoly-manager-ui/package-lock.json index 7ddf33f..a886685 100644 --- a/datapoly-manager-ui/package-lock.json +++ b/datapoly-manager-ui/package-lock.json @@ -9,53 +9,110 @@ "version": "1.0.0", "license": "BSD-3-Clause", "dependencies": { - "@babel/runtime": "^7.25.2", - "axios": "^0.21.4", + "@babel/runtime": "^7.29.7", + "axios": "^1.20.0", "buffer": "^6.0.3", - "echarts": "^5.4.1", - "element-ui": "^2.15.6", + "echarts": "^5.6.0", + "element-ui": "^2.15.14", "mxgraph-js": "^1.0.1", + "string_decoder": "^1.3.0", "urlencode": "^1.1.0", - "vue": "^2.6.14", + "vue": "^2.7.16", "vue-codemirror": "^4.0.6", "vue-count-to": "^1.0.13", - "vue-i18n": "^8.28.0", + "vue-i18n": "^8.28.2", "vue-json-viewer": "^2.2.22", - "vue-router": "^3.0.1", + "vue-router": "^3.6.5", "vue2-ace-editor": "^0.0.15" }, "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-runtime": "^7.25.2", - "@babel/preset-env": "^7.25.3", + "@babel/core": "^7.29.7", + "@babel/plugin-transform-runtime": "^7.29.7", + "@babel/preset-env": "^7.29.7", "@vue/babel-preset-jsx": "^1.4.0", - "autoprefixer": "^10.4.20", - "babel-loader": "^9.1.3", + "autoprefixer": "^10.6.1", + "babel-loader": "^10.1.1", "chalk": "^4.1.2", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^6.0.0", - "html-webpack-plugin": "^5.6.0", - "less": "^3.11.3", - "less-loader": "^11.1.0", - "mini-css-extract-plugin": "^2.9.0", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.5", + "css-minimizer-webpack-plugin": "^8.0.0", + "html-webpack-plugin": "^5.6.8", + "jsdom": "^30.1.0", + "less": "^4.9.1", + "less-loader": "^13.0.0", + "mini-css-extract-plugin": "^2.10.2", "portfinder": "^1.0.32", - "postcss": "^8.4.47", + "postcss": "^8.5.0", "postcss-import": "^15.1.0", - "postcss-loader": "^7.3.3", - "terser-webpack-plugin": "^5.3.10", + "postcss-loader": "^8.2.1", + "terser-webpack-plugin": "^5.6.1", + "vite": "^7.2.0", + "vitest": "^5.0.1", "vue-loader": "^15.11.1", "vue-style-loader": "^4.0.0", - "vue-template-compiler": "^2.6.14", - "webpack": "^5.94.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.2.2", + "vue-template-compiler": "^2.7.16", + "webpack": "^5.111.0", + "webpack-bundle-analyzer": "^5.4.0", + "webpack-cli": "^7.2.3", + "webpack-dev-server": "^6.0.0", "webpack-merge": "^5.10.0" }, "engines": { - "node": ">= 18.0.0", - "npm": ">= 8.0.0" + "node": ">= 22.0.0", + "npm": ">= 10.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.0.0.tgz", + "integrity": "sha512-IxddtGnnoidVSO6O/aiTueptbTThHvi2qGHu9/AWuD1J7i86Ox+jl+Y5SXcDH1RixzQlP2cjH/PjnlImZiAurQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.2.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.1.4.tgz", + "integrity": "sha512-ynDE7RDZM1z+YuSU+iGhRp8WcSXHqK9+G32ZEzsL35TKSoy72fYR7VfrJCQkWe3ciiIWGSTAIBc9XB5jVpqyTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.1.0", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/@babel/code-frame": { @@ -1646,195 +1703,812 @@ "node": ">=6.9.0" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@colordx/core": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.8.0.tgz", + "integrity": "sha512-cG0QJAO6VkaRUlIb0zOzX9gfJgs1pjoOL9gZ/PK1kfvBs0GCkADu5oQh6gPxXgQnZ3gV575h+lQRC0NlMDTclA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=10.0.0" + "node": ">=20.19.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@csstools/css-calc": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.0.tgz", + "integrity": "sha512-XQKj5B7QiZcHiegCOCAzcAOJdhGgWOHbbu62h5e5mkHnn8lWcfiJhllkqWmxu5zWR9jucPHuo1iTB56P033hcg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@csstools/css-color-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.3.tgz", + "integrity": "sha512-y4LpL+lmpuyKDiEFq2PnZUVFdAjsoB/qQJod79yLNokXyW7jewi+/WJ69EfItj8A2unWtxXnGjw6LYXgXu5ZjA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz", + "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=20.19.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "engines": { + "node": ">=14.17.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">=18" } }, - "node_modules/@jsonjoy.com/buffers": { - "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", - "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">=18" } }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">=18" } }, - "node_modules/@jsonjoy.com/fs-core": { - "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-core/-/fs-core-4.78.0.tgz", - "integrity": "sha512-0YE5XQj383AwNj5fG0Mm1gyx/ViVe2DJzS5FeVbJulw0pteEK9mfbeB0MWH3/ARoXb2AhHHuiiNthdWF0Y6EYQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.78.0", - "@jsonjoy.com/fs-node-utils": "4.78.0", - "thingies": "^2.5.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">=18" } }, - "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-fsa/-/fs-fsa-4.78.0.tgz", - "integrity": "sha512-V1lUo5k4xUypsfdmvCrrUJ44BCixbgvpus71WI3a3BOQt39bK2LasU/HgpJ1P9or8a2d5mWR3kk26sRoehDlaw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/fs-core": "4.78.0", - "@jsonjoy.com/fs-node-builtins": "4.78.0", - "@jsonjoy.com/fs-node-utils": "4.78.0", - "thingies": "^2.5.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10.0" + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jest/pattern": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.5.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.78.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.78.0.tgz", + "integrity": "sha512-0YE5XQj383AwNj5fG0Mm1gyx/ViVe2DJzS5FeVbJulw0pteEK9mfbeB0MWH3/ARoXb2AhHHuiiNthdWF0Y6EYQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.78.0", + "@jsonjoy.com/fs-node-utils": "4.78.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.78.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.78.0.tgz", + "integrity": "sha512-V1lUo5k4xUypsfdmvCrrUJ44BCixbgvpus71WI3a3BOQt39bK2LasU/HgpJ1P9or8a2d5mWR3kk26sRoehDlaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.78.0", + "@jsonjoy.com/fs-node-builtins": "4.78.0", + "@jsonjoy.com/fs-node-utils": "4.78.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" }, "funding": { "type": "github", @@ -1846,7 +2520,7 @@ }, "node_modules/@jsonjoy.com/fs-node": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-node/-/fs-node-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.78.0.tgz", "integrity": "sha512-AhoKCEEzsTU5C+9pSegNkErrQijvpHj10kzVhaXIxjs/0taxjCCLG7zE+Y+EWCMNCVmgFEyca+oTQQWm2RO+sg==", "dev": true, "license": "Apache-2.0", @@ -1872,7 +2546,7 @@ }, "node_modules/@jsonjoy.com/fs-node-builtins": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.78.0.tgz", "integrity": "sha512-yiyMh9uKnQvRVDYzINP9P2AXZPdHYM2iVsQ/2U2V+g+gfTr4vPcT1Ox0aQs0IIXGQRAnksyA63KSqpOOJ/O3lA==", "dev": true, "license": "Apache-2.0", @@ -1889,7 +2563,7 @@ }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.78.0.tgz", "integrity": "sha512-L1e0UgRJ/igqt3JUDJh3z8YXTKqJc9r9MY4ZakEMOqqEDq/C5fmSZDDzq03d7HqdG1rehkmCDpKyXWqQEVpQGw==", "dev": true, "license": "Apache-2.0", @@ -1911,7 +2585,7 @@ }, "node_modules/@jsonjoy.com/fs-node-utils": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.78.0.tgz", "integrity": "sha512-Am25+Y/rEFtkJBZCFgs84XPXqeqXq4FvXmCYMZ0bEC/M+vT0e6tHBU4Camp9/Yzuf6NoxaInC3iXIN791wVu9g==", "dev": true, "license": "Apache-2.0", @@ -1932,7 +2606,7 @@ }, "node_modules/@jsonjoy.com/fs-print": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-print/-/fs-print-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.78.0.tgz", "integrity": "sha512-Uwk0SvDN1X+5FNW3MxibrFVXXUnd0nTUP6qwvPikg3QDeXRiRww91mz34lXYa/xO5eVuUqa1WXmd0ljy1+Ej0w==", "dev": true, "license": "Apache-2.0", @@ -1953,7 +2627,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.78.0.tgz", "integrity": "sha512-+HvPGwV1IcXtwFFxMBK5x8rmqBuIkPpEFZMGkO8BolJH+AuSvQGVwvlKulUjQ9KECpN4GTCcgeQtyUvYC8heSA==", "dev": true, "license": "Apache-2.0", @@ -1976,7 +2650,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "dev": true, "license": "Apache-2.0", @@ -1993,7 +2667,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", "dev": true, "license": "Apache-2.0", @@ -2010,7 +2684,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", "dev": true, "license": "Apache-2.0", @@ -2037,7 +2711,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", "dev": true, "license": "Apache-2.0", @@ -2057,7 +2731,7 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { "version": "17.67.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/util/-/util-17.67.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", "dev": true, "license": "Apache-2.0", @@ -2078,7 +2752,7 @@ }, "node_modules/@jsonjoy.com/json-pack": { "version": "1.21.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, "license": "Apache-2.0", @@ -2105,7 +2779,7 @@ }, "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", @@ -2122,7 +2796,7 @@ }, "node_modules/@jsonjoy.com/json-pointer": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", "dev": true, "license": "Apache-2.0", @@ -2143,7 +2817,7 @@ }, "node_modules/@jsonjoy.com/util": { "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/util/-/util-1.9.0.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, "license": "Apache-2.0", @@ -2164,7 +2838,7 @@ }, "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", @@ -2186,6 +2860,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.4.0.tgz", @@ -2199,44 +2893,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@peculiar/asn1-cms": { "version": "2.9.4", "resolved": "https://registry.npmmirror.com/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", @@ -2449,88 +3105,477 @@ "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", "dev": true, "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.9.4", - "@peculiar/asn1-x509": "^2.9.4", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@peculiar/asn1-x509-attr/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-attr/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-x509/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/utils/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmmirror.com/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@peculiar/x509/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz", + "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz", + "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz", + "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz", + "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz", + "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz", + "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz", + "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz", + "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz", + "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz", + "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz", + "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz", + "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz", + "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz", + "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz", + "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz", + "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz", + "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz", + "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz", + "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "0BSD" - }, - "node_modules/@peculiar/asn1-x509/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz", + "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "0BSD" - }, - "node_modules/@peculiar/utils": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/@peculiar/utils/-/utils-2.0.3.tgz", - "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz", + "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/utils/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz", + "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "0BSD" - }, - "node_modules/@peculiar/x509": { - "version": "1.14.3", - "resolved": "https://registry.npmmirror.com/@peculiar/x509/-/x509-1.14.3.tgz", - "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz", + "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-csr": "^2.6.0", - "@peculiar/asn1-ecc": "^2.6.0", - "@peculiar/asn1-pkcs9": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "pvtsutils": "^1.3.6", - "reflect-metadata": "^0.2.2", - "tslib": "^2.8.1", - "tsyringe": "^4.10.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@peculiar/x509/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz", + "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "0BSD" - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz", + "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@sinclair/typebox": { - "version": "0.27.12", - "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.12.tgz", - "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, "license": "MIT" }, @@ -2555,6 +3600,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", @@ -2576,6 +3632,13 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", @@ -2584,22 +3647,21 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.9", - "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", - "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", "dev": true, "license": "MIT", "dependencies": { @@ -2618,31 +3680,21 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, - "node_modules/@types/http-proxy": { - "version": "1.17.17", - "resolved": "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.17.tgz", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "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==", "dev": true, "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==", "dev": true, "license": "MIT", @@ -2652,7 +3704,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==", "dev": true, "license": "MIT", @@ -2667,10 +3719,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "node_modules/@types/less": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/less/-/less-3.0.8.tgz", + "integrity": "sha512-Gjm4+H9noDJgu5EdT3rUw5MhPBag46fiOy27BefvWkNL8mlZnKnCaVVVTLKj6RYXed9b62CPKnPav9govyQDzA==", "dev": true, "license": "MIT" }, @@ -2698,13 +3750,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmmirror.com/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", @@ -2726,35 +3771,13 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmmirror.com/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "license": "MIT", - "dependencies": { "@types/node": "*" } }, @@ -2770,7 +3793,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==", "dev": true, "license": "MIT", @@ -2780,11 +3803,56 @@ }, "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==", "dev": true, "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/mocker": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz", + "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.1", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz", + "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vue/babel-helper-vue-jsx-merge-props": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz", @@ -3158,53 +4226,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -3269,6 +4290,18 @@ "node": ">=0.4.0" } }, + "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/ajv": { "version": "8.20.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", @@ -3356,34 +4389,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "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==", "dev": true, "license": "Python-2.0" }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, "node_modules/asn1js": { "version": "3.0.10", "resolved": "https://registry.npmmirror.com/asn1js/-/asn1js-3.0.10.tgz", @@ -3406,6 +4418,16 @@ "dev": true, "license": "0BSD" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", @@ -3421,6 +4443,12 @@ "babel-runtime": "6.x" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.6.1", "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.6.1.tgz", @@ -3459,12 +4487,15 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/babel-helper-vue-jsx-merge-props": { @@ -3474,21 +4505,29 @@ "license": "MIT" }, "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz", + "integrity": "sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==", "dev": true, "license": "MIT", "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" + "find-up": "^5.0.0" }, "engines": { - "node": ">= 14.15.0" + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }, "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" + "@babel/core": "^7.12.0 || ^8.0.0-beta.1", + "@rspack/core": "^1.0.0 || ^2.0.0-0", + "webpack": ">=5.61.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -3583,6 +4622,16 @@ "dev": true, "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", @@ -3593,19 +4642,6 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", @@ -3613,48 +4649,62 @@ "dev": true, "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.8", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.8.tgz", - "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.16.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, "node_modules/bonjour-service": { "version": "1.4.4", "resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.4.4.tgz", @@ -3681,7 +4731,7 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", @@ -3759,7 +4809,7 @@ }, "node_modules/bundle-name": { "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "license": "MIT", @@ -3795,9 +4845,8 @@ }, "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3809,7 +4858,7 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", @@ -3826,7 +4875,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==", "dev": true, "license": "MIT", @@ -3857,7 +4906,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==", "dev": true, "license": "MIT", @@ -3889,6 +4938,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", @@ -3907,41 +4966,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/chrome-trace-event": { @@ -3955,9 +4992,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -4035,19 +5072,17 @@ "dev": true, "license": "MIT" }, - "node_modules/colord": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/colord/-/colord-2.10.0.tgz", - "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } }, "node_modules/commander": { "version": "8.3.0", @@ -4059,13 +5094,6 @@ "node": ">= 12" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, - "license": "ISC" - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", @@ -4145,21 +5173,22 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", @@ -4176,7 +5205,7 @@ }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", @@ -4185,41 +5214,46 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "dev": true, "license": "MIT", "dependencies": { - "is-what": "^3.14.1" + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" }, "funding": { "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", "dependencies": { - "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", - "globby": "^13.1.1", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -4254,24 +5288,17 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -4305,7 +5332,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==", "dev": true, "license": "ISC", @@ -4317,31 +5344,31 @@ } }, "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.5.tgz", + "integrity": "sha512-Q7iAfQkU2twNBryKX/vGAlE+GAmkF7quhSzAGNK8fBimxk3+tqg245rH52UPb9dioBolBc8rP0ihmHBaDTJQBA==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "semver": "^7.6.3" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { @@ -4366,21 +5393,21 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-6.0.0.tgz", - "integrity": "sha512-BLpR9CCDkKvhO3i0oZQgad6v9pCxUuhSc5RT6iUEy9M8hBXi4TJb5vqF2GQ2deqYHmRi3O6IR9hgAZQWg0EBwA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-9bEpzHs8gEq6/cbEj418jXL/YWjBUD2YTLLk905Npt2JODqnRITin0+So5Vx4Dp5vyi2Lpt9pp2QHzQ7fdxNrw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.21", - "cssnano": "^6.0.3", - "jest-worker": "^29.7.0", - "postcss": "^8.4.33", + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.4", + "jest-worker": "^30.0.5", + "postcss": "^8.4.40", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" + "serialize-javascript": "^7.0.3" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -4428,14 +5455,14 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -4468,87 +5495,87 @@ } }, "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.9.tgz", + "integrity": "sha512-uPR75+5Dk/WJ/YSPR1/YDHdwMM9c5FsaARljfKWgeCKLKOtJ0we21xy/RcCjn53fZnD/f6yYEIZ8pu18+GnbNQ==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.17", + "lilconfig": "^3.1.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.17.tgz", + "integrity": "sha512-11qO63A+czwguQFJCaTdICvbaxn0pJzz/XghLlv+OT7WyToDxAMR0Xb3/26/l0y0hQJywwNbj/SLSQlGBHE1OA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "cssnano-utils": "^5.0.3", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.10", + "postcss-convert-values": "^7.0.12", + "postcss-discard-comments": "^7.0.8", + "postcss-discard-duplicates": "^7.0.4", + "postcss-discard-empty": "^7.0.3", + "postcss-discard-overridden": "^7.0.3", + "postcss-merge-longhand": "^7.0.7", + "postcss-merge-rules": "^7.0.11", + "postcss-minify-font-values": "^7.0.3", + "postcss-minify-gradients": "^7.0.5", + "postcss-minify-params": "^7.0.9", + "postcss-minify-selectors": "^7.1.2", + "postcss-normalize-charset": "^7.0.3", + "postcss-normalize-display-values": "^7.0.3", + "postcss-normalize-positions": "^7.0.4", + "postcss-normalize-repeat-style": "^7.0.4", + "postcss-normalize-string": "^7.0.3", + "postcss-normalize-timing-functions": "^7.0.3", + "postcss-normalize-unicode": "^7.0.9", + "postcss-normalize-url": "^7.0.3", + "postcss-normalize-whitespace": "^7.0.3", + "postcss-ordered-values": "^7.0.4", + "postcss-reduce-initial": "^7.0.9", + "postcss-reduce-transforms": "^7.0.3", + "postcss-svgo": "^7.1.3", + "postcss-unique-selectors": "^7.0.7" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.3.tgz", + "integrity": "sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg==", "dev": true, "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "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==", "dev": true, "license": "MIT", @@ -4562,7 +5589,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==", "dev": true, "license": "MIT", @@ -4577,7 +5604,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==", "dev": true, "license": "CC0-1.0" @@ -4588,6 +5615,53 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", @@ -4595,18 +5669,10 @@ "dev": true, "license": "MIT" }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "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" @@ -4620,6 +5686,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "1.5.2", "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz", @@ -4631,7 +5704,7 @@ }, "node_modules/default-browser": { "version": "5.5.1", - "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.1.tgz", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", @@ -4648,7 +5721,7 @@ }, "node_modules/default-browser-id": { "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", @@ -4661,7 +5734,7 @@ }, "node_modules/define-lazy-prop": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", @@ -4672,6 +5745,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegate": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz", @@ -4680,7 +5762,7 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", @@ -4699,32 +5781,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT" - }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz", "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", "license": "Apache-2.0" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmmirror.com/dns-packet/-/dns-packet-5.6.1.tgz", @@ -4820,9 +5882,8 @@ }, "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==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4833,13 +5894,6 @@ "node": ">= 0.4" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true, - "license": "MIT" - }, "node_modules/echarts": { "version": "5.6.0", "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz", @@ -4852,7 +5906,7 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, "license": "MIT" @@ -4893,7 +5947,7 @@ }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", @@ -4925,6 +5979,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.21.0", "resolved": "https://registry.npmmirror.com/envinfo/-/envinfo-7.21.0.tgz", @@ -4954,7 +6018,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==", "dev": true, "license": "MIT", @@ -4964,9 +6028,8 @@ }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4976,7 +6039,6 @@ "version": "1.3.0", "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4991,9 +6053,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5002,6 +6063,63 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", @@ -5020,18 +6138,28 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", @@ -5044,7 +6172,7 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", @@ -5052,13 +6180,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", @@ -5069,70 +6190,122 @@ "node": ">=0.8.x" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { - "version": "4.22.3", - "resolved": "https://registry.npmmirror.com/express/-/express-4.22.3.tgz", - "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.13", - "proxy-addr": "~2.0.7", - "qs": "~6.16.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" }, "engines": { - "node": ">= 0.10.0" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5140,36 +6313,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-uri": { "version": "3.1.8", "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.8.tgz", @@ -5187,42 +6330,27 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" + "node": ">=12.0.0" }, - "engines": { - "node": ">=0.8.0" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", @@ -5234,70 +6362,39 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=14.16" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5333,9 +6430,25 @@ } } }, + "node_modules/form-data": { + "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.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", @@ -5358,13 +6471,13 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fsevents": { @@ -5386,7 +6499,6 @@ "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5404,9 +6516,8 @@ }, "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==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5429,9 +6540,8 @@ }, "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==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5456,7 +6566,7 @@ }, "node_modules/glob-to-regex.js": { "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz", "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", "dev": true, "license": "Apache-2.0", @@ -5471,26 +6581,6 @@ "tslib": "2" } }, - "node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmmirror.com/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/good-listener": { "version": "1.2.2", "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz", @@ -5502,9 +6592,8 @@ }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5520,29 +6609,6 @@ "dev": true, "license": "ISC" }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -5555,9 +6621,8 @@ }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5566,6 +6631,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hash-sum": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz", @@ -5577,7 +6657,6 @@ "version": "2.0.4", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5596,56 +6675,41 @@ "he": "bin/he" } }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/html-encoding-sniffer/node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", "dev": true, "license": "MIT" }, @@ -5734,16 +6798,9 @@ "entities": "^2.0.0" } }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, - "license": "MIT" - }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", @@ -5762,56 +6819,46 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/http-proxy-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-4.2.0.tgz", + "integrity": "sha512-ZA+oNOoM+GLoFTIzhkJptVQov73Srep2LBqhF8hG8CIPKO3nam1jonXVQ/QUH8RbwsmaaVz2SOJdzBNBHNtKbw==", "dev": true, "license": "MIT", "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "debug": "^4.4.3", + "httpxy": "^0.5.4", + "is-glob": "^4.0.3", + "is-plain-obj": "^4.1.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=8.0.0" + "node": "^22.15.0 || ^24.0.0 || >=26.0.0" } }, - "node_modules/http-proxy-middleware": { - "version": "2.0.10", - "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", - "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", - "dev": true, + "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": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "node": ">= 6" } }, + "node_modules/httpxy": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.5.tgz", + "integrity": "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==", + "dev": true, + "license": "MIT" + }, "node_modules/hyperdyperid": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "dev": true, "license": "MIT", @@ -5820,12 +6867,14 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -5864,33 +6913,9 @@ ], "license": "BSD-3-Clause" }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "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==", "dev": true, "license": "MIT", @@ -5981,16 +7006,6 @@ "node": ">=8" } }, - "node_modules/import-local/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/import-local/node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -6033,24 +7048,11 @@ }, "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==", "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-core-module": { "version": "2.16.2", "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", @@ -6069,7 +7071,7 @@ }, "node_modules/is-docker": { "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", @@ -6106,9 +7108,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", @@ -6127,7 +7142,7 @@ }, "node_modules/is-network-error": { "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/is-network-error/-/is-network-error-1.3.2.tgz", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "dev": true, "license": "MIT", @@ -6140,7 +7155,7 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", @@ -6149,13 +7164,13 @@ } }, "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6174,16 +7189,36 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } }, "node_modules/is-wsl": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", @@ -6197,13 +7232,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", @@ -6221,43 +7249,54 @@ "node": ">=0.10.0" } }, + "node_modules/jest-regex-util": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.5.1", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "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==", "dev": true, "license": "MIT", @@ -6272,13 +7311,13 @@ } }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-tokens": { @@ -6290,7 +7329,7 @@ }, "node_modules/js-yaml": { "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.2.tgz", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ @@ -6311,6 +7350,74 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.0.tgz", + "integrity": "sha512-h/Q+H+KmlGf49ITw3TrykSVbg04S75S2O7ZtJc9gMGOWBrQRZmx5AJIKLtJ01e6Kszl+/tpFthCVqxNifxhoqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^7.0.0", + "@asamuzakjp/dom-selector": "^9.1.2", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.13", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "tough-cookie": "^6.0.2", + "undici": "^8.10.2", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", @@ -6326,7 +7433,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==", "dev": true, "license": "MIT" @@ -6373,59 +7480,64 @@ } }, "node_modules/less": { - "version": "3.13.1", - "resolved": "https://registry.npmmirror.com/less/-/less-3.13.1.tgz", - "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.9.1.tgz", + "integrity": "sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "copy-anything": "^2.0.1", - "tslib": "^1.10.0" + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" }, "bin": { "lessc": "bin/lessc" }, "engines": { - "node": ">=6" + "node": ">=18" }, "optionalDependencies": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", + "make-dir": "^5.1.0", "mime": "^1.4.1", - "native-request": "^1.0.5", + "needle": "^3.1.0", + "probe-image-size": "^7.2.3", "source-map": "~0.6.0" } }, "node_modules/less-loader": { - "version": "11.1.4", - "resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-11.1.4.tgz", - "integrity": "sha512-6/GrYaB6QcW6Vj+/9ZPgKKs6G10YZai/l/eJ4SLwbzqNTBsAqt5hSLVF47TgsiBxV1P6eAU0GYRH3YRuQU9V3A==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-13.0.0.tgz", + "integrity": "sha512-TIa8d6znKH634Mg+7OU3jevZT6KeOhh0amW+YeMPD0GM9buUn5Y7HvtyCR5pUDdLaFfqLA8AX5PTSIHMNSexEA==", "dev": true, "license": "MIT", + "dependencies": { + "@types/less": "^3.0.8" + }, "engines": { - "node": ">= 14.15.0" + "node": ">= 22.11.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "less": "^3.5.0 || ^4.0.0", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/less/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, "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==", "dev": true, "license": "MIT", @@ -6438,7 +7550,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==", "dev": true, "license": "MIT" @@ -6472,16 +7584,16 @@ } }, "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^6.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6510,14 +7622,22 @@ }, "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==", "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "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==", "dev": true, "license": "MIT" @@ -6542,62 +7662,63 @@ "yallist": "^3.0.2" } }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "node_modules/magic-string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/make-dir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", "dev": true, - "license": "ISC", + "license": "MIT", "optional": true, - "bin": { - "semver": "bin/semver" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/memfs": { "version": "4.78.0", - "resolved": "https://registry.npmmirror.com/memfs/-/memfs-4.78.0.tgz", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.78.0.tgz", "integrity": "sha512-gX14RGPP+fyNodL8g9jc5DfKtWNCronEMItOKDyZatTI3CPpMZDPtm7rAbftDqQAFovP4nzR0RO80sESSEnI4g==", "dev": true, "license": "Apache-2.0", @@ -6623,11 +7744,14 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -6649,29 +7773,9 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", @@ -6683,12 +7787,26 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", + "optional": true, "bin": { "mime": "cli.js" }, @@ -6710,7 +7828,6 @@ "version": "2.1.35", "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -6723,7 +7840,6 @@ "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6750,13 +7866,6 @@ "webpack": "^5.0.0" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", @@ -6873,7 +7982,7 @@ }, "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", @@ -6885,7 +7994,6 @@ "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/multicast-dns": { @@ -6926,13 +8034,23 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/native-request": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/native-request/-/native-request-1.1.2.tgz", - "integrity": "sha512-/etjwrK0J4Ebbcnt35VMWnfiUX/B04uwGJxyJInagxDqf2z5drSt/lsOvEMWGYunz1kaLZAFrV4NDAbOoDKvAQ==", + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } }, "node_modules/negotiator": { "version": "0.6.4", @@ -6996,7 +8114,7 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", @@ -7007,16 +8125,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, - "license": "MIT" + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", @@ -7037,20 +8162,32 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7067,50 +8204,48 @@ } }, "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^4.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmmirror.com/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.1.tgz", + "integrity": "sha512-NAigyu8hfvJe7MsS18mnc4is0MZtau3QMdBklaJKK23oBQb6occO3UPM3vHmTckW8KhfyjMZaUOqdTFEYliHgg==", "dev": true, "license": "MIT", "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" + "is-network-error": "^1.3.0" }, "engines": { - "node": ">=16.17" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7139,7 +8274,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==", "dev": true, "license": "MIT", @@ -7152,7 +8287,7 @@ }, "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==", "dev": true, "license": "MIT", @@ -7169,6 +8304,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", @@ -7191,13 +8362,13 @@ } }, "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" } }, "node_modules/path-key": { @@ -7218,20 +8389,14 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/picocolors": { @@ -7241,45 +8406,18 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pkijs": { "version": "3.4.0", "resolved": "https://registry.npmmirror.com/pkijs/-/pkijs-3.4.0.tgz", @@ -7347,109 +8485,140 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "node_modules/postcss-calc": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12 || ^20.9 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-calc/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-colormin": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.10.tgz", + "integrity": "sha512-yFr6JezOolHLta/buLE71VKPh2mXursp4saVe98/ol8ZnEWhL+racShqPKlvd/DKWLre/39B6HhcMXf7RZ3hxg==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "@colordx/core": "^5.4.3", + "browserslist": "^4.28.2", + "caniuse-api": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.5.13" } }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "node_modules/postcss-convert-values": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.12.tgz", + "integrity": "sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", + "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "node_modules/postcss-discard-comments": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.8.tgz", + "integrity": "sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "node_modules/postcss-discard-comments/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "dev": true, "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "peerDependencies": { - "postcss": "^8.4.31" + "engines": { + "node": ">=4" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.4.tgz", + "integrity": "sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw==", "dev": true, "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.3.tgz", + "integrity": "sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw==", "dev": true, "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.3.tgz", + "integrity": "sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ==", "dev": true, "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-import": { @@ -7471,26 +8640,35 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", "dev": true, "license": "MIT", "dependencies": { - "cosmiconfig": "^8.3.5", - "jiti": "^1.20.0", - "semver": "^7.5.4" + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/postcss-loader/node_modules/semver": { @@ -7507,107 +8685,138 @@ } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.7.tgz", + "integrity": "sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^7.0.11" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.11.tgz", + "integrity": "sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^5.0.3", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "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", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.3.tgz", + "integrity": "sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.5.tgz", + "integrity": "sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ==", "dev": true, "license": "MIT", "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "@colordx/core": "^5.4.3", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.9.tgz", + "integrity": "sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.28.2", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.1.2.tgz", + "integrity": "sha512-aQtrEWKwqafNlExcKHQvPGsXR2+vlUqqJtf5XsCQcgsSb5PL4wlujWBYDJuWsP4UnQX1YHDHU8qRlD+1PzTQ+Q==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.28.1", + "caniuse-api": "^3.0.0", + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-modules-extract-imports": { @@ -7702,195 +8911,195 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.3.tgz", + "integrity": "sha512-NoBfZu8PR4c2NlmjvrqQTzCzLY79hwcSRgNQ3ZiNK0ABzf9kYKloE/jNj+/8GQY1wsm8pRRgANk6ydLH8cwo0Q==", "dev": true, "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "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", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.3.tgz", + "integrity": "sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.4.tgz", + "integrity": "sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "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", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.4.tgz", + "integrity": "sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.3.tgz", + "integrity": "sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "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", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.3.tgz", + "integrity": "sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.9.tgz", + "integrity": "sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.3.tgz", + "integrity": "sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.3.tgz", + "integrity": "sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.4.tgz", + "integrity": "sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.9.tgz", + "integrity": "sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.28.2", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.3.tgz", + "integrity": "sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-selector-parser": { @@ -7908,36 +9117,50 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.3.tgz", + "integrity": "sha512-2QfoFOYMcj8lwcVEf9WeTlkVIAm7u2QvOEhMzkQU3KUhhGX/l8hVV9EtjMv4iq3E9iI3OeeMN0YoMLbGusuigw==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.1" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.7.tgz", + "integrity": "sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-value-parser": { @@ -7947,6 +9170,19 @@ "dev": true, "license": "MIT" }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", @@ -7974,16 +9210,76 @@ "renderkid": "^3.0.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "node_modules/probe-image-size": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.4.0.tgz", + "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/probe-image-size/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/probe-image-size/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/probe-image-size/node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } }, "node_modules/proxy-addr": { "version": "2.0.8", - "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.8.tgz", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", @@ -8001,7 +9297,7 @@ }, "node_modules/proxy-addr/node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", @@ -8009,6 +9305,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", @@ -8024,6 +9329,16 @@ "dev": true, "license": "ISC" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmmirror.com/pvtsutils/-/pvtsutils-1.3.6.tgz", @@ -8053,7 +9368,7 @@ }, "node_modules/qs": { "version": "6.16.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.16.0.tgz", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", @@ -8068,96 +9383,72 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/read-cache": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.2.tgz", - "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "dev": true, + "license": "MIT" + }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -8278,13 +9569,6 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -8338,7 +9622,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==", "dev": true, "license": "MIT", @@ -8346,69 +9630,86 @@ "node": ">=4" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "node_modules/rollup": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz", + "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, "engines": { - "node": ">= 4" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.3", + "@rollup/rollup-android-arm64": "4.63.3", + "@rollup/rollup-darwin-arm64": "4.63.3", + "@rollup/rollup-darwin-x64": "4.63.3", + "@rollup/rollup-freebsd-arm64": "4.63.3", + "@rollup/rollup-freebsd-x64": "4.63.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.3", + "@rollup/rollup-linux-arm-musleabihf": "4.63.3", + "@rollup/rollup-linux-arm64-gnu": "4.63.3", + "@rollup/rollup-linux-arm64-musl": "4.63.3", + "@rollup/rollup-linux-loong64-gnu": "4.63.3", + "@rollup/rollup-linux-loong64-musl": "4.63.3", + "@rollup/rollup-linux-ppc64-gnu": "4.63.3", + "@rollup/rollup-linux-ppc64-musl": "4.63.3", + "@rollup/rollup-linux-riscv64-gnu": "4.63.3", + "@rollup/rollup-linux-riscv64-musl": "4.63.3", + "@rollup/rollup-linux-s390x-gnu": "4.63.3", + "@rollup/rollup-linux-x64-gnu": "4.63.3", + "@rollup/rollup-linux-x64-musl": "4.63.3", + "@rollup/rollup-openbsd-x64": "4.63.3", + "@rollup/rollup-openharmony-arm64": "4.63.3", + "@rollup/rollup-win32-arm64-msvc": "4.63.3", + "@rollup/rollup-win32-ia32-msvc": "4.63.3", + "@rollup/rollup-win32-x64-gnu": "4.63.3", + "@rollup/rollup-win32-x64-msvc": "4.63.3", + "fsevents": "~2.3.2" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 18" } }, "node_modules/run-applescript": { "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -8427,13 +9728,13 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/sax": { "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "dev": true, "license": "BlueOak-1.0.0", @@ -8441,6 +9742,19 @@ "node": ">=11.0.0" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/schema-utils": { "version": "4.5.0", "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.5.0.tgz", @@ -8467,13 +9781,6 @@ "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", "license": "MIT" }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, - "license": "MIT" - }, "node_modules/selfsigned": { "version": "5.5.0", "resolved": "https://registry.npmmirror.com/selfsigned/-/selfsigned-5.5.0.tgz", @@ -8499,55 +9806,57 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz", + "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-index": { @@ -8628,19 +9937,23 @@ } }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -8701,7 +10014,7 @@ }, "node_modules/side-channel": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", @@ -8721,7 +10034,7 @@ }, "node_modules/side-channel-list": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", @@ -8738,7 +10051,7 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", @@ -8757,7 +10070,7 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", @@ -8775,10 +10088,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, "license": "MIT", "dependencies": { @@ -8787,32 +10107,7 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" + "node": ">=18" } }, "node_modules/source-map": { @@ -8844,53 +10139,64 @@ "source-map": "^0.6.0" } }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.8" } }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" + "debug": "2" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "optional": true, + "dependencies": { + "ms": "2.0.0" } }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -8910,20 +10216,34 @@ } }, "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.11.tgz", + "integrity": "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.28.2", + "postcss-selector-parser": "^7.1.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.5.13" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/supports-color": { @@ -8959,25 +10279,25 @@ "dev": true }, "node_modules/svgo": { - "version": "3.3.5", - "resolved": "https://registry.npmmirror.com/svgo/-/svgo-3.3.5.tgz", - "integrity": "sha512-8SQMzdrvWaD8deUmrnYB+ASyxBVgWUOilg+A75nE/76WdLpj6LopCwiAVvkzkcqy/9b7t2Mg7faFLjg0ZRcZ3w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", "csso": "^5.0.5", - "picocolors": "^1.0.0", - "sax": "^1.5.0" + "picocolors": "^1.1.1", + "sax": "1.6.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -8985,27 +10305,40 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/svgo/node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" }, "funding": { "url": "https://github.com/sponsors/fb55" @@ -9013,7 +10346,7 @@ }, "node_modules/svgo/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==", "dev": true, "license": "MIT", @@ -9028,7 +10361,7 @@ }, "node_modules/svgo/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==", "dev": true, "license": "BSD-2-Clause", @@ -9044,7 +10377,7 @@ }, "node_modules/svgo/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==", "dev": true, "license": "BSD-2-Clause", @@ -9059,7 +10392,7 @@ }, "node_modules/svgo/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==", "dev": true, "license": "BSD-2-Clause", @@ -9204,7 +10537,7 @@ }, "node_modules/thingies": { "version": "2.6.1", - "resolved": "https://registry.npmmirror.com/thingies/-/thingies-2.6.1.tgz", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", "dev": true, "license": "MIT", @@ -9241,9 +10574,66 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.4.13", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.13.tgz", + "integrity": "sha512-iHtaIWWIbMDkCeJdTBzZFGgbluE5J+oHlb2g7+oAz1S1gpuVpabRZdQyd471Vl8UUkcz2vXSL8xZH2kyCe8tfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.13" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.13", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.13.tgz", + "integrity": "sha512-mbYsrih5FRtGxs3Usvl/PqwJsNpp+jsmrdFviiK02teHDG0/HebBG/pqCylje3kzgXYzuLoHJF/0mz9W53t8Xg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", @@ -9266,7 +10656,7 @@ }, "node_modules/totalist": { "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", @@ -9274,9 +10664,35 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tree-dump": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/tree-dump/-/tree-dump-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, "license": "Apache-2.0", @@ -9291,44 +10707,90 @@ "tslib": "2" } }, - "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/tsyringe": { - "version": "4.10.0", - "resolved": "https://registry.npmmirror.com/tsyringe/-/tsyringe-4.10.0.tgz", - "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmmirror.com/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^1.9.3" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/tsyringe/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/undici": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", "dev": true, "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, "engines": { - "node": ">= 0.6" + "node": ">=22.19.0" } }, "node_modules/undici-types": { @@ -9384,7 +10846,7 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", @@ -9425,13 +10887,25 @@ }, "node_modules/urlencode": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/urlencode/-/urlencode-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/urlencode/-/urlencode-1.1.0.tgz", "integrity": "sha512-OOAOh9owHXr/rCN1tteSnYwIvsrGHamSz0hafMhmQa7RcS4+Ets6/2iVClVGjt9jkDW84UqoMw/Gmpc7QolX6A==", "license": "MIT", "dependencies": { "iconv-lite": "~0.4.11" } }, + "node_modules/urlencode/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9446,34 +10920,172 @@ "dev": true, "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">= 0.8" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, "bin": { - "uuid": "dist/bin/uuid" + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vitest": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz", + "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.1", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">= 0.8" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.1", + "@vitest/browser-preview": "5.0.1", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.1", + "@vitest/coverage-v8": "5.0.1", + "@vitest/ui": "5.0.1", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, "node_modules/vue": { @@ -9606,6 +11218,19 @@ "brace": "^0.11.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.5.2", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.2.tgz", @@ -9619,14 +11244,14 @@ "node": ">=10.13.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" } }, "node_modules/webpack": { @@ -9671,78 +11296,82 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmmirror.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.4.0.tgz", + "integrity": "sha512-6OjsEFQIwcRT1nId/O7OyHjDvrE0JSFAef/XCwGarrf9MF5/CaD0lhDvoySbus57dYEdJ3vrRVj5F3i30c4ozw==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "0.5.7", + "@discoveryjs/json-ext": "^0.6.3", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", + "commander": "^14.0.2", + "escape-string-regexp": "^5.0.0", + "html-escaper": "^3.0.3", "opener": "^1.5.2", "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" + "sirv": "^3.0.2", + "ws": "^8.19.0" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 20.9.0" } }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=20" } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.2.3.tgz", + "integrity": "sha512-vDFU7jrfCctnN7jJQWPl+V26B51GLp11prVZXg50oeonsgeBzSTJEWmXkjHsOjTgMjlPOQM8GWLh33X9RN/0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^1.1.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.21.0", + "import-local": "^3.2.0", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "js-yaml": "^4.0.0 || ^5.0.0", + "json5": "^2.2.3", + "toml": "^3.0.0 || ^4.0.0 || ^5.0.0", + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { + "js-yaml": { + "optional": true + }, + "json5": { + "optional": true + }, + "toml": { "optional": true }, "webpack-bundle-analyzer": { @@ -9753,39 +11382,63 @@ } } }, + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz", + "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=20" + } + }, + "node_modules/webpack-cli/node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/webpack-dev-middleware": { - "version": "7.4.6", - "resolved": "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.6.tgz", - "integrity": "sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.3.0.tgz", + "integrity": "sha512-jFNNMB29ugmkEUFW9cpWNqcWFQNwuMTMVoRpKVAiz/5nh3gA4x9x6DRPhNHGc/SLre7pEP9yrAPDAmCvdCblKg==", "dev": true, "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.43.1", - "mime-types": "^3.0.1", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "ansi-html-community": "^0.0.8", + "memfs": "^4.68.2", + "mime-types": "^3.0.2", + "range-parser": "^1.3.0", + "schema-utils": "^4.3.3" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.101.0" }, "peerDependenciesMeta": { "webpack": { @@ -9795,7 +11448,7 @@ }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", @@ -9811,53 +11464,50 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.6", - "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", - "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-6.0.0.tgz", + "integrity": "sha512-q9SD4ItOGhZLeU6EGT10caDZdHjF50Pz1DtkRZZOPsfluMXOkacWKKOtSBSLVkPqKiF67eFUC0rI88U/tSFPEw==", "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.25", - "@types/express-serve-static-core": "^4.17.21", + "@types/express": "^5.0.6", + "@types/express-serve-static-core": "^5.1.1", "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", + "@types/serve-static": "^2.2.0", + "@types/ws": "^8.18.1", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", + "bonjour-service": "^1.3.0", + "chokidar": "^5.0.0", "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", - "express": "^4.22.1", - "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.9", - "ipaddr.js": "^2.1.0", + "express": "^5.2.1", + "graceful-fs": "^4.2.11", + "http-proxy-middleware": "^4.1.1", + "ipaddr.js": "^2.3.0", "launch-editor": "^2.14.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "schema-utils": "^4.2.0", + "open": "^11.0.0", + "p-retry": "^8.0.0", + "schema-utils": "^4.3.3", "selfsigned": "^5.5.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.4.2", - "ws": "^8.18.0" + "serve-index": "^1.9.2", + "tinyglobby": "^0.2.15", + "webpack-dev-middleware": "^8.0.3", + "ws": "^8.20.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 22.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.101.0" }, "peerDependenciesMeta": { "webpack": { @@ -9868,28 +11518,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -9915,29 +11543,47 @@ "node": ">=10.13.0" } }, - "node_modules/websocket-driver": { - "version": "0.7.5", - "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.5.tgz", - "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.1.tgz", + "integrity": "sha512-ohjk1mdUebJVadRt3bAhQhx8lSnISq+GDttK79LFl8EHQkAPvzwctoasC4hs8tBt6kLAncBWWyq1N52qEfKvDw==", + "dev": true, + "license": "MIT", "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=0.8.0" + "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "node_modules/whatwg-url/node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/which": { @@ -9956,6 +11602,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.1.tgz", @@ -9963,18 +11626,25 @@ "dev": true, "license": "MIT" }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -9986,21 +11656,52 @@ } }, "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", "dev": true, "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", @@ -10009,13 +11710,13 @@ "license": "ISC" }, "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/datapoly-manager-ui/package.json b/datapoly-manager-ui/package.json index f6b706a..8ccc134 100644 --- a/datapoly-manager-ui/package.json +++ b/datapoly-manager-ui/package.json @@ -9,56 +9,62 @@ "dev": "webpack serve --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "build": "node build/build.js", - "build:debug": "DATAPOLY_UI_ENV=debug node build/build.js" + "build:debug": "DATAPOLY_UI_ENV=debug node build/build.js", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { - "@babel/runtime": "^7.25.2", - "axios": "^0.21.4", + "@babel/runtime": "^7.29.7", + "axios": "^1.20.0", "buffer": "^6.0.3", - "echarts": "^5.4.1", - "element-ui": "^2.15.6", + "string_decoder": "^1.3.0", + "echarts": "^5.6.0", + "element-ui": "^2.15.14", "mxgraph-js": "^1.0.1", "urlencode": "^1.1.0", - "vue": "^2.6.14", + "vue": "^2.7.16", "vue-codemirror": "^4.0.6", "vue-count-to": "^1.0.13", - "vue-i18n": "^8.28.0", + "vue-i18n": "^8.28.2", "vue-json-viewer": "^2.2.22", - "vue-router": "^3.0.1", + "vue-router": "^3.6.5", "vue2-ace-editor": "^0.0.15" }, "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-runtime": "^7.25.2", - "@babel/preset-env": "^7.25.3", + "@babel/core": "^7.29.7", + "@babel/plugin-transform-runtime": "^7.29.7", + "@babel/preset-env": "^7.29.7", "@vue/babel-preset-jsx": "^1.4.0", - "autoprefixer": "^10.4.20", - "babel-loader": "^9.1.3", + "autoprefixer": "^10.6.1", + "babel-loader": "^10.1.1", "chalk": "^4.1.2", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^6.0.0", - "html-webpack-plugin": "^5.6.0", - "less": "^3.11.3", - "less-loader": "^11.1.0", - "mini-css-extract-plugin": "^2.9.0", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.5", + "css-minimizer-webpack-plugin": "^8.0.0", + "html-webpack-plugin": "^5.6.8", + "jsdom": "^30.1.0", + "less": "^4.9.1", + "less-loader": "^13.0.0", + "mini-css-extract-plugin": "^2.10.2", "portfinder": "^1.0.32", - "postcss": "^8.4.47", + "postcss": "^8.5.0", "postcss-import": "^15.1.0", - "postcss-loader": "^7.3.3", - "terser-webpack-plugin": "^5.3.10", + "postcss-loader": "^8.2.1", + "terser-webpack-plugin": "^5.6.1", + "vite": "^7.2.0", + "vitest": "^5.0.1", "vue-loader": "^15.11.1", "vue-style-loader": "^4.0.0", - "vue-template-compiler": "^2.6.14", - "webpack": "^5.94.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.2.2", + "vue-template-compiler": "^2.7.16", + "webpack": "^5.111.0", + "webpack-bundle-analyzer": "^5.4.0", + "webpack-cli": "^7.2.3", + "webpack-dev-server": "^6.0.0", "webpack-merge": "^5.10.0" }, "engines": { - "node": ">= 18.0.0", - "npm": ">= 8.0.0" + "node": ">= 22.0.0", + "npm": ">= 10.0.0" }, "browserslist": [ "> 1%", diff --git a/datapoly-manager-ui/src/assets/axios.js b/datapoly-manager-ui/src/assets/axios.js index 4388a83..6c1dd1b 100644 --- a/datapoly-manager-ui/src/assets/axios.js +++ b/datapoly-manager-ui/src/assets/axios.js @@ -1,4 +1,5 @@ import Axios from 'axios'; +import { setHeader } from './http.js'; var root = process.env.API_ROOT || ''; const axios = Axios.create(); @@ -21,7 +22,7 @@ axios.interceptors.request.use((config) => { } // Send language header for backend i18n const locale = localStorage.getItem('locale') || 'zh-CN'; - config.headers['Accept-Language'] = locale; + setHeader(config, 'Accept-Language', locale); return config; }); diff --git a/datapoly-manager-ui/src/assets/http.js b/datapoly-manager-ui/src/assets/http.js new file mode 100644 index 0000000..0a6bee8 --- /dev/null +++ b/datapoly-manager-ui/src/assets/http.js @@ -0,0 +1,68 @@ +// Use of this source code is governed by a BSD-style license + +// 从 main.js 抽出的 HTTP 拦截器与鉴权跳转逻辑(行为不变,供单测覆盖)。 +// axios 1.x 起拦截器里的 config.headers 是 AxiosHeaders 实例,统一经 setHeader +// 写入以同时兼容实例与普通对象两种形态。 + +export function setHeader (config, name, value) { + if (config.headers && typeof config.headers.set === 'function') { + config.headers.set(name, value) + } else { + config.headers = config.headers || {} + config.headers[name] = value + } +} + +export function redirectToLoginIfAuthError (body, router, notifyError) { + if (!body) { + return + } + if (body.code === 401 || body.code === 403) { + // Only redirect when not already on the login page + if (router.currentRoute.path !== '/login') { + router.push({path: '/login'}).catch(() => { + }) + } + return + } + if (body.code === 404 && router.currentRoute.path !== '/login') { + // Business 404 is not an auth failure — surfacing it instead of silently logging out + notifyError(body.message || 'Not Found') + } +} + +export function setupHttpInterceptors (axios, router, notifyError) { + // http request interceptor + axios.interceptors.request.use(config => { + // Attach Authorization header with token if present + const token = sessionStorage.getItem('token') + if (token) { + setHeader(config, 'Authorization', 'Bearer ' + token) + } + + // Send language header for backend i18n + const locale = localStorage.getItem('locale') || 'zh-CN' + setHeader(config, 'Accept-Language', locale) + + return config + }, function (error) { + return Promise.reject(error) + }) + + // Response interceptor + axios.interceptors.response.use(res => { + redirectToLoginIfAuthError(res.data, router, notifyError) + + return res + }, error => { + // Backend now maps real HTTP status from error codes (H5): for non-2xx + // responses with a ResultEntity body, normalize to the old shape (resolve + // {data}) so page-level code checking res.data.code stays unchanged + const resp = error.response + if (resp && resp.data && resp.data.code !== undefined) { + redirectToLoginIfAuthError(resp.data, router, notifyError) + return Promise.resolve({data: resp.data, status: resp.status, headers: resp.headers}) + } + return Promise.reject(resp) + }) +} diff --git a/datapoly-manager-ui/src/lang/merge.js b/datapoly-manager-ui/src/lang/merge.js new file mode 100644 index 0000000..be4f67e --- /dev/null +++ b/datapoly-manager-ui/src/lang/merge.js @@ -0,0 +1,23 @@ +// Use of this source code is governed by a BSD-style license + +// 从 main.js 抽出的扩展 i18n 深合并逻辑(行为不变,供单测覆盖): +// 在创建 i18n 实例前把 UI 扩展词条深合并进宿主 messages,扩展页共享同一个 +// $t() 与 locale 处理,且宿主键(含共享的 `menu` 命名空间)保持不被覆盖。 +// '@extension' 入口指向仓库内 stub 时为 no-op。 +export function mergeExtensionI18n (target, ext) { + Object.keys(ext || {}).forEach(locale => { + deepMerge(target[locale] || (target[locale] = {}), ext[locale] || {}) + }) +} + +export function deepMerge (target, source) { + Object.keys(source).forEach(key => { + const value = source[key] + if (value && typeof value === 'object' && !Array.isArray(value) + && target[key] && typeof target[key] === 'object') { + deepMerge(target[key], value) + } else { + target[key] = value + } + }) +} diff --git a/datapoly-manager-ui/src/main.js b/datapoly-manager-ui/src/main.js index eafd0b1..87be8ca 100644 --- a/datapoly-manager-ui/src/main.js +++ b/datapoly-manager-ui/src/main.js @@ -8,6 +8,8 @@ import ElementUI from 'element-ui'; import VueI18n from 'vue-i18n' import messages from './lang' import extension from '@extension' +import { mergeExtensionI18n } from './lang/merge' +import { setupHttpInterceptors } from './assets/http.js' import './assets/iconfont/iconfont.css' import './assets/dbicon/iconfont.css' import './assets/dbicon/iconfont.js' @@ -42,24 +44,6 @@ const i18n = new VueI18n({ messages }) -function mergeExtensionI18n (target, ext) { - Object.keys(ext || {}).forEach(locale => { - deepMerge(target[locale] || (target[locale] = {}), ext[locale] || {}) - }) -} - -function deepMerge (target, source) { - Object.keys(source).forEach(key => { - const value = source[key] - if (value && typeof value === 'object' && !Array.isArray(value) - && target[key] && typeof target[key] === 'object') { - deepMerge(target[key], value) - } else { - target[key] = value - } - }) -} - Vue.prototype.$http = axios Vue.config.productionTip = false if (process.env.NODE_ENV !== 'production') { @@ -68,59 +52,9 @@ if (process.env.NODE_ENV !== 'production') { } Vue.prototype.$echarts = echarts - -// http request interceptor -axios.interceptors.request.use(config => { - - // Attach Authorization header with token if present - let token = sessionStorage.getItem('token'); - if (token) { - config.headers.Authorization = 'Bearer ' + token; - } - - // Send language header for backend i18n - const locale = localStorage.getItem('locale') || 'zh-CN'; - config.headers['Accept-Language'] = locale; - - return config; -}, function (error) { - return Promise.reject(error) -}) - -// Response interceptor -axios.interceptors.response.use(res => { - redirectToLoginIfAuthError(res.data) - - return res -}, error => { - // Backend now maps real HTTP status from error codes (H5): for non-2xx - // responses with a ResultEntity body, normalize to the old shape (resolve - // {data}) so page-level code checking res.data.code stays unchanged - const resp = error.response - if (resp && resp.data && resp.data.code !== undefined) { - redirectToLoginIfAuthError(resp.data) - return Promise.resolve({data: resp.data, status: resp.status, headers: resp.headers}) - } - return Promise.reject(resp) -}) - -function redirectToLoginIfAuthError(body) { - if (!body) { - return; - } - if (body.code === 401 || body.code === 403) { - // Only redirect when not already on the login page - if (router.currentRoute.path !== '/login') { - router.push({path: "/login"}).catch(() => { - }); - } - return; - } - if (body.code === 404 && router.currentRoute.path !== '/login') { - // Business 404 is not an auth failure — surfacing it instead of silently logging out - ElementUI.Message.error(body.message || 'Not Found'); - } -} +// http interceptors (token/language headers, auth-error redirect, ResultEntity +// error normalization) — extracted to assets/http.js and covered by unit tests +setupHttpInterceptors(axios, router, message => ElementUI.Message.error(message)) /* eslint-disable no-new */ new Vue({ diff --git a/datapoly-manager-ui/test/axios.spec.js b/datapoly-manager-ui/test/axios.spec.js new file mode 100644 index 0000000..af718b9 --- /dev/null +++ b/datapoly-manager-ui/test/axios.spec.js @@ -0,0 +1,47 @@ +// Use of this source code is governed by a BSD-style license +import { describe, it, expect, beforeEach, vi } from 'vitest' + +// 基础 axios 实例(assets/axios.js):URL 守卫、API_ROOT 前缀与语言头。 +// 通过自定义 adapter 捕获最终请求头,验证 axios 1.x AxiosHeaders 下的真实序列化结果。 +import axios from '../src/assets/axios.js' + +function withCaptureAdapter (instance) { + let captured = null + instance.defaults.adapter = async config => { + captured = config + return { data: { code: 0 }, status: 200, statusText: 'OK', headers: {}, config } + } + return () => captured +} + +beforeEach(() => { + localStorage.clear() +}) + +describe('基础请求拦截器', () => { + it('携带 Accept-Language(localStorage 优先,默认 zh-CN)', async () => { + const capture = withCaptureAdapter(axios) + await axios.get('/health') + expect(capture().headers.get('accept-language')).toBe('zh-CN') + + localStorage.setItem('locale', 'en-US') + await axios.get('/health') + expect(capture().headers.get('accept-language')).toBe('en-US') + }) + + it('API_ROOT 存在时为相对 URL 加前缀', async () => { + vi.resetModules() + vi.stubEnv('API_ROOT', '/gateway-prefix') + const fresh = (await import('../src/assets/axios.js')).default + const capture = withCaptureAdapter(fresh) + await fresh.get('/user/login') + expect(capture().url).toBe('/gateway-prefix/user/login') + vi.unstubAllEnvs() + }) + + it('缺失 URL 时拦截器直接拒绝', async () => { + const handler = axios.interceptors.request.handlers[0].fulfilled + await expect(handler({ headers: {} })).rejects.toThrow('Missing URL') + await expect(handler(undefined)).rejects.toThrow('Config is undefined') + }) +}) diff --git a/datapoly-manager-ui/test/http.spec.js b/datapoly-manager-ui/test/http.spec.js new file mode 100644 index 0000000..bef4f47 --- /dev/null +++ b/datapoly-manager-ui/test/http.spec.js @@ -0,0 +1,117 @@ +// Use of this source code is governed by a BSD-style license +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { setHeader, redirectToLoginIfAuthError, setupHttpInterceptors } from '../src/assets/http.js' + +// 注册拦截器的伪 axios:捕获 use() 调用以便直接驱动各 handler +function registerInterceptors (router) { + const notify = vi.fn() + const axios = { + interceptors: { + request: { use: vi.fn() }, + response: { use: vi.fn() } + } + } + setupHttpInterceptors(axios, router, notify) + const [requestOk] = axios.interceptors.request.use.mock.calls[0] + const [responseOk, responseError] = axios.interceptors.response.use.mock.calls[0] + return { requestOk, responseOk, responseError, notify } +} + +function fakeRouter (path) { + return { + currentRoute: { path }, + push: vi.fn(() => Promise.resolve()) + } +} + +describe('setHeader', () => { + it('普通对象 headers 直接赋值', () => { + const config = { headers: {} } + setHeader(config, 'Accept-Language', 'zh-CN') + expect(config.headers['Accept-Language']).toBe('zh-CN') + }) + + it('axios 1.x 的 AxiosHeaders 实例走 set()(大小写不敏感)', () => { + const store = {} + const config = { headers: { set: vi.fn((k, v) => { store[k.toLowerCase()] = v }) } } + setHeader(config, 'Accept-Language', 'en-US') + expect(config.headers.set).toHaveBeenCalledWith('Accept-Language', 'en-US') + expect(store['accept-language']).toBe('en-US') + }) + + it('headers 缺失时先创建普通对象', () => { + const config = {} + setHeader(config, 'Authorization', 'Bearer t') + expect(config.headers.Authorization).toBe('Bearer t') + }) +}) + +describe('请求拦截器(token 与语言头)', () => { + beforeEach(() => { + sessionStorage.clear() + localStorage.clear() + }) + + it('有 token 时注入 Bearer Authorization 与 Accept-Language', async () => { + sessionStorage.setItem('token', 'abc123') + localStorage.setItem('locale', 'en-US') + const { requestOk } = registerInterceptors(fakeRouter('/dashboard')) + const headers = {} + const config = await requestOk({ url: '/x', headers: { set: (k, v) => { headers[k.toLowerCase()] = v } } }) + expect(headers.authorization).toBe('Bearer abc123') + expect(headers['accept-language']).toBe('en-US') + expect(config.url).toBe('/x') + }) + + it('无 token / 无 locale 时使用默认 zh-CN 且不带 Authorization', async () => { + const { requestOk } = registerInterceptors(fakeRouter('/dashboard')) + const headers = {} + await requestOk({ url: '/x', headers: { set: (k, v) => { headers[k.toLowerCase()] = v } } }) + expect(headers.authorization).toBeUndefined() + expect(headers['accept-language']).toBe('zh-CN') + }) +}) + +describe('响应拦截器(鉴权跳转与 ResultEntity 归一化)', () => { + it('2xx 响应体 code=401 时跳转登录页且原样返回响应', () => { + const router = fakeRouter('/dashboard') + const { responseOk } = registerInterceptors(router) + const res = { data: { code: 401, message: 'expired' } } + expect(responseOk(res)).toBe(res) + expect(router.push).toHaveBeenCalledWith({ path: '/login' }) + }) + + it('已在登录页时不再重复跳转', () => { + const router = fakeRouter('/login') + redirectToLoginIfAuthError({ code: 401 }, router, vi.fn()) + expect(router.push).not.toHaveBeenCalled() + }) + + it('业务 404 弹出错误消息而不是登出', () => { + const notify = vi.fn() + redirectToLoginIfAuthError({ code: 404, message: '资源不存在' }, fakeRouter('/dashboard'), notify) + expect(notify).toHaveBeenCalledWith('资源不存在') + redirectToLoginIfAuthError({ code: 404 }, fakeRouter('/dashboard'), notify) + expect(notify).toHaveBeenLastCalledWith('Not Found') + }) + + it('非 2xx 带 ResultEntity 体归一化为 resolve({data})', async () => { + const router = fakeRouter('/dashboard') + const { responseError } = registerInterceptors(router) + const body = { code: 7, message: '业务失败' } + const headers = { 'x-trace': '1' } + const normalized = await responseError({ response: { data: body, status: 500, headers } }) + expect(normalized).toEqual({ data: body, status: 500, headers }) + }) + + it('非 ResultEntity 错误(无 code 字段)原样 reject', async () => { + const { responseError } = registerInterceptors(fakeRouter('/dashboard')) + const resp = { data: 'Bad Gateway', status: 502 } + await expect(responseError({ response: resp })).rejects.toBe(resp) + }) + + it('网络错误(无 response)reject undefined', async () => { + const { responseError } = registerInterceptors(fakeRouter('/dashboard')) + await expect(responseError(new Error('network'))).rejects.toBeUndefined() + }) +}) diff --git a/datapoly-manager-ui/test/merge.spec.js b/datapoly-manager-ui/test/merge.spec.js new file mode 100644 index 0000000..de04512 --- /dev/null +++ b/datapoly-manager-ui/test/merge.spec.js @@ -0,0 +1,59 @@ +// Use of this source code is governed by a BSD-style license +import { describe, it, expect } from 'vitest' +import { deepMerge, mergeExtensionI18n } from '../src/lang/merge' + +describe('deepMerge', () => { + it('深合并嵌套对象且宿主兄弟键保留', () => { + const target = { menu: { dashboard: '总览', datasource: '数据源' }, other: 1 } + deepMerge(target, { menu: { datasource: '扩展数据源', extra: '新增' } }) + expect(target).toEqual({ + menu: { dashboard: '总览', datasource: '扩展数据源', extra: '新增' }, + other: 1 + }) + }) + + it('数组整体替换,不逐项合并', () => { + const target = { routes: [1, 2, 3] } + deepMerge(target, { routes: [9] }) + expect(target.routes).toEqual([9]) + }) + + it('源值为 null/undefined 时按值覆盖', () => { + const target = { a: 'host', b: 'host' } + deepMerge(target, { a: null }) + expect(target).toEqual({ a: null, b: 'host' }) + }) + + it('目标缺少对应键时直接写入源值', () => { + const target = {} + deepMerge(target, { a: { b: 1 } }) + expect(target).toEqual({ a: { b: 1 } }) + }) +}) + +describe('mergeExtensionI18n', () => { + it('扩展词条并入宿主 locale 且宿主其余键不被覆盖', () => { + const messages = { 'zh-CN': { menu: { login: '登录' } } } + mergeExtensionI18n(messages, { 'zh-CN': { ext: { title: '扩展页' }, menu: { extra: '扩展菜单' } } }) + expect(messages['zh-CN']).toEqual({ + menu: { login: '登录', extra: '扩展菜单' }, + ext: { title: '扩展页' } + }) + }) + + it('扩展引入新 locale 时自动创建', () => { + const messages = { 'zh-CN': {} } + mergeExtensionI18n(messages, { 'ja-JP': { hello: 'こんにちは' } }) + expect(messages['ja-JP']).toEqual({ hello: 'こんにちは' }) + expect(messages['zh-CN']).toEqual({}) + }) + + it('空扩展(stub 场景)为 no-op', () => { + const messages = { 'zh-CN': { menu: { login: '登录' } }, 'en-US': {} } + const snapshot = JSON.parse(JSON.stringify(messages)) + mergeExtensionI18n(messages, {}) + mergeExtensionI18n(messages, null) + mergeExtensionI18n(messages, { 'zh-CN': {}, 'en-US': {} }) + expect(messages).toEqual(snapshot) + }) +}) diff --git a/datapoly-manager-ui/vitest.config.js b/datapoly-manager-ui/vitest.config.js new file mode 100644 index 0000000..3df671a --- /dev/null +++ b/datapoly-manager-ui/vitest.config.js @@ -0,0 +1,8 @@ +// Use of this source code is governed by a BSD-style license +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'jsdom' + } +}) diff --git a/datapoly-manager/pom.xml b/datapoly-manager/pom.xml index b7175a5..94b35d0 100644 --- a/datapoly-manager/pom.xml +++ b/datapoly-manager/pom.xml @@ -40,9 +40,8 @@ - junit - junit - test + org.springdoc + springdoc-openapi-starter-webmvc-ui diff --git a/datapoly-manager/src/main/java/com/cs/manager/config/AdminPasswordInitializer.java b/datapoly-manager/src/main/java/com/cs/manager/config/AdminPasswordInitializer.java index 1bbbfdd..b91528f 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/config/AdminPasswordInitializer.java +++ b/datapoly-manager/src/main/java/com/cs/manager/config/AdminPasswordInitializer.java @@ -12,7 +12,7 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; diff --git a/datapoly-manager/src/main/java/com/cs/manager/config/AuthenticationConfiguration.java b/datapoly-manager/src/main/java/com/cs/manager/config/AuthenticationConfiguration.java index cbbe59d..b97bd81 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/config/AuthenticationConfiguration.java +++ b/datapoly-manager/src/main/java/com/cs/manager/config/AuthenticationConfiguration.java @@ -13,8 +13,8 @@ import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.*; +import jakarta.annotation.Resource; +import jakarta.servlet.http.*; import java.util.Arrays; @Configuration diff --git a/datapoly-manager/src/main/java/com/cs/manager/config/McpAdminServerConfiguration.java b/datapoly-manager/src/main/java/com/cs/manager/config/McpAdminServerConfiguration.java new file mode 100644 index 0000000..e8c3334 --- /dev/null +++ b/datapoly-manager/src/main/java/com/cs/manager/config/McpAdminServerConfiguration.java @@ -0,0 +1,77 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.manager.config; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.consts.Constants; +import com.cs.common.util.PomVersionUtils; +import com.cs.persistence.dao.McpClientDao; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServer; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.transport.WebMvcSseServerAuthChecker; +import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; +import io.modelcontextprotocol.server.transport.WebMvcStreamHttpServerProvider; +import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +/** + * 管理 MCP 服务:与数据工具服务({@link McpServerConfiguration})并行的第二个 MCP Server 实例, + * 暴露 DataPoly 管理实体(数据源/模块/分组/API/应用客户端/MCP配置)的增删改查工具,供 AI Agent 运维使用。 + * 仅接受 manage_flag=1 的 MCP 令牌,与数据工具令牌隔离,避免普通令牌获得管理能力。 + */ +@Configuration +public class McpAdminServerConfiguration { + + @Bean + public WebMvcSseServerAuthChecker mcpAdminAuthChecker() { + return new WebMvcSseServerAuthChecker() { + + @Override + public String getTokenParamName() { + return Constants.DEFAULT_MCP_TOKEN_PRAM_NAME; + } + + @Override + public boolean checkTokenValid(String token) { + McpClientDao clientDao = SpringUtil.getBean(McpClientDao.class); + return clientDao.existsManageAccessToken(token); + } + }; + } + + @Bean + public WebMvcSseServerTransportProvider mcpAdminSseTransportProvider( + @Qualifier("mcpAdminAuthChecker") WebMvcSseServerAuthChecker checker) { + return new WebMvcSseServerTransportProvider(new ObjectMapper(), checker, + Constants.ADMIN_MESSAGE_ENDPOINT, Constants.ADMIN_SSE_ENDPOINT); + } + + @Bean + public McpSyncServer mcpAdminSyncServer( + @Qualifier("mcpAdminSseTransportProvider") WebMvcSseServerTransportProvider transportProvider) { + return McpServer.sync(transportProvider) + .serverInfo(Constants.MCP_ADMIN_SERVER_NAME, PomVersionUtils.getCachedProjectVersion()) + .capabilities(ServerCapabilities.builder().tools(true).build()) + .build(); + } + + @Bean + public WebMvcStreamHttpServerProvider mcpAdminStreamHttpServerProvider( + @Qualifier("mcpAdminAuthChecker") WebMvcSseServerAuthChecker checker, + @Qualifier("mcpAdminSyncServer") McpSyncServer mcpAdminSyncServer) { + return new WebMvcStreamHttpServerProvider(new ObjectMapper(), checker, + Constants.ADMIN_STREAM_ENDPOINT, mcpAdminSyncServer); + } + + @Bean + public RouterFunction mcpAdminRouterFunction( + @Qualifier("mcpAdminSseTransportProvider") WebMvcSseServerTransportProvider transportProvider, + @Qualifier("mcpAdminStreamHttpServerProvider") WebMvcStreamHttpServerProvider streamHttpServerProvider) { + return transportProvider.getRouterFunction().and(streamHttpServerProvider.getRouterFunction()); + } +} diff --git a/datapoly-manager/src/main/java/com/cs/manager/config/SwaggerConfiguration.java b/datapoly-manager/src/main/java/com/cs/manager/config/SwaggerConfiguration.java index a836615..0a9ee71 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/config/SwaggerConfiguration.java +++ b/datapoly-manager/src/main/java/com/cs/manager/config/SwaggerConfiguration.java @@ -2,105 +2,46 @@ // Use of this source code is governed by a BSD-style license package com.cs.manager.config; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.annotation.*; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; -import springfox.documentation.builders.*; -import springfox.documentation.service.*; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -import javax.servlet.http.*; -import java.lang.reflect.Field; -import java.util.*; -import java.util.stream.Collectors; - -@Slf4j +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.parameters.Parameter; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * API 文档由 springdoc 提供(springfox 已停更且不兼容 Boot 3)。 + * 文档与 UI 的总开关经 springdoc.api-docs.enabled / springdoc.swagger-ui.enabled 配置 + * (沿用环境变量 DATAPOLY_MANAGER_SWAGGER_ENABLE,见 application.yaml)。 + */ @Configuration -@EnableSwagger2 public class SwaggerConfiguration { private static final String API_CONTROLLER_PACKAGE = "com.cs.manager.controller"; - @Value("${datapoly.manager.swagger.enable:true}") - private boolean enable; - @Bean - public Docket managerApi() { - RequestParameterBuilder ticketPar = new RequestParameterBuilder(); - List pars = new ArrayList<>(); - ticketPar.name("Authorization") - .description("认证头,格式:Bearer {token}") - .in(ParameterType.HEADER) - .required(false) + public GroupedOpenApi managerApi() { + return GroupedOpenApi.builder() + .group("Manager的接口") + .packagesToScan(API_CONTROLLER_PACKAGE) + .addOperationCustomizer((operation, handlerMethod) -> { + operation.addParametersItem(new Parameter() + .name("Authorization") + .description("认证头,格式:Bearer {token}") + .in("header") + .required(false)); + return operation; + }) .build(); - pars.add(ticketPar.build()); - - return new Docket(DocumentationType.SWAGGER_2) - .enable(enable) - .groupName("Manager的接口") - .apiInfo(new ApiInfoBuilder() - .title("DataPoly管理服务API文档") - .description("在线API文档") - .version("1.0") - .build()) - .select() - .apis(RequestHandlerSelectors.basePackage(API_CONTROLLER_PACKAGE)) - .paths(PathSelectors.any()) - .build() - .globalRequestParameters(pars) - .ignoredParameterTypes(HttpServletResponse.class, HttpServletRequest.class); } - /** - * Springfox 3.0 与 Boot 2.6+ 的 actuator 端点不兼容(WebMvcRequestHandlerProvider 遍历到 - * 使用 PathPatternParser 的 handler mapping 时 NPE)。在 provider 初始化后剔除基于 - * PathPatternParser 的 mapping(actuator 端点本就不进 swagger 文档),保证启动不失败。 - */ @Bean - public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { - return new BeanPostProcessor() { - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof WebMvcRequestHandlerProvider) { - customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); - } - return bean; - } - - private > void customizeSpringfoxHandlerMappings(List mappings) { - List copy = mappings.stream() - .filter(mapping -> mapping.getPatternParser() == null) - .collect(Collectors.toList()); - if (copy.size() != mappings.size()) { - log.info("Springfox: excluded {} PathPatternParser-based handler mapping(s) (actuator)", - mappings.size() - copy.size()); - mappings.clear(); - mappings.addAll(copy); - } - } - - @SuppressWarnings("unchecked") - private List> getHandlerMappings(Object bean) { - try { - Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); - if (field == null) { - return Collections.emptyList(); - } - field.setAccessible(true); - return (List>) field.get(bean); - } catch (IllegalAccessException e) { - log.warn("Springfox handler mapping adjustment skipped: {}", e.getMessage()); - return Collections.emptyList(); - } - } - }; + public OpenAPI managerOpenApi() { + return new OpenAPI() + .info(new Info() + .title("DataPoly管理服务API文档") + .description("在线API文档") + .version("1.0")); } } diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/AliveHealthController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/AliveHealthController.java index ba2a5fb..d924547 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/AliveHealthController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/AliveHealthController.java @@ -5,11 +5,11 @@ import com.cs.common.consts.Constants; import com.cs.common.dto.ResultEntity; import com.cs.common.util.PomVersionUtils; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -@Api(tags = {"存活健康接口"}) +@Tag(name = "存活健康接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/health") public class AliveHealthController { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiAssignmentController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiAssignmentController.java index 25ab0aa..7cf17bb 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiAssignmentController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiAssignmentController.java @@ -7,20 +7,21 @@ import com.cs.common.enums.*; import com.cs.core.dto.*; import com.cs.core.service.*; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -@Api(tags = {"API配置接口"}) +@Tag(name = "API配置接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/assignment") public class ApiAssignmentController { @@ -30,13 +31,13 @@ public class ApiAssignmentController { @Resource private ExportImportService exportImportService; - @ApiOperation(value = "获取自动提示列表") + @Operation(summary = "获取自动提示列表") @GetMapping(value = "/completions", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity completions() { return ResultEntity.success(apiAssignmentService.completions()); } - @ApiOperation(value = "响应属性命名策略") + @Operation(summary = "响应属性命名策略") @GetMapping(value = "/response-naming-strategy", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity responseNamingStrategy() { return ResultEntity.success( @@ -51,7 +52,7 @@ public ResultEntity responseNamingStrategy() { ); } - @ApiOperation(value = "响应数据类型格式") + @Operation(summary = "响应数据类型格式") @GetMapping(value = "/response-type-format", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity responseTypeFormat() { return ResultEntity.success( @@ -67,73 +68,73 @@ public ResultEntity responseTypeFormat() { ); } - @ApiOperation(value = "获取SQL中的入参列表") + @Operation(summary = "获取SQL中的入参列表") @PostMapping(value = "/parse", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity parse(@RequestParam("sql") String sql) { return ResultEntity.success(apiAssignmentService.parseSqlParams(sql)); } - @ApiOperation(value = "调试API配置") + @Operation(summary = "调试API配置") @PostMapping(value = "/debug", produces = MediaType.APPLICATION_JSON_VALUE) public void debug(@Valid @RequestBody ApiDebugExecuteRequest request, HttpServletResponse response) throws IOException { apiAssignmentService.debugExecute(request, response); } - @ApiOperation(value = "添加API配置") + @Operation(summary = "添加API配置") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @RequestBody ApiAssignmentSaveRequest request) { Long id = apiAssignmentService.createAssignment(request); return ResultEntity.success(id); } - @ApiOperation(value = "更新API配置") + @Operation(summary = "更新API配置") @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@Valid @RequestBody ApiAssignmentSaveRequest request) { apiAssignmentService.updateAssignment(request); return ResultEntity.success(); } - @ApiOperation(value = "查看API配置") + @Operation(summary = "查看API配置") @GetMapping(value = "/detail/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity detail(@PathVariable("id") Long id) { return ResultEntity.success(apiAssignmentService.detailAssignment(id)); } - @ApiOperation(value = "删除API配置") + @Operation(summary = "删除API配置") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { apiAssignmentService.deleteAssignment(id); return ResultEntity.success(); } - @ApiOperation(value = "查询API配置列表") + @Operation(summary = "查询API配置列表") @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult listAll(@RequestBody AssignmentSearchRequest request) { return apiAssignmentService.listAll(request); } - @ApiOperation(value = "上线接口搜索") + @Operation(summary = "上线接口搜索") @PostMapping(value = "/search", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult search(@RequestBody ApiOnlineSearchRequest request) { return apiAssignmentService.search(request); } - @ApiOperation(value = "批量更新授权组") + @Operation(summary = "批量更新授权组") @PostMapping(value = "/group/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity updateGroup(@PathVariable("groupId") Long groupId, @RequestBody List ids) { apiAssignmentService.updateGroup(groupId, ids); return ResultEntity.success(); } - @ApiOperation(value = "发布版本") + @Operation(summary = "发布版本") @PutMapping(value = "/publish", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity publish(@Valid @RequestBody AssignmentPublishRequest request) { apiAssignmentService.publish(request); return ResultEntity.success(); } - @ApiOperation(value = "上线") + @Operation(summary = "上线") @PutMapping(value = "/deploy/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity deploy(@PathVariable("id") Long id, @RequestParam(value = "commitId", required = false) Long commitId) { @@ -141,20 +142,20 @@ public ResultEntity deploy(@PathVariable("id") Long id, return ResultEntity.success(); } - @ApiOperation(value = "下线") + @Operation(summary = "下线") @PutMapping(value = "/retire/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity retire(@PathVariable("id") Long id) { apiAssignmentService.retireAssignment(id); return ResultEntity.success(); } - @ApiOperation(value = "批量导出API配置") + @Operation(summary = "批量导出API配置") @PostMapping(value = "/export", produces = MediaType.APPLICATION_JSON_VALUE) public void exportAssignments(@Valid @NotEmpty @RequestBody List ids, HttpServletResponse response) { exportImportService.exportAssignments(ids, response); } - @ApiOperation(value = "批量导入API配置") + @Operation(summary = "批量导入API配置") @PostMapping(value = "/import", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity importAssignments(@RequestPart(value = "file") MultipartFile file) throws IOException { exportImportService.importAssignments(file); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiGroupController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiGroupController.java index 6dac253..6f2d75c 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiGroupController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiGroupController.java @@ -7,16 +7,17 @@ import com.cs.core.dto.EntitySearchRequest; import com.cs.core.service.ApiGroupService; import com.cs.persistence.entity.ApiGroupEntity; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; -@Api(tags = {"分组管理接口"}) +@Tag(name = "分组管理接口") @Validated @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/group") @@ -25,14 +26,14 @@ public class ApiGroupController { @Resource private ApiGroupService apiGroupService; - @ApiOperation(value = "添加分组") + @Operation(summary = "添加分组") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { apiGroupService.createGroup(name); return ResultEntity.success(); } - @ApiOperation(value = "更新分组") + @Operation(summary = "更新分组") @PostMapping(value = "/update/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@PathVariable("id") Long id, @Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { @@ -40,14 +41,14 @@ public ResultEntity update(@PathVariable("id") Long id, return ResultEntity.success(); } - @ApiOperation(value = "删除分组") + @Operation(summary = "删除分组") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { apiGroupService.deleteGroup(id); return ResultEntity.success(); } - @ApiOperation(value = "分组列表") + @Operation(summary = "分组列表") @PostMapping(value = "/listAll", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult listAll(@RequestBody EntitySearchRequest request) { return apiGroupService.listAll(request); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiModuleController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiModuleController.java index 5c2ac09..1322a2e 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/ApiModuleController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/ApiModuleController.java @@ -7,16 +7,17 @@ import com.cs.core.dto.EntitySearchRequest; import com.cs.core.service.ApiModuleService; import com.cs.persistence.entity.ApiModuleEntity; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; -@Api(tags = {"模块管理接口"}) +@Tag(name = "模块管理接口") @Validated @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/module") @@ -25,14 +26,14 @@ public class ApiModuleController { @Resource private ApiModuleService apiModuleService; - @ApiOperation(value = "添加模块") + @Operation(summary = "添加模块") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { apiModuleService.createModule(name); return ResultEntity.success(); } - @ApiOperation(value = "更新模块") + @Operation(summary = "更新模块") @PostMapping(value = "/update/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@PathVariable("id") Long id, @Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { @@ -40,20 +41,20 @@ public ResultEntity update(@PathVariable("id") Long id, return ResultEntity.success(); } - @ApiOperation(value = "删除模块") + @Operation(summary = "删除模块") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { apiModuleService.deleteModule(id); return ResultEntity.success(); } - @ApiOperation(value = "模块列表") + @Operation(summary = "模块列表") @PostMapping(value = "/listAll", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult listAll(@RequestBody EntitySearchRequest request) { return apiModuleService.listAll(request); } - @ApiOperation(value = "模块接口树") + @Operation(summary = "模块接口树") @GetMapping(value = "/moduleTree/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity moduleTree(@PathVariable("id") Long groupId) { return ResultEntity.success(apiModuleService.moduleTree(groupId)); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/AppClientController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/AppClientController.java index 71579f1..77010a1 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/AppClientController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/AppClientController.java @@ -6,15 +6,16 @@ import com.cs.common.dto.*; import com.cs.core.dto.*; import com.cs.core.service.AppClientService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import java.util.List; -@Api(tags = {"客户端应用接口"}) +@Tag(name = "客户端应用接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/client") public class AppClientController { @@ -22,40 +23,40 @@ public class AppClientController { @Resource private AppClientService appClientService; - @ApiOperation(value = "添加客户端应用") + @Operation(summary = "添加客户端应用") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @RequestBody AppClientSaveRequest request) { appClientService.create(request); return ResultEntity.success(); } - @ApiOperation(value = "删除客户端应用") + @Operation(summary = "删除客户端应用") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { appClientService.delete(id); return ResultEntity.success(); } - @ApiOperation(value = "客户端应用列表") + @Operation(summary = "客户端应用列表") @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult searchList(@RequestBody AppClientSearchRequest request) { return appClientService.searchList(request); } - @ApiOperation(value = "查询应用密钥") + @Operation(summary = "查询应用密钥") @GetMapping(value = "/secret/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity secret(@PathVariable("id") Long id) { return ResultEntity.success(appClientService.getSecret(id)); } - @ApiOperation(value = "创建分组关联") + @Operation(summary = "创建分组关联") @PostMapping(value = "/auth/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity createGroupAuth(@Valid @RequestBody AppClientGroupRequest request) { appClientService.createGroupAuth(request); return ResultEntity.success(); } - @ApiOperation(value = "查询分组关联") + @Operation(summary = "查询分组关联") @GetMapping(value = "/auth/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public List getGroupAuth(@PathVariable("id") Long id) { return appClientService.getGroupAuth(id); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/AuthenticationController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/AuthenticationController.java index c70d56e..d92817b 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/AuthenticationController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/AuthenticationController.java @@ -7,16 +7,20 @@ import com.cs.core.dto.UserLoginRequest; import com.cs.core.service.SystemUserService; import com.cs.manager.config.LoginGuard; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; -@Api(tags = {"登陆认证接口"}) +@Tag(name = "登陆认证接口") @Slf4j @RestController @RequestMapping(value = "/user") @@ -28,7 +32,7 @@ public class AuthenticationController { @Resource private LoginGuard loginGuard; - @ApiOperation(value = "账号登录", notes = "使用一个账号密码登录") + @Operation(summary = "账号登录", description = "使用一个账号密码登录") @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity login(@Valid @RequestBody UserLoginRequest request, HttpServletRequest httpRequest) { String username = request.getUsername(); @@ -47,9 +51,9 @@ public ResultEntity login(@Valid @RequestBody UserLoginRequest request, HttpServ } @GetMapping(value = "/logout", produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "认证登出", notes = "登出系统") - @ApiImplicitParams({ - @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "token标记", required = true) + @Operation(summary = "认证登出", description = "登出系统") + @Parameters({ + @Parameter(in = ParameterIn.HEADER, name = "token", description = "token标记", required = true) }) public ResultEntity logout(HttpServletRequest request) { systemUserService.logout(request); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/DataSourceController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/DataSourceController.java index d35c168..233935d 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/DataSourceController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/DataSourceController.java @@ -8,14 +8,15 @@ import com.cs.core.driver.DriverLoadService; import com.cs.core.dto.*; import com.cs.core.service.DataSourceService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; -@Api(tags = {"数据源管理接口"}) +@Tag(name = "数据源管理接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/datasource") public class DataSourceController { @@ -25,66 +26,66 @@ public class DataSourceController { @Resource private DriverLoadService driverLoadService; - @ApiOperation(value = "数据库类型") + @Operation(summary = "数据库类型") @GetMapping(value = "/types", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getTypes() { return ResultEntity.success(datasourceService.getTypes()); } - @ApiOperation(value = "数据库驱动") + @Operation(summary = "数据库驱动") @GetMapping(value = "/{type}/drivers", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getDrivers(@PathVariable("type") ProductTypeEnum type) { return ResultEntity.success(driverLoadService.getDrivers(type)); } - @ApiOperation(value = "连接预测试") + @Operation(summary = "连接预测试") @PostMapping(value = "/preTest", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity preTest(@Valid @RequestBody DataSourceBaseRequest request) { datasourceService.preTest(request); return ResultEntity.success(); } - @ApiOperation(value = "数据源列表") + @Operation(summary = "数据源列表") @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult getConnections(@RequestBody EntitySearchRequest request) { return datasourceService.searchList(request); } - @ApiOperation(value = "数据源详情") + @Operation(summary = "数据源详情") @GetMapping(value = "/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getDetail(@PathVariable("id") Long id) { return ResultEntity.success(datasourceService.getDetailById(id)); } - @ApiOperation(value = "数据源测试连接") + @Operation(summary = "数据源测试连接") @GetMapping(value = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity test(@PathVariable("id") Long id) { datasourceService.testDataSource(id); return ResultEntity.success(); } - @ApiOperation(value = "添加数据源") + @Operation(summary = "添加数据源") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @RequestBody DataSourceSaveRequest request) { datasourceService.createDataSource(request); return ResultEntity.success(); } - @ApiOperation(value = "修改数据源") + @Operation(summary = "修改数据源") @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@Valid @RequestBody DataSourceSaveRequest request) { datasourceService.updateDataSource(request); return ResultEntity.success(); } - @ApiOperation(value = "删除数据源") + @Operation(summary = "删除数据源") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { datasourceService.deleteDataSource(id); return ResultEntity.success(); } - @ApiOperation(value = "数据源名称列表") + @Operation(summary = "数据源名称列表") @GetMapping(value = "/list/name", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult getNameList( @RequestParam(value = "page", required = false) Integer page, @@ -92,27 +93,27 @@ public PageResult getNameList( return datasourceService.getDataSourceNames(page, size); } - @ApiOperation(value = "查询数据源的Schema列表") + @Operation(summary = "查询数据源的Schema列表") @GetMapping(value = "/schemas/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getSchemas(@PathVariable("id") Long id) { return ResultEntity.success(datasourceService.getDatasourceSchemas(id)); } - @ApiOperation(value = "查询数据源在指定Schema下的所有物理表列表") + @Operation(summary = "查询数据源在指定Schema下的所有物理表列表") @GetMapping(value = "/tables/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getSchemaTables(@PathVariable("id") Long id, @RequestParam("schema") String schema) { return ResultEntity.success(datasourceService.getSchemaTables(id, schema)); } - @ApiOperation(value = "查询数据源在指定Schema下的所有视图表列表") + @Operation(summary = "查询数据源在指定Schema下的所有视图表列表") @GetMapping(value = "/views/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getSchemaViews(@PathVariable("id") Long id, @RequestParam("schema") String schema) { return ResultEntity.success(datasourceService.getSchemaViews(id, schema)); } - @ApiOperation(value = "查询数据源指定表的列信息") + @Operation(summary = "查询数据源指定表的列信息") @GetMapping(value = "/columns/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getTableColumns(@PathVariable("id") Long id, @RequestParam("schema") String schema, @RequestParam("table") String table) { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/DataTaskController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/DataTaskController.java index cd276b0..653b458 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/DataTaskController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/DataTaskController.java @@ -9,13 +9,13 @@ import com.cs.core.dto.*; import com.cs.core.service.ApiAssignmentService; import com.cs.persistence.util.PageUtils; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; @@ -25,7 +25,7 @@ * order / type formats), synchronous previews and job submission with status polling * for completion. Delivery targets themselves come from DataTaskSink extensions. */ -@Api(tags = {"数据任务接口"}) +@Tag(name = "数据任务接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/data-task") public class DataTaskController { @@ -37,7 +37,7 @@ public class DataTaskController { @Resource private DataTaskSinkRegistry sinkRegistry; - @ApiOperation(value = "已注册的投递类型(sinkType 取值来源)") + @Operation(summary = "已注册的投递类型(sinkType 取值来源)") @GetMapping(value = "/sink-types", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity sinkTypes() { return ResultEntity.success(sinkRegistry.knownTypes().stream() @@ -45,39 +45,39 @@ public ResultEntity sinkTypes() { .collect(java.util.stream.Collectors.toList())); } - @ApiOperation(value = "解析SQL中的入参列表") + @Operation(summary = "解析SQL中的入参列表") @PostMapping(value = "/parse", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity parse(@RequestParam("sql") String sql) { return ResultEntity.success(apiAssignmentService.parseSqlParams(sql)); } - @ApiOperation(value = "创建数据任务") + @Operation(summary = "创建数据任务") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@RequestBody DataTaskSaveRequest request) { return ResultEntity.success(dataTaskService.create(request)); } - @ApiOperation(value = "更新数据任务") + @Operation(summary = "更新数据任务") @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@RequestBody DataTaskSaveRequest request) { dataTaskService.update(request); return ResultEntity.success(); } - @ApiOperation(value = "查看数据任务") + @Operation(summary = "查看数据任务") @GetMapping(value = "/detail/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity detail(@PathVariable("id") Long id) { return ResultEntity.success(dataTaskService.detail(id)); } - @ApiOperation(value = "删除数据任务") + @Operation(summary = "删除数据任务") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { dataTaskService.delete(id); return ResultEntity.success(); } - @ApiOperation(value = "查询数据任务列表") + @Operation(summary = "查询数据任务列表") @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult list(@RequestBody DataTaskSearchRequest request) { return PageUtils.getPage(dataTaskService.list(request), @@ -85,14 +85,14 @@ public PageResult list(@RequestBody DataTaskSearchRequest null == request.getSize() ? 0 : request.getSize()); } - @ApiOperation(value = "试运行(不产生执行记录,返回 shaping 后的行)") + @Operation(summary = "试运行(不产生执行记录,返回 shaping 后的行)") @PostMapping(value = "/preview", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity preview(@RequestBody DataTaskRunRequest request) { Map view = dataTaskService.preview(request); return ResultEntity.success(view); } - @ApiOperation(value = "提交异步执行") + @Operation(summary = "提交异步执行") @PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity submit(@RequestBody DataTaskRunRequest request, HttpServletRequest servletRequest) { Object username = servletRequest.getAttribute("username"); @@ -100,13 +100,13 @@ public ResultEntity submit(@RequestBody DataTaskRunRequest request, HttpServletR return ResultEntity.success(jobId); } - @ApiOperation(value = "执行记录详情(前端轮询此接口获取完成状态与产物地址)") + @Operation(summary = "执行记录详情(前端轮询此接口获取完成状态与产物地址)") @GetMapping(value = "/job/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity job(@PathVariable("id") Long id) { return ResultEntity.success(dataTaskService.jobView(id)); } - @ApiOperation(value = "执行记录搜索") + @Operation(summary = "执行记录搜索") @PostMapping(value = "/jobs/search", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult jobs(@RequestBody DataTaskJobSearchRequest request) { return PageUtils.getPage(dataTaskService.jobViews(request), @@ -114,7 +114,7 @@ public PageResult jobs(@RequestBody DataTaskJobSearchRequest re null == request.getSize() ? 0 : request.getSize()); } - @ApiOperation(value = "取消执行(排队中直接取消;运行中的为协作式取消)") + @Operation(summary = "取消执行(排队中直接取消;运行中的为协作式取消)") @PostMapping(value = "/job/{id}/cancel", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity cancel(@PathVariable("id") Long id) { dataTaskService.cancel(id); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/FirewallController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/FirewallController.java index d272da8..b6e55be 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/FirewallController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/FirewallController.java @@ -7,14 +7,14 @@ import com.cs.core.dto.UpdateFirewallRulesRequest; import com.cs.core.gateway.FirewallFilterService; import com.cs.persistence.entity.FirewallRulesEntity; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; -@Api(tags = {"防火墙管理接口"}) +@Tag(name = "防火墙管理接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/firewall") public class FirewallController { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/McpClientController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/McpClientController.java index ae179e1..ee4366c 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/McpClientController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/McpClientController.java @@ -7,16 +7,17 @@ import com.cs.core.dto.*; import com.cs.manager.service.McpManageService; import com.cs.persistence.entity.McpClientEntity; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; -@Api(tags = {"MCP令牌管理接口"}) +@Tag(name = "MCP令牌管理接口") @Validated @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/mcp/client") @@ -25,41 +26,43 @@ public class McpClientController { @Resource private McpManageService mcpManageService; - @ApiOperation(value = "获取MCP服务地址") + @Operation(summary = "获取MCP服务地址") @GetMapping(value = "/endpoint", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getMcpServerEndpoint() { return ResultEntity.success(mcpManageService.getMcpServerEndpoint()); } - @ApiOperation(value = "添加令牌") + @Operation(summary = "添加令牌") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) - public ResultEntity create(@Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { - mcpManageService.createClient(name); + public ResultEntity create(@Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name, + @RequestParam(value = "manage", required = false, defaultValue = "false") boolean manage) { + mcpManageService.createClient(name, manage); return ResultEntity.success(); } - @ApiOperation(value = "更新令牌") + @Operation(summary = "更新令牌") @PostMapping(value = "/update/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@PathVariable("id") Long id, - @Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name) { - mcpManageService.updateClient(id, name); + @Valid @NotBlank(message = "name不能为空") @RequestParam("name") String name, + @RequestParam(value = "manage", required = false) Boolean manage) { + mcpManageService.updateClient(id, name, manage); return ResultEntity.success(); } - @ApiOperation(value = "删除令牌") + @Operation(summary = "删除令牌") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { mcpManageService.deleteClient(id); return ResultEntity.success(); } - @ApiOperation(value = "令牌列表") + @Operation(summary = "令牌列表") @PostMapping(value = "/listAll", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult listAll(@RequestBody EntitySearchRequest request) { return mcpManageService.listClientAll(request); } - @ApiOperation(value = "获取令牌明文(唯一出口,需已认证会话)") + @Operation(summary = "获取令牌明文(唯一出口,需已认证会话)") @GetMapping(value = "/token/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getToken(@PathVariable("id") Long id) { return ResultEntity.success(mcpManageService.getClientToken(id)); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/McpToolController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/McpToolController.java index 28b97c8..7420958 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/McpToolController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/McpToolController.java @@ -6,14 +6,15 @@ import com.cs.common.dto.*; import com.cs.core.dto.*; import com.cs.manager.service.McpManageService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; -@Api(tags = {"MCP工具管理接口"}) +@Tag(name = "MCP工具管理接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/mcp/tool") public class McpToolController { @@ -21,28 +22,28 @@ public class McpToolController { @Resource private McpManageService mcpManageService; - @ApiOperation(value = "添加MCP工具") + @Operation(summary = "添加MCP工具") @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity create(@Valid @RequestBody McpToolSaveRequest request) { mcpManageService.createTool(request); return ResultEntity.success(); } - @ApiOperation(value = "更新MCP工具") + @Operation(summary = "更新MCP工具") @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity update(@Valid @RequestBody McpToolSaveRequest request) { mcpManageService.updateTool(request); return ResultEntity.success(); } - @ApiOperation(value = "删除MCP工具") + @Operation(summary = "删除MCP工具") @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity delete(@PathVariable("id") Long id) { mcpManageService.deleteTool(id); return ResultEntity.success(); } - @ApiOperation(value = "MCP工具列表") + @Operation(summary = "MCP工具列表") @PostMapping(value = "/listAll", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult listAll(@RequestBody EntitySearchRequest request) { return mcpManageService.listToolAll(request); diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/NodeController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/NodeController.java index 456808a..852e3cb 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/NodeController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/NodeController.java @@ -6,13 +6,13 @@ import com.cs.common.dto.ResultEntity; import com.cs.core.dto.TopologyNodeResponse; import com.cs.manager.service.NodeService; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; -@Api(tags = {"节点信息接口"}) +@Tag(name = "节点信息接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/node") public class NodeController { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/OverviewController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/OverviewController.java index d3000b2..f8e661d 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/OverviewController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/OverviewController.java @@ -6,14 +6,15 @@ import com.cs.common.dto.*; import com.cs.core.dto.ApiAccessLogBasicResponse; import com.cs.core.service.OverviewService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; -@Api(tags = {"统计相关接口"}) +@Tag(name = "统计相关接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/overview") public class OverviewController { @@ -21,43 +22,43 @@ public class OverviewController { @Resource private OverviewService overviewService; - @ApiOperation(value = "计数统计") + @Operation(summary = "计数统计") @GetMapping(value = "/counter", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity count() { return ResultEntity.success(overviewService.count()); } - @ApiOperation(value = "趋势统计") + @Operation(summary = "趋势统计") @GetMapping(value = "/trend/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> trend(@PathVariable("days") Integer days) { return ResultEntity.success(overviewService.trend(days)); } - @ApiOperation(value = "HTTP状态统计") + @Operation(summary = "HTTP状态统计") @GetMapping(value = "/ratio/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity httpStatus(@PathVariable("days") Integer days) { return ResultEntity.success(overviewService.httpStatus(days)); } - @ApiOperation(value = "路径TOP") + @Operation(summary = "路径TOP") @GetMapping(value = "/top/path/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> topPath(@PathVariable("days") Integer days, @RequestParam("n") Integer n) { return ResultEntity.success(overviewService.topPath(days, n)); } - @ApiOperation(value = "地址TOP") + @Operation(summary = "地址TOP") @GetMapping(value = "/top/addr/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> topAddr(@PathVariable("days") Integer days, @RequestParam("n") Integer n) { return ResultEntity.success(overviewService.topAddr(days, n)); } - @ApiOperation(value = "客户端TOP") + @Operation(summary = "客户端TOP") @GetMapping(value = "/top/client/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> topClient(@PathVariable("days") Integer days, @RequestParam("n") Integer n) { return ResultEntity.success(overviewService.topClient(days, n)); } - @ApiOperation(value = "接口调用日志") + @Operation(summary = "接口调用日志") @GetMapping(value = "/log/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public PageResult callLogs(@PathVariable("id") Long id, @RequestParam("page") Integer page, @RequestParam("size") Integer size, @@ -67,51 +68,51 @@ public PageResult callLogs(@PathVariable("id") Long i return overviewService.pageByApiId(id, page, size, statusCode, startTime, endTime); } - @ApiOperation(value = "数据源类别占比") + @Operation(summary = "数据源类别占比") @GetMapping(value = "/datasource-type-ratio", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> datasourceTypeRatio() { return ResultEntity.success(overviewService.datasourceTypeRatio()); } - @ApiOperation(value = "引擎类型占比") + @Operation(summary = "引擎类型占比") @GetMapping(value = "/engine-ratio", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> engineRatio() { return ResultEntity.success(overviewService.engineRatio()); } - @ApiOperation(value = "数据源接口数量") + @Operation(summary = "数据源接口数量") @GetMapping(value = "/datasource-api-count", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> datasourceApiCount() { return ResultEntity.success(overviewService.datasourceApiCount()); } - @ApiOperation(value = "接口方法占比") + @Operation(summary = "接口方法占比") @GetMapping(value = "/method-ratio", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> methodRatio() { return ResultEntity.success(overviewService.methodRatio()); } - @ApiOperation(value = "模块接口数量") + @Operation(summary = "模块接口数量") @GetMapping(value = "/module-api-count", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> moduleApiCount() { return ResultEntity.success(overviewService.moduleApiCount()); } - @ApiOperation(value = "API状态码占比") + @Operation(summary = "API状态码占比") @GetMapping(value = "/api/{apiId}/status-ratio/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> apiStatusRatio(@PathVariable("apiId") Long apiId, @PathVariable("days") Integer days) { return ResultEntity.success(overviewService.apiStatusRatio(apiId, days)); } - @ApiOperation(value = "API每日调用趋势") + @Operation(summary = "API每日调用趋势") @GetMapping(value = "/api/{apiId}/daily-trend/{days}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> apiDailyTrend(@PathVariable("apiId") Long apiId, @PathVariable("days") Integer days) { return ResultEntity.success(overviewService.apiDailyTrend(apiId, days)); } - @ApiOperation(value = "API 24小时调用趋势") + @Operation(summary = "API 24小时调用趋势") @GetMapping(value = "/api/{apiId}/hourly-trend", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> apiHourlyTrend(@PathVariable("apiId") Long apiId, @RequestParam("date") String date) { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/SystemParamController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/SystemParamController.java index 5c913e8..72ed6b3 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/SystemParamController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/SystemParamController.java @@ -5,13 +5,13 @@ import com.cs.common.consts.Constants; import com.cs.common.dto.ResultEntity; import com.cs.core.service.SystemParamService; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; -@Api(tags = {"参数管理接口"}) +@Tag(name = "参数管理接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/param") public class SystemParamController { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/SystemUserController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/SystemUserController.java index 4c11698..0f88ea3 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/SystemUserController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/SystemUserController.java @@ -6,14 +6,15 @@ import com.cs.common.dto.ResultEntity; import com.cs.core.dto.SystemUserDetailResponse; import com.cs.core.service.SystemUserService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; -@Api(tags = {"用户管理接口"}) +@Tag(name = "用户管理接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/user") public class SystemUserController { @@ -21,19 +22,19 @@ public class SystemUserController { @Resource private SystemUserService systemUserService; - @ApiOperation(value = "用户详情", notes = "根据用户ID获取用户详情") + @Operation(summary = "用户详情", description = "根据用户ID获取用户详情") @GetMapping(value = "/detail/id", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getUserById(@RequestParam("id") Long id) { return ResultEntity.success(systemUserService.getUserDetailById(id)); } - @ApiOperation(value = "用户详情", notes = "根据用户名获取用户详情") + @Operation(summary = "用户详情", description = "根据用户名获取用户详情") @GetMapping(value = "/detail/name", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity getUserByName(@RequestParam("username") String username) { return ResultEntity.success(systemUserService.getUserDetailByUsername(username)); } - @ApiOperation(value = "修改密码", notes = "用户修改自己的密码") + @Operation(summary = "修改密码", description = "用户修改自己的密码") @RequestMapping(value = "/changePassword", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity changeOwnPassword(HttpServletRequest request, @RequestParam("oldPassword") String oldPassword, diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/UnifyAlarmController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/UnifyAlarmController.java index ac824f0..47479a6 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/UnifyAlarmController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/UnifyAlarmController.java @@ -7,15 +7,15 @@ import com.cs.core.dto.*; import com.cs.core.executor.UnifyAlarmOpsService; import com.cs.persistence.entity.UnifyAlarmEntity; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import java.util.List; -@Api(tags = {"告警配置接口"}) +@Tag(name = "告警配置接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/alarm") public class UnifyAlarmController { diff --git a/datapoly-manager/src/main/java/com/cs/manager/controller/VersionCommitController.java b/datapoly-manager/src/main/java/com/cs/manager/controller/VersionCommitController.java index c63a5ba..9de19a2 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/controller/VersionCommitController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/controller/VersionCommitController.java @@ -6,14 +6,15 @@ import com.cs.common.dto.ResultEntity; import com.cs.core.dto.VersionCommitResponse; import com.cs.core.service.ApiAssignmentService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; -@Api(tags = {"版本控制接口"}) +@Tag(name = "版本控制接口") @RestController @RequestMapping(value = Constants.MANAGER_API_V1 + "/version") public class VersionCommitController { @@ -21,19 +22,19 @@ public class VersionCommitController { @Resource private ApiAssignmentService apiAssignmentService; - @ApiOperation(value = "查询版本列表") + @Operation(summary = "查询版本列表") @GetMapping(value = "/list/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity> listVersions(@PathVariable("id") Long bizId) { return ResultEntity.success(apiAssignmentService.listVersions(bizId)); } - @ApiOperation(value = "查询版本详情") + @Operation(summary = "查询版本详情") @GetMapping(value = "/show/{commitId}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity showVersion(@PathVariable("commitId") Long commitId) { return ResultEntity.success(apiAssignmentService.showVersion(commitId)); } - @ApiOperation(value = "回滚指定版本") + @Operation(summary = "回滚指定版本") @GetMapping(value = "/revert/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResultEntity revertVersion(@PathVariable("id") Long bizId, @RequestParam("commitId") Long commitId) { apiAssignmentService.revertVersion(bizId, commitId); diff --git a/datapoly-manager/src/main/java/com/cs/manager/exception/ExceptionController.java b/datapoly-manager/src/main/java/com/cs/manager/exception/ExceptionController.java index 1df539a..4d7b3b9 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/exception/ExceptionController.java +++ b/datapoly-manager/src/main/java/com/cs/manager/exception/ExceptionController.java @@ -9,9 +9,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.bind.annotation.*; -import javax.validation.ConstraintViolationException; +import jakarta.validation.ConstraintViolationException; import java.util.stream.Collectors; @Slf4j @@ -50,6 +51,18 @@ public ResponseEntity constraintViolationException(ConstraintViola ResultEntity.failed(ResponseErrorCode.ERROR_INVALID_ARGUMENT, errorMessage)); } + /** + * Boot 3 起 Spring MVC 对未映射路径恒抛 NoHandlerFoundException(原 + * spring.mvc.throw-exception-if-no-handler-found=false 已弃用失效),在此等价恢复 + * 旧 404 语义,避免未匹配路径落入 500 内部错误。 + */ + @ExceptionHandler(value = {NoHandlerFoundException.class}) + public ResponseEntity noHandlerFoundException(NoHandlerFoundException e) { + return respond(ResponseErrorCode.ERROR_PATH_NOT_EXISTS, + ResultEntity.failed(ResponseErrorCode.ERROR_PATH_NOT_EXISTS, + e.getHttpMethod() + " " + e.getRequestURL() + " not exists")); + } + @ExceptionHandler(value = Exception.class) public ResponseEntity errorHandler(Exception e) { if (e instanceof CommonException) { diff --git a/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminToolSupport.java b/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminToolSupport.java new file mode 100644 index 0000000..e6a8c4a --- /dev/null +++ b/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminToolSupport.java @@ -0,0 +1,176 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.manager.mcp; + +import cn.hutool.core.exceptions.ExceptionUtil; +import com.cs.common.exception.CommonException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; +import io.modelcontextprotocol.spec.McpSchema.CallToolResult; +import io.modelcontextprotocol.spec.McpSchema.TextContent; + +import java.util.List; +import java.util.Map; + +/** + * 管理 MCP 工具的公共支撑:JSON Schema 构建、参数提取与转换、结果包装。 + */ +public final class McpAdminToolSupport { + + // Agent 传参可能带多余字段,与 Spring Boot 默认一致地忽略未知属性 + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + private static final String FN_TYPE = "type"; + private static final String FN_ID = "id"; + private static final String FN_DESCRIPTION = "description"; + private static final String FN_PROPERTIES = "properties"; + private static final String FN_REQUIRED = "required"; + private static final String FN_ITEMS = "items"; + private static final String FV_ID = "urn:jsonschema:Operation"; + private static final String FV_OBJECT = "object"; + private static final String FV_ARRAY = "array"; + + private McpAdminToolSupport() { + } + + /** + * Fluent JSON Schema(object) 构建器,输出 MCP Tool 的 inputSchema 字符串。 + * 嵌套 schema 被引用时按当前内容快照导出,引用后继续修改原 schema 不影响已生成的字段。 + */ + public static final class Schema { + + private final ObjectNode root = OBJECT_MAPPER.createObjectNode(); + private final ObjectNode properties = OBJECT_MAPPER.createObjectNode(); + private final ArrayNode required = OBJECT_MAPPER.createArrayNode(); + + private Schema() { + root.put(FN_TYPE, FV_OBJECT); + root.put(FN_ID, FV_ID); + } + + public static Schema object() { + return new Schema(); + } + + public Schema property(String name, String type, String description, boolean required) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + node.put(FN_TYPE, type); + node.put(FN_DESCRIPTION, description); + return setProperty(name, node, required); + } + + public Schema property(String name, Schema nested, String description, boolean required) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + node.put(FN_TYPE, FV_OBJECT); + node.put(FN_DESCRIPTION, description); + node.set(FN_PROPERTIES, nested.properties.deepCopy()); + node.set(FN_REQUIRED, nested.required.deepCopy()); + return setProperty(name, node, required); + } + + public Schema arrayProperty(String name, String itemType, String description, boolean required) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + node.put(FN_TYPE, FV_ARRAY); + node.put(FN_DESCRIPTION, description); + ObjectNode items = OBJECT_MAPPER.createObjectNode(); + items.put(FN_TYPE, itemType); + node.set(FN_ITEMS, items); + return setProperty(name, node, required); + } + + public Schema arrayProperty(String name, Schema itemSchema, String description, boolean required) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + node.put(FN_TYPE, FV_ARRAY); + node.put(FN_DESCRIPTION, description); + ObjectNode items = OBJECT_MAPPER.createObjectNode(); + items.put(FN_TYPE, FV_OBJECT); + items.set(FN_PROPERTIES, itemSchema.properties.deepCopy()); + items.set(FN_REQUIRED, itemSchema.required.deepCopy()); + node.set(FN_ITEMS, items); + return setProperty(name, node, required); + } + + public Schema copy() { + Schema copied = new Schema(); + this.properties.fields().forEachRemaining( + entry -> copied.properties.set(entry.getKey(), entry.getValue().deepCopy())); + copied.required.addAll(this.required); + return copied; + } + + private Schema setProperty(String name, ObjectNode node, boolean required) { + this.properties.set(name, node); + if (required) { + this.required.add(name); + } + return this; + } + + public String toJson() { + root.set(FN_PROPERTIES, properties); + root.set(FN_REQUIRED, required); + return root.toString(); + } + } + + public static T convert(Map arguments, Class type) { + return OBJECT_MAPPER.convertValue(arguments, type); + } + + public static Long getLong(Map arguments, String key) { + Object value = arguments.get(key); + return null == value ? null : ((Number) value).longValue(); + } + + public static String getString(Map arguments, String key) { + Object value = arguments.get(key); + return null == value ? null : value.toString(); + } + + public static Boolean getBoolean(Map arguments, String key) { + Object value = arguments.get(key); + return null == value ? null : (Boolean) value; + } + + public static List getLongList(Map arguments, String key) { + Object value = arguments.get(key); + if (null == value) { + return null; + } + return OBJECT_MAPPER.convertValue(value, new TypeReference>() { + }); + } + + public static CallToolResult ok(Object data) { + String text = null == data + ? "操作成功" + : "操作成功,JSON格式的响应数据为:\n " + writeJson(data); + return new CallToolResult(Lists.newArrayList(new TextContent(text)), false); + } + + public static CallToolResult fail(Throwable t) { + String message; + if (t instanceof CommonException) { + CommonException exception = (CommonException) t; + message = null != exception.getCode() + ? exception.getCode().getCode() + ":" + t.getMessage() + : t.getMessage(); + } else { + message = null != t.getMessage() ? t.getMessage() : ExceptionUtil.getRootCauseMessage(t); + } + return new CallToolResult(Lists.newArrayList(new TextContent("操作异常: " + message)), true); + } + + private static String writeJson(Object data) { + try { + return OBJECT_MAPPER.writeValueAsString(data); + } catch (Exception e) { + return String.valueOf(data); + } + } +} diff --git a/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminTools.java b/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminTools.java new file mode 100644 index 0000000..43f3b8c --- /dev/null +++ b/datapoly-manager/src/main/java/com/cs/manager/mcp/McpAdminTools.java @@ -0,0 +1,479 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.manager.mcp; + +import com.cs.core.dto.*; +import com.cs.core.service.*; +import com.cs.manager.service.McpManageService; +import com.google.common.collect.Lists; +import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.cs.manager.mcp.McpAdminToolSupport.Schema; + +/** + * 管理 MCP 工具集:将 manager 的实体管理 REST 能力(数据源/模块/分组/API定义/应用客户端/MCP配置) + * 包装为 MCP tools,命名与 REST 接口语义保持一致(dp_{entity}_{action}),供外部 AI Agent 增删改查。 + * 注册于独立的 admin MCP Server,仅 manage 权限令牌可调用。 + */ +@Slf4j +@Component +public class McpAdminTools { + + private static final String T_INTEGER = "integer"; + private static final String T_STRING = "string"; + private static final String T_BOOLEAN = "boolean"; + + private static final String D_PAGE = "页码,从1开始"; + private static final String D_SIZE = "每页数量"; + private static final String D_SEARCH = "名称模糊搜索关键字"; + + // package-private: datapoly-test 同包注入 fake 服务直接构建工具 + @Resource + McpSyncServer mcpAdminSyncServer; + @Resource + DataSourceService dataSourceService; + @Resource + ApiModuleService apiModuleService; + @Resource + ApiGroupService apiGroupService; + @Resource + ApiAssignmentService apiAssignmentService; + @Resource + AppClientService appClientService; + @Resource + McpManageService mcpManageService; + + @EventListener(ApplicationReadyEvent.class) + public void registerAdminTools() { + List tools = buildToolSpecifications(); + tools.forEach(mcpAdminSyncServer::addTool); + log.info("Finish load total count [{}] admin mcp tools to memory.", tools.size()); + } + + List buildToolSpecifications() { + return Stream.of(buildDataSourceTools(), buildModuleTools(), buildGroupTools(), + buildApiTools(), buildClientTools(), buildMcpConfigTools()) + .flatMap(List::stream) + .collect(Collectors.toList()); + } + + private SyncToolSpecification tool(String name, String description, Schema schema, + Function, Object> action) { + return new SyncToolSpecification( + new McpSchema.Tool(name, description, schema.toJson()), + (exchange, arguments) -> { + try { + Object data = action.apply(arguments); + return McpAdminToolSupport.ok(data); + } catch (Throwable t) { + return McpAdminToolSupport.fail(t); + } + }); + } + + private Schema pageSchema() { + return Schema.object() + .property("page", T_INTEGER, D_PAGE, false) + .property("size", T_INTEGER, D_SIZE, false) + .property("searchText", T_STRING, D_SEARCH, false); + } + + private T search(Map arguments, Class type) { + return McpAdminToolSupport.convert(arguments, type); + } + + // ------------------------------------------------------------------ + // 数据源 + // ------------------------------------------------------------------ + + private List buildDataSourceTools() { + String typeValues = "MYSQL, MARIADB, ORACLE, SQLSERVER, POSTGRESQL, DB2, DM, KINGBASE, OSCAR, GBASE8A, " + + "SYBASE, HIVE, IMPALA, INCEPTOR, SQLITE3, OPENGAUSS, CLICKHOUSE, DORIS, STARROCKS, " + + "OCEANBASE, TDENGINE, MONGODB, ELASTICSEARCH, ODPS, HTTP"; + Schema poolConfig = Schema.object() + .property("maximumPoolSize", T_INTEGER, "最大连接数,默认10", false) + .property("minimumIdle", T_INTEGER, "最小空闲连接数,默认10", false) + .property("maxLifetime", T_INTEGER, "连接最大存活毫秒数,默认3600000", false) + .property("connectionTimeout", T_INTEGER, "获取连接超时毫秒数,默认60000", false) + .property("idleTimeout", T_INTEGER, "空闲超时毫秒数,默认60000", false); + Schema saveSchema = Schema.object() + .property("name", T_STRING, "数据源名称(唯一)", true) + .property("type", T_STRING, "数据库类型,枚举: " + typeValues, true) + .property("version", T_STRING, "驱动版本(先调用dp_datasource_types查询可选值)", true) + .property("driver", T_STRING, "驱动类名(先调用dp_datasource_types查询可选值)", true) + .property("url", T_STRING, "JDBC连接URL", true) + .property("username", T_STRING, "用户名", true) + .property("password", T_STRING, "密码(更新时省略表示保留原密码)", false) + .property("poolConfig", poolConfig, "连接池配置", false); + Schema updateSchema = saveSchema.property("id", T_INTEGER, "数据源ID", true); + + return Lists.newArrayList( + tool("dp_datasource_types", + "查询支持的数据库类型及可用驱动(type/version/driver 可选值),创建数据源前先调用", + Schema.object(), + args -> dataSourceService.getTypes()), + tool("dp_datasource_list", "分页查询数据源列表(密码已脱敏)", + pageSchema(), + args -> dataSourceService.searchList(search(args, EntitySearchRequest.class))), + tool("dp_datasource_get", "按ID查询数据源详情(密码已脱敏)", + Schema.object().property("id", T_INTEGER, "数据源ID", true), + args -> dataSourceService.getDetailById(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_datasource_create", "创建数据源", + saveSchema, + args -> { + dataSourceService.createDataSource(search(args, DataSourceSaveRequest.class)); + return null; + }), + tool("dp_datasource_update", "更新数据源(省略password表示保留原密码)", + updateSchema, + args -> { + dataSourceService.updateDataSource(search(args, DataSourceSaveRequest.class)); + return null; + }), + tool("dp_datasource_delete", "删除数据源(被API引用时拒绝删除)", + Schema.object().property("id", T_INTEGER, "数据源ID", true), + args -> { + dataSourceService.deleteDataSource(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_datasource_test", "测试已保存数据源的连通性", + Schema.object().property("id", T_INTEGER, "数据源ID", true), + args -> { + dataSourceService.testDataSource(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_datasource_schemas", "查询数据源下的schema清单", + Schema.object().property("id", T_INTEGER, "数据源ID", true), + args -> dataSourceService.getDatasourceSchemas(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_datasource_tables", "查询数据源指定schema下的表清单", + Schema.object().property("id", T_INTEGER, "数据源ID", true) + .property("schema", T_STRING, "schema名称", true), + args -> dataSourceService.getSchemaTables( + McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getString(args, "schema"))), + tool("dp_datasource_columns", "查询数据源指定表的字段元信息(名称/类型),编写SQL前调用", + Schema.object().property("id", T_INTEGER, "数据源ID", true) + .property("schema", T_STRING, "schema名称", true) + .property("table", T_STRING, "表名", true), + args -> dataSourceService.getTableColumns( + McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getString(args, "schema"), + McpAdminToolSupport.getString(args, "table")))); + } + + // ------------------------------------------------------------------ + // 模块与分组 + // ------------------------------------------------------------------ + + private List buildModuleTools() { + return Lists.newArrayList( + tool("dp_module_list", "分页查询API模块(组织API的目录)列表", + pageSchema(), + args -> apiModuleService.listAll(search(args, EntitySearchRequest.class))), + tool("dp_module_create", "创建API模块", + Schema.object().property("name", T_STRING, "模块名称(唯一)", true), + args -> { + apiModuleService.createModule(McpAdminToolSupport.getString(args, "name")); + return null; + }), + tool("dp_module_update", "重命名API模块", + Schema.object().property("id", T_INTEGER, "模块ID", true) + .property("name", T_STRING, "新模块名称", true), + args -> { + apiModuleService.updateModule(McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getString(args, "name")); + return null; + }), + tool("dp_module_delete", "删除API模块(被API引用时拒绝删除)", + Schema.object().property("id", T_INTEGER, "模块ID", true), + args -> { + apiModuleService.deleteModule(McpAdminToolSupport.getLong(args, "id")); + return null; + })); + } + + private List buildGroupTools() { + return Lists.newArrayList( + tool("dp_group_list", "分页查询API授权分组列表(API按分组授权给应用客户端)", + pageSchema(), + args -> apiGroupService.listAll(search(args, EntitySearchRequest.class))), + tool("dp_group_create", "创建API授权分组", + Schema.object().property("name", T_STRING, "分组名称(唯一)", true), + args -> { + apiGroupService.createGroup(McpAdminToolSupport.getString(args, "name")); + return null; + }), + tool("dp_group_update", "重命名API授权分组", + Schema.object().property("id", T_INTEGER, "分组ID", true) + .property("name", T_STRING, "新分组名称", true), + args -> { + apiGroupService.updateGroup(McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getString(args, "name")); + return null; + }), + tool("dp_group_delete", "删除API授权分组(被API引用时拒绝删除)", + Schema.object().property("id", T_INTEGER, "分组ID", true), + args -> { + apiGroupService.deleteGroup(McpAdminToolSupport.getLong(args, "id")); + return null; + })); + } + + // ------------------------------------------------------------------ + // API 定义(Assignment)与上下线 + // ------------------------------------------------------------------ + + private List buildApiTools() { + // ItemParam 子参数为 BaseParam(无更深层嵌套),故 children 用独立的一层 schema + Schema baseParam = Schema.object() + .property("name", T_STRING, "参数名(SQL中#{name}或${name}占位)", true) + .property("type", T_STRING, "参数类型,枚举: LONG, DOUBLE, STRING, DATE, TIME, BOOLEAN, OBJECT", true) + .property("location", T_STRING, + "参数位置,枚举: REQUEST_HEADER(header头), REQUEST_FORM(query参数), REQUEST_BODY(body体)", true) + .property("isArray", T_BOOLEAN, "是否数组", false) + .property("required", T_BOOLEAN, "是否必填", false) + .property("defaultValue", T_STRING, "默认值", false) + .property("remark", T_STRING, "参数说明", false); + Schema itemParam = baseParam.copy() + .arrayProperty("children", baseParam, "OBJECT类型参数的子参数", false); + Schema outParam = Schema.object() + .property("name", T_STRING, "输出字段名", true) + .property("type", T_STRING, "输出类型,枚举: LONG, DOUBLE, STRING, DATE, TIME, BOOLEAN, OBJECT", true) + .property("isArray", T_BOOLEAN, "是否数组", false) + .property("remark", T_STRING, "字段说明", false); + Schema formatMap = Schema.object() + .property("key", T_STRING, "类型,枚举: DATE, TIME, LOCAL_DATE, LOCAL_DATE_TIME, TIMESTAMP, " + + "BIG_DECIMAL, USE_SYSTEM_RESPONSE_FORMAT", true) + .property("value", T_STRING, "格式串,如 yyyy-MM-dd HH:mm:ss", true) + .property("remark", T_STRING, "说明", false); + Schema apiSaveSchema = Schema.object() + .property("groupId", T_INTEGER, "授权分组ID(先用dp_group_list查询)", true) + .property("moduleId", T_INTEGER, "所属模块ID(先用dp_module_list查询)", true) + .property("datasourceId", T_INTEGER, "数据源ID(先用dp_datasource_list查询)", true) + .property("name", T_STRING, "API名称", true) + .property("method", T_STRING, "HTTP方法,枚举: GET, HEAD, PUT, POST, DELETE(创建后不可修改)", true) + .property("path", T_STRING, "接口路径,不以api/开头,如 /user/list(创建后不可修改)", true) + .property("contentType", T_STRING, + "请求格式: application/x-www-form-urlencoded 或 application/json", true) + .property("engine", T_STRING, "执行引擎,枚举: SQL, SCRIPT(Groovy脚本)", true) + .arrayProperty("contextList", T_STRING, + "SQL/脚本内容数组,SQL中用#{name}预编译取参或${name}直接拼接(仅内部数据,open=false)", true) + .property("description", T_STRING, "API描述", false) + .property("open", T_BOOLEAN, "是否公开(公开可匿名访问)", true) + .property("alarm", T_BOOLEAN, "是否启用统一告警", true) + .arrayProperty("params", itemParam, "入参定义列表(可用dp_api_parse_sql解析SQL得到参数名)", false) + .arrayProperty("outputs", outParam, "输出字段定义列表(不填则按查询结果自动映射)", false) + .property("namingStrategy", T_STRING, + "响应字段命名策略,枚举: NONE, CAMEL_CASE, SNAKE_CASE, LOWER_CASE, UPPER_CASE,默认CAMEL_CASE", false) + .arrayProperty("formatMap", formatMap, "响应类型格式映射列表", false) + .property("flowStatus", T_BOOLEAN, "是否启用流控", true) + .property("flowGrade", T_INTEGER, "流控级别(参考Sentinel:0-QPS/1-并发线程数)", false) + .property("flowCount", T_INTEGER, "流控阈值", false) + .property("cacheKeyType", T_STRING, "缓存键类型,枚举: NONE, AUTO, SPEL", false) + .property("cacheKeyExpr", T_STRING, "SPEL缓存键表达式(cacheKeyType=SPEL时必填)", false) + .property("cacheExpireSeconds", T_INTEGER, "缓存过期秒数(启用缓存时必填>0)", false); + Schema apiUpdateSchema = apiSaveSchema.property("id", T_INTEGER, "API ID", true); + + return Lists.newArrayList( + tool("dp_api_list", "分页查询API定义列表(支持按上线状态/分组/模块过滤)", + pageSchema() + .property("online", T_BOOLEAN, "过滤上线状态", false) + .property("groupId", T_INTEGER, "过滤授权分组ID", false) + .property("moduleId", T_INTEGER, "过滤模块ID", false) + .property("open", T_BOOLEAN, "过滤公开状态", false), + args -> apiAssignmentService.listAll(search(args, AssignmentSearchRequest.class))), + tool("dp_api_get", "按ID查询API定义详情(含SQL与参数定义)", + Schema.object().property("id", T_INTEGER, "API ID", true), + args -> apiAssignmentService.detailAssignment(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_api_create", "创建API定义,返回新API的ID;上线需依次调用 dp_api_publish 与 dp_api_deploy", + apiSaveSchema, + args -> apiAssignmentService.createAssignment(search(args, ApiAssignmentSaveRequest.class))), + tool("dp_api_update", "更新API定义(method与path不可修改);更新后需重新 publish+deploy 才生效到网关", + apiUpdateSchema, + args -> { + apiAssignmentService.updateAssignment(search(args, ApiAssignmentSaveRequest.class)); + return null; + }), + tool("dp_api_delete", "删除API定义(已上线时拒绝删除,需先 dp_api_retire 下线)", + Schema.object().property("id", T_INTEGER, "API ID", true), + args -> { + apiAssignmentService.deleteAssignment(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_api_parse_sql", "解析SQL中的#{}与${}占位符,返回参数名清单,编写params前调用", + Schema.object().property("sql", T_STRING, "SQL内容", true), + args -> apiAssignmentService.parseSqlParams(McpAdminToolSupport.getString(args, "sql"))), + tool("dp_api_publish", "发布API:将当前定义保存为版本快照,随后用 dp_api_deploy 上线", + Schema.object().property("id", T_INTEGER, "API ID", true) + .property("description", T_STRING, "版本说明", false), + args -> { + apiAssignmentService.publish(search(args, AssignmentPublishRequest.class)); + return null; + }), + tool("dp_api_deploy", "上线API(将版本快照部署到网关,commitId省略时取最新版本)", + Schema.object().property("id", T_INTEGER, "API ID", true) + .property("commitId", T_INTEGER, "版本快照ID(省略取最新)", false), + args -> { + apiAssignmentService.deployAssignment(McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getLong(args, "commitId")); + return null; + }), + tool("dp_api_retire", "下线API(从网关移除,定义保留)", + Schema.object().property("id", T_INTEGER, "API ID", true), + args -> { + apiAssignmentService.retireAssignment(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_api_set_group", "批量修改API的授权分组", + Schema.object().property("groupId", T_INTEGER, "目标分组ID", true) + .arrayProperty("ids", T_INTEGER, "API ID列表", true), + args -> { + apiAssignmentService.updateGroup(McpAdminToolSupport.getLong(args, "groupId"), + McpAdminToolSupport.getLongList(args, "ids")); + return null; + }), + tool("dp_api_versions", "查询API的版本快照列表", + Schema.object().property("id", T_INTEGER, "API ID", true), + args -> apiAssignmentService.listVersions(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_api_version_detail", "查询API指定版本快照的完整定义", + Schema.object().property("commitId", T_INTEGER, "版本快照ID", true), + args -> apiAssignmentService.showVersion(McpAdminToolSupport.getLong(args, "commitId"))), + tool("dp_api_revert", "将API定义回退到指定版本快照(回退后需重新 publish+deploy)", + Schema.object().property("id", T_INTEGER, "API ID", true) + .property("commitId", T_INTEGER, "版本快照ID", true), + args -> { + apiAssignmentService.revertVersion(McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getLong(args, "commitId")); + return null; + })); + } + + // ------------------------------------------------------------------ + // 应用客户端(网关API调用方) + // ------------------------------------------------------------------ + + private List buildClientTools() { + return Lists.newArrayList( + tool("dp_client_list", "分页查询应用客户端列表(网关API调用方)", + pageSchema().property("groupId", T_INTEGER, "过滤已授权分组ID", false), + args -> appClientService.searchList(search(args, AppClientSearchRequest.class))), + tool("dp_client_create", "创建应用客户端,appKey需全局唯一,appSecret由服务端生成(用dp_client_secret查询)", + Schema.object() + .property("name", T_STRING, "客户端名称", true) + .property("appKey", T_STRING, "客户端标识(唯一)", true) + .property("description", T_STRING, "描述", false) + .property("expireTime", T_STRING, "凭证有效期,枚举: EXPIRE_FOR_EVER(永久), " + + "EXPIRE_ONLY_ONCE(一次性), EXPIRE_05_MIN, EXPIRE_30_MIN, EXPIRE_01_HOUR, " + + "EXPIRE_12_HOUR, EXPIRE_01_DAY, EXPIRE_15_DAY, EXPIRE_01_MOUTH", true) + .property("tokenAlive", T_STRING, "token存活策略,枚举: PERIOD(短期,2小时), " + + "LONGEVITY(长期)", true), + args -> { + appClientService.create(search(args, AppClientSaveRequest.class)); + return null; + }), + tool("dp_client_delete", "删除应用客户端", + Schema.object().property("id", T_INTEGER, "客户端ID", true), + args -> { + appClientService.delete(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_client_secret", "查询应用客户端的appSecret(用于换取网关访问token)", + Schema.object().property("id", T_INTEGER, "客户端ID", true), + args -> appClientService.getSecret(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_client_get_groups", "查询应用客户端已授权的API分组", + Schema.object().property("id", T_INTEGER, "客户端ID", true), + args -> appClientService.getGroupAuth(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_client_set_groups", "设置应用客户端的授权分组(全量覆盖)", + Schema.object().property("id", T_INTEGER, "客户端ID", true) + .arrayProperty("groupIds", T_INTEGER, "分组ID列表", true), + args -> { + appClientService.createGroupAuth(search(args, AppClientGroupRequest.class)); + return null; + })); + } + + // ------------------------------------------------------------------ + // MCP 配置(数据工具服务注册表 + MCP 令牌) + // ------------------------------------------------------------------ + + private List buildMcpConfigTools() { + return Lists.newArrayList( + tool("dp_mcp_tool_list", "分页查询数据MCP服务已注册的工具(把在线API暴露给大模型)", + pageSchema(), + args -> mcpManageService.listToolAll(search(args, EntitySearchRequest.class))), + tool("dp_mcp_tool_create", "将已上线的API注册为数据MCP服务的工具(供大模型调用)", + Schema.object() + .property("name", T_STRING, "工具名(唯一)", true) + .property("description", T_STRING, "工具描述(写给大模型看)", true) + .property("apiId", T_INTEGER, "已上线的API ID", true), + args -> { + mcpManageService.createTool(search(args, McpToolSaveRequest.class)); + return null; + }), + tool("dp_mcp_tool_update", "更新数据MCP服务的工具定义", + Schema.object() + .property("id", T_INTEGER, "工具ID", true) + .property("name", T_STRING, "工具名(唯一)", true) + .property("description", T_STRING, "工具描述", true) + .property("apiId", T_INTEGER, "已上线的API ID", true), + args -> { + mcpManageService.updateTool(search(args, McpToolSaveRequest.class)); + return null; + }), + tool("dp_mcp_tool_delete", "删除数据MCP服务的工具", + Schema.object().property("id", T_INTEGER, "工具ID", true), + args -> { + mcpManageService.deleteTool(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_mcp_client_list", "分页查询MCP连接令牌列表(manage=true的令牌可访问本管理服务)", + pageSchema(), + args -> mcpManageService.listClientAll(search(args, EntitySearchRequest.class))), + tool("dp_mcp_client_create", "创建MCP连接令牌,manage=true时该令牌可访问本管理服务(等同管理员,谨慎授予)", + Schema.object() + .property("name", T_STRING, "令牌名称(唯一)", true) + .property("manage", T_BOOLEAN, "是否管理权限,默认false", false), + args -> { + Boolean manage = McpAdminToolSupport.getBoolean(args, "manage"); + mcpManageService.createClient(McpAdminToolSupport.getString(args, "name"), + Boolean.TRUE.equals(manage)); + return null; + }), + tool("dp_mcp_client_update", "更新MCP连接令牌(名称或manage权限)", + Schema.object() + .property("id", T_INTEGER, "令牌ID", true) + .property("name", T_STRING, "新令牌名称", true) + .property("manage", T_BOOLEAN, "是否管理权限(省略表示不变)", false), + args -> { + mcpManageService.updateClient(McpAdminToolSupport.getLong(args, "id"), + McpAdminToolSupport.getString(args, "name"), + McpAdminToolSupport.getBoolean(args, "manage")); + return null; + }), + tool("dp_mcp_client_delete", "删除MCP连接令牌", + Schema.object().property("id", T_INTEGER, "令牌ID", true), + args -> { + mcpManageService.deleteClient(McpAdminToolSupport.getLong(args, "id")); + return null; + }), + tool("dp_mcp_client_token", "查询MCP连接令牌的明文(用于配置Agent客户端连接)", + Schema.object().property("id", T_INTEGER, "令牌ID", true), + args -> mcpManageService.getClientToken(McpAdminToolSupport.getLong(args, "id"))), + tool("dp_mcp_endpoint", "查询数据MCP服务与管理MCP服务的连接地址前缀", + Schema.object(), + args -> mcpManageService.getMcpServerEndpoint())); + } +} diff --git a/datapoly-manager/src/main/java/com/cs/manager/service/AccessLogCleanService.java b/datapoly-manager/src/main/java/com/cs/manager/service/AccessLogCleanService.java index 48aff3e..93239e2 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/service/AccessLogCleanService.java +++ b/datapoly-manager/src/main/java/com/cs/manager/service/AccessLogCleanService.java @@ -10,7 +10,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Slf4j @Service diff --git a/datapoly-manager/src/main/java/com/cs/manager/service/McpManageService.java b/datapoly-manager/src/main/java/com/cs/manager/service/McpManageService.java index 483563d..5750926 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/service/McpManageService.java +++ b/datapoly-manager/src/main/java/com/cs/manager/service/McpManageService.java @@ -28,7 +28,7 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -92,15 +92,24 @@ public McpServerAddrResponse getMcpServerEndpoint() { Constants.DEFAULT_SSE_ENDPOINT, tokenParamName)) .streamAddrPrefix(String.format("%s%s?%s=", managerAddress, Constants.DEFAULT_STREAM_ENDPOINT, tokenParamName)) + .adminSseAddrPrefix(String.format("%s%s?%s=", managerAddress, + Constants.ADMIN_SSE_ENDPOINT, tokenParamName)) + .adminStreamAddrPrefix(String.format("%s%s?%s=", managerAddress, + Constants.ADMIN_STREAM_ENDPOINT, tokenParamName)) .build(); } public void createClient(String name) { + createClient(name, false); + } + + public void createClient(String name, boolean manage) { try { mcpClientDao.insert( McpClientEntity.builder() .name(name) .token(TokenUtils.generateValue()) + .manage(manage) .build() ); } catch (DuplicateKeyException e) { @@ -109,11 +118,18 @@ public void createClient(String name) { } public void updateClient(Long id, String newName) { + updateClient(id, newName, null); + } + + public void updateClient(Long id, String newName, Boolean manage) { McpClientEntity clientEntity = mcpClientDao.getById(id); if (null == clientEntity) { throw new CommonException(ResponseErrorCode.ERROR_RESOURCE_NOT_EXISTS, "common.id.not.found", id); } clientEntity.setName(newName); + if (null != manage) { + clientEntity.setManage(manage); + } try { mcpClientDao.updateById(clientEntity); } catch (DuplicateKeyException e) { diff --git a/datapoly-manager/src/main/java/com/cs/manager/service/NodeService.java b/datapoly-manager/src/main/java/com/cs/manager/service/NodeService.java index 8a90521..b0b48ec 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/service/NodeService.java +++ b/datapoly-manager/src/main/java/com/cs/manager/service/NodeService.java @@ -18,7 +18,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.client.RestTemplate; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.lang.management.*; import java.util.*; diff --git a/datapoly-manager/src/main/java/com/cs/manager/upgrade/VersionUpgradeRunner.java b/datapoly-manager/src/main/java/com/cs/manager/upgrade/VersionUpgradeRunner.java index 95cf6c2..25b19a7 100644 --- a/datapoly-manager/src/main/java/com/cs/manager/upgrade/VersionUpgradeRunner.java +++ b/datapoly-manager/src/main/java/com/cs/manager/upgrade/VersionUpgradeRunner.java @@ -12,7 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StopWatch; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; /** diff --git a/datapoly-manager/src/main/resources/application-mysql.yaml b/datapoly-manager/src/main/resources/application-mysql.yaml index 843bbd3..b66a5d6 100644 --- a/datapoly-manager/src/main/resources/application-mysql.yaml +++ b/datapoly-manager/src/main/resources/application-mysql.yaml @@ -4,8 +4,8 @@ spring: url: jdbc:mysql://${MYSQLDB_HOST}:${MYSQLDB_PORT}/${MYSQLDB_NAME}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&allowMultiQueries=true&failOverReadOnly=false&connectTimeout=30000&serverTimezone=Asia/Shanghai username: ${MYSQLDB_USERNAME} password: ${MYSQLDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true + hikari: + connection-test-query: SELECT 1 liquibase: enabled: true change-log: classpath:db/changelog/db.changelog-master.yaml diff --git a/datapoly-manager/src/main/resources/application-postgres.yaml b/datapoly-manager/src/main/resources/application-postgres.yaml index 10a64e1..9fa710a 100644 --- a/datapoly-manager/src/main/resources/application-postgres.yaml +++ b/datapoly-manager/src/main/resources/application-postgres.yaml @@ -4,8 +4,8 @@ spring: url: jdbc:postgresql://${PGDB_HOST}:${PGDB_PORT}/${PGDB_NAME}?currentSchema=public username: ${PGDB_USERNAME} password: ${PGDB_PASSWORD} - validation-query: SELECT 1 - test-on-borrow: true + hikari: + connection-test-query: SELECT 1 liquibase: enabled: true change-log: classpath:pg/changelog/db.changelog-master.yaml diff --git a/datapoly-manager/src/main/resources/application.yaml b/datapoly-manager/src/main/resources/application.yaml index 89d967a..9b38bab 100644 --- a/datapoly-manager/src/main/resources/application.yaml +++ b/datapoly-manager/src/main/resources/application.yaml @@ -9,20 +9,13 @@ spring: # Boot 2.6+ 默认拒绝循环依赖;先保持 2.5 行为,待全量启动回归后再收紧 allow-circular-references: true mvc: - throw-exception-if-no-handler-found: false static-path-pattern: /** - pathmatch: - # springfox 3.0 不支持 PathPatternParser(Boot 2.6+ 默认),必须回退 AntPathMatcher - matching-strategy: ant_path_matcher messages: basename: i18n/messages encoding: UTF-8 datapoly: manager: - # 管理端 swagger 默认关闭(收口):开发联调需要时经环境变量/本地配置打开 - swagger: - enable: ${DATAPOLY_MANAGER_SWAGGER_ENABLE:false} # 错误响应是否映射真实 HTTP 状态码(H5):关闭后一切错误回 HTTP 200(兼容旧内置前端) http-status-mapping: enabled: ${DATAPOLY_MANAGER_HTTP_STATUS_MAPPING_ENABLED:true} @@ -73,6 +66,13 @@ mybatis-plus: global-config: banner: false +# API 文档(springdoc)默认关闭(收口):开发联调需要时经环境变量/本地配置打开 +springdoc: + api-docs: + enabled: ${DATAPOLY_MANAGER_SWAGGER_ENABLE:false} + swagger-ui: + enabled: ${DATAPOLY_MANAGER_SWAGGER_ENABLE:false} + logging: config: classpath:logback-manager.xml diff --git a/datapoly-manager/src/main/resources/db/changelog/db.changelog-master.yaml b/datapoly-manager/src/main/resources/db/changelog/db.changelog-master.yaml index 22ac79a..691c684 100644 --- a/datapoly-manager/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/datapoly-manager/src/main/resources/db/changelog/db.changelog-master.yaml @@ -5,3 +5,5 @@ databaseChangeLog: file: classpath:db/changelog/log-v1.1.0.yaml - include: file: classpath:db/changelog/log-v1.2.0.yaml + - include: + file: classpath:db/changelog/log-v1.3.0.yaml diff --git a/datapoly-manager/src/main/resources/db/changelog/log-v1.3.0.yaml b/datapoly-manager/src/main/resources/db/changelog/log-v1.3.0.yaml new file mode 100644 index 0000000..7e7bf51 --- /dev/null +++ b/datapoly-manager/src/main/resources/db/changelog/log-v1.3.0.yaml @@ -0,0 +1,9 @@ +databaseChangeLog: + - changeSet: + id: 1.3.0-mcp-client-manage-flag + author: datapoly + runOnChange: false + changes: + - sqlFile: + encoding: UTF-8 + path: db/migration/V1_3_0__mcp-client-manage-flag.sql diff --git a/datapoly-manager/src/main/resources/db/migration/V1_3_0__mcp-client-manage-flag.sql b/datapoly-manager/src/main/resources/db/migration/V1_3_0__mcp-client-manage-flag.sql new file mode 100644 index 0000000..72af98e --- /dev/null +++ b/datapoly-manager/src/main/resources/db/migration/V1_3_0__mcp-client-manage-flag.sql @@ -0,0 +1,3 @@ +-- 管理MCP服务权限标记:为1时该令牌可访问 /mcp/admin 管理端点(Agent 实体增删改查) +ALTER TABLE `DATAPOLY_MCP_CLIENT` + ADD COLUMN `manage_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '管理MCP服务权限' AFTER `token`; diff --git a/datapoly-manager/src/main/resources/pg/changelog/db.changelog-master.yaml b/datapoly-manager/src/main/resources/pg/changelog/db.changelog-master.yaml index fe782e2..0cd6696 100644 --- a/datapoly-manager/src/main/resources/pg/changelog/db.changelog-master.yaml +++ b/datapoly-manager/src/main/resources/pg/changelog/db.changelog-master.yaml @@ -5,3 +5,5 @@ databaseChangeLog: file: classpath:pg/changelog/log-v1.1.0.yaml - include: file: classpath:pg/changelog/log-v1.2.0.yaml + - include: + file: classpath:pg/changelog/log-v1.3.0.yaml diff --git a/datapoly-manager/src/main/resources/pg/changelog/log-v1.3.0.yaml b/datapoly-manager/src/main/resources/pg/changelog/log-v1.3.0.yaml new file mode 100644 index 0000000..c322173 --- /dev/null +++ b/datapoly-manager/src/main/resources/pg/changelog/log-v1.3.0.yaml @@ -0,0 +1,9 @@ +databaseChangeLog: + - changeSet: + id: 1.3.0-mcp-client-manage-flag + author: datapoly + runOnChange: false + changes: + - sqlFile: + encoding: UTF-8 + path: pg/migration/V1_3_0__mcp-client-manage-flag.sql diff --git a/datapoly-manager/src/main/resources/pg/migration/V1_3_0__mcp-client-manage-flag.sql b/datapoly-manager/src/main/resources/pg/migration/V1_3_0__mcp-client-manage-flag.sql new file mode 100644 index 0000000..835383c --- /dev/null +++ b/datapoly-manager/src/main/resources/pg/migration/V1_3_0__mcp-client-manage-flag.sql @@ -0,0 +1,3 @@ +-- 管理MCP服务权限标记:为 true 时该令牌可访问 /mcp/admin 管理端点(Agent 实体增删改查) +ALTER TABLE datapoly_mcp_client + ADD COLUMN manage_flag boolean NOT NULL DEFAULT false; diff --git a/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransport.java b/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransport.java index 76ae183..fff8239 100644 --- a/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransport.java +++ b/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransport.java @@ -9,9 +9,9 @@ import org.slf4j.*; import reactor.core.publisher.Mono; -import javax.servlet.*; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.*; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.*; import java.io.*; import java.time.Duration; import java.util.*; diff --git a/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 53477a2..838537e 100644 --- a/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/datapoly-mcp/datapoly-mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -9,9 +9,9 @@ import org.slf4j.*; import reactor.core.publisher.*; -import javax.servlet.*; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.*; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.*; import java.io.*; import java.time.Duration; import java.util.*; diff --git a/datapoly-persistence/pom.xml b/datapoly-persistence/pom.xml index 8a0b92f..37236d2 100644 --- a/datapoly-persistence/pom.xml +++ b/datapoly-persistence/pom.xml @@ -25,8 +25,8 @@ - mysql - mysql-connector-java + com.mysql + mysql-connector-j @@ -41,7 +41,7 @@ com.baomidou - mybatis-plus-boot-starter + mybatis-plus-spring-boot3-starter diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiAssignmentDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiAssignmentDao.java index 6a3ecd0..43f9c10 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiAssignmentDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiAssignmentDao.java @@ -12,7 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiContextDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiContextDao.java index ea6fe51..c141b61 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiContextDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiContextDao.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiGroupDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiGroupDao.java index def1cac..fb0030a 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiGroupDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiGroupDao.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiModuleDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiModuleDao.java index cd274d8..8bdf8c4 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiModuleDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiModuleDao.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiOnlineDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiOnlineDao.java index 71bff16..4f6821e 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiOnlineDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/ApiOnlineDao.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Repository; import org.springframework.util.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/AppClientDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/AppClientDao.java index f292a31..2e5e9cd 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/AppClientDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/AppClientDao.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataSourceDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataSourceDao.java index 92eaa25..fa898d3 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataSourceDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataSourceDao.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; import java.util.stream.Collectors; diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskDefDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskDefDao.java index 4ebad71..ba0dda6 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskDefDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskDefDao.java @@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskJobDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskJobDao.java index b870a71..2154c95 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskJobDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/DataTaskJobDao.java @@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/FirewallRulesDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/FirewallRulesDao.java index 8a29cb1..8764da4 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/FirewallRulesDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/FirewallRulesDao.java @@ -7,7 +7,7 @@ import com.cs.persistence.mapper.FirewallRulesMapper; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Repository public class FirewallRulesDao { diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpClientDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpClientDao.java index 14ec032..b124821 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpClientDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpClientDao.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository @@ -43,6 +43,14 @@ public boolean existsAccessToken(String accessToken) { return null != mcpClientMapper.selectOne(queryWrapper); } + public boolean existsManageAccessToken(String accessToken) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda() + .eq(McpClientEntity::getToken, accessToken) + .eq(McpClientEntity::getManage, Boolean.TRUE); + return null != mcpClientMapper.selectOne(queryWrapper); + } + public void updateById(McpClientEntity entity) { mcpClientMapper.updateById(entity); } diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpToolDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpToolDao.java index 9684b88..f2e139b 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpToolDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/McpToolDao.java @@ -8,7 +8,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.List; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemParamDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemParamDao.java index 0fa0630..1a304f4 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemParamDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemParamDao.java @@ -7,7 +7,7 @@ import com.cs.persistence.mapper.SystemParamMapper; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.Objects; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemUserDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemUserDao.java index 3a5f524..c83b1c3 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemUserDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/SystemUserDao.java @@ -7,7 +7,7 @@ import com.cs.persistence.mapper.SystemUserMapper; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.Objects; @Repository diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/UnifyAlarmDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/UnifyAlarmDao.java index bb0cfcf..6b898fa 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/UnifyAlarmDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/UnifyAlarmDao.java @@ -7,7 +7,7 @@ import com.cs.persistence.mapper.UnifyAlarmMapper; import org.springframework.stereotype.Repository; -import javax.annotation.Resource; +import jakarta.annotation.Resource; @Repository public class UnifyAlarmDao { diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/dao/VersionCommitDao.java b/datapoly-persistence/src/main/java/com/cs/persistence/dao/VersionCommitDao.java index dedc0db..e5df18e 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/dao/VersionCommitDao.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/dao/VersionCommitDao.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.*; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.util.*; @Slf4j diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/entity/McpClientEntity.java b/datapoly-persistence/src/main/java/com/cs/persistence/entity/McpClientEntity.java index e34a0e5..e47a64f 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/entity/McpClientEntity.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/entity/McpClientEntity.java @@ -27,6 +27,10 @@ public class McpClientEntity { @TableField("token") private String token; + // True grants the admin MCP server (entity CRUD for agents) on top of the data-tool server + @TableField("manage_flag") + private Boolean manage; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @TableField(value = "create_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER) private Timestamp createTime; diff --git a/datapoly-persistence/src/main/java/com/cs/persistence/mapper/AccessRecordMapper.java b/datapoly-persistence/src/main/java/com/cs/persistence/mapper/AccessRecordMapper.java index 36c916f..49c6d13 100644 --- a/datapoly-persistence/src/main/java/com/cs/persistence/mapper/AccessRecordMapper.java +++ b/datapoly-persistence/src/main/java/com/cs/persistence/mapper/AccessRecordMapper.java @@ -181,14 +181,14 @@ public interface AccessRecordMapper extends BaseMapper { + "" + "SELECT HOUR(create_time) as hour,count(*) as count from DATAPOLY_ACCESS_RECORD " + "WHERE api_id = #{apiId} " - + "AND create_time >= #{date} AND create_time < DATE_ADD(#{date}, INTERVAL 1 DAY) " + + "AND create_time >= #{date} AND create_time < DATE_ADD(#{date}, INTERVAL 1 DAY) " + "GROUP BY HOUR(create_time) " + "ORDER BY hour ASC " + "" + "" + "SELECT EXTRACT(HOUR FROM create_time)::int as hour,count(*) as count from DATAPOLY_ACCESS_RECORD " + "WHERE api_id = #{apiId} " - + "AND create_time >= CAST(#{date} AS timestamp) AND create_time < CAST(#{date} AS timestamp) + INTERVAL '1 day' " + + "AND create_time >= CAST(#{date} AS timestamp) AND create_time < CAST(#{date} AS timestamp) + INTERVAL '1 day' " + "GROUP BY EXTRACT(HOUR FROM create_time) " + "ORDER BY hour ASC " + "" @@ -207,7 +207,7 @@ public interface AccessRecordMapper extends BaseMapper { + "" + "" + "DELETE FROM DATAPOLY_ACCESS_RECORD " - + "WHERE create_time < CURRENT_DATE - (#{days} * INTERVAL '1 day') + INTERVAL '1 day' " + + "WHERE create_time < CURRENT_DATE - (#{days} * INTERVAL '1 day') + INTERVAL '1 day' " + "" + "") void deleteHistoryBeforeDays(@Param("days") Integer days); diff --git a/datapoly-template/pom.xml b/datapoly-template/pom.xml index 3b6c599..652871d 100644 --- a/datapoly-template/pom.xml +++ b/datapoly-template/pom.xml @@ -17,11 +17,5 @@ mybatis - - junit - junit - test - - \ No newline at end of file diff --git a/datapoly-test/pom.xml b/datapoly-test/pom.xml new file mode 100644 index 0000000..c594cdb --- /dev/null +++ b/datapoly-test/pom.xml @@ -0,0 +1,91 @@ + + + + datapoly-parent + com.cs + 1.9.0 + + 4.0.0 + datapoly-test + + Centralized unit tests for all DataPoly modules(JUnit 4 + hand-written fakes,无外部服务依赖) + + + + com.cs + datapoly-common + ${project.version} + + + com.cs + datapoly-template + ${project.version} + + + com.cs + datapoly-persistence + ${project.version} + + + com.cs + datapoly-cache + ${project.version} + + + com.cs + datapoly-core + ${project.version} + + + com.cs + datapoly-executor + ${project.version} + + + com.cs + datapoly-gateway + ${project.version} + + + com.cs + datapoly-manager + ${project.version} + + + + junit + junit + test + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + prepare-agent + + prepare-agent + + + + report-aggregate + verify + + report-aggregate + + + + + + + + diff --git a/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastCacheFactoryTest.java b/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastCacheFactoryTest.java new file mode 100644 index 0000000..88f8c01 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastCacheFactoryTest.java @@ -0,0 +1,76 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.hazelcast; + +import com.cs.cache.DistributedCache; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class HazelcastCacheFactoryTest { + + private HazelcastCacheFactory factory; + private Map backingStore; + + @Before + public void setUp() { + backingStore = new HashMap<>(); + IMap map = (IMap) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{IMap.class}, + (p, m, a) -> { + if ("get".equals(m.getName())) { + return backingStore.get(a[0]); + } + if ("put".equals(m.getName())) { + return backingStore.put(a[0], a[1]); + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + HazelcastInstance hazelcast = (HazelcastInstance) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{HazelcastInstance.class}, + (p, m, a) -> { + if ("getMap".equals(m.getName())) { + return map; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + factory = new HazelcastCacheFactory(); + com.cs.core.datatask.DataTaskTestSupport.setField(factory, "hazelcastInstance", hazelcast); + } + + @Test + public void testCacheMapDelegatesToHazelcastMap() { + Map cacheMap = factory.getCacheMap("m", String.class); + cacheMap.put("k", "v"); + assertEquals("v", backingStore.get("k")); + assertEquals("v", cacheMap.get("k")); + } + + @Test + public void testDistributedCacheCachedByName() { + DistributedCache first = factory.getDistributedCache("cache-a"); + assertSame(first, factory.getDistributedCache("cache-a")); + assertNotSame(first, factory.getDistributedCache("cache-b")); + assertEquals("cache-a", first.getName()); + first.put("k", "v", 0, java.util.concurrent.TimeUnit.SECONDS); + assertEquals("v", backingStore.get("k")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastDistributedCacheTest.java b/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastDistributedCacheTest.java new file mode 100644 index 0000000..d25c3ff --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/hazelcast/HazelcastDistributedCacheTest.java @@ -0,0 +1,120 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.hazelcast; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class HazelcastDistributedCacheTest { + + /** Records operations on the fake IMap by method name and argument count. */ + static class RecordingMap { + final List ops = new ArrayList<>(); + final Map store = new HashMap<>(); + + IMap proxy() { + return (IMap) Proxy.newProxyInstance(HazelcastDistributedCacheTest.class.getClassLoader(), + new Class[]{IMap.class}, + (p, m, a) -> { + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + if ("toString".equals(m.getName())) { + return "fake-imap"; + } + if ("get".equals(m.getName())) { + return store.get(a[0]); + } + if ("put".equals(m.getName()) && a.length == 2) { + ops.add("put"); + return store.put(a[0], a[1]); + } + if ("put".equals(m.getName()) && a.length == 4) { + ops.add("put:" + a[2] + ":" + ((TimeUnit) a[3]).name()); + return store.put(a[0], a[1]); + } + if ("evict".equals(m.getName())) { + ops.add("evict"); + return store.remove(a[0]) != null; + } + return null; + }); + } + } + + private HazelcastInstance instance(final IMap map, final String[] nameSink) { + return (HazelcastInstance) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{HazelcastInstance.class}, + (p, m, a) -> { + if ("getMap".equals(m.getName())) { + nameSink[0] = (String) a[0]; + return map; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testConstructorRejectsNulls() { + try { + new HazelcastDistributedCache(null, "name"); + fail("null instance must be rejected"); + } catch (NullPointerException expected) { + } + try { + new HazelcastDistributedCache((HazelcastInstance) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{HazelcastInstance.class}, + (p, m, a) -> null), null); + fail("null name must be rejected"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testGetCastsStoredValue() { + RecordingMap recording = new RecordingMap(); + recording.store.put("k", "value"); + String[] mapName = new String[1]; + HazelcastDistributedCache cache = new HazelcastDistributedCache(instance(recording.proxy(), mapName), "cache"); + assertEquals("value", cache.get("k", String.class)); + assertEquals("cache", mapName[0]); + assertNull(cache.get("missing", String.class)); + } + + @Test + public void testPutWithAndWithoutExpiry() { + RecordingMap recording = new RecordingMap(); + HazelcastDistributedCache cache = new HazelcastDistributedCache( + instance(recording.proxy(), new String[1]), "cache"); + cache.put("k", "v", 0, TimeUnit.SECONDS); + cache.put("k2", "v2", 30, TimeUnit.SECONDS); + assertEquals(Arrays.asList("put", "put:30:SECONDS"), recording.ops); + assertEquals("v", cache.get("k", String.class)); + } + + @Test + public void testEvictRemovesKey() { + RecordingMap recording = new RecordingMap(); + recording.store.put("k", "v"); + HazelcastDistributedCache cache = new HazelcastDistributedCache( + instance(recording.proxy(), new String[1]), "cache"); + cache.evict("k"); + assertNull(cache.get("k", String.class)); + assertTrue(recording.ops.contains("evict")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheFactoryTest.java b/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheFactoryTest.java new file mode 100644 index 0000000..fd7dd8d --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheFactoryTest.java @@ -0,0 +1,50 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.redis; + +import com.cs.cache.DistributedCache; +import com.cs.core.datatask.DataTaskTestSupport; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RedisCacheFactoryTest { + + private RedisCacheFactory factory; + private RedisTestSupport.FakePool pool; + + @Before + public void setUp() { + pool = new RedisTestSupport.FakePool(); + factory = new RedisCacheFactory(); + DataTaskTestSupport.setField(factory, "jedisClient", new JedisClient(pool)); + } + + @Test + public void testCacheMapWritesThroughToJedis() { + // NOTE: bean-like value types round-trip; plain String values do not survive + // JSONUtil.toBean on a bare JSON string (main-code limitation, see run report) + Map> cacheMap = + factory.getCacheMap("ht", (Class>) (Class) Map.class); + Map value = new java.util.LinkedHashMap<>(); + value.put("f", 1); + cacheMap.put("k", value); + assertEquals("{\"f\":1}", pool.jedis.hashes.get("ht").get("k")); + assertEquals(value, cacheMap.get("k")); + } + + @Test + public void testDistributedCacheCachedByName() { + DistributedCache first = factory.getDistributedCache("cache-a"); + DistributedCache second = factory.getDistributedCache("cache-a"); + assertSame(first, second); + assertNotSame(first, factory.getDistributedCache("cache-b")); + assertEquals("cache-a", first.getName()); + + first.put("k", "v", 5, TimeUnit.SECONDS); + assertTrue(pool.jedis.strings.containsKey("cache-a#k")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheMapTest.java b/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheMapTest.java new file mode 100644 index 0000000..b565887 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/redis/RedisCacheMapTest.java @@ -0,0 +1,93 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.redis; + +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RedisCacheMapTest { + + private RedisTestSupport.FakePool pool; + private RedisCacheMap> map; + + @Before + public void setUp() { + pool = new RedisTestSupport.FakePool(); + map = new RedisCacheMap<>("ht", new JedisClient(pool), + (Class>) (Class) Map.class); + } + + private Map row(String key, int value) { + Map row = new LinkedHashMap<>(); + row.put(key, value); + return row; + } + + @Test + public void testSizeAndEmptinessTrackBackingHash() { + assertTrue(map.isEmpty()); + map.put("a", row("v", 1)); + assertEquals(1, map.size()); + assertFalse(map.isEmpty()); + } + + @Test + public void testPutReturnsPreviousValueAndRoundTripsJson() { + assertNull(map.put("a", row("v", 1))); + assertEquals(row("v", 1), map.get("a")); + assertEquals(row("v", 1), map.put("a", row("v", 2))); + assertEquals(row("v", 2), map.get("a")); + } + + @Test + public void testContainsKeyAndContainsValue() { + map.put("a", row("v", 1)); + assertTrue(map.containsKey("a")); + assertFalse(map.containsKey("b")); + assertTrue(map.containsValue(row("v", 1))); + assertFalse(map.containsValue(row("v", 9))); + } + + @Test + public void testRemoveReturnsPreviousValue() { + map.put("a", row("v", 1)); + assertEquals(row("v", 1), map.remove("a")); + assertNull(map.get("a")); + assertNull(map.remove("a")); + } + + @Test + public void testPutAllWritesThroughAndIgnoresEmptyInput() { + Map> batch = new LinkedHashMap<>(); + batch.put("a", row("v", 1)); + batch.put("b", row("v", 2)); + map.putAll(batch); + assertEquals(2, map.size()); + + map.putAll(null); + map.putAll(new HashMap>()); + assertEquals(2, map.size()); + } + + @Test + public void testKeySetValuesEntrySetFromSnapshot() { + map.put("a", row("v", 1)); + map.put("b", row("v", 2)); + assertEquals(new HashSet<>(Arrays.asList("a", "b")), map.keySet()); + assertEquals(2, map.values().size()); + assertTrue(map.values().contains(row("v", 1))); + assertEquals(2, map.entrySet().size()); + } + + @Test + public void testClearDropsWholeHash() { + map.put("a", row("v", 1)); + map.clear(); + assertTrue(map.isEmpty()); + assertFalse(pool.jedis.hashes.containsKey("ht")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/cache/redis/RedisDistributedCacheTest.java b/datapoly-test/src/test/java/com/cs/cache/redis/RedisDistributedCacheTest.java new file mode 100644 index 0000000..813b7c5 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/redis/RedisDistributedCacheTest.java @@ -0,0 +1,72 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.redis; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class RedisDistributedCacheTest { + + private RedisTestSupport.FakePool pool; + + @Before + public void setUp() { + pool = new RedisTestSupport.FakePool(); + } + + @Test + public void testConstructorRejectsNulls() { + try { + new RedisDistributedCache(null, new JedisClient(pool)); + fail("null cache name must be rejected"); + } catch (NullPointerException expected) { + } + try { + new RedisDistributedCache("name", null); + fail("null client must be rejected"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testGetMissingKeyReturnsNull() { + RedisDistributedCache cache = new RedisDistributedCache("demo", new JedisClient(pool)); + assertNull(cache.get("missing", String.class)); + } + + @Test + public void testPutGetRoundTripUnderNamespacedKey() { + RedisDistributedCache cache = new RedisDistributedCache("demo", new JedisClient(pool)); + Map value = new LinkedHashMap<>(); + value.put("a", 1); + cache.put("k", value, 0, TimeUnit.SECONDS); + // value stored under "demo#k" as JSON + assertTrue(pool.jedis.strings.containsKey("demo#k")); + @SuppressWarnings("unchecked") + Map read = (Map) cache.get("k", Map.class); + assertEquals(1, read.get("a")); + assertEquals("demo", cache.getName()); + } + + @Test + public void testPositiveExpirySetsExpiration() { + RedisDistributedCache cache = new RedisDistributedCache("demo", new JedisClient(pool)); + cache.put("k", "v", 30, TimeUnit.SECONDS); + assertEquals(Collections.singletonList("demo#k:30"), pool.jedis.expireCalls); + } + + @Test + public void testEvictDeletesKey() { + RedisDistributedCache cache = new RedisDistributedCache("demo", new JedisClient(pool)); + cache.put("k", "v", 0, TimeUnit.SECONDS); + cache.evict("k"); + assertNull(cache.get("k", String.class)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/cache/redis/RedisTestSupport.java b/datapoly-test/src/test/java/com/cs/cache/redis/RedisTestSupport.java new file mode 100644 index 0000000..9164794 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/cache/redis/RedisTestSupport.java @@ -0,0 +1,182 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.cache.redis; + +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.util.Pool; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.*; + +/** + * Hand-written jedis fakes: an in-memory Jedis subclass plus a Pool that hands it out, + * so RedisCacheMap / RedisDistributedCache run their real lambda bodies without a server. + * A loopback ServerSocket backs the Jedis constructor because Jedis 4+ may establish the + * TCP connection eagerly; no Redis protocol is ever spoken (all commands are overridden). + */ +public final class RedisTestSupport { + + private static final ServerSocket DUMMY_SERVER = createDummyServer(); + + private RedisTestSupport() { + } + + private static ServerSocket createDummyServer() { + try { + return new ServerSocket(0, 1, InetAddress.getLoopbackAddress()); + } catch (Exception e) { + throw new IllegalStateException("Cannot open dummy server socket for jedis fake", e); + } + } + + public static class FakeJedis extends Jedis { + public final Map> hashes = new HashMap<>(); + public final Map strings = new HashMap<>(); + public final List expireCalls = new ArrayList<>(); + + public FakeJedis() { + super("localhost", DUMMY_SERVER.getLocalPort()); + } + + @Override + public String ping() { + return "PONG"; + } + + @Override + public void close() { + // never touch the (dummy) connection + } + + @Override + public long hlen(String key) { + Map hash = hashes.get(key); + return null == hash ? 0L : (long) hash.size(); + } + + @Override + public boolean hexists(String key, String field) { + Map hash = hashes.get(key); + return null != hash && hash.containsKey(field); + } + + @Override + public String hget(String key, String field) { + Map hash = hashes.get(key); + return null == hash ? null : hash.get(field); + } + + @Override + public long hset(String key, String field, String value) { + hashes.computeIfAbsent(key, k -> new HashMap()).put(field, value); + return 1L; + } + + @Override + public long hdel(String key, String... fields) { + Map hash = hashes.get(key); + if (null == hash) { + return 0L; + } + long removed = 0; + for (String field : fields) { + if (null != hash.remove(field)) { + removed++; + } + } + return removed; + } + + @Override + public String hmset(String key, Map map) { + hashes.computeIfAbsent(key, k -> new HashMap()).putAll(map); + return "OK"; + } + + @Override + public Set hkeys(String key) { + Map hash = hashes.get(key); + return null == hash ? new HashSet() : new HashSet<>(hash.keySet()); + } + + @Override + public Map hgetAll(String key) { + Map hash = hashes.get(key); + return null == hash ? new HashMap() : new HashMap<>(hash); + } + + @Override + public String get(String key) { + return strings.get(key); + } + + @Override + public String set(String key, String value) { + strings.put(key, value); + return "OK"; + } + + @Override + public long expire(String key, long seconds) { + expireCalls.add(key + ":" + seconds); + return 1L; + } + + @Override + public long del(String key) { + return del(new String[]{key}); + } + + @Override + public long del(String... keys) { + long removed = 0; + for (String key : keys) { + if (null != hashes.remove(key)) { + removed++; + } + if (null != strings.remove(key)) { + removed++; + } + } + return removed; + } + } + + public static class FakePool extends Pool { + public final FakeJedis jedis = new FakeJedis(); + + public FakePool() { + // Jedis 6 的 Pool 直接继承 GenericObjectPool,构造必须给工厂; + // getResource 已覆写为直接返回 fake,工厂永远不会被实际调用 + super(new BasePooledObjectFactory() { + @Override + public Jedis create() { + return null; + } + + @Override + public PooledObject wrap(Jedis value) { + return new DefaultPooledObject<>(value); + } + }); + } + + @Override + public Jedis getResource() { + return jedis; + } + + @Override + public void close() { + // pool holds no real resources + } + + @Override + public void destroy() { + // pool holds no real resources + } + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/dto/ItemParamTest.java b/datapoly-test/src/test/java/com/cs/common/dto/ItemParamTest.java new file mode 100644 index 0000000..24109ed --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/dto/ItemParamTest.java @@ -0,0 +1,76 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.dto; + +import com.cs.common.enums.HttpMethodEnum; +import com.cs.common.enums.ParamTypeEnum; +import com.cs.common.exception.CommonException; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.*; + +public class ItemParamTest { + + private ItemParam param(String name, ParamTypeEnum type) { + ItemParam param = new ItemParam(); + param.setName(name); + param.setType(type); + return param; + } + + @Test + public void testBlankNameRejected() { + try { + param(" ", ParamTypeEnum.STRING).checkValid(HttpMethodEnum.POST); + fail("blank name must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectInputRequiresBodyMethod() { + try { + param("obj", ParamTypeEnum.OBJECT).checkValid(HttpMethodEnum.GET); + fail("object input on a bodyless method must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectInputWithoutChildrenRejected() { + try { + param("obj", ParamTypeEnum.OBJECT).checkValid(HttpMethodEnum.POST); + fail("object input without children must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectChildWithBlankNameRejected() { + ItemParam parent = param("obj", ParamTypeEnum.OBJECT); + parent.setChildren(Collections.singletonList(param(null, ParamTypeEnum.STRING))); + try { + parent.checkValid(HttpMethodEnum.POST); + fail("child with blank name must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectInputWithChildrenPassesOnBodyMethod() { + ItemParam parent = param("obj", ParamTypeEnum.OBJECT); + parent.setChildren(Collections.singletonList(param("field", ParamTypeEnum.STRING))); + parent.checkValid(HttpMethodEnum.POST); + assertEquals(1, parent.getChildren().size()); + } + + @Test + public void testNonObjectInputResetsChildrenToEmpty() { + ItemParam plain = param("plain", ParamTypeEnum.STRING); + plain.setChildren(null); + plain.checkValid(HttpMethodEnum.GET); + assertNotNull(plain.getChildren()); + assertTrue(plain.getChildren().isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/dto/OutParamTest.java b/datapoly-test/src/test/java/com/cs/common/dto/OutParamTest.java new file mode 100644 index 0000000..ff1d34c --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/dto/OutParamTest.java @@ -0,0 +1,87 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.dto; + +import com.cs.common.enums.ParamTypeEnum; +import com.cs.common.exception.CommonException; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +public class OutParamTest { + + @Test + public void testBlankNameRejected() { + OutParam param = new OutParam(); + param.setType(ParamTypeEnum.STRING); + try { + param.checkValid(); + fail("blank name must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testNullTypeRejected() { + OutParam param = new OutParam(); + param.setName("name-only"); + try { + param.checkValid(); + fail("null type must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectWithoutChildrenRejected() { + OutParam param = new OutParam(); + param.setName("obj"); + param.setType(ParamTypeEnum.OBJECT); + try { + param.checkValid(); + fail("object output without children must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectChildWithBlankNameRejected() { + OutParam child = new OutParam(); + child.setType(ParamTypeEnum.STRING); + OutParam param = new OutParam(); + param.setName("obj"); + param.setType(ParamTypeEnum.OBJECT); + param.setChildren(Arrays.asList(child)); + try { + param.checkValid(); + fail("object child with blank name must be rejected"); + } catch (CommonException expected) { + } + } + + @Test + public void testObjectWithValidChildrenPasses() { + OutParam child = new OutParam(); + child.setName("field"); + child.setType(ParamTypeEnum.STRING); + OutParam param = new OutParam(); + param.setName("obj"); + param.setType(ParamTypeEnum.OBJECT); + param.setChildren(Collections.singletonList(child)); + param.checkValid(); + assertEquals(1, param.getChildren().size()); + } + + @Test + public void testNonObjectResetsChildrenToEmpty() { + OutParam param = new OutParam(); + param.setName("plain"); + param.setType(ParamTypeEnum.LONG); + param.setChildren(null); + param.checkValid(); + assertNotNull(param.getChildren()); + assertTrue(param.getChildren().isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/dto/ResultEntityTest.java b/datapoly-test/src/test/java/com/cs/common/dto/ResultEntityTest.java new file mode 100644 index 0000000..ec39fed --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/dto/ResultEntityTest.java @@ -0,0 +1,45 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.dto; + +import com.cs.common.exception.ResponseErrorCode; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ResultEntityTest { + + @Test + public void testSuccessWithoutData() { + ResultEntity entity = ResultEntity.success(); + assertEquals(Integer.valueOf(ResponseErrorCode.SUCCESS.getCode()), entity.getCode()); + assertEquals("success", entity.getMessage()); + assertNull(entity.getData()); + } + + @Test + public void testSuccessWithData() { + ResultEntity entity = ResultEntity.success("payload"); + assertEquals(Integer.valueOf(ResponseErrorCode.SUCCESS.getCode()), entity.getCode()); + assertEquals("payload", entity.getData()); + } + + @Test + public void testFailedWithErrorCodeOnly() { + ResultEntity entity = ResultEntity.failed(ResponseErrorCode.ERROR_INVALID_ARGUMENT); + assertEquals(Integer.valueOf(ResponseErrorCode.ERROR_INVALID_ARGUMENT.getCode()), entity.getCode()); + assertEquals(ResponseErrorCode.ERROR_INVALID_ARGUMENT.getMessage(), entity.getMessage()); + } + + @Test + public void testFailedAppendsDetailMessage() { + ResultEntity entity = ResultEntity.failed(ResponseErrorCode.ERROR_INVALID_ARGUMENT, "extra detail"); + assertTrue(entity.getMessage().endsWith(",extra detail")); + } + + @Test + public void testFailedWithMessageOnlyUsesInternalErrorCode() { + ResultEntity entity = ResultEntity.failed("boom"); + assertEquals(Integer.valueOf(ResponseErrorCode.ERROR_INTERNAL_ERROR.getCode()), entity.getCode()); + assertEquals("boom", entity.getMessage()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/enums/DataTypeFormatEnumTest.java b/datapoly-test/src/test/java/com/cs/common/enums/DataTypeFormatEnumTest.java new file mode 100644 index 0000000..33ecab1 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/enums/DataTypeFormatEnumTest.java @@ -0,0 +1,35 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.enums; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DataTypeFormatEnumTest { + + @Test + public void testDateAndTimePatterns() { + assertEquals("yyyy-MM-dd", DataTypeFormatEnum.DATE.getDefaultPattern()); + assertEquals("HH:mm:ss", DataTypeFormatEnum.TIME.getDefaultPattern()); + assertEquals("yyyy-MM-dd HH:mm:ss", DataTypeFormatEnum.LOCAL_DATE_TIME.getDefaultPattern()); + assertEquals(java.sql.Timestamp.class.getName(), DataTypeFormatEnum.TIMESTAMP.getClassName()); + } + + @Test + public void testBigDecimalScale() { + assertEquals(6, DataTypeFormatEnum.BIG_DECIMAL.getNumberScale()); + assertEquals("6", DataTypeFormatEnum.BIG_DECIMAL.getDefault()); + } + + @Test + public void testRemarkUsesI18nKeyWhenPresent() { + // no Spring context in unit tests: the key itself is the fallback message + assertEquals("format.use.system.response", DataTypeFormatEnum.USE_SYSTEM_RESPONSE_FORMAT.getRemark()); + assertEquals(java.sql.Date.class.getName(), DataTypeFormatEnum.DATE.getRemark()); + } + + @Test + public void testDefaultReturnsPatternWhenNoScale() { + assertEquals("yyyy-MM-dd", DataTypeFormatEnum.DATE.getDefault()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/enums/ExpireTimeEnumTest.java b/datapoly-test/src/test/java/com/cs/common/enums/ExpireTimeEnumTest.java new file mode 100644 index 0000000..82b6444 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/enums/ExpireTimeEnumTest.java @@ -0,0 +1,27 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.enums; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ExpireTimeEnumTest { + + @Test + public void testFromMatchesExactDurationAndValue() { + assertSame(ExpireTimeEnum.EXPIRE_FOR_EVER, ExpireTimeEnum.from(DurationTimeEnum.FOR_EVER, -1L)); + assertSame(ExpireTimeEnum.EXPIRE_ONLY_ONCE, ExpireTimeEnum.from(DurationTimeEnum.ONLY_ONCE, 0L)); + assertSame(ExpireTimeEnum.EXPIRE_05_MIN, ExpireTimeEnum.from(DurationTimeEnum.TIME_VALUE, 5 * 60L)); + assertSame(ExpireTimeEnum.EXPIRE_01_MOUTH, ExpireTimeEnum.from(DurationTimeEnum.TIME_VALUE, 30 * 24 * 3600L)); + } + + @Test + public void testFromFallsBackToUnknownForUnmatchedValue() { + assertSame(ExpireTimeEnum.EXPIRE_UNKNOWN, ExpireTimeEnum.from(DurationTimeEnum.TIME_VALUE, 12345L)); + } + + @Test + public void testFromFallsBackToUnknownForNullDuration() { + assertSame(ExpireTimeEnum.EXPIRE_UNKNOWN, ExpireTimeEnum.from(null, 0L)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/enums/HttpMethodEnumTest.java b/datapoly-test/src/test/java/com/cs/common/enums/HttpMethodEnumTest.java new file mode 100644 index 0000000..e41dc46 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/enums/HttpMethodEnumTest.java @@ -0,0 +1,27 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.enums; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class HttpMethodEnumTest { + + @Test + public void testBodyCapability() { + assertFalse(HttpMethodEnum.GET.isHasBody()); + assertFalse(HttpMethodEnum.HEAD.isHasBody()); + assertTrue(HttpMethodEnum.POST.isHasBody()); + assertTrue(HttpMethodEnum.PUT.isHasBody()); + assertTrue(HttpMethodEnum.DELETE.isHasBody()); + } + + @Test + public void testExistsIsCaseSensitiveOnEnumName() { + assertTrue(HttpMethodEnum.exists("GET")); + assertTrue(HttpMethodEnum.exists("POST")); + assertFalse(HttpMethodEnum.exists("get")); + assertFalse(HttpMethodEnum.exists("PATCH")); + assertFalse(HttpMethodEnum.exists(null)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/enums/ParamLocationEnumTest.java b/datapoly-test/src/test/java/com/cs/common/enums/ParamLocationEnumTest.java new file mode 100644 index 0000000..1aa9b5e --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/enums/ParamLocationEnumTest.java @@ -0,0 +1,30 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.enums; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ParamLocationEnumTest { + + @Test + public void testLocationCapabilities() { + assertTrue(ParamLocationEnum.REQUEST_HEADER.isParameter()); + assertTrue(ParamLocationEnum.REQUEST_HEADER.isHeader()); + assertFalse(ParamLocationEnum.REQUEST_HEADER.isRequestBody()); + + assertTrue(ParamLocationEnum.REQUEST_FORM.isParameter()); + assertFalse(ParamLocationEnum.REQUEST_FORM.isHeader()); + assertFalse(ParamLocationEnum.REQUEST_FORM.isRequestBody()); + + assertFalse(ParamLocationEnum.REQUEST_BODY.isParameter()); + assertTrue(ParamLocationEnum.REQUEST_BODY.isRequestBody()); + } + + @Test + public void testSwaggerInNames() { + assertEquals("header", ParamLocationEnum.REQUEST_HEADER.getIn()); + assertEquals("query", ParamLocationEnum.REQUEST_FORM.getIn()); + assertEquals("body", ParamLocationEnum.REQUEST_BODY.getIn()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java b/datapoly-test/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java new file mode 100644 index 0000000..0ba9ee1 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/enums/ProductTypeEnumTest.java @@ -0,0 +1,157 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.enums; + +import com.cs.common.util.JdbcUrlUtils; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; + +import static org.junit.Assert.*; + +public class ProductTypeEnumTest { + + @Test + public void testEnumUiNameInvariant() { + for (ProductTypeEnum type : ProductTypeEnum.values()) { + assertEquals("name must uppercase to its enum constant", type.name(), type.getName().toUpperCase()); + } + } + + @Test + public void testEnumIdsAreUnique() { + Set ids = new HashSet<>(); + for (ProductTypeEnum type : ProductTypeEnum.values()) { + assertTrue("duplicated id " + type.getId() + " for " + type.name(), ids.add(type.getId())); + } + } + + @Test + public void testOdpsLookup() { + assertTrue(ProductTypeEnum.exists("odps")); + assertTrue(ProductTypeEnum.exists("ODPS")); + assertEquals(ProductTypeEnum.ODPS, ProductTypeEnum.of("odps")); + assertEquals("com.aliyun.odps.jdbc.OdpsDriver", ProductTypeEnum.ODPS.getDriver()); + } + + @Test + public void testLookupByNameAndRejections() { + assertTrue(ProductTypeEnum.exists("mysql")); + assertTrue(ProductTypeEnum.exists("MySQL")); + assertFalse(ProductTypeEnum.exists("not-a-db")); + assertEquals(ProductTypeEnum.MYSQL, ProductTypeEnum.of("mysql")); + // context name may differ in case from the enum constant name + assertEquals(ProductTypeEnum.TDENGINE, ProductTypeEnum.of("TDengine")); + assertEquals(ProductTypeEnum.MONGODB, ProductTypeEnum.of("MongoDB")); + try { + ProductTypeEnum.of("not-a-db"); + fail("unknown name must be rejected"); + } catch (IllegalArgumentException expected) { + } + try { + ProductTypeEnum.of(null); + fail("null name must be rejected"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testCapabilitySwitches() { + // database-name capability: catalog-less types answer false + assertFalse(ProductTypeEnum.MYSQL.hasDatabaseName()); + assertFalse(ProductTypeEnum.MONGODB.hasDatabaseName()); + assertTrue(ProductTypeEnum.ORACLE.hasDatabaseName()); + assertTrue(ProductTypeEnum.POSTGRESQL.hasDatabaseName()); + + // file/address capability only diverges for sqlite + assertTrue(ProductTypeEnum.SQLITE3.hasFilePath()); + assertFalse(ProductTypeEnum.SQLITE3.hasAddress()); + assertFalse(ProductTypeEnum.MYSQL.hasFilePath()); + assertTrue(ProductTypeEnum.MYSQL.hasAddress()); + + // transactional off-switch for engines without real transactions + assertTrue(ProductTypeEnum.HIVE.offTransactional()); + assertTrue(ProductTypeEnum.SYBASE.offTransactional()); + assertTrue(ProductTypeEnum.IMPALA.offTransactional()); + assertFalse(ProductTypeEnum.MYSQL.offTransactional()); + + // multi-dialect only for OceanBase + assertTrue(ProductTypeEnum.OCEANBASE.isMultiDialect()); + assertFalse(ProductTypeEnum.MARIADB.isMultiDialect()); + + assertEquals(ProductTypeEnum.MYSQL.getSample(), ProductTypeEnum.MYSQL.getSample()); + assertEquals("/* ping */ SELECT 1", ProductTypeEnum.MYSQL.getSql()); + assertEquals(ProductTypeEnum.MYSQL.getTestSql(), ProductTypeEnum.MYSQL.getSql()); + } + + @Test + public void testGetPageSqlPerDialect() { + assertEquals("SELECT 1 LIMIT ? OFFSET ? ", ProductTypeEnum.MYSQL.getPageSql("SELECT 1", 2, 10)); + + String sqlserver = ProductTypeEnum.SQLSERVER.getPageSql("SELECT 1", 3, 10); + assertTrue(sqlserver.startsWith("SELECT TOP 10 ")); + assertTrue(sqlserver.contains("ROW_NUMBER()")); + assertTrue(sqlserver.trim().endsWith("ALIAS_ROW_NUM > ?")); + + // ODPS pagination is a no-op: pageSql blank returns the original sql unchanged + assertEquals("SELECT 1", ProductTypeEnum.ODPS.getPageSql("SELECT 1", 2, 10)); + // MONGODB defines no pageSql template either + assertEquals("SELECT 1", ProductTypeEnum.MONGODB.getPageSql("SELECT 1", 2, 10)); + } + + @Test + public void testSybaseResultSetFuncWalksMultiResultStatements() throws Exception { + java.sql.Statement statement = (java.sql.Statement) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{java.sql.Statement.class}, + (proxy, method, args) -> { + if ("getUpdateCount".equals(method.getName())) { + return -1; + } + if ("getMoreResults".equals(method.getName())) { + return false; + } + return null; + }); + assertTrue(ProductTypeEnum.SYBASE.getResultSetFunc().apply(true, statement)); + assertFalse("exhausted statement must report no result", + ProductTypeEnum.SYBASE.getResultSetFunc().apply(false, statement)); + assertNull(ProductTypeEnum.MYSQL.getResultSetFunc()); + } + + @Test + public void testOdpsContextSwitches() { + assertFalse(ProductTypeEnum.ODPS.hasDatabaseName()); + assertFalse(ProductTypeEnum.ODPS.hasFilePath()); + assertFalse(ProductTypeEnum.ODPS.isNoViewTables()); + assertTrue(ProductTypeEnum.ODPS.offTransactional()); + assertNull(ProductTypeEnum.ODPS.getSql()); + assertNotNull(ProductTypeEnum.ODPS.getPageConsumer()); + } + + @Test + public void testOdpsUrlPatternMatchesOfficialSample() { + String httpUrl = "jdbc:odps:http://service.cn-hangzhou.maxcompute.aliyun.com/api?project=test_project&useProjectTimeZone=true"; + String httpsUrl = "jdbc:odps:https://service.cn-hangzhou.maxcompute.aliyun.com/api?project=test_project"; + assertTrue(matchesAny(ProductTypeEnum.ODPS.getUrl(), httpUrl)); + assertTrue(matchesAny(ProductTypeEnum.ODPS.getUrl(), httpsUrl)); + assertFalse(matchesAny(ProductTypeEnum.ODPS.getUrl(), "jdbc:odps:http://bad")); + assertFalse(matchesAny(ProductTypeEnum.ODPS.getUrl(), + "jdbc:odps:http://service.cn-hangzhou.maxcompute.aliyun.com/other?project=test_project")); + assertTrue(httpUrl.startsWith(ProductTypeEnum.ODPS.getUrlPrefix())); + + Matcher matcher = JdbcUrlUtils.getPattern(ProductTypeEnum.ODPS.getUrl()[0]).matcher(httpUrl); + assertTrue(matcher.matches()); + assertEquals("service.cn-hangzhou.maxcompute.aliyun.com", matcher.group("host")); + assertEquals("project=test_project&useProjectTimeZone=true", matcher.group("params")); + } + + private boolean matchesAny(String[] templates, String url) { + return Arrays.stream(templates) + .map(JdbcUrlUtils::getPattern) + .map(pattern -> pattern.matcher(url)) + .anyMatch(Matcher::matches); + } +} \ No newline at end of file diff --git a/datapoly-common/src/test/java/com/cs/common/exception/ResponseErrorCodeTest.java b/datapoly-test/src/test/java/com/cs/common/exception/ResponseErrorCodeTest.java similarity index 100% rename from datapoly-common/src/test/java/com/cs/common/exception/ResponseErrorCodeTest.java rename to datapoly-test/src/test/java/com/cs/common/exception/ResponseErrorCodeTest.java diff --git a/datapoly-test/src/test/java/com/cs/common/model/JarFileClassLoaderTest.java b/datapoly-test/src/test/java/com/cs/common/model/JarFileClassLoaderTest.java new file mode 100644 index 0000000..09c976e --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/model/JarFileClassLoaderTest.java @@ -0,0 +1,84 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.model; + +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.junit.Assert.*; + +public class JarFileClassLoaderTest { + + private Path tempDirWithJars() throws IOException { + Path root = Files.createTempDirectory("jarloader"); + Files.createFile(root.resolve("a.jar")); + Files.createFile(root.resolve("readme.txt")); + Path sub = root.resolve("sub"); + Files.createDirectory(sub); + Files.createFile(sub.resolve("b.jar")); + return root; + } + + @Test + public void testCollectsJarsFromRootAndNestedDirs() throws IOException { + Path root = tempDirWithJars(); + try (URLClassLoader loader = new JarFileClassLoader(root.toString(), getClass().getClassLoader())) { + String[] urls = Arrays.stream(loader.getURLs()).map(URL::toString).toArray(String[]::new); + assertEquals(2, urls.length); + assertTrue(urls[0].endsWith("a.jar")); + assertTrue(urls[1].endsWith("sub/b.jar") || urls[1].endsWith("sub" + File.separator + "b.jar")); + } + } + + @Test + public void testVarargsConstructorAggregatesPaths() throws IOException { + Path root = tempDirWithJars(); + Path other = Files.createTempDirectory("jarloader2"); + Files.createFile(other.resolve("c.jar")); + try (URLClassLoader loader = new JarFileClassLoader( + new String[]{root.toString(), other.toString()}, getClass().getClassLoader())) { + assertEquals(3, loader.getURLs().length); + } + } + + @Test + public void testNullPathsRejected() { + try { + new JarFileClassLoader((String[]) null, getClass().getClassLoader()); + fail("null paths array must be rejected"); + } catch (IllegalArgumentException expected) { + } + // single null path: current implementation surfaces NPE while scanning the null entry + try { + new JarFileClassLoader((String) null, getClass().getClassLoader()); + fail("null single path must be rejected"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testEmptyPathsArrayRejected() { + try { + new JarFileClassLoader(new String[0], getClass().getClassLoader()); + fail("empty paths must be rejected"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testMissingDirectoryYieldsNoJarError() { + try { + new JarFileClassLoader(new File("/definitely/not/exist-" + System.nanoTime()).toString(), + getClass().getClassLoader()); + fail("missing directory must raise No jar file found"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("No jar file found")); + } + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/model/SimpleDataSourceTest.java b/datapoly-test/src/test/java/com/cs/common/model/SimpleDataSourceTest.java new file mode 100644 index 0000000..e543e6a --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/model/SimpleDataSourceTest.java @@ -0,0 +1,201 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.model; + +import org.junit.Before; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.lang.reflect.Proxy; + +import static org.junit.Assert.*; + +public class SimpleDataSourceTest { + + /** + * Hand-written fake driver loaded by name through the data source's classloader. + * doGetConnection instantiates a fresh driver on every getConnection call, so the + * observed state must live in static fields. + */ + public static class FakeDriver implements Driver { + static Connection next; + static String lastUrl; + static Properties lastInfo; + + @Override + public Connection connect(String url, Properties info) { + lastUrl = url; + lastInfo = info; + return next; + } + + @Override + public boolean acceptsURL(String url) { + return true; + } + + @Override + public java.sql.DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { + return new java.sql.DriverPropertyInfo[0]; + } + + @Override + public int getMajorVersion() { + return 1; + } + + @Override + public int getMinorVersion() { + return 0; + } + + @Override + public boolean jdbcCompliant() { + return false; + } + + @Override + public java.util.logging.Logger getParentLogger() { + return null; + } + } + + private static Connection fakeConnection() { + return (Connection) Proxy.newProxyInstance(SimpleDataSourceTest.class.getClassLoader(), + new Class[]{Connection.class}, + (proxy, method, args) -> "toString".equals(method.getName()) ? "fake-connection" : null); + } + + @Before + public void resetFakeDriver() { + FakeDriver.next = null; + FakeDriver.lastUrl = null; + FakeDriver.lastInfo = null; + } + + private SimpleDataSource newDataSource() { + Properties props = new Properties(); + return new SimpleDataSource(getClass().getClassLoader(), "jdbc:fake://host:1234/db", + FakeDriver.class.getName(), "default-user", "default-pass", props); + } + + @Test + public void testConstructorAppliesCredentialDefaults() { + Properties props = new Properties(); + SimpleDataSource ds = new SimpleDataSource(getClass().getClassLoader(), "jdbc:fake://h/db", + FakeDriver.class.getName(), "u1", "p1", props); + assertEquals("u1", ds.getProperties().getProperty("user")); + assertEquals("p1", ds.getProperties().getProperty("password")); + assertEquals("jdbc:fake://h/db", ds.getJdbcUrl()); + assertEquals(FakeDriver.class.getName(), ds.getDriverClassName()); + } + + @Test + public void testConstructorRejectsNulls() { + Properties props = new Properties(); + String url = "jdbc:fake://h/db"; + String driver = FakeDriver.class.getName(); + ClassLoader cl = getClass().getClassLoader(); + assertNpe(() -> new SimpleDataSource(null, url, driver, null, null, props)); + assertNpe(() -> new SimpleDataSource(cl, null, driver, null, null, props)); + assertNpe(() -> new SimpleDataSource(cl, url, null, null, null, props)); + assertNpe(() -> new SimpleDataSource(cl, url, driver, null, null, null)); + } + + private void assertNpe(Runnable construction) { + try { + construction.run(); + fail("expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testGetConnectionDelegatesToDriverWithDefaultCredentials() throws SQLException { + SimpleDataSource ds = newDataSource(); + Connection connection = fakeConnection(); + FakeDriver.next = connection; + + assertSame(connection, ds.getConnection()); + assertEquals("jdbc:fake://host:1234/db", FakeDriver.lastUrl); + assertEquals("default-user", FakeDriver.lastInfo.getProperty("user")); + assertEquals("default-pass", FakeDriver.lastInfo.getProperty("password")); + } + + @Test + public void testGetConnectionWithCredentialsOverridesPropertiesClone() throws SQLException { + SimpleDataSource ds = newDataSource(); + FakeDriver.next = fakeConnection(); + + ds.getConnection("override-user", "override-pass"); + assertEquals("override-user", FakeDriver.lastInfo.getProperty("user")); + assertEquals("override-pass", FakeDriver.lastInfo.getProperty("password")); + // original properties untouched (clone semantics) + assertEquals("default-user", ds.getProperties().getProperty("user")); + } + + @Test + public void testNullConnectionFromDriverRaisesSqlException() { + SimpleDataSource ds = newDataSource(); + FakeDriver.next = null; + try { + ds.getConnection(); + fail("driver returning null must surface as SQLException"); + } catch (SQLException expected) { + assertTrue(expected.getMessage().contains("Maybe invalid driver class name")); + } + } + + @Test + public void testUnknownDriverClassRaisesSqlException() { + Properties props = new Properties(); + SimpleDataSource ds = new SimpleDataSource(getClass().getClassLoader(), "jdbc:fake://h/db", + "com.example.no.SuchDriver", null, null, props); + try { + ds.getConnection(); + fail("unknown driver must surface as SQLException"); + } catch (SQLException expected) { + assertTrue(expected.getMessage().contains("Invalid driver class name")); + } + } + + @Test + public void testUnsupportedDataSourceOperations() throws SQLException { + SimpleDataSource ds = newDataSource(); + assertFalse(ds.isWrapperFor(Connection.class)); + try { + ds.unwrap(Connection.class); + fail("unwrap must not be supported"); + } catch (SQLFeatureNotSupportedException expected) { + } + try { + ds.getLogWriter(); + fail("getLogWriter must not be supported"); + } catch (SQLFeatureNotSupportedException expected) { + } + try { + ds.setLogWriter(null); + fail("setLogWriter must not be supported"); + } catch (SQLFeatureNotSupportedException expected) { + } + try { + ds.getParentLogger(); + fail("getParentLogger must not be supported"); + } catch (SQLFeatureNotSupportedException expected) { + } + } + + @Test + public void testLoginTimeoutDelegatesToDriverManager() throws SQLException { + SimpleDataSource ds = newDataSource(); + try { + ds.setLoginTimeout(7); + assertEquals(7, ds.getLoginTimeout()); + } finally { + ds.setLoginTimeout(0); + } + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/CacheUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/CacheUtilsTest.java new file mode 100644 index 0000000..f50a2bd --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/CacheUtilsTest.java @@ -0,0 +1,28 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CacheUtilsTest { + + @Test + public void testPutGetRemoveAndClearLifecycle() { + CacheUtils.clear(); + assertNull(CacheUtils.get("missing")); + + CacheUtils.put("a", "value-a"); + CacheUtils.put("b", "value-b"); + assertEquals("value-a", CacheUtils.get("a")); + + CacheUtils.remove("a"); + assertNull(CacheUtils.get("a")); + assertEquals(1, CacheUtils.getAll().size()); + assertTrue(CacheUtils.getAllValue().contains("value-b")); + + CacheUtils.clear(); + assertNull(CacheUtils.get("b")); + assertTrue(CacheUtils.getAll().isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/I18nUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/I18nUtilsTest.java new file mode 100644 index 0000000..2448510 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/I18nUtilsTest.java @@ -0,0 +1,22 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import java.util.Locale; + +import static org.junit.Assert.*; + +public class I18nUtilsTest { + + /** + * Without a Spring container the resolver stays null, the lookup path fails and + * every getMessage variant must degrade gracefully to its fallback. + */ + @Test + public void testMessagesFallBackWithoutSpringContext() { + assertEquals("some.missing.key", I18nUtils.getMessage("some.missing.key")); + assertEquals("default msg", I18nUtils.getMessage("some.missing.key", "default msg")); + assertEquals("some.missing.key", I18nUtils.getMessage("some.missing.key", Locale.ENGLISH)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/InetUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/InetUtilsTest.java new file mode 100644 index 0000000..5767fb9 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/InetUtilsTest.java @@ -0,0 +1,24 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import java.net.InetAddress; + +import static org.junit.Assert.*; + +public class InetUtilsTest { + + @Test + public void testLocalAddressResolvedAndCached() { + InetAddress first = InetUtils.getLocalAddress(); + assertNotNull("a local address must resolve on build hosts", first); + assertSame("second lookup must return the cached instance", first, InetUtils.getLocalAddress()); + } + + @Test + public void testLocalIpStrMatchesCachedAddress() { + InetAddress address = InetUtils.getLocalAddress(); + assertEquals(address.getHostAddress(), InetUtils.getLocalIpStr()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/JdbcUrlUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/JdbcUrlUtilsTest.java new file mode 100644 index 0000000..9a3f566 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/JdbcUrlUtilsTest.java @@ -0,0 +1,122 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import java.net.ServerSocket; +import java.util.regex.Matcher; + +import static org.junit.Assert.*; + +public class JdbcUrlUtilsTest { + + @Test + public void testMysqlUrlWithAllSegments() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:mysql://{host}[:{port}]/[{database}][\\?{params}]") + .matcher("jdbc:mysql://localhost:3306/test_demo?useUnicode=true&useSSL=false"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("3306", matcher.group("port")); + assertEquals("test_demo", matcher.group("database")); + assertEquals("useUnicode=true&useSSL=false", matcher.group("params")); + } + + @Test + public void testMysqlUrlWithoutOptionalSegments() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:mysql://{host}[:{port}]/[{database}][\\?{params}]") + .matcher("jdbc:mysql://dbhost/mysql"); + assertTrue(matcher.matches()); + assertEquals("dbhost", matcher.group("host")); + assertNull(matcher.group("port")); + assertEquals("mysql", matcher.group("database")); + assertNull(matcher.group("params")); + } + + @Test + public void testMysqlUrlRejectsForeignPrefix() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:mysql://{host}[:{port}]/[{database}][\\?{params}]") + .matcher("jdbc:mariadb://localhost:3306/mysql"); + assertFalse(matcher.matches()); + } + + @Test + public void testPostgresUrlWithoutDatabase() { + // the "/" before [database] is mandatory; only the database itself is optional + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:postgresql://{host}[:{port}]/[{database}][\\?{params}]") + .matcher("jdbc:postgresql://localhost:5432/"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("5432", matcher.group("port")); + assertNull(matcher.group("database")); + } + + @Test + public void testOracleSidUrl() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:oracle:thin:@{host}[:{port}]:{sid}") + .matcher("jdbc:oracle:thin:@localhost:1521:orcl"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("1521", matcher.group("port")); + assertEquals("orcl", matcher.group("sid")); + } + + @Test + public void testOracleServiceNameUrl() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:oracle:thin:@//{host}[:{port}]/{name}") + .matcher("jdbc:oracle:thin:@//localhost:1521/orcl.city.com"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("orcl.city.com", matcher.group("name")); + } + + @Test + public void testSqlServerUrlWithParams() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:sqlserver://{host}[:{port}][;DatabaseName={database}][;{params}]") + .matcher("jdbc:sqlserver://localhost:1433;DatabaseName=中文;user=MyUserName"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("1433", matcher.group("port")); + assertEquals("中文", matcher.group("database")); + assertEquals("user=MyUserName", matcher.group("params")); + } + + @Test + public void testSqliteFileUrl() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:sqlite:{file}") + .matcher("jdbc:sqlite:/tmp/phone.db"); + assertTrue(matcher.matches()); + assertEquals("/tmp/phone.db", matcher.group("file")); + } + + @Test + public void testTeradataUrlWithParams() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:teradata://{host}/DATABASE={database},DBS_PORT={port}[,{params}]") + .matcher("jdbc:teradata://localhost/DATABASE=test,DBS_PORT=1234,CLIENT_CHARSET=EUC_CN"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("test", matcher.group("database")); + assertEquals("1234", matcher.group("port")); + assertEquals("CLIENT_CHARSET=EUC_CN", matcher.group("params")); + } + + @Test + public void testDb2UrlWithColonSeparatedParams() { + Matcher matcher = JdbcUrlUtils.getPattern("jdbc:db2://{host}:{port}/{database}[:{params}]") + .matcher("jdbc:db2://localhost:50000/testdb:driverType=4;fullyMaterializeLobData=true"); + assertTrue(matcher.matches()); + assertEquals("localhost", matcher.group("host")); + assertEquals("50000", matcher.group("port")); + assertEquals("testdb", matcher.group("database")); + assertEquals("driverType=4;fullyMaterializeLobData=true", matcher.group("params")); + } + + @Test + public void testReachableAgainstOpenAndClosedLocalPorts() throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0, 1, java.net.InetAddress.getByName("127.0.0.1"))) { + int openPort = serverSocket.getLocalPort(); + assertTrue(JdbcUrlUtils.reachable("127.0.0.1", String.valueOf(openPort))); + } + // port 1 on loopback is almost certainly closed in build environments + assertFalse(JdbcUrlUtils.reachable("127.0.0.1", "1")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/LambdaUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/LambdaUtilsTest.java new file mode 100644 index 0000000..c7de9b3 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/LambdaUtilsTest.java @@ -0,0 +1,82 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class LambdaUtilsTest { + + private static class RecordedFailure extends Exception { + } + + @Test + public void testIfDoRunsActionOnlyWhenConditionTrue() { + AtomicInteger counter = new AtomicInteger(); + LambdaUtils.ifDo(true, counter::incrementAndGet); + assertEquals(1, counter.get()); + LambdaUtils.ifDo(false, counter::incrementAndGet); + assertEquals(1, counter.get()); + } + + @Test + public void testIfDoWrapsCheckedException() { + try { + LambdaUtils.ifDo(true, () -> { + throw new RecordedFailure(); + }); + fail("expected RuntimeException wrapping the checked failure"); + } catch (RuntimeException e) { + assertTrue(e.getCause() instanceof RecordedFailure); + } + } + + /** + * Documents current behaviour: the condition argument is ignored and the action + * always runs. Suspected bug kept as-is per repo policy; flagged in the report. + */ + @Test + public void testIfDoMayThrowRunsRegardlessOfCondition() throws Exception { + AtomicInteger counter = new AtomicInteger(); + LambdaUtils.ifDoMayThrow(false, counter::incrementAndGet); + assertEquals(1, counter.get()); + LambdaUtils.ifDoMayThrow(true, counter::incrementAndGet); + assertEquals(2, counter.get()); + } + + @Test + public void testIfDoIgnoreThrowSwallowsFailure() { + AtomicInteger counter = new AtomicInteger(); + LambdaUtils.ifDoIgnoreThrow(true, () -> { + counter.incrementAndGet(); + throw new RecordedFailure(); + }); + assertEquals(1, counter.get()); + LambdaUtils.ifDoIgnoreThrow(false, counter::incrementAndGet); + assertEquals("condition is not honoured by ifDoIgnoreThrow either", 2, counter.get()); + } + + @Test + public void testIfDoElseSelectsBranch() { + AtomicInteger counter = new AtomicInteger(); + LambdaUtils.ifDoElse(true, () -> counter.set(10), () -> counter.set(20)); + assertEquals(10, counter.get()); + LambdaUtils.ifDoElse(false, () -> counter.set(10), () -> counter.set(20)); + assertEquals(20, counter.get()); + } + + @Test + public void testIfDoElseWrapsThrowable() { + try { + LambdaUtils.ifDoElse(true, () -> { + throw new RecordedFailure(); + }, () -> { + }); + fail("expected RuntimeException wrapping the branch failure"); + } catch (RuntimeException e) { + assertTrue(e.getCause() instanceof RecordedFailure); + } + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/PasswordUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/PasswordUtilsTest.java new file mode 100644 index 0000000..18906fe --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/PasswordUtilsTest.java @@ -0,0 +1,19 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import cn.hutool.crypto.digest.BCrypt; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class PasswordUtilsTest { + + @Test + public void testEncryptPasswordVerifiesWithBcrypt() { + String salt = BCrypt.gensalt(); + String hashed = PasswordUtils.encryptPassword("secret-123", salt); + assertNotEquals("secret-123", hashed); + assertTrue(BCrypt.checkpw("secret-123", hashed)); + assertFalse(BCrypt.checkpw("wrong-password", hashed)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/common/util/ServletUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/ServletUtilsTest.java new file mode 100644 index 0000000..d3a77de --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/ServletUtilsTest.java @@ -0,0 +1,76 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class ServletUtilsTest { + + private HttpServletRequest fakeRequest(String url, String uri, String remoteAddr, Map headers) { + return (HttpServletRequest) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{HttpServletRequest.class}, + (proxy, method, args) -> { + switch (method.getName()) { + case "getRequestURL": + return new StringBuffer(url); + case "getRequestURI": + return uri; + case "getRemoteAddr": + return remoteAddr; + case "getHeader": + return headers.get(String.valueOf(args[0])); + default: + return null; + } + }); + } + + @Test + public void testGetDomainStripsRequestUri() { + HttpServletRequest request = fakeRequest("http://api.example.com:8090/svc/api", + "/svc/api", null, new HashMap<>()); + assertEquals("http://api.example.com:8090", ServletUtils.getDomain(request)); + } + + @Test + public void testHeaderAccessors() { + Map headers = new HashMap<>(); + headers.put("Origin", "http://front.example.com"); + headers.put("user-agent", "DataPoly-Test/1.0"); + HttpServletRequest request = fakeRequest("http://x/y", "/y", null, headers); + assertEquals("http://front.example.com", ServletUtils.getOrigin(request)); + assertEquals("DataPoly-Test/1.0", ServletUtils.getUserAgent(request)); + assertEquals("/y", ServletUtils.getPathUri(request)); + } + + @Test + public void testIpAddrPrefersForwardedForHeader() { + Map headers = new HashMap<>(); + headers.put("x-forwarded-for", "1.2.3.4, 5.6.7.8"); + HttpServletRequest request = fakeRequest("http://x/y", "/y", "10.0.0.1", headers); + // current implementation returns the raw header value without splitting the chain + assertEquals("1.2.3.4, 5.6.7.8", ServletUtils.getIpAddr(request)); + } + + @Test + public void testIpAddrFallsThroughUnknownHeaders() { + Map headers = new HashMap<>(); + headers.put("x-forwarded-for", "unknown"); + headers.put("Proxy-Client-IP", "9.9.9.9"); + HttpServletRequest request = fakeRequest("http://x/y", "/y", "10.0.0.1", headers); + assertEquals("9.9.9.9", ServletUtils.getIpAddr(request)); + } + + @Test + public void testIpAddrFallsBackToRemoteAddr() { + HttpServletRequest request = fakeRequest("http://x/y", "/y", "10.0.0.1", new HashMap<>()); + assertEquals("10.0.0.1", ServletUtils.getIpAddr(request)); + } +} diff --git a/datapoly-common/src/test/java/com/cs/common/util/TokenUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/TokenUtilsTest.java similarity index 99% rename from datapoly-common/src/test/java/com/cs/common/util/TokenUtilsTest.java rename to datapoly-test/src/test/java/com/cs/common/util/TokenUtilsTest.java index 7b70517..529841e 100644 --- a/datapoly-common/src/test/java/com/cs/common/util/TokenUtilsTest.java +++ b/datapoly-test/src/test/java/com/cs/common/util/TokenUtilsTest.java @@ -7,7 +7,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.Environment; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.lang.reflect.*; import java.util.*; diff --git a/datapoly-test/src/test/java/com/cs/common/util/UuidUtilsTest.java b/datapoly-test/src/test/java/com/cs/common/util/UuidUtilsTest.java new file mode 100644 index 0000000..8da981f --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/common/util/UuidUtilsTest.java @@ -0,0 +1,17 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.common.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class UuidUtilsTest { + + @Test + public void testGenerateUuidIs32HexCharsWithoutDashes() { + String uuid = UuidUtils.generateUuid(); + assertEquals(32, uuid.length()); + assertTrue(uuid.matches("[0-9a-f]{32}")); + assertNotEquals(uuid, UuidUtils.generateUuid()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/JacksonUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/JacksonUtilsTest.java new file mode 100644 index 0000000..853a0af --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/JacksonUtilsTest.java @@ -0,0 +1,205 @@ +// Copyright tang. All rights reserved. +// Use of this source code is governed by a BSD-style license +package com.cs.core; + +import com.cs.common.enums.DataTypeFormatEnum; +import com.cs.core.util.JacksonUtils; +import com.fasterxml.jackson.databind.*; +import org.junit.*; + +import java.time.LocalDateTime; +import java.util.*; + +public class JacksonUtilsTest { + + /** + * Without a Spring container (plain unit test), falls back to the default timezone and serializes normally + */ + @Test + public void testToJsonStrWithoutFormatMap() { + Map result = new LinkedHashMap<>(); + result.put("user_name", "test"); + result.put("user_age", 23); + result.put("user_sex", 1); + String json = JacksonUtils.toJsonStr(result, Collections.emptyMap()); + Assert.assertEquals("{\"user_name\":\"test\",\"user_age\":23,\"user_sex\":1}", json); + } + + @Test + public void testToJsonStrWithDatetimeFormat() { + Map formatMap = new HashMap<>(); + formatMap.put(DataTypeFormatEnum.LOCAL_DATE_TIME, "yyyy/MM/dd HH:mm:ss"); + Map result = new LinkedHashMap<>(); + result.put("created", LocalDateTime.of(2026, 8, 25, 10, 15, 30)); + String json = JacksonUtils.toJsonStr(result, formatMap); + Assert.assertEquals("{\"created\":\"2026/08/25 10:15:30\"}", json); + } + + @Test + public void testJsonStrToMap() throws Exception { + String jsonString = "{\"user_sex\":1,\"user_name\":\"test\",\"user_age\":23}"; + + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(jsonString); + Map resultMap = new HashMap<>(); + rootNode.fields().forEachRemaining(entry -> resultMap.put(entry.getKey(), + mapper.convertValue(entry.getValue(), Object.class))); + Assert.assertEquals(3, resultMap.size()); + Assert.assertEquals("test", resultMap.get("user_name")); + Assert.assertEquals(23, resultMap.get("user_age")); + Assert.assertEquals(1, resultMap.get("user_sex")); + } + + @Test + public void testParseFieldTypesNullAndEmptyInputs() { + Assert.assertTrue(JacksonUtils.parseFieldTypes(null).isEmpty()); + Assert.assertTrue(JacksonUtils.parseFieldTypes(new ArrayList()).isEmpty()); + Map row = new LinkedHashMap<>(); + row.put("k", "v"); + Assert.assertFalse(JacksonUtils.parseFieldTypes(Collections.singletonList(row)).isEmpty()); + } + + @Test + public void testParseFieldTypesScalarTypeDetection() { + Map row = new LinkedHashMap<>(); + row.put("bool", Boolean.TRUE); + row.put("byte", (byte) 1); + row.put("int", 1); + row.put("long", 1L); + row.put("bigint", new java.math.BigInteger("9")); + row.put("dbl", 1.5d); + row.put("time", new java.sql.Time(0)); + row.put("ts", new java.sql.Timestamp(0)); + row.put("ldt", LocalDateTime.of(2026, 1, 1, 0, 0)); + row.put("date", new java.util.Date(0)); + row.put("ld", java.time.LocalDate.of(2026, 1, 1)); + row.put("str", "s"); + + List params = JacksonUtils.parseFieldTypes(row); + Assert.assertEquals(row.size(), params.size()); + Map byName = new HashMap<>(); + for (com.cs.common.dto.OutParam param : params) { + byName.put(param.getName(), param.getType()); + } + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.BOOLEAN, byName.get("bool")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.BOOLEAN, byName.get("byte")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.LONG, byName.get("int")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.LONG, byName.get("long")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.LONG, byName.get("bigint")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.DOUBLE, byName.get("dbl")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.TIME, byName.get("time")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.TIME, byName.get("ts")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.TIME, byName.get("ldt")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.DATE, byName.get("date")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.DATE, byName.get("ld")); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.STRING, byName.get("str")); + } + + @Test + public void testParseFieldTypesNestedObjectBecomesChildren() { + Map inner = new LinkedHashMap<>(); + inner.put("inner_field", 1); + Map row = new LinkedHashMap<>(); + row.put("outer", inner); + + List params = JacksonUtils.parseFieldTypes(row); + Assert.assertEquals(1, params.size()); + com.cs.common.dto.OutParam outer = params.get(0); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.OBJECT, outer.getType()); + Assert.assertEquals(Boolean.FALSE, outer.getIsArray()); + Assert.assertEquals(1, outer.getChildren().size()); + Assert.assertEquals("inner_field", outer.getChildren().get(0).getName()); + } + + @Test + public void testParseFieldTypesArraysTakeTypeOfFirstElement() { + Map row = new LinkedHashMap<>(); + row.put("ids", Arrays.asList(1, 2)); + row.put("empty", new ArrayList()); + + List params = JacksonUtils.parseFieldTypes(row); + Map byName = new HashMap<>(); + for (com.cs.common.dto.OutParam param : params) { + byName.put(param.getName(), param); + } + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.LONG, byName.get("ids").getType()); + Assert.assertEquals(Boolean.TRUE, byName.get("ids").getIsArray()); + // empty arrays have no element type at all + Assert.assertNull(byName.get("empty").getType()); + } + + @Test + public void testParseFieldTypesDeduplicatesColumnNames() { + Map first = new LinkedHashMap<>(); + first.put("name", "a"); + Map second = new LinkedHashMap<>(); + second.put("name", "b"); + + List params = + JacksonUtils.parseFieldTypes(Arrays.asList(first, second)); + Assert.assertEquals(1, params.size()); + } + + @Test + public void testParseFieldTypesListInsideListInsideList() { + Map row = new LinkedHashMap<>(); + row.put("deep", 1); + List innerList = new ArrayList<>(); + innerList.add(row); + List outerList = new ArrayList<>(); + outerList.add(innerList); + + List params = JacksonUtils.parseFieldTypes( + Collections.singletonList(outerList)); + Assert.assertEquals(1, params.size()); + Assert.assertEquals("deep", params.get(0).getName()); + } + + @Test + public void testParseFiledTypesFillsNullTypeAsString() { + Map row = new LinkedHashMap<>(); + row.put("empty", new ArrayList()); + + List params = JacksonUtils.parseFiledTypesAndFillNullAsString(row); + Assert.assertEquals(com.cs.common.enums.ParamTypeEnum.STRING, params.get(0).getType()); + } + + @Test + public void testToJsonStrWithTimeDateTimestampAndDecimalFormats() { + long millis = java.time.ZonedDateTime.of(2026, 8, 25, 10, 15, 30, 0, + java.time.ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli(); + + Map formatMap = new HashMap<>(); + formatMap.put(DataTypeFormatEnum.TIME, "HH:mm:ss"); + formatMap.put(DataTypeFormatEnum.DATE, "yyyy/MM/dd"); + formatMap.put(DataTypeFormatEnum.TIMESTAMP, "yyyy/MM/dd HH:mm:ss"); + formatMap.put(DataTypeFormatEnum.BIG_DECIMAL, "2"); + + Map result = new LinkedHashMap<>(); + result.put("t", new java.sql.Time(millis)); + result.put("d", new java.sql.Date(millis)); + result.put("ts", new java.sql.Timestamp(millis)); + result.put("num", new java.math.BigDecimal("1.5")); + + String json = JacksonUtils.toJsonStr(result, formatMap); + Assert.assertEquals( + "{\"t\":\"10:15:30\",\"d\":\"2026/08/25\",\"ts\":\"2026/08/25 10:15:30\",\"num\":1.50}", + json); + } + + @Test + public void testToJsonStrWithDefaultFormats() { + long millis = java.time.ZonedDateTime.of(2026, 8, 25, 10, 15, 30, 0, + java.time.ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli(); + Map result = new LinkedHashMap<>(); + result.put("ts", new java.sql.Timestamp(millis)); + // null format map: default pattern yyyy-MM-dd HH:mm:ss applies + Assert.assertEquals("{\"ts\":\"2026-08-25 10:15:30\"}", + JacksonUtils.toJsonStr(result, null)); + } + + @Test + public void testGetTimezoneFallsBackToDefaultWithoutConfig() { + Assert.assertEquals("Asia/Shanghai", JacksonUtils.getTimezone()); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskJobEngineStreamQueryTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskJobEngineStreamQueryTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskJobEngineStreamQueryTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskJobEngineStreamQueryTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskJobEngineTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskJobEngineTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskJobEngineTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskJobEngineTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskOutputPlanTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskOutputPlanTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskOutputPlanTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskOutputPlanTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskServiceTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskServiceTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskSinkRegistryWiringTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskSinkRegistryWiringTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskSinkRegistryWiringTest.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskSinkRegistryWiringTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/datatask/DataTaskTestSupport.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskTestSupport.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/datatask/DataTaskTestSupport.java rename to datapoly-test/src/test/java/com/cs/core/datatask/DataTaskTestSupport.java diff --git a/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskWorkerTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskWorkerTest.java new file mode 100644 index 0000000..68e774a --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskWorkerTest.java @@ -0,0 +1,106 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.datatask; + +import org.junit.Test; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class DataTaskWorkerTest { + + /** Engine fake: only the three entry points the worker touches. */ + private static class StubEngine extends DataTaskJobEngine { + final Queue claims = new ConcurrentLinkedQueue<>(); + final List ran = new java.util.concurrent.CopyOnWriteArrayList<>(); + final List reaped = new java.util.concurrent.CopyOnWriteArrayList<>(); + final AtomicInteger claimFailures = new AtomicInteger(); + volatile boolean failNextClaim; + + @Override + public Long claimNext(String workerAddr) { + if (failNextClaim) { + failNextClaim = false; + claimFailures.incrementAndGet(); + throw new IllegalStateException("claim exploded"); + } + return claims.poll(); + } + + @Override + public void run(Long jobId) { + ran.add(jobId); + } + + @Override + public void reapLost(String message) { + reaped.add(message); + } + } + + private static void awaitTrue(java.util.function.BooleanSupplier condition, long timeoutMs) + throws InterruptedException { + long deadline = System.currentTimeMillis() + timeoutMs; + while (!condition.getAsBoolean()) { + if (System.currentTimeMillis() > deadline) { + fail("condition not met within " + timeoutMs + "ms"); + } + TimeUnit.MILLISECONDS.sleep(20); + } + } + + @Test + public void testPollTickRunsClaimedJobsAndStopsOnEmptyQueue() throws Exception { + StubEngine engine = new StubEngine(); + Collections.addAll(engine.claims, 101L, 102L); + DataTaskWorker worker = new DataTaskWorker(2, "reap-msg"); + DataTaskTestSupport.setField(worker, "engine", engine); + try { + worker.pollTick(); + awaitTrue(() -> engine.ran.containsAll(java.util.Arrays.asList(101L, 102L)), 5000); + // queue exhausted: nothing else runs + assertEquals(2, engine.ran.size()); + } finally { + worker.shutdown(); + } + } + + @Test + public void testPollTickSurvivesClaimFailure() throws Exception { + StubEngine engine = new StubEngine(); + engine.failNextClaim = true; + DataTaskWorker worker = new DataTaskWorker(1, "reap-msg"); + DataTaskTestSupport.setField(worker, "engine", engine); + try { + worker.pollTick(); + assertEquals(1, engine.claimFailures.get()); + assertTrue(engine.ran.isEmpty()); + + // worker stays usable: a later tick with a claim runs the job + engine.claims.add(201L); + worker.pollTick(); + awaitTrue(() -> engine.ran.contains(201L), 5000); + } finally { + worker.shutdown(); + } + } + + @Test + public void testReapTickDelegatesWithConfiguredMessage() { + StubEngine engine = new StubEngine(); + DataTaskWorker worker = new DataTaskWorker(1, "lease expired"); + DataTaskTestSupport.setField(worker, "engine", engine); + try { + worker.reapTick(); + assertEquals(Collections.singletonList("lease expired"), engine.reaped); + } finally { + worker.shutdown(); + } + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/driver/DriverLoadServiceTest.java b/datapoly-test/src/test/java/com/cs/core/driver/DriverLoadServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/driver/DriverLoadServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/driver/DriverLoadServiceTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/exec/ApiExecuteServiceTest.java b/datapoly-test/src/test/java/com/cs/core/exec/ApiExecuteServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/exec/ApiExecuteServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/exec/ApiExecuteServiceTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/exec/ExecutorMetadataCacheTest.java b/datapoly-test/src/test/java/com/cs/core/exec/ExecutorMetadataCacheTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/exec/ExecutorMetadataCacheTest.java rename to datapoly-test/src/test/java/com/cs/core/exec/ExecutorMetadataCacheTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/exec/engine/impl/ScriptSandboxConfigurationTest.java b/datapoly-test/src/test/java/com/cs/core/exec/engine/impl/ScriptSandboxConfigurationTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/exec/engine/impl/ScriptSandboxConfigurationTest.java rename to datapoly-test/src/test/java/com/cs/core/exec/engine/impl/ScriptSandboxConfigurationTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractorTest.java b/datapoly-test/src/test/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractorTest.java new file mode 100644 index 0000000..a2039fa --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/exec/extractor/FormHttpRequestBodyExtractorTest.java @@ -0,0 +1,94 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.exec.extractor; + +import org.junit.Test; +import org.springframework.http.MediaType; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static org.junit.Assert.*; + +public class FormHttpRequestBodyExtractorTest { + + private ServletInputStream stream(String body) { + ByteArrayInputStream bytes = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + return new ServletInputStream() { + @Override + public int read() { + return bytes.read(); + } + + @Override + public boolean isFinished() { + return bytes.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + + @Test + public void testSupportFormUrlencodedAndUnknownTypes() { + FormHttpRequestBodyExtractor extractor = new FormHttpRequestBodyExtractor(); + assertTrue(extractor.support(MediaType.APPLICATION_FORM_URLENCODED)); + // multipart types are explicitly skipped, but null falls back to supported + assertFalse(extractor.support(MediaType.MULTIPART_FORM_DATA)); + assertFalse(extractor.support(MediaType.MULTIPART_MIXED)); + assertFalse(extractor.support(MediaType.APPLICATION_JSON)); + assertTrue(extractor.support(null)); + } + + @Test + @SuppressWarnings("unchecked") + public void testReadPlainPairs() { + Map result = new FormHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("a=1&b=x")); + assertEquals(java.util.Arrays.asList("1"), (java.util.List) result.get("a")); + assertEquals(java.util.Arrays.asList("x"), (java.util.List) result.get("b")); + } + + @Test + @SuppressWarnings("unchecked") + public void testCommaSeparatedValuesSplitIntoList() { + Map result = new FormHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("tags=x,y,z")); + assertEquals(java.util.Arrays.asList("x", "y", "z"), (java.util.List) result.get("tags")); + } + + @Test + @SuppressWarnings("unchecked") + public void testValuelessKeyMapsToNullEntry() { + Map result = new FormHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("flag&k=v")); + java.util.List flag = (java.util.List) result.get("flag"); + assertEquals(1, flag.size()); + assertNull(flag.get(0)); + assertEquals(java.util.Arrays.asList("v"), (java.util.List) result.get("k")); + } + + @Test + @SuppressWarnings("unchecked") + public void testUrlDecodingAppliedToNamesAndCommaSegmentsOnly() { + // Current behaviour: names are always URL-decoded, but simple values are stored raw — + // only comma-separated segments go through URLDecoder (inconsistency, see run report) + Map result = new FormHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("%E5%90%8D=%E5%80%BC,%E5%80%BC2")); + assertTrue(result.containsKey("名")); + assertEquals(java.util.Arrays.asList("值", "值2"), (java.util.List) result.get("名")); + + Map simple = new FormHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("k=%E5%80%BC")); + assertEquals(java.util.Arrays.asList("%E5%80%BC"), (java.util.List) simple.get("k")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractorTest.java b/datapoly-test/src/test/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractorTest.java new file mode 100644 index 0000000..6585c8c --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/exec/extractor/JsonHttpRequestBodyExtractorTest.java @@ -0,0 +1,79 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.exec.extractor; + +import org.junit.Test; +import org.springframework.http.MediaType; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static org.junit.Assert.*; + +public class JsonHttpRequestBodyExtractorTest { + + private ServletInputStream stream(String body) { + ByteArrayInputStream bytes = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + return new ServletInputStream() { + @Override + public int read() { + return bytes.read(); + } + + @Override + public boolean isFinished() { + return bytes.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + + @Test + public void testSupportOnlyJson() { + JsonHttpRequestBodyExtractor extractor = new JsonHttpRequestBodyExtractor(); + assertTrue(extractor.support(MediaType.APPLICATION_JSON)); + assertTrue(extractor.support(MediaType.parseMediaType("application/json;charset=UTF-8"))); + assertFalse(extractor.support(MediaType.TEXT_PLAIN)); + assertFalse(extractor.support(MediaType.APPLICATION_FORM_URLENCODED)); + assertFalse(extractor.support(null)); + } + + @Test + public void testReadFlatObject() { + Map result = new JsonHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("{\"a\":1,\"b\":\"x\",\"c\":true}")); + assertEquals(3, result.size()); + // USE_LONG_FOR_INTS: integer literals become Long + assertEquals(Long.valueOf(1), result.get("a")); + assertEquals("x", result.get("b")); + assertEquals(Boolean.TRUE, result.get("c")); + } + + @Test + public void testReadNestedStructures() { + Map result = new JsonHttpRequestBodyExtractor().read( + StandardCharsets.UTF_8, stream("{\"arr\":[1,2],\"obj\":{\"k\":\"v\"}}")); + assertEquals(java.util.Arrays.asList(1L, 2L), result.get("arr")); + @SuppressWarnings("unchecked") + Map nested = (Map) result.get("obj"); + assertEquals("v", nested.get("k")); + } + + @Test + public void testReadBlankAndScalarBodiesYieldEmptyMap() { + JsonHttpRequestBodyExtractor extractor = new JsonHttpRequestBodyExtractor(); + assertTrue(extractor.read(StandardCharsets.UTF_8, stream(" ")).isEmpty()); + assertTrue(extractor.read(StandardCharsets.UTF_8, stream("42")).isEmpty()); + assertTrue(extractor.read(StandardCharsets.UTF_8, stream("[1,2]")).isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/exec/logger/DebugExecuteLoggerTest.java b/datapoly-test/src/test/java/com/cs/core/exec/logger/DebugExecuteLoggerTest.java new file mode 100644 index 0000000..fd18219 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/exec/logger/DebugExecuteLoggerTest.java @@ -0,0 +1,49 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.exec.logger; + +import com.cs.common.service.DisplayRecord; +import org.junit.After; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +public class DebugExecuteLoggerTest { + + @After + public void cleanUp() { + DebugExecuteLogger.clear(); + } + + @Test + public void testWithoutInitAddsAreIgnored() { + DebugExecuteLogger.add("sql", Collections.emptyList(), 1L); + DebugExecuteLogger.add("text"); + assertTrue(DebugExecuteLogger.get().isEmpty()); + } + + @Test + public void testRecordsSqlAndScriptEntriesInOrder() { + DebugExecuteLogger.init(); + DebugExecuteLogger.add("SELECT 1", Arrays.asList("p1", 2), 42L); + DebugExecuteLogger.add("script output"); + assertEquals(2, DebugExecuteLogger.get().size()); + DisplayRecord first = DebugExecuteLogger.get().get(0); + assertTrue(first instanceof com.cs.core.dto.ExecuteSqlRecord); + DisplayRecord second = DebugExecuteLogger.get().get(1); + assertTrue(second instanceof com.cs.core.dto.ScripDebugRecord); + } + + @Test + public void testClearResetsThreadLocal() { + DebugExecuteLogger.init(); + DebugExecuteLogger.add("SELECT 1", Collections.emptyList(), 1L); + DebugExecuteLogger.clear(); + assertTrue(DebugExecuteLogger.get().isEmpty()); + // after clear, adds are ignored again until re-init + DebugExecuteLogger.add("SELECT 2", Collections.emptyList(), 1L); + assertTrue(DebugExecuteLogger.get().isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/exec/logger/RequestParamLoggerTest.java b/datapoly-test/src/test/java/com/cs/core/exec/logger/RequestParamLoggerTest.java new file mode 100644 index 0000000..4735122 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/exec/logger/RequestParamLoggerTest.java @@ -0,0 +1,22 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.exec.logger; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class RequestParamLoggerTest { + + @Test + public void testGetAndClearReturnsOnce() { + assertNull(RequestParamLogger.getAndClear()); + Map params = new HashMap<>(); + params.put("k", "v"); + RequestParamLogger.set(params); + assertSame(params, RequestParamLogger.getAndClear()); + assertNull("second read after clear must be null", RequestParamLogger.getAndClear()); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/extension/ApiAssignmentPostProcessorsTest.java b/datapoly-test/src/test/java/com/cs/core/extension/ApiAssignmentPostProcessorsTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/extension/ApiAssignmentPostProcessorsTest.java rename to datapoly-test/src/test/java/com/cs/core/extension/ApiAssignmentPostProcessorsTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/extension/TestSpiFixtureProcessor.java b/datapoly-test/src/test/java/com/cs/core/extension/TestSpiFixtureProcessor.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/extension/TestSpiFixtureProcessor.java rename to datapoly-test/src/test/java/com/cs/core/extension/TestSpiFixtureProcessor.java diff --git a/datapoly-core/src/test/java/com/cs/core/gateway/FirewallFilterServiceTest.java b/datapoly-test/src/test/java/com/cs/core/gateway/FirewallFilterServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/gateway/FirewallFilterServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/gateway/FirewallFilterServiceTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/service/ApiAssignmentServiceTest.java b/datapoly-test/src/test/java/com/cs/core/service/ApiAssignmentServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/service/ApiAssignmentServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/service/ApiAssignmentServiceTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/service/ApiGroupServiceTest.java b/datapoly-test/src/test/java/com/cs/core/service/ApiGroupServiceTest.java new file mode 100644 index 0000000..03b3764 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/service/ApiGroupServiceTest.java @@ -0,0 +1,216 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.service; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.dto.PageResult; +import com.cs.common.exception.CommonException; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.core.dto.EntitySearchRequest; +import com.cs.persistence.dao.ApiAssignmentDao; +import com.cs.persistence.dao.ApiGroupDao; +import com.cs.persistence.dao.ApiOnlineDao; +import com.cs.persistence.dao.AppClientDao; +import com.cs.persistence.entity.ApiGroupEntity; +import com.cs.persistence.mapper.ApiGroupMapper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.dao.DuplicateKeyException; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class ApiGroupServiceTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + return result; + } + Class rt = m.getReturnType(); + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + } + + private Recorder groupRecorder; + private Recorder assignmentRecorder; + private Recorder onlineRecorder; + private Recorder clientRecorder; + private ApiGroupService service; + + @org.junit.BeforeClass + public static void initEntityMetadata() { + com.cs.persistence.PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.ApiGroupEntity.class); + } + + @Before + public void setUp() throws Exception { + groupRecorder = new Recorder(); + assignmentRecorder = new Recorder(); + onlineRecorder = new Recorder(); + clientRecorder = new Recorder(); + + ApiAssignmentDao assignmentDao = new ApiAssignmentDao(); + DataTaskTestSupport.setField(assignmentDao, "apiAssignmentMapper", assignmentRecorder.create( + com.cs.persistence.mapper.ApiAssignmentMapper.class)); + ApiOnlineDao onlineDao = new ApiOnlineDao(); + DataTaskTestSupport.setField(onlineDao, "apiOnlineMapper", + onlineRecorder.create(com.cs.persistence.mapper.ApiOnlineMapper.class)); + AppClientDao appClientDao = new AppClientDao(); + DataTaskTestSupport.setField(appClientDao, "appClientMapper", + clientRecorder.create(com.cs.persistence.mapper.AppClientMapper.class)); + DataTaskTestSupport.setField(appClientDao, "clientAuthMapper", + clientRecorder.create(com.cs.persistence.mapper.ClientGroupMapper.class)); + + service = new ApiGroupService(); + ApiGroupDao groupDao = new ApiGroupDao(); + DataTaskTestSupport.setField(groupDao, "apiGroupMapper", groupRecorder.create(ApiGroupMapper.class)); + DataTaskTestSupport.setField(service, "apiGroupDao", groupDao); + DataTaskTestSupport.setField(service, "apiAssignmentDao", assignmentDao); + DataTaskTestSupport.setField(service, "appClientDao", appClientDao); + + installOnlineDao(onlineDao); + com.github.pagehelper.PageHelper.clearPage(); + } + + @After + public void tearDown() throws Exception { + com.github.pagehelper.PageHelper.clearPage(); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, null); + } + + private void installOnlineDao(final ApiOnlineDao onlineDao) throws Exception { + Object beanFactory = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ConfigurableListableBeanFactory.class}, + (p, m, a) -> { + if ("getBean".equals(m.getName()) && a != null && a.length == 1 + && a[0] == ApiOnlineDao.class) { + return onlineDao; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, beanFactory); + } + + @Test + public void testCreateGroupTranslatesDuplicateKey() { + service.createGroup("g"); + assertEquals(1, groupRecorder.count("insert")); + + groupRecorder.stub("insert", new DuplicateKeyException("dup")); + try { + service.createGroup("g"); + fail("duplicate name must translate to CommonException"); + } catch (CommonException expected) { + } + } + + @Test + public void testUpdateGroupRequiresExistingRow() { + groupRecorder.stub("selectById", (Object) null); + try { + service.updateGroup(5L, "new-name"); + fail("missing group must throw"); + } catch (CommonException expected) { + } + + ApiGroupEntity entity = ApiGroupEntity.builder().id(5L).name("old").build(); + groupRecorder.stub("selectById", entity); + service.updateGroup(5L, "new-name"); + assertEquals("new-name", entity.getName()); + assertEquals(1, groupRecorder.count("updateById")); + } + + @Test + public void testDeleteGroupGuards() { + try { + service.deleteGroup(1L); + fail("default group must not be deletable"); + } catch (CommonException expected) { + } + + assignmentRecorder.stub("selectCount", 1L); + try { + service.deleteGroup(5L); + fail("group in use by assignments must not be deletable"); + } catch (CommonException expected) { + } + assertEquals(0, groupRecorder.count("deleteById")); + + assignmentRecorder.stub("selectCount", 0L); + onlineRecorder.stub("selectCount", 2L); + try { + service.deleteGroup(5L); + fail("group in use by online APIs must not be deletable"); + } catch (CommonException expected) { + } + assertEquals(0, groupRecorder.count("deleteById")); + } + + @Test + public void testDeleteGroupHappyPathCascadesClientAuth() { + assignmentRecorder.stub("selectCount", 0L); + onlineRecorder.stub("selectCount", 0L); + service.deleteGroup(5L); + assertEquals(1, groupRecorder.count("deleteById")); + assertEquals(1, clientRecorder.count("delete")); + } + + @Test + public void testListAllWrapsPageHelperPagination() { + groupRecorder.stub("selectList", Arrays.asList(ApiGroupEntity.builder().id(1L).build())); + EntitySearchRequest request = new EntitySearchRequest(); + request.setPage(1); + request.setSize(10); + PageResult result = service.listAll(request); + assertEquals(1, result.getData().size()); + assertEquals(1, result.getPagination().getPage()); + assertEquals(10, result.getPagination().getSize()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/service/ApiModuleServiceTest.java b/datapoly-test/src/test/java/com/cs/core/service/ApiModuleServiceTest.java new file mode 100644 index 0000000..fa01b6f --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/service/ApiModuleServiceTest.java @@ -0,0 +1,220 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.service; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.dto.PageResult; +import com.cs.common.exception.CommonException; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.core.dto.ApiModuleAssignments; +import com.cs.core.dto.EntitySearchRequest; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.persistence.dao.ApiAssignmentDao; +import com.cs.persistence.dao.ApiModuleDao; +import com.cs.persistence.dao.ApiOnlineDao; +import com.cs.persistence.entity.ApiModuleEntity; +import com.cs.persistence.entity.ModuleAssignmentEntity; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.dao.DuplicateKeyException; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class ApiModuleServiceTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + return result; + } + Class rt = m.getReturnType(); + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + } + + private Recorder moduleRecorder; + private Recorder assignmentRecorder; + private Recorder onlineRecorder; + private ApiModuleService service; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.ApiAssignmentEntity.class); + } + + @Before + public void setUp() throws Exception { + moduleRecorder = new Recorder(); + assignmentRecorder = new Recorder(); + onlineRecorder = new Recorder(); + + ApiModuleDao moduleDao = new ApiModuleDao(); + DataTaskTestSupport.setField(moduleDao, "apiModuleMapper", + moduleRecorder.create(com.cs.persistence.mapper.ApiModuleMapper.class)); + ApiAssignmentDao assignmentDao = new ApiAssignmentDao(); + DataTaskTestSupport.setField(assignmentDao, "apiAssignmentMapper", + assignmentRecorder.create(com.cs.persistence.mapper.ApiAssignmentMapper.class)); + + service = new ApiModuleService(); + DataTaskTestSupport.setField(service, "apiModuleDao", moduleDao); + DataTaskTestSupport.setField(service, "apiAssignmentDao", assignmentDao); + + ApiOnlineDao onlineDao = new ApiOnlineDao(); + DataTaskTestSupport.setField(onlineDao, "apiOnlineMapper", + onlineRecorder.create(com.cs.persistence.mapper.ApiOnlineMapper.class)); + Object beanFactory = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ConfigurableListableBeanFactory.class}, + (p, m, a) -> { + if ("getBean".equals(m.getName()) && a != null && a.length == 1 + && a[0] == ApiOnlineDao.class) { + return onlineDao; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, beanFactory); + com.github.pagehelper.PageHelper.clearPage(); + } + + @After + public void tearDown() throws Exception { + com.github.pagehelper.PageHelper.clearPage(); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, null); + } + + @Test + public void testCreateAndUpdateModule() { + service.createModule("m"); + assertEquals(1, moduleRecorder.count("insert")); + + moduleRecorder.stub("insert", new DuplicateKeyException("dup")); + try { + service.createModule("m"); + fail("duplicate module name must translate to CommonException"); + } catch (CommonException expected) { + } + + ApiModuleEntity entity = ApiModuleEntity.builder().id(5L).name("old").build(); + moduleRecorder.stub("selectById", entity); + service.updateModule(5L, "new"); + assertEquals("new", entity.getName()); + assertEquals(1, moduleRecorder.count("updateById")); + + moduleRecorder.stub("selectById", (Object) null); + try { + service.updateModule(6L, "new"); + fail("missing module must throw"); + } catch (CommonException expected) { + } + } + + @Test + public void testDeleteModuleGuards() { + assignmentRecorder.stub("selectCount", 1L); + try { + service.deleteModule(5L); + fail("module in use by assignments must not be deletable"); + } catch (CommonException expected) { + } + assertEquals(0, moduleRecorder.count("deleteById")); + + assignmentRecorder.stub("selectCount", 0L); + onlineRecorder.stub("selectCount", 3L); + try { + service.deleteModule(5L); + fail("module in use by online APIs must not be deletable"); + } catch (CommonException expected) { + } + assertEquals(0, moduleRecorder.count("deleteById")); + + onlineRecorder.stub("selectCount", 0L); + assignmentRecorder.stub("selectCount", 0L); + service.deleteModule(5L); + assertEquals(1, moduleRecorder.count("deleteById")); + } + + @Test + public void testListAllWrapsPageHelperPagination() { + moduleRecorder.stub("selectList", Arrays.asList(ApiModuleEntity.builder().id(1L).build())); + EntitySearchRequest request = new EntitySearchRequest(); + request.setPage(1); + request.setSize(10); + PageResult result = service.listAll(request); + assertEquals(1, result.getData().size()); + assertEquals(1, result.getPagination().getPage()); + } + + @Test + public void testModuleTreeGroupsAssignmentsByModule() { + ModuleAssignmentEntity first = new ModuleAssignmentEntity(); + first.setModuleId(2L); + first.setModuleName("mod-2"); + first.setAssigmentId(10L); + first.setAssigmentName("api-a"); + first.setGroupId(30L); + ModuleAssignmentEntity second = new ModuleAssignmentEntity(); + second.setModuleId(2L); + second.setModuleName("mod-2"); + second.setAssigmentId(11L); + second.setAssigmentName("api-b"); + second.setGroupId(31L); + + assignmentRecorder.stub("getModuleAssignments", Arrays.asList(first, second)); + List tree = service.moduleTree(30L); + assertEquals(1, tree.size()); + ApiModuleAssignments module = tree.get(0); + assertEquals(Long.valueOf(2L), module.getId()); + assertEquals("mod-2", module.getName()); + assertEquals(2, module.getChildren().size()); + assertEquals(Boolean.TRUE, module.getChildren().get(0).getSelected()); + assertEquals(Boolean.FALSE, module.getChildren().get(1).getSelected()); + assertEquals("[11]api-b", module.getChildren().get(1).getName()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/service/SystemParamServiceTest.java b/datapoly-test/src/test/java/com/cs/core/service/SystemParamServiceTest.java new file mode 100644 index 0000000..803ca1d --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/service/SystemParamServiceTest.java @@ -0,0 +1,132 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.service; + +import com.cs.common.enums.ParamTypeEnum; +import com.cs.common.exception.CommonException; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.dao.SystemParamDao; +import com.cs.persistence.entity.SystemParamEntity; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class SystemParamServiceTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + return result; + } + Class rt = m.getReturnType(); + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + } + + private Recorder recorder; + private SystemParamService service; + + private SystemParamEntity param(ParamTypeEnum type, String value) { + return SystemParamEntity.builder().paramType(type).paramValue(value).build(); + } + + @Before + public void setUp() { + recorder = new Recorder(); + SystemParamDao systemParamDao = new SystemParamDao(); + DataTaskTestSupport.setField(systemParamDao, "systemParamMapper", + recorder.create(com.cs.persistence.mapper.SystemParamMapper.class)); + service = new SystemParamService(); + DataTaskTestSupport.setField(service, "systemParamDao", systemParamDao); + } + + @Test + public void testGetByParamKeyConvertsValue() { + recorder.stub("selectOne", param(ParamTypeEnum.LONG, "42"), param(ParamTypeEnum.BOOLEAN, "true")); + assertEquals(Long.valueOf(42L), service.getByParamKey("k1")); + assertEquals(Boolean.TRUE, service.getByParamKey("k2")); + } + + @Test + public void testGetByParamKeyMissingThrows() { + recorder.stub("selectOne", (Object) null); + try { + service.getByParamKey("missing"); + fail("missing key must throw"); + } catch (CommonException expected) { + } + } + + @Test + public void testUpdateByParamKeyConvertsAndPersists() { + // service lookup + dao re-lookup both resolve via selectOne + recorder.stub("selectOne", + param(ParamTypeEnum.BOOLEAN, "false"), param(ParamTypeEnum.BOOLEAN, "false")); + service.updateByParamKey("k", "true"); + assertEquals(1, recorder.count("updateById")); + SystemParamEntity updated = (SystemParamEntity) recorder.calls.get("updateById").get(0)[0]; + assertEquals("true", updated.getParamValue()); + } + + @Test + public void testUpdateByParamKeyRejectsBlankConvertibleValue() { + recorder.stub("selectOne", param(ParamTypeEnum.LONG, "1"), (Object) null); + try { + service.updateByParamKey("k", " "); + fail("blank numeric value must be rejected"); + } catch (CommonException expected) { + } + try { + service.updateByParamKey("k", "1"); + fail("missing key must throw on update"); + } catch (CommonException expected) { + } + assertEquals(0, recorder.count("updateById")); + } + + @Test + public void testGetIntByParamKeyDefaults() { + recorder.stub("selectOne", (Object) null, param(ParamTypeEnum.LONG, "9"), + param(ParamTypeEnum.LONG, "not-a-number"), param(ParamTypeEnum.STRING, "9")); + assertEquals(7, service.getIntByParamKey("missing", 7).intValue()); + assertEquals(9, service.getIntByParamKey("k", 7).intValue()); + assertEquals("unparsable value falls back to default", 7, service.getIntByParamKey("k", 7).intValue()); + assertEquals("non-LONG typed params fall back to default", 7, service.getIntByParamKey("k", 7).intValue()); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/service/SystemUserServiceTest.java b/datapoly-test/src/test/java/com/cs/core/service/SystemUserServiceTest.java similarity index 99% rename from datapoly-core/src/test/java/com/cs/core/service/SystemUserServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/service/SystemUserServiceTest.java index db698ea..1193da7 100644 --- a/datapoly-core/src/test/java/com/cs/core/service/SystemUserServiceTest.java +++ b/datapoly-test/src/test/java/com/cs/core/service/SystemUserServiceTest.java @@ -10,7 +10,7 @@ import com.cs.persistence.entity.SystemUserEntity; import org.junit.*; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicReference; diff --git a/datapoly-test/src/test/java/com/cs/core/servlet/AuthenticationFilterTest.java b/datapoly-test/src/test/java/com/cs/core/servlet/AuthenticationFilterTest.java new file mode 100644 index 0000000..d440e58 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/servlet/AuthenticationFilterTest.java @@ -0,0 +1,345 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.servlet; + +import com.cs.common.enums.HttpMethodEnum; +import com.cs.common.exception.CommonException; +import com.cs.common.exception.ResponseErrorCode; +import com.cs.common.service.FlowControlManger; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.core.exec.ExecutorMetadataCache; +import com.cs.core.executor.UnifyAlarmOpsService; +import com.cs.persistence.dao.ApiOnlineDao; +import com.cs.persistence.entity.AccessRecordEntity; +import com.cs.persistence.entity.ApiAssignmentEntity; +import com.cs.persistence.mapper.AccessRecordMapper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class AuthenticationFilterTest { + + private static class MapperRecorder { + final List inserts = new CopyOnWriteArrayList<>(); + final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + if ("insert".equals(m.getName())) { + inserts.add(a); + return 1; + } + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + return queue.poll(); + } + if (m.getReturnType() == int.class) { + return 0; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + void awaitInserts(int expected, long timeoutMs) throws InterruptedException { + long deadline = System.currentTimeMillis() + timeoutMs; + while (inserts.size() < expected) { + if (System.currentTimeMillis() > deadline) { + fail("expected " + expected + " access records, saw " + inserts.size()); + } + TimeUnit.MILLISECONDS.sleep(20); + } + } + } + + /** Records token verification results without touching the real token pipeline. */ + private static class StubTokenService extends ClientTokenService { + String appKeyForToken = "client-1"; + boolean groupAllowed = true; + + @Override + public String verifyTokenAndGetAppKey(String tokenStr) { + return appKeyForToken; + } + + @Override + public boolean verifyAuthGroup(String clientId, Long groupId) { + return groupAllowed; + } + } + + private static class StubAlarmService extends UnifyAlarmOpsService { + final List> alarms = new CopyOnWriteArrayList<>(); + + @Override + public void triggerAlarm(Map dataModel) { + alarms.add(dataModel); + } + } + + private AuthenticationFilter filter; + private MapperRecorder onlineRecorder; + private MapperRecorder accessRecorder; + private StubTokenService tokenService; + private StubAlarmService alarmService; + private final AtomicInteger flowChecks = new AtomicInteger(); + private volatile boolean flowAllowed = true; + private final AtomicInteger chainCalls = new AtomicInteger(); + private volatile RuntimeException chainFailure; + + private StringWriter responseBody; + private HttpServletResponse response; + + @Before + public void setUp() { + onlineRecorder = new MapperRecorder(); + accessRecorder = new MapperRecorder(); + tokenService = new StubTokenService(); + alarmService = new StubAlarmService(); + + ApiOnlineDao apiOnlineDao = new ApiOnlineDao(); + DataTaskTestSupport.setField(apiOnlineDao, "apiOnlineMapper", + onlineRecorder.create(com.cs.persistence.mapper.ApiOnlineMapper.class)); + + ExecutorMetadataCache metadataCache = new ExecutorMetadataCache(); + metadataCache.init(); + + FlowControlManger flowControl = (FlowControlManger) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{FlowControlManger.class}, + (p, m, a) -> { + if ("checkFlowControl".equals(m.getName())) { + flowChecks.incrementAndGet(); + return flowAllowed; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + + filter = new AuthenticationFilter(); + DataTaskTestSupport.setField(filter, "apiOnlineDao", apiOnlineDao); + DataTaskTestSupport.setField(filter, "executorMetadataCache", metadataCache); + DataTaskTestSupport.setField(filter, "flowControlManger", flowControl); + DataTaskTestSupport.setField(filter, "clientTokenService", tokenService); + DataTaskTestSupport.setField(filter, "accessRecordMapper", accessRecorder.create(AccessRecordMapper.class)); + DataTaskTestSupport.setField(filter, "unifyAlarmOpsService", alarmService); + + responseBody = new StringWriter(); + response = (HttpServletResponse) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{HttpServletResponse.class}, + (p, m, a) -> { + switch (m.getName()) { + case "getWriter": + return new PrintWriter(responseBody, true); + case "getStatus": + return statusRef[0]; + case "setStatus": + statusRef[0] = (Integer) a[0]; + return null; + default: + return null; + } + }); + } + + private final Integer[] statusRef = new Integer[]{200}; + + @After + public void tearDown() { + RequestContextHolder.resetRequestAttributes(); + } + + private HttpServletRequest request(String uri, String method, Map headers) { + HttpServletRequest request = (HttpServletRequest) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{HttpServletRequest.class}, + (proxy, m, a) -> { + switch (m.getName()) { + case "getRequestURI": + return uri; + case "getMethod": + return method; + case "getHeader": + return headers.get(String.valueOf(a[0])); + case "getRemoteAddr": + return "10.0.0.1"; + default: + return null; + } + }); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + return request; + } + + private FilterChain chain() { + return (request, response) -> { + chainCalls.incrementAndGet(); + if (chainFailure != null) { + throw chainFailure; + } + }; + } + + /** ApiOnlineDao.getByUk maps an ApiOnlineEntity row; semantics ride in the content JSON + columns. */ + private com.cs.persistence.entity.ApiOnlineEntity entity(boolean open, boolean flowStatus, boolean alarm) { + com.cs.persistence.entity.ApiOnlineEntity entity = new com.cs.persistence.entity.ApiOnlineEntity(); + entity.setId(7L); + entity.setContent("{\"id\":7,\"method\":\"GET\",\"path\":\"ping\",\"name\":\"ping-api\"}"); + entity.setGroupId(5L); + entity.setOpen(open); + entity.setFlowStatus(flowStatus); + entity.setAlarm(alarm); + return entity; + } + + @Test + public void testUnknownPathYields404WithoutChainCall() throws Exception { + onlineRecorder.stub("selectOne", (Object) null); + filter.doFilter(request("/api/missing", "GET", new HashMap()), + response, chain()); + assertEquals(404, statusRef[0].intValue()); + assertEquals(0, chainCalls.get()); + assertTrue(responseBody.toString().contains("ERROR_PATH_NOT_EXISTS")); + } + + @Test + public void testOpenApiPassesThroughAndRecordsAccess() throws Exception { + onlineRecorder.stub("selectOne", entity(true, false, false)); + filter.doFilter(request("/api/ping", "GET", new HashMap()), response, chain()); + assertEquals(1, chainCalls.get()); + + accessRecorder.awaitInserts(1, 5000); + AccessRecordEntity record = (AccessRecordEntity) accessRecorder.inserts.get(0)[0]; + assertEquals("/api/ping", record.getPath()); + assertEquals(Integer.valueOf(200), record.getStatus()); + assertEquals(Long.valueOf(7L), record.getApiId()); + assertNull(record.getClientKey()); + assertTrue("no alarm for successful open api", alarmService.alarms.isEmpty()); + } + + @Test + public void testClosedApiRequiresBearerToken() throws Exception { + onlineRecorder.stub("selectOne", entity(false, false, false)); + filter.doFilter(request("/api/ping", "GET", new HashMap()), response, chain()); + assertEquals(401, statusRef[0].intValue()); + assertEquals(0, chainCalls.get()); + + accessRecorder.awaitInserts(1, 5000); + AccessRecordEntity record = (AccessRecordEntity) accessRecorder.inserts.get(0)[0]; + assertEquals(Integer.valueOf(401), record.getStatus()); + assertNotNull(record.getException()); + } + + @Test + public void testClosedApiWithTokenAndGroupPasses() throws Exception { + onlineRecorder.stub("selectOne", entity(false, false, false)); + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer token-1"); + filter.doFilter(request("/api/ping", "GET", headers), response, chain()); + assertEquals(1, chainCalls.get()); + + accessRecorder.awaitInserts(1, 5000); + AccessRecordEntity record = (AccessRecordEntity) accessRecorder.inserts.get(0)[0]; + assertEquals("client-1", record.getClientKey()); + assertEquals(Integer.valueOf(200), record.getStatus()); + } + + @Test + public void testClosedApiWithWrongGroupYields403() throws Exception { + onlineRecorder.stub("selectOne", entity(false, false, false)); + tokenService.groupAllowed = false; + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer token-1"); + filter.doFilter(request("/api/ping", "GET", headers), response, chain()); + assertEquals(403, statusRef[0].intValue()); + assertEquals(0, chainCalls.get()); + } + + @Test + public void testClosedApiWithInvalidTokenYields401() throws Exception { + onlineRecorder.stub("selectOne", entity(false, false, false)); + tokenService.appKeyForToken = null; + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer expired"); + filter.doFilter(request("/api/ping", "GET", headers), response, chain()); + assertEquals(401, statusRef[0].intValue()); + assertEquals(0, chainCalls.get()); + } + + @Test + public void testFlowControlGateConsultedOnlyWhenEnabled() throws Exception { + onlineRecorder.stub("selectOne", + entity(true, true, false), entity(true, true, false), entity(true, false, false)); + flowAllowed = false; + filter.doFilter(request("/api/flow", "GET", new HashMap()), response, chain()); + assertEquals(1, flowChecks.get()); + assertEquals(0, chainCalls.get()); + + flowAllowed = true; + filter.doFilter(request("/api/flow2", "GET", new HashMap()), response, chain()); + assertEquals(2, flowChecks.get()); + assertEquals(1, chainCalls.get()); + + filter.doFilter(request("/api/noflow", "GET", new HashMap()), response, chain()); + assertEquals("flow disabled apis skip the flow check", 2, flowChecks.get()); + assertEquals(2, chainCalls.get()); + } + + @Test + public void testBusinessExceptionFromChainMapsToHttpStatus() throws Exception { + onlineRecorder.stub("selectOne", entity(true, false, false)); + chainFailure = new CommonException(ResponseErrorCode.ERROR_INVALID_ARGUMENT, "bad input"); + filter.doFilter(request("/api/ping", "GET", new HashMap()), response, chain()); + assertEquals(ResponseErrorCode.ERROR_INVALID_ARGUMENT.getHttpStatus(), statusRef[0].intValue()); + assertEquals("chain ran once before the business exception surfaced", 1, chainCalls.get()); + } + + @Test + public void testFailingClosedApiWithAlarmTriggersAlarm() throws Exception { + onlineRecorder.stub("selectOne", entity(false, false, true)); + filter.doFilter(request("/api/ping", "GET", new HashMap()), response, chain()); + assertEquals(401, statusRef[0].intValue()); + + long deadline = System.currentTimeMillis() + 5000; + while (alarmService.alarms.isEmpty() && System.currentTimeMillis() < deadline) { + TimeUnit.MILLISECONDS.sleep(20); + } + assertEquals(1, alarmService.alarms.size()); + assertEquals("GET", alarmService.alarms.get(0).get("method")); + assertEquals("/api/ping", alarmService.alarms.get(0).get("path")); + } + + @Test + public void testUnknownHttpMethodFallsBackToGet() throws Exception { + onlineRecorder.stub("selectOne", (Object) null, entity(true, false, false)); + filter.doFilter(request("/api/missing", "PROPFIND", new HashMap()), response, chain()); + assertEquals(404, statusRef[0].intValue()); + filter.doFilter(request("/api/ping2", "PROPFIND", new HashMap()), response, chain()); + assertEquals(1, chainCalls.get()); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/servlet/ClientTokenGuardTest.java b/datapoly-test/src/test/java/com/cs/core/servlet/ClientTokenGuardTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/servlet/ClientTokenGuardTest.java rename to datapoly-test/src/test/java/com/cs/core/servlet/ClientTokenGuardTest.java diff --git a/datapoly-core/src/test/java/com/cs/core/servlet/ClientTokenServiceTest.java b/datapoly-test/src/test/java/com/cs/core/servlet/ClientTokenServiceTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/servlet/ClientTokenServiceTest.java rename to datapoly-test/src/test/java/com/cs/core/servlet/ClientTokenServiceTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/util/AlarmModelUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/AlarmModelUtilsTest.java new file mode 100644 index 0000000..0e25d6a --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/AlarmModelUtilsTest.java @@ -0,0 +1,76 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import com.cs.common.enums.HttpMethodEnum; +import com.cs.persistence.entity.AccessRecordEntity; +import com.cs.persistence.entity.ApiAssignmentEntity; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +import static org.junit.Assert.*; + +public class AlarmModelUtilsTest { + + @Test + public void testExampleModelCoversAllFields() { + Map model = AlarmModelUtils.getExampleModel(); + assertEquals("/api/test/create", model.get("path")); + assertEquals("POST", model.get("method")); + assertEquals("application/json", model.get("contentType")); + assertEquals("127.0.0.1", model.get("ipAddr")); + assertTrue(model.containsKey("exception")); + } + + @Test + public void testBeforeTestAlarmAddsAccessTime() { + Map model = AlarmModelUtils.getExampleModel(); + AlarmModelUtils.setBeforeTestAlarm(model); + assertNotNull(model.get("accessTime")); + assertTrue(model.get("accessTime").matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")); + } + + @Test + public void testBusinessModelMapsEntityAndRecord() { + ApiAssignmentEntity api = new ApiAssignmentEntity(); + api.setMethod(HttpMethodEnum.POST); + api.setName("create-user"); + api.setContentType("application/json"); + api.setDescription("desc"); + api.setOpen(Boolean.TRUE); + + AccessRecordEntity record = new AccessRecordEntity(); + record.setPath("/api/user/create"); + record.setClientKey("client-1"); + record.setIpAddr("10.0.0.9"); + record.setUserAgent("ua"); + record.setException("boom"); + + long accessTimestamp = 1760000000000L; + Map model = AlarmModelUtils.getBusinessModel(api, record, accessTimestamp); + assertEquals("POST", model.get("method")); + assertEquals("create-user", model.get("name")); + assertEquals("true", model.get("open")); + assertEquals("client-1", model.get("clientKey")); + assertEquals("boom", model.get("exception")); + assertEquals(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(accessTimestamp)), + model.get("accessTime")); + } + + @Test + public void testBusinessModelNullablesBecomeEmptyStrings() { + ApiAssignmentEntity api = new ApiAssignmentEntity(); + api.setMethod(HttpMethodEnum.GET); + api.setOpen(Boolean.FALSE); + AccessRecordEntity record = new AccessRecordEntity(); + Map model = AlarmModelUtils.getBusinessModel(api, record, 0L); + assertEquals("", model.get("description")); + assertEquals("", model.get("clientKey")); + assertEquals("", model.get("ipAddr")); + assertEquals("", model.get("userAgent")); + assertEquals("", model.get("exception")); + assertEquals("false", model.get("open")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/util/ApiPathUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/ApiPathUtilsTest.java new file mode 100644 index 0000000..68117cf --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/ApiPathUtilsTest.java @@ -0,0 +1,20 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ApiPathUtilsTest { + + @Test + public void testLeadingSlashIsNormalised() { + assertEquals("/api/user/list", ApiPathUtils.getFullPath("/user/list")); + assertEquals("/api/user/list", ApiPathUtils.getFullPath("user/list")); + } + + @Test + public void testPrefixAlwaysPresent() { + assertTrue(ApiPathUtils.getFullPath("x").startsWith("/api/")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/util/ConvertUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/ConvertUtilsTest.java new file mode 100644 index 0000000..5f5f9b3 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/ConvertUtilsTest.java @@ -0,0 +1,55 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import static org.junit.Assert.*; + +public class ConvertUtilsTest { + + @Test + public void testNullConverterReturnsSameRow() { + Map row = new LinkedHashMap<>(); + row.put("A", 1); + assertSame(row, ConvertUtils.to(row, null)); + assertSame(row, ConvertUtils.to(row)); + } + + @Test + public void testNullRowConvertedToNull() { + assertNull(ConvertUtils.to(null, String::toLowerCase)); + } + + @Test + public void testConvertsKeysRecursivelyLeavingValuesUntouched() { + Map inner = new LinkedHashMap<>(); + inner.put("NESTED_KEY", 1); + Map row = new LinkedHashMap<>(); + row.put("OUTER_KEY", "v"); + row.put("INNER_MAP", inner); + + Map converted = ConvertUtils.to(row, String::toLowerCase); + assertEquals("v", converted.get("outer_key")); + @SuppressWarnings("unchecked") + Map convertedInner = (Map) converted.get("inner_map"); + assertEquals(1, convertedInner.get("nested_key")); + // the original map is not mutated + assertTrue(row.containsKey("OUTER_KEY")); + assertTrue(inner.containsKey("NESTED_KEY")); + } + + @Test + public void testWorksWithNamingStrategyFunctions() { + Map row = new LinkedHashMap<>(); + row.put("userName", 1); + Map snake = ConvertUtils.to(row, com.cs.common.enums.NamingStrategyEnum.SNAKE_CASE.getFunction()); + assertTrue(snake.containsKey("user_name")); + + Map camel = ConvertUtils.to(new LinkedHashMap<>(row), com.cs.common.enums.NamingStrategyEnum.CAMEL_CASE.getFunction()); + assertTrue(camel.containsKey("userName")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/util/DataSourceUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/DataSourceUtilsTest.java new file mode 100644 index 0000000..9631b89 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/DataSourceUtilsTest.java @@ -0,0 +1,364 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.enums.ProductTypeEnum; +import com.cs.persistence.entity.DataSourceEntity; +import com.cs.persistence.entity.PoolConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.env.Environment; + +import java.io.InputStream; +import java.lang.reflect.Proxy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.*; +import java.util.HashMap; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.Assert.*; + +public class DataSourceUtilsTest { + + /** + * Test driver packaged into a runtime-built jar so JarFileClassLoader can load it. + * Fully self-contained: the jar classloader's parent cannot see the outer test class, + * so every fake the driver needs lives here. + */ + public static class FakeJarDriver implements Driver { + @Override + public Connection connect(String url, Properties info) { + return fakeConnection(); + } + + static Connection fakeConnection() { + return (Connection) Proxy.newProxyInstance(FakeJarDriver.class.getClassLoader(), + new Class[]{Connection.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + if ("toString".equals(name)) { + return "fake-connection"; + } + if ("isValid".equals(name)) { + return true; + } + if ("isClosed".equals(name)) { + return false; + } + if ("getAutoCommit".equals(name)) { + return true; + } + if ("createStatement".equals(name)) { + return Proxy.newProxyInstance(FakeJarDriver.class.getClassLoader(), + new Class[]{Statement.class}, + (st, m, a) -> { + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(st); + } + if ("equals".equals(m.getName())) { + return st == a[0]; + } + if ("execute".equals(m.getName())) { + return true; + } + return defaultValue(m.getReturnType()); + }); + } + if ("getMetaData".equals(name)) { + return Proxy.newProxyInstance(FakeJarDriver.class.getClassLoader(), + new Class[]{DatabaseMetaData.class}, + (md, m, a) -> { + if ("getDatabaseProductName".equals(m.getName())) { + return "FakeDB"; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(md); + } + if ("equals".equals(m.getName())) { + return md == a[0]; + } + return defaultValue(m.getReturnType()); + }); + } + return defaultValue(method.getReturnType()); + }); + } + + static Object defaultValue(Class returnType) { + if (returnType == boolean.class) { + return false; + } + if (returnType == int.class) { + return 0; + } + if (returnType == long.class) { + return 0L; + } + return null; + } + + @Override + public boolean acceptsURL(String url) { + return true; + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { + return new DriverPropertyInfo[0]; + } + + @Override + public int getMajorVersion() { + return 1; + } + + @Override + public int getMinorVersion() { + return 0; + } + + @Override + public boolean jdbcCompliant() { + return false; + } + + @Override + public java.util.logging.Logger getParentLogger() { + return null; + } + } + + private static final java.util.Map SPRING_PROPERTIES = new HashMap<>(); + + static { + SPRING_PROPERTIES.put("datapoly.datasource.encrypt", "true"); + SPRING_PROPERTIES.put("datapoly.datasource.aes-key", "0123456789ABCDEF0123456789ABCDEF"); + } + + /** + * Reflectively injects a stub beanFactory into hutool SpringUtil (same pattern as TokenUtilsTest): + * getBean(Environment.class) answers the datasource encrypt switch and AES key from a fixed map. + * Cleared again in {@link #uninstallEnvironment()} so other test classes see the no-container default. + */ + @Before + public void installEnvironment() throws Exception { + Environment environment = (Environment) Proxy.newProxyInstance(DataSourceUtilsTest.class.getClassLoader(), + new Class[]{Environment.class}, (proxy, method, args) -> { + if ("getProperty".equals(method.getName()) && args != null && args.length >= 1) { + String value = SPRING_PROPERTIES.get(String.valueOf(args[0])); + if (value != null) { + return value; + } + return args.length > 1 ? args[args.length - 1] : null; + } + if ("hashCode".equals(method.getName())) { + return System.identityHashCode(proxy); + } + if ("equals".equals(method.getName())) { + return proxy == args[0]; + } + if ("toString".equals(method.getName())) { + return "stubEnvironment"; + } + return null; + }); + Object beanFactory = Proxy.newProxyInstance(DataSourceUtilsTest.class.getClassLoader(), + new Class[]{ConfigurableListableBeanFactory.class}, (proxy, method, args) -> { + if ("getBean".equals(method.getName()) && args != null && args.length == 1 + && args[0] instanceof Class) { + return environment; + } + if ("hashCode".equals(method.getName())) { + return System.identityHashCode(proxy); + } + if ("equals".equals(method.getName())) { + return proxy == args[0]; + } + if ("toString".equals(method.getName())) { + return "stubBeanFactory"; + } + return null; + }); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, beanFactory); + } + + @After + public void uninstallEnvironment() throws Exception { + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, null); + } + + /** Packs the compiled FakeJarDriver class into a fresh jar under a temp dir. */ + private static String driverJarDir() throws Exception { + Path dir = Files.createTempDirectory("ds-driver"); + Path jar = dir.resolve("fake-driver.jar"); + String entry = FakeJarDriver.class.getName().replace('.', '/') + ".class"; + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar)); + InputStream in = DataSourceUtilsTest.class.getClassLoader().getResourceAsStream(entry)) { + assertNotNull("compiled fake driver not found: " + entry, in); + out.putNextEntry(new JarEntry(entry)); + byte[] buffer = new byte[4096]; + int read; + while ((read = in.read(buffer)) > 0) { + out.write(buffer, 0, read); + } + out.closeEntry(); + } + return dir.toString(); + } + + private DataSourceEntity entity(long id, ProductTypeEnum type, String password) { + DataSourceEntity entity = new DataSourceEntity(); + entity.setId(id); + entity.setType(type); + entity.setName("ds-" + id); + entity.setUrl("jdbc:fake://host:1234/db"); + entity.setDriver(FakeJarDriver.class.getName()); + entity.setUsername("user"); + entity.setPassword(password); + return entity; + } + + @Test + public void testNullEntityIdRejected() { + DataSourceEntity nullId = entity(1, ProductTypeEnum.MYSQL, "pw"); + nullId.setId(null); + try { + DataSourceUtils.getHikariDataSource(nullId, "unused"); + fail("null id must be rejected"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBlankDriverPathRejected() { + try { + DataSourceUtils.createDataSource(entity(1, ProductTypeEnum.MYSQL, "pw"), " "); + fail("blank driver path must be rejected"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("Invalid driver path")); + } + } + + @Test + public void testBlankDriverClassRejected() throws Exception { + DataSourceEntity e = entity(2, ProductTypeEnum.MYSQL, "pw"); + e.setDriver(" "); + try { + DataSourceUtils.createDataSource(e, driverJarDir()); + fail("blank driver class must be rejected"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("Invalid driver class")); + } + } + + @Test + public void testCreateDataSourceAppliesDefaultPoolConfig() throws Exception { + HikariDataSource ds = DataSourceUtils.createDataSource(entity(3, ProductTypeEnum.MYSQL, "pw"), driverJarDir()); + assertEquals(DataSourceUtils.MAX_THREAD_COUNT, ds.getMaximumPoolSize()); + assertEquals(DataSourceUtils.MAX_THREAD_COUNT, ds.getMinimumIdle()); + assertEquals(DataSourceUtils.MAX_LIFE_TIME_MS, ds.getMaxLifetime()); + assertEquals(DataSourceUtils.CONNECTION_TIMEOUT, ds.getConnectionTimeout()); + assertEquals(DataSourceUtils.MAX_TIMEOUT_MS, ds.getIdleTimeout()); + assertEquals("/* ping */ SELECT 1", ds.getConnectionTestQuery()); + ds.close(); + } + + @Test + public void testCreateDataSourceAppliesPoolConfigOverrides() throws Exception { + DataSourceEntity e = entity(4, ProductTypeEnum.MYSQL, "pw"); + e.setPoolConfig(PoolConfig.builder() + .maximumPoolSize(5) + .minimumIdle(2) + .maxLifetime(1234L) + .connectionTimeout(567L) + .idleTimeout(89) + .build()); + HikariDataSource ds = DataSourceUtils.createDataSource(e, driverJarDir()); + assertEquals(5, ds.getMaximumPoolSize()); + assertEquals(2, ds.getMinimumIdle()); + assertEquals(1234L, ds.getMaxLifetime()); + assertEquals(567L, ds.getConnectionTimeout()); + assertEquals(89, ds.getIdleTimeout()); + ds.close(); + } + + @Test + public void testCreateDataSourceProductTypeBranches() throws Exception { + HikariDataSource oracle = DataSourceUtils.createDataSource(entity(5, ProductTypeEnum.ORACLE, "pw"), driverJarDir()); + assertEquals("SELECT 'Hello' from DUAL", oracle.getConnectionTestQuery()); + assertEquals("true", System.getProperty("oracle.jdbc.J2EE13Compliant")); + oracle.close(); + + // INCEPTOR has no test sql: no connection test query is configured + HikariDataSource inceptor = DataSourceUtils.createDataSource(entity(6, ProductTypeEnum.INCEPTOR, "pw"), driverJarDir()); + assertNull(inceptor.getConnectionTestQuery()); + inceptor.close(); + + // ODPS config branch only adds connection properties + HikariDataSource odps = DataSourceUtils.createDataSource(entity(7, ProductTypeEnum.ODPS, "pw"), driverJarDir()); + assertTrue(odps.getPoolName().contains("ODPS")); + odps.close(); + } + + @Test + public void testGetHikariDataSourceCachesByEntityAndSwapsOnConfigChange() throws Exception { + String driverPath = driverJarDir(); + long id = 4200L; + HikariDataSource first = DataSourceUtils.getHikariDataSource(entity(id, ProductTypeEnum.MYSQL, "pw"), driverPath); + assertNotNull(first); + assertTrue(DataSourceUtils.getAllDataSourceIdSet().contains(id)); + + // same entity definition: cached pool returned + assertSame(first, DataSourceUtils.getHikariDataSource(entity(id, ProductTypeEnum.MYSQL, "pw"), driverPath)); + + // password change: new pool created and swapped in + HikariDataSource swapped = DataSourceUtils.getHikariDataSource(entity(id, ProductTypeEnum.MYSQL, "pw2"), driverPath); + assertNotSame(first, swapped); + assertTrue(DataSourceUtils.getAllDataSourceIdSet().contains(id)); + + DataSourceUtils.dropHikariDataSource(id); + assertFalse(DataSourceUtils.getAllDataSourceIdSet().contains(id)); + DataSourceUtils.dropHikariDataSource(null); + DataSourceUtils.dropHikariDataSource(999999L); + } + + @Test + public void testEncryptDecryptRoundTrip() { + DataSourceEntity e = entity(10, ProductTypeEnum.MYSQL, "secret-pw"); + e.setUsername("secret-user"); + DataSourceUtils.encrypt(e); + assertNotEquals("secret-user", e.getUsername()); + assertNotEquals("secret-pw", e.getPassword()); + DataSourceUtils.decrypt(e); + assertEquals("secret-user", e.getUsername()); + assertEquals("secret-pw", e.getPassword()); + } + + @Test + public void testEncryptDecryptKeepNullCredentials() { + DataSourceEntity e = entity(11, ProductTypeEnum.MYSQL, null); + e.setUsername(null); + DataSourceUtils.encrypt(e); + assertNull(e.getUsername()); + assertNull(e.getPassword()); + DataSourceUtils.decrypt(e); + assertNull(e.getUsername()); + assertNull(e.getPassword()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/core/util/LocaleUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/LocaleUtilsTest.java new file mode 100644 index 0000000..b79211a --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/LocaleUtilsTest.java @@ -0,0 +1,50 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; + +import java.lang.reflect.Proxy; +import java.util.Locale; + +import static org.junit.Assert.*; + +public class LocaleUtilsTest { + + private ServerHttpRequest requestWithAcceptLanguage(String header) { + HttpHeaders headers = new HttpHeaders(); + if (header != null) { + headers.add("Accept-Language", header); + } + return (ServerHttpRequest) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ServerHttpRequest.class}, + (p, method, args) -> { + if ("getHeaders".equals(method.getName())) { + return headers; + } + if ("hashCode".equals(method.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(method.getName())) { + return p == args[0]; + } + return null; + }); + } + + @Test + public void testUnderscoreTagNormalisedToLocale() { + assertEquals(Locale.SIMPLIFIED_CHINESE, LocaleUtils.resolveLocale(requestWithAcceptLanguage("zh_CN"))); + } + + @Test + public void testPlainTagResolved() { + assertEquals(Locale.ENGLISH, LocaleUtils.resolveLocale(requestWithAcceptLanguage("en"))); + } + + @Test + public void testMissingHeaderFallsBackToUsEnglish() { + assertEquals(Locale.US, LocaleUtils.resolveLocale(requestWithAcceptLanguage(null))); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/util/PageSizeUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/PageSizeUtilsTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/util/PageSizeUtilsTest.java rename to datapoly-test/src/test/java/com/cs/core/util/PageSizeUtilsTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/util/PageSqlUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/PageSqlUtilsTest.java new file mode 100644 index 0000000..7b6a178 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/PageSqlUtilsTest.java @@ -0,0 +1,124 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import com.cs.common.enums.ProductTypeEnum; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +public class PageSqlUtilsTest { + + /** + * Builds a connection whose prepareStatement(sqlDialect) yields a result set with + * the given mode value on column 2 (or no row / prepare failure). + */ + private Connection buildObConnection(String modeValue, boolean hasNext, boolean failPrepare, + List preparedSql) { + ResultSet resultSet = (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, method, args) -> { + switch (method.getName()) { + case "next": + return hasNext; + case "getString": + return modeValue; + case "hashCode": + return System.identityHashCode(p); + case "equals": + return p == args[0]; + default: + return null; + } + }); + PreparedStatement statement = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, method, args) -> { + switch (method.getName()) { + case "executeQuery": + return resultSet; + case "hashCode": + return System.identityHashCode(p); + case "equals": + return p == args[0]; + default: + return null; + } + }); + return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{Connection.class}, + (p, method, args) -> { + switch (method.getName()) { + case "prepareStatement": + if (failPrepare) { + throw new SQLException("prepare failed"); + } + preparedSql.add((String) args[0]); + return statement; + case "hashCode": + return System.identityHashCode(p); + case "equals": + return p == args[0]; + default: + return null; + } + }); + } + + @Test + public void testNonOceanBaseDelegatesToProductType() { + assertEquals("SELECT 1 LIMIT ? OFFSET ? ", + PageSqlUtils.getPageSql(ProductTypeEnum.MYSQL, null, "SELECT 1", 2, 10)); + } + + @Test + public void testOceanBaseMysqlModeUsesMysqlPaging() { + List preparedSql = new ArrayList<>(); + Connection conn = buildObConnection("MySQL", true, false, preparedSql); + assertEquals("SELECT 1 LIMIT ? OFFSET ? ", + PageSqlUtils.getPageSql(ProductTypeEnum.OCEANBASE, conn, "SELECT 1", 2, 10)); + assertEquals(PageSqlUtils.sqlDialect, preparedSql.get(0)); + } + + @Test + public void testOceanBaseOracleModeUsesOraclePaging() { + Connection conn = buildObConnection("Oracle", true, false, new ArrayList()); + String pageSql = PageSqlUtils.getPageSql(ProductTypeEnum.OCEANBASE, conn, "SELECT 1", 2, 10); + assertTrue(pageSql.contains("ROWNUM")); + } + + @Test + public void testOceanBaseNullModeValueRejected() { + Connection conn = buildObConnection(null, true, false, new ArrayList()); + try { + PageSqlUtils.isInMysqlMode(conn); + fail("null mode value must be rejected"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("return null value")); + } + } + + @Test + public void testOceanBaseNoResultRejected() { + Connection conn = buildObConnection("MySQL", false, false, new ArrayList()); + try { + PageSqlUtils.isInMysqlMode(conn); + fail("empty mode result must be rejected"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("no result")); + } + } + + @Test + public void testSqlFailureFallsBackToMysqlModeGuess() { + Connection conn = buildObConnection("MySQL", true, true, new ArrayList()); + assertTrue(PageSqlUtils.isInMysqlMode(conn)); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/util/ParamMaskUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/ParamMaskUtilsTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/util/ParamMaskUtilsTest.java rename to datapoly-test/src/test/java/com/cs/core/util/ParamMaskUtilsTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/util/ServletUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/ServletUtilsTest.java new file mode 100644 index 0000000..6581daf --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/ServletUtilsTest.java @@ -0,0 +1,80 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import org.junit.After; +import org.junit.Test; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class ServletUtilsTest { + + private HttpServletRequest fakeRequest(String url, String uri, String remoteAddr, Map headers) { + return (HttpServletRequest) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{HttpServletRequest.class}, + (proxy, method, args) -> { + switch (method.getName()) { + case "getRequestURL": + return new StringBuffer(url); + case "getRequestURI": + return uri; + case "getRemoteAddr": + return remoteAddr; + case "getHeader": + return headers.get(String.valueOf(args[0])); + default: + return null; + } + }); + } + + private void bind(HttpServletRequest request) { + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + } + + @After + public void unbind() { + RequestContextHolder.resetRequestAttributes(); + } + + @Test + public void testCurrentRequestResolvedFromHolder() { + HttpServletRequest request = fakeRequest("http://h/x", "/x", null, new HashMap()); + bind(request); + assertSame(request, ServletUtils.getHttpServletRequest()); + } + + @Test + public void testDomainOriginPathAndUserAgent() { + Map headers = new HashMap<>(); + headers.put("Origin", "http://front"); + headers.put("user-agent", "ua-1"); + bind(fakeRequest("http://api.example.com/svc/a", "/svc/a", null, headers)); + assertEquals("http://api.example.com", ServletUtils.getDomain()); + assertEquals("http://front", ServletUtils.getOrigin()); + assertEquals("/svc/a", ServletUtils.getPathUri()); + assertEquals("ua-1", ServletUtils.getUserAgent()); + } + + @Test + public void testIpAddrHeaderFallbackChain() { + Map headers = new HashMap<>(); + headers.put("x-forwarded-for", "unknown"); + headers.put("WL-Proxy-Client-IP", "8.8.4.4"); + bind(fakeRequest("http://h/x", "/x", "10.0.0.1", headers)); + assertEquals("8.8.4.4", ServletUtils.getIpAddr()); + } + + @Test + public void testIpAddrFallsBackToRemoteAddr() { + bind(fakeRequest("http://h/x", "/x", "10.0.0.2", new HashMap())); + assertEquals("10.0.0.2", ServletUtils.getIpAddr()); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/util/SpelUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/SpelUtilsTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/util/SpelUtilsTest.java rename to datapoly-test/src/test/java/com/cs/core/util/SpelUtilsTest.java diff --git a/datapoly-test/src/test/java/com/cs/core/util/SqlJdbcUtilsTest.java b/datapoly-test/src/test/java/com/cs/core/util/SqlJdbcUtilsTest.java new file mode 100644 index 0000000..e300dbe --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/core/util/SqlJdbcUtilsTest.java @@ -0,0 +1,384 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.core.util; + +import com.cs.common.enums.NamingStrategyEnum; +import com.cs.common.enums.ProductTypeEnum; +import com.cs.template.SqlMeta; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.*; +import java.util.*; + +import static org.junit.Assert.*; + +public class SqlJdbcUtilsTest { + + /** Per-test JDBC fake state captured by the proxies below. */ + private static class FakeJdbc { + final String productName; + final List preparedSqls = new ArrayList<>(); + final List plainStatements = new ArrayList<>(); + final List boundParams = new ArrayList<>(); + int queryTimeout = -1; + int fetchSize = Integer.MAX_VALUE; + boolean hasResult = true; + int updateCount = -1; + List columns = Collections.emptyList(); + List> rows = Collections.emptyList(); + + FakeJdbc(String productName) { + this.productName = productName; + } + + Connection connection() { + return (Connection) Proxy.newProxyInstance(FakeJdbc.class.getClassLoader(), + new Class[]{Connection.class}, this::dispatch); + } + + private Object dispatch(Object proxy, Method method, Object[] args) { + String name = method.getName(); + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + if ("toString".equals(name)) { + return "fake-" + method.getDeclaringClass().getSimpleName(); + } + if ("prepareStatement".equals(name)) { + preparedSqls.add((String) args[0]); + return statement(); + } + if ("createStatement".equals(name)) { + return statement(); + } + if ("getMetaData".equals(name)) { + return databaseMetaData(); + } + return defaultValue(method.getReturnType()); + } + + private Object statement() { + return Proxy.newProxyInstance(FakeJdbc.class.getClassLoader(), + new Class[]{PreparedStatement.class, Statement.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + switch (name) { + case "setQueryTimeout": + queryTimeout = (Integer) args[0]; + return null; + case "setFetchSize": + fetchSize = (Integer) args[0]; + return null; + case "setObject": + boundParams.add(args[1]); + return null; + case "execute": + if (args != null && args.length == 1) { + plainStatements.add(String.valueOf(args[0])); + } + return hasResult; + case "getResultSet": + return resultSet(); + case "getUpdateCount": + return updateCount; + default: + return defaultValue(method.getReturnType()); + } + }); + } + + private Object resultSet() { + Iterator> rowIterator = rows.iterator(); + Map[] current = new Map[]{null}; + return Proxy.newProxyInstance(FakeJdbc.class.getClassLoader(), + new Class[]{ResultSet.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + switch (name) { + case "getMetaData": + return resultSetMetaData(); + case "next": + if (rowIterator.hasNext()) { + current[0] = rowIterator.next(); + return true; + } + return false; + case "getObject": + return current[0].get(String.valueOf(args[0])); + default: + return defaultValue(method.getReturnType()); + } + }); + } + + private Object resultSetMetaData() { + return Proxy.newProxyInstance(FakeJdbc.class.getClassLoader(), + new Class[]{ResultSetMetaData.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("getColumnCount".equals(name)) { + return columns.size(); + } + if ("getColumnLabel".equals(name)) { + return columns.get((Integer) args[0] - 1); + } + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + return defaultValue(method.getReturnType()); + }); + } + + private Object databaseMetaData() { + return Proxy.newProxyInstance(FakeJdbc.class.getClassLoader(), + new Class[]{DatabaseMetaData.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("getDatabaseProductName".equals(name)) { + return productName; + } + if ("hashCode".equals(name)) { + return System.identityHashCode(proxy); + } + if ("equals".equals(name)) { + return proxy == args[0]; + } + return defaultValue(method.getReturnType()); + }); + } + + private static Object defaultValue(Class returnType) { + if (returnType == boolean.class) { + return false; + } + if (returnType == int.class) { + return 0; + } + if (returnType == long.class) { + return 0L; + } + return null; + } + } + + private Object clob(String content) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Clob.class}, + (p, m, a) -> { + if ("length".equals(m.getName())) { + return (long) content.length(); + } + if ("getSubString".equals(m.getName())) { + return content; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + private Object blob(final byte[] bytes) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Blob.class}, + (p, m, a) -> { + if ("length".equals(m.getName())) { + return (long) bytes.length; + } + if ("getBytes".equals(m.getName())) { + return bytes; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + private Object array(String json) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Array.class}, + (p, m, a) -> { + if ("toString".equals(m.getName())) { + return json; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + private Object struct(String json) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Struct.class}, + (p, m, a) -> { + if ("toString".equals(m.getName())) { + return json; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testGetConverterByNamingStrategy() { + assertEquals("AbC", SqlJdbcUtils.getConverter(null).apply("AbC")); + assertEquals("user_name", SqlJdbcUtils.getConverter(NamingStrategyEnum.SNAKE_CASE).apply("userName")); + assertEquals("userName", SqlJdbcUtils.getConverter(NamingStrategyEnum.CAMEL_CASE).apply("user_name")); + assertEquals("x", SqlJdbcUtils.getConverter(NamingStrategyEnum.LOWER_CASE).apply("X")); + assertEquals("X", SqlJdbcUtils.getConverter(NamingStrategyEnum.UPPER_CASE).apply("x")); + } + + @Test + @SuppressWarnings("unchecked") + public void testExecuteQueryAppliesPagingAndNamingStrategy() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("MySQL"); + jdbc.columns = Arrays.asList("USER_NAME", "USER_AGE"); + Map row1 = new LinkedHashMap<>(); + row1.put("USER_NAME", "tom"); + row1.put("USER_AGE", 23); + Map row2 = new LinkedHashMap<>(); + row2.put("USER_NAME", "amy"); + row2.put("USER_AGE", 31); + jdbc.rows = Arrays.asList(row1, row2); + + SqlMeta meta = new SqlMeta("SELECT * FROM t", new ArrayList<>(Collections.singletonList("seed"))); + Object result = SqlJdbcUtils.execute(ProductTypeEnum.MYSQL, jdbc.connection(), meta, + NamingStrategyEnum.LOWER_CASE, 2, 5, true, false); + + List> list = (List>) result; + assertEquals(2, list.size()); + assertEquals("tom", list.get(0).get("user_name")); + assertEquals(23, list.get(0).get("user_age")); + assertEquals("amy", list.get(1).get("user_name")); + // MySQL connections stream with Integer.MIN_VALUE fetch size + assertEquals(Integer.MIN_VALUE, jdbc.fetchSize); + assertEquals(300, jdbc.queryTimeout); + assertEquals("SELECT * FROM t LIMIT ? OFFSET ? ", jdbc.preparedSqls.get(0)); + // original param first, then pagination params appended by the page consumer (size, offset) + assertEquals(Arrays.asList("seed", 5, 5), jdbc.boundParams); + } + + @Test + public void testExecuteNonQueryReturnsUpdateCountMessage() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("FakeDB"); + jdbc.hasResult = false; + jdbc.updateCount = 7; + + SqlMeta meta = new SqlMeta("UPDATE t SET a = 1", new ArrayList()); + Object result = SqlJdbcUtils.execute(ProductTypeEnum.POSTGRESQL, jdbc.connection(), meta, + null, 1, 10, false, false); + assertEquals("(7) rows affected", result); + assertEquals("UPDATE t SET a = 1", jdbc.preparedSqls.get(0)); + assertEquals(0, jdbc.fetchSize); + } + + @Test + public void testIllegalPagingSizeClampedToDefault() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("FakeDB"); + jdbc.columns = Collections.emptyList(); + jdbc.rows = Collections.emptyList(); + + SqlMeta meta = new SqlMeta("SELECT * FROM t", new ArrayList()); + SqlJdbcUtils.execute(ProductTypeEnum.POSTGRESQL, jdbc.connection(), meta, null, 3, 0, true, false); + + // size clamped to 10: pagination params become (10, 20) for page 3 + assertEquals(Arrays.asList(10, 20), jdbc.boundParams); + assertEquals(10, jdbc.fetchSize); + } + + @Test + public void testHiveRunsBeforeQueryHook() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("Hive"); + jdbc.columns = Collections.emptyList(); + jdbc.rows = Collections.emptyList(); + + SqlMeta meta = new SqlMeta("SELECT * FROM t", new ArrayList()); + SqlJdbcUtils.execute(ProductTypeEnum.HIVE, jdbc.connection(), meta, null, 1, 5, true, false); + + assertEquals(1, jdbc.plainStatements.size()); + assertTrue(jdbc.plainStatements.get(0).startsWith("set hive.resultset")); + // non-MySQL connection with pagination uses the page size as fetch size + assertEquals(5, jdbc.fetchSize); + } + + @Test + @SuppressWarnings("unchecked") + public void testClobAndBlobValuesConverted() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("FakeDB"); + jdbc.columns = Arrays.asList("TXT", "BIN"); + Map row = new LinkedHashMap<>(); + row.put("TXT", clob("hello")); + row.put("BIN", blob(new byte[]{9, 8})); + jdbc.rows = Collections.singletonList(row); + + SqlMeta meta = new SqlMeta("SELECT TXT, BIN FROM t", new ArrayList()); + List> result = (List>) SqlJdbcUtils.execute( + ProductTypeEnum.POSTGRESQL, jdbc.connection(), meta, null, 1, 10, false, false); + + assertEquals("hello", result.get(0).get("TXT")); + assertArrayEquals(new byte[]{9, 8}, (byte[]) result.get(0).get("BIN")); + } + + @Test + @SuppressWarnings("unchecked") + public void testHttpArrayAndStructConvertedViaJson() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("Http"); + jdbc.columns = Arrays.asList("ARR", "STRUCT"); + Map row = new LinkedHashMap<>(); + row.put("ARR", array("[1,2,3]")); + row.put("STRUCT", struct("{\"a\":1}")); + jdbc.rows = Collections.singletonList(row); + + SqlMeta meta = new SqlMeta("SELECT ARR, STRUCT FROM t", new ArrayList()); + // isPaging must stay off: the HTTP type defines no page consumer (latent NPE, recorded in the run report) + List> result = (List>) SqlJdbcUtils.execute( + ProductTypeEnum.HTTP, jdbc.connection(), meta, null, 1, 10, false, false); + + assertEquals(Arrays.asList(1, 2, 3), result.get(0).get("ARR")); + Map structValue = (Map) result.get(0).get("STRUCT"); + assertEquals(1, structValue.get("a")); + } + + @Test + public void testQueryParametersBoundInOrder() throws SQLException { + FakeJdbc jdbc = new FakeJdbc("FakeDB"); + jdbc.columns = Collections.emptyList(); + jdbc.rows = Collections.emptyList(); + + SqlMeta meta = new SqlMeta("SELECT * FROM t WHERE a = ? AND b = ?", + new ArrayList<>(Arrays.asList("x", 42))); + SqlJdbcUtils.execute(ProductTypeEnum.SQLSERVER, jdbc.connection(), meta, null, 1, 10, false, false); + assertEquals(Arrays.asList("x", 42), jdbc.boundParams); + } +} diff --git a/datapoly-core/src/test/java/com/cs/core/util/SqlTemplateGuardTest.java b/datapoly-test/src/test/java/com/cs/core/util/SqlTemplateGuardTest.java similarity index 100% rename from datapoly-core/src/test/java/com/cs/core/util/SqlTemplateGuardTest.java rename to datapoly-test/src/test/java/com/cs/core/util/SqlTemplateGuardTest.java diff --git a/datapoly-executor/src/test/java/com/cs/executor/filter/GatewaySourceFilterTest.java b/datapoly-test/src/test/java/com/cs/executor/filter/GatewaySourceFilterTest.java similarity index 100% rename from datapoly-executor/src/test/java/com/cs/executor/filter/GatewaySourceFilterTest.java rename to datapoly-test/src/test/java/com/cs/executor/filter/GatewaySourceFilterTest.java diff --git a/datapoly-executor/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java b/datapoly-test/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java similarity index 99% rename from datapoly-executor/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java rename to datapoly-test/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java index 9d32c58..c1024ab 100644 --- a/datapoly-executor/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java +++ b/datapoly-test/src/test/java/com/cs/executor/interceptor/ExecutorInterceptorTest.java @@ -7,7 +7,7 @@ import com.cs.persistence.entity.SystemParamEntity; import org.junit.*; -import javax.servlet.http.*; +import jakarta.servlet.http.*; import java.lang.reflect.Proxy; public class ExecutorInterceptorTest { diff --git a/datapoly-gateway/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java b/datapoly-test/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java similarity index 62% rename from datapoly-gateway/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java rename to datapoly-test/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java index e84de23..ffa2c13 100644 --- a/datapoly-gateway/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java +++ b/datapoly-test/src/test/java/com/cs/gateway/filter/ClientAddressFilterCronTest.java @@ -3,9 +3,9 @@ package com.cs.gateway.filter; import org.junit.Test; -import org.springframework.scheduling.support.CronSequenceGenerator; +import org.springframework.scheduling.support.CronExpression; -import java.util.Date; +import java.time.LocalDateTime; import static org.junit.Assert.assertEquals; @@ -18,13 +18,11 @@ public class ClientAddressFilterCronTest { @Test public void defaultFirewallCronFiresEvery30Seconds() { - CronSequenceGenerator generator = - new CronSequenceGenerator(ClientAddressFilter.DEFAULT_FIREWALL_CRON); - Date start = new Date(0L); - Date previous = generator.next(start); + CronExpression expression = CronExpression.parse(ClientAddressFilter.DEFAULT_FIREWALL_CRON); + LocalDateTime previous = expression.next(LocalDateTime.of(1970, 1, 1, 0, 0, 0)); for (int i = 0; i < 6; i++) { - Date next = generator.next(previous); - assertEquals(30_000L, next.getTime() - previous.getTime()); + LocalDateTime next = expression.next(previous); + assertEquals(30L, java.time.Duration.between(previous, next).getSeconds()); previous = next; } } diff --git a/datapoly-manager/src/test/java/com/cs/manager/config/LoginGuardTest.java b/datapoly-test/src/test/java/com/cs/manager/config/LoginGuardTest.java similarity index 100% rename from datapoly-manager/src/test/java/com/cs/manager/config/LoginGuardTest.java rename to datapoly-test/src/test/java/com/cs/manager/config/LoginGuardTest.java diff --git a/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolSupportTest.java b/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolSupportTest.java new file mode 100644 index 0000000..27ae2fd --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolSupportTest.java @@ -0,0 +1,136 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.manager.mcp; + +import com.cs.common.exception.CommonException; +import com.cs.common.exception.ResponseErrorCode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.spec.McpSchema.CallToolResult; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +public class McpAdminToolSupportTest { + + private static String textOf(CallToolResult result) { + return ((io.modelcontextprotocol.spec.McpSchema.TextContent) result.getContent().get(0)).getText(); + } + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void schemaToJsonContainsPropertiesAndRequired() throws Exception { + String json = McpAdminToolSupport.Schema.object() + .property("id", "integer", "主键", true) + .property("name", "string", "名称", false) + .toJson(); + + JsonNode root = MAPPER.readTree(json); + assertEquals("object", root.get("type").asText()); + assertTrue(root.get("properties").has("id")); + assertTrue(root.get("properties").has("name")); + assertTrue(root.get("properties").get("id").get("type").asText().equals("integer")); + assertEquals(1, root.get("required").size()); + assertEquals("id", root.get("required").get(0).asText()); + } + + @Test + public void nestedObjectAndArraySchemasAreEmbedded() throws Exception { + McpAdminToolSupport.Schema item = McpAdminToolSupport.Schema.object() + .property("key", "string", "类型", true) + .property("value", "string", "格式串", true); + String json = McpAdminToolSupport.Schema.object() + .property("pool", item, "嵌套对象", false) + .arrayProperty("items", item, "对象数组", false) + .arrayProperty("codes", "string", "字符串数组", false) + .toJson(); + + JsonNode root = MAPPER.readTree(json); + JsonNode pool = root.get("properties").get("pool"); + assertEquals("object", pool.get("type").asText()); + assertTrue(pool.get("properties").has("key")); + assertEquals(2, pool.get("required").size()); + + JsonNode items = root.get("properties").get("items"); + assertEquals("array", items.get("type").asText()); + assertEquals("object", items.get("items").get("type").asText()); + assertTrue(items.get("items").get("properties").has("key")); + + JsonNode codes = root.get("properties").get("codes"); + assertEquals("array", codes.get("type").asText()); + assertEquals("string", codes.get("items").get("type").asText()); + } + + @Test + public void schemaCopyIsIndependentFromOriginal() { + McpAdminToolSupport.Schema original = McpAdminToolSupport.Schema.object() + .property("name", "string", "名称", true); + McpAdminToolSupport.Schema copied = original.copy() + .property("children", "string", "子参数", false); + + assertFalse(original.toJson().contains("children")); + assertTrue(copied.toJson().contains("children")); + assertTrue(original.toJson().contains("name")); + } + + @Test + public void longListConversionHandlesJsonNumbers() { + Map arguments = new HashMap<>(); + arguments.put("ids", Arrays.asList(1, 2, 3)); + + List ids = McpAdminToolSupport.getLongList(arguments, "ids"); + + assertEquals(Arrays.asList(1L, 2L, 3L), ids); + assertNull(McpAdminToolSupport.getLongList(arguments, "missing")); + } + + @Test + public void typedGettersReadArguments() { + Map arguments = new HashMap<>(); + arguments.put("id", 7); + arguments.put("name", "demo"); + arguments.put("flag", Boolean.TRUE); + + assertEquals(Long.valueOf(7), McpAdminToolSupport.getLong(arguments, "id")); + assertEquals("demo", McpAdminToolSupport.getString(arguments, "name")); + assertEquals(Boolean.TRUE, McpAdminToolSupport.getBoolean(arguments, "flag")); + assertNull(McpAdminToolSupport.getLong(arguments, "none")); + } + + @Test + public void okWrapsNullAsPlainTextAndDataAsJson() { + CallToolResult plain = McpAdminToolSupport.ok(null); + assertFalse(plain.getIsError()); + assertEquals("操作成功", textOf(plain)); + + CallToolResult withData = McpAdminToolSupport.ok(Collections.singletonMap("id", 42L)); + assertFalse(withData.getIsError()); + assertTrue(textOf(withData).contains("\"id\":42")); + } + + @Test + public void failCarriesErrorCodeAndMessage() { + CallToolResult result = McpAdminToolSupport.fail( + new CommonException(ResponseErrorCode.ERROR_RESOURCE_NOT_EXISTS, "id not found")); + + assertTrue(result.getIsError()); + String text = textOf(result); + assertTrue(text.contains("3")); + assertTrue(text.contains("id not found")); + } + + @Test + public void convertBuildsDtoFromArgumentMapAndIgnoresUnknownFields() { + Map arguments = new HashMap<>(); + arguments.put("searchText", "demo"); + arguments.put("page", 2); + arguments.put("hallucinatedField", "agents may send extra keys"); + com.cs.core.dto.EntitySearchRequest request = + McpAdminToolSupport.convert(arguments, com.cs.core.dto.EntitySearchRequest.class); + + assertEquals("demo", request.getSearchText()); + assertEquals(Integer.valueOf(2), request.getPage()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolsTest.java b/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolsTest.java new file mode 100644 index 0000000..c8fe7b7 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/manager/mcp/McpAdminToolsTest.java @@ -0,0 +1,389 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.manager.mcp; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.dto.PageResult; +import com.cs.common.exception.CommonException; +import com.cs.common.exception.ResponseErrorCode; +import com.cs.core.dto.*; +import com.cs.core.service.*; +import com.cs.manager.config.McpAdminServerConfiguration; +import com.cs.manager.service.McpManageService; +import com.cs.persistence.dao.McpClientDao; +import com.cs.persistence.entity.ApiModuleEntity; +import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; +import io.modelcontextprotocol.server.transport.WebMvcSseServerAuthChecker; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpSchema.CallToolResult; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class McpAdminToolsTest { + + private static String textOf(CallToolResult result) { + return ((io.modelcontextprotocol.spec.McpSchema.TextContent) result.getContent().get(0)).getText(); + } + + // ------------------------------------------------------------------ + // fake services: 仅覆写被工具调用的方法,记录入参 + // ------------------------------------------------------------------ + + private static class FakeApiModuleService extends ApiModuleService { + String createdName; + EntitySearchRequest listedRequest; + + @Override + public void createModule(String name) { + this.createdName = name; + } + + @Override + public PageResult listAll(EntitySearchRequest request) { + this.listedRequest = request; + PageResult page = new PageResult<>(); + ApiModuleEntity entity = new ApiModuleEntity(); + entity.setId(11L); + entity.setName("module-1"); + page.setData(Collections.singletonList(entity)); + return page; + } + } + + private static class FakeApiAssignmentService extends ApiAssignmentService { + ApiAssignmentSaveRequest created; + Long createId = 88L; + Long updateGroupGroupId; + List updateGroupIds; + + @Override + public Long createAssignment(ApiAssignmentSaveRequest request) { + this.created = request; + return createId; + } + + @Override + public void updateGroup(Long groupId, List ids) { + this.updateGroupGroupId = groupId; + this.updateGroupIds = ids; + } + } + + private static class FakeDataSourceService extends DataSourceService { + DataSourceSaveRequest created; + + @Override + public void createDataSource(DataSourceSaveRequest request) { + this.created = request; + } + } + + private static class FakeAppClientService extends AppClientService { + AppClientSaveRequest created; + + @Override + public void create(AppClientSaveRequest request) { + this.created = request; + } + } + + private static class FakeMcpManageService extends McpManageService { + String clientName; + boolean clientManage; + boolean failCreateTool; + + @Override + public void createClient(String name, boolean manage) { + this.clientName = name; + this.clientManage = manage; + } + + @Override + public void createTool(McpToolSaveRequest request) { + if (failCreateTool) { + throw new CommonException(ResponseErrorCode.ERROR_RESOURCE_NOT_ONLINE, "apiId={0}", request.getApiId()); + } + } + } + + // ------------------------------------------------------------------ + // helpers + // ------------------------------------------------------------------ + + private McpAdminTools newTools(ApiModuleService moduleService, ApiAssignmentService assignmentService, + DataSourceService dataSourceService, AppClientService appClientService, + McpManageService mcpManageService) { + McpAdminTools tools = new McpAdminTools(); + tools.apiModuleService = moduleService; + tools.apiAssignmentService = assignmentService; + tools.dataSourceService = dataSourceService; + tools.appClientService = appClientService; + tools.mcpManageService = mcpManageService; + return tools; + } + + private SyncToolSpecification findTool(List tools, String name) { + return tools.stream() + .filter(t -> name.equals(t.getTool().getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("tool not found: " + name)); + } + + private CallToolResult call(SyncToolSpecification tool, Map args) { + return tool.getCall().apply(null, args); + } + + // ------------------------------------------------------------------ + // tests + // ------------------------------------------------------------------ + + @Test + public void toolSchemasAreUniquePrefixedAndValidJson() throws Exception { + McpAdminTools tools = newTools(new FakeApiModuleService(), new FakeApiAssignmentService(), + new FakeDataSourceService(), new FakeAppClientService(), new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + assertTrue("expected a meaningful tool set", specs.size() >= 40); + Set names = new HashSet<>(); + for (SyncToolSpecification spec : specs) { + String name = spec.getTool().getName(); + assertTrue("tool name must keep dp_ prefix: " + name, name.startsWith("dp_")); + assertTrue("duplicate tool name: " + name, names.add(name)); + assertFalse("description must not be empty: " + name, spec.getTool().getDescription().isEmpty()); + + McpSchema.JsonSchema schema = spec.getTool().getInputSchema(); + assertEquals("object", schema.getType()); + assertNotNull(schema.getProperties()); + for (String requiredField : schema.getRequired()) { + assertTrue("required field missing in properties: " + requiredField + " of " + name, + schema.getProperties().containsKey(requiredField)); + } + } + } + + @Test + public void moduleCreateAndListInvokeService() { + FakeApiModuleService moduleService = new FakeApiModuleService(); + McpAdminTools tools = newTools(moduleService, new FakeApiAssignmentService(), + new FakeDataSourceService(), new FakeAppClientService(), new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + CallToolResult created = call(findTool(specs, "dp_module_create"), + Collections.singletonMap("name", "orders")); + assertFalse(created.getIsError()); + assertEquals("orders", moduleService.createdName); + + Map listArgs = new HashMap<>(); + listArgs.put("searchText", "ord"); + listArgs.put("page", 1); + listArgs.put("size", 10); + CallToolResult listed = call(findTool(specs, "dp_module_list"), listArgs); + assertFalse(listed.getIsError()); + assertEquals("ord", moduleService.listedRequest.getSearchText()); + assertTrue(textOf(listed).contains("module-1")); + } + + @Test + public void apiCreateConvertsComplexArgumentsToDto() { + FakeApiAssignmentService assignmentService = new FakeApiAssignmentService(); + McpAdminTools tools = newTools(new FakeApiModuleService(), assignmentService, + new FakeDataSourceService(), new FakeAppClientService(), new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + Map param = new HashMap<>(); + param.put("name", "userId"); + param.put("type", "LONG"); + param.put("location", "REQUEST_FORM"); + param.put("required", true); + Map child = new HashMap<>(); + child.put("name", "city"); + child.put("type", "STRING"); + child.put("location", "REQUEST_BODY"); + Map objectParam = new HashMap<>(); + objectParam.put("name", "filter"); + objectParam.put("type", "OBJECT"); + objectParam.put("location", "REQUEST_BODY"); + objectParam.put("children", Collections.singletonList(child)); + Map format = new HashMap<>(); + format.put("key", "DATE"); + format.put("value", "yyyy-MM-dd"); + + Map args = new HashMap<>(); + args.put("groupId", 1); + args.put("moduleId", 2); + args.put("datasourceId", 3); + args.put("name", "查询用户"); + args.put("method", "POST"); + args.put("path", "/user/query"); + args.put("contentType", "application/json"); + args.put("engine", "SQL"); + args.put("contextList", Collections.singletonList("select * from t_user where id = #{userId}")); + args.put("open", false); + args.put("alarm", false); + args.put("flowStatus", false); + args.put("namingStrategy", "CAMEL_CASE"); + args.put("params", Arrays.asList(param, objectParam)); + args.put("formatMap", Collections.singletonList(format)); + + CallToolResult result = call(findTool(specs, "dp_api_create"), args); + assertFalse(result.getIsError()); + assertTrue(textOf(result).contains("88")); + + ApiAssignmentSaveRequest request = assignmentService.created; + assertEquals(Long.valueOf(1), request.getGroupId()); + assertEquals(Long.valueOf(3), request.getDatasourceId()); + assertEquals(com.cs.common.enums.HttpMethodEnum.POST, request.getMethod()); + assertEquals(com.cs.common.enums.ExecuteEngineEnum.SQL, request.getEngine()); + assertEquals(1, request.getContextList().size()); + assertEquals(2, request.getParams().size()); + assertEquals(com.cs.common.enums.ParamTypeEnum.OBJECT, request.getParams().get(1).getType()); + assertEquals(1, request.getParams().get(1).getChildren().size()); + assertEquals(com.cs.common.enums.NamingStrategyEnum.CAMEL_CASE, request.getNamingStrategy()); + assertEquals(com.cs.common.enums.DataTypeFormatEnum.DATE, request.getFormatMap().get(0).getKey()); + } + + @Test + public void apiSetGroupConvertsJsonNumbersToLongList() { + FakeApiAssignmentService assignmentService = new FakeApiAssignmentService(); + McpAdminTools tools = newTools(new FakeApiModuleService(), assignmentService, + new FakeDataSourceService(), new FakeAppClientService(), new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + Map args = new HashMap<>(); + args.put("groupId", 5); + args.put("ids", Arrays.asList(1, 2, 3)); + CallToolResult result = call(findTool(specs, "dp_api_set_group"), args); + + assertFalse(result.getIsError()); + assertEquals(Long.valueOf(5), assignmentService.updateGroupGroupId); + assertEquals(Arrays.asList(1L, 2L, 3L), assignmentService.updateGroupIds); + } + + @Test + public void datasourceCreateConvertsTypeAndPoolConfig() { + FakeDataSourceService dataSourceService = new FakeDataSourceService(); + McpAdminTools tools = newTools(new FakeApiModuleService(), new FakeApiAssignmentService(), + dataSourceService, new FakeAppClientService(), new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + Map pool = new HashMap<>(); + pool.put("maximumPoolSize", 20); + Map args = new HashMap<>(); + args.put("name", "mysql-main"); + args.put("type", "MYSQL"); + args.put("version", "8.0"); + args.put("driver", "com.mysql.cj.jdbc.Driver"); + args.put("url", "jdbc:mysql://127.0.0.1:3306/demo"); + args.put("username", "root"); + args.put("password", "secret"); + args.put("poolConfig", pool); + + CallToolResult result = call(findTool(specs, "dp_datasource_create"), args); + assertFalse(result.getIsError()); + assertEquals(com.cs.common.enums.ProductTypeEnum.MYSQL, dataSourceService.created.getType()); + assertEquals(Integer.valueOf(20), dataSourceService.created.getPoolConfig().getMaximumPoolSize()); + assertEquals("jdbc:mysql://127.0.0.1:3306/demo", dataSourceService.created.getUrl()); + } + + @Test + public void appClientCreateConvertsExpireEnums() { + FakeAppClientService appClientService = new FakeAppClientService(); + McpAdminTools tools = newTools(new FakeApiModuleService(), new FakeApiAssignmentService(), + new FakeDataSourceService(), appClientService, new FakeMcpManageService()); + List specs = tools.buildToolSpecifications(); + + Map args = new HashMap<>(); + args.put("name", "bi-tool"); + args.put("appKey", "bi"); + args.put("expireTime", "EXPIRE_01_DAY"); + args.put("tokenAlive", "LONGEVITY"); + + CallToolResult result = call(findTool(specs, "dp_client_create"), args); + assertFalse(result.getIsError()); + assertEquals(com.cs.common.enums.ExpireTimeEnum.EXPIRE_01_DAY, appClientService.created.getExpireTime()); + assertEquals(com.cs.common.enums.AliveTimeEnum.LONGEVITY, appClientService.created.getTokenAlive()); + } + + @Test + public void mcpClientCreatePassesManageFlag() { + FakeMcpManageService mcpManageService = new FakeMcpManageService(); + McpAdminTools tools = newTools(new FakeApiModuleService(), new FakeApiAssignmentService(), + new FakeDataSourceService(), new FakeAppClientService(), mcpManageService); + List specs = tools.buildToolSpecifications(); + + Map args = new HashMap<>(); + args.put("name", "agent-admin"); + args.put("manage", true); + CallToolResult result = call(findTool(specs, "dp_mcp_client_create"), args); + assertFalse(result.getIsError()); + assertEquals("agent-admin", mcpManageService.clientName); + assertTrue(mcpManageService.clientManage); + } + + @Test + public void serviceErrorBecomesIsErrorResultWithCode() { + FakeMcpManageService mcpManageService = new FakeMcpManageService(); + mcpManageService.failCreateTool = true; + McpAdminTools tools = newTools(new FakeApiModuleService(), new FakeApiAssignmentService(), + new FakeDataSourceService(), new FakeAppClientService(), mcpManageService); + List specs = tools.buildToolSpecifications(); + + Map args = new HashMap<>(); + args.put("name", "t1"); + args.put("description", "d1"); + args.put("apiId", 9); + CallToolResult result = call(findTool(specs, "dp_mcp_tool_create"), args); + + assertTrue(result.getIsError()); + String text = textOf(result); + assertTrue(text.contains("6")); + } + + @Test + public void adminAuthCheckerValidatesManageScopedTokenOnly() throws Exception { + McpClientDao dao = new McpClientDao() { + @Override + public boolean existsManageAccessToken(String accessToken) { + return "manage-token".equals(accessToken); + } + }; + Object beanFactory = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ConfigurableListableBeanFactory.class}, + (p, m, a) -> { + if ("getBean".equals(m.getName()) && a != null && a.length == 1 + && a[0] == McpClientDao.class) { + return dao; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, beanFactory); + try { + WebMvcSseServerAuthChecker checker = new McpAdminServerConfiguration().mcpAdminAuthChecker(); + assertEquals("token", checker.getTokenParamName()); + assertTrue(checker.checkTokenValid("manage-token")); + assertFalse(checker.checkTokenValid("data-only-token")); + } finally { + field.set(null, null); + } + } + + @After + public void tearDown() throws Exception { + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, null); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/PersistenceTestSupport.java b/datapoly-test/src/test/java/com/cs/persistence/PersistenceTestSupport.java new file mode 100644 index 0000000..6bc8e29 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/PersistenceTestSupport.java @@ -0,0 +1,23 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import org.apache.ibatis.builder.MapperBuilderAssistant; + +/** + * Registers MyBatis-Plus TableInfo for entities so lambda wrappers (Wrappers.lambdaQuery() + * with column references) resolve in plain unit tests, where no mapper parsing happens. + */ +public final class PersistenceTestSupport { + + private PersistenceTestSupport() { + } + + public static void initTableInfo(Class... entities) { + MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), ""); + for (Class entity : entities) { + TableInfoHelper.initTableInfo(assistant, entity); + } + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/ApiAssignmentDaoTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/ApiAssignmentDaoTest.java new file mode 100644 index 0000000..f06c93b --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/ApiAssignmentDaoTest.java @@ -0,0 +1,234 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import com.cs.common.enums.HttpMethodEnum; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.persistence.entity.ApiAssignmentEntity; +import com.cs.persistence.entity.ApiContextEntity; +import com.cs.persistence.entity.ModuleAssignmentEntity; +import com.cs.persistence.mapper.ApiAssignmentMapper; +import com.cs.persistence.mapper.ApiContextMapper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class ApiAssignmentDaoTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Class rt = m.getReturnType(); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + if (result == null && rt.isPrimitive()) { + return defaultValue(rt); + } + return result; + } + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + + private static Object defaultValue(Class rt) { + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + + Object[] argsOf(String method) { + return calls.get(method).get(0); + } + } + + private Recorder assignmentRecorder; + private Recorder contextRecorder; + private ApiAssignmentDao dao; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.ApiAssignmentEntity.class, com.cs.persistence.entity.ApiContextEntity.class); + } + + @Before + public void setUp() { + assignmentRecorder = new Recorder(); + contextRecorder = new Recorder(); + ApiContextDao apiContextDao = new ApiContextDao(); + DataTaskTestSupport.setField(apiContextDao, "apiContextMapper", + contextRecorder.create(ApiContextMapper.class)); + dao = new ApiAssignmentDao(); + DataTaskTestSupport.setField(dao, "apiAssignmentMapper", + assignmentRecorder.create(ApiAssignmentMapper.class)); + DataTaskTestSupport.setField(dao, "apiContextDao", apiContextDao); + } + + private ApiContextEntity context(Long id, Long apiId) { + return ApiContextEntity.builder().id(id).apiId(apiId).build(); + } + + @Test + public void testInsertRewiresContextRows() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(77L).build(); + entity.setContextList(new ArrayList<>(Collections.singletonList(context(1L, 999L)))); + dao.insert(entity); + assertEquals(1, assignmentRecorder.count("insert")); + ApiContextEntity inserted = (ApiContextEntity) contextRecorder.argsOf("insert")[0]; + assertNull("context ids must be reset for reinsert", inserted.getId()); + assertEquals(Long.valueOf(77L), inserted.getApiId()); + } + + @Test + public void testInsertWithoutContextsSkipsBatch() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(1L).build(); + dao.insert(entity); + assertEquals(0, contextRecorder.count("insert")); + } + + @Test + public void testUpdateReplacesContexts() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(77L).build(); + entity.setContextList(new ArrayList<>(Collections.singletonList(context(1L, 999L)))); + dao.update(entity); + assertEquals(1, assignmentRecorder.count("updateById")); + assertEquals(1, contextRecorder.count("delete")); + ApiContextEntity inserted = (ApiContextEntity) contextRecorder.argsOf("insert")[0]; + assertNull(inserted.getId()); + assertEquals(Long.valueOf(77L), inserted.getApiId()); + } + + @Test + public void testGetByIdWithAndWithoutSql() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(3L).build(); + assignmentRecorder.stub("selectById", entity, entity); + contextRecorder.stub("selectList", + Collections.singletonList(context(1L, 3L)), Collections.emptyList()); + + ApiAssignmentEntity withSql = dao.getById(3L, true); + assertEquals(1, withSql.getContextList().size()); + + dao.getById(3L, false); + assertEquals("second lookup must not query contexts again", 1, contextRecorder.count("selectList")); + } + + @Test + public void testGetByIdsAndModuleAssignments() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(1L).build(); + assignmentRecorder.stub("selectBatchIds", Collections.singletonList(entity)); + assertEquals(entity, dao.getByIds(Collections.singletonList(1L)).get(0)); + + ModuleAssignmentEntity moduleAssignment = new ModuleAssignmentEntity(); + assignmentRecorder.stub("getModuleAssignments", Collections.singletonList(moduleAssignment)); + assertSame(moduleAssignment, dao.getModuleAssignments().get(0)); + } + + @Test + public void testGetByUkLoadsContexts() { + ApiAssignmentEntity entity = ApiAssignmentEntity.builder().id(3L).build(); + assignmentRecorder.stub("selectOne", entity, (Object) null); + contextRecorder.stub("selectList", Collections.singletonList(context(1L, 3L))); + + assertEquals(1, dao.getByUk(HttpMethodEnum.GET, "/p").getContextList().size()); + assertNull(dao.getByUk(HttpMethodEnum.POST, "/missing")); + } + + @Test + public void testSearchAllWrapsSearchText() { + assignmentRecorder.stub("searchAll", Collections.emptyList()); + dao.searchAll(1L, 2L, Boolean.TRUE, "text", Boolean.FALSE); + Object[] args = assignmentRecorder.argsOf("searchAll"); + assertEquals(1L, args[0]); + assertEquals(2L, args[1]); + assertEquals(Boolean.TRUE, args[2]); + assertEquals("%text%", args[3]); + assertEquals(Boolean.FALSE, args[4]); + + dao.searchAll(null, null, null, "", null); + assertEquals("blank search text is passed through unchanged", "", assignmentRecorder.calls.get("searchAll").get(1)[3]); + } + + @Test + public void testListAllAndExistChecks() { + assignmentRecorder.stub("selectList", Collections.emptyList()); + assertTrue(dao.listAll().isEmpty()); + + assignmentRecorder.stub("selectCount", 2L, 0L, 2L, 0L, 2L, 0L); + assertTrue(dao.existsDataSourceById(1L)); + assertFalse(dao.existsDataSourceById(2L)); + assertTrue(dao.existsGroupById(1L)); + assertFalse(dao.existsGroupById(2L)); + assertTrue(dao.existsModuleById(1L)); + assertFalse(dao.existsModuleById(2L)); + } + + @Test + public void testDeleteByIdAndGroupResets() { + dao.deleteById(5L); + assertEquals(1, contextRecorder.count("delete")); + assertEquals(1, assignmentRecorder.count("deleteById")); + + dao.resetGroupByGroupId(5L); + assertEquals(1, assignmentRecorder.count("resetGroup")); + + dao.updateGroup(5L, Arrays.asList(1L, 2L)); + assertEquals(1, assignmentRecorder.count("updateGroup")); + dao.updateGroup(5L, Collections.emptyList()); + dao.updateGroup(5L, null); + assertEquals(1, assignmentRecorder.count("updateGroup")); + } + + @Test + public void testUpgradeAssignmentDelegation() { + assignmentRecorder.stub("getUpgradeOnlineAssignments", Collections.singletonList(9L)); + assertEquals(Long.valueOf(9L), dao.getUpgradeOnlineAssignments().get(0)); + + dao.resetUpgradeOnlineAssignments(Collections.singletonList(9L)); + assertEquals(1, assignmentRecorder.count("resetUpgradeOnlineAssignments")); + dao.resetUpgradeOnlineAssignments(Collections.emptyList()); + dao.resetUpgradeOnlineAssignments(null); + assertEquals(1, assignmentRecorder.count("resetUpgradeOnlineAssignments")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/ApiOnlineDaoTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/ApiOnlineDaoTest.java new file mode 100644 index 0000000..d2e2901 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/ApiOnlineDaoTest.java @@ -0,0 +1,213 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import com.cs.common.enums.HttpMethodEnum; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.common.dto.ApiIdVersion; +import com.cs.persistence.entity.ApiAssignmentEntity; +import com.cs.persistence.entity.ApiOnlineEntity; +import com.cs.persistence.mapper.ApiOnlineMapper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class ApiOnlineDaoTest { + + /** Recording fake mapper: captures invocations, replays stubbed results. */ + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Class rt = m.getReturnType(); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + if (result == null && rt.isPrimitive()) { + return defaultValue(rt); + } + return result; + } + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + + private static Object defaultValue(Class rt) { + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + } + + private Recorder recorder; + private ApiOnlineDao dao; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.ApiOnlineEntity.class); + } + + @Before + public void setUp() { + recorder = new Recorder(); + dao = new ApiOnlineDao(); + DataTaskTestSupport.setField(dao, "apiOnlineMapper", recorder.create(ApiOnlineMapper.class)); + } + + private ApiOnlineEntity onlineEntity(Long id, String content) { + ApiOnlineEntity entity = new ApiOnlineEntity(); + entity.setId(id); + entity.setContent(content); + entity.setGroupId(11L); + entity.setModuleId(22L); + entity.setDatasourceId(33L); + entity.setOpen(Boolean.TRUE); + entity.setFlowStatus(Boolean.FALSE); + entity.setCommitId(44L); + return entity; + } + + @Test + public void testGetByApiIdMapsContentAndColumns() { + recorder.stub("selectOne", onlineEntity(1L, "{\"name\":\"from-json\",\"path\":\"/p\"}")); + ApiAssignmentEntity mapped = dao.getByApiId(7L); + assertEquals("from-json", mapped.getName()); + assertEquals(Long.valueOf(11L), mapped.getGroupId()); + assertEquals(Long.valueOf(22L), mapped.getModuleId()); + assertEquals(Long.valueOf(33L), mapped.getDatasourceId()); + assertEquals(Boolean.TRUE, mapped.getOpen()); + assertEquals(Boolean.FALSE, mapped.getFlowStatus()); + assertEquals(Long.valueOf(44L), mapped.getCommitId()); + } + + @Test + public void testGetByApiIdNullWhenNoRow() { + recorder.stub("selectOne", (Object) null); + assertNull(dao.getByApiId(7L)); + } + + @Test + public void testFilterOnlineVariants() { + assertTrue(dao.filterOnline(Collections.emptyList()).isEmpty()); + assertEquals(0, recorder.count("filterOnline")); + + ApiIdVersion idVersion = new ApiIdVersion(); + recorder.stub("filterOnline", Collections.singletonList(idVersion)); + assertEquals(idVersion, dao.filterOnline(Arrays.asList(1L, 2L)).get(0)); + + recorder.stub("filterOnline", Collections.singletonList(idVersion)); + assertEquals(idVersion, dao.filterOnline(5L)); + + recorder.stub("filterOnline", Collections.emptyList()); + assertNull(dao.filterOnline(6L)); + } + + @Test + public void testListAndSearchAllMapEveryRow() { + recorder.stub("selectList", + Arrays.asList(onlineEntity(1L, "{\"name\":\"a\"}"), onlineEntity(2L, "{\"name\":\"b\"}"))); + List all = dao.listAll(); + assertEquals(2, all.size()); + assertEquals("a", all.get(0).getName()); + + recorder.stub("selectList", Collections.singletonList(onlineEntity(3L, "{\"name\":\"c\"}"))); + assertEquals(1, dao.searchAll(null, null, Boolean.TRUE, "c").size()); + } + + @Test + public void testGetByUkAndCommitId() { + recorder.stub("selectOne", onlineEntity(1L, "{\"name\":\"a\"}")); + assertNotNull(dao.getByUk(HttpMethodEnum.GET, "/p")); + assertNull(dao.getByUk(HttpMethodEnum.POST, "/missing")); + + recorder.stub("selectOne", onlineEntity(2L, "{}")); + assertEquals(Long.valueOf(44L), dao.getCommitIdByUk(HttpMethodEnum.GET, "/p")); + assertNull(dao.getCommitIdByUk(HttpMethodEnum.POST, "/missing")); + } + + @Test + public void testExistsByUniqueKey() { + recorder.stub("selectOne", onlineEntity(9L, "{}")); + assertTrue(dao.existsByUniqueKey(HttpMethodEnum.GET, "/p")); + + recorder.stub("selectOne", (Object) null); + assertFalse(dao.existsByUniqueKey(HttpMethodEnum.GET, "/p")); + } + + @Test + public void testExistsByForeignKeys() { + recorder.stub("selectCount", 3L, 0L, 3L, 0L, 3L, 0L); + assertTrue(dao.existsDataSourceById(1L)); + assertFalse(dao.existsDataSourceById(2L)); + assertTrue(dao.existsGroupById(1L)); + assertFalse(dao.existsGroupById(2L)); + assertTrue(dao.existsModuleById(1L)); + assertFalse(dao.existsModuleById(2L)); + } + + @Test + public void testFlowControlListPassthrough() { + recorder.stub("selectList", Collections.singletonList(onlineEntity(1L, "{\"name\":\"f\"}"))); + assertEquals("f", dao.listFlowControlAll().get(0).getName()); + } + + @Test + public void testMutatorsDelegateToMapper() { + dao.upsert(onlineEntity(1L, "{}")); + assertEquals(1, recorder.count("upsert")); + + dao.deleteByApiId(8L); + assertEquals(1, recorder.count("delete")); + + dao.resetGroupByGroupId(5L); + assertEquals(1, recorder.count("resetGroup")); + + dao.updateGroup(5L, Arrays.asList(1L, 2L)); + assertEquals(1, recorder.count("updateGroup")); + + dao.updateGroup(5L, Collections.emptyList()); + dao.updateGroup(5L, null); + assertEquals("empty/null ids must not reach the mapper", 1, recorder.count("updateGroup")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/AppClientDaoTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/AppClientDaoTest.java new file mode 100644 index 0000000..bac22c6 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/AppClientDaoTest.java @@ -0,0 +1,205 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import com.cs.common.dto.IdWithName; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.persistence.entity.AppClientEntity; +import com.cs.persistence.entity.ClientGroupEntity; +import com.cs.persistence.mapper.AppClientMapper; +import com.cs.persistence.mapper.ClientGroupMapper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class AppClientDaoTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Class rt = m.getReturnType(); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + if (result == null && rt.isPrimitive()) { + return defaultValue(rt); + } + return result; + } + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + + private static Object defaultValue(Class rt) { + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + + Object[] argsOf(String method) { + return calls.get(method).get(0); + } + } + + private Recorder appClientRecorder; + private Recorder clientGroupRecorder; + private AppClientDao dao; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.AppClientEntity.class, com.cs.persistence.entity.ClientGroupEntity.class); + } + + @Before + public void setUp() { + appClientRecorder = new Recorder(); + clientGroupRecorder = new Recorder(); + dao = new AppClientDao(); + DataTaskTestSupport.setField(dao, "appClientMapper", appClientRecorder.create(AppClientMapper.class)); + DataTaskTestSupport.setField(dao, "clientAuthMapper", clientGroupRecorder.create(ClientGroupMapper.class)); + } + + @Test + public void testListAllVariants() { + AppClientEntity entity = new AppClientEntity(); + appClientRecorder.stub("selectList", Collections.singletonList(entity), Collections.singletonList(entity)); + assertEquals(entity, dao.listAll().get(0)); + assertEquals(entity, dao.listAll("text").get(0)); + + appClientRecorder.stub("searchAppClient", Collections.singletonList(entity)); + assertEquals(entity, dao.listAll("text", 5L).get(0)); + assertEquals("%text%", appClientRecorder.argsOf("searchAppClient")[0]); + assertEquals(5L, appClientRecorder.argsOf("searchAppClient")[1]); + + appClientRecorder.stub("searchAppClient", Collections.singletonList(entity)); + assertEquals(entity, dao.listAll(null, 5L).get(0)); + assertNull(appClientRecorder.calls.get("searchAppClient").get(1)[0]); + } + + @Test + public void testLookups() { + AppClientEntity entity = new AppClientEntity(); + entity.setId(9L); + appClientRecorder.stub("selectById", entity); + assertSame(entity, dao.getById(9L)); + + appClientRecorder.stub("selectOne", entity, entity, (Object) null); + assertSame(entity, dao.getByAppKey("key")); + assertSame(entity, dao.getByAccessToken("token")); + assertNull(dao.getByAccessToken("missing")); + + appClientRecorder.stub("selectList", Collections.singletonList(entity)); + assertEquals(entity, dao.getByName("name").get(0)); + } + + @Test + public void testExistsAuthGroupsRequiresClient() { + appClientRecorder.stub("selectOne", (Object) null); + assertFalse(dao.existsAuthGroups("key", 5L)); + assertEquals("unknown app key must short-circuit", 0, clientGroupRecorder.count("selectCount")); + + AppClientEntity entity = new AppClientEntity(); + entity.setId(9L); + appClientRecorder.stub("selectOne", entity); + clientGroupRecorder.stub("selectCount", 1L, 0L); + assertTrue(dao.existsAuthGroups("key", 5L)); + assertFalse(dao.existsAuthGroups("key", 6L)); + } + + @Test + public void testTokenUpdateAndClear() { + dao.updateTokenByAppKey("key", "new-token"); + AppClientEntity patch = (AppClientEntity) appClientRecorder.argsOf("update")[0]; + assertEquals("key", patch.getAppKey()); + assertEquals("new-token", patch.getAccessToken()); + + dao.clearTokenByAppKey("key"); + assertNull(appClientRecorder.calls.get("update").get(1)[0]); + } + + @Test + public void testDeleteByIdRemovesAuthRows() { + dao.deleteById(9L); + assertEquals(1, appClientRecorder.count("deleteById")); + assertEquals(1, clientGroupRecorder.count("delete")); + } + + @Test + public void testDeleteClientAuthByGroupId() { + dao.deleteClientAuthByGroupId(5L); + assertEquals(1, clientGroupRecorder.count("delete")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSaveAuthGroupDeduplicatesAndSetsClient() { + dao.saveAuthGroup(5L, Arrays.asList(1L, 2L, 2L, 3L)); + assertEquals(1, clientGroupRecorder.count("delete")); + List inserted = + (List) clientGroupRecorder.argsOf("insertList")[0]; + assertEquals(3, inserted.size()); + for (ClientGroupEntity row : inserted) { + assertEquals(Long.valueOf(5L), row.getClientId()); + } + assertEquals(Long.valueOf(1L), inserted.get(0).getGroupId()); + assertEquals(Long.valueOf(2L), inserted.get(1).getGroupId()); + assertEquals(Long.valueOf(3L), inserted.get(2).getGroupId()); + } + + @Test + public void testSaveAuthGroupWithoutGroupsOnlyDeletes() { + dao.saveAuthGroup(5L, Collections.emptyList()); + dao.saveAuthGroup(5L, null); + assertEquals(2, clientGroupRecorder.count("delete")); + assertEquals(0, clientGroupRecorder.count("insertList")); + } + + @Test + public void testGetGroupAuthPassthrough() { + IdWithName expected = new IdWithName(); + clientGroupRecorder.stub("getGroupAuth", Collections.singletonList(expected)); + assertSame(expected, dao.getGroupAuth(9L).get(0)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/DataTaskJobDaoTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/DataTaskJobDaoTest.java new file mode 100644 index 0000000..457ece6 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/DataTaskJobDaoTest.java @@ -0,0 +1,196 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import com.cs.common.enums.DataTaskStatus; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.persistence.entity.DataTaskJobEntity; +import com.cs.persistence.mapper.DataTaskJobMapper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.Timestamp; +import java.util.*; + +import static org.junit.Assert.*; + +public class DataTaskJobDaoTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Class rt = m.getReturnType(); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + if (result == null && rt.isPrimitive()) { + return defaultValue(rt); + } + return result; + } + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + + private static Object defaultValue(Class rt) { + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + + Object[] argsOf(String method) { + return calls.get(method).get(0); + } + } + + private Recorder recorder; + private DataTaskJobDao dao; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.DataTaskJobEntity.class); + } + + @Before + public void setUp() { + recorder = new Recorder(); + dao = new DataTaskJobDao(); + DataTaskTestSupport.setField(dao, "dataTaskJobMapper", recorder.create(DataTaskJobMapper.class)); + } + + @Test + public void testGetByIdAndInsert() { + DataTaskJobEntity entity = DataTaskJobEntity.builder().id(5L).build(); + recorder.stub("selectById", entity); + assertSame(entity, dao.getById(5L)); + + DataTaskJobEntity fresh = DataTaskJobEntity.builder().build(); + dao.insert(fresh); + assertEquals(1, recorder.count("insert")); + assertSame(fresh, recorder.argsOf("insert")[0]); + } + + @Test + public void testClaimPendingWithNoCandidates() { + recorder.stub("selectClaimableIds", Collections.emptyList()); + assertTrue(dao.claimPending(3, "worker", new Timestamp(0), new Timestamp(1)).isEmpty()); + assertEquals(0, recorder.count("update")); + } + + @Test + public void testClaimPendingOnlyKeepsActuallyFlippedIds() { + recorder.stub("selectClaimableIds", Arrays.asList(1L, 2L, 3L)); + recorder.stub("update", 1, 0, 1); + List claimed = dao.claimPending(3, "worker-1", new Timestamp(0), new Timestamp(9)); + assertEquals(Arrays.asList(1L, 3L), claimed); + + DataTaskJobEntity patch = (DataTaskJobEntity) recorder.argsOf("update")[0]; + assertEquals(DataTaskStatus.RUNNING, patch.getStatus()); + assertEquals("worker-1", patch.getWorkerAddr()); + assertEquals(0L, patch.getStartTime().getTime()); + assertEquals(9L, patch.getLeaseExpireAt().getTime()); + } + + @Test + public void testHeartbeatRefreshesProgressAndLease() { + recorder.stub("update", 1, 0); + assertTrue(dao.heartbeat(7L, 12345L, new Timestamp(42))); + DataTaskJobEntity patch = (DataTaskJobEntity) recorder.argsOf("update")[0]; + assertEquals(Long.valueOf(12345L), patch.getTotalRows()); + assertEquals(42L, patch.getLeaseExpireAt().getTime()); + assertFalse(dao.heartbeat(7L, 1L, new Timestamp(1))); + } + + @Test + public void testFinishSuccessClearsLeaseAndStoresArtifact() { + recorder.stub("update", 1); + assertTrue(dao.finishSuccess(7L, 10L, "file://out.csv", "{\"rows\":10}", new Timestamp(5))); + DataTaskJobEntity patch = (DataTaskJobEntity) recorder.argsOf("update")[0]; + assertEquals(DataTaskStatus.SUCCESS, patch.getStatus()); + assertEquals(Long.valueOf(10L), patch.getTotalRows()); + assertEquals("file://out.csv", patch.getArtifactUri()); + assertEquals("{\"rows\":10}", patch.getArtifactInfo()); + assertEquals(5L, patch.getFinishTime().getTime()); + assertNull(patch.getLeaseExpireAt()); + } + + @Test + public void testFinishFailureRecordsError() { + recorder.stub("update", 1); + assertTrue(dao.finishFailure(7L, "boom", 5L, new Timestamp(5))); + DataTaskJobEntity patch = (DataTaskJobEntity) recorder.argsOf("update")[0]; + assertEquals(DataTaskStatus.FAILED, patch.getStatus()); + assertEquals("boom", patch.getErrorMessage()); + assertEquals(Long.valueOf(5L), patch.getTotalRows()); + } + + @Test + public void testCancelFlags() { + recorder.stub("update", 1, 1); + assertTrue(dao.markCancelRequested(7L)); + assertEquals(Boolean.TRUE, ((DataTaskJobEntity) recorder.argsOf("update")[0]).getCancelRequested()); + + assertTrue(dao.cancelIfPending(7L)); + DataTaskJobEntity patch = (DataTaskJobEntity) recorder.calls.get("update").get(1)[0]; + assertEquals(DataTaskStatus.CANCELED, patch.getStatus()); + assertNotNull(patch.getFinishTime()); + assertEquals(Boolean.FALSE, patch.getCancelRequested()); + } + + @Test + public void testReapExpiredAndActiveChecks() { + recorder.stub("failExpiredLeases", 5); + assertEquals(5, dao.reapExpired("expired")); + + recorder.stub("selectCount", 2L, 0L); + assertTrue(dao.hasActiveByDef(1L)); + assertFalse(dao.hasActiveByDef(2L)); + } + + @Test + public void testSearchPassthrough() { + DataTaskJobEntity entity = DataTaskJobEntity.builder().id(1L).build(); + recorder.stub("selectList", Collections.singletonList(entity), Collections.singletonList(entity)); + assertEquals(entity, dao.search(null, null).get(0)); + assertEquals(entity, dao.search(5L, DataTaskStatus.RUNNING).get(0)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/ListGuardTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/ListGuardTest.java new file mode 100644 index 0000000..1e5e5f0 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/ListGuardTest.java @@ -0,0 +1,21 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ListGuardTest { + + @Test + public void testGuardConstants() { + assertEquals(10_000, ListGuard.MAX_ROWS); + assertEquals("LIMIT 10000", ListGuard.LIMIT_SQL); + } + + @Test + public void testWarnIfHitDoesNotThrowAtOrBelowBound() { + ListGuard.warnIfHit("api online", 9_999); + ListGuard.warnIfHit("api online", 10_000); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/dao/VersionCommitDaoTest.java b/datapoly-test/src/test/java/com/cs/persistence/dao/VersionCommitDaoTest.java new file mode 100644 index 0000000..c0c6716 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/dao/VersionCommitDaoTest.java @@ -0,0 +1,180 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.dao; + +import cn.hutool.extra.spring.SpringUtil; +import com.cs.common.exception.CommonException; +import com.cs.core.datatask.DataTaskTestSupport; +import com.cs.persistence.PersistenceTestSupport; +import com.cs.persistence.entity.VersionCommitEntity; +import com.cs.persistence.mapper.VersionCommitMapper; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.dao.DuplicateKeyException; + +import java.lang.reflect.Proxy; +import java.util.*; + +import static org.junit.Assert.*; + +public class VersionCommitDaoTest { + + private static class Recorder { + final Map> calls = new LinkedHashMap<>(); + private final Map> stubs = new HashMap<>(); + + @SuppressWarnings("unchecked") + M create(Class type) { + return (M) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, + (p, m, a) -> { + calls.computeIfAbsent(m.getName(), k -> new ArrayList<>()).add(a); + Class rt = m.getReturnType(); + Queue queue = stubs.get(m.getName()); + if (queue != null && !queue.isEmpty()) { + Object result = queue.poll(); + if (result instanceof RuntimeException) { + throw (RuntimeException) result; + } + if (result == null && rt.isPrimitive()) { + return defaultValue(rt); + } + return result; + } + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + }); + } + + void stub(String method, Object... results) { + Queue queue = stubs.computeIfAbsent(method, k -> new LinkedList<>()); + for (Object result : results) { + queue.add(result); + } + } + + + private static Object defaultValue(Class rt) { + if (rt == boolean.class) { + return false; + } + if (rt == int.class) { + return 0; + } + if (rt == long.class) { + return 0L; + } + return null; + } + + int count(String method) { + return calls.getOrDefault(method, Collections.emptyList()).size(); + } + } + + private Recorder recorder; + private VersionCommitDao dao; + + @BeforeClass + public static void initEntityMetadata() { + PersistenceTestSupport.initTableInfo(com.cs.persistence.entity.VersionCommitEntity.class); + } + + @Before + public void setUp() throws Exception { + recorder = new Recorder(); + dao = new VersionCommitDao(); + DataTaskTestSupport.setField(dao, "versionCommitMapper", recorder.create(VersionCommitMapper.class)); + // createVersion resolves itself through SpringUtil: stub the bean factory to return this dao + Object beanFactory = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ConfigurableListableBeanFactory.class}, + (p, m, a) -> { + if ("getBean".equals(m.getName()) && a != null && a.length == 1 + && a[0] == VersionCommitDao.class) { + return dao; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + if ("toString".equals(m.getName())) { + return "stubBeanFactory"; + } + return null; + }); + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, beanFactory); + } + + @After + public void tearDown() throws Exception { + java.lang.reflect.Field field = SpringUtil.class.getDeclaredField("beanFactory"); + field.setAccessible(true); + field.set(null, null); + } + + @Test + public void testDoCreateVersionStartsAtOne() { + recorder.stub("getMaxVersion", (Object) null); + VersionCommitEntity created = dao.doCreateVersion(5L, "desc", "content"); + assertEquals(Long.valueOf(5L), created.getBizId()); + assertEquals(Integer.valueOf(1), created.getVersion()); + assertEquals("desc", created.getDescription()); + assertEquals("content", created.getContent()); + } + + @Test + public void testDoCreateVersionIncrementsMax() { + recorder.stub("getMaxVersion", 7); + assertEquals(Integer.valueOf(8), dao.doCreateVersion(5L, null, null).getVersion()); + } + + @Test + public void testCreateVersionRetriesOnDuplicateKeyThenSucceeds() { + recorder.stub("getMaxVersion", null, null, null); + recorder.stub("insert", + new DuplicateKeyException("dup"), new DuplicateKeyException("dup"), (Object) null); + VersionCommitEntity created = dao.createVersion(5L, "desc", "content"); + assertEquals(Integer.valueOf(1), created.getVersion()); + assertEquals(3, recorder.count("insert")); + } + + @Test + public void testCreateVersionGivesUpAfterMaxAttempts() { + recorder.stub("getMaxVersion", null, null, null); + recorder.stub("insert", + new DuplicateKeyException("dup"), new DuplicateKeyException("dup"), new DuplicateKeyException("dup")); + try { + dao.createVersion(5L, "desc", "content"); + fail("persistent conflicts must surface as CommonException"); + } catch (CommonException expected) { + assertEquals(3, recorder.count("insert")); + } + } + + @Test + public void testLookups() { + VersionCommitEntity entity = VersionCommitEntity.builder().id(1L).build(); + recorder.stub("getLatestVersion", entity); + assertSame(entity, dao.getLatestVersion(5L)); + + recorder.stub("selectById", entity); + assertSame(entity, dao.getByCommitId(1L)); + + recorder.stub("selectList", Collections.singletonList(entity), Collections.singletonList(entity)); + assertEquals(entity, dao.getVersionList(5L, true).get(0)); + assertEquals(entity, dao.getVersionList(5L, false).get(0)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/FormatMapHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/FormatMapHandlerTest.java new file mode 100644 index 0000000..47b1d7e --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/FormatMapHandlerTest.java @@ -0,0 +1,69 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import com.cs.common.enums.DataTypeFormatEnum; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class FormatMapHandlerTest { + + private ResultSet resultSet(String value) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + Map value = new HashMap<>(); + value.put(DataTypeFormatEnum.DATE, "yyyy/MM/dd"); + new FormatMapHandler().setNonNullParameter(ps, 1, value, null); + assertEquals("{\"DATE\":\"yyyy/MM/dd\"}", captured.get()); + + new FormatMapHandler().setNonNullParameter(ps, 1, null, null); + assertNull(captured.get()); + new FormatMapHandler().setNonNullParameter(ps, 1, new HashMap(), null); + assertNull(captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + FormatMapHandler handler = new FormatMapHandler(); + Map parsed = + handler.getNullableResult(resultSet("{\"DATE\":\"yyyy/MM/dd\"}"), "col"); + assertEquals("yyyy/MM/dd", parsed.get(DataTypeFormatEnum.DATE)); + assertEquals("by index", "yyyy/MM/dd", + handler.getNullableResult(resultSet("{\"DATE\":\"yyyy/MM/dd\"}"), 1) + .get(DataTypeFormatEnum.DATE)); + assertTrue(handler.getNullableResult(resultSet(null), "col").isEmpty()); + assertTrue(handler.getNullableResult(resultSet(""), "col").isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/ListOutputHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/ListOutputHandlerTest.java new file mode 100644 index 0000000..093793e --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/ListOutputHandlerTest.java @@ -0,0 +1,87 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import com.cs.common.dto.OutParam; +import com.cs.common.enums.ParamTypeEnum; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class ListOutputHandlerTest { + + private ResultSet resultSet(String value, boolean wasNull) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("wasNull".equals(m.getName())) { + return wasNull; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + OutParam param = new OutParam(); + param.setName("o"); + param.setType(ParamTypeEnum.STRING); + new ListOutputHandler().setNonNullParameter(ps, 1, + new ArrayList<>(java.util.Collections.singletonList(param)), null); + // exact JSON layout is Jackson-defined; assert on the parsed round-trip instead + OutParam written = com.cs.persistence.util.JsonUtils.toBeanList(captured.get(), OutParam.class).get(0); + assertEquals("o", written.getName()); + assertEquals(ParamTypeEnum.STRING, written.getType()); + } + + @Test + public void testSetNonNullParameterNullAndEmptyLists() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + new ListOutputHandler().setNonNullParameter(ps, 1, null, null); + assertNull(captured.get()); + new ListOutputHandler().setNonNullParameter(ps, 1, new ArrayList(), null); + assertNull(captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + ListOutputHandler handler = new ListOutputHandler(); + assertEquals("o", handler.getNullableResult( + resultSet("[{\"name\":\"o\",\"type\":\"STRING\"}]", false), "col").get(0).getName()); + assertEquals("by index", "o", + handler.getNullableResult(resultSet("[{\"name\":\"o\"}]", false), 1).get(0).getName()); + assertNull(handler.getNullableResult(resultSet(null, true), "col")); + assertTrue(handler.getNullableResult(resultSet("", false), "col").isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/ListParamHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/ListParamHandlerTest.java new file mode 100644 index 0000000..08744b9 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/ListParamHandlerTest.java @@ -0,0 +1,93 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import com.cs.common.dto.ItemParam; +import com.cs.common.enums.ParamTypeEnum; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class ListParamHandlerTest { + + private ResultSet resultSet(String value, boolean wasNull) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("wasNull".equals(m.getName())) { + return wasNull; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + ItemParam param = new ItemParam(); + param.setName("p"); + param.setType(ParamTypeEnum.STRING); + new ListParamHandler().setNonNullParameter(ps, 1, + new ArrayList<>(java.util.Collections.singletonList(param)), null); + // exact JSON layout is Jackson-defined; assert on the parsed round-trip instead + ItemParam written = com.cs.persistence.util.JsonUtils.toBeanList(captured.get(), ItemParam.class).get(0); + assertEquals("p", written.getName()); + assertEquals(ParamTypeEnum.STRING, written.getType()); + } + + @Test + public void testSetNonNullParameterNullAndEmptyLists() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + new ListParamHandler().setNonNullParameter(ps, 1, null, null); + assertNull(captured.get()); + new ListParamHandler().setNonNullParameter(ps, 1, new ArrayList(), null); + assertNull(captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + ListParamHandler handler = new ListParamHandler(); + List parsed = handler.getNullableResult( + resultSet("[{\"name\":\"p\",\"type\":\"STRING\"}]", false), "col"); + assertEquals("p", parsed.get(0).getName()); + + assertEquals("by index", "p", + handler.getNullableResult(resultSet("[{\"name\":\"p\"}]", false), 1).get(0).getName()); + assertNull("sql null must map to null", handler.getNullableResult(resultSet(null, true), "col")); + assertTrue("empty string maps to empty list", + handler.getNullableResult(resultSet("", false), "col").isEmpty()); + assertTrue("non-null json null-string maps to empty list", + handler.getNullableResult(resultSet(null, false), "col").isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/ParamMapHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/ParamMapHandlerTest.java new file mode 100644 index 0000000..1e6a17e --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/ParamMapHandlerTest.java @@ -0,0 +1,66 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class ParamMapHandlerTest { + + private ResultSet resultSet(String value) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + Map value = new LinkedHashMap<>(); + value.put("k", 1); + new ParamMapHandler().setNonNullParameter(ps, 1, value, null); + assertEquals("{\"k\":1}", captured.get()); + + new ParamMapHandler().setNonNullParameter(ps, 1, null, null); + assertNull(captured.get()); + new ParamMapHandler().setNonNullParameter(ps, 1, new HashMap(), null); + assertNull(captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + ParamMapHandler handler = new ParamMapHandler(); + Map parsed = handler.getNullableResult(resultSet("{\"k\":1}"), "col"); + assertEquals(1, parsed.get("k")); + assertEquals(1, handler.getNullableResult(resultSet("{\"k\":1}"), 1).get("k")); + assertTrue(handler.getNullableResult(resultSet(null), "col").isEmpty()); + assertTrue(handler.getNullableResult(resultSet(""), "col").isEmpty()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/PoolConfigHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/PoolConfigHandlerTest.java new file mode 100644 index 0000000..f9cff53 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/PoolConfigHandlerTest.java @@ -0,0 +1,63 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import com.cs.persistence.entity.PoolConfig; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class PoolConfigHandlerTest { + + private ResultSet resultSet(String value) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + PoolConfig config = PoolConfig.builder().maximumPoolSize(7).minimumIdle(3).build(); + new PoolConfigHandler().setNonNullParameter(ps, 1, config, null); + PoolConfig written = com.cs.persistence.util.JsonUtils.toBeanObject(captured.get(), PoolConfig.class); + assertEquals(Integer.valueOf(7), written.getMaximumPoolSize()); + assertEquals(Integer.valueOf(3), written.getMinimumIdle()); + } + + @Test + public void testGetNullableResultDefaultsAndParses() throws Exception { + PoolConfigHandler handler = new PoolConfigHandler(); + assertEquals(PoolConfig.DEFAULT.getMaximumPoolSize(), + handler.getNullableResult(resultSet(null), "col").getMaximumPoolSize()); + assertEquals(PoolConfig.DEFAULT.getMaximumPoolSize(), + handler.getNullableResult(resultSet(""), 1).getMaximumPoolSize()); + PoolConfig parsed = handler.getNullableResult( + resultSet("{\"maximumPoolSize\":9,\"minimumIdle\":4}"), "col"); + assertEquals(Integer.valueOf(9), parsed.getMaximumPoolSize()); + assertEquals(Integer.valueOf(4), parsed.getMinimumIdle()); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/StringListHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/StringListHandlerTest.java new file mode 100644 index 0000000..639b9c1 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/StringListHandlerTest.java @@ -0,0 +1,62 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class StringListHandlerTest { + + private ResultSet resultSet(String value) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + new StringListHandler().setNonNullParameter(ps, 1, Arrays.asList("a", "b"), null); + assertEquals("[\"a\",\"b\"]", captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + StringListHandler handler = new StringListHandler(); + assertEquals(Arrays.asList("a", "b"), handler.getNullableResult(resultSet("[\"a\",\"b\"]"), "col")); + assertEquals(Arrays.asList("a"), handler.getNullableResult(resultSet("[\"a\"]"), 1)); + assertTrue(handler.getNullableResult(resultSet(null), "col").isEmpty()); + assertTrue(handler.getNullableResult(resultSet(""), "col").isEmpty()); + + CallableStatement cs = (CallableStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{CallableStatement.class}, + (p, m, a) -> "getString".equals(m.getName()) ? "[\"c\"]" : null); + assertEquals(Arrays.asList("c"), handler.getNullableResult(cs, 1)); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/handler/StringMapHandlerTest.java b/datapoly-test/src/test/java/com/cs/persistence/handler/StringMapHandlerTest.java new file mode 100644 index 0000000..0be5943 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/handler/StringMapHandlerTest.java @@ -0,0 +1,70 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.handler; + +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class StringMapHandlerTest { + + @SuppressWarnings("unchecked") + private ResultSet resultSet(String value) { + return (ResultSet) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ResultSet.class}, + (p, m, a) -> { + if ("getString".equals(m.getName())) { + return value; + } + if ("hashCode".equals(m.getName())) { + return System.identityHashCode(p); + } + if ("equals".equals(m.getName())) { + return p == a[0]; + } + return null; + }); + } + + @Test + public void testSetNonNullParameterWritesJson() throws Exception { + AtomicReference captured = new AtomicReference<>(); + PreparedStatement ps = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{PreparedStatement.class}, + (p, m, a) -> { + if ("setString".equals(m.getName())) { + captured.set((String) a[1]); + } + return null; + }); + Map value = new LinkedHashMap<>(); + value.put("k", "v"); + new StringMapHandler().setNonNullParameter(ps, 1, value, null); + assertEquals("{\"k\":\"v\"}", captured.get()); + } + + @Test + public void testGetNullableResultVariants() throws Exception { + StringMapHandler handler = new StringMapHandler(); + Map parsed = handler.getNullableResult(resultSet("{\"k\":\"v\"}"), "col"); + assertEquals("v", parsed.get("k")); + + Map byIndex = handler.getNullableResult(resultSet("{\"k\":\"v2\"}"), 1); + assertEquals("v2", byIndex.get("k")); + + assertEquals(0, handler.getNullableResult(resultSet(null), "col").size()); + assertEquals(0, handler.getNullableResult(resultSet(""), "col").size()); + + CallableStatement cs = (CallableStatement) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{CallableStatement.class}, + (p, m, a) -> "getString".equals(m.getName()) ? "{\"k\":\"v3\"}" : null); + assertEquals("v3", handler.getNullableResult(cs, 1).get("k")); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/util/JsonUtilsTest.java b/datapoly-test/src/test/java/com/cs/persistence/util/JsonUtilsTest.java new file mode 100644 index 0000000..e9f9c7a --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/util/JsonUtilsTest.java @@ -0,0 +1,100 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +public class JsonUtilsTest { + + public static class BrokenBean { + @SuppressWarnings("unused") + public String getBoom() { + throw new IllegalStateException("kaboom"); + } + } + + public static class SampleBean { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Test + public void testToJsonStringNullSafe() { + assertNull(JsonUtils.toJsonString(null)); + Map single = new LinkedHashMap<>(); + single.put("a", 1); + assertEquals("{\"a\":1}", JsonUtils.toJsonString(single)); + } + + @Test + public void testToJsonStringFailureSurfacesAsRuntimeException() { + try { + JsonUtils.toJsonString(new BrokenBean()); + fail("broken bean must surface as RuntimeException"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("convert object to json string error")); + } + } + + @Test + public void testToBeanObject() { + assertNull(JsonUtils.toBeanObject(null, SampleBean.class)); + SampleBean bean = JsonUtils.toBeanObject("{\"name\":\"x\"}", SampleBean.class); + assertEquals("x", bean.getName()); + try { + JsonUtils.toBeanObject("not-json", SampleBean.class); + fail("invalid json must surface as RuntimeException"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("parse json string to object error")); + } + } + + @Test + public void testToBeanType() { + assertNull(JsonUtils.toBeanType(null, new TypeReference>() { + })); + Map map = JsonUtils.toBeanType("{\"a\":1}", new TypeReference>() { + }); + assertEquals(Integer.valueOf(1), map.get("a")); + try { + JsonUtils.toBeanType("not-json", new TypeReference>() { + }); + fail("invalid json must surface as RuntimeException"); + } catch (RuntimeException expected) { + assertTrue(expected.getMessage().contains("parse json string to object error")); + } + } + + @Test + public void testToBeanList() { + assertTrue(JsonUtils.toBeanList(null, Object.class).isEmpty()); + List beans = JsonUtils.toBeanList( + "[{\"name\":\"a\"},{\"name\":\"b\"}]", SampleBean.class); + assertEquals(2, beans.size()); + assertEquals("a", beans.get(0).getName()); + assertEquals("b", beans.get(1).getName()); + } + + @Test + public void testRoundTripKeepsKeyOrderForLinkedMaps() { + Map ordered = new LinkedHashMap<>(); + ordered.put("z", 1); + ordered.put("a", 2); + assertEquals("{\"z\":1,\"a\":2}", JsonUtils.toJsonString(ordered)); + LinkedHashMap parsed = JsonUtils.toBeanType("{\"z\":1,\"a\":2}", + new TypeReference>() { + }); + assertEquals(Arrays.asList("z", "a"), new ArrayList<>(parsed.keySet())); + } +} diff --git a/datapoly-test/src/test/java/com/cs/persistence/util/PageUtilsTest.java b/datapoly-test/src/test/java/com/cs/persistence/util/PageUtilsTest.java new file mode 100644 index 0000000..ab29132 --- /dev/null +++ b/datapoly-test/src/test/java/com/cs/persistence/util/PageUtilsTest.java @@ -0,0 +1,75 @@ +// Use of this source code is governed by a BSD-style license +package com.cs.persistence.util; + +import com.cs.common.dto.PageResult; +import org.junit.After; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class PageUtilsTest { + + @After + public void clearPageHelper() { + com.github.pagehelper.PageHelper.clearPage(); + } + + @Test + public void testInMemoryPaginationSlicesList() { + List data = new ArrayList<>(); + for (int i = 1; i <= 25; i++) { + data.add(i); + } + PageResult page = PageUtils.getPage(data, 2, 10); + assertEquals(25, page.getPagination().getTotal()); + assertEquals(2, page.getPagination().getPage()); + assertEquals(10, page.getPagination().getSize()); + assertEquals(Arrays.asList(11, 12, 13, 14, 15, 16, 17, 18, 19, 20), page.getData()); + } + + @Test + public void testInMemoryPaginationLastPartialPage() { + List data = new ArrayList<>(); + for (int i = 1; i <= 25; i++) { + data.add(i); + } + PageResult page = PageUtils.getPage(data, 3, 10); + assertEquals(Arrays.asList(21, 22, 23, 24, 25), page.getData()); + } + + @Test + public void testInMemoryPaginationBeyondEndIsEmpty() { + PageResult page = PageUtils.getPage(Arrays.asList(1, 2, 3), 5, 10); + assertTrue(page.getData().isEmpty()); + assertEquals(3, page.getPagination().getTotal()); + } + + @Test + public void testInMemoryPaginationInvalidInputReturnsWholeList() { + PageResult page = PageUtils.getPage(Arrays.asList(1, 2, 3), 0, 0); + assertEquals(Arrays.asList(1, 2, 3), page.getData()); + assertEquals(0, page.getPagination().getPage()); + assertEquals(0, page.getPagination().getSize()); + } + + @Test + public void testPageHelperVariantWrapsSupplierResult() { + PageResult page = PageUtils.getPage(() -> Arrays.asList("a", "b"), 1, 10); + assertEquals(Arrays.asList("a", "b"), page.getData()); + assertEquals(1, page.getPagination().getPage()); + assertEquals(10, page.getPagination().getSize()); + } + + @Test + public void testPageHelperVariantDefaultsInvalidPaging() { + PageResult page = PageUtils.getPage(() -> Arrays.asList("a"), null, 0); + // page defaults to 1; size defaults to Integer.MAX_VALUE + assertEquals(1, page.getPagination().getPage()); + assertEquals(Integer.MAX_VALUE, page.getPagination().getSize()); + assertEquals(Arrays.asList("a"), page.getData()); + } +} diff --git a/datapoly-template/src/test/java/com/cs/template/TemplateTest.java b/datapoly-test/src/test/java/com/cs/template/TemplateTest.java similarity index 100% rename from datapoly-template/src/test/java/com/cs/template/TemplateTest.java rename to datapoly-test/src/test/java/com/cs/template/TemplateTest.java diff --git a/datapoly-template/src/test/java/com/cs/template/XmlSqlTemplateDollarTest.java b/datapoly-test/src/test/java/com/cs/template/XmlSqlTemplateDollarTest.java similarity index 100% rename from datapoly-template/src/test/java/com/cs/template/XmlSqlTemplateDollarTest.java rename to datapoly-test/src/test/java/com/cs/template/XmlSqlTemplateDollarTest.java diff --git a/datapoly-core/src/test/resources/META-INF/services/com.cs.core.extension.ApiAssignmentPostProcessor b/datapoly-test/src/test/resources/META-INF/services/com.cs.core.extension.ApiAssignmentPostProcessor similarity index 100% rename from datapoly-core/src/test/resources/META-INF/services/com.cs.core.extension.ApiAssignmentPostProcessor rename to datapoly-test/src/test/resources/META-INF/services/com.cs.core.extension.ApiAssignmentPostProcessor diff --git a/docker-maven-build.sh b/docker-maven-build.sh index 71f8a38..f4cd635 100644 --- a/docker-maven-build.sh +++ b/docker-maven-build.sh @@ -1,12 +1,11 @@ #!/bin/sh -# maven:3.6.3-jdk-8 只有 amd64 镜像,Apple Silicon 上无法运行; -# 改用基于 Eclipse Temurin 8 的镜像(同为 JDK 8,与 CI 的 temurin 8 对齐,且支持 arm64) +# 构建基线为 JDK 25(LTS,与 CI 的 temurin 25 对齐),镜像支持 arm64 set -e -# 先装配宿主扩展(build-extension.sh:宿主机同步 git 仓库 + JDK 8 构建后端 + 投放 lib-extra, -# 无配置时无操作;容器内 temurin 8 只负责宿主自身的 clean package);maven 镜像不含 node,不单独跑 npm。 +# 先装配宿主扩展(build-extension.sh:宿主机同步 git 仓库 + JDK 25 构建后端 + 投放 lib-extra, +# 无配置时无操作;容器内 temurin 25 只负责宿主自身的 clean package);maven 镜像不含 node,不单独跑 npm。 # 传 "debug" 可构建 devtools 可用的调试版 UI(透传给 build-ui.sh),仅限本机联调。 sh "$(dirname "$0")/build-extension.sh" sh "$(dirname "$0")/build-ui.sh" "$1" @@ -17,6 +16,6 @@ docker run -it --rm \ -v "$PWD":/usr/src/mymaven \ -w /usr/src/mymaven \ --entrypoint /usr/share/maven/bin/mvn \ - maven:3.9-eclipse-temurin-8 \ + maven:3.9-eclipse-temurin-25 \ -s /usr/src/mymaven/docker-maven-settings.xml clean package diff --git a/docker-maven-clean.sh b/docker-maven-clean.sh index 6e3f6de..84b8d4c 100644 --- a/docker-maven-clean.sh +++ b/docker-maven-clean.sh @@ -1,7 +1,6 @@ #!/bin/sh -# maven:3.6.3-jdk-8 只有 amd64 镜像,Apple Silicon 上无法运行; -# 改用基于 Eclipse Temurin 8 的镜像(同为 JDK 8,与 CI 的 temurin 8 对齐,且支持 arm64) +# 构建基线为 JDK 25(LTS,与 CI 的 temurin 25 对齐),镜像支持 arm64 set -e docker run -it --rm \ @@ -10,6 +9,6 @@ docker run -it --rm \ -v "$PWD":/usr/src/mymaven \ -w /usr/src/mymaven \ --entrypoint /usr/share/maven/bin/mvn \ - maven:3.9-eclipse-temurin-8 \ + maven:3.9-eclipse-temurin-25 \ -s /usr/src/mymaven/docker-maven-settings.xml clean diff --git a/docs/en/build-deploy.md b/docs/en/build-deploy.md index 760b695..f996c16 100644 --- a/docs/en/build-deploy.md +++ b/docs/en/build-deploy.md @@ -8,13 +8,13 @@ This tool is developed in pure Java, with all dependencies from open-source proj - Requirements: - **JDK**: >=1.8 (JDK 1.8 recommended) + **JDK**: >=25 (JDK 25 LTS recommended) **Maven**: >=3.6 **Docker** (or a local Node): `build.sh` / `docker-maven-build.sh` use Docker to build the built-in UI - (`datapoly-manager-ui`, a Vue 2 + webpack 5 project built in a `node:23-alpine` container; the build is - verified on Node 23). UI assets are build artifacts and are not committed to git. + (`datapoly-manager-ui`, a Vue 2 + webpack 5 project built in a `node:24-alpine` container; the build is + verified on Node 24). UI assets are build artifacts and are not committed to git. > The Maven repository is hosted overseas by default, which can be slow in China. You can switch to the Alibaba Cloud > mirror. @@ -65,7 +65,7 @@ export DATAPOLY_EXTENSION_GIT_URL= # git-clone-able export DATAPOLY_EXTENSION_GIT_REF=master # branch/tag, defaults to master # -- or skip the env vars and clone the repository under datapoly-extension/ by hand once -# Assemble extensions only: build backend modules (host JDK 8 preferred, 8+ works too, compile target 1.8) +# Assemble extensions only: build backend modules (host JDK 25 required, below 25 not supported, compile target 25) # and drop the jars into lib-extra/ sh build-extension.sh diff --git a/docs/en/overview.md b/docs/en/overview.md index c991fb1..9862a01 100644 --- a/docs/en/overview.md +++ b/docs/en/overview.md @@ -26,7 +26,7 @@ DataPoly provides the following features: - **Document Database Support**: Supports MongoDB, ElasticSearch, and other document databases via DSL. - **API Version Management**: Supports API version control management. - **Batch Import/Export**: Supports bulk import and export of APIs. -- **LLM MCP Service**: Supports creating MCP tools with simple configuration. +- **LLM MCP Service**: Supports creating MCP tools with simple configuration, plus an admin MCP server (`/mcp/admin`, manage-scoped token) that lets AI agents perform full CRUD over datasources, modules, groups, APIs and clients. As a data access middleware in microservice architectures, DataPoly is suitable for the following scenarios: diff --git a/docs/en/usage.md b/docs/en/usage.md index 67da4c7..850f9dc 100644 --- a/docs/en/usage.md +++ b/docs/en/usage.md @@ -150,3 +150,61 @@ Semantics and boundaries: - The debug context carries the full query result and execution logs; extension jars belong to the host trust domain (same model as DataTaskSink) — do not forward results to untrusted destinations. + +## MCP services (data tools + entity CRUD for agents) + +The manager hosts **two independent MCP servers**, both authenticated through the +`?token=` query parameter (tokens are created on the management UI "MCP tokens" page; +address prefixes are served by `/datapoly/manager/api/v1/mcp/client/endpoint`): + +| Server | Endpoint (Streamable HTTP / SSE) | Token required | Purpose | +| --- | --- | --- | --- | +| Data MCP server `datapoly-mcp-server` | `/mcp` · `/mcp/sse` | any MCP token | Call registered data-API tools (business queries) | +| Admin MCP server `datapoly-mcp-admin-server` | `/mcp/admin` · `/mcp/admin/sse` | token with **manage** scope | Full CRUD over DataPoly entities and lifecycle ops | + +### Admin MCP server (letting agents operate DataPoly) + +The admin server wraps the manager's entity REST capabilities as tools named +`dp_{entity}_{action}`, covering the whole lifecycle: datasources (list/detail/create/ +update/delete/connectivity test/schema/table/column metadata), modules, authorization +groups, API assignments (list/detail/create/update/delete/SQL placeholder parsing/ +publish/deploy/retire/batch group change/version list/version detail/revert), +app clients (create/delete/secret/group grants) and MCP configuration (data-tool +registry, MCP token management). + +Creating a manage-scoped token (equivalent to an administrator — grant with care): + +```bash +# 1. log in to obtain a session token +curl -X POST http://:8090/datapoly/manager/api/v1/user/login \ + -H 'Content-Type: application/json' -d '{"username":"admin","password":"***"}' +# 2. create the manage token and reveal its plaintext +curl -X POST 'http://:8090/datapoly/manager/api/v1/mcp/client/create?name=agent-admin&manage=true' \ + -H 'Authorization: Bearer ' +curl 'http://:8090/datapoly/manager/api/v1/mcp/client/token/' -H 'Authorization: Bearer ' +``` + +Connecting an agent client (Claude Desktop / Cline style config): + +```json +{ + "mcpServers": { + "datapoly-admin": { + "url": "http://:8090/mcp/admin?token=" + } + } +} +``` + +SSE clients use `http://:8090/mcp/admin/sse?token=`. +Typical agent workflow: `dp_datasource_types` → `dp_datasource_create/test` → +`dp_api_parse_sql` → `dp_api_create` → `dp_api_publish` → `dp_api_deploy` (once online, +`dp_mcp_tool_create` registers the API on the data MCP server for LLM consumption). + +Security notes: +- A manage token is administrator-equivalent: it can read and write every entity and + reveal appSecrets and MCP token plaintext — store it like the admin password. +- `/mcp/**` (including the admin endpoints) bypasses the management-UI session auth; + the exposure surface matches the existing data MCP endpoints and only tokens + separate privileges. In production, reach the manager through an internal + network/gateway path only. diff --git a/docs/zh/build-deploy.md b/docs/zh/build-deploy.md index a062fe4..def49e8 100644 --- a/docs/zh/build-deploy.md +++ b/docs/zh/build-deploy.md @@ -8,12 +8,12 @@ - 环境要求: - **JDK**:>=1.8 (建议用JDK 1.8) + **JDK**:>=25(建议用 JDK 25,LTS) **maven**:>=3.6 **Docker**(或本机 Node):`build.sh` / `docker-maven-build.sh` 会用 Docker 构建内置管理端 UI - (`datapoly-manager-ui`,Vue 2 + webpack 5 工程,经 `node:23-alpine` 容器构建,Node 23 实测可构建)。 + (`datapoly-manager-ui`,Vue 2 + webpack 5 工程,经 `node:24-alpine` 容器构建,Node 24 实测可构建)。 UI 产物不随仓库提交,打包前由脚本自动生成。 > Maven 仓库默认在国外, 国内使用难免很慢,可以更换为阿里云的仓库。 @@ -63,7 +63,7 @@ export DATAPOLY_EXTENSION_GIT_URL=<扩展仓库地址> # 可用 git clone 的 export DATAPOLY_EXTENSION_GIT_REF=master # 分支/标签,默认 master # 方式二:不配置环境变量,手动把扩展仓库克隆到宿主根目录(只需一次) -# 单独装配扩展:构建 backend(宿主机 JDK 8 优先,无 8 时 8 以上 JDK 亦可用,编译目标 1.8)并投放 jar 到 lib-extra/ +# 单独装配扩展:构建 backend(宿主机 JDK 25,低于 25 不可用,编译目标 25)并投放 jar 到 lib-extra/ sh build-extension.sh # 或直接走常规打包:先自动装配扩展,再构建 UI、mvn 打包,产出带扩展的发行包 diff --git a/docs/zh/overview.md b/docs/zh/overview.md index 3464556..5a0b21a 100644 --- a/docs/zh/overview.md +++ b/docs/zh/overview.md @@ -25,7 +25,7 @@ DataPoly的功能包括: - **文档数据库支持**:支持通过DSL进行MongoDB和ElasticSearch等文档数据库开发接口。 - **接口版本管理**:支持接口的版本控制管理功能。 - **批量导入导出**:支持接口的批量导入导出功能。 -- **大模型MCP服务**:支持简单配置即可创建MCP的tool。 +- **大模型MCP服务**:支持简单配置即可创建MCP的tool;另提供管理MCP服务(`/mcp/admin`,manage 权限令牌),AI Agent 可对数据源、模块、分组、API、客户端等实体直接增删改查。 DataPoly作为微服务架构下的数据访问中间件,适合以下场景: diff --git a/docs/zh/usage.md b/docs/zh/usage.md index 47a40b1..920fb7a 100644 --- a/docs/zh/usage.md +++ b/docs/zh/usage.md @@ -135,3 +135,55 @@ public class AuditPostProcessor implements com.cs.core.extension.ApiAssignmentPo - 钩子在 manager 请求线程**同步**执行,请保持轻量,耗时操作建议自行转异步。 - 单个处理器抛异常只记 warn 日志,不影响管理接口响应、其余处理器与事件发布;更新在钩子触发前已提交,扩展失败不会回滚业务更新。 - 调试上下文携带完整查询结果与执行日志,扩展 jar 属宿主信任域(与 DataTaskSink 同一信任模型),请勿转发到不受信目的地。 + +## MCP 服务(数据工具 + 管理实体增删改查) + +manager 内置**两个相互独立的 MCP Server**,均通过 `?token=` 查询参数鉴权(令牌在管理页"MCP令牌"中创建,经 +`/datapoly/manager/api/v1/mcp/client/endpoint` 可查询地址前缀): + +| 服务 | 端点(Streamable HTTP / SSE) | 令牌要求 | 用途 | +| --- | --- | --- | --- | +| 数据 MCP 服务 `datapoly-mcp-server` | `/mcp` · `/mcp/sse` | 任意 MCP 令牌 | 调用已注册的数据 API tools(查询业务数据)| +| 管理 MCP 服务 `datapoly-mcp-admin-server` | `/mcp/admin` · `/mcp/admin/sse` | **manage 权限**令牌 | 对 DataPoly 实体做增删改查与上下线运维 | + +### 管理 MCP 服务(Agent 运维 DataPoly) + +管理服务把 manager 的实体管理 REST 能力包装为 `dp_{entity}_{action}` 命名的 tools,覆盖完整生命周期: +数据源(列表/详情/创建/更新/删除/连通性测试/schema/表/字段元信息)、模块、授权分组、 +API 定义(列表/详情/创建/更新/删除/SQL占位符解析/发布/上线/下线/批量改组/版本查询/版本详情/回退)、 +应用客户端(创建/删除/密钥/授权分组)、MCP 配置(数据工具注册表、MCP 令牌管理)。 + +创建 manage 令牌(等同管理员权限,谨慎授予): + +```bash +# 1. 登录取会话 token +curl -X POST http://:8090/datapoly/manager/api/v1/user/login \ + -H 'Content-Type: application/json' -d '{"username":"admin","password":"***"}' +# 2. 创建 manage 令牌并取明文 +curl -X POST 'http://:8090/datapoly/manager/api/v1/mcp/client/create?name=agent-admin&manage=true' \ + -H 'Authorization: Bearer <会话token>' +curl 'http://:8090/datapoly/manager/api/v1/mcp/client/token/' -H 'Authorization: Bearer <会话token>' +``` + +Agent 客户端接入(以 Claude Desktop / Cline 类客户端为例): + +```json +{ + "mcpServers": { + "datapoly-admin": { + "url": "http://:8090/mcp/admin?token=" + } + } +} +``` + +SSE 客户端使用 `http://:8090/mcp/admin/sse?token=`。 +典型 Agent 工作流:`dp_datasource_types` → `dp_datasource_create/test` → `dp_api_parse_sql` → +`dp_api_create` → `dp_api_publish` → `dp_api_deploy`(上线后可用 `dp_mcp_tool_create` 把 API +注册进数据 MCP 服务供大模型调用)。 + +安全须知: +- manage 令牌等同管理员:可读写全部实体、读取 appSecret 与 MCP 令牌明文,请像保管管理员密码一样保管。 +- `/mcp/**`(含 admin 端点)不走管理页会话鉴权,网络暴露面与既有数据 MCP 端点一致,仅靠令牌区分权限; + 生产环境建议仅经内网/网关侧到达 manager。 + diff --git a/lib-extra/README.md b/lib-extra/README.md index f03d374..49f73b6 100644 --- a/lib-extra/README.md +++ b/lib-extra/README.md @@ -6,7 +6,7 @@ classpath(三服务的 `bin/datapolyctl.sh` 均以 `lib/common/*` 通配加载 宿主扩展(根目录 `datapoly-extension/`,已被 `.gitignore` 排除,独立 git 仓库)构建后的产物由入库脚本 `build-extension.sh` 自动投放到这里——`build.sh` / `docker-maven-build.sh` 会先调用它:配置 `DATAPOLY_EXTENSION_GIT_URL` -或本地已有 `datapoly-extension/` 目录时即构建 backend(宿主机 JDK 8 优先、无 8 时 8 以上可用)并投放本目录,两者皆无则无操作(纯开源构建零影响)。本地一键入口仍是 `dev-local/dev.sh`(薄包装:注入 env.sh 后转发)。第三方额外 jar 也可手工投放。 +或本地已有 `datapoly-extension/` 目录时即构建 backend(宿主机 JDK 25,低于 25 不可用)并投放本目录,两者皆无则无操作(纯开源构建零影响)。本地一键入口仍是 `dev-local/dev.sh`(薄包装:注入 env.sh 后转发)。第三方额外 jar 也可手工投放。 - 仅 `*.jar` 参与装配;本文件与 `.gitkeep` 只占位,保证目录在干净 clone 中恒存在。 - 手工清理(回归纯开源构建):`rm -f lib-extra/*.jar` diff --git a/pom.xml b/pom.xml index e4c12d3..b6823d6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,19 +34,18 @@ scm:git:https://github.com/camilesing/datapoly.git - 1.8 - 1.18.46 - 3.1.1 + 25 + 1.18.48 + 3.5.1 false - 1.8 - 1.8 + 25 UTF-8 UTF-8 - - 2.7.18 - 2021.0.9 + + 3.5.16 + 2025.0.3 @@ -59,6 +58,7 @@ datapoly-executor datapoly-gateway datapoly-manager + datapoly-test datapoly-dist @@ -82,17 +82,16 @@ import - + com.alibaba.cloud spring-cloud-alibaba-dependencies - 2021.0.4.0 + 2025.0.0.0 pom import - + org.projectlombok lombok @@ -102,13 +101,14 @@ org.apache.httpcomponents.client5 httpclient5 - 5.4.3 + 5.6.4 + com.hazelcast - hazelcast-all - 4.2.8 + hazelcast + 5.7.0 @@ -129,71 +129,75 @@ cn.hutool hutool-all - 5.8.38 + 5.8.47 com.google.guava guava - 32.1.3-jre + 33.7.1-jre org.apache.commons commons-collections4 - 4.4 + 4.6.0 org.apache.commons commons-lang3 - 3.18.0 + 3.20.0 + - io.springfox - springfox-swagger2 - 3.0.0 + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.17 - io.springfox - springfox-swagger-ui - 3.0.0 + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.55 - io.springfox - springfox-boot-starter - 3.0.0 + io.swagger.core.v3 + swagger-models-jakarta + 2.2.55 + - mysql - mysql-connector-java - 8.0.33 + com.mysql + mysql-connector-j + 8.4.0 org.mybatis.spring.boot mybatis-spring-boot-starter - 2.2.2 + 3.0.5 org.mybatis mybatis - 3.5.9 + 3.5.19 + com.baomidou - mybatis-plus-boot-starter - 3.5.4.1 + mybatis-plus-spring-boot3-starter + 3.5.17 com.github.pagehelper pagehelper-spring-boot-starter - 1.4.2 + 2.1.1 @@ -224,10 +228,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + org.apache.maven.plugins maven-jar-plugin - 3.1.2 + ${maven-jar-plugin.version} **/*.xml @@ -240,7 +249,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.8 + 3.9.0 copy-dependencies