From bb5ae7b7d6a2cb841a94fab53dd2a907b17b3570 Mon Sep 17 00:00:00 2001 From: DdddM123 Date: Tue, 13 Jan 2026 17:11:06 +0800 Subject: [PATCH 1/3] =?UTF-8?q?2in1=E9=80=82=E9=85=8D=EF=BC=9A=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=94=B6=E8=B5=B7=E9=94=AE=E7=9B=98=E3=80=81=E7=A6=81?= =?UTF-8?q?=E6=AD=A2=E7=82=B9=E5=87=BB=E6=9C=80=E5=B0=8F=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 3844 ++++++++++++++++++++++++++++++++++ package.json | 1 + src/device/device.ts | 94 +- src/runner/event_action.ts | 8 +- src/runner/fuzz.ts | 12 + src/runner/fuzz_options.ts | 2 + src/runner/runner_manager.ts | 2 +- 7 files changed, 3956 insertions(+), 7 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..71dd0ab --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3844 @@ +{ + "name": "haptest", + "version": "0.2.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "haptest", + "version": "0.2.1", + "license": "Apache License, Version 2.0", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.3", + "@types/ws": "^8.5.12", + "adm-zip": "^0.5.15", + "bjc": "^1.0.22", + "class-transformer": "^0.5.1", + "commander": "^12.1.0", + "express": "^4.19.2", + "graphology": "^0.25.4", + "graphology-shortest-path": "^2.1.0", + "haptest": "^0.2.1", + "image-hash": "^5.3.2", + "log4js": "^6.9.1", + "moment": "^2.30.1", + "openai": "^5.12.2", + "promise-socket": "7.0.0", + "ts-graphviz": "^2.1.2", + "ws": "^8.18.0" + }, + "bin": { + "haptest": "bin/haptest" + }, + "devDependencies": { + "@types/adm-zip": "^0.5.5", + "@types/express": "^4.17.21", + "@types/node": "^20.14.9", + "ts-node": "^10.9.2", + "vitest": "^3.2.4" + } + }, + "node_modules/@canvas/image-data": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", + "integrity": "sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==", + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cwasm/webp": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@cwasm/webp/-/webp-0.1.5.tgz", + "integrity": "sha512-ceIZQkyxK+s7mmItNcWqqHdOBiJAxYxTnrnPNgUNjldB1M9j+Bp/3eVIVwC8rUFyN/zoFwuT0331pyY3ackaNA==", + "license": "MIT", + "dependencies": { + "@canvas/image-data": "^1.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@ts-graphviz/adapter": { + "version": "2.0.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/@ts-graphviz/adapter/-/adapter-2.0.3.tgz", + "integrity": "sha512-wHSN23UdLz4vuYUBZCzq2/tfLicwStSo3cUWnzvMNxG2ngcuYauQCQInv4CI5IObq+PFol28RVrG9Ffa9BuIRA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "dependencies": { + "@ts-graphviz/common": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/ast": { + "version": "2.0.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/@ts-graphviz/ast/-/ast-2.0.3.tgz", + "integrity": "sha512-NhOgJdOHGSn5h5ydsFreLIKFBwQ59drzZ6y0B98+KeEMqduv5hXxcQoDabw8yzeNe9B92AfR5OpUYthcdAsYgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "dependencies": { + "@ts-graphviz/common": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/common": { + "version": "2.1.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/@ts-graphviz/common/-/common-2.1.2.tgz", + "integrity": "sha512-Wyh5fOZNYyNP1mymbcHg/9atWR33NhHWIDrNa4hfbel3v340YQ+q+LMwAuIPuPt1qXINvOEhkowO5dvJWqfnPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/core": { + "version": "2.0.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/@ts-graphviz/core/-/core-2.0.3.tgz", + "integrity": "sha512-EZ+XlSwjdLtscoBOnA/Ba6QBrmoxAR73tJFjnWxaJQsZxWBQv6bLUrDgZUdXkXRAOSkRHn0uXY6Wq/3SsV2WtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "dependencies": { + "@ts-graphviz/ast": "^2.0.3", + "@ts-graphviz/common": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/adm-zip": { + "version": "0.5.5", + "resolved": "https://repo.huaweicloud.com/repository/npm/@types/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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 + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.24.tgz", + "integrity": "sha512-Mbrt4SRlXSTWryOnHAh2d4UQ/E7n9lZyGSi6KgX+4hkuL9soYbLOVXVhnk/ODp12YsGc95f4pOvqywJ6kngUwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-escaper": { + "version": "3.0.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/@types/html-escaper/-/html-escaper-3.0.2.tgz", + "integrity": "sha512-A8vk09eyYzk8J/lFO4OUMKCmRN0rRzfZf4n3Olwapgox/PtTiU8zPYlL1UEkJ/WeHvV6v9Xnj3o/705PKz9r4Q==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "2.2.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/@types/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-NrVug5woqbvNZ0WX+Gv4R+L4TGddtmFek2u8RtccAgFZWtS9QXF2xCXY22/M4nzkaKF0q9Fc6M/5rxLDhfwc/A==", + "deprecated": "This is a stub types definition. json5 provides its own type definitions, so you do not need this installed.", + "dependencies": { + "json5": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "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/ws": { + "version": "8.5.12", + "resolved": "https://repo.huaweicloud.com/repository/npm/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@yomguithereal/helpers": { + "version": "1.1.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/@yomguithereal/helpers/-/helpers-1.1.1.tgz", + "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.15", + "resolved": "https://repo.huaweicloud.com/repository/npm/adm-zip/-/adm-zip-0.5.15.tgz", + "integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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, + "engines": { + "node": ">=12" + } + }, + "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/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bjc": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/bjc/-/bjc-1.0.22.tgz", + "integrity": "sha512-Vefbjbmb1vQaw4U4cBTdDVYhhKGRUemUgzLoOLeQblP4m6n5eJtgl/Bh0bhnY+Mepb08ZMudhdxGDFUp9YZ+Sg==", + "dependencies": { + "@types/html-escaper": "^3.0.2", + "@types/json5": "^2.2.0", + "commander": "^12.0.0", + "html-escaper": "^3.0.3", + "json5": "^2.2.3", + "log4js": "^6.7.1", + "typescript": "^5.1.6" + }, + "bin": { + "bjc": "bin/bjc" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "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.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "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": "12.1.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.39.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://repo.huaweicloud.com/repository/npm/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.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", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "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.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.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" + }, + "engines": { + "node": ">= 0.10.0" + }, + "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==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "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.1", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://repo.huaweicloud.com/repository/npm/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://repo.huaweicloud.com/repository/npm/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-indices": { + "version": "0.17.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/graphology-indices/-/graphology-indices-0.17.0.tgz", + "integrity": "sha512-A7RXuKQvdqSWOpn7ZVQo4S33O0vCfPBnUSf7FwE0zNCasqwZVUaCXePuWo5HBpWw68KJcwObZDHpFk6HKH6MYQ==", + "dependencies": { + "graphology-utils": "^2.4.2", + "mnemonist": "^0.39.0" + }, + "peerDependencies": { + "graphology-types": ">=0.20.0" + } + }, + "node_modules/graphology-shortest-path": { + "version": "2.1.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/graphology-shortest-path/-/graphology-shortest-path-2.1.0.tgz", + "integrity": "sha512-KbT9CTkP/u72vGEJzyRr24xFC7usI9Es3LMmCPHGwQ1KTsoZjxwA9lMKxfU0syvT/w+7fZUdB/Hu2wWYcJBm6Q==", + "dependencies": { + "@yomguithereal/helpers": "^1.1.1", + "graphology-indices": "^0.17.0", + "graphology-utils": "^2.4.3", + "mnemonist": "^0.39.0" + }, + "peerDependencies": { + "graphology-types": ">=0.20.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.7", + "resolved": "https://repo.huaweicloud.com/repository/npm/graphology-types/-/graphology-types-0.24.7.tgz", + "integrity": "sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==", + "peer": true + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, + "node_modules/haptest": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/haptest/-/haptest-0.2.1.tgz", + "integrity": "sha512-0nN/Ev/rS0quroIyAAMX6y3qEowYNoY2TCXbk0Pyr31WL/bFhK7cp4adsafygd/ttYjHU1eIKZnrG93jJ4c9gA==", + "license": "Apache License, Version 2.0", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.3", + "@types/ws": "^8.5.12", + "adm-zip": "^0.5.15", + "bjc": "^1.0.22", + "class-transformer": "^0.5.1", + "commander": "^12.1.0", + "express": "^4.19.2", + "graphology": "^0.25.4", + "graphology-shortest-path": "^2.1.0", + "image-hash": "^5.3.2", + "log4js": "^6.9.1", + "moment": "^2.30.1", + "openai": "^5.12.2", + "promise-socket": "7.0.0", + "ts-graphviz": "^2.1.2", + "ws": "^8.18.0" + }, + "bin": { + "haptest": "bin/haptest" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-hash": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/image-hash/-/image-hash-5.3.2.tgz", + "integrity": "sha512-of8SekDXKFoaK4R93dP/Lzw6+NRGag8Jr9YlIIZ9jJVn9KYLfYVo/ARbKtbRn+tdTz/wDzBObx6yflKpLSYbxA==", + "license": "MIT", + "dependencies": { + "@cwasm/webp": "^0.1.5", + "file-type": "^16.5.3", + "jpeg-js": "^0.4.0", + "pngjs": "^6.0.0", + "request": "^2.81.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://repo.huaweicloud.com/repository/npm/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://repo.huaweicloud.com/repository/npm/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://repo.huaweicloud.com/repository/npm/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openai": { + "version": "5.12.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.2.tgz", + "integrity": "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-duplex": { + "version": "6.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/promise-duplex/-/promise-duplex-6.0.0.tgz", + "integrity": "sha512-ZL7rquzjTFzInDBeWYcsT+qddolNvzigahk6MI6qLSbQvlyRRCJkU3JztgaVunzvkH28smRa2Qu/cY9RXtSkgA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.6.5", + "promise-readable": "^6.0.0", + "promise-writable": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/promise-readable": { + "version": "6.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/promise-readable/-/promise-readable-6.0.0.tgz", + "integrity": "sha512-5NxtmUswijvX5cAM0zPSy6yiCXH/eKBpiiBq6JfAUrmngMquMbzcBhF2qA+ocs4rYYKdvAfv3cOvZxADLtL1CA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.6.5" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/promise-socket": { + "version": "7.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/promise-socket/-/promise-socket-7.0.0.tgz", + "integrity": "sha512-Oic9BrxmcHOPEnzKp2Js+ehFyvsbd0WxsE5khweCTHuRvdzbXjHUZmSDT6F9TW8SIkAJ0lCzoHjMYnb0WQJPiw==", + "license": "MIT", + "dependencies": { + "promise-duplex": "^6.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/promise-writable": { + "version": "6.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/promise-writable/-/promise-writable-6.0.0.tgz", + "integrity": "sha512-b81zre/itgJFS7dwWzIdKNVVqvLiUxYRS/wolUB0H1YY/tAaS146XGKa4Q/5wCbsnXLyn0MCeV6f8HHe4iUHLg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rollup": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://repo.huaweicloud.com/repository/npm/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ts-graphviz": { + "version": "2.1.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/ts-graphviz/-/ts-graphviz-2.1.2.tgz", + "integrity": "sha512-9GnOA3yiFaqZeHBEZXWa6kqc61FVhAhxQU5g3KLyGrhRr7OsDGRzs+1z35ctvD+hTTEhrBza6D41+qz+3qs7Zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "dependencies": { + "@ts-graphviz/adapter": "^2.0.3", + "@ts-graphviz/ast": "^2.0.3", + "@ts-graphviz/common": "^2.1.2", + "@ts-graphviz/core": "^2.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "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==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://repo.huaweicloud.com/repository/npm/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://repo.huaweicloud.com/repository/npm/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://repo.huaweicloud.com/repository/npm/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": 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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.1.tgz", + "integrity": "sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "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/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.1.tgz", + "integrity": "sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://repo.huaweicloud.com/repository/npm/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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/yn": { + "version": "3.1.1", + "resolved": "https://repo.huaweicloud.com/repository/npm/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json index a68b410..69c8060 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "express": "^4.19.2", "graphology": "^0.25.4", "graphology-shortest-path": "^2.1.0", + "haptest": "^0.2.1", "image-hash": "^5.3.2", "log4js": "^6.9.1", "moment": "^2.30.1", diff --git a/src/device/device.ts b/src/device/device.ts index 6a68934..6aad8ec 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 +* http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -208,7 +208,18 @@ export class Device implements EventSimulator { `dumpViewTree attempt=${attempt} layoutType=${layout ? typeof layout : 'undefined'} pages=${pages.length}` ); // if exist keyboard then close and dump again. - if (this.closeKeyboard(pages)) { + // If device type is 2in1 (from options) send Enter to confirm input and skip hide action. + const rawDeviceTypeOpt = this.options && this.options.deviceType ? String(this.options.deviceType) : ''; + const deviceTypeOpt = rawDeviceTypeOpt.trim().toLowerCase(); + logger.debug(`dumpViewTree deviceType (from options): '${rawDeviceTypeOpt}' -> '${deviceTypeOpt}'`); + if (deviceTypeOpt === '2in1') { + logger.info('2in1 设备:发送回车以确认输入,跳过收起键盘逻辑'); + try { + await this.inputKey(KeyCode.KEYCODE_ENTER); + } catch (err) { + logger.warn('发送回车键失败', err); + } + } else if (this.closeKeyboard(pages)) { logger.info('Keyboard detected during dumpViewTree, sending hide event and retrying.'); // for sleep this.hdc.getDeviceUdid(); @@ -266,11 +277,54 @@ export class Device implements EventSimulator { return this.arkuiInspector.dump(bundleName, this.options.connectkey); } + /** + * Check if a point is inside a component's bounds + */ + private isPointInComponent(point: Point, component: Component): boolean { + if (!component.bounds || component.bounds.length < 2) { + return false; + } + const left = Math.min(component.bounds[0].x, component.bounds[1].x); + const right = Math.max(component.bounds[0].x, component.bounds[1].x); + const top = Math.min(component.bounds[0].y, component.bounds[1].y); + const bottom = Math.max(component.bounds[0].y, component.bounds[1].y); + + return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom; + } + + /** + * Check if a component is a control button that should be skipped + */ + private isControlButton(component: Component): boolean { + const controlButtonIds = ['EnhanceMaximizeBtn', 'EnhanceMinimizeBtn', 'EnhanceCloseBtn']; + const id = component.id || ''; + const key = component.key || ''; + + return controlButtonIds.includes(id) || controlButtonIds.includes(key); + } + /** * Simulate a single click * @param point */ async click(point: Point): Promise { + const deviceType = this.getDeviceType().trim().toLowerCase(); + + if (deviceType === '2in1') { + try { + const page = await this.dumpViewTree(); + const components = page.getComponents(); + for (const comp of components) { + if (this.isPointInComponent(point, comp) && this.isControlButton(comp)) { + logger.info(`2in1 device: skipping click on control button ${comp.id || comp.key}`); + return; + } + } + } catch (err) { + logger.warn('Error detecting component at click point', err); + } + } + await this.driverCtx?.driver?.click(point.x, point.y); } @@ -279,6 +333,23 @@ export class Device implements EventSimulator { * @param point */ async doubleClick(point: Point): Promise { + const deviceType = this.getDeviceType().trim().toLowerCase(); + + if (deviceType === '2in1') { + try { + const page = await this.dumpViewTree(); + const components = page.getComponents(); + for (const comp of components) { + if (this.isPointInComponent(point, comp) && this.isControlButton(comp)) { + logger.info(`2in1 device: skipping double click on control button ${comp.id || comp.key}`); + return; + } + } + } catch (err) { + logger.warn('Error detecting component at double click point', err); + } + } + await this.driverCtx?.driver?.doubleClick(point.x, point.y); } @@ -287,6 +358,23 @@ export class Device implements EventSimulator { * @param point */ async longClick(point: Point): Promise { + const deviceType = this.getDeviceType().trim().toLowerCase(); + + if (deviceType === '2in1') { + try { + const page = await this.dumpViewTree(); + const components = page.getComponents(); + for (const comp of components) { + if (this.isPointInComponent(point, comp) && this.isControlButton(comp)) { + logger.info(`2in1 device: skipping long click on control button ${comp.id || comp.key}`); + return; + } + } + } catch (err) { + logger.warn('Error detecting component at long click point', err); + } + } + await this.driverCtx?.driver?.longClick(point.x, point.y); } @@ -594,4 +682,4 @@ export class Device implements EventSimulator { fs.renameSync(localPath, targetPath); } } -} +} \ No newline at end of file diff --git a/src/runner/event_action.ts b/src/runner/event_action.ts index 201e228..7e20efc 100644 --- a/src/runner/event_action.ts +++ b/src/runner/event_action.ts @@ -21,19 +21,21 @@ import { Event } from '../event/event'; import { Hap } from '../model/hap'; import { SerializeUtils } from '../utils/serialize_utils'; import { Page } from '../model/page'; -import { getLogger } from 'log4js'; +import { HapTestLogger } from '../utils/logger'; import { Transition } from '../model/ptg'; -const logger = getLogger(); +const logger = HapTestLogger.getLogger(); export class EventAction { device: Device; hap: Hap; + deviceType?: string; transition: Transition; output: string; - constructor(device: Device, hap: Hap, page: Page, event: Event) { + constructor(device: Device, hap: Hap, page: Page, event: Event, deviceType?: string) { this.device = device; this.hap = hap; + this.deviceType = deviceType; this.transition = { from: page, event: event, to: page }; this.output = path.join(device.getOutput(), 'events'); if (!fs.existsSync(this.output)) { diff --git a/src/runner/fuzz.ts b/src/runner/fuzz.ts index 0b2fc9a..8ba5256 100644 --- a/src/runner/fuzz.ts +++ b/src/runner/fuzz.ts @@ -17,6 +17,9 @@ import { Device } from '../device/device'; import { GlobMatch } from '../utils/glob_match'; import { FuzzOptions } from './fuzz_options'; import { RunnerManager } from './runner_manager'; +import { HapTestLogger } from '../utils/logger'; + +const logger = HapTestLogger.getLogger(); /** * Fuzz test entrance @@ -31,6 +34,15 @@ export class Fuzz { } async start() { + // 在开始执行时,先检测设备类型并保存到 options 中 + try { + const deviceType = this.device.getDeviceType(); + this.options.deviceType = deviceType; + logger.info(`Detected device type: ${deviceType}`); + console.log(`设备类型: ${deviceType}`); + } catch (err) { + logger.error('Failed to detect device type.', err); + } if (this.options.bundleName !== 'ALL') { await this.startOneBundle(this.options.bundleName); return; diff --git a/src/runner/fuzz_options.ts b/src/runner/fuzz_options.ts index 1cc57f7..801fbd2 100644 --- a/src/runner/fuzz_options.ts +++ b/src/runner/fuzz_options.ts @@ -24,6 +24,8 @@ export interface FuzzOptions { hapFile?: string; reportRoot?: string; excludes?: string[]; + // 设备类型,例如: phone, tablet, wearable, car, tv, 2in1 + deviceType?: string; // xmq: add cli option llm?: boolean; simK: number; diff --git a/src/runner/runner_manager.ts b/src/runner/runner_manager.ts index e900ae4..dd9ba01 100644 --- a/src/runner/runner_manager.ts +++ b/src/runner/runner_manager.ts @@ -117,7 +117,7 @@ export class RunnerManager { } protected async addEvent(page: Page, event: Event): Promise { - let eventExcute = new EventAction(this.device, this.hap, page, event); + let eventExcute = new EventAction(this.device, this.hap, page, event, this.options.deviceType); await eventExcute.start(); // sleep interval await sleep(EVENT_INTERVAL); From cdd34571b2b409be4822f38ff6b42515fec70688 Mon Sep 17 00:00:00 2001 From: DdddM123 Date: Wed, 4 Mar 2026 11:10:50 +0800 Subject: [PATCH 2/3] Feat_2in1 device adaptation --- scripts/extract_viewtree.js | 72 ++++++ scripts/format_and_analyze.js | 119 ++++++++++ scripts/run_haptests.sh | 111 ++++++++++ src/cli/cli.ts | 39 ++++ src/device/device.ts | 192 +++++++++++++++- src/runner/event_action.ts | 8 + src/utils/dynamic_compare.ts | 397 ++++++++++++++++++++++++++++++++++ 7 files changed, 926 insertions(+), 12 deletions(-) create mode 100644 scripts/extract_viewtree.js create mode 100644 scripts/format_and_analyze.js create mode 100755 scripts/run_haptests.sh create mode 100644 src/utils/dynamic_compare.ts diff --git a/scripts/extract_viewtree.js b/scripts/extract_viewtree.js new file mode 100644 index 0000000..3f1172f --- /dev/null +++ b/scripts/extract_viewtree.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); + +function fmtBounds(bounds) { + if (!bounds || !Array.isArray(bounds) || bounds.length < 2) return ''; + const a = bounds[0]; + const b = bounds[1]; + return `bounds=${a.x},${a.y}-${b.x},${b.y}`; +} + +function nodeSummary(node) { + if (!node || typeof node !== 'object') return ''; + const parts = []; + if (node.type) parts.push(node.type); + const b = fmtBounds(node.bounds || node.origBounds); + if (b) parts.push(b); + if (node.id) parts.push(`id=${node.id}`); + if (node.key) parts.push(`key=${node.key}`); + if (node.text) parts.push(`text=${String(node.text).replace(/\s+/g, ' ').slice(0,80)}`); + return parts.join(' | '); +} + +function walk(node, indent, lines) { + if (!node || typeof node !== 'object') return; + const summary = nodeSummary(node) || '(no-type)'; + lines.push(`${' '.repeat(indent)}- ${summary}`); + const children = node.children; + if (Array.isArray(children) && children.length > 0) { + for (const c of children) walk(c, indent + 1, lines); + } +} + +if (process.argv.length < 3) { + console.error('Usage: node scripts/extract_viewtree.js '); + process.exit(2); +} + +const infile = process.argv[2]; +if (!fs.existsSync(infile)) { + console.error('File not found:', infile); + process.exit(2); +} + +let obj; +try { + obj = JSON.parse(fs.readFileSync(infile, 'utf8')); +} catch (e) { + console.error('JSON parse error:', e.message); + process.exit(2); +} + +const outPath = infile.replace(/\.json$/i, '_viewtree.txt'); +const lines = []; + +function extractSide(sideName) { + const root = obj[sideName] && obj[sideName].viewTree && obj[sideName].viewTree.root; + lines.push(`${sideName.toUpperCase()} VIEWTREE:`); + if (!root) { + lines.push(' (no viewTree.root)'); + lines.push(''); + return; + } + walk(root, 0, lines); + lines.push(''); +} + +extractSide('from'); +extractSide('to'); + +fs.writeFileSync(outPath, lines.join('\n'), 'utf8'); +console.log('Wrote:', outPath); diff --git a/scripts/format_and_analyze.js b/scripts/format_and_analyze.js new file mode 100644 index 0000000..fc67c69 --- /dev/null +++ b/scripts/format_and_analyze.js @@ -0,0 +1,119 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); + +function maxDepth(obj) { + if (obj === null || typeof obj !== 'object') return 0; + if (Array.isArray(obj)) { + let m = 0; + for (const v of obj) m = Math.max(m, maxDepth(v)); + return 1 + m; + } + let m = 0; + for (const k of Object.keys(obj)) m = Math.max(m, maxDepth(obj[k])); + return 1 + m; +} + +function countViewNodes(node) { + if (!node || typeof node !== 'object') return 0; + let count = 0; + if (node.type) count = 1; + const children = node.children || []; + for (const c of children) count += countViewNodes(c); + return count; +} + +function safeGet(o, pathArr) { + try { + let cur = o; + for (const p of pathArr) { + if (cur == null) return undefined; + cur = cur[p]; + } + return cur; + } catch (e) { + return undefined; + } +} + +if (process.argv.length < 3) { + console.error('Usage: node scripts/format_and_analyze.js '); + process.exit(2); +} + +const infile = process.argv[2]; +if (!fs.existsSync(infile)) { + console.error('File not found:', infile); + process.exit(2); +} +const raw = fs.readFileSync(infile, 'utf8'); +let obj; +try { + obj = JSON.parse(raw); +} catch (e) { + console.error('JSON parse error:', e.message); + process.exit(2); +} + +const pretty = JSON.stringify(obj, null, 2); +const outPretty = infile.replace(/\.json$/i, '_pretty.json'); +fs.writeFileSync(outPretty, pretty, 'utf8'); + +// Analysis +const stats = fs.statSync(infile); +const analysis = []; +analysis.push('File: ' + infile); +analysis.push('Size: ' + stats.size + ' bytes'); +analysis.push('Pretty JSON: ' + outPretty); + +const topKeys = Object.keys(obj); +analysis.push('Top-level keys: ' + topKeys.join(', ')); +analysis.push('Top-level key count: ' + topKeys.length); +analysis.push('Max object depth: ' + maxDepth(obj)); + +// Try to detect viewTree nodes +let totalViewNodes = 0; +const fromViewRoot = safeGet(obj, ['from','viewTree','root']); +const toViewRoot = safeGet(obj, ['to','viewTree','root']); +if (fromViewRoot) totalViewNodes += countViewNodes(fromViewRoot); +if (toViewRoot) totalViewNodes += countViewNodes(toViewRoot); +if (totalViewNodes > 0) analysis.push('Estimated total view nodes (from+to): ' + totalViewNodes); + +const eventType = safeGet(obj, ['event','type']) || safeGet(obj, ['type']); +if (eventType) analysis.push('Event type: ' + eventType); + +const fromAbility = safeGet(obj, ['from','abilityName']) || safeGet(obj, ['from','ability']); +const toAbility = safeGet(obj, ['to','abilityName']) || safeGet(obj, ['to','ability']); +if (fromAbility) analysis.push('From ability: ' + fromAbility); +if (toAbility) analysis.push('To ability: ' + toAbility); + +const fromPage = safeGet(obj, ['from','pagePath']); +const toPage = safeGet(obj, ['to','pagePath']); +if (fromPage) analysis.push('From pagePath: ' + fromPage); +if (toPage) analysis.push('To pagePath: ' + toPage); + +const fromCap = safeGet(obj, ['from','snapshot','screenCapPath']); +const toCap = safeGet(obj, ['to','snapshot','screenCapPath']); +if (fromCap) analysis.push('From screenCap: ' + fromCap); +if (toCap) analysis.push('To screenCap: ' + toCap); + +// Summarize first-level differences between from and to (common keys) +const diffs = []; +for (const k of topKeys) { + if (k === 'from' || k === 'to') continue; +} + +// Provide a short sample of 'to' top-level structure (first 200 chars) +try { + const sample = JSON.stringify(obj.to || obj, null, 2).slice(0, 2000); + analysis.push('Sample of `to` (truncated):'); + analysis.push(sample); +} catch (e) {} + +const outAnalysis = infile.replace(/\.json$/i, '.analysis.txt'); +fs.writeFileSync(outAnalysis, analysis.join('\n'), 'utf8'); + +console.log('Wrote pretty JSON to:', outPretty); +console.log('Wrote analysis to:', outAnalysis); +console.log('Summary:'); +console.log(analysis.join('\n')); diff --git a/scripts/run_haptests.sh b/scripts/run_haptests.sh new file mode 100755 index 0000000..ab5c1ca --- /dev/null +++ b/scripts/run_haptests.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +set -uo pipefail + +# 超时时间(秒),可通过环境变量覆盖,例如: +# TIMEOUT_SECS=300 ./run_haptests.sh +TIMEOUT_SECS="${TIMEOUT_SECS:-600}" + +# 在下面的数组中按顺序添加要执行的命令(每条一行)。 +# 保持格式为: node bin/haptest -i com.huawei.hmos.* -o out/* + +# 万兴喵影(剪辑软件逻辑太复杂无法测试)"node bin/haptest -i cn.wondershare.filmora.2in1 -o out/2in1/wondershare" + # 钉钉(暂时无法登陆)"node bin/haptest -i com.dingtalk.hmos.pc -o out/2in1/dingtalk" +COMMANDS=( + #"node bin/haptest -i cn.wps.office.hap -o out/2in1/wps" + "node bin/haptest -i com.eastmoney.emapp -o out/2in1/eastmoney" + "node bin/haptest -i com.edrawsoft.edrawmax.pc -o out/2in1/edrawsoft_edrawmax" + "node bin/haptest -i com.edrawsoft.mindmaster.pc -o out/2in1/edrawsoft_mindmaster" + "node bin/haptest -i com.example.first -o out/2in1/example_first" + "node bin/haptest -i com.example.memoryleak -o out/2in1/example_memoryleak" + "node bin/haptest -i com.foxit.foxitpdfeditor -o out/2in1/foxitpdfeditor" + "node bin/haptest -i com.haitai.htbrowser -o out/2in1/haitai_htbrowser" + "node bin/haptest -i com.haozip2345.app -o out/2in1/haozip2345" + "node bin/haptest -i com.hos.moonshot.kimichat -o out/2in1/moonshot_kimichat" + "node bin/haptest -i com.hp.printercontrol.china -o out/2in1/hp_printercontrol" + "node bin/haptest -i com.oray.sunloginclient -o out/2in1/oray_sunloginclient" + "node bin/haptest -i com.quark.ohosbrowser -o out/2in1/quark_ohosbrowser" + "node bin/haptest -i com.renyitu.pumpkinssh -o out/2in1/renyitu_pumpkinssh" + "node bin/haptest -i com.ss.ohpc.ugc.aweme -o out/2in1/ss_ohpc_ugc_aweme" + "node bin/haptest -i com.tencent.harmonyqq -o out/2in1/harmonyqq" + "node bin/haptest -i com.tencent.wechat.pc -o out/2in1/wechat" + "node bin/haptest -i com.usb.right -o out/2in1/usb_right" + "node bin/haptest -i com.wifiservice.portallogin -o out/2in1/wifiservice_portallogin" + "node bin/haptest -i com.xunlei.thunder -o out/2in1/xunlei_thunder" + "node bin/haptest -i com.xunlei.xmp -o out/2in1/xunlei_xmp" + "node bin/haptest -i com.zhihu.hmos -o out/2in1/zhihu" + "node bin/haptest -i com.zuler.ohospc.todesk -o out/2in1/zuler_ohospc_todesk" + "node bin/haptest -i com.zwsoft.zwcad.PE -o out/2in1/zwsoft_zwcad" + "node bin/haptest -i yylx.danmaku.bili -o out/2in1/yylx_danmaku_bili" +) + + +run_with_timeout() { + local cmd="$1" + + # Prefer coreutils `timeout` if available + if command -v timeout >/dev/null 2>&1; then + timeout "${TIMEOUT_SECS}" bash -c "$cmd" + return $? + fi + + # Fallback implementation using background process and manual kill + bash -c "$cmd" & + local pid=$! + local start_ts + start_ts=$(date +%s) + + while kill -0 "$pid" >/dev/null 2>&1; do + sleep 1 + local now + now=$(date +%s) + local elapsed=$((now - start_ts)) + if [ "$elapsed" -ge "$TIMEOUT_SECS" ]; then + echo "Timeout (${TIMEOUT_SECS}s) reached for PID $pid, terminating..." + kill -TERM "$pid" >/dev/null 2>&1 || true + sleep 2 + kill -KILL "$pid" >/dev/null 2>&1 || true + wait "$pid" 2>/dev/null || true + return 124 + fi + done + + wait "$pid" + return $? +} + +for cmd in "${COMMANDS[@]}"; do + if [[ -z "$cmd" ]]; then + continue + fi + + echo "Running: $cmd (timeout ${TIMEOUT_SECS}s)" + run_with_timeout "$cmd" + rc=$? + + if [ $rc -eq 0 ]; then + echo "Command finished successfully." + elif [ $rc -eq 124 ]; then + echo "Command timed out after ${TIMEOUT_SECS}s — retrying once..." + # retry once + run_with_timeout "$cmd" + rc2=$? + if [ $rc2 -eq 0 ]; then + echo "Retry succeeded." + elif [ $rc2 -eq 124 ]; then + echo "Retry also timed out after ${TIMEOUT_SECS}s — skipping to next." + else + echo "Retry exited with status $rc2 — continuing to next." + fi + else + echo "Command exited with status $rc — continuing to next." + fi +done + +# 处理 JSON 文件并生成结构化输出 +for json_file in events/*.json; do + if [[ -f "$json_file" ]]; then + node scripts/format_and_analyze.js "$json_file" + fi +done + +echo "All commands finished." diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 44e61da..f3b68bb 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -22,6 +22,7 @@ import { FuzzOptions } from '../runner/fuzz_options'; import { EnvChecker } from './env_checker'; import { HapTestLogger, LOG_LEVEL } from '../utils/logger'; import { startUIViewerServer } from '../ui/ui_viewer_server'; +import { compareDynamicLogs } from '../utils/dynamic_compare'; const logger = getLogger(); @@ -97,6 +98,25 @@ async function runUIViewerCommand(options: any, version: string): Promise }); } +async function runCompareCommand(options: any): Promise { + const outputDir = path.resolve(options.output ?? 'out'); + const logLevel = resolveLogLevel(options); + HapTestLogger.configure(path.join(outputDir, 'haptest.log'), logLevel); + + const reportPath = options.report + ? path.resolve(options.report) + : path.join(outputDir, `compare_${options.app}_mobile_2in1.json`); + + compareDynamicLogs({ + outputRoot: outputDir, + appFolder: options.app, + mobileDir: options.mobile, + twoInOneDir: options.twoInOne, + reportPath, + fullWidthTolerance: Number(options.tolerance), + }); +} + (async function (): Promise { const packageCfg = parsePackageConfig(); @@ -118,6 +138,25 @@ async function runUIViewerCommand(options: any, version: string): Promise } }); + program + .command('compare') + .description('Compare mobile and 2in1 dynamic logs for the same app') + .requiredOption('-a, --app ', 'app log folder name under each device directory') + .option('-o, --output ', 'output dir', 'out') + .option('--mobile ', 'mobile device folder name', 'mobile') + .option('--twoInOne ', '2in1 device folder name', '2in1') + .option('--report ', 'output report file path') + .option('--tolerance ', 'full width tolerance in px', '1') + .option('--debug', 'debug log level', false) + .action(async (cmdOptions) => { + try { + await runCompareCommand(cmdOptions); + } catch (err) { + logger.error('haptest compare command failed.', err); + process.exit(1); + } + }); + program .description('HapTest fuzz runner') .option('-i, --hap ', 'HAP bundle name or HAP file path or HAP project source root (can specify multiple)') diff --git a/src/device/device.ts b/src/device/device.ts index 6aad8ec..5e79a8a 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -49,12 +49,15 @@ export class Device implements EventSimulator { private displaySize?: Point; private udid: string; private options: FuzzOptions; + private readonly is2in1Device: boolean; private arkuiInspector: ArkUIInspector; private lastFaultlogs: Set; private driverCtx?: DriverContext; constructor(options: FuzzOptions) { this.options = options; + const rawDeviceTypeOpt = options && options.deviceType ? String(options.deviceType) : ''; + this.is2in1Device = rawDeviceTypeOpt.trim().toLowerCase() === '2in1'; this.hdc = new Hdc(options.connectkey); this.output = path.join(path.resolve(options.output), moment().format('YYYY-MM-DD-HH-mm-ss')); this.udid = this.hdc.getDeviceUdid(); @@ -193,6 +196,125 @@ export class Device implements EventSimulator { this.hdc.forceStop(bundleName); } + /** + * Ensure the application is in fullscreen mode. + * If not, find and click the EnhanceMaximizeBtn to maximize the window. + * @param hap The HAP instance to check + * @returns true if already fullscreen or successfully maximized, false otherwise + */ + async ensureFullscreen(hap: Hap): Promise { + try { + // Wait a bit for the app to fully load after launch + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Get all pages from dumpViewTree, not just the "current" one + // because the app window might not be the largest page yet + let layout = await this.driverCtx!.driver.dumpLayout(); + let pages = PageBuilder.buildPagesFromLayout(layout); + + // Try to find the page that matches the target bundle name + let targetPage: Page | undefined; + for (const p of pages) { + if (p.getBundleName() === hap.bundleName && !p.isStop() && !p.isBackground()) { + targetPage = p; + break; + } + } + + // If not found, use the largest page (similar to dumpViewTree logic) + if (!targetPage && pages.length > 0) { + pages.sort((a: Page, b: Page) => { + return b.getRoot().getHeight() - a.getRoot().getHeight(); + }); + targetPage = pages[0]; + } + + if (!targetPage) { + logger.warn('No page found for fullscreen check'); + return false; + } + + const page = targetPage; + + // Check if the app is stopped or in background + if (page.isStop() || page.isBackground()) { + logger.debug('App is stopped or in background, skipping fullscreen check'); + return false; + } + + const root = page.getRoot(); + const windowWidth = root.getWidth(); + const windowHeight = root.getHeight(); + const screenWidth = this.getWidth(); + const screenHeight = this.getHeight(); + + // Check if window size matches screen size (with small tolerance for rounding) + const widthMatch = Math.abs(windowWidth - screenWidth) <= 10; + const heightMatch = Math.abs(windowHeight - screenHeight) <= 100; + + if (widthMatch && heightMatch) { + logger.debug('Application is already in fullscreen mode'); + return true; + } + + logger.info( + `Application is not fullscreen. Window: ${windowWidth}x${windowHeight}, Screen: ${screenWidth}x${screenHeight}. Attempting to maximize.` + ); + + // Find the EnhanceMaximizeBtn component (similar to closeKeyboard method) + // Debug: log page bundle name and component count + const pageBundleName = page.getBundleName(); + const components = page.getComponents(); + logger.debug(`ensureFullscreen: page bundleName=${pageBundleName}, hap bundleName=${hap.bundleName}, component count=${components.length}`); + + let maximizeBtnFound = false; + for (const component of components) { + // Debug: log components with id or key containing "Enhance" or "Maximize" + if (component.id && (component.id.includes('Enhance') || component.id.includes('Maximize'))) { + logger.debug(`Found component with id=${component.id}, key=${component.key}, type=${component.type}`); + } + if (component.key && (component.key.includes('Enhance') || component.key.includes('Maximize'))) { + logger.debug(`Found component with id=${component.id}, key=${component.key}, type=${component.type}`); + } + + if (component.id === 'EnhanceMaximizeBtn' || component.key === 'EnhanceMaximizeBtn') { + logger.info(`Found EnhanceMaximizeBtn, id=${component.id}, key=${component.key}, bounds=${JSON.stringify(component.bounds)}, clicking to maximize`); + this.sendEvent(new TouchEvent(component)); + maximizeBtnFound = true; + break; + } + } + + if (!maximizeBtnFound) { + logger.warn(`EnhanceMaximizeBtn not found. Searched ${components.length} components in page with bundleName=${pageBundleName}`); + return false; + } + + // Wait for the window to maximize + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify the window is now fullscreen + const updatedPage = await this.getCurrentPage(hap); + const updatedRoot = updatedPage.getRoot(); + const newWidth = updatedRoot.getWidth(); + const newHeight = updatedRoot.getHeight(); + + const newWidthMatch = Math.abs(newWidth - screenWidth) <= 2; + const newHeightMatch = Math.abs(newHeight - screenHeight) <= 2; + + if (newWidthMatch && newHeightMatch) { + logger.info('Successfully maximized application to fullscreen'); + return true; + } else { + logger.warn(`Maximize button clicked but window size still not fullscreen. New size: ${newWidth}x${newHeight}`); + return false; + } + } catch (error) { + logger.error('Error during fullscreen check/maximize operation', error); + return false; + } + } + /** * Dump UI component view tree * @returns @@ -207,17 +329,18 @@ export class Device implements EventSimulator { logger.debug( `dumpViewTree attempt=${attempt} layoutType=${layout ? typeof layout : 'undefined'} pages=${pages.length}` ); - // if exist keyboard then close and dump again. - // If device type is 2in1 (from options) send Enter to confirm input and skip hide action. - const rawDeviceTypeOpt = this.options && this.options.deviceType ? String(this.options.deviceType) : ''; - const deviceTypeOpt = rawDeviceTypeOpt.trim().toLowerCase(); - logger.debug(`dumpViewTree deviceType (from options): '${rawDeviceTypeOpt}' -> '${deviceTypeOpt}'`); - if (deviceTypeOpt === '2in1') { - logger.info('2in1 设备:发送回车以确认输入,跳过收起键盘逻辑'); - try { - await this.inputKey(KeyCode.KEYCODE_ENTER); - } catch (err) { - logger.warn('发送回车键失败', err); + const hasKeyboardPage = this.hasKeyboardPage(pages); + // 2in1: 仅在检测到输入法页面时发送回车确认,且不尝试收起键盘 + if (this.is2in1Device) { + if (hasKeyboardPage) { + logger.info('2in1 设备检测到输入法页面:发送回车以确认输入,跳过收起键盘逻辑'); + try { + await this.inputKey(KeyCode.KEYCODE_ENTER); + } catch (err) { + logger.warn('2in1 设备发送回车键失败', err); + } + } else { + logger.debug('2in1 设备:未检测到输入法页面,跳过键盘处理'); } } else if (this.closeKeyboard(pages)) { logger.info('Keyboard detected during dumpViewTree, sending hide event and retrying.'); @@ -268,6 +391,15 @@ export class Device implements EventSimulator { return false; } + private hasKeyboardPage(pages: Page[]): boolean { + for (const page of pages) { + if (page.getBundleName() === 'com.huawei.hmos.inputmethod') { + return true; + } + } + return false; + } + /** * Dump inspector layout and snapshot * @param bundleName @@ -463,11 +595,47 @@ export class Device implements EventSimulator { * @returns */ async getCurrentPage(hap: Hap): Promise { - let page = await this.dumpViewTree(); + // Get all pages from dumpLayout to find the correct one + let layout = await this.driverCtx!.driver.dumpLayout(); + let pages = PageBuilder.buildPagesFromLayout(layout); + + // If we have a target bundleName, try to find matching page first + let page: Page | undefined; + if (hap.bundleName) { + for (const p of pages) { + if (p.getBundleName() === hap.bundleName && !p.isStop() && !p.isBackground()) { + page = p; + logger.debug(`getCurrentPage: Found matching page with bundleName=${hap.bundleName}`); + break; + } + } + } + + // If no matching page found, use dumpViewTree logic (sort by height) + if (!page) { + pages.sort((a: Page, b: Page) => { + return b.getRoot().getHeight() - a.getRoot().getHeight(); + }); + if (pages.length > 0) { + page = pages[0]; + const pageBundleName = page.getBundleName(); + if (!hap.bundleName) { + hap.bundleName = pageBundleName; + } + logger.debug(`getCurrentPage: Using largest page with bundleName=${pageBundleName}`); + } + } + + if (!page) { + logger.warn('getCurrentPage: No page found, returning fallback'); + return this.createFallbackPage(); + } + const pageBundleName = page.getBundleName(); if (!hap.bundleName) { hap.bundleName = pageBundleName; } + if (this.options.sourceRoot) { let inspector = await this.dumpInspector(hap.bundleName); page.mergeInspector(inspector.layout); diff --git a/src/runner/event_action.ts b/src/runner/event_action.ts index 7e20efc..4a3f605 100644 --- a/src/runner/event_action.ts +++ b/src/runner/event_action.ts @@ -18,6 +18,7 @@ import fs from 'fs'; import moment from 'moment'; import { Device } from '../device/device'; import { Event } from '../event/event'; +import { AbilityEvent } from '../event/system_event'; import { Hap } from '../model/hap'; import { SerializeUtils } from '../utils/serialize_utils'; import { Page } from '../model/page'; @@ -50,6 +51,13 @@ export class EventAction { async stop() { this.transition.to = await this.device.getCurrentPage(this.hap); + + // If this was an AbilityEvent (app launch), ensure the app is in fullscreen mode + if (this.transition.event instanceof AbilityEvent) { + logger.info('AbilityEvent detected, checking and ensuring fullscreen mode'); + await this.device.ensureFullscreen(this.hap); + } + logger.info(`EventAction->stop`); this.save(); } diff --git a/src/utils/dynamic_compare.ts b/src/utils/dynamic_compare.ts new file mode 100644 index 0000000..9e019a2 --- /dev/null +++ b/src/utils/dynamic_compare.ts @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import path from 'path'; +import { Component } from '../model/component'; +import { Page } from '../model/page'; +import { Point } from '../model/point'; +import { ViewTree } from '../model/viewtree'; +import { HapTestLogger } from './logger'; + +const logger = HapTestLogger.getLogger(); + +export interface CompareOptions { + outputRoot: string; + appFolder: string; + mobileDir?: string; + twoInOneDir?: string; + reportPath?: string; + fullWidthTolerance?: number; +} + +export interface CompareIssue { + pageIndex: number; + componentName: string; + mobileScreenshot: string; + twoInOneScreenshot: string; +} + +export interface CompareResult { + issues: CompareIssue[]; + pageCount: number; + mobilePages: number; + twoInOnePages: number; + mobileScreenshots: number; + twoInOneScreenshots: number; +} + +interface TransitionRecord { + from: Page; + to: Page; +} + +interface ScreenRect { + left: number; + right: number; +} + +const DEFAULT_TOLERANCE = 1; +const SCREENSHOT_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']); + +export function compareDynamicLogs(options: CompareOptions): CompareResult { + const mobileDir = options.mobileDir ?? 'mobile'; + const twoInOneDir = options.twoInOneDir ?? '2in1'; + const tolerance = Number.isFinite(options.fullWidthTolerance) ? options.fullWidthTolerance! : DEFAULT_TOLERANCE; + + const mobileResolved = resolveRunDirectories(options.outputRoot, mobileDir, options.appFolder, 'mobile'); + const twoInOneResolved = resolveRunDirectories(options.outputRoot, twoInOneDir, options.appFolder, '2in1'); + + const mobileEventsDir = mobileResolved.eventsDir; + const twoInOneEventsDir = twoInOneResolved.eventsDir; + const mobileTempDir = mobileResolved.tempDir; + const twoInOneTempDir = twoInOneResolved.tempDir; + + const mobileTransitions = loadTransitions(mobileEventsDir); + const twoInOneTransitions = loadTransitions(twoInOneEventsDir); + + const mobilePages = buildPageSequence(mobileTransitions); + const twoInOnePages = buildPageSequence(twoInOneTransitions); + + const mobileScreenshots = listScreenshots(mobileTempDir); + const twoInOneScreenshots = listScreenshots(twoInOneTempDir); + + const pageCount = Math.min(mobilePages.length, twoInOnePages.length, mobileScreenshots.length, twoInOneScreenshots.length); + if (mobilePages.length !== twoInOnePages.length) { + logger.warn(`Page count mismatch: mobile=${mobilePages.length}, 2in1=${twoInOnePages.length}. Using min=${pageCount}.`); + } + if (mobileScreenshots.length !== twoInOneScreenshots.length) { + logger.warn( + `Screenshot count mismatch: mobile=${mobileScreenshots.length}, 2in1=${twoInOneScreenshots.length}. Using min=${pageCount}.` + ); + } + + const issues: CompareIssue[] = []; + for (let i = 0; i < pageCount; i += 1) { + const mobilePage = mobilePages[i]; + const twoInOnePage = twoInOnePages[i]; + const mobileScreen = mobileScreenshots[i]; + const twoInOneScreen = twoInOneScreenshots[i]; + + const mobileRect = getScreenRect(mobilePage); + const twoInOneRect = getScreenRect(twoInOnePage); + if (!mobileRect || !twoInOneRect) { + logger.warn(`Skipping pageIndex=${i} because screen rect is missing.`); + continue; + } + + const mobileMap = buildComponentNameMap(mobilePage); + const twoInOneMap = buildComponentNameMap(twoInOnePage); + const sharedNames = new Set(); + for (const name of mobileMap.keys()) { + if (twoInOneMap.has(name)) { + sharedNames.add(name); + } + } + + for (const name of sharedNames) { + const mobileComponents = mobileMap.get(name)!; + const twoInOneComponents = twoInOneMap.get(name)!; + if ( + hasFullWidthComponent(mobileComponents, mobileRect, tolerance) && + hasFullWidthComponent(twoInOneComponents, twoInOneRect, tolerance) + ) { + issues.push({ + pageIndex: i, + componentName: name, + mobileScreenshot: mobileScreen, + twoInOneScreenshot: twoInOneScreen, + }); + } + } + } + + const result: CompareResult = { + issues, + pageCount, + mobilePages: mobilePages.length, + twoInOnePages: twoInOnePages.length, + mobileScreenshots: mobileScreenshots.length, + twoInOneScreenshots: twoInOneScreenshots.length, + }; + + if (options.reportPath) { + fs.mkdirSync(path.dirname(options.reportPath), { recursive: true }); + fs.writeFileSync(options.reportPath, JSON.stringify(result, null, 2), { encoding: 'utf-8' }); + logger.info(`Dynamic compare report saved: ${options.reportPath}`); + } + + logger.info(`Dynamic compare finished. Issues=${issues.length}, PagesCompared=${pageCount}`); + return result; +} + +function resolveRunDirectories( + outputRoot: string, + deviceDir: string, + appFolder: string, + label: string +): { eventsDir: string; tempDir: string } { + const deviceRoot = resolveDeviceRoot(outputRoot, deviceDir, label); + const appRoot = path.join(deviceRoot, appFolder); + ensureDirectory(appRoot, `${label} app folder`); + + const runRoot = resolveRunRoot(appRoot, label); + const eventsDir = path.join(runRoot, 'events'); + const tempDir = path.join(runRoot, 'temp'); + ensureDirectory(eventsDir, `${label} events`); + ensureDirectory(tempDir, `${label} temp`); + return { eventsDir, tempDir }; +} + +function resolveDeviceRoot(outputRoot: string, deviceDir: string, label: string): string { + const exact = path.join(outputRoot, deviceDir); + if (fs.existsSync(exact)) { + return exact; + } + + const entries = fs.readdirSync(outputRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()); + const trimmedMatch = entries.find((entry) => entry.name.trim() === deviceDir); + if (trimmedMatch) { + const resolved = path.join(outputRoot, trimmedMatch.name); + logger.warn(`Resolved ${label} device dir "${deviceDir}" -> "${trimmedMatch.name}"`); + return resolved; + } + + throw new Error(`Missing ${label} device directory: ${exact}`); +} + +function resolveRunRoot(appRoot: string, label: string): string { + const directEvents = path.join(appRoot, 'events'); + const directTemp = path.join(appRoot, 'temp'); + if (fs.existsSync(directEvents) && fs.existsSync(directTemp)) { + return appRoot; + } + + const runDirs = fs + .readdirSync(appRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((name) => { + const runRoot = path.join(appRoot, name); + return fs.existsSync(path.join(runRoot, 'events')) && fs.existsSync(path.join(runRoot, 'temp')); + }) + .sort() + .reverse(); + + if (runDirs.length === 0) { + throw new Error(`No run directory with events/temp found under ${label} app folder: ${appRoot}`); + } + + if (runDirs.length > 1) { + logger.warn(`Multiple ${label} runs found. Using latest: ${runDirs[0]}`); + } + + return path.join(appRoot, runDirs[0]); +} + +function ensureDirectory(dirPath: string, label: string): void { + if (!fs.existsSync(dirPath)) { + throw new Error(`Missing ${label} directory: ${dirPath}`); + } +} + +function loadTransitions(eventsDir: string): TransitionRecord[] { + const files = fs + .readdirSync(eventsDir) + .filter((file) => file.endsWith('.json')) + .sort(); + + return files.map((file) => { + const fullPath = path.join(eventsDir, file); + const raw = fs.readFileSync(fullPath, { encoding: 'utf-8' }); + const parsed = JSON.parse(raw) as { from?: unknown; to?: unknown }; + if (!parsed.from || !parsed.to) { + throw new Error(`Invalid transition file: ${fullPath}`); + } + return { + from: revivePage(parsed.from), + to: revivePage(parsed.to), + }; + }); +} + +function revivePage(raw: any): Page { + const abilityName = raw?.abilityName ?? ''; + const bundleName = raw?.bundleName ?? ''; + const pagePath = raw?.pagePath ?? ''; + const viewTreeRaw = raw?.viewTree ?? raw?.viewtree ?? raw?.root ?? raw; + const rootRaw = viewTreeRaw?.root ?? viewTreeRaw; + if (!rootRaw) { + throw new Error('Invalid page data: missing viewTree root'); + } + const root = reviveComponent(rootRaw); + const viewTree = new ViewTree(root); + return new Page(viewTree, abilityName, bundleName, pagePath); +} + +function reviveComponent(raw: any): Component { + const component = Object.assign(new Component(), raw); + component.bounds = parseBounds(raw?.bounds ?? component.bounds); + component.origBounds = parseBounds(raw?.origBounds ?? component.origBounds); + const children = Array.isArray(raw?.children) ? raw.children : []; + component.children = children.map((child: any) => { + const revived = reviveComponent(child); + revived.parent = component; + return revived; + }); + return component; +} + +function parseBounds(bounds: any): Point[] | undefined { + if (!bounds) { + return undefined; + } + if (typeof bounds === 'string') { + const regex = /\[(\d+),(\d+)\]/g; + const points: Point[] = []; + let match; + while ((match = regex.exec(bounds)) !== null) { + points.push({ x: parseInt(match[1], 10), y: parseInt(match[2], 10) }); + } + return points.length ? points : undefined; + } + if (Array.isArray(bounds)) { + return bounds + .map((item) => { + if (!item || typeof item !== 'object') { + return undefined; + } + const x = Number((item as any).x); + const y = Number((item as any).y); + if (Number.isFinite(x) && Number.isFinite(y)) { + return { x, y } as Point; + } + return undefined; + }) + .filter((item): item is Point => Boolean(item)); + } + return undefined; +} + +function buildPageSequence(transitions: TransitionRecord[]): Page[] { + if (transitions.length === 0) { + return []; + } + const pages: Page[] = []; + pages.push(transitions[0].from); + for (const transition of transitions) { + pages.push(transition.to); + } + return pages; +} + +function listScreenshots(tempDir: string): string[] { + return fs + .readdirSync(tempDir) + .filter((file) => SCREENSHOT_EXTENSIONS.has(path.extname(file).toLowerCase())) + .sort() + .map((file) => path.join(tempDir, file)); +} + +function buildComponentNameMap(page: Page): Map { + const map = new Map(); + for (const component of page.getComponents()) { + const matchKey = buildMatchKey(component); + if (!matchKey) { + continue; + } + const list = map.get(matchKey); + if (list) { + list.push(component); + } else { + map.set(matchKey, [component]); + } + } + return map; +} + +function buildMatchKey(component: Component): string | undefined { + const type = component.type?.trim(); + if (!type) { + return undefined; + } + const keyOrId = (component.key ?? component.id ?? '').trim(); + if (!keyOrId) { + return undefined; + } + return `${type}::${keyOrId}`; +} + +function hasFullWidthComponent(components: Component[], screenRect: ScreenRect, tolerance: number): boolean { + return components.some((component) => isFullWidth(component, screenRect, tolerance)); +} + +function isFullWidth(component: Component, screenRect: ScreenRect, tolerance: number): boolean { + const rect = getBoundsRect(component.bounds ?? component.origBounds); + if (!rect) { + return false; + } + return rect.left <= screenRect.left + tolerance && rect.right >= screenRect.right - tolerance; +} + +function getScreenRect(page: Page): ScreenRect | undefined { + const root = page.getRoot(); + const rootRect = getBoundsRect(root.bounds ?? root.origBounds); + if (rootRect && rootRect.right > rootRect.left) { + return rootRect; + } + + let minX = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + for (const component of page.getComponents()) { + const rect = getBoundsRect(component.bounds ?? component.origBounds); + if (!rect) { + continue; + } + minX = Math.min(minX, rect.left); + maxX = Math.max(maxX, rect.right); + } + + if (!Number.isFinite(minX) || !Number.isFinite(maxX) || maxX <= minX) { + return undefined; + } + + return { left: minX, right: maxX }; +} + +function getBoundsRect(bounds?: Point[]): ScreenRect | undefined { + if (!bounds || bounds.length < 2) { + return undefined; + } + const xs = bounds.map((point) => point.x); + const left = Math.min(...xs); + const right = Math.max(...xs); + return { left, right }; +} From 29686a82d3c304f7801c99b8e0510e0aa960c276 Mon Sep 17 00:00:00 2001 From: DdddM123 Date: Sat, 20 Jun 2026 17:40:44 +0800 Subject: [PATCH 3/3] compare module --- .github/agents/comparer_of_haptest.agent.md | 0 .gitignore | 1 + config.json | 7 +- output.png | Bin 0 -> 231339 bytes package-lock.json | 1757 ++++++++++-------- package.json | 6 +- scripts/run_haptests.sh | 177 +- src/cli/cli.ts | 71 +- src/compare/README.md | 283 +++ src/compare/ai_component_matcher.ts | 417 +++++ src/compare/component_matcher.ts | 327 ++++ src/compare/detectors/diff_detector.ts | 225 +++ src/compare/detectors/full_width_detector.ts | 72 + src/compare/detectors/ratio_detector.ts | 64 + src/compare/detectors/scene_detector.ts | 192 ++ src/compare/geometry.ts | 64 + src/compare/page_loader.ts | 180 ++ src/compare/types.ts | 112 ++ src/device/device.ts | 74 +- src/device/uidriver/hypium_rpc.ts | 81 +- src/utils/dynamic_compare.ts | 467 ++--- test/unit/component_matcher_ai.test.ts | 150 ++ test/unit/diff_detector.test.ts | 119 ++ test/unit/scene_detector.test.ts | 123 ++ 24 files changed, 3767 insertions(+), 1202 deletions(-) create mode 100644 .github/agents/comparer_of_haptest.agent.md create mode 100644 output.png create mode 100644 src/compare/README.md create mode 100644 src/compare/ai_component_matcher.ts create mode 100644 src/compare/component_matcher.ts create mode 100644 src/compare/detectors/diff_detector.ts create mode 100644 src/compare/detectors/full_width_detector.ts create mode 100644 src/compare/detectors/ratio_detector.ts create mode 100644 src/compare/detectors/scene_detector.ts create mode 100644 src/compare/geometry.ts create mode 100644 src/compare/page_loader.ts create mode 100644 src/compare/types.ts create mode 100644 test/unit/component_matcher_ai.test.ts create mode 100644 test/unit/diff_detector.test.ts create mode 100644 test/unit/scene_detector.test.ts diff --git a/.github/agents/comparer_of_haptest.agent.md b/.github/agents/comparer_of_haptest.agent.md new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 50f07a0..1456340 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist/ lib/ node_modules/ +package.json package-lock.json *.log *.log.* diff --git a/config.json b/config.json index da7fc57..91fd20b 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,8 @@ { "GPT_CONFIG":{ - "baseURL": "", - "apiKey": "" + "baseURL": "${YOUR_BASE_URL}", + "apiKey": "${YOUR_API_KEY}", + "siteURL": "https://github.com/SMAT-Lab/HapTest", + "appName": "HapTest" } - } \ No newline at end of file diff --git a/output.png b/output.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6b038ff515d2b4d823c95d864b5cd313039f56 GIT binary patch literal 231339 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 71dd0ab..5eac55c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,8 @@ "express": "^4.19.2", "graphology": "^0.25.4", "graphology-shortest-path": "^2.1.0", - "haptest": "^0.2.1", - "image-hash": "^5.3.2", + "haptest": "^0.1.9", + "image-hash": "^7.0.1", "log4js": "^6.9.1", "moment": "^2.30.1", "openai": "^5.12.2", @@ -34,28 +34,26 @@ "@types/adm-zip": "^0.5.5", "@types/express": "^4.17.21", "@types/node": "^20.14.9", - "ts-node": "^10.9.2", + "ts-node": "^1.7.1", "vitest": "^3.2.4" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", + "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@canvas/image-data": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", - "integrity": "sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.1.0.tgz", + "integrity": "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==", "license": "MIT" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://repo.huaweicloud.com/repository/npm/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@cwasm/webp": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@cwasm/webp/-/webp-0.1.5.tgz", @@ -401,6 +399,23 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", @@ -465,31 +480,12 @@ "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", @@ -750,6 +746,23 @@ "win32" ] }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -838,30 +851,6 @@ "node": ">=18" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://repo.huaweicloud.com/repository/npm/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, "node_modules/@types/adm-zip": { "version": "0.5.5", "resolved": "https://repo.huaweicloud.com/repository/npm/@types/adm-zip/-/adm-zip-0.5.5.tgz", @@ -972,7 +961,6 @@ "version": "20.14.10", "resolved": "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-20.14.10.tgz", "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1119,18 +1107,6 @@ "resolved": "https://repo.huaweicloud.com/repository/npm/@yomguithereal/helpers/-/helpers-1.1.1.tgz", "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", @@ -1144,30 +1120,6 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://repo.huaweicloud.com/repository/npm/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://repo.huaweicloud.com/repository/npm/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", - "dev": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/adm-zip": { "version": "0.5.15", "resolved": "https://repo.huaweicloud.com/repository/npm/adm-zip/-/adm-zip-0.5.15.tgz", @@ -1176,27 +1128,32 @@ "node": ">=12.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://repo.huaweicloud.com/repository/npm/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -1204,22 +1161,14 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=0.10.0" } }, "node_modules/assertion-error": { @@ -1231,56 +1180,6 @@ "node": ">=12" } }, - "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/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/bjc": { "version": "1.0.22", "resolved": "https://registry.npmjs.org/bjc/-/bjc-1.0.22.tgz", @@ -1299,23 +1198,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "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.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -1331,6 +1230,26 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "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", @@ -1338,12 +1257,12 @@ "license": "MIT" }, "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1352,33 +1271,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "engines": { + "node": ">= 0.8" } }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { @@ -1396,7 +1300,7 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { @@ -1409,7 +1313,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==", "license": "MIT", "dependencies": { @@ -1423,12 +1327,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "license": "Apache-2.0" - }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -1445,6 +1343,23 @@ "node": ">=12" } }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -1459,18 +1374,6 @@ "resolved": "https://repo.huaweicloud.com/repository/npm/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, - "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": "12.1.0", "resolved": "https://repo.huaweicloud.com/repository/npm/commander/-/commander-12.1.0.tgz", @@ -1526,30 +1429,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://repo.huaweicloud.com/repository/npm/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://repo.huaweicloud.com/repository/npm/date-format/-/date-format-4.0.14.tgz", @@ -1559,9 +1438,10 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1583,15 +1463,6 @@ "node": ">=6" } }, - "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -1611,18 +1482,9 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://repo.huaweicloud.com/repository/npm/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { @@ -1634,16 +1496,6 @@ "node": ">= 0.4" } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", @@ -1659,9 +1511,19 @@ "node": ">= 0.8" } }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { @@ -1670,7 +1532,7 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { @@ -1685,7 +1547,7 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { @@ -1741,6 +1603,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1759,15 +1631,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://repo.huaweicloud.com/repository/npm/events/-/events-3.3.0.tgz", @@ -1786,39 +1649,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "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", + "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", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1847,12 +1710,12 @@ "license": "MIT" }, "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1861,38 +1724,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "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": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -1903,17 +1743,18 @@ } }, "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", + "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -1957,29 +1798,6 @@ "resolved": "https://repo.huaweicloud.com/repository/npm/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", @@ -2027,7 +1845,7 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { @@ -2036,7 +1854,7 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { @@ -2060,7 +1878,7 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { @@ -2071,18 +1889,9 @@ "node": ">= 0.4" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { @@ -2150,24 +1959,21 @@ } }, "node_modules/haptest": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/haptest/-/haptest-0.2.1.tgz", - "integrity": "sha512-0nN/Ev/rS0quroIyAAMX6y3qEowYNoY2TCXbk0Pyr31WL/bFhK7cp4adsafygd/ttYjHU1eIKZnrG93jJ4c9gA==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/haptest/-/haptest-0.1.9.tgz", + "integrity": "sha512-AXlGKsRhAOiSy6WzAya3H10gCqwVZbiVx/B1XVsTqjD/ECpDXF2Tzr+MxuGGz0WlBNKWesWalKi+0Oof6JDNcA==", "license": "Apache License, Version 2.0", "dependencies": { "@ts-graphviz/adapter": "^2.0.3", "@types/ws": "^8.5.12", "adm-zip": "^0.5.15", - "bjc": "^1.0.22", + "bjc": "^1.0.18", "class-transformer": "^0.5.1", "commander": "^12.1.0", - "express": "^4.19.2", "graphology": "^0.25.4", "graphology-shortest-path": "^2.1.0", - "image-hash": "^5.3.2", "log4js": "^6.9.1", "moment": "^2.30.1", - "openai": "^5.12.2", "promise-socket": "7.0.0", "ts-graphviz": "^2.1.2", "ws": "^8.18.0" @@ -2176,32 +1982,22 @@ "haptest": "bin/haptest" } }, - "node_modules/har-schema": { + "node_modules/has-ansi": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" + "ansi-regex": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { @@ -2213,7 +2009,7 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { @@ -2245,24 +2041,9 @@ "node": ">= 0.8" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { @@ -2293,16 +2074,15 @@ "license": "BSD-3-Clause" }, "node_modules/image-hash": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/image-hash/-/image-hash-5.3.2.tgz", - "integrity": "sha512-of8SekDXKFoaK4R93dP/Lzw6+NRGag8Jr9YlIIZ9jJVn9KYLfYVo/ARbKtbRn+tdTz/wDzBObx6yflKpLSYbxA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/image-hash/-/image-hash-7.0.1.tgz", + "integrity": "sha512-UFd/RfmacH3c1MISLm1k1AFOtG1py2gy46qNE8aK7UwmAEHAZGzTfsBsF6VbLxirD30p6t2GgULzSZh1XmsWQA==", "license": "MIT", "dependencies": { "@cwasm/webp": "^0.1.5", - "file-type": "^16.5.3", + "file-type": "^21.0.0", "jpeg-js": "^0.4.0", - "pngjs": "^6.0.0", - "request": "^2.81.0" + "pngjs": "^7.0.0" } }, "node_modules/inherits": { @@ -2320,16 +2100,18 @@ "node": ">= 0.10" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true, "license": "MIT" }, "node_modules/jpeg-js": { @@ -2344,30 +2126,6 @@ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://repo.huaweicloud.com/repository/npm/json5/-/json5-2.2.3.tgz", @@ -2387,21 +2145,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://repo.huaweicloud.com/repository/npm/log4js/-/log4js-6.9.1.tgz", @@ -2440,7 +2183,7 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { @@ -2507,6 +2250,29 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mnemonist": { "version": "0.39.8", "resolved": "https://repo.huaweicloud.com/repository/npm/mnemonist/-/mnemonist-0.39.8.tgz", @@ -2555,18 +2321,19 @@ "node": ">= 0.6" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "license": "Apache-2.0", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=0.10.0" } }, "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==", "license": "MIT", "engines": { @@ -2614,6 +2381,19 @@ } } }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", @@ -2644,25 +2424,6 @@ "node": ">= 14.16" } }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2670,10 +2431,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2681,13 +2443,23 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "license": "MIT", "engines": { - "node": ">=12.13.0" + "node": ">=14.19.0" } }, "node_modules/postcss": { @@ -2718,15 +2490,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/promise-duplex": { "version": "6.0.0", "resolved": "https://repo.huaweicloud.com/repository/npm/promise-duplex/-/promise-duplex-6.0.0.tgz", @@ -2788,36 +2551,6 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", @@ -2828,81 +2561,47 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "readable-stream": "^4.7.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/rfdc": { @@ -3046,7 +2745,7 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { @@ -3065,7 +2764,7 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { @@ -3081,7 +2780,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==", "license": "MIT", "dependencies": { @@ -3099,7 +2798,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==", "license": "MIT", "dependencies": { @@ -3122,6 +2821,16 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3131,29 +2840,14 @@ "node": ">=0.10.0" } }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, "license": "MIT", "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" + "source-map": "^0.5.6" } }, "node_modules/stackback": { @@ -3190,13 +2884,40 @@ "node": ">=8.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/strip-literal": { @@ -3212,22 +2933,31 @@ } }, "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", "license": "MIT", "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "@tokenizer/token": "^0.3.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3241,13 +2971,14 @@ "dev": true }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -3293,35 +3024,23 @@ } }, "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/ts-graphviz": { "version": "2.1.2", "resolved": "https://repo.huaweicloud.com/repository/npm/ts-graphviz/-/ts-graphviz-2.1.2.tgz", @@ -3347,46 +3066,39 @@ } }, "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://repo.huaweicloud.com/repository/npm/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-1.7.1.tgz", + "integrity": "sha512-2b6YmKQ0052pEP5Y+KnBce0NkyjuQRBTLKd5XV0O/+WHLbj3CJ5rUrhB2Co1a5IaBiaC2DhynS/v4UfWtHy+2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^1.0.0", + "chalk": "^1.1.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "pinkie": "^2.0.4", + "source-map-support": "^0.4.0", + "tsconfig": "^5.0.2", + "v8flags": "^2.0.11", + "xtend": "^4.0.0", + "yn": "^1.2.0" }, "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "ts-node": "dist/bin.js" + } + }, + "node_modules/tsconfig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha512-Cq65A3kVp6BbsUgg9DRHafaGmbMb9EhAc7fjWvudNWKjkbWrt43FnrtZt6awshH1R0ocfF2Z0uxock3lVqEgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.3.0", + "parse-json": "^2.2.0", + "strip-bom": "^2.0.0", + "strip-json-comments": "^2.0.0" } }, "node_modules/tslib": { @@ -3395,24 +3107,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "license": "Unlicense" - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", @@ -3430,7 +3124,6 @@ "version": "5.5.3", "resolved": "https://repo.huaweicloud.com/repository/npm/typescript/-/typescript-5.5.3.tgz", "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3439,6 +3132,18 @@ "node": ">=14.17" } }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://repo.huaweicloud.com/repository/npm/undici-types/-/undici-types-5.26.5.tgz", @@ -3461,13 +3166,17 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "node_modules/user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha512-aggiKfEEubv3UwRNqTzLInZpAOmKzwdHqEBmW/hBA/mt99eg+b4VrX6i+IRLxU8+WJYfa33rGwRseg4eElUgsQ==", + "dev": true, + "license": "MIT", + "bin": { + "user-home": "cli.js" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/utils-merge": { @@ -3479,22 +3188,18 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "license": "MIT", - "bin": { - "uuid": "bin/uuid" + "node_modules/v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha512-SKfhk/LlaXzvtowJabLZwD4K6SGRYeoxA7KJeISlUMAB/NT4CBkZjMq3WceX2Ckm4llwqYVo8TICgsDYCBU2tA==", + "dev": true, + "dependencies": { + "user-home": "^1.1.1" + }, + "engines": { + "node": ">= 0.10.0" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://repo.huaweicloud.com/repository/npm/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", @@ -3504,20 +3209,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vite-node": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", @@ -3540,26 +3231,494 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-node/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT", + "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/vite-node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT", "optional": true }, "node_modules/vite-node/node_modules/vite": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.1.tgz", - "integrity": "sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.2", + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -3831,13 +3990,27 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://repo.huaweicloud.com/repository/npm/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-1.3.0.tgz", + "integrity": "sha512-cUr+6jz1CH+E9wIGgFW5lyMMOHLbCe/UCOVqV/TTnf5XMe0NBC3TS7pR9ZpDsb84iCWKBd6ETPRBqQjssDKsIA==", "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1" + }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } } } diff --git a/package.json b/package.json index 69c8060..d45e8b8 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "express": "^4.19.2", "graphology": "^0.25.4", "graphology-shortest-path": "^2.1.0", - "haptest": "^0.2.1", - "image-hash": "^5.3.2", + "haptest": "^0.1.9", + "image-hash": "^7.0.1", "log4js": "^6.9.1", "moment": "^2.30.1", "openai": "^5.12.2", @@ -38,7 +38,7 @@ "@types/adm-zip": "^0.5.5", "@types/express": "^4.17.21", "@types/node": "^20.14.9", - "ts-node": "^10.9.2", + "ts-node": "^1.7.1", "vitest": "^3.2.4" }, "files": [ diff --git a/scripts/run_haptests.sh b/scripts/run_haptests.sh index ab5c1ca..445c1a8 100755 --- a/scripts/run_haptests.sh +++ b/scripts/run_haptests.sh @@ -8,34 +8,157 @@ TIMEOUT_SECS="${TIMEOUT_SECS:-600}" # 在下面的数组中按顺序添加要执行的命令(每条一行)。 # 保持格式为: node bin/haptest -i com.huawei.hmos.* -o out/* -# 万兴喵影(剪辑软件逻辑太复杂无法测试)"node bin/haptest -i cn.wondershare.filmora.2in1 -o out/2in1/wondershare" - # 钉钉(暂时无法登陆)"node bin/haptest -i com.dingtalk.hmos.pc -o out/2in1/dingtalk" COMMANDS=( - #"node bin/haptest -i cn.wps.office.hap -o out/2in1/wps" - "node bin/haptest -i com.eastmoney.emapp -o out/2in1/eastmoney" - "node bin/haptest -i com.edrawsoft.edrawmax.pc -o out/2in1/edrawsoft_edrawmax" - "node bin/haptest -i com.edrawsoft.mindmaster.pc -o out/2in1/edrawsoft_mindmaster" - "node bin/haptest -i com.example.first -o out/2in1/example_first" - "node bin/haptest -i com.example.memoryleak -o out/2in1/example_memoryleak" - "node bin/haptest -i com.foxit.foxitpdfeditor -o out/2in1/foxitpdfeditor" - "node bin/haptest -i com.haitai.htbrowser -o out/2in1/haitai_htbrowser" - "node bin/haptest -i com.haozip2345.app -o out/2in1/haozip2345" - "node bin/haptest -i com.hos.moonshot.kimichat -o out/2in1/moonshot_kimichat" - "node bin/haptest -i com.hp.printercontrol.china -o out/2in1/hp_printercontrol" - "node bin/haptest -i com.oray.sunloginclient -o out/2in1/oray_sunloginclient" - "node bin/haptest -i com.quark.ohosbrowser -o out/2in1/quark_ohosbrowser" - "node bin/haptest -i com.renyitu.pumpkinssh -o out/2in1/renyitu_pumpkinssh" - "node bin/haptest -i com.ss.ohpc.ugc.aweme -o out/2in1/ss_ohpc_ugc_aweme" - "node bin/haptest -i com.tencent.harmonyqq -o out/2in1/harmonyqq" - "node bin/haptest -i com.tencent.wechat.pc -o out/2in1/wechat" - "node bin/haptest -i com.usb.right -o out/2in1/usb_right" - "node bin/haptest -i com.wifiservice.portallogin -o out/2in1/wifiservice_portallogin" - "node bin/haptest -i com.xunlei.thunder -o out/2in1/xunlei_thunder" - "node bin/haptest -i com.xunlei.xmp -o out/2in1/xunlei_xmp" - "node bin/haptest -i com.zhihu.hmos -o out/2in1/zhihu" - "node bin/haptest -i com.zuler.ohospc.todesk -o out/2in1/zuler_ohospc_todesk" - "node bin/haptest -i com.zwsoft.zwcad.PE -o out/2in1/zwsoft_zwcad" - "node bin/haptest -i yylx.danmaku.bili -o out/2in1/yylx_danmaku_bili" + #"node bin/haptest -i cn.wps.office.hap -o out/2in1/cn_wps_office_hap" + "node bin/haptest -i cn.damai.hongmeng -o out/2in1/cn_damai_hongmeng" + "node bin/haptest -i cn.gov.tax.its.hm -o out/2in1/cn_gov_tax_its_hm" + # "node bin/haptest -i cn.icheny.wechat -o out/2in1/cn_icheny_wechat" + "node bin/haptest -i cn.wps.2in1office.hap -o out/2in1/cn_wps_2in1office_hap" + # "node bin/haptest -i com.100mi.ddmc -o out/2in1/com_100mi_ddmc" + # "node bin/haptest -i com.airchina.harmonynext -o out/2in1/com_airchina_harmonynext" + "node bin/haptest -i com.alibaba.wireless_hmos -o out/2in1/com_alibaba_wireless_hmos" + # "node bin/haptest -i com.alipay.2in1.client -o out/2in1/com_alipay_2in1_client" + # "node bin/haptest -i com.amap.hmapp -o out/2in1/com_amap_hmapp" + "node bin/haptest -i com.anjuke.home -o out/2in1/com_anjuke_home" + # "node bin/haptest -i com.app.xt.retouch -o out/2in1/com_app_xt_retouch" + # "node bin/haptest -i com.autohome.main -o out/2in1/com_autohome_main" + "node bin/haptest -i com.baicizhan.bcz.hm -o out/2in1/com_baicizhan_bcz_hm" + "node bin/haptest -i com.baidu.baiduapp -o out/2in1/com_baidu_baiduapp" + # "node bin/haptest -i com.baidu.hmmap -o out/2in1/com_baidu_hmmap" + # "node bin/haptest -i com.baidu.netdisk.hmos -o out/2in1/com_baidu_netdisk_hmos" + # "node bin/haptest -i com.bankabc.openharmonyapp.release -o out/2in1/com_bankabc_openharmonyapp_release" + # "node bin/haptest -i com.beike.hongmeng -o out/2in1/com_beike_hongmeng" + # "node bin/haptest -i com.cainiao.cainiao4hmos -o out/2in1/com_cainiao_cainiao4hmos" + # "node bin/haptest -i com.ccb.2in1bank.hm -o out/2in1/com_ccb_2in1bank_hm" + # "node bin/haptest -i com.cctv.yangshipin.app.harmonyp -o out/2in1/com_cctv_yangshipin_app_harmonyp" + # "node bin/haptest -i com.china2in1.cmcc -o out/2in1/com_china2in1_cmcc" + # "node bin/haptest -i com.chinarailway.ticketingHM -o out/2in1/com_chinarailway_ticketingHM" + # "node bin/haptest -i com.cmbchina.harmony -o out/2in1/com_cmbchina_harmony" + # "node bin/haptest -i com.cmcc.DigitalHome -o out/2in1/com_cmcc_DigitalHome" + # "node bin/haptest -i com.cmcc.cmvideohm -o out/2in1/com_cmcc_cmvideohm" + # "node bin/haptest -i com.ctrip.harmonynext -o out/2in1/com_ctrip_harmonynext" + # "node bin/haptest -i com.dewu.hos -o out/2in1/com_dewu_hos" + # "node bin/haptest -i com.dingtalk.hmos -o out/2in1/com_dingtalk_hmos" + # "node bin/haptest -i com.douban.frodo.hap -o out/2in1/com_douban_frodo_hap" + # "node bin/haptest -i com.dragon.read.next -o out/2in1/com_dragon_read_next" + # "node bin/haptest -i com.droi.tong -o out/2in1/com_droi_tong" + # "node bin/haptest -i com.eastmoney.hmn.berlin -o out/2in1/com_eastmoney_hmn_berlin" + # "node bin/haptest -i com.easy.hmos.abroad -o out/2in1/com_easy_hmos_abroad" + # "node bin/haptest -i com.eternaljust.msea.huawei -o out/2in1/com_eternaljust_msea_huawei" + # "node bin/haptest -i com.example.cameran2 -o out/2in1/com_example_cameran2" + # "node bin/haptest -i com.example.deephierarchy -o out/2in1/com_example_deephierarchy" + # "node bin/haptest -i com.fliggy.hmos -o out/2in1/com_fliggy_hmos" + # "node bin/haptest -i com.hexin.hmn.sjcg -o out/2in1/com_hexin_hmn_sjcg" + # "node bin/haptest -i com.hm.cat.readall -o out/2in1/com_hm_cat_readall" + # "node bin/haptest -i com.hm.youdao -o out/2in1/com_hm_youdao" + "node bin/haptest -i com.hos.moonshot.kimichat -o out/2in1/com_hos_moonshot_kimichat" + # "node bin/haptest -i com.htinns.application -o out/2in1/com_htinns_application" + # "node bin/haptest -i com.hupu.heroes -o out/2in1/com_hupu_heroes" + # "node bin/haptest -i com.icbc.harmonyclient -o out/2in1/com_icbc_harmonyclient" + # "node bin/haptest -i com.jd.hm.mall -o out/2in1/com_jd_hm_mall" + # "node bin/haptest -i com.jiaxiao.driveharmony -o out/2in1/com_jiaxiao_driveharmony" + # "node bin/haptest -i com.jinrishuiyinxiangji.camera -o out/2in1/com_jinrishuiyinxiangji_camera" + # "node bin/haptest -i com.kanyun.hos.leo -o out/2in1/com_kanyun_hos_leo" + # "node bin/haptest -i com.kanyun.hos.solar -o out/2in1/com_kanyun_hos_solar" + # "node bin/haptest -i com.kuaishou.hmapp -o out/2in1/com_kuaishou_hmapp" + # "node bin/haptest -i com.kugou.hmmusic -o out/2in1/com_kugou_hmmusic" + # "node bin/haptest -i com.legado.app -o out/2in1/com_legado_app" + # "node bin/haptest -i com.lfr.accessibility -o out/2in1/com_lfr_accessibility" + # "node bin/haptest -i com.lfr.uitest -o out/2in1/com_lfr_uitest" + # "node bin/haptest -i com.lianjia.hongmeng -o out/2in1/com_lianjia_hongmeng" + # "node bin/haptest -i com.liuzh.deviceinfo.hmos -o out/2in1/com_liuzh_deviceinfo_hmos" + # "node bin/haptest -i com.lucky.luckincoffee -o out/2in1/com_lucky_luckincoffee" + # "node bin/haptest -i com.luna.hm.music -o out/2in1/com_luna_hm_music" + # "node bin/haptest -i com.meitu.beautycam -o out/2in1/com_meitu_beautycam" + "node bin/haptest -i com.meitu.meitupic -o out/2in1/com_meitu_meitupic" + # "node bin/haptest -i com.meituan.takeaway -o out/2in1/com_meituan_takeaway" + # "node bin/haptest -i com.mgtv.phone -o out/2in1/com_mgtv_phone" + # "node bin/haptest -i com.ohos.FusionSearch -o out/2in1/com_ohos_FusionSearch" + # "node bin/haptest -i com.ohos.UserFile.ExternalFileManager -o out/2in1/com_ohos_UserFile_ExternalFileManager" + # "node bin/haptest -i com.ohos.amsdialog -o out/2in1/com_ohos_amsdialog" + # "node bin/haptest -i com.ohos.backgroundtaskmgr.resources -o out/2in1/com_ohos_backgroundtaskmgr_resources" + # "node bin/haptest -i com.ohos.callui -o out/2in1/com_ohos_callui" + # "node bin/haptest -i com.ohos.certmanager -o out/2in1/com_ohos_certmanager" + # "node bin/haptest -i com.ohos.commondialog -o out/2in1/com_ohos_commondialog" + # "node bin/haptest -i com.ohos.contacts -o out/2in1/com_ohos_contacts" + # "node bin/haptest -i com.ohos.contactsdataability -o out/2in1/com_ohos_contactsdataability" + # "node bin/haptest -i com.ohos.devicemanagerui -o out/2in1/com_ohos_devicemanagerui" + # "node bin/haptest -i com.ohos.dhardwareui -o out/2in1/com_ohos_dhardwareui" + # "node bin/haptest -i com.ohos.dlpmanager -o out/2in1/com_ohos_dlpmanager" + # "node bin/haptest -i com.ohos.formrenderservice -o out/2in1/com_ohos_formrenderservice" + # "node bin/haptest -i com.ohos.inputmethodchoosedialog -o out/2in1/com_ohos_inputmethodchoosedialog" + # "node bin/haptest -i com.ohos.locationdialog -o out/2in1/com_ohos_locationdialog" + # "node bin/haptest -i com.ohos.medialibrary.medialibrarydata -o out/2in1/com_ohos_medialibrary_medialibrarydata" + # "node bin/haptest -i com.ohos.mms -o out/2in1/com_ohos_mms" + # "node bin/haptest -i com.ohos.notificationdialog -o out/2in1/com_ohos_notificationdialog" + # "node bin/haptest -i com.ohos.pasteboarddialog -o out/2in1/com_ohos_pasteboarddialog" + # "node bin/haptest -i com.ohos.permissionmanager -o out/2in1/com_ohos_permissionmanager" + # "node bin/haptest -i com.ohos.powerdialog -o out/2in1/com_ohos_powerdialog" + # "node bin/haptest -i com.ohos.ringtonelibrary.ringtonelibrarydata -o out/2in1/com_ohos_ringtonelibrary_ringtonelibrarydata" + # "node bin/haptest -i com.ohos.sceneboard -o out/2in1/com_ohos_sceneboard" + # "node bin/haptest -i com.ohos.settingsdata -o out/2in1/com_ohos_settingsdata" + # "node bin/haptest -i com.ohos.telephonydataability -o out/2in1/com_ohos_telephonydataability" + # "node bin/haptest -i com.ohos.useriam.authwidget -o out/2in1/com_ohos_useriam_authwidget" + # "node bin/haptest -i com.psbc.mbank.hm -o out/2in1/com_psbc_mbank_hm" + # "node bin/haptest -i com.qihoo.hms.browser -o out/2in1/com_qihoo_hms_browser" + # "node bin/haptest -i com.qimao.novel -o out/2in1/com_qimao_novel" + # "node bin/haptest -i com.qiyi.video.hmy -o out/2in1/com_qiyi_video_hmy" + # "node bin/haptest -i com.quark.ohosbrowser -o out/2in1/com_quark_ohosbrowser" + # "node bin/haptest -i com.qunar.hos -o out/2in1/com_qunar_hos" + # "node bin/haptest -i com.sankuai.dianping -o out/2in1/com_sankuai_dianping" + "node bin/haptest -i com.sankuai.hmeituan -o out/2in1/com_sankuai_hmeituan" + # "node bin/haptest -i com.sdu.didi.hmos.psnger -o out/2in1/com_sdu_didi_hmos_psnger" + # "node bin/haptest -i com.sina.news.hm.next -o out/2in1/com_sina_news_hm_next" + "node bin/haptest -i com.sina.weibo.stage -o out/2in1/com_sina_weibo_stage" + # "node bin/haptest -i com.sinovatech.unicom.ha -o out/2in1/com_sinovatech_unicom_ha" + # "node bin/haptest -i com.sogou.input -o out/2in1/com_sogou_input" + # "node bin/haptest -i com.ss.dcar.auto -o out/2in1/com_ss_dcar_auto" + # "node bin/haptest -i com.ss.feishu -o out/2in1/com_ss_feishu" + # "node bin/haptest -i com.ss.hm.article.news -o out/2in1/com_ss_hm_article_news" + # "node bin/haptest -i com.ss.hm.article.video -o out/2in1/com_ss_hm_article_video" + "node bin/haptest -i com.ss.hm.ugc.aweme -o out/2in1/com_ss_hm_ugc_aweme" + "node bin/haptest -i com.taobao.idlefish4ohos -o out/2in1/com_taobao_idlefish4ohos" + # "node bin/haptest -i com.taobao.movie.hongmeng -o out/2in1/com_taobao_movie_hongmeng" + # "node bin/haptest -i com.taobao.taobao4hmos -o out/2in1/com_taobao_taobao4hmos" + # "node bin/haptest -i com.taobao.taobaolive4hmos -o out/2in1/com_taobao_taobaolive4hmos" + # "node bin/haptest -i com.tencent.docsohos -o out/2in1/com_tencent_docsohos" + # "node bin/haptest -i com.tencent.hm.news -o out/2in1/com_tencent_hm_news" + # "node bin/haptest -i com.tencent.hm.qqmusic -o out/2in1/com_tencent_hm_qqmusic" + # "node bin/haptest -i com.tencent.meeting.app -o out/2in1/com_tencent_meeting_app" + # "node bin/haptest -i com.tencent.mqq -o out/2in1/com_tencent_mqq" + # "node bin/haptest -i com.tencent.mtthm -o out/2in1/com_tencent_mtthm" + # "node bin/haptest -i com.tencent.videohm -o out/2in1/com_tencent_videohm" + "node bin/haptest -i com.tencent.wechat -o out/2in1/com_tencent_wechat" + # "node bin/haptest -i com.tencent.wework.hmos -o out/2in1/com_tencent_wework_hmos" + # "node bin/haptest -i com.tianyancha.skyeye.hm -o out/2in1/com_tianyancha_skyeye_hm" + # "node bin/haptest -i com.tmall.tmall4hmos -o out/2in1/com_tmall_tmall4hmos" + # "node bin/haptest -i com.tmri.app.harmony12123 -o out/2in1/com_tmri_app_harmony12123" + # "node bin/haptest -i com.tongcheng.hmos -o out/2in1/com_tongcheng_hmos" + # "node bin/haptest -i com.uc.2in1 -o out/2in1/com_uc_2in1" + # "node bin/haptest -i com.umetrip.hm.app -o out/2in1/com_umetrip_hm_app" + # "node bin/haptest -i com.unionpay.hmos.wallet -o out/2in1/com_unionpay_hmos_wallet" + # "node bin/haptest -i com.usb.right -o out/2in1/com_usb_right" + # "node bin/haptest -i com.vip.hosapp -o out/2in1/com_vip_hosapp" + # "node bin/haptest -i com.wifi.hm -o out/2in1/com_wifi_hm" + # "node bin/haptest -i com.wifiservice.portallogin -o out/2in1/com_wifiservice_portallogin" + # "node bin/haptest -i com.wuba.life -o out/2in1/com_wuba_life" + # "node bin/haptest -i com.wudaokou.hippo_hmos -o out/2in1/com_wudaokou_hippo_hmos" + # "node bin/haptest -i com.ximalaya.ting.xmharmony -o out/2in1/com_ximalaya_ting_xmharmony" + # "node bin/haptest -i com.xingin.xhs_hos -o out/2in1/com_xingin_xhs_hos" + # "node bin/haptest -i com.xs.fm.next -o out/2in1/com_xs_fm_next" + # "node bin/haptest -i com.xunlei.thunder -o out/2in1/com_xunlei_thunder" + # "node bin/haptest -i com.xunmeng.pinduoduo.hos -o out/2in1/com_xunmeng_pinduoduo_hos" + # "node bin/haptest -i com.yiche.autoeasyh -o out/2in1/com_yiche_autoeasyh" + "node bin/haptest -i com.youku.next -o out/2in1/com_youku_next" + # "node bin/haptest -i com.yumc.kfc.superapp -o out/2in1/com_yumc_kfc_superapp" + # "node bin/haptest -i com.zhibo8.hmclient -o out/2in1/com_zhibo8_hmclient" + # "node bin/haptest -i com.zhihu.hmos -o out/2in1/com_zhihu_hmos" + "node bin/haptest -i com.zhuanzhuan.hmoszz -o out/2in1/com_zhuanzhuan_hmoszz" + "node bin/haptest -i com.zuoyebang.homework -o out/2in1/com_zuoyebang_homework" + # "node bin/haptest -i me.ele.eleme -o out/2in1/me_ele_eleme" + # "node bin/haptest -i ohos.global.systemres -o out/2in1/ohos_global_systemres" + # "node bin/haptest -i yylx.bilibili.comic -o out/2in1/yylx_bilibili_comic" + "node bin/haptest -i yylx.danmaku.bili -o out/2in1/yylx_danmaku_bili" ) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index f3b68bb..c57f112 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -22,6 +22,7 @@ import { FuzzOptions } from '../runner/fuzz_options'; import { EnvChecker } from './env_checker'; import { HapTestLogger, LOG_LEVEL } from '../utils/logger'; import { startUIViewerServer } from '../ui/ui_viewer_server'; +import { CompareDetector } from '../compare/types'; import { compareDynamicLogs } from '../utils/dynamic_compare'; const logger = getLogger(); @@ -38,6 +39,37 @@ function resolveLogLevel(opts: BaseOptions): LOG_LEVEL { return opts.debug ? LOG_LEVEL.DEBUG : LOG_LEVEL.INFO; } +function formatTimestamp(date: Date): string { + const yyyy = date.getFullYear().toString(); + const mm = (date.getMonth() + 1).toString().padStart(2, '0'); + const dd = date.getDate().toString().padStart(2, '0'); + const hh = date.getHours().toString().padStart(2, '0'); + const min = date.getMinutes().toString().padStart(2, '0'); + const ss = date.getSeconds().toString().padStart(2, '0'); + return `${yyyy}${mm}${dd}${hh}${min}${ss}`; +} + +function resolveCompareReportPath( + outputOption: string | undefined, + dataRoot: string, + appFolder: string, + detector: CompareDetector +): string { + const defaultName = `${detector}_${formatTimestamp(new Date())}`; + const defaultDir = path.join(dataRoot, 'compare', appFolder); + if (!outputOption) { + return path.join(defaultDir, defaultName); + } + + const resolvedOutput = path.resolve(outputOption); + const looksLikeDirectory = + outputOption.endsWith('/') || + outputOption.endsWith('\\') || + (path.extname(outputOption) === '' && !outputOption.endsWith('.json')); + + return looksLikeDirectory ? path.join(resolvedOutput, defaultName) : resolvedOutput; +} + async function runFuzzCommand(options: any): Promise { const outputDir = path.resolve(options.output ?? 'out'); const logLevel = resolveLogLevel(options); @@ -99,21 +131,34 @@ async function runUIViewerCommand(options: any, version: string): Promise } async function runCompareCommand(options: any): Promise { - const outputDir = path.resolve(options.output ?? 'out'); + const outputOption = options.output; + const dataRoot = path.resolve(options.dataRoot ?? 'out'); + const detector = (options.detector ?? 'all') as CompareDetector; + const reportPath = resolveCompareReportPath(outputOption, dataRoot, options.app, detector); + const reportDir = path.dirname(reportPath); const logLevel = resolveLogLevel(options); - HapTestLogger.configure(path.join(outputDir, 'haptest.log'), logLevel); + fs.mkdirSync(reportDir, { recursive: true }); + HapTestLogger.configure(path.join(reportDir, 'haptest.log'), logLevel); - const reportPath = options.report - ? path.resolve(options.report) - : path.join(outputDir, `compare_${options.app}_mobile_2in1.json`); + if (!['all', 'full-width', 'ratio', 'scene', 'diff'].includes(detector)) { + throw new Error(`Invalid detector: ${options.detector}. Use one of: all, full-width, ratio, scene, diff.`); + } - compareDynamicLogs({ - outputRoot: outputDir, + await compareDynamicLogs({ + outputRoot: dataRoot, appFolder: options.app, mobileDir: options.mobile, twoInOneDir: options.twoInOne, reportPath, fullWidthTolerance: Number(options.tolerance), + aspectRatioTolerance: Number(options.ratioTolerance), + sceneSimilarityThreshold: Number(options.sceneSimilarityThreshold), + detector, + aiComponentMatch: options.aiComponentMatch, + aiComponentModel: options.aiComponentModel, + aiComponentThreshold: Number(options.aiComponentThreshold), + aiComponentMaxCalls: Number(options.aiComponentMaxCalls), + aiComponentConfigPath: options.aiComponentConfig, }); } @@ -142,11 +187,19 @@ async function runCompareCommand(options: any): Promise { .command('compare') .description('Compare mobile and 2in1 dynamic logs for the same app') .requiredOption('-a, --app ', 'app log folder name under each device directory') - .option('-o, --output ', 'output dir', 'out') + .option('-o, --output ', 'report output file or directory path') + .option('--dataRoot ', 'dynamic logs root directory containing mobile/2in1', 'out') .option('--mobile ', 'mobile device folder name', 'mobile') .option('--twoInOne ', '2in1 device folder name', '2in1') - .option('--report ', 'output report file path') .option('--tolerance ', 'full width tolerance in px', '1') + .option('--ratioTolerance ', 'aspect ratio tolerance', '0.01') + .option('--sceneSimilarityThreshold ', 'business scene similarity threshold', '0.35') + .option('--detector ', 'detector to run: all | full-width | ratio | scene | diff', 'all') + .option('--aiComponentMatch', 'enable AI fallback to judge whether two components are the same', false) + .option('--aiComponentModel ', 'AI model used for component matching', 'openrouter/free') + .option('--aiComponentThreshold ', 'minimum AI confidence to accept a matched component pair', '0.6') + .option('--aiComponentMaxCalls ', 'maximum number of AI matching calls in one compare task', '200') + .option('--aiComponentConfig ', 'config file path containing GPT_CONFIG', 'config.json') .option('--debug', 'debug log level', false) .action(async (cmdOptions) => { try { diff --git a/src/compare/README.md b/src/compare/README.md new file mode 100644 index 0000000..e22140e --- /dev/null +++ b/src/compare/README.md @@ -0,0 +1,283 @@ +# Dynamic Compare 使用说明 + +本目录提供 `haptest compare` 动态日志对比能力,主要用于对比 `mobile` 与 `2in1` 两端执行同一应用后的 UI 一致性问题。 + +当前可检测 4 类问题: + +- full-width:检测同一组件是否都为横向铺满 +- ratio:检测同一组件宽高比是否明显变化 +- scene:检测同一事件前后业务场景是否发生偏移 +- diff:对已匹配到的同一组件逐字段比对,任何字段差异都会上报 + +并支持可选 AI 组件匹配:开启后组件匹配阶段仅使用 AI 判定,不再执行 `type + key/id` 精确匹配。 + +--- + +## 1. 快速开始 + +```bash +haptest compare -a +``` + +最小示例: + +```bash +haptest compare -a com.demo.app +``` + +指定数据根目录与输出文件: + +```bash +haptest compare -a com.demo.app \ + --dataRoot out \ + --output out/reports/compare_all_com.demo.app.json +``` + +只跑某一个检测器: + +```bash +haptest compare -a com.demo.app --detector ratio +``` + +启用 AI 组件匹配: + +```bash +haptest compare -a com.demo.app \ + --aiComponentMatch \ + --aiComponentModel openrouter/free \ + --aiComponentThreshold 0.6 \ + --aiComponentMaxCalls 200 \ + --aiComponentConfig config.json +``` + +--- + +## 2. 输入目录约定 + +默认从以下结构读取数据(可通过参数覆盖目录名): + +```text +/ + mobile/ + / + / + events/ + temp/ + 2in1/ + / + / + events/ + temp/ +``` + +- `events/`:事件与页面快照 JSON +- `temp/`:截图文件(png/jpg/jpeg) + +如果 `` 下有多个 `runDir`,会按目录名排序后选择最新一个。 + +--- + +## 3. 按可检测功能分章 + +### 3.1 all(一次跑完全部检测) + +用途:一次执行 `full-width + ratio + scene + diff` 四类检测。 + +运行方式: + +```bash +haptest compare -a com.demo.app --detector all +``` + +说明: + +- `--detector ` 默认值为 `all`。 +- 建议先跑 `all` 获取全量问题,再按单个 detector 精确定位。 + +--- + +### 3.2 diff(组件字段差异全量检测) + +用途:针对已匹配的同一组件,按字段逐项比较并上报所有差异。 + +运行方式: + +```bash +haptest compare -a com.demo.app --detector diff +``` + +说明: + +- 该检测器不设置容差,属于“有差异即报”。 +- 报告会给出每个字段在 `mobile` 与 `2in1` 两端的值。 +- 报告中的 `diffs` 字段会直接按三组输出:结构差异(`structuralDiffs`)、状态差异(`statusDiffs`)、文本差异(`textDiffs`)。 +- 当前会比较:`type/id/key/name/text/hint`、交互状态字段(如 `clickable`、`enabled`、`visible` 等)、`bounds` 与 `origBounds`。 + +--- + +### 3.3 full-width(横向铺满一致性) + +用途:检测同一组件在两端是否都表现为“横向铺满容器”。 + +运行方式: + +```bash +haptest compare -a com.demo.app --detector full-width +``` + +相关数值参数: + +- `--tolerance `(默认 `1`) + - 含义:像素容差,允许两端在“是否铺满”判定时有小范围误差。 + - 可以理解为:当组件宽度与容器宽度的差值在该阈值内时,仍可视为铺满。 + - 调参建议: + - 值更小(如 `0`):更严格,容易报出更多问题。 + - 值更大(如 `2`、`3`):更宽松,可减少因取整/缩放导致的误报。 + +--- + +### 3.4 ratio(宽高比一致性) + +用途:检测同一组件在两端是否出现明显形变(宽高比变化)。 + +运行方式: + +```bash +haptest compare -a com.demo.app --detector ratio +``` + +相关数值参数: + +- `--ratioTolerance `(默认 `0.01`) + - 含义:宽高比差异容差。 + - 判定思路可理解为:比较两端组件的宽高比差值,若超过该阈值则记为问题。 + - 例如:`0.01` 约等于允许 $1\%$ 的比例偏差量级(用于过滤轻微浮动)。 + - 调参建议: + - 值更小(如 `0.005`):更敏感,更容易发现细微变形。 + - 值更大(如 `0.02`):更宽松,减少轻微差异告警。 + +--- + +### 3.5 scene(场景偏移检测) + +用途:检测同一事件前后,两端是否进入了不同业务场景。 + +运行方式: + +```bash +haptest compare -a com.demo.app --detector scene +``` + +相关数值参数: + +- `--sceneSimilarityThreshold `(默认 `0.35`) + - 含义:场景相似度阈值。 + - 相似度通常在 `[0, 1]` 区间,值越大表示越相似。 + - 判定逻辑可理解为:相似度低于该阈值时,认为发生场景偏移。 + - 调参建议: + - 值更高(如 `0.5`):更严格,更容易判定为场景偏移。 + - 值更低(如 `0.25`):更宽松,只关注明显偏移。 + +--- + +## 4. 通用参数与数值参数说明 + +| 参数 | 类型 | 默认值 | 说明 | +|---|---|---|---| +| `-a, --app ` | string | 无(必填) | 设备目录下的应用日志目录名 | +| `-o, --output ` | string | 自动生成 | 报告输出文件或目录 | +| `--dataRoot ` | string | `out` | 动态日志根目录 | +| `--mobile ` | string | `mobile` | 手机侧目录名 | +| `--twoInOne ` | string | `2in1` | 2in1 侧目录名 | +| `--tolerance ` | number | `1` | full-width 检测像素容差 | +| `--ratioTolerance ` | number | `0.01` | ratio 检测宽高比容差 | +| `--sceneSimilarityThreshold ` | number | `0.35` | scene 检测场景相似度阈值 | +| `--detector ` | enum | `all` | 运行检测器:`all \| full-width \| ratio \| scene \| diff` | +| `--aiComponentMatch` | boolean | `false` | 开启 AI 组件匹配(不再做精确匹配) | +| `--aiComponentModel ` | string | `openrouter/free` | AI 匹配模型名 | +| `--aiComponentThreshold ` | number | `0.6` | AI 判定为同一组件的最小置信度 | +| `--aiComponentMaxCalls ` | number | `200` | 单次 compare 最大 AI 调用次数 | +| `--aiComponentConfig ` | string | `config.json` | 包含 `GPT_CONFIG` 的配置文件路径 | +| `--debug` | boolean | `false` | 使用 debug 日志级别 | + +### 4.1 需要填写数值的参数(速查) + +- `--tolerance` + - 单位:像素。 + - 含义:full-width 判定时允许的宽度误差。 +- `--ratioTolerance` + - 单位:比例差(无单位)。 + - 含义:ratio 判定时允许的宽高比差值。 +- `--sceneSimilarityThreshold` + - 单位:相似度分数(通常 `0~1`)。 + - 含义:scene 判定为“同场景”的最低相似度门槛。 +- `--aiComponentThreshold` + - 单位:置信度分数(通常 `0~1`)。 + - 含义:AI 认为两组件可匹配的最小置信度。 + - 取值越高,匹配越保守;取值越低,匹配越激进。 +- `--aiComponentMaxCalls` + - 单位:次数。 + - 含义:一次 compare 过程中允许的最大 AI 调用次数上限。 + - 值越大,覆盖更多候选匹配,但耗时/成本也可能增加。 + +### output 参数行为 + +- 未指定 `--output`:自动写入 `dataRoot/compare___mobile_2in1.json` +- 指定为目录:自动在目录下生成上述文件名 +- 指定为 `.json` 文件:直接写入该文件 + +--- + +## 5. AI 匹配配置 + +当启用 `--aiComponentMatch` 时,会读取配置文件中的 `GPT_CONFIG`: + +```json +{ + "GPT_CONFIG": { + "baseURL": "https://openrouter.ai/api/v1", + "apiKey": "", + "siteURL": "https://github.com/SMAT-Lab/HapTest", + "appName": "HapTest" + } +} +``` + +注意: + +- `apiKey` 为空时会跳过 AI 调用;由于已开启仅 AI 匹配,组件匹配结果会显著减少 +- 开启 `--aiComponentMatch` 后,组件匹配阶段仅由 AI 决定 +- OpenRouter 下会自动附带 `HTTP-Referer` 与 `X-Title` 请求头,并在连接错误时自动重试 +- 若 `openrouter/free` 连接不稳定,会自动回退尝试其他免费路由模型 + +### 5.1 OpenRouter `Connection error` 排查 + +若日志出现 `LLM call failed: Error: Connection error`,建议确认: + +- `GPT_CONFIG.baseURL` 是否为 `https://openrouter.ai/api/v1` +- 网络是否可访问 OpenRouter(公司网络/代理可能拦截) +- `apiKey` 是否有效且账户可用 + +可先用最简命令验证: + +```bash +haptest compare -a com.demo.app --aiComponentMatch --aiComponentModel openrouter/free --aiComponentConfig config.json +``` + +--- + +## 6. 输出报告结构 + +核心字段如下: + +- `issues`:full-width 问题列表 +- `aspectRatioIssues`:ratio 问题列表 +- `sceneIssues`:scene 问题列表 +- `componentDiffIssues`:diff 问题列表(字段级差异) +- `pageCount`:页面对比数量 +- `transitionCount`:转移对比数量 +- `mobilePages` / `twoInOnePages`:两端页面总数 +- `mobileTransitions` / `twoInOneTransitions`:两端转移总数 +- `mobileScreenshots` / `twoInOneScreenshots`:两端截图数量 + +建议先使用 `--detector all` 观察全量结果,再按问题类型缩小到单个 detector 做定位。 diff --git a/src/compare/ai_component_matcher.ts b/src/compare/ai_component_matcher.ts new file mode 100644 index 0000000..32f546e --- /dev/null +++ b/src/compare/ai_component_matcher.ts @@ -0,0 +1,417 @@ +import fs from 'fs'; +import path from 'path'; +import OpenAI from 'openai'; +import { Component } from '../model/component'; +import { HapTestLogger } from '../utils/logger'; +import { AiComponentMatchContext, AiComponentMatcher } from './component_matcher'; + +interface GptConfig { + baseURL?: string; + apiKey?: string; + siteURL?: string; + appName?: string; +} + +interface AiCompareResponse { + same?: boolean; + confidence?: number; + reason?: string; +} + +export interface OpenAiComponentMatcherOptions { + configPath?: string; + model?: string; + threshold?: number; + maxCalls?: number; +} + +const DEFAULT_MODEL = 'openrouter/free'; +const DEFAULT_THRESHOLD = 0.6; +const DEFAULT_MAX_CALLS = 200; +const DEFAULT_TIMEOUT_MS = 45000; +const RETRY_ATTEMPTS = 3; +const STRUCTURE_PARENT_DEPTH = 4; +const STRUCTURE_CHILD_PREVIEW = 6; +const STRUCTURE_DESCENDANT_DEPTH = 2; +const STRUCTURE_DESCENDANT_SAMPLE_LIMIT = 24; +const STRUCTURE_SIBLING_WINDOW = 2; + +const OPENROUTER_FREE_MODEL_FALLBACKS = [ + 'openrouter/free', + 'meta-llama/llama-3.1-8b-instruct:free', + 'mistralai/mistral-7b-instruct:free', +]; + +const logger = HapTestLogger.getLogger(); + +export class OpenAiComponentMatcher implements AiComponentMatcher { + private readonly openai: OpenAI; + private readonly model: string; + private readonly baseURL?: string; + private readonly threshold: number; + private readonly maxCalls: number; + private readonly cache: Map = new Map(); + private calls = 0; + + constructor(openai: OpenAI, model: string, threshold: number, maxCalls: number, baseURL?: string) { + this.openai = openai; + this.model = model; + this.threshold = threshold; + this.maxCalls = maxCalls; + this.baseURL = baseURL; + } + + static createFromConfig(options: OpenAiComponentMatcherOptions = {}): OpenAiComponentMatcher | undefined { + const configPath = path.resolve(options.configPath ?? path.join(__dirname, '../../config.json')); + let gptConfig: GptConfig | undefined; + try { + const raw = fs.readFileSync(configPath, { encoding: 'utf-8' }); + const json = JSON.parse(raw) as { GPT_CONFIG?: GptConfig }; + gptConfig = json.GPT_CONFIG; + } catch (error) { + logger.warn(`[ai-match] Skip AI component match because config is unreadable: ${String(error)}`); + return undefined; + } + + const apiKey = gptConfig?.apiKey?.trim(); + if (!apiKey) { + logger.warn('[ai-match] Skip AI component match because GPT_CONFIG.apiKey is empty.'); + return undefined; + } + + const baseURL = gptConfig?.baseURL?.trim() || undefined; + const openRouterHeaders = buildOpenRouterHeaders(baseURL, gptConfig); + + const openai = new OpenAI({ + apiKey, + baseURL, + timeout: DEFAULT_TIMEOUT_MS, + maxRetries: 0, + defaultHeaders: openRouterHeaders, + }); + + const model = options.model?.trim() || DEFAULT_MODEL; + const threshold = Number.isFinite(options.threshold) ? options.threshold! : DEFAULT_THRESHOLD; + const maxCalls = Number.isFinite(options.maxCalls) ? options.maxCalls! : DEFAULT_MAX_CALLS; + logger.info(`[ai-match] Enabled AI component matcher (model=${model}, threshold=${threshold}, maxCalls=${maxCalls})`); + return new OpenAiComponentMatcher(openai, model, threshold, maxCalls, baseURL); + } + + async isSameComponent( + mobileComponent: Component, + twoInOneComponent: Component, + context: AiComponentMatchContext + ): Promise { + const cacheKey = this.buildCacheKey(mobileComponent, twoInOneComponent, context); + const cached = this.cache.get(cacheKey); + if (cached !== undefined) { + return cached; + } + + if (this.calls >= this.maxCalls) { + this.cache.set(cacheKey, false); + return false; + } + + this.calls += 1; + + const modelCandidates = this.resolveModelCandidates(); + + for (const model of modelCandidates) { + let lastError: unknown; + for (let attempt = 1; attempt <= RETRY_ATTEMPTS; attempt += 1) { + try { + const response = await this.openai.chat.completions.create({ + model, + temperature: 0, + messages: [ + { + role: 'system', + content: + 'You are a strict GUI component matcher. Decide if two components represent the same business UI element across devices. Structural context (ancestor chain, sibling neighborhood, child/descendant patterns) is high-priority evidence when identity fields are missing. Respond with compact JSON only: {"same": boolean, "confidence": number, "reason": string}.', + }, + { + role: 'user', + content: this.buildPrompt(mobileComponent, twoInOneComponent, context), + }, + ], + }); + + const content = response.choices[0]?.message?.content?.trim() ?? ''; + const parsed = this.parseResponse(content); + const sameByFlag = parsed.same === true; + const confidence = Number.isFinite(parsed.confidence) ? parsed.confidence! : 0.5; + const decision = sameByFlag && confidence >= this.threshold; + this.cache.set(cacheKey, decision); + return decision; + } catch (error) { + lastError = error; + const shouldRetry = isConnectionLikeError(error) && attempt < RETRY_ATTEMPTS; + const modelInfo = model === this.model ? model : `${this.model}->${model}`; + logger.warn( + `[ai-match] LLM call failed (model=${modelInfo}, attempt=${attempt}/${RETRY_ATTEMPTS}): ${String(error)}` + ); + if (!shouldRetry) { + break; + } + await sleep(attempt * 500); + } + } + + if (!isConnectionLikeError(lastError)) { + break; + } + } + + this.cache.set(cacheKey, false); + return false; + } + + private resolveModelCandidates(): string[] { + if (!isOpenRouterBaseURL(this.baseURL)) { + return [this.model]; + } + + if (this.model !== 'openrouter/free') { + return [this.model]; + } + + return OPENROUTER_FREE_MODEL_FALLBACKS; + } + + private buildPrompt(mobileComponent: Component, twoInOneComponent: Component, context: AiComponentMatchContext): string { + const payload = { + mode: context.mode, + mobileKey: context.mobileKey, + twoInOneKey: context.twoInOneKey, + mobile: this.summarizeComponent(mobileComponent), + twoInOne: this.summarizeComponent(twoInOneComponent), + rules: [ + 'Type must be semantically compatible.', + 'Prefer key/id/name/text/hint consistency.', + 'Treat parent-child nesting and nearby siblings as strong evidence, especially when text/id/key are empty.', + 'Use bounds only as weak evidence because resolution differs across devices.', + 'If uncertain, return same=false.', + ], + }; + return JSON.stringify(payload); + } + + private summarizeComponent(component: Component): Record { + const bounds = component.bounds ?? component.origBounds; + const left = bounds?.[0]?.x; + const top = bounds?.[0]?.y; + const right = bounds?.[1]?.x; + const bottom = bounds?.[1]?.y; + return { + type: component.type?.trim() ?? '', + id: component.id?.trim() ?? '', + key: component.key?.trim() ?? '', + name: component.name?.trim() ?? '', + text: component.text?.trim() ?? '', + hint: component.hint?.trim() ?? '', + width: Number.isFinite(left) && Number.isFinite(right) ? Math.abs((right as number) - (left as number)) : null, + height: Number.isFinite(top) && Number.isFinite(bottom) ? Math.abs((bottom as number) - (top as number)) : null, + structure: this.summarizeStructure(component), + }; + } + + private summarizeStructure(component: Component): Record { + const parentChain = this.collectParentChain(component, STRUCTURE_PARENT_DEPTH).map((item) => + this.summarizeNodeIdentity(item) + ); + const siblingContext = this.collectSiblingContext(component); + const childPreview = (component.children ?? []) + .slice(0, STRUCTURE_CHILD_PREVIEW) + .map((child) => ({ + ...this.summarizeNodeIdentity(child), + childCount: child.children?.length ?? 0, + })); + const descendantTypeHistogram = this.collectDescendantTypeHistogram( + component, + STRUCTURE_DESCENDANT_DEPTH, + STRUCTURE_DESCENDANT_SAMPLE_LIMIT + ); + + return { + parentChain, + siblingContext, + childCount: component.children?.length ?? 0, + childPreview, + descendantTypeHistogram, + }; + } + + private collectParentChain(component: Component, depth: number): Component[] { + const chain: Component[] = []; + let current = component.parent ?? null; + let remaining = depth; + while (current && remaining > 0) { + chain.push(current); + current = current.parent ?? null; + remaining -= 1; + } + return chain; + } + + private collectSiblingContext(component: Component): Record { + const siblings = component.parent?.children ?? []; + const currentIndex = siblings.indexOf(component); + if (currentIndex < 0) { + return { + index: null, + total: siblings.length, + nearby: [], + }; + } + + const start = Math.max(0, currentIndex - STRUCTURE_SIBLING_WINDOW); + const end = Math.min(siblings.length, currentIndex + STRUCTURE_SIBLING_WINDOW + 1); + const nearby = siblings.slice(start, end).map((sibling, offsetIndex) => { + const absoluteIndex = start + offsetIndex; + return { + ...this.summarizeNodeIdentity(sibling), + relativeIndex: absoluteIndex - currentIndex, + }; + }); + + return { + index: currentIndex, + total: siblings.length, + nearby, + }; + } + + private collectDescendantTypeHistogram(component: Component, maxDepth: number, sampleLimit: number): Record { + if (maxDepth <= 0 || sampleLimit <= 0) { + return {}; + } + + const histogram = new Map(); + const queue: Array<{ node: Component; depth: number }> = []; + for (const child of component.children ?? []) { + queue.push({ node: child, depth: 1 }); + } + + let sampled = 0; + while (queue.length > 0 && sampled < sampleLimit) { + const item = queue.shift(); + if (!item) { + break; + } + + const type = item.node.type?.trim() || 'UNKNOWN'; + histogram.set(type, (histogram.get(type) ?? 0) + 1); + sampled += 1; + + if (item.depth >= maxDepth) { + continue; + } + for (const child of item.node.children ?? []) { + queue.push({ node: child, depth: item.depth + 1 }); + } + } + + return Object.fromEntries( + [...histogram.entries()].sort((left, right) => { + if (right[1] !== left[1]) { + return right[1] - left[1]; + } + return left[0].localeCompare(right[0]); + }) + ); + } + + private summarizeNodeIdentity(component: Component): Record { + return { + type: component.type?.trim() ?? '', + id: component.id?.trim() ?? '', + key: component.key?.trim() ?? '', + name: component.name?.trim() ?? '', + text: component.text?.trim() ?? '', + hint: component.hint?.trim() ?? '', + }; + } + + private parseResponse(content: string): AiCompareResponse { + if (!content) { + return {}; + } + + const jsonLike = content.match(/\{[\s\S]*\}/)?.[0] ?? content; + try { + const parsed = JSON.parse(jsonLike) as AiCompareResponse; + return parsed; + } catch { + const lowered = content.toLowerCase(); + if (lowered.includes('true')) { + return { same: true, confidence: 0.5, reason: 'fallback true parse' }; + } + if (lowered.includes('false')) { + return { same: false, confidence: 0.5, reason: 'fallback false parse' }; + } + return {}; + } + } + + private buildCacheKey( + mobileComponent: Component, + twoInOneComponent: Component, + context: AiComponentMatchContext + ): string { + return [ + context.mode, + context.mobileKey, + context.twoInOneKey, + this.componentFingerprint(mobileComponent), + this.componentFingerprint(twoInOneComponent), + ].join('||'); + } + + private componentFingerprint(component: Component): string { + const structure = this.summarizeStructure(component); + return [ + component.type?.trim() ?? '', + component.key?.trim() ?? '', + component.id?.trim() ?? '', + component.name?.trim() ?? '', + component.text?.trim() ?? '', + component.hint?.trim() ?? '', + JSON.stringify(structure), + ].join('::'); + } +} + +function buildOpenRouterHeaders(baseURL?: string, config?: GptConfig): Record | undefined { + if (!isOpenRouterBaseURL(baseURL)) { + return undefined; + } + + const siteURL = config?.siteURL?.trim() || 'https://github.com/SMAT-Lab/HapTest'; + const appName = config?.appName?.trim() || 'HapTest'; + return { + 'HTTP-Referer': siteURL, + 'X-Title': appName, + }; +} + +function isOpenRouterBaseURL(baseURL?: string): boolean { + return (baseURL ?? '').toLowerCase().includes('openrouter.ai'); +} + +function isConnectionLikeError(error: unknown): boolean { + const content = String(error ?? '').toLowerCase(); + return ( + content.includes('connection error') || + content.includes('fetch failed') || + content.includes('network') || + content.includes('econnreset') || + content.includes('etimedout') || + content.includes('enotfound') || + content.includes('socket hang up') + ); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/compare/component_matcher.ts b/src/compare/component_matcher.ts new file mode 100644 index 0000000..e4f2fbd --- /dev/null +++ b/src/compare/component_matcher.ts @@ -0,0 +1,327 @@ +import { Component } from '../model/component'; +import { Page } from '../model/page'; + +export interface ComponentWithParent { + component: Component; + componentName: string; + parentName: string; +} + +export interface AiComponentMatchContext { + mode: 'type-and-identity' | 'parent-and-identity'; + mobileKey: string; + twoInOneKey: string; +} + +export interface AiComponentMatcher { + isSameComponent( + mobileComponent: Component, + twoInOneComponent: Component, + context: AiComponentMatchContext + ): Promise; +} + +export interface MatchedNameComponents { + mobileKey: string; + twoInOneKey: string; + matchedName: string; + mobileComponents: Component[]; + twoInOneComponents: Component[]; + aiMatched: boolean; +} + +export interface MatchedParentComponents { + mobileKey: string; + twoInOneKey: string; + matchedName: string; + mobileComponents: ComponentWithParent[]; + twoInOneComponents: ComponentWithParent[]; + aiMatched: boolean; +} + +export interface ComponentMatchOptions { + aiOnly?: boolean; +} + +export function buildComponentNameMap(page: Page): Map { + const map = new Map(); + for (const component of page.getComponents()) { + const matchKey = buildTypeAndIdentityMatchKey(component); + if (!matchKey) { + continue; + } + const list = map.get(matchKey); + if (list) { + list.push(component); + } else { + map.set(matchKey, [component]); + } + } + return map; +} + +export function buildComponentParentMap(page: Page): Map { + const map = new Map(); + for (const component of page.getComponents()) { + const key = buildParentIdentityMatchKey(component); + if (!key) { + continue; + } + const componentName = getIdentityLabel(component); + const parentName = component.parent ? getIdentityLabel(component.parent) : 'ROOT'; + if (!componentName || !parentName) { + continue; + } + const item: ComponentWithParent = { + component, + componentName, + parentName, + }; + const list = map.get(key); + if (list) { + list.push(item); + } else { + map.set(key, [item]); + } + } + return map; +} + +export async function matchComponentNameMaps( + mobileMap: Map, + twoInOneMap: Map, + aiMatcher?: AiComponentMatcher, + options: ComponentMatchOptions = {} +): Promise { + const aiOnly = options.aiOnly === true; + const matches: MatchedNameComponents[] = []; + const usedTwoInOneKeys = new Set(); + + if (!aiOnly) { + for (const mobileKey of mobileMap.keys()) { + if (!twoInOneMap.has(mobileKey)) { + continue; + } + usedTwoInOneKeys.add(mobileKey); + matches.push({ + mobileKey, + twoInOneKey: mobileKey, + matchedName: mobileKey, + mobileComponents: mobileMap.get(mobileKey)!, + twoInOneComponents: twoInOneMap.get(mobileKey)!, + aiMatched: false, + }); + } + } + + if (!aiMatcher) { + return matches; + } + + const unresolvedMobileKeys = aiOnly + ? [...mobileMap.keys()] + : [...mobileMap.keys()].filter((mobileKey) => !twoInOneMap.has(mobileKey)); + const unresolvedTwoInOneKeys = aiOnly + ? [...twoInOneMap.keys()] + : [...twoInOneMap.keys()].filter((twoInOneKey) => !usedTwoInOneKeys.has(twoInOneKey)); + + for (const mobileKey of unresolvedMobileKeys) { + const mobileComponents = mobileMap.get(mobileKey); + if (!mobileComponents || mobileComponents.length === 0) { + continue; + } + const representativeMobile = pickRepresentativeComponent(mobileComponents); + if (!representativeMobile) { + continue; + } + + let matchedTwoInOneKey: string | undefined; + for (const twoInOneKey of unresolvedTwoInOneKeys) { + if (usedTwoInOneKeys.has(twoInOneKey)) { + continue; + } + const twoInOneComponents = twoInOneMap.get(twoInOneKey); + if (!twoInOneComponents || twoInOneComponents.length === 0) { + continue; + } + const representativeTwoInOne = pickRepresentativeComponent(twoInOneComponents); + if (!representativeTwoInOne) { + continue; + } + if (!isSameType(representativeMobile, representativeTwoInOne)) { + continue; + } + + const isSame = await aiMatcher.isSameComponent(representativeMobile, representativeTwoInOne, { + mode: 'type-and-identity', + mobileKey, + twoInOneKey, + }); + if (!isSame) { + continue; + } + + matchedTwoInOneKey = twoInOneKey; + break; + } + + if (!matchedTwoInOneKey) { + continue; + } + + usedTwoInOneKeys.add(matchedTwoInOneKey); + matches.push({ + mobileKey, + twoInOneKey: matchedTwoInOneKey, + matchedName: `${mobileKey}~${matchedTwoInOneKey}`, + mobileComponents: mobileComponents, + twoInOneComponents: twoInOneMap.get(matchedTwoInOneKey)!, + aiMatched: true, + }); + } + + return matches; +} + +export async function matchComponentParentMaps( + mobileMap: Map, + twoInOneMap: Map, + aiMatcher?: AiComponentMatcher, + options: ComponentMatchOptions = {} +): Promise { + const aiOnly = options.aiOnly === true; + const matches: MatchedParentComponents[] = []; + const usedTwoInOneKeys = new Set(); + + if (!aiOnly) { + for (const mobileKey of mobileMap.keys()) { + if (!twoInOneMap.has(mobileKey)) { + continue; + } + usedTwoInOneKeys.add(mobileKey); + matches.push({ + mobileKey, + twoInOneKey: mobileKey, + matchedName: mobileKey, + mobileComponents: mobileMap.get(mobileKey)!, + twoInOneComponents: twoInOneMap.get(mobileKey)!, + aiMatched: false, + }); + } + } + + if (!aiMatcher) { + return matches; + } + + const unresolvedMobileKeys = aiOnly + ? [...mobileMap.keys()] + : [...mobileMap.keys()].filter((mobileKey) => !twoInOneMap.has(mobileKey)); + const unresolvedTwoInOneKeys = aiOnly + ? [...twoInOneMap.keys()] + : [...twoInOneMap.keys()].filter((twoInOneKey) => !usedTwoInOneKeys.has(twoInOneKey)); + + for (const mobileKey of unresolvedMobileKeys) { + const mobileComponents = mobileMap.get(mobileKey); + if (!mobileComponents || mobileComponents.length === 0) { + continue; + } + const representativeMobile = pickRepresentativeWithParent(mobileComponents); + if (!representativeMobile) { + continue; + } + + let matchedTwoInOneKey: string | undefined; + for (const twoInOneKey of unresolvedTwoInOneKeys) { + if (usedTwoInOneKeys.has(twoInOneKey)) { + continue; + } + const twoInOneComponents = twoInOneMap.get(twoInOneKey); + if (!twoInOneComponents || twoInOneComponents.length === 0) { + continue; + } + const representativeTwoInOne = pickRepresentativeWithParent(twoInOneComponents); + if (!representativeTwoInOne) { + continue; + } + if (!isSameType(representativeMobile.component, representativeTwoInOne.component)) { + continue; + } + + const isSame = await aiMatcher.isSameComponent( + representativeMobile.component, + representativeTwoInOne.component, + { + mode: 'parent-and-identity', + mobileKey, + twoInOneKey, + } + ); + if (!isSame) { + continue; + } + + matchedTwoInOneKey = twoInOneKey; + break; + } + + if (!matchedTwoInOneKey) { + continue; + } + + usedTwoInOneKeys.add(matchedTwoInOneKey); + matches.push({ + mobileKey, + twoInOneKey: matchedTwoInOneKey, + matchedName: `${mobileKey}~${matchedTwoInOneKey}`, + mobileComponents: mobileComponents, + twoInOneComponents: twoInOneMap.get(matchedTwoInOneKey)!, + aiMatched: true, + }); + } + + return matches; +} + +function buildTypeAndIdentityMatchKey(component: Component): string | undefined { + const type = component.type?.trim(); + if (!type) { + return undefined; + } + const keyOrId = getIdentityLabel(component); + if (!keyOrId) { + return undefined; + } + return `${type}::${keyOrId}`; +} + +function buildParentIdentityMatchKey(component: Component): string | undefined { + const componentIdentity = getIdentityLabel(component); + const parentIdentity = component.parent ? getIdentityLabel(component.parent) : 'ROOT'; + if (!componentIdentity || !parentIdentity) { + return undefined; + } + return `${parentIdentity}>>${componentIdentity}`; +} + +function getIdentityLabel(component: Component): string | undefined { + const identity = (component.key ?? component.id ?? '').trim(); + if (!identity) { + return undefined; + } + return identity; +} + +function pickRepresentativeComponent(components: Component[]): Component | undefined { + return components.find((component) => !!component.type?.trim()) ?? components[0]; +} + +function pickRepresentativeWithParent(components: ComponentWithParent[]): ComponentWithParent | undefined { + return components.find((item) => !!item.component.type?.trim()) ?? components[0]; +} + +function isSameType(left: Component, right: Component): boolean { + const leftType = left.type?.trim(); + const rightType = right.type?.trim(); + return !!leftType && leftType === rightType; +} diff --git a/src/compare/detectors/diff_detector.ts b/src/compare/detectors/diff_detector.ts new file mode 100644 index 0000000..a925bf4 --- /dev/null +++ b/src/compare/detectors/diff_detector.ts @@ -0,0 +1,225 @@ +import { Page } from '../../model/page'; +import { Point } from '../../model/point'; +import { + AiComponentMatcher, + buildComponentParentMap, + ComponentWithParent, + matchComponentParentMaps, +} from '../component_matcher'; +import { CompareComponentDiffGroups, CompareComponentDiffIssue, CompareComponentFieldDiff } from '../types'; +import { HapTestLogger } from '../../utils/logger'; + +const logger = HapTestLogger.getLogger(); + +const COMPARABLE_FIELDS: Array = [ + 'type', + 'id', + 'key', + 'name', + 'text', + 'hint', + 'checkable', + 'checked', + 'clickable', + 'enabled', + 'focused', + 'longClickable', + 'scrollable', + 'selected', + 'visible', + 'debugLine', + 'bounds', + 'origBounds', +]; + +interface ComparableComponentFields { + type?: string; + id?: string; + key?: string; + name?: string; + text?: string; + hint?: string; + checkable?: boolean; + checked?: boolean; + clickable?: boolean; + enabled?: boolean; + focused?: boolean; + longClickable?: boolean; + scrollable?: boolean; + selected?: boolean; + visible?: boolean; + debugLine?: string; + bounds?: string; + origBounds?: string; +} + +export async function detectComponentDiffIssues( + pageIndex: number, + mobilePage: Page, + twoInOnePage: Page, + mobileScreenshot: string, + twoInOneScreenshot: string, + aiMatcher?: AiComponentMatcher, + aiOnlyMatch = false +): Promise { + const mobileParentMap = buildComponentParentMap(mobilePage); + const twoInOneParentMap = buildComponentParentMap(twoInOnePage); + const matchedGroups = await matchComponentParentMaps(mobileParentMap, twoInOneParentMap, aiMatcher, { aiOnly: aiOnlyMatch }); + + let matchedComponentCount = 0; + let aiMatchedCount = 0; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + matchedComponentCount += Math.min(mobileComponents.length, twoInOneComponents.length); + if (group.aiMatched) { + aiMatchedCount += 1; + } + } + const aiLog = aiMatcher ? ` (ai-matched: ${aiMatchedCount})` : ''; + logger.info(`[diff] Matched components: ${matchedComponentCount}${aiLog} (pageIndex=${pageIndex})`); + + const issues: CompareComponentDiffIssue[] = []; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + const pairCount = Math.min(mobileComponents.length, twoInOneComponents.length); + for (let index = 0; index < pairCount; index += 1) { + const mobileComponent = mobileComponents[index]; + const twoInOneComponent = twoInOneComponents[index]; + const fieldDiffs = collectFieldDiffs(mobileComponent, twoInOneComponent); + if (fieldDiffs.length === 0) { + continue; + } + issues.push({ + pageIndex, + componentName: mobileComponent.componentName, + parentName: mobileComponent.parentName, + mobileScreenshot, + twoInOneScreenshot, + diffs: groupFieldDiffs(fieldDiffs), + }); + } + } + return issues; +} + +function groupFieldDiffs(fieldDiffs: CompareComponentFieldDiff[]): CompareComponentDiffGroups { + const grouped: CompareComponentDiffGroups = { + structuralDiffs: [], + statusDiffs: [], + textDiffs: [], + }; + + for (const diff of fieldDiffs) { + const group = classifyDiffField(diff.field); + if (group === 'structural') { + grouped.structuralDiffs.push(diff); + continue; + } + if (group === 'status') { + grouped.statusDiffs.push(diff); + continue; + } + grouped.textDiffs.push(diff); + } + + return grouped; +} + +function classifyDiffField(field: string): 'structural' | 'status' | 'text' { + if (STATUS_FIELDS.has(field)) { + return 'status'; + } + if (TEXT_FIELDS.has(field)) { + return 'text'; + } + return 'structural'; +} + +const STATUS_FIELDS = new Set([ + 'checkable', + 'checked', + 'clickable', + 'enabled', + 'focused', + 'longClickable', + 'scrollable', + 'selected', + 'visible', +]); + +const TEXT_FIELDS = new Set([ + 'text', + 'hint', +]); + +function collectFieldDiffs( + mobileComponent: ComponentWithParent, + twoInOneComponent: ComponentWithParent +): CompareComponentFieldDiff[] { + const mobileSnapshot = snapshotComparableFields(mobileComponent); + const twoInOneSnapshot = snapshotComparableFields(twoInOneComponent); + const diffs: CompareComponentFieldDiff[] = []; + + for (const field of COMPARABLE_FIELDS) { + const mobileValue = mobileSnapshot[field]; + const twoInOneValue = twoInOneSnapshot[field]; + if (mobileValue === twoInOneValue) { + continue; + } + diffs.push({ + field, + mobileValue: stringifyValue(mobileValue), + twoInOneValue: stringifyValue(twoInOneValue), + }); + } + + return diffs; +} + +function snapshotComparableFields(componentWithParent: ComponentWithParent): ComparableComponentFields { + const component = componentWithParent.component; + return { + type: normalizeString(component.type), + id: normalizeString(component.id), + key: normalizeString(component.key), + name: normalizeString(component.name), + text: normalizeString(component.text), + hint: normalizeString(component.hint), + checkable: component.checkable, + checked: component.checked, + clickable: component.clickable, + enabled: component.enabled, + focused: component.focused, + longClickable: component.longClickable, + scrollable: component.scrollable, + selected: component.selected, + visible: component.visible, + debugLine: normalizeString(component.debugLine), + bounds: normalizeBounds(component.bounds), + origBounds: normalizeBounds(component.origBounds), + }; +} + +function normalizeString(value?: string): string | undefined { + const trimmed = value?.trim(); + return trimmed && trimmed.length > 0 ? trimmed : undefined; +} + +function normalizeBounds(bounds?: Point[]): string | undefined { + if (!bounds || bounds.length === 0) { + return undefined; + } + return bounds.map((point) => `${point.x},${point.y}`).join('|'); +} + +function stringifyValue(value: unknown): string { + if (value === undefined) { + return 'undefined'; + } + if (value === null) { + return 'null'; + } + return String(value); +} diff --git a/src/compare/detectors/full_width_detector.ts b/src/compare/detectors/full_width_detector.ts new file mode 100644 index 0000000..341a089 --- /dev/null +++ b/src/compare/detectors/full_width_detector.ts @@ -0,0 +1,72 @@ +import { Page } from '../../model/page'; +import { Component } from '../../model/component'; +import { AiComponentMatcher, buildComponentNameMap, matchComponentNameMaps } from '../component_matcher'; +import { getBoundsRect, getScreenRect } from '../geometry'; +import { CompareIssue, ScreenRect } from '../types'; +import { HapTestLogger } from '../../utils/logger'; + +const logger = HapTestLogger.getLogger(); + +export async function detectFullWidthIssues( + pageIndex: number, + mobilePage: Page, + twoInOnePage: Page, + mobileScreenshot: string, + twoInOneScreenshot: string, + tolerance: number, + aiMatcher?: AiComponentMatcher, + aiOnlyMatch = false +): Promise { + const mobileRect = getScreenRect(mobilePage); + const twoInOneRect = getScreenRect(twoInOnePage); + if (!mobileRect || !twoInOneRect) { + return []; + } + + const mobileMap = buildComponentNameMap(mobilePage); + const twoInOneMap = buildComponentNameMap(twoInOnePage); + const matchedGroups = await matchComponentNameMaps(mobileMap, twoInOneMap, aiMatcher, { aiOnly: aiOnlyMatch }); + + let matchedComponentCount = 0; + let aiMatchedCount = 0; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + matchedComponentCount += Math.min(mobileComponents.length, twoInOneComponents.length); + if (group.aiMatched) { + aiMatchedCount += 1; + } + } + const aiLog = aiMatcher ? ` (ai-matched: ${aiMatchedCount})` : ''; + logger.info(`[full-width] Matched components: ${matchedComponentCount}${aiLog} (pageIndex=${pageIndex})`); + + const issues: CompareIssue[] = []; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + if ( + hasFullWidthComponent(mobileComponents, mobileRect, tolerance) && + hasFullWidthComponent(twoInOneComponents, twoInOneRect, tolerance) + ) { + issues.push({ + pageIndex, + componentName: group.matchedName, + mobileScreenshot, + twoInOneScreenshot, + }); + } + } + return issues; +} + +function hasFullWidthComponent(components: Component[], screenRect: ScreenRect, tolerance: number): boolean { + return components.some((component) => isFullWidth(component, screenRect, tolerance)); +} + +function isFullWidth(component: Component, screenRect: ScreenRect, tolerance: number): boolean { + const rect = getBoundsRect(component.bounds ?? component.origBounds); + if (!rect) { + return false; + } + return rect.left <= screenRect.left + tolerance && rect.right >= screenRect.right - tolerance; +} diff --git a/src/compare/detectors/ratio_detector.ts b/src/compare/detectors/ratio_detector.ts new file mode 100644 index 0000000..764460c --- /dev/null +++ b/src/compare/detectors/ratio_detector.ts @@ -0,0 +1,64 @@ +import { Page } from '../../model/page'; +import { AiComponentMatcher, buildComponentParentMap, matchComponentParentMaps } from '../component_matcher'; +import { getAspectRatio } from '../geometry'; +import { CompareAspectRatioIssue } from '../types'; +import { HapTestLogger } from '../../utils/logger'; + +const logger = HapTestLogger.getLogger(); + +export async function detectAspectRatioIssues( + pageIndex: number, + mobilePage: Page, + twoInOnePage: Page, + mobileScreenshot: string, + twoInOneScreenshot: string, + tolerance: number, + aiMatcher?: AiComponentMatcher, + aiOnlyMatch = false +): Promise { + const mobileParentMap = buildComponentParentMap(mobilePage); + const twoInOneParentMap = buildComponentParentMap(twoInOnePage); + const matchedGroups = await matchComponentParentMaps(mobileParentMap, twoInOneParentMap, aiMatcher, { aiOnly: aiOnlyMatch }); + + let matchedComponentCount = 0; + let aiMatchedCount = 0; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + matchedComponentCount += Math.min(mobileComponents.length, twoInOneComponents.length); + if (group.aiMatched) { + aiMatchedCount += 1; + } + } + const aiLog = aiMatcher ? ` (ai-matched: ${aiMatchedCount})` : ''; + logger.info(`[ratio] Matched components: ${matchedComponentCount}${aiLog} (pageIndex=${pageIndex})`); + + const issues: CompareAspectRatioIssue[] = []; + for (const group of matchedGroups) { + const mobileComponents = group.mobileComponents; + const twoInOneComponents = group.twoInOneComponents; + const pairCount = Math.min(mobileComponents.length, twoInOneComponents.length); + for (let index = 0; index < pairCount; index += 1) { + const mobileComponent = mobileComponents[index]; + const twoInOneComponent = twoInOneComponents[index]; + const mobileRatio = getAspectRatio(mobileComponent.component); + const twoInOneRatio = getAspectRatio(twoInOneComponent.component); + if (mobileRatio === undefined || twoInOneRatio === undefined) { + continue; + } + if (Math.abs(mobileRatio - twoInOneRatio) > tolerance) { + issues.push({ + pageIndex, + componentName: mobileComponent.componentName, + parentName: mobileComponent.parentName, + mobileAspectRatio: mobileRatio, + twoInOneAspectRatio: twoInOneRatio, + mobileScreenshot, + twoInOneScreenshot, + }); + } + } + } + + return issues; +} diff --git a/src/compare/detectors/scene_detector.ts b/src/compare/detectors/scene_detector.ts new file mode 100644 index 0000000..cfb079c --- /dev/null +++ b/src/compare/detectors/scene_detector.ts @@ -0,0 +1,192 @@ +import { Page } from '../../model/page'; +import { Component } from '../../model/component'; +import { CompareSceneIssue, TransitionRecord } from '../types'; +import { HapTestLogger } from '../../utils/logger'; + +const logger = HapTestLogger.getLogger(); + +const MAX_ANCHOR_TEXT_LENGTH = 12; + +export function detectSceneIssues( + transitionIndex: number, + mobileTransition: TransitionRecord, + twoInOneTransition: TransitionRecord, + mobileScreenshot: string, + twoInOneScreenshot: string, + similarityThreshold: number +): CompareSceneIssue[] { + const mobileEventType = getEventType(mobileTransition); + const twoInOneEventType = getEventType(twoInOneTransition); + if (!mobileEventType || !twoInOneEventType || mobileEventType !== twoInOneEventType) { + return []; + } + + if (!isLooseSameScene(mobileTransition.from, twoInOneTransition.from, similarityThreshold)) { + return []; + } + + const similarity = getBusinessSceneSimilarity(mobileTransition.to, twoInOneTransition.to); + const sameSceneClass = isSameBusinessSceneClass( + mobileTransition.to, + twoInOneTransition.to, + similarityThreshold, + similarity + ); + + logger.info( + `[scene] transition=${transitionIndex} event=${mobileEventType} fromMatch=true toSimilarity=${similarity.toFixed(3)}` + ); + + if (sameSceneClass) { + return []; + } + + return [ + { + transitionIndex, + eventType: mobileEventType, + reason: `destination business scene mismatch, similarity=${similarity.toFixed(3)}`, + similarity, + mobileFromPagePath: mobileTransition.from.getPagePath(), + twoInOneFromPagePath: twoInOneTransition.from.getPagePath(), + mobileToPagePath: mobileTransition.to.getPagePath(), + twoInOneToPagePath: twoInOneTransition.to.getPagePath(), + mobileToSceneKey: buildBusinessSceneKey(mobileTransition.to), + twoInOneToSceneKey: buildBusinessSceneKey(twoInOneTransition.to), + mobileScreenshot, + twoInOneScreenshot, + }, + ]; +} + +function getEventType(transition: TransitionRecord): string | undefined { + const json = transition.event?.toJson(); + const type = json?.type; + return typeof type === 'string' && type.trim().length > 0 ? type.trim() : undefined; +} + +function isLooseSameScene(left: Page, right: Page, similarityThreshold: number): boolean { + if (!isSameAbility(left, right)) { + return false; + } + + if (hasSamePagePath(left, right)) { + return true; + } + + if (left.getStructualSig() === right.getStructualSig()) { + return true; + } + + return getBusinessSceneSimilarity(left, right) >= similarityThreshold; +} + +function isSameBusinessSceneClass( + left: Page, + right: Page, + similarityThreshold: number, + similarity?: number +): boolean { + if (!isSameAbility(left, right)) { + return false; + } + + if (hasSamePagePath(left, right)) { + return true; + } + + if (left.getStructualSig() === right.getStructualSig()) { + return true; + } + + const sceneSimilarity = similarity ?? getBusinessSceneSimilarity(left, right); + return sceneSimilarity >= similarityThreshold; +} + +function isSameAbility(left: Page, right: Page): boolean { + return ( + left.getBundleName() === right.getBundleName() && + left.getAbilityName() === right.getAbilityName() + ); +} + +function hasSamePagePath(left: Page, right: Page): boolean { + const leftPath = left.getPagePath().trim(); + const rightPath = right.getPagePath().trim(); + return leftPath.length > 0 && leftPath === rightPath; +} + +function getBusinessSceneSimilarity(left: Page, right: Page): number { + const leftAnchors = collectBusinessAnchors(left); + const rightAnchors = collectBusinessAnchors(right); + if (leftAnchors.size === 0 || rightAnchors.size === 0) { + return 0; + } + + let intersection = 0; + for (const anchor of leftAnchors) { + if (rightAnchors.has(anchor)) { + intersection += 1; + } + } + + const union = new Set([...leftAnchors, ...rightAnchors]).size; + return union === 0 ? 0 : intersection / union; +} + +function buildBusinessSceneKey(page: Page): string { + const anchors = [...collectBusinessAnchors(page)].sort().slice(0, 8); + return [page.getBundleName(), page.getAbilityName(), page.getPagePath(), anchors.join('|')].join('::'); +} + +function collectBusinessAnchors(page: Page): Set { + const anchors = new Set(); + for (const component of page.getComponents()) { + appendComponentAnchor(anchors, 'id', component.id); + appendComponentAnchor(anchors, 'key', component.key); + appendComponentTextAnchor(anchors, component); + } + return anchors; +} + +function appendComponentAnchor(anchors: Set, prefix: string, value?: string): void { + const normalized = normalizeAnchorValue(value); + if (!normalized) { + return; + } + anchors.add(`${prefix}:${normalized}`); +} + +function appendComponentTextAnchor(anchors: Set, component: Component): void { + const text = normalizeTextAnchor(component.text); + const type = component.type?.trim(); + if (!text || !type) { + return; + } + anchors.add(`text:${type}:${text}`); +} + +function normalizeAnchorValue(value?: string): string | undefined { + const trimmed = value?.trim(); + if (!trimmed) { + return undefined; + } + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { + return undefined; + } + return trimmed; +} + +function normalizeTextAnchor(text?: string): string | undefined { + const trimmed = text?.trim(); + if (!trimmed) { + return undefined; + } + if (trimmed.length > MAX_ANCHOR_TEXT_LENGTH) { + return undefined; + } + if (/^\d+$/.test(trimmed)) { + return undefined; + } + return trimmed; +} \ No newline at end of file diff --git a/src/compare/geometry.ts b/src/compare/geometry.ts new file mode 100644 index 0000000..d103200 --- /dev/null +++ b/src/compare/geometry.ts @@ -0,0 +1,64 @@ +import { Component } from '../model/component'; +import { Page } from '../model/page'; +import { Point } from '../model/point'; +import { BoundsBox, ScreenRect } from './types'; + +export function getScreenRect(page: Page): ScreenRect | undefined { + const root = page.getRoot(); + const rootRect = getBoundsRect(root.bounds ?? root.origBounds); + if (rootRect && rootRect.right > rootRect.left) { + return rootRect; + } + + let minX = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + for (const component of page.getComponents()) { + const rect = getBoundsRect(component.bounds ?? component.origBounds); + if (!rect) { + continue; + } + minX = Math.min(minX, rect.left); + maxX = Math.max(maxX, rect.right); + } + + if (!Number.isFinite(minX) || !Number.isFinite(maxX) || maxX <= minX) { + return undefined; + } + + return { left: minX, right: maxX }; +} + +export function getBoundsRect(bounds?: Point[]): ScreenRect | undefined { + const box = getBoundsBox(bounds); + if (!box) { + return undefined; + } + return { left: box.left, right: box.right }; +} + +export function getBoundsBox(bounds?: Point[]): BoundsBox | undefined { + if (!bounds || bounds.length < 2) { + return undefined; + } + const xs = bounds.map((point) => point.x); + const ys = bounds.map((point) => point.y); + return { + left: Math.min(...xs), + right: Math.max(...xs), + top: Math.min(...ys), + bottom: Math.max(...ys), + }; +} + +export function getAspectRatio(component: Component): number | undefined { + const rect = getBoundsBox(component.bounds ?? component.origBounds); + if (!rect) { + return undefined; + } + const width = rect.right - rect.left; + const height = rect.bottom - rect.top; + if (width <= 0 || height <= 0) { + return undefined; + } + return width / height; +} diff --git a/src/compare/page_loader.ts b/src/compare/page_loader.ts new file mode 100644 index 0000000..d8d92f0 --- /dev/null +++ b/src/compare/page_loader.ts @@ -0,0 +1,180 @@ +import fs from 'fs'; +import path from 'path'; +import { EventBuilder } from '../event/event_builder'; +import { Component } from '../model/component'; +import { Page } from '../model/page'; +import { Point } from '../model/point'; +import { ViewTree } from '../model/viewtree'; +import { HapTestLogger } from '../utils/logger'; +import { SCREENSHOT_EXTENSIONS, TransitionRecord } from './types'; + +const logger = HapTestLogger.getLogger(); + +export function resolveRunDirectories( + outputRoot: string, + deviceDir: string, + appFolder: string, + label: string +): { eventsDir: string; tempDir: string } { + const deviceRoot = resolveDeviceRoot(outputRoot, deviceDir, label); + const appRoot = path.join(deviceRoot, appFolder); + ensureDirectory(appRoot, `${label} app folder`); + + const runRoot = resolveRunRoot(appRoot, label); + const eventsDir = path.join(runRoot, 'events'); + const tempDir = path.join(runRoot, 'temp'); + ensureDirectory(eventsDir, `${label} events`); + ensureDirectory(tempDir, `${label} temp`); + return { eventsDir, tempDir }; +} + +export function loadTransitions(eventsDir: string): TransitionRecord[] { + const files = fs + .readdirSync(eventsDir) + .filter((file) => file.endsWith('.json')) + .sort(); + + return files.map((file) => { + const fullPath = path.join(eventsDir, file); + const raw = fs.readFileSync(fullPath, { encoding: 'utf-8' }); + const parsed = JSON.parse(raw) as { from?: unknown; event?: unknown; to?: unknown }; + if (!parsed.from || !parsed.to) { + throw new Error(`Invalid transition file: ${fullPath}`); + } + return { + from: revivePage(parsed.from), + event: parsed.event ? EventBuilder.createEventFromJson(parsed.event) : undefined, + to: revivePage(parsed.to), + }; + }); +} + +export function buildPageSequence(transitions: TransitionRecord[]): Page[] { + if (transitions.length === 0) { + return []; + } + const pages: Page[] = []; + pages.push(transitions[0].from); + for (const transition of transitions) { + pages.push(transition.to); + } + return pages; +} + +export function listScreenshots(tempDir: string): string[] { + return fs + .readdirSync(tempDir) + .filter((file) => SCREENSHOT_EXTENSIONS.has(path.extname(file).toLowerCase())) + .sort() + .map((file) => path.join(tempDir, file)); +} + +function resolveDeviceRoot(outputRoot: string, deviceDir: string, label: string): string { + const exact = path.join(outputRoot, deviceDir); + if (fs.existsSync(exact)) { + return exact; + } + + const entries = fs.readdirSync(outputRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()); + const trimmedMatch = entries.find((entry) => entry.name.trim() === deviceDir); + if (trimmedMatch) { + const resolved = path.join(outputRoot, trimmedMatch.name); + logger.warn(`Resolved ${label} device dir "${deviceDir}" -> "${trimmedMatch.name}"`); + return resolved; + } + + throw new Error(`Missing ${label} device directory: ${exact}`); +} + +function resolveRunRoot(appRoot: string, label: string): string { + const directEvents = path.join(appRoot, 'events'); + const directTemp = path.join(appRoot, 'temp'); + if (fs.existsSync(directEvents) && fs.existsSync(directTemp)) { + return appRoot; + } + + const runDirs = fs + .readdirSync(appRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((name) => { + const runRoot = path.join(appRoot, name); + return fs.existsSync(path.join(runRoot, 'events')) && fs.existsSync(path.join(runRoot, 'temp')); + }) + .sort() + .reverse(); + + if (runDirs.length === 0) { + throw new Error(`No run directory with events/temp found under ${label} app folder: ${appRoot}`); + } + + if (runDirs.length > 1) { + logger.warn(`Multiple ${label} runs found. Using latest: ${runDirs[0]}`); + } + + return path.join(appRoot, runDirs[0]); +} + +function ensureDirectory(dirPath: string, label: string): void { + if (!fs.existsSync(dirPath)) { + throw new Error(`Missing ${label} directory: ${dirPath}`); + } +} + +function revivePage(raw: any): Page { + const abilityName = raw?.abilityName ?? ''; + const bundleName = raw?.bundleName ?? ''; + const pagePath = raw?.pagePath ?? ''; + const viewTreeRaw = raw?.viewTree ?? raw?.viewtree ?? raw?.root ?? raw; + const rootRaw = viewTreeRaw?.root ?? viewTreeRaw; + if (!rootRaw) { + throw new Error('Invalid page data: missing viewTree root'); + } + const root = reviveComponent(rootRaw); + const viewTree = new ViewTree(root); + return new Page(viewTree, abilityName, bundleName, pagePath); +} + +function reviveComponent(raw: any): Component { + const component = Object.assign(new Component(), raw); + component.bounds = parseBounds(raw?.bounds ?? component.bounds); + component.origBounds = parseBounds(raw?.origBounds ?? component.origBounds); + const children = Array.isArray(raw?.children) ? raw.children : []; + component.children = children.map((child: any) => { + const revived = reviveComponent(child); + revived.parent = component; + return revived; + }); + return component; +} + +function parseBounds(bounds: any): Point[] | undefined { + if (!bounds) { + return undefined; + } + if (typeof bounds === 'string') { + const regex = /\[(\d+),(\d+)\]/g; + const points: Point[] = []; + let match; + while ((match = regex.exec(bounds)) !== null) { + points.push({ x: parseInt(match[1], 10), y: parseInt(match[2], 10) }); + } + return points.length ? points : undefined; + } + if (Array.isArray(bounds)) { + return bounds + .map((item) => { + if (!item || typeof item !== 'object') { + return undefined; + } + const x = Number((item as any).x); + const y = Number((item as any).y); + if (Number.isFinite(x) && Number.isFinite(y)) { + return { x, y } as Point; + } + return undefined; + }) + .filter((item): item is Point => Boolean(item)); + } + return undefined; +} diff --git a/src/compare/types.ts b/src/compare/types.ts new file mode 100644 index 0000000..4ee88a1 --- /dev/null +++ b/src/compare/types.ts @@ -0,0 +1,112 @@ +import { Event } from '../event/event'; +import { Page } from '../model/page'; + +export type CompareDetector = 'all' | 'full-width' | 'ratio' | 'scene' | 'diff'; + +export interface CompareOptions { + outputRoot: string; + appFolder: string; + mobileDir?: string; + twoInOneDir?: string; + reportPath?: string; + fullWidthTolerance?: number; + aspectRatioTolerance?: number; + sceneSimilarityThreshold?: number; + detector?: CompareDetector; + aiComponentMatch?: boolean; + aiComponentModel?: string; + aiComponentThreshold?: number; + aiComponentMaxCalls?: number; + aiComponentConfigPath?: string; +} + +export interface CompareIssue { + pageIndex: number; + componentName: string; + mobileScreenshot: string; + twoInOneScreenshot: string; +} + +export interface CompareAspectRatioIssue { + pageIndex: number; + componentName: string; + parentName: string; + mobileAspectRatio: number; + twoInOneAspectRatio: number; + mobileScreenshot: string; + twoInOneScreenshot: string; +} + +export interface CompareSceneIssue { + transitionIndex: number; + eventType: string; + reason: string; + similarity: number; + mobileFromPagePath: string; + twoInOneFromPagePath: string; + mobileToPagePath: string; + twoInOneToPagePath: string; + mobileToSceneKey: string; + twoInOneToSceneKey: string; + mobileScreenshot: string; + twoInOneScreenshot: string; +} + +export interface CompareComponentFieldDiff { + field: string; + mobileValue: string; + twoInOneValue: string; +} + +export interface CompareComponentDiffGroups { + structuralDiffs: CompareComponentFieldDiff[]; + statusDiffs: CompareComponentFieldDiff[]; + textDiffs: CompareComponentFieldDiff[]; +} + +export interface CompareComponentDiffIssue { + pageIndex: number; + componentName: string; + parentName: string; + mobileScreenshot: string; + twoInOneScreenshot: string; + diffs: CompareComponentDiffGroups; +} + +export interface CompareResult { + issues: CompareIssue[]; + aspectRatioIssues: CompareAspectRatioIssue[]; + sceneIssues: CompareSceneIssue[]; + componentDiffIssues: CompareComponentDiffIssue[]; + pageCount: number; + transitionCount: number; + mobilePages: number; + twoInOnePages: number; + mobileTransitions: number; + twoInOneTransitions: number; + mobileScreenshots: number; + twoInOneScreenshots: number; +} + +export interface TransitionRecord { + from: Page; + event?: Event; + to: Page; +} + +export interface ScreenRect { + left: number; + right: number; +} + +export interface BoundsBox { + left: number; + right: number; + top: number; + bottom: number; +} + +export const DEFAULT_TOLERANCE = 1; +export const DEFAULT_RATIO_TOLERANCE = 0.01; +export const DEFAULT_SCENE_SIMILARITY_THRESHOLD = 0.35; +export const SCREENSHOT_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']); diff --git a/src/device/device.ts b/src/device/device.ts index 5e79a8a..521dd27 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -204,8 +204,8 @@ export class Device implements EventSimulator { */ async ensureFullscreen(hap: Hap): Promise { try { - // Wait a bit for the app to fully load after launch - await new Promise((resolve) => setTimeout(resolve, 1000)); + // Wait for the app to fully stabilize after launch + await new Promise((resolve) => setTimeout(resolve, 5000)); // Get all pages from dumpViewTree, not just the "current" one // because the app window might not be the largest page yet @@ -234,7 +234,24 @@ export class Device implements EventSimulator { return false; } - const page = targetPage; + let page = targetPage; + + const focusedInput = page.getComponents().find((component) => { + return component.focused === true && component.inputable; + }); + + if (focusedInput) { + logger.info( + `Focused input detected before fullscreen check, id=${focusedInput.id}, key=${focusedInput.key}, type=${focusedInput.type}. Sending Escape to dismiss focus.` + ); + try { + await this.inputKey(KeyCode.KEYCODE_ESCAPE); + await new Promise((resolve) => setTimeout(resolve, 300)); + page = await this.getCurrentPage(hap); + } catch (err) { + logger.warn('Failed to dismiss focused input before fullscreen check', err); + } + } // Check if the app is stopped or in background if (page.isStop() || page.isBackground()) { @@ -261,34 +278,25 @@ export class Device implements EventSimulator { `Application is not fullscreen. Window: ${windowWidth}x${windowHeight}, Screen: ${screenWidth}x${screenHeight}. Attempting to maximize.` ); - // Find the EnhanceMaximizeBtn component (similar to closeKeyboard method) - // Debug: log page bundle name and component count const pageBundleName = page.getBundleName(); const components = page.getComponents(); - logger.debug(`ensureFullscreen: page bundleName=${pageBundleName}, hap bundleName=${hap.bundleName}, component count=${components.length}`); - - let maximizeBtnFound = false; - for (const component of components) { - // Debug: log components with id or key containing "Enhance" or "Maximize" - if (component.id && (component.id.includes('Enhance') || component.id.includes('Maximize'))) { - logger.debug(`Found component with id=${component.id}, key=${component.key}, type=${component.type}`); - } - if (component.key && (component.key.includes('Enhance') || component.key.includes('Maximize'))) { - logger.debug(`Found component with id=${component.id}, key=${component.key}, type=${component.type}`); - } - - if (component.id === 'EnhanceMaximizeBtn' || component.key === 'EnhanceMaximizeBtn') { - logger.info(`Found EnhanceMaximizeBtn, id=${component.id}, key=${component.key}, bounds=${JSON.stringify(component.bounds)}, clicking to maximize`); - this.sendEvent(new TouchEvent(component)); - maximizeBtnFound = true; - break; - } - } + logger.debug( + `ensureFullscreen: page bundleName=${pageBundleName}, hap bundleName=${hap.bundleName}, component count=${components.length}` + ); + + const maximizeBtn = components.find((component) => { + return component.id === 'EnhanceMaximizeBtn' || component.key === 'EnhanceMaximizeBtn'; + }); - if (!maximizeBtnFound) { + if (!maximizeBtn) { logger.warn(`EnhanceMaximizeBtn not found. Searched ${components.length} components in page with bundleName=${pageBundleName}`); return false; } + + logger.info( + `Found EnhanceMaximizeBtn, id=${maximizeBtn.id}, key=${maximizeBtn.key}, bounds=${JSON.stringify(maximizeBtn.bounds)}, clicking to maximize` + ); + await this.sendEvent(new TouchEvent(maximizeBtn)); // Wait for the window to maximize await new Promise((resolve) => setTimeout(resolve, 500)); @@ -435,6 +443,16 @@ export class Device implements EventSimulator { return controlButtonIds.includes(id) || controlButtonIds.includes(key); } + /** + * Only inspect the window control region for 2in1 clicks. + */ + private shouldInspectControlButtons(point: Point): boolean { + const screenWidth = this.getWidth(); + const screenHeight = this.getHeight(); + + return point.x >= Math.floor(screenWidth * 0.75) && point.y <= Math.floor(screenHeight * 0.25); + } + /** * Simulate a single click * @param point @@ -442,7 +460,7 @@ export class Device implements EventSimulator { async click(point: Point): Promise { const deviceType = this.getDeviceType().trim().toLowerCase(); - if (deviceType === '2in1') { + if (deviceType === '2in1' && this.shouldInspectControlButtons(point)) { try { const page = await this.dumpViewTree(); const components = page.getComponents(); @@ -467,7 +485,7 @@ export class Device implements EventSimulator { async doubleClick(point: Point): Promise { const deviceType = this.getDeviceType().trim().toLowerCase(); - if (deviceType === '2in1') { + if (deviceType === '2in1' && this.shouldInspectControlButtons(point)) { try { const page = await this.dumpViewTree(); const components = page.getComponents(); @@ -492,7 +510,7 @@ export class Device implements EventSimulator { async longClick(point: Point): Promise { const deviceType = this.getDeviceType().trim().toLowerCase(); - if (deviceType === '2in1') { + if (deviceType === '2in1' && this.shouldInspectControlButtons(point)) { try { const page = await this.dumpViewTree(); const components = page.getComponents(); diff --git a/src/device/uidriver/hypium_rpc.ts b/src/device/uidriver/hypium_rpc.ts index 64728ed..0f6e298 100644 --- a/src/device/uidriver/hypium_rpc.ts +++ b/src/device/uidriver/hypium_rpc.ts @@ -14,52 +14,83 @@ */ import moment from 'moment'; +import { getLogger } from 'log4js'; import { ClientSocket } from '../../utils/net_utils'; +const logger = getLogger(); + export class HypiumRpc { private socket: ClientSocket; private timeout: number; private connected: boolean; + private hostPort?: number; + private hostAddress: string; constructor(timeout: number = 10000) { this.socket = new ClientSocket(); this.timeout = timeout; this.connected = false; + this.hostAddress = '127.0.0.1'; } - async connect(port: number, address: string = '127.0.0.1'): Promise { - this.socket.setTimeout(this.timeout); - await this.socket.connect(port, address); - this.socket.setTimeout(0); - this.connected = true; - return this.connected; - } - - async close() { - if (this.connected) { - await this.socket.close(); - this.connected = false; - } - } + async connect(port: number, address: string = '127.0.0.1'): Promise { + this.hostPort = port; + this.hostAddress = address; + this.socket = new ClientSocket(); + this.socket.setTimeout(this.timeout); + await this.socket.connect(port, address); + this.socket.setTimeout(0); + this.connected = true; + return this.connected; + } + + async close() { + if (this.connected) { + await this.socket.close(); + this.connected = false; + } + } async request(method: string, params: any): Promise { - // if (!this.connected) { - // throw new Error('Socket not connected.'); - // } - let data = { + const data = { module: 'com.ohos.devicetest.hypiumApiHelper', method: method, params: params, request_id: moment().format('YYYYMMDDHHmmssSSSSSS'), client: '127.0.0.1', }; - this.socket.setTimeout(this.timeout); - await this.socket.write(JSON.stringify(data) + '\n'); - let response = await this.socket.read(); - this.socket.setTimeout(0); - if (response) { - response = JSON.parse(response).result; + + try { + if (!this.connected && this.hostPort !== undefined) { + await this.connect(this.hostPort, this.hostAddress); + } + + this.socket.setTimeout(this.timeout); + await this.socket.write(JSON.stringify(data) + '\n'); + let response = await this.socket.read(); + this.socket.setTimeout(0); + if (response) { + response = JSON.parse(response).result; + } + return response; + } catch (error) { + logger.warn(`HypiumRpc request failed for ${method}, reconnecting and continuing`, error); + this.connected = false; + try { + await this.socket.close(); + } catch { + // ignore close errors + } + + if (this.hostPort !== undefined) { + try { + await this.connect(this.hostPort, this.hostAddress); + } catch (reconnectError) { + logger.warn(`HypiumRpc reconnect failed for ${method}`, reconnectError); + } + } + + return undefined; } - return response; } } diff --git a/src/utils/dynamic_compare.ts b/src/utils/dynamic_compare.ts index 9e019a2..b9c31da 100644 --- a/src/utils/dynamic_compare.ts +++ b/src/utils/dynamic_compare.ts @@ -15,75 +15,82 @@ import fs from 'fs'; import path from 'path'; -import { Component } from '../model/component'; -import { Page } from '../model/page'; -import { Point } from '../model/point'; -import { ViewTree } from '../model/viewtree'; import { HapTestLogger } from './logger'; +import { detectAspectRatioIssues } from '../compare/detectors/ratio_detector'; +import { detectFullWidthIssues } from '../compare/detectors/full_width_detector'; +import { detectSceneIssues } from '../compare/detectors/scene_detector'; +import { detectComponentDiffIssues } from '../compare/detectors/diff_detector'; +import { OpenAiComponentMatcher } from '../compare/ai_component_matcher'; +import { + buildPageSequence, + listScreenshots, + loadTransitions, + resolveRunDirectories, +} from '../compare/page_loader'; +import { + CompareAspectRatioIssue, + CompareComponentDiffIssue, + CompareDetector, + CompareIssue, + CompareOptions, + CompareResult, + CompareSceneIssue, + DEFAULT_RATIO_TOLERANCE, + DEFAULT_SCENE_SIMILARITY_THRESHOLD, + DEFAULT_TOLERANCE, +} from '../compare/types'; const logger = HapTestLogger.getLogger(); -export interface CompareOptions { - outputRoot: string; - appFolder: string; - mobileDir?: string; - twoInOneDir?: string; - reportPath?: string; - fullWidthTolerance?: number; -} - -export interface CompareIssue { - pageIndex: number; - componentName: string; - mobileScreenshot: string; - twoInOneScreenshot: string; -} - -export interface CompareResult { - issues: CompareIssue[]; - pageCount: number; - mobilePages: number; - twoInOnePages: number; - mobileScreenshots: number; - twoInOneScreenshots: number; -} - -interface TransitionRecord { - from: Page; - to: Page; -} - -interface ScreenRect { - left: number; - right: number; -} - -const DEFAULT_TOLERANCE = 1; -const SCREENSHOT_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']); - -export function compareDynamicLogs(options: CompareOptions): CompareResult { +export type { + CompareAspectRatioIssue, + CompareComponentDiffIssue, + CompareDetector, + CompareIssue, + CompareOptions, + CompareResult, + CompareSceneIssue, +}; + +export async function compareDynamicLogs(options: CompareOptions): Promise { + const detector: CompareDetector = options.detector ?? 'all'; + const runFullWidth = detector === 'all' || detector === 'full-width'; + const runAspectRatio = detector === 'all' || detector === 'ratio'; + const runScene = detector === 'all' || detector === 'scene'; + const runDiff = detector === 'all' || detector === 'diff'; const mobileDir = options.mobileDir ?? 'mobile'; const twoInOneDir = options.twoInOneDir ?? '2in1'; const tolerance = Number.isFinite(options.fullWidthTolerance) ? options.fullWidthTolerance! : DEFAULT_TOLERANCE; + const aspectRatioTolerance = Number.isFinite(options.aspectRatioTolerance) + ? options.aspectRatioTolerance! + : DEFAULT_RATIO_TOLERANCE; + const sceneSimilarityThreshold = Number.isFinite(options.sceneSimilarityThreshold) + ? options.sceneSimilarityThreshold! + : DEFAULT_SCENE_SIMILARITY_THRESHOLD; + const aiComponentMatcher = options.aiComponentMatch + ? OpenAiComponentMatcher.createFromConfig({ + configPath: options.aiComponentConfigPath, + model: options.aiComponentModel, + threshold: options.aiComponentThreshold, + maxCalls: options.aiComponentMaxCalls, + }) + : undefined; + const aiOnlyMatch = options.aiComponentMatch === true; const mobileResolved = resolveRunDirectories(options.outputRoot, mobileDir, options.appFolder, 'mobile'); const twoInOneResolved = resolveRunDirectories(options.outputRoot, twoInOneDir, options.appFolder, '2in1'); - const mobileEventsDir = mobileResolved.eventsDir; - const twoInOneEventsDir = twoInOneResolved.eventsDir; - const mobileTempDir = mobileResolved.tempDir; - const twoInOneTempDir = twoInOneResolved.tempDir; - - const mobileTransitions = loadTransitions(mobileEventsDir); - const twoInOneTransitions = loadTransitions(twoInOneEventsDir); + const mobileTransitions = loadTransitions(mobileResolved.eventsDir); + const twoInOneTransitions = loadTransitions(twoInOneResolved.eventsDir); const mobilePages = buildPageSequence(mobileTransitions); const twoInOnePages = buildPageSequence(twoInOneTransitions); - const mobileScreenshots = listScreenshots(mobileTempDir); - const twoInOneScreenshots = listScreenshots(twoInOneTempDir); + const mobileScreenshots = listScreenshots(mobileResolved.tempDir); + const twoInOneScreenshots = listScreenshots(twoInOneResolved.tempDir); const pageCount = Math.min(mobilePages.length, twoInOnePages.length, mobileScreenshots.length, twoInOneScreenshots.length); + const transitionCount = Math.min(mobileTransitions.length, twoInOneTransitions.length); if (mobilePages.length !== twoInOnePages.length) { logger.warn(`Page count mismatch: mobile=${mobilePages.length}, 2in1=${twoInOnePages.length}. Using min=${pageCount}.`); } @@ -94,50 +101,86 @@ export function compareDynamicLogs(options: CompareOptions): CompareResult { } const issues: CompareIssue[] = []; + const aspectRatioIssues: CompareAspectRatioIssue[] = []; + const sceneIssues: CompareSceneIssue[] = []; + const componentDiffIssues: CompareComponentDiffIssue[] = []; for (let i = 0; i < pageCount; i += 1) { const mobilePage = mobilePages[i]; const twoInOnePage = twoInOnePages[i]; const mobileScreen = mobileScreenshots[i]; const twoInOneScreen = twoInOneScreenshots[i]; - const mobileRect = getScreenRect(mobilePage); - const twoInOneRect = getScreenRect(twoInOnePage); - if (!mobileRect || !twoInOneRect) { - logger.warn(`Skipping pageIndex=${i} because screen rect is missing.`); - continue; + if (runFullWidth) { + const fullWidthFindings = await detectFullWidthIssues( + i, + mobilePage, + twoInOnePage, + mobileScreen, + twoInOneScreen, + tolerance, + aiComponentMatcher, + aiOnlyMatch + ); + issues.push(...fullWidthFindings); } - const mobileMap = buildComponentNameMap(mobilePage); - const twoInOneMap = buildComponentNameMap(twoInOnePage); - const sharedNames = new Set(); - for (const name of mobileMap.keys()) { - if (twoInOneMap.has(name)) { - sharedNames.add(name); - } + if (runAspectRatio) { + const ratioFindings = await detectAspectRatioIssues( + i, + mobilePage, + twoInOnePage, + mobileScreen, + twoInOneScreen, + aspectRatioTolerance, + aiComponentMatcher, + aiOnlyMatch + ); + aspectRatioIssues.push(...ratioFindings); } - for (const name of sharedNames) { - const mobileComponents = mobileMap.get(name)!; - const twoInOneComponents = twoInOneMap.get(name)!; - if ( - hasFullWidthComponent(mobileComponents, mobileRect, tolerance) && - hasFullWidthComponent(twoInOneComponents, twoInOneRect, tolerance) - ) { - issues.push({ - pageIndex: i, - componentName: name, - mobileScreenshot: mobileScreen, - twoInOneScreenshot: twoInOneScreen, - }); - } + if (runDiff) { + const diffFindings = await detectComponentDiffIssues( + i, + mobilePage, + twoInOnePage, + mobileScreen, + twoInOneScreen, + aiComponentMatcher, + aiOnlyMatch + ); + componentDiffIssues.push(...diffFindings); + } + } + + if (runScene) { + for (let i = 0; i < transitionCount; i += 1) { + const mobileTransition = mobileTransitions[i]; + const twoInOneTransition = twoInOneTransitions[i]; + const mobileScreen = getTransitionScreenshot(mobileScreenshots, i); + const twoInOneScreen = getTransitionScreenshot(twoInOneScreenshots, i); + const findings = detectSceneIssues( + i, + mobileTransition, + twoInOneTransition, + mobileScreen, + twoInOneScreen, + sceneSimilarityThreshold + ); + sceneIssues.push(...findings); } } const result: CompareResult = { issues, + aspectRatioIssues, + sceneIssues, + componentDiffIssues, pageCount, + transitionCount, mobilePages: mobilePages.length, twoInOnePages: twoInOnePages.length, + mobileTransitions: mobileTransitions.length, + twoInOneTransitions: twoInOneTransitions.length, mobileScreenshots: mobileScreenshots.length, twoInOneScreenshots: twoInOneScreenshots.length, }; @@ -148,250 +191,44 @@ export function compareDynamicLogs(options: CompareOptions): CompareResult { logger.info(`Dynamic compare report saved: ${options.reportPath}`); } - logger.info(`Dynamic compare finished. Issues=${issues.length}, PagesCompared=${pageCount}`); - return result; -} - -function resolveRunDirectories( - outputRoot: string, - deviceDir: string, - appFolder: string, - label: string -): { eventsDir: string; tempDir: string } { - const deviceRoot = resolveDeviceRoot(outputRoot, deviceDir, label); - const appRoot = path.join(deviceRoot, appFolder); - ensureDirectory(appRoot, `${label} app folder`); - - const runRoot = resolveRunRoot(appRoot, label); - const eventsDir = path.join(runRoot, 'events'); - const tempDir = path.join(runRoot, 'temp'); - ensureDirectory(eventsDir, `${label} events`); - ensureDirectory(tempDir, `${label} temp`); - return { eventsDir, tempDir }; -} - -function resolveDeviceRoot(outputRoot: string, deviceDir: string, label: string): string { - const exact = path.join(outputRoot, deviceDir); - if (fs.existsSync(exact)) { - return exact; - } - - const entries = fs.readdirSync(outputRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()); - const trimmedMatch = entries.find((entry) => entry.name.trim() === deviceDir); - if (trimmedMatch) { - const resolved = path.join(outputRoot, trimmedMatch.name); - logger.warn(`Resolved ${label} device dir "${deviceDir}" -> "${trimmedMatch.name}"`); - return resolved; - } - - throw new Error(`Missing ${label} device directory: ${exact}`); -} - -function resolveRunRoot(appRoot: string, label: string): string { - const directEvents = path.join(appRoot, 'events'); - const directTemp = path.join(appRoot, 'temp'); - if (fs.existsSync(directEvents) && fs.existsSync(directTemp)) { - return appRoot; - } - - const runDirs = fs - .readdirSync(appRoot, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) - .filter((name) => { - const runRoot = path.join(appRoot, name); - return fs.existsSync(path.join(runRoot, 'events')) && fs.existsSync(path.join(runRoot, 'temp')); - }) - .sort() - .reverse(); - - if (runDirs.length === 0) { - throw new Error(`No run directory with events/temp found under ${label} app folder: ${appRoot}`); - } - - if (runDirs.length > 1) { - logger.warn(`Multiple ${label} runs found. Using latest: ${runDirs[0]}`); - } - - return path.join(appRoot, runDirs[0]); -} - -function ensureDirectory(dirPath: string, label: string): void { - if (!fs.existsSync(dirPath)) { - throw new Error(`Missing ${label} directory: ${dirPath}`); - } -} - -function loadTransitions(eventsDir: string): TransitionRecord[] { - const files = fs - .readdirSync(eventsDir) - .filter((file) => file.endsWith('.json')) - .sort(); - - return files.map((file) => { - const fullPath = path.join(eventsDir, file); - const raw = fs.readFileSync(fullPath, { encoding: 'utf-8' }); - const parsed = JSON.parse(raw) as { from?: unknown; to?: unknown }; - if (!parsed.from || !parsed.to) { - throw new Error(`Invalid transition file: ${fullPath}`); + const issueCounters: string[] = []; + const fullWidthIssueCount = issues.length; + const aspectRatioIssueCount = aspectRatioIssues.length; + const sceneIssueCount = sceneIssues.length; + const componentDiffIssueCount = componentDiffIssues.length; + + if (detector === 'all') { + const totalIssues = fullWidthIssueCount + aspectRatioIssueCount + sceneIssueCount + componentDiffIssueCount; + issueCounters.push(`Issues=${totalIssues}`); + issueCounters.push(`FullWidthIssues=${fullWidthIssueCount}`); + issueCounters.push(`AspectRatioIssues=${aspectRatioIssueCount}`); + issueCounters.push(`SceneIssues=${sceneIssueCount}`); + issueCounters.push(`ComponentDiffIssues=${componentDiffIssueCount}`); + } else { + if (runFullWidth) { + issueCounters.push(`FullWidthIssues=${fullWidthIssueCount}`); } - return { - from: revivePage(parsed.from), - to: revivePage(parsed.to), - }; - }); -} - -function revivePage(raw: any): Page { - const abilityName = raw?.abilityName ?? ''; - const bundleName = raw?.bundleName ?? ''; - const pagePath = raw?.pagePath ?? ''; - const viewTreeRaw = raw?.viewTree ?? raw?.viewtree ?? raw?.root ?? raw; - const rootRaw = viewTreeRaw?.root ?? viewTreeRaw; - if (!rootRaw) { - throw new Error('Invalid page data: missing viewTree root'); - } - const root = reviveComponent(rootRaw); - const viewTree = new ViewTree(root); - return new Page(viewTree, abilityName, bundleName, pagePath); -} - -function reviveComponent(raw: any): Component { - const component = Object.assign(new Component(), raw); - component.bounds = parseBounds(raw?.bounds ?? component.bounds); - component.origBounds = parseBounds(raw?.origBounds ?? component.origBounds); - const children = Array.isArray(raw?.children) ? raw.children : []; - component.children = children.map((child: any) => { - const revived = reviveComponent(child); - revived.parent = component; - return revived; - }); - return component; -} - -function parseBounds(bounds: any): Point[] | undefined { - if (!bounds) { - return undefined; - } - if (typeof bounds === 'string') { - const regex = /\[(\d+),(\d+)\]/g; - const points: Point[] = []; - let match; - while ((match = regex.exec(bounds)) !== null) { - points.push({ x: parseInt(match[1], 10), y: parseInt(match[2], 10) }); - } - return points.length ? points : undefined; - } - if (Array.isArray(bounds)) { - return bounds - .map((item) => { - if (!item || typeof item !== 'object') { - return undefined; - } - const x = Number((item as any).x); - const y = Number((item as any).y); - if (Number.isFinite(x) && Number.isFinite(y)) { - return { x, y } as Point; - } - return undefined; - }) - .filter((item): item is Point => Boolean(item)); - } - return undefined; -} - -function buildPageSequence(transitions: TransitionRecord[]): Page[] { - if (transitions.length === 0) { - return []; - } - const pages: Page[] = []; - pages.push(transitions[0].from); - for (const transition of transitions) { - pages.push(transition.to); - } - return pages; -} - -function listScreenshots(tempDir: string): string[] { - return fs - .readdirSync(tempDir) - .filter((file) => SCREENSHOT_EXTENSIONS.has(path.extname(file).toLowerCase())) - .sort() - .map((file) => path.join(tempDir, file)); -} - -function buildComponentNameMap(page: Page): Map { - const map = new Map(); - for (const component of page.getComponents()) { - const matchKey = buildMatchKey(component); - if (!matchKey) { - continue; + if (runAspectRatio) { + issueCounters.push(`AspectRatioIssues=${aspectRatioIssueCount}`); } - const list = map.get(matchKey); - if (list) { - list.push(component); - } else { - map.set(matchKey, [component]); + if (runScene) { + issueCounters.push(`SceneIssues=${sceneIssueCount}`); } - } - return map; -} - -function buildMatchKey(component: Component): string | undefined { - const type = component.type?.trim(); - if (!type) { - return undefined; - } - const keyOrId = (component.key ?? component.id ?? '').trim(); - if (!keyOrId) { - return undefined; - } - return `${type}::${keyOrId}`; -} - -function hasFullWidthComponent(components: Component[], screenRect: ScreenRect, tolerance: number): boolean { - return components.some((component) => isFullWidth(component, screenRect, tolerance)); -} - -function isFullWidth(component: Component, screenRect: ScreenRect, tolerance: number): boolean { - const rect = getBoundsRect(component.bounds ?? component.origBounds); - if (!rect) { - return false; - } - return rect.left <= screenRect.left + tolerance && rect.right >= screenRect.right - tolerance; -} - -function getScreenRect(page: Page): ScreenRect | undefined { - const root = page.getRoot(); - const rootRect = getBoundsRect(root.bounds ?? root.origBounds); - if (rootRect && rootRect.right > rootRect.left) { - return rootRect; - } - - let minX = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - for (const component of page.getComponents()) { - const rect = getBoundsRect(component.bounds ?? component.origBounds); - if (!rect) { - continue; + if (runDiff) { + issueCounters.push(`ComponentDiffIssues=${componentDiffIssueCount}`); } - minX = Math.min(minX, rect.left); - maxX = Math.max(maxX, rect.right); - } - - if (!Number.isFinite(minX) || !Number.isFinite(maxX) || maxX <= minX) { - return undefined; } + const detectorSummary = issueCounters.join(', '); - return { left: minX, right: maxX }; + logger.info( + `Dynamic compare finished. Detector=${detector}, ${detectorSummary}, PagesCompared=${pageCount}, TransitionsCompared=${transitionCount}` + ); + return result; } -function getBoundsRect(bounds?: Point[]): ScreenRect | undefined { - if (!bounds || bounds.length < 2) { - return undefined; +function getTransitionScreenshot(screenshots: string[], transitionIndex: number): string { + if (screenshots.length === 0) { + return ''; } - const xs = bounds.map((point) => point.x); - const left = Math.min(...xs); - const right = Math.max(...xs); - return { left, right }; + return screenshots[Math.min(transitionIndex + 1, screenshots.length - 1)]; } diff --git a/test/unit/component_matcher_ai.test.ts b/test/unit/component_matcher_ai.test.ts new file mode 100644 index 0000000..bed4fdd --- /dev/null +++ b/test/unit/component_matcher_ai.test.ts @@ -0,0 +1,150 @@ +import { describe, expect, it } from 'vitest'; +import { Component } from '../../src/model/component'; +import { Page } from '../../src/model/page'; +import { ViewTree } from '../../src/model/viewtree'; +import { + AiComponentMatchContext, + AiComponentMatcher, + buildComponentNameMap, + buildComponentParentMap, + matchComponentNameMaps, + matchComponentParentMaps, +} from '../../src/compare/component_matcher'; + +function createComponent( + type: string, + options: { + id?: string; + key?: string; + text?: string; + children?: Component[]; + } = {} +): Component { + const component = new Component(); + component.type = type; + component.id = options.id; + component.key = options.key; + component.text = options.text; + component.bounds = [ + { x: 0, y: 0 }, + { x: 100, y: 100 }, + ]; + component.children = options.children ?? []; + for (const child of component.children) { + child.parent = component; + } + return component; +} + +function createPage(children: Component[]): Page { + const root = createComponent('Root', { id: 'root', children }); + return new Page(new ViewTree(root), 'Ability', 'com.example.demo', 'pages/index'); +} + +class MockAiMatcher implements AiComponentMatcher { + async isSameComponent( + mobileComponent: Component, + twoInOneComponent: Component, + _context: AiComponentMatchContext + ): Promise { + const mobileText = mobileComponent.text?.trim(); + const twoInOneText = twoInOneComponent.text?.trim(); + return !!mobileText && mobileText === twoInOneText; + } +} + +describe('component_matcher ai fallback', () => { + it('uses ai fallback for name map when identity differs', async () => { + const mobilePage = createPage([ + createComponent('Button', { key: 'btn_login_mobile', text: '登录' }), + ]); + const twoInOnePage = createPage([ + createComponent('Button', { key: 'btn_login_2in1', text: '登录' }), + ]); + + const mobileMap = buildComponentNameMap(mobilePage); + const twoInOneMap = buildComponentNameMap(twoInOnePage); + + const exactMatches = await matchComponentNameMaps(mobileMap, twoInOneMap); + expect(exactMatches).toHaveLength(1); + + const aiMatches = await matchComponentNameMaps(mobileMap, twoInOneMap, new MockAiMatcher()); + expect(aiMatches).toHaveLength(2); + expect(aiMatches[1].aiMatched).toBe(true); + }); + + it('uses ai-only mode for name map and skips exact pre-match', async () => { + const mobilePage = createPage([ + createComponent('Button', { key: 'btn_same_identity', text: '登录' }), + ]); + const twoInOnePage = createPage([ + createComponent('Button', { key: 'btn_same_identity', text: '注册' }), + ]); + + const mobileMap = buildComponentNameMap(mobilePage); + const twoInOneMap = buildComponentNameMap(twoInOnePage); + const targetKey = 'Button::btn_same_identity'; + + const exactMatches = await matchComponentNameMaps(mobileMap, twoInOneMap); + expect(exactMatches.some((match) => match.mobileKey === targetKey && match.twoInOneKey === targetKey)).toBe(true); + expect(exactMatches.some((match) => match.aiMatched)).toBe(false); + + const aiOnlyMatches = await matchComponentNameMaps(mobileMap, twoInOneMap, new MockAiMatcher(), { + aiOnly: true, + }); + expect(aiOnlyMatches.some((match) => match.mobileKey === targetKey && match.twoInOneKey === targetKey)).toBe(false); + }); + + it('uses ai fallback for parent map when parent-child identity differs', async () => { + const mobilePage = createPage([ + createComponent('Column', { + key: 'container_mobile', + children: [createComponent('Image', { key: 'cover_mobile', text: '海报' })], + }), + ]); + const twoInOnePage = createPage([ + createComponent('Column', { + key: 'container_2in1', + children: [createComponent('Image', { key: 'cover_2in1', text: '海报' })], + }), + ]); + + const mobileMap = buildComponentParentMap(mobilePage); + const twoInOneMap = buildComponentParentMap(twoInOnePage); + + const exactMatches = await matchComponentParentMaps(mobileMap, twoInOneMap); + expect(exactMatches).toHaveLength(1); + + const aiMatches = await matchComponentParentMaps(mobileMap, twoInOneMap, new MockAiMatcher()); + expect(aiMatches).toHaveLength(2); + expect(aiMatches[1].aiMatched).toBe(true); + }); + + it('uses ai-only mode for parent map and skips exact pre-match', async () => { + const mobilePage = createPage([ + createComponent('Column', { + key: 'container', + children: [createComponent('Image', { key: 'cover', text: '海报A' })], + }), + ]); + const twoInOnePage = createPage([ + createComponent('Column', { + key: 'container', + children: [createComponent('Image', { key: 'cover', text: '海报B' })], + }), + ]); + + const mobileMap = buildComponentParentMap(mobilePage); + const twoInOneMap = buildComponentParentMap(twoInOnePage); + const targetKey = 'container>>cover'; + + const exactMatches = await matchComponentParentMaps(mobileMap, twoInOneMap); + expect(exactMatches.some((match) => match.mobileKey === targetKey && match.twoInOneKey === targetKey)).toBe(true); + expect(exactMatches.some((match) => match.aiMatched)).toBe(false); + + const aiOnlyMatches = await matchComponentParentMaps(mobileMap, twoInOneMap, new MockAiMatcher(), { + aiOnly: true, + }); + expect(aiOnlyMatches.some((match) => match.mobileKey === targetKey && match.twoInOneKey === targetKey)).toBe(false); + }); +}); diff --git a/test/unit/diff_detector.test.ts b/test/unit/diff_detector.test.ts new file mode 100644 index 0000000..5cb450b --- /dev/null +++ b/test/unit/diff_detector.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it } from 'vitest'; +import { Component } from '../../src/model/component'; +import { Page } from '../../src/model/page'; +import { ViewTree } from '../../src/model/viewtree'; +import { detectComponentDiffIssues } from '../../src/compare/detectors/diff_detector'; + +function createComponent( + type: string, + options: { + id?: string; + key?: string; + text?: string; + visible?: boolean; + children?: Component[]; + bounds?: Array<{ x: number; y: number }>; + } = {} +): Component { + const component = new Component(); + component.type = type; + component.id = options.id; + component.key = options.key; + component.text = options.text; + component.visible = options.visible; + component.bounds = options.bounds ?? [ + { x: 0, y: 0 }, + { x: 100, y: 100 }, + ]; + component.children = options.children ?? []; + for (const child of component.children) { + child.parent = component; + } + return component; +} + +function createPage(children: Component[]): Page { + const root = createComponent('Root', { id: 'root', children }); + return new Page(new ViewTree(root), 'Ability', 'com.example.demo', 'pages/index'); +} + +describe('detectComponentDiffIssues', () => { + it('returns empty when matched components are identical', async () => { + const mobilePage = createPage([ + createComponent('Column', { + key: 'container', + children: [ + createComponent('Button', { + key: 'btn_submit', + text: '提交', + visible: true, + }), + ], + }), + ]); + + const twoInOnePage = createPage([ + createComponent('Column', { + key: 'container', + children: [ + createComponent('Button', { + key: 'btn_submit', + text: '提交', + visible: true, + }), + ], + }), + ]); + + const issues = await detectComponentDiffIssues(0, mobilePage, twoInOnePage, 'mobile.png', '2in1.png'); + expect(issues).toHaveLength(0); + }); + + it('reports every field-level difference for matched components', async () => { + const mobilePage = createPage([ + createComponent('Column', { + key: 'container', + children: [ + createComponent('Button', { + key: 'btn_submit', + text: '提交', + visible: true, + bounds: [ + { x: 10, y: 20 }, + { x: 110, y: 80 }, + ], + }), + ], + }), + ]); + + const twoInOnePage = createPage([ + createComponent('Column', { + key: 'container', + children: [ + createComponent('Button', { + key: 'btn_submit', + text: '确认', + visible: false, + bounds: [ + { x: 12, y: 20 }, + { x: 140, y: 86 }, + ], + }), + ], + }), + ]); + + const issues = await detectComponentDiffIssues(1, mobilePage, twoInOnePage, 'mobile.png', '2in1.png'); + expect(issues).toHaveLength(1); + expect(issues[0].componentName).toBe('btn_submit'); + + const structuralFields = issues[0].diffs.structuralDiffs.map((item) => item.field); + const statusFields = issues[0].diffs.statusDiffs.map((item) => item.field); + const textFields = issues[0].diffs.textDiffs.map((item) => item.field); + + expect(structuralFields).toContain('bounds'); + expect(statusFields).toContain('visible'); + expect(textFields).toContain('text'); + }); +}); diff --git a/test/unit/scene_detector.test.ts b/test/unit/scene_detector.test.ts new file mode 100644 index 0000000..30f23a9 --- /dev/null +++ b/test/unit/scene_detector.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from 'vitest'; +import { Direct } from '../../src/device/event_simulator'; +import { Event } from '../../src/event/event'; +import { ScrollEvent, TouchEvent } from '../../src/event/ui_event'; +import { Component } from '../../src/model/component'; +import { Page } from '../../src/model/page'; +import { ViewTree } from '../../src/model/viewtree'; +import { detectSceneIssues } from '../../src/compare/detectors/scene_detector'; +import { TransitionRecord } from '../../src/compare/types'; + +function createComponent( + type: string, + options: { + text?: string; + id?: string; + key?: string; + children?: Component[]; + } = {} +): Component { + const component = new Component(); + component.type = type; + component.text = options.text; + component.id = options.id; + component.key = options.key; + component.bounds = [ + { x: 0, y: 0 }, + { x: 100, y: 100 }, + ]; + component.children = options.children ?? []; + for (const child of component.children) { + child.parent = component; + } + return component; +} + +function createPage(pagePath: string, children: Component[]): Page { + const root = createComponent('root', { children }); + return new Page(new ViewTree(root), 'PhoneAbility', 'com.example.demo', pagePath); +} + +function createTransition(from: Page, to: Page, event: Event = new TouchEvent({ x: 10, y: 10 })): TransitionRecord { + return { from, event, to }; +} + +describe('detectSceneIssues', () => { + it('skips when destination pages belong to the same business scene class', () => { + const mobileFrom = createPage('pages/Home', [ + createComponent('Tabs', { key: 'home_tabs' }), + createComponent('Text', { text: '首页' }), + ]); + const twoInOneFrom = createPage('pages/Home', [ + createComponent('Tabs', { key: 'home_tabs' }), + createComponent('Text', { text: '首页' }), + createComponent('Blank'), + ]); + + const mobileTo = createPage('pages/Detail', [ + createComponent('Text', { text: '评论' }), + createComponent('Button', { key: 'collect' }), + ]); + const twoInOneTo = createPage('pages/Detail', [ + createComponent('Button', { key: 'collect' }), + createComponent('Text', { text: '评论' }), + createComponent('Column'), + ]); + + const issues = detectSceneIssues( + 0, + createTransition(mobileFrom, mobileTo), + createTransition(twoInOneFrom, twoInOneTo), + 'mobile.png', + '2in1.png', + 0.35 + ); + + expect(issues).toHaveLength(0); + }); + + it('reports when the event type matches but destination business scenes diverge', () => { + const sharedFromMobile = createPage('pages/Home', [ + createComponent('Tabs', { key: 'home_tabs' }), + createComponent('Text', { text: '首页' }), + ]); + const sharedFromTwoInOne = createPage('pages/Home', [ + createComponent('Tabs', { key: 'home_tabs' }), + createComponent('Text', { text: '首页' }), + ]); + + const mobileTo = createPage('pages/Detail', [ + createComponent('Button', { key: 'comment' }), + createComponent('Text', { text: '评论' }), + ]); + const twoInOneTo = createPage('pages/Profile', [ + createComponent('Button', { key: 'setting' }), + createComponent('Text', { text: '设置' }), + ]); + + const issues = detectSceneIssues( + 1, + createTransition(sharedFromMobile, mobileTo), + createTransition(sharedFromTwoInOne, twoInOneTo), + 'mobile_detail.png', + '2in1_profile.png', + 0.35 + ); + + expect(issues).toHaveLength(1); + expect(issues[0].eventType).toBe('TouchEvent'); + expect(issues[0].mobileToPagePath).toBe('pages/Detail'); + expect(issues[0].twoInOneToPagePath).toBe('pages/Profile'); + }); + + it('skips when event types do not match', () => { + const from = createPage('pages/Home', [createComponent('Text', { text: '首页' })]); + const to = createPage('pages/Detail', [createComponent('Text', { text: '详情' })]); + const mobileTransition = createTransition(from, to, new TouchEvent({ x: 10, y: 10 })); + const twoInOneTransition = createTransition(from, to, new ScrollEvent({ x: 10, y: 10 }, Direct.DOWN)); + + const issues = detectSceneIssues(2, mobileTransition, twoInOneTransition, 'a.png', 'b.png', 0.35); + + expect(issues).toHaveLength(0); + }); +}); \ No newline at end of file