diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index 18ea4327..be6ebe93 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -4,10 +4,51 @@ on: pull_request: paths: - 'template/nestJs/**' + - 'src/lib/**/*.ts' workflow_dispatch: jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + cli: ${{ steps.filter.outputs.cli }} + backend: ${{ steps.filter.outputs.backend }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Detect changed paths + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + cli: + - 'src/lib/**/*.ts' + backend: + - 'template/nestJs/**' + + Cli: + needs: detect-changes + if: needs.detect-changes.outputs.cli == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Install depend + run: pnpm install --no-frozen-lockfile + - name: Run Init Command + run: pnpm test:cli Backend: + needs: detect-changes + if: needs.detect-changes.outputs.backend == 'true' runs-on: ubuntu-latest permissions: checks: write diff --git a/package.json b/package.json index 98a5166e..e972150c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "test:unit:backend": "pnpm -F tinyui-nestjs-server test", "test:cov:backend": "pnpm -F tinyui-nestjs-server test:cov", "test:e2e": "playwright test", + "test:cli": "vitest", "watch": "run-s clean build:main && run-p \"build:main -- -w\" ", "cov": "run-s build test:unit cov:html && open-cli coverage/index.html", "cov:html": "nyc report --reporter=html", @@ -118,7 +119,8 @@ "tslint-config-airbnb": "^5.11.1", "tslint-config-prettier": "^1.18.0", "tslint-immutable": "^6.0.1", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "vitest": "^4.1.10" }, "ava": { "failFast": true, @@ -166,4 +168,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31d3d243..d483a34f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: typescript: specifier: ^5.1.6 version: 5.1.6 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@14.18.63)(vite@6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) template/lowcode-designer: dependencies: @@ -147,7 +150,7 @@ importers: version: 2.9.0(supports-color@7.2.0) '@opentiny/tiny-engine-vite-config': specifier: ^2.7.0 - version: 2.9.0(esbuild@0.25.2)(monaco-editor@0.51.0)(rollup@4.40.0)(sugarss@2.0.0)(supports-color@7.2.0)(terser@5.39.0)(vue@3.5.40(typescript@5.1.6)) + version: 2.9.0(esbuild@0.28.1)(monaco-editor@0.51.0)(rollup@4.40.0)(sugarss@2.0.0)(supports-color@7.2.0)(terser@5.39.0)(vue@3.5.40(typescript@5.1.6)) '@vitejs/plugin-vue': specifier: ^5.1.2 version: 5.2.3(vite@5.4.21(@types/node@18.19.130)(less@4.3.0)(sugarss@2.0.0)(terser@5.39.0))(vue@3.5.40(typescript@5.1.6)) @@ -165,7 +168,7 @@ importers: dependencies: '@liaoliaots/nestjs-redis': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(ioredis@5.11.1) + version: 10.0.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(ioredis@5.11.1(supports-color@7.2.0)) '@mikro-orm/core': specifier: ^7.1.5 version: 7.1.7 @@ -180,31 +183,31 @@ importers: version: 7.1.7(@mikro-orm/core@7.1.7)(@types/node@24.13.3) '@mikro-orm/nestjs': specifier: ^7.0.2 - version: 7.0.2(@mikro-orm/core@7.1.7)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2) + version: 7.0.2(@mikro-orm/core@7.1.7)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2) '@nestjs/common': specifier: ^11.0.1 - version: 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) '@nestjs/config': specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) + version: 3.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(rxjs@7.8.1) '@nestjs/core': specifier: ^11.0.1 - version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/cqrs': specifier: ^11.0.3 - version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^11.0.2 - version: 11.0.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 11.0.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0)) '@nestjs/mapped-types': specifier: ^2.1.1 - version: 2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + version: 2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) '@nestjs/platform-express': specifier: ^11.0.1 - version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28) + version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28) '@nestjs/swagger': specifier: 11.4.5 - version: 11.4.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + version: 11.4.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -213,7 +216,7 @@ importers: version: 0.14.4 ioredis: specifier: ^5.11.1 - version: 5.11.1 + version: 5.11.1(supports-color@7.2.0) lodash.get: specifier: ^4.4.2 version: 4.4.2 @@ -222,10 +225,10 @@ importers: version: 1.1.0 nest-winston: specifier: ^1.10.2 - version: 1.10.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(winston@3.17.0) + version: 1.10.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(winston@3.17.0) nestjs-i18n: specifier: ^10.5.1 - version: 10.8.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(class-validator@0.14.4)(rxjs@7.8.1) + version: 10.8.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(class-validator@0.14.4)(rxjs@7.8.1) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -265,7 +268,7 @@ importers: version: 11.1.0(chokidar@4.0.3)(prettier@3.8.0)(typescript@5.7.3) '@nestjs/testing': specifier: ^11.0.1 - version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(@nestjs/platform-express@11.1.28) + version: 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(@nestjs/platform-express@11.1.28) '@types/express': specifier: ^5.0.6 version: 5.0.6 @@ -298,7 +301,7 @@ importers: version: 17.0.0 jest: specifier: ^30.0.0 - version: 30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) + version: 30.4.2(@types/node@24.13.3)(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) picocolors: specifier: ^1.1.1 version: 1.1.1 @@ -310,10 +313,10 @@ importers: version: 0.5.21 supertest: specifier: ^7.0.0 - version: 7.2.2 + version: 7.2.2(supports-color@7.2.0) ts-jest: specifier: ^29.2.5 - version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)))(typescript@5.7.3) + version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)))(typescript@5.7.3) ts-loader: specifier: ^9.5.2 version: 9.6.2(loader-utils@2.0.4)(typescript@5.7.3)(webpack@5.108.4) @@ -328,7 +331,7 @@ importers: version: 5.7.3 typescript-eslint: specifier: ^8.20.0 - version: 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) + version: 8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3) template/nestJs: dependencies: @@ -364,7 +367,7 @@ importers: version: 8.1.1(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))) + version: 10.0.2(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -376,7 +379,7 @@ importers: version: 16.4.7 ioredis: specifier: ^5.4.1 - version: 5.11.1 + version: 5.11.1(supports-color@7.2.0) joi: specifier: ^18.0.2 version: 18.2.3 @@ -394,7 +397,7 @@ importers: version: 10.8.5(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(class-validator@0.14.4)(rxjs@7.8.1) nestjs-typeorm-paginate: specifier: ^4.0.4 - version: 4.1.0(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))) + version: 4.1.0(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))) pm2: specifier: ^6.0.14 version: 6.0.14(supports-color@7.2.0) @@ -418,7 +421,7 @@ importers: version: 2.1.5(@types/node@20.3.3)(@types/validator@13.12.3)(reflect-metadata@0.1.13)(sequelize@6.32.1(mysql2@3.23.1(@types/node@20.3.3))(supports-color@8.1.1)) typeorm: specifier: ^0.3.22 - version: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) + version: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) typescript: specifier: 5.1.6 version: 5.1.6 @@ -582,10 +585,10 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^7.0.1 - version: 7.0.1(@unocss/eslint-plugin@66.6.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6))(@vue/compiler-sfc@3.5.40)(eslint-plugin-format@1.3.1(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6) + version: 7.0.1(@unocss/eslint-plugin@66.6.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6))(@vue/compiler-sfc@3.5.40)(eslint-plugin-format@1.3.1(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6)(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))) '@babel/preset-env': specifier: ^7.25.4 - version: 7.29.7(@babel/core@7.29.7) + version: 7.29.7(@babel/core@7.29.7)(supports-color@7.2.0) '@babel/preset-typescript': specifier: ^7.24.7 version: 7.29.7(@babel/core@7.29.7) @@ -723,7 +726,7 @@ importers: version: 66.1.0-beta.11(postcss@8.5.20)(supports-color@7.2.0)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@5.1.6)) unplugin-vue-components: specifier: ^0.17.21 - version: 0.17.21(@babel/parser@7.29.7)(@babel/traverse@7.29.7)(esbuild@0.25.2)(rollup@2.79.2)(supports-color@8.1.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@5.1.6))(webpack@5.108.4) + version: 0.17.21(@babel/parser@7.29.7)(@babel/traverse@7.29.7)(esbuild@0.28.1)(rollup@2.79.2)(supports-color@8.1.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@5.1.6))(webpack@5.108.4) vite: specifier: ^6.1.0 version: 6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0) @@ -5999,55 +6002,121 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.40.0': resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.40.0': resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.40.0': resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.40.0': resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.40.0': resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-arm-musleabihf@4.40.0': resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-arm64-gnu@4.40.0': resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-arm64-musl@4.40.0': resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] @@ -6060,51 +6129,123 @@ packages: os: [linux] libc: [glibc] + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-riscv64-gnu@4.40.0': resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-riscv64-musl@4.40.0': resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} cpu: [riscv64] os: [linux] libc: [musl] + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-s390x-gnu@4.40.0': resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] libc: [glibc] + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-x64-gnu@4.40.0': resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-x64-musl@4.40.0': resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] libc: [musl] + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.40.0': resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.40.0': resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.40.0': resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + '@rspack/binding-darwin-arm64@0.7.5': resolution: {integrity: sha512-mNBIm36s1BA7v4SL/r4f3IXIsjyH5CZX4eXMRPE52lBc3ClVuUB7d/8zk8dkyjJCMAj8PsZSnAJ3cfXnn7TN4g==} cpu: [arm64] @@ -6355,6 +6496,9 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} @@ -6367,6 +6511,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/diff-match-patch@1.0.36': resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} @@ -7107,6 +7254,35 @@ packages: vitest: optional: true + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@volar/language-core@1.11.1': resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} @@ -7209,9 +7385,6 @@ packages: '@vue/compiler-sfc@3.5.13': resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} - '@vue/compiler-sfc@3.5.29': - resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==} - '@vue/compiler-sfc@3.5.40': resolution: {integrity: sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==} @@ -7221,9 +7394,6 @@ packages: '@vue/compiler-ssr@3.5.13': resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - '@vue/compiler-ssr@3.5.29': - resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==} - '@vue/compiler-ssr@3.5.40': resolution: {integrity: sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==} @@ -7746,6 +7916,10 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} @@ -8258,6 +8432,10 @@ packages: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -10106,6 +10284,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@1.1.6: resolution: {integrity: sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A==} engines: {node: '>=0.10.0'} @@ -10202,6 +10383,10 @@ packages: resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} engines: {node: '>=0.10.0'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -15435,6 +15620,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -15771,6 +15961,9 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -16007,6 +16200,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} @@ -16029,6 +16225,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} @@ -16523,21 +16722,24 @@ packages: tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -17616,6 +17818,47 @@ packages: yaml: optional: true + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vizion@2.2.1: resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} engines: {node: '>=4.0'} @@ -17907,6 +18150,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -18336,7 +18584,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@antfu/eslint-config@7.0.1(@unocss/eslint-plugin@66.6.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6))(@vue/compiler-sfc@3.5.40)(eslint-plugin-format@1.3.1(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6)': + '@antfu/eslint-config@7.0.1(@unocss/eslint-plugin@66.6.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6))(@vue/compiler-sfc@3.5.40)(eslint-plugin-format@1.3.1(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6)(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0)))': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 @@ -18345,7 +18593,7 @@ snapshots: '@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6) '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.1.6) - '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6) + '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6)(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))) ansis: 4.2.0 cac: 6.7.14 eslint: 9.39.2(jiti@2.6.1) @@ -18393,7 +18641,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.0.2 + tinyexec: 1.2.4 '@antfu/utils@0.4.0': dependencies: @@ -18634,7 +18882,7 @@ snapshots: regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)(supports-color@7.2.0)': dependencies: '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 @@ -19407,7 +19655,7 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 - '@babel/preset-env@7.29.7(@babel/core@7.29.7)': + '@babel/preset-env@7.29.7(@babel/core@7.29.7)(supports-color@7.2.0)': dependencies: '@babel/compat-data': 7.29.7 '@babel/core': 7.29.7 @@ -19476,7 +19724,7 @@ snapshots: '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7)(supports-color@7.2.0) babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7) babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) core-js-compat: 3.49.0 @@ -19917,13 +20165,13 @@ snapshots: '@es-joy/resolve.exports@1.2.0': {} - '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.25.2)': + '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.28.1)': dependencies: - esbuild: 0.25.2 + esbuild: 0.28.1 - '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.25.2)': + '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.28.1)': dependencies: - esbuild: 0.25.2 + esbuild: 0.28.1 escape-string-regexp: 4.0.0 rollup-plugin-node-polyfills: 0.2.1 @@ -20830,11 +21078,11 @@ snapshots: - supports-color - ts-node - '@jest/core@30.4.2(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3))': + '@jest/core@30.4.2(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3))': dependencies: '@jest/console': 30.4.1 '@jest/pattern': 30.4.0 - '@jest/reporters': 30.4.1 + '@jest/reporters': 30.4.1(supports-color@7.2.0) '@jest/test-result': 30.4.1 '@jest/transform': 30.4.1 '@jest/types': 30.4.1 @@ -20976,7 +21224,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@jest/reporters@30.4.1': + '@jest/reporters@30.4.1(supports-color@7.2.0)': dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 30.4.1 @@ -20993,7 +21241,7 @@ snapshots: istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 + istanbul-lib-source-maps: 5.0.6(supports-color@7.2.0) istanbul-reports: 3.1.7 jest-message-util: 30.4.1 jest-util: 30.4.1 @@ -21181,11 +21429,11 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(ioredis@5.11.1)': + '@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(ioredis@5.11.1(supports-color@7.2.0))': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) - ioredis: 5.11.1 + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + ioredis: 5.11.1(supports-color@7.2.0) tslib: 2.7.0 '@ljharb/through@2.3.14': @@ -21276,11 +21524,11 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@mikro-orm/nestjs@7.0.2(@mikro-orm/core@7.1.7)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)': + '@mikro-orm/nestjs@7.0.2(@mikro-orm/core@7.1.7)(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)': dependencies: '@mikro-orm/core': 7.1.7 - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 '@mikro-orm/seeder@7.1.7(@mikro-orm/core@7.1.7)': @@ -21451,9 +21699,9 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0)': dependencies: - file-type: 21.3.4 + file-type: 21.3.4(supports-color@7.2.0) iterare: 1.2.1 load-esm: 1.0.3 reflect-metadata: 0.2.2 @@ -21474,9 +21722,9 @@ snapshots: lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/config@3.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': + '@nestjs/config@3.3.0(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) dotenv: 16.4.5 dotenv-expand: 10.0.0 lodash: 4.17.21 @@ -21499,9 +21747,9 @@ snapshots: transitivePeerDependencies: - encoding - '@nestjs/core@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) fast-safe-stringify: 2.1.1 iterare: 1.2.1 path-to-regexp: 8.4.2 @@ -21510,12 +21758,12 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28) + '@nestjs/platform-express': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28) - '@nestjs/cqrs@11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/cqrs@11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 rxjs: 7.8.1 @@ -21525,9 +21773,9 @@ snapshots: '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/jwt@11.0.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@nestjs/jwt@11.0.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) '@types/jsonwebtoken': 9.0.10 jsonwebtoken: 9.0.3 @@ -21547,9 +21795,9 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) reflect-metadata: 0.2.2 optionalDependencies: class-transformer: 0.5.1 @@ -21564,7 +21812,7 @@ snapshots: rxjs: 7.8.1 tslib: 2.8.1 optionalDependencies: - ioredis: 5.11.1 + ioredis: 5.11.1(supports-color@7.2.0) '@nestjs/platform-express@10.0.3(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)': dependencies: @@ -21578,10 +21826,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-express@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)': + '@nestjs/platform-express@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) cors: 2.8.6 express: 5.2.1 multer: 2.2.0 @@ -21648,12 +21896,12 @@ snapshots: sequelize-typescript: 2.1.5(@types/node@20.3.3)(@types/validator@13.12.3)(reflect-metadata@0.1.13)(sequelize@6.32.1(mysql2@3.23.1(@types/node@20.3.3))(supports-color@8.1.1)) uuid: 9.0.0 - '@nestjs/swagger@11.4.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.4.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) js-yaml: 4.3.0 lodash: 4.18.1 path-to-regexp: 8.4.2 @@ -21687,21 +21935,21 @@ snapshots: '@nestjs/microservices': 10.4.22(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(ioredis@5.11.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/platform-express': 10.0.3(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3) - '@nestjs/testing@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(@nestjs/platform-express@11.1.28)': + '@nestjs/testing@11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(@nestjs/platform-express@11.1.28)': dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28) + '@nestjs/platform-express': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)))': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.3)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)))': dependencies: '@nestjs/common': 10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.0.3(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/microservices@10.4.22)(@nestjs/platform-express@10.0.3)(encoding@0.1.13)(reflect-metadata@0.1.13)(rxjs@7.8.1) reflect-metadata: 0.1.13 rxjs: 7.8.1 - typeorm: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) + typeorm: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) uuid: 9.0.1 '@noble/hashes@1.1.5': {} @@ -22511,7 +22759,7 @@ snapshots: '@opentiny/tiny-engine-utils': 2.9.0(@opentiny/vue-renderless@3.20.3)(vue@3.5.40(typescript@5.1.6)) '@opentiny/vue': 3.20.0(echarts@5.6.0) '@opentiny/vue-icon': 3.20.0 - magic-string: 0.30.21 + magic-string: 0.30.17 vue: 3.5.40(typescript@5.1.6) transitivePeerDependencies: - '@cfworker/json-schema' @@ -22928,20 +23176,20 @@ snapshots: jszip: 3.10.1 vue: 3.5.40(typescript@5.1.6) - '@opentiny/tiny-engine-vite-config@2.9.0(esbuild@0.25.2)(monaco-editor@0.51.0)(rollup@4.40.0)(sugarss@2.0.0)(supports-color@7.2.0)(terser@5.39.0)(vue@3.5.40(typescript@5.1.6))': + '@opentiny/tiny-engine-vite-config@2.9.0(esbuild@0.28.1)(monaco-editor@0.51.0)(rollup@4.40.0)(sugarss@2.0.0)(supports-color@7.2.0)(terser@5.39.0)(vue@3.5.40(typescript@5.1.6))': dependencies: '@babel/core': 7.23.9 '@babel/generator': 7.23.6 '@babel/parser': 7.28.5 '@babel/traverse': 7.23.9 - '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.25.2) - '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.25.2) + '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.28.1) + '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.28.1) '@opentiny/tiny-engine-vite-plugin-meta-comments': 2.9.0 '@rollup/plugin-replace': 6.0.3(rollup@4.40.0) '@types/node': 18.19.130 '@vitejs/plugin-vue': 5.2.3(vite@5.4.21(@types/node@18.19.130)(less@4.3.0)(sugarss@2.0.0)(terser@5.39.0))(vue@3.5.40(typescript@5.1.6)) '@vitejs/plugin-vue-jsx': 4.1.2(vite@5.4.21(@types/node@18.19.130)(less@4.3.0)(sugarss@2.0.0)(terser@5.39.0))(vue@3.5.40(typescript@5.1.6)) - esbuild-plugin-copy: 2.1.1(esbuild@0.25.2) + esbuild-plugin-copy: 2.1.1(esbuild@0.28.1) fast-glob: 3.3.3 fs-extra: 10.1.0 less: 4.3.0 @@ -28311,7 +28559,7 @@ snapshots: dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.40.0) estree-walker: 2.0.2 - magic-string: 0.30.21 + magic-string: 0.30.17 optionalDependencies: rollup: 4.40.0 @@ -28338,63 +28586,138 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.40.0': optional: true + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + '@rollup/rollup-android-arm64@4.40.0': optional: true + '@rollup/rollup-android-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-arm64@4.40.0': optional: true + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-x64@4.40.0': optional: true + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + '@rollup/rollup-freebsd-arm64@4.40.0': optional: true + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + '@rollup/rollup-freebsd-x64@4.40.0': optional: true + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.40.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.40.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-musl@4.40.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.40.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-riscv64-musl@4.40.0': optional: true + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.40.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-gnu@4.40.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-musl@4.40.0': optional: true + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.40.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.40.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + '@rspack/binding-darwin-arm64@0.7.5': optional: true @@ -28602,7 +28925,7 @@ snapshots: dependencies: defer-to-connect: 1.1.3 - '@tokenizer/inflate@0.4.1': + '@tokenizer/inflate@0.4.1(supports-color@7.2.0)': dependencies: debug: 4.4.3(supports-color@7.2.0) token-types: 6.1.2 @@ -28646,24 +28969,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/body-parser@1.19.5': dependencies: @@ -28674,6 +28997,11 @@ snapshots: dependencies: '@types/node': 22.20.1 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.1.2 @@ -28689,6 +29017,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/diff-match-patch@1.0.36': {} '@types/eslint-scope@3.7.7': @@ -29061,12 +29391,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/parser': 8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3) '@typescript-eslint/utils': 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) '@typescript-eslint/visitor-keys': 8.65.0 eslint: 9.39.2(jiti@2.6.1) @@ -29102,7 +29432,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 @@ -29179,7 +29509,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 8.65.0 '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.7.3) @@ -29340,7 +29670,7 @@ snapshots: chokidar: 3.6.0 colorette: 2.0.20 consola: 3.4.2 - magic-string: 0.30.21 + magic-string: 0.30.17 pathe: 2.0.3 perfect-debounce: 1.0.0 tinyglobby: 0.2.17 @@ -29457,7 +29787,7 @@ snapshots: '@unocss/rule-utils@66.1.0-beta.11': dependencies: '@unocss/core': 66.1.0-beta.11 - magic-string: 0.30.21 + magic-string: 0.30.17 '@unocss/rule-utils@66.6.0': dependencies: @@ -29489,7 +29819,7 @@ snapshots: '@unocss/core': 66.1.0-beta.11 '@unocss/inspector': 66.1.0-beta.11(vue@3.5.40(typescript@5.1.6)) chokidar: 3.6.0 - magic-string: 0.30.21 + magic-string: 0.30.17 pathe: 2.0.3 tinyglobby: 0.2.17 unplugin-utils: 0.2.4 @@ -29599,16 +29929,67 @@ snapshots: vite: 6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0) vue: 3.5.40(typescript@5.1.6) - '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6)': + '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6)(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0)))': dependencies: '@typescript-eslint/scope-manager': 8.53.0 '@typescript-eslint/utils': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.1.6) eslint: 9.39.2(jiti@2.6.1) optionalDependencies: typescript: 5.1.6 + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0)) transitivePeerDependencies: - supports-color + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.10(vite@6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + + '@vitest/mocker@4.1.10(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0) + optional: true + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@volar/language-core@1.11.1': dependencies: '@volar/source-map': 1.11.1 @@ -29633,10 +30014,10 @@ snapshots: '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.18.13(supports-color@7.2.0)) '@babel/template': 7.29.7 '@babel/traverse': 7.29.7(supports-color@7.2.0) - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@vue/babel-helper-vue-transform-on': 1.4.0 '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.18.13(supports-color@7.2.0)) - '@vue/shared': 3.5.29 + '@vue/shared': 3.5.40 optionalDependencies: '@babel/core': 7.18.13(supports-color@7.2.0) transitivePeerDependencies: @@ -29649,10 +30030,10 @@ snapshots: '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.18.13) '@babel/template': 7.29.7 '@babel/traverse': 7.29.7(supports-color@7.2.0) - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@vue/babel-helper-vue-transform-on': 1.4.0 '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.18.13) - '@vue/shared': 3.5.29 + '@vue/shared': 3.5.40 optionalDependencies: '@babel/core': 7.18.13 transitivePeerDependencies: @@ -29665,10 +30046,10 @@ snapshots: '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/template': 7.29.7 '@babel/traverse': 7.29.7(supports-color@7.2.0) - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@vue/babel-helper-vue-transform-on': 1.4.0 '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.29.7) - '@vue/shared': 3.5.29 + '@vue/shared': 3.5.40 optionalDependencies: '@babel/core': 7.29.7 transitivePeerDependencies: @@ -29680,8 +30061,8 @@ snapshots: '@babel/core': 7.18.13(supports-color@7.2.0) '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/parser': 7.29.0 - '@vue/compiler-sfc': 3.5.29 + '@babel/parser': 7.29.7 + '@vue/compiler-sfc': 3.5.40 transitivePeerDependencies: - supports-color @@ -29691,8 +30072,8 @@ snapshots: '@babel/core': 7.18.13 '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/parser': 7.29.0 - '@vue/compiler-sfc': 3.5.29 + '@babel/parser': 7.29.7 + '@vue/compiler-sfc': 3.5.40 transitivePeerDependencies: - supports-color @@ -29702,8 +30083,8 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/parser': 7.29.0 - '@vue/compiler-sfc': 3.5.29 + '@babel/parser': 7.29.7 + '@vue/compiler-sfc': 3.5.40 transitivePeerDependencies: - supports-color @@ -29852,18 +30233,6 @@ snapshots: postcss: 8.5.3 source-map-js: 1.2.1 - '@vue/compiler-sfc@3.5.29': - dependencies: - '@babel/parser': 7.29.0 - '@vue/compiler-core': 3.5.29 - '@vue/compiler-dom': 3.5.29 - '@vue/compiler-ssr': 3.5.29 - '@vue/shared': 3.5.29 - estree-walker: 2.0.2 - magic-string: 0.30.21 - postcss: 8.5.6 - source-map-js: 1.2.1 - '@vue/compiler-sfc@3.5.40': dependencies: '@babel/parser': 7.29.7 @@ -29886,11 +30255,6 @@ snapshots: '@vue/compiler-dom': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-ssr@3.5.29': - dependencies: - '@vue/compiler-dom': 3.5.29 - '@vue/shared': 3.5.29 - '@vue/compiler-ssr@3.5.40': dependencies: '@vue/compiler-dom': 3.5.40 @@ -30199,6 +30563,10 @@ snapshots: optionalDependencies: ajv: 8.17.1 + ajv-formats@2.1.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -30216,6 +30584,11 @@ snapshots: ajv: 8.17.1 fast-deep-equal: 3.1.3 + ajv-keywords@5.1.0(ajv@8.18.0): + dependencies: + ajv: 8.18.0 + fast-deep-equal: 3.1.3 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -30434,6 +30807,8 @@ snapshots: assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + assign-symbols@1.0.0: {} ast-kit@2.2.0: @@ -30650,7 +31025,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 @@ -30658,11 +31033,11 @@ snapshots: dependencies: '@types/babel__core': 7.20.5 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7)(supports-color@7.2.0): dependencies: '@babel/compat-data': 7.29.7 '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7)(supports-color@7.2.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -30670,7 +31045,7 @@ snapshots: babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7): dependencies: '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7)(supports-color@7.2.0) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color @@ -30678,7 +31053,7 @@ snapshots: babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): dependencies: '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7)(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -30724,7 +31099,7 @@ snapshots: dependencies: '@babel/core': 7.29.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.29.7) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) babel-preset-jest@30.4.0(@babel/core@7.29.7): dependencies: @@ -31237,6 +31612,8 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 + chai@6.2.2: {} + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -32716,11 +33093,11 @@ snapshots: esbuild-openbsd-64@0.14.54: optional: true - esbuild-plugin-copy@2.1.1(esbuild@0.25.2): + esbuild-plugin-copy@2.1.1(esbuild@0.28.1): dependencies: chalk: 4.1.2 chokidar: 3.6.0 - esbuild: 0.25.2 + esbuild: 0.28.1 fs-extra: 10.1.0 globby: 11.1.0 @@ -33061,7 +33438,7 @@ snapshots: jsonc-eslint-parser: 2.4.2 pathe: 2.0.3 pnpm-workspace-yaml: 1.4.3 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 yaml: 2.9.0 yaml-eslint-parser: 1.3.2 @@ -33314,6 +33691,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@1.1.6: {} esutils@2.0.3: {} @@ -33440,6 +33821,8 @@ snapshots: transitivePeerDependencies: - supports-color + expect-type@1.4.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -33828,9 +34211,9 @@ snapshots: file-type@12.4.2: {} - file-type@21.3.4: + file-type@21.3.4(supports-color@7.2.0): dependencies: - '@tokenizer/inflate': 0.4.1 + '@tokenizer/inflate': 0.4.1(supports-color@7.2.0) strtok3: 10.3.5 token-types: 6.1.2 uint8array-extras: 1.5.0 @@ -35166,7 +35549,7 @@ snapshots: invert-kv@3.0.1: {} - ioredis@5.11.1: + ioredis@5.11.1(supports-color@7.2.0): dependencies: '@ioredis/commands': 1.10.0 cluster-key-slot: 1.1.1 @@ -35548,7 +35931,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -35588,7 +35971,7 @@ snapshots: transitivePeerDependencies: - supports-color - istanbul-lib-source-maps@5.0.6: + istanbul-lib-source-maps@5.0.6(supports-color@7.2.0): dependencies: '@jridgewell/trace-mapping': 0.3.31 debug: 4.4.3(supports-color@7.2.0) @@ -35706,7 +36089,7 @@ snapshots: jest-cli@30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)): dependencies: - '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) + '@jest/core': 30.4.2(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) '@jest/test-result': 30.4.1 '@jest/types': 30.4.1 chalk: 4.1.2 @@ -36266,9 +36649,9 @@ snapshots: - supports-color - ts-node - jest@30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)): + jest@30.4.2(@types/node@24.13.3)(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)): dependencies: - '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) + '@jest/core': 30.4.2(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) '@jest/types': 30.4.1 import-local: 3.2.0 jest-cli: 30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) @@ -37982,9 +38365,9 @@ snapshots: fast-safe-stringify: 2.1.1 winston: 3.17.0 - nest-winston@1.10.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(winston@3.17.0): + nest-winston@1.10.2(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(winston@3.17.0): dependencies: - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) fast-safe-stringify: 2.1.1 winston: 3.17.0 @@ -38003,11 +38386,11 @@ snapshots: optionalDependencies: class-validator: 0.14.4 - nestjs-i18n@10.8.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.1.28)(class-validator@0.14.4)(rxjs@7.8.1): + nestjs-i18n@10.8.5(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/core@11.1.28)(class-validator@0.14.4)(rxjs@7.8.1): dependencies: '@messageformat/core': 3.4.0 - '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.1)(supports-color@7.2.0))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.1) chokidar: 3.6.0 cookie: 1.1.1 rxjs: 7.8.1 @@ -38016,10 +38399,10 @@ snapshots: optionalDependencies: class-validator: 0.14.4 - nestjs-typeorm-paginate@4.1.0(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))): + nestjs-typeorm-paginate@4.1.0(@nestjs/common@10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1))(typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6))): dependencies: '@nestjs/common': 10.0.3(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.13)(rxjs@7.8.1) - typeorm: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) + typeorm: 0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)) netmask@2.0.2: {} @@ -40038,6 +40421,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.3(supports-color@7.2.0) @@ -40132,9 +40546,9 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) scslre@0.3.0: dependencies: @@ -40440,6 +40854,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -40717,6 +41133,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + standard-as-callback@2.1.0: {} state-toggle@1.0.3: {} @@ -40732,6 +41150,8 @@ snapshots: statuses@2.0.2: {} + std-env@4.2.0: {} + stream-events@1.0.5: dependencies: stubs: 3.0.0 @@ -41074,7 +41494,7 @@ snapshots: dependencies: postcss: 7.0.39 - superagent@10.3.0: + superagent@10.3.0(supports-color@7.2.0): dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 @@ -41118,11 +41538,11 @@ snapshots: transitivePeerDependencies: - supports-color - supertest@7.2.2: + supertest@7.2.2(supports-color@7.2.0): dependencies: cookie-signature: 1.2.2 methods: 1.1.2 - superagent: 10.3.0 + superagent: 10.3.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -41353,20 +41773,19 @@ snapshots: tiny-emitter@2.1.0: {} - tinyexec@0.3.2: {} + tinybench@2.9.0: {} - tinyexec@1.0.2: {} + tinyexec@0.3.2: {} - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + tinyexec@1.2.4: {} tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinyrainbow@3.1.0: {} + titleize@3.0.0: {} tmp@0.0.33: @@ -41517,12 +41936,12 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.29.7) - ts-jest@29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)))(typescript@5.7.3): + ts-jest@29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)))(typescript@5.7.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 30.4.2(@types/node@24.13.3)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) + jest: 30.4.2(@types/node@24.13.3)(supports-color@7.2.0)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.7.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -41658,8 +42077,8 @@ snapshots: rolldown: 1.0.0-beta.45 rolldown-plugin-dts: 0.17.8(rolldown@1.0.0-beta.45)(typescript@5.1.6) semver: 7.8.5 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig: 7.4.2 optionalDependencies: @@ -41878,7 +42297,7 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)): + typeorm@0.3.31(ioredis@5.11.1)(mysql2@3.23.1(@types/node@20.3.3))(redis@4.7.0)(supports-color@7.2.0)(ts-node@10.9.1(@types/node@20.3.3)(typescript@5.1.6)): dependencies: '@sqltools/formatter': 1.2.5 ansis: 4.3.1 @@ -41896,7 +42315,7 @@ snapshots: uuid: 11.1.1 yargs: 17.7.3 optionalDependencies: - ioredis: 5.11.1 + ioredis: 5.11.1(supports-color@7.2.0) mysql2: 3.23.1(@types/node@20.3.3) redis: 4.7.0 ts-node: 10.9.1(@types/node@20.3.3)(typescript@5.1.6) @@ -41904,10 +42323,10 @@ snapshots: - babel-plugin-macros - supports-color - typescript-eslint@8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3): + typescript-eslint@8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) - '@typescript-eslint/parser': 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3))(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3) + '@typescript-eslint/parser': 8.65.0(eslint@9.39.2(jiti@2.6.1))(supports-color@7.2.0)(typescript@5.7.3) '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.7.3) '@typescript-eslint/utils': 8.65.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) eslint: 9.39.2(jiti@2.6.1) @@ -42153,7 +42572,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.5 - unplugin-vue-components@0.17.21(@babel/parser@7.29.7)(@babel/traverse@7.29.7)(esbuild@0.25.2)(rollup@2.79.2)(supports-color@8.1.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@5.1.6))(webpack@5.108.4): + unplugin-vue-components@0.17.21(@babel/parser@7.29.7)(@babel/traverse@7.29.7)(esbuild@0.28.1)(rollup@2.79.2)(supports-color@8.1.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@5.1.6))(webpack@5.108.4): dependencies: '@antfu/utils': 0.4.0 '@rollup/pluginutils': 4.2.1 @@ -42164,7 +42583,7 @@ snapshots: magic-string: 0.25.9 minimatch: 3.1.2 resolve: 1.22.10 - unplugin: 0.3.3(esbuild@0.25.2)(rollup@2.79.2)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(webpack@5.108.4) + unplugin: 0.3.3(esbuild@0.28.1)(rollup@2.79.2)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(webpack@5.108.4) vue: 3.5.40(typescript@5.1.6) optionalDependencies: '@babel/parser': 7.29.7 @@ -42176,11 +42595,11 @@ snapshots: - vite - webpack - unplugin@0.3.3(esbuild@0.25.2)(rollup@2.79.2)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(webpack@5.108.4): + unplugin@0.3.3(esbuild@0.28.1)(rollup@2.79.2)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0))(webpack@5.108.4): dependencies: webpack-virtual-modules: 0.4.6 optionalDependencies: - esbuild: 0.25.2 + esbuild: 0.28.1 rollup: 2.79.2 vite: 6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0) webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.20)(webpack-cli@5.1.4) @@ -42535,13 +42954,31 @@ snapshots: sugarss: 2.0.0 terser: 5.39.0 + vite@6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): + dependencies: + esbuild: 0.25.2 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.20 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 14.18.63 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.7.0 + sugarss: 2.0.0 + terser: 5.49.0 + tsx: 4.23.1 + yaml: 2.9.0 + vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0): dependencies: esbuild: 0.25.2 fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 - postcss: 8.5.3 - rollup: 4.40.0 + postcss: 8.5.20 + rollup: 4.62.2 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.20.1 @@ -42553,6 +42990,63 @@ snapshots: tsx: 4.23.1 yaml: 2.9.0 + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@14.18.63)(vite@6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 6.4.3(@types/node@14.18.63)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 14.18.63 + transitivePeerDependencies: + - msw + + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.20.1)(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 6.4.3(@types/node@22.20.1)(jiti@2.6.1)(less@4.7.0)(sugarss@2.0.0)(terser@5.39.0)(tsx@4.23.1)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 22.20.1 + transitivePeerDependencies: + - msw + optional: true + vizion@2.2.1: dependencies: async: 2.6.4 @@ -43075,6 +43569,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 1.0.2 diff --git a/src/lib/__tests__/env-config.test.ts b/src/lib/__tests__/env-config.test.ts new file mode 100644 index 00000000..ec821601 --- /dev/null +++ b/src/lib/__tests__/env-config.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { objToEnv } from '../init/env-config'; + +describe('objToEnv', () => { + it('should convert string values with single quotes', () => { + const result = objToEnv({ NAME: 'hello' }); + expect(result).toBe("NAME = 'hello'"); + }); + + it('should convert number values without quotes', () => { + const result = objToEnv({ PORT: 3306 }); + expect(result).toBe('PORT = 3306'); + }); + + it('should convert boolean values without quotes', () => { + const result = objToEnv({ ENABLED: true }); + expect(result).toBe('ENABLED = true'); + }); + + it('should convert false values without quotes', () => { + const result = objToEnv({ DISABLED: false }); + expect(result).toBe('DISABLED = false'); + }); + + it('should join multiple entries with newlines', () => { + const result = objToEnv({ HOST: 'localhost', PORT: 3306, SYNC: false }); + expect(result).toBe("HOST = 'localhost'\nPORT = 3306\nSYNC = false"); + }); + + it('should handle empty object', () => { + const result = objToEnv({}); + expect(result).toBe(''); + }); +}); diff --git a/src/lib/__tests__/package-json.test.ts b/src/lib/__tests__/package-json.test.ts new file mode 100644 index 00000000..a9383464 --- /dev/null +++ b/src/lib/__tests__/package-json.test.ts @@ -0,0 +1,173 @@ +import * as path from 'path'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { BuildTool } from '../interfaces'; + +vi.mock('fs', () => ({ + existsSync: vi.fn(), + rmSync: vi.fn(), +})); + +vi.mock('@opentiny/cli-devkit', () => ({ + logs: () => ({ + error: vi.fn(), + success: vi.fn(), + info: vi.fn(), + }), +})); + +import { existsSync, rmSync } from 'fs'; +import { packageJsonProcess } from '../init/package-json'; + +const mockExistsSync = vi.mocked(existsSync); +const mockRmSync = vi.mocked(rmSync); + +describe('packageJsonProcess', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const basePackages = () => ({ + name: 'test', + description: 'test project', + scripts: { + build: 'webpack --config webpack.config.js', + start: 'webpack-dev-server', + dev: 'vite', + 'dev:wp': 'webpack-dev-server', + 'build:wp': 'webpack build', + 'dev:rp': 'rspack serve', + 'build:rp': 'rspack build', + 'dev:fr': 'farm', + 'build:fr': 'farm build', + }, + devDependencies: { + vite: '^5.0.0', + '@vitejs/plugin-vue': '^4.0.0', + 'vite-plugin-mock': '^2.0.0', + webpack: '^5.0.0', + 'webpack-cli': '^5.0.0', + 'webpack-dev-server': '^4.0.0', + '@rspack/cli': '^0.3.0', + '@rspack/core': '^0.3.0', + '@farmfe/core': '^1.0.0', + 'style-resources-loader': '^1.0.0', + 'vue-style-loader': '^4.0.0', + }, + dependencies: { + vue: '^3.0.0', + 'style-resources-loader': '^1.0.0', + 'vue-style-loader': '^4.0.0', + }, + }); + + it('should remove obsolete scripts', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + expect(packages.scripts['dev:wp']).toBeUndefined(); + expect(packages.scripts['build:wp']).toBeUndefined(); + expect(packages.scripts['dev:rp']).toBeUndefined(); + expect(packages.scripts['build:rp']).toBeUndefined(); + expect(packages.scripts['dev']).toBeUndefined(); + expect(packages.scripts['dev:fr']).toBeUndefined(); + expect(packages.scripts['build:fr']).toBeUndefined(); + }); + + it('should apply vite build tool scripts', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + expect(packages.scripts['build']).toBe('vite build --config ./config/vite.config.prod.ts'); + expect(packages.scripts['start']).toBe('vite --config ./config/vite.config.dev.ts --port 3031'); + }); + + it('should apply webpack build tool scripts', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Webpack, packages, '/fake/path'); + + expect(packages.scripts['build']).toBe('webpack --config webpack.config.js'); + expect(packages.scripts['start']).toBe('webpack-dev-server --progress --config webpack.config.js'); + }); + + it('should apply rspack build tool scripts', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Rspack, packages, '/fake/path'); + + expect(packages.scripts['build']).toBe('rspack build'); + expect(packages.scripts['start']).toBe('rspack serve'); + }); + + it('should apply farm build tool scripts', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Farm, packages, '/fake/path'); + + expect(packages.scripts['build']).toBe('farm build'); + expect(packages.scripts['start']).toBe('farm'); + }); + + it('should remove vite devDependencies when buildTool is webpack', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Webpack, packages, '/fake/path'); + + expect(packages.devDependencies['vite']).toBeUndefined(); + expect(packages.devDependencies['@vitejs/plugin-vue']).toBeUndefined(); + expect(packages.devDependencies['vite-plugin-mock']).toBeUndefined(); + }); + + it('should remove webpack devDependencies when buildTool is vite', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + expect(packages.devDependencies['webpack']).toBeUndefined(); + expect(packages.devDependencies['webpack-cli']).toBeUndefined(); + expect(packages.devDependencies['webpack-dev-server']).toBeUndefined(); + expect(packages.devDependencies['@rspack/cli']).toBeUndefined(); + expect(packages.devDependencies['@rspack/core']).toBeUndefined(); + expect(packages.devDependencies['@farmfe/core']).toBeUndefined(); + }); + + it('should remove style-resources-loader and vue-style-loader from dependencies when buildTool is vite', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + expect(packages.dependencies['style-resources-loader']).toBeUndefined(); + expect(packages.dependencies['vue-style-loader']).toBeUndefined(); + }); + + it('should return a function that removes build config files', () => { + mockExistsSync.mockReturnValue(true); + const packages = basePackages(); + const removeConfigFiles = packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + removeConfigFiles(); + + expect(mockRmSync).toHaveBeenCalled(); + const calls = mockRmSync.mock.calls.map((call) => call[0]); + expect(calls).toContain(path.join('/fake/path', 'webpack.config.js')); + expect(calls).toContain(path.join('/fake/path', 'rspack.config.js')); + expect(calls).toContain(path.join('/fake/path', 'farm.config.ts')); + }); + + it('should not remove build config files if they do not exist', () => { + mockExistsSync.mockReturnValue(false); + const packages = basePackages(); + const removeConfigFiles = packageJsonProcess(BuildTool.Vite, packages, '/fake/path'); + + removeConfigFiles(); + + expect(mockRmSync).not.toHaveBeenCalled(); + }); + + it('should handle regex pattern matching for devDependencies removal', () => { + const packages = basePackages(); + packageJsonProcess(BuildTool.Rspack, packages, '/fake/path'); + + expect(packages.devDependencies['vite']).toBeUndefined(); + expect(packages.devDependencies['@vitejs/plugin-vue']).toBeUndefined(); + expect(packages.devDependencies['vite-plugin-mock']).toBeUndefined(); + expect(packages.devDependencies['webpack']).toBeUndefined(); + expect(packages.devDependencies['webpack-cli']).toBeUndefined(); + expect(packages.devDependencies['webpack-dev-server']).toBeUndefined(); + expect(packages.devDependencies['@farmfe/core']).toBeUndefined(); + }); +}); diff --git a/src/lib/__tests__/project.test.ts b/src/lib/__tests__/project.test.ts new file mode 100644 index 00000000..9c5250ae --- /dev/null +++ b/src/lib/__tests__/project.test.ts @@ -0,0 +1,343 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { LowcodeEngine, ServerFrameworks, BuildTool, VueVersion } from '../interfaces'; + +vi.mock('fs-extra', () => ({ + copySync: vi.fn(), +})); + +vi.mock('cross-spawn', () => ({ + default: { + sync: vi.fn(), + }, +})); + +vi.mock('dotenv', () => ({ + parse: vi.fn(), +})); + +vi.mock('@opentiny/cli-devkit', () => ({ + cliConfig: { + getBinName: vi.fn(() => 'tiny'), + }, + logs: () => ({ + error: vi.fn(), + success: vi.fn(), + info: vi.fn(), + }), + fs: { + copyTpl: vi.fn(), + readFileSync: vi.fn(), + writeFileSync: vi.fn(), + }, +})); + +vi.mock('../utils', () => ({ + default: { + getTemplatePath: vi.fn((p) => `/template/${p}`), + getDistPath: vi.fn((p) => `/dist/${p}`), + generateNames: vi.fn(), + setModuleCache: vi.fn(), + }, +})); + +vi.mock('../init/server-springboot', () => ({ + createSpringBootServer: vi.fn(), +})); + +vi.mock('../init/server-nestjs', () => ({ + createNestJsServer: vi.fn(), +})); + +vi.mock('../init/package-json', () => ({ + packageJsonProcess: vi.fn(() => vi.fn()), +})); + +import { copySync } from 'fs-extra'; +import spawn from 'cross-spawn'; +import * as dotenv from 'dotenv'; +import { cliConfig, fs as cliFs } from '@opentiny/cli-devkit'; +import utils from '../utils'; +import { createSpringBootServer } from '../init/server-springboot'; +import { createNestJsServer } from '../init/server-nestjs'; +import { + createLowcodeDesignerSync, + createServerSync, + createProjectSync, + installDependencies, +} from '../init/project'; + +const mockCopySync = vi.mocked(copySync); +const mockSpawnSync = vi.mocked(spawn.sync); +const mockDotnetParse = vi.mocked(dotenv.parse); +const mockCliFsReadFile = vi.mocked(cliFs.readFileSync); +const mockCliFsWriteFile = vi.mocked(cliFs.writeFileSync); +const mockCreateSpringBoot = vi.mocked(createSpringBootServer); +const mockCreateNestJs = vi.mocked(createNestJsServer); + +describe('createLowcodeDesignerSync', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should not copy when lowcodeEngine is not Include', () => { + createLowcodeDesignerSync({ + name: 'my-project', + lowcodeEngine: LowcodeEngine.Skip, + } as any); + + expect(mockCopySync).not.toHaveBeenCalled(); + }); + + it('should copy lowcode template when lowcodeEngine is Include', () => { + createLowcodeDesignerSync({ + name: 'my-project', + lowcodeEngine: LowcodeEngine.Include, + } as any); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${LowcodeEngine.Include}`, + `/dist/my-project/${LowcodeEngine.Include}` + ); + }); +}); + +describe('createServerSync', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call createSpringBootServer when serverFramework is SpringBoot', () => { + const answers = { serverFramework: ServerFrameworks.SpringBoot } as any; + createServerSync(answers); + + expect(mockCreateSpringBoot).toHaveBeenCalledWith(answers); + expect(mockCreateNestJs).not.toHaveBeenCalled(); + }); + + it('should call createNestJsServer when serverFramework is NestJs', () => { + const answers = { serverFramework: ServerFrameworks.NestJs } as any; + createServerSync(answers); + + expect(mockCreateNestJs).toHaveBeenCalledWith(answers); + expect(mockCreateSpringBoot).not.toHaveBeenCalled(); + }); + + it('should not call any server creator for other frameworks', () => { + const answers = { serverFramework: ServerFrameworks.Skip } as any; + createServerSync(answers); + + expect(mockCreateSpringBoot).not.toHaveBeenCalled(); + expect(mockCreateNestJs).not.toHaveBeenCalled(); + }); +}); + +describe('createProjectSync', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockCliFsReadFile.mockReturnValue('VITE_MOCK_IGNORE=true\nVITE_LOWCODE_DESIGNER_ENABLED=false\n'); + mockDotnetParse.mockReturnValue({ + VITE_MOCK_IGNORE: 'true', + VITE_LOWCODE_DESIGNER_ENABLED: 'false', + }); + }); + + it('should copy Vue3 template to dist path', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: false, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.Skip, + } as any); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${VueVersion.Vue3}`, + `/dist/my-project`, + expect.objectContaining({ filter: expect.any(Function) }) + ); + }); + + it('should copy to web subdirectory when serverConfirm is true', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: true, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.NestJs, + } as any); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${VueVersion.Vue3}`, + `/dist/my-project/web`, + expect.objectContaining({ filter: expect.any(Function) }) + ); + }); + + it('should call createServerSync when serverConfirm is true', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: true, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.NestJs, + } as any); + + expect(mockCreateNestJs).toHaveBeenCalled(); + }); + + it('should not call createServerSync when serverConfirm is false', () => { + createProjectSync({ + name: 'my-project', + serverConfirm: false, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.Skip, + } as any); + + expect(mockCreateSpringBoot).not.toHaveBeenCalled(); + expect(mockCreateNestJs).not.toHaveBeenCalled(); + }); + + it('should call createLowcodeDesignerSync when lowcodeEngine is Include', () => { + createProjectSync({ + name: 'my-project', + serverConfirm: false, + lowcodeEngine: LowcodeEngine.Include, + serverFramework: ServerFrameworks.Skip, + } as any); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${LowcodeEngine.Include}`, + `/dist/my-project/${LowcodeEngine.Include}` + ); + }); + + it('should configure env file and set lowcode enabled to true', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: true, + lowcodeEngine: LowcodeEngine.Include, + serverFramework: ServerFrameworks.NestJs, + } as any); + + expect(mockCliFsWriteFile).toHaveBeenCalled(); + const writeCall = mockCliFsWriteFile.mock.calls.find( + (call) => typeof call[1] === 'string' && call[1].includes('VITE_LOWCODE_DESIGNER_ENABLED=true') + ); + expect(writeCall).toBeDefined(); + }); + + it('should configure env file and keep lowcode disabled when Skip', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: true, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.NestJs, + } as any); + + expect(mockCliFsWriteFile).toHaveBeenCalled(); + const writeCall = mockCliFsWriteFile.mock.calls.find( + (call) => typeof call[1] === 'string' && call[1].includes('VITE_LOWCODE_DESIGNER_ENABLED=false') + ); + expect(writeCall).toBeDefined(); + }); + + it('should remove VITE_MOCK_IGNORE when serverConfirm is false', () => { + createProjectSync({ + name: 'my-project', + serverConfirm: false, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.Skip, + } as any); + + expect(mockDotnetParse).toHaveBeenCalled(); + expect(mockCliFsWriteFile).toHaveBeenCalled(); + const writeCall = mockCliFsWriteFile.mock.calls.find( + (call) => typeof call[1] === 'string' && !call[1].includes('VITE_MOCK_IGNORE') + ); + expect(writeCall).toBeDefined(); + }); + + it('should not configure packageJson when serverFramework is not NestJs', async () => { + await createProjectSync({ + name: 'my-project', + serverConfirm: true, + lowcodeEngine: LowcodeEngine.Skip, + serverFramework: ServerFrameworks.SpringBoot, + buildTool: BuildTool.Vite, + } as any); + + expect(mockCliFsReadFile).not.toHaveBeenCalledWith( + expect.stringContaining('package.json'), + expect.anything() + ); + }); +}); + +describe('installDependencies', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockSpawnSync.mockReturnValue({ status: 0 } as any); + }); + + it('should run npm install for client when serverConfirm is false', () => { + installDependencies({ + name: 'my-project', + serverConfirm: false, + serverFramework: ServerFrameworks.Skip, + lowcodeEngine: LowcodeEngine.Skip, + } as any); + + expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['install'], expect.objectContaining({ + cwd: 'my-project/', + })); + }); + + it('should run npm install for web when serverConfirm is true', () => { + installDependencies({ + name: 'my-project', + serverConfirm: true, + serverFramework: ServerFrameworks.NestJs, + lowcodeEngine: LowcodeEngine.Skip, + } as any); + + expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['install'], expect.objectContaining({ + cwd: 'my-project/web', + })); + }); + + it('should run npm install for server when serverFramework is EggJs and serverConfirm is true', () => { + installDependencies({ + name: 'my-project', + serverConfirm: true, + serverFramework: ServerFrameworks.EggJs, + lowcodeEngine: LowcodeEngine.Skip, + } as any); + + expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['install'], expect.objectContaining({ + cwd: 'my-project/eggJs/', + })); + }); + + it('should run npm install for lowcode designer when lowcodeEngine is Include', () => { + installDependencies({ + name: 'my-project', + serverConfirm: false, + serverFramework: ServerFrameworks.Skip, + lowcodeEngine: LowcodeEngine.Include, + } as any); + + expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['install'], expect.objectContaining({ + cwd: 'my-project/lowcode-designer/', + })); + }); + + it('should throw error when npm install fails', () => { + mockSpawnSync.mockReturnValue({ status: 1, error: new Error('npm install failed') } as any); + + expect(() => { + installDependencies({ + name: 'my-project', + serverConfirm: false, + serverFramework: ServerFrameworks.Skip, + lowcodeEngine: LowcodeEngine.Skip, + } as any); + }).toThrow('npm install failed'); + }); +}); diff --git a/src/lib/__tests__/questions.test.ts b/src/lib/__tests__/questions.test.ts new file mode 100644 index 00000000..3070c710 --- /dev/null +++ b/src/lib/__tests__/questions.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect, vi } from 'vitest'; +import inquirer from 'inquirer'; +import { BuildTool, LowcodeEngine, ServerFrameworks } from '../interfaces'; + +vi.mock('inquirer', () => ({ + default: { + prompt: vi.fn(), + }, +})); + +const mockPrompt = vi.mocked(inquirer.prompt); + +describe('getProjectInfo', () => { + it('should return project info from inquirer prompt', async () => { + const mockAnswers = { + name: 'my-project', + description: 'test project', + framework: 'tinyvue', + serverFramework: ServerFrameworks.NestJs, + lowcodeEngine: LowcodeEngine.Skip, + buildTool: BuildTool.Vite, + serverConfirm: true, + redisHost: 'localhost', + redisPort: 6379, + dialect: 'mysql', + host: 'localhost', + port: 3306, + database: 'testdb', + username: 'root', + password: 'secret', + }; + mockPrompt.mockResolvedValue(mockAnswers); + + const { getProjectInfo } = await import('../init/questions'); + const result = await getProjectInfo(); + + expect(result).toEqual(mockAnswers); + expect(mockPrompt).toHaveBeenCalledOnce(); + }); + + it('should pass question collection to inquirer prompt', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + expect(questions.length).toBeGreaterThan(0); + + const nameQuestion = questions.find((q: any) => q.name === 'name'); + expect(nameQuestion).toBeDefined(); + expect(nameQuestion.default).toBe('tiny-pro'); + expect(nameQuestion.type).toBe('input'); + }); + + it('should have serverFramework question with correct choices', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const serverQ = questions.find((q: any) => q.name === 'serverFramework'); + expect(serverQ).toBeDefined(); + expect(serverQ.choices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ value: ServerFrameworks.SpringBoot }), + expect.objectContaining({ value: ServerFrameworks.NestJs }), + expect.objectContaining({ value: ServerFrameworks.Skip }), + ]) + ); + }); + + it('should have buildTool question with correct choices', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const buildQ = questions.find((q: any) => q.name === 'buildTool'); + expect(buildQ).toBeDefined(); + expect(buildQ.choices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ value: BuildTool.Vite }), + expect.objectContaining({ value: BuildTool.Webpack }), + expect.objectContaining({ value: BuildTool.Rspack }), + expect.objectContaining({ value: BuildTool.Farm }), + ]) + ); + }); + + it('should have lowcodeEngine question with correct choices', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const lowcodeQ = questions.find((q: any) => q.name === 'lowcodeEngine'); + expect(lowcodeQ).toBeDefined(); + expect(lowcodeQ.choices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ value: LowcodeEngine.Include }), + expect.objectContaining({ value: LowcodeEngine.Skip }), + ]) + ); + }); + + it('should validate name is not empty', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const nameQ = questions.find((q: any) => q.name === 'name'); + expect(nameQ.validate('')).toBe(false); + expect(nameQ.validate('my-project')).toBe(true); + }); + + it('should validate database name is not empty', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const dbQ = questions.find((q: any) => q.name === 'database'); + expect(dbQ.validate('')).toBe(false); + expect(dbQ.validate('mydb')).toBe(true); + }); + + it('should conditionally show serverFramework when framework is tinyvue', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const serverQ = questions.find((q: any) => q.name === 'serverFramework'); + expect(serverQ.when({ framework: 'tinyvue' })).toBe(true); + expect(serverQ.when({ framework: 'react' })).toBe(false); + }); + + it('should conditionally show serverConfirm when framework is tinyvue and serverFramework is not Skip', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const confirmQ = questions.find((q: any) => q.name === 'serverConfirm'); + expect(confirmQ.when({ framework: 'tinyvue', serverFramework: ServerFrameworks.NestJs })).toBe(true); + expect(confirmQ.when({ framework: 'tinyvue', serverFramework: ServerFrameworks.Skip })).toBe(false); + expect(confirmQ.when({ framework: 'react', serverFramework: ServerFrameworks.NestJs })).toBe(false); + }); + + it('should conditionally show redis questions when serverConfirm is true', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const redisHostQ = questions.find((q: any) => q.name === 'redisHost'); + const redisPortQ = questions.find((q: any) => q.name === 'redisPort'); + expect(redisHostQ.when({ serverConfirm: true })).toBe(true); + expect(redisHostQ.when({ serverConfirm: false })).toBe(false); + expect(redisPortQ.when({ serverConfirm: true })).toBe(true); + expect(redisPortQ.when({ serverConfirm: false })).toBe(false); + }); + + it('should conditionally show database questions based on dialect and host', async () => { + mockPrompt.mockResolvedValue({ name: 'test' }); + + const { getProjectInfo } = await import('../init/questions'); + await getProjectInfo(); + + const questions = mockPrompt.mock.calls[0][0]; + const dialectQ = questions.find((q: any) => q.name === 'dialect'); + const hostQ = questions.find((q: any) => q.name === 'host'); + const portQ = questions.find((q: any) => q.name === 'port'); + + expect(dialectQ.when({ serverConfirm: true })).toBeTruthy(); + expect(dialectQ.when({ serverConfirm: false })).toBe(false); + expect(hostQ.when({ dialect: 'mysql' })).toBeTruthy(); + expect(hostQ.when({ dialect: '' })).toBeFalsy(); + expect(portQ.when({ host: 'localhost' })).toBeTruthy(); + expect(portQ.when({ host: undefined })).toBeFalsy(); + }); +}); diff --git a/src/lib/__tests__/server-nestjs-next.spec.ts b/src/lib/__tests__/server-nestjs-next.spec.ts new file mode 100644 index 00000000..5034cce5 --- /dev/null +++ b/src/lib/__tests__/server-nestjs-next.spec.ts @@ -0,0 +1,407 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ServerFrameworks, BuildTool } from '../interfaces'; + +vi.mock('inquirer', () => ({ + default: { + prompt: vi.fn(), + }, + prompt: vi.fn(), +})); + +vi.mock('fs-extra', () => ({ + copySync: vi.fn(), +})); + +vi.mock('fs', () => ({ + writeFileSync: vi.fn(), +})); + +vi.mock('../utils', () => ({ + default: { + getTemplatePath: vi.fn((p) => `/template/${p}`), + getDistPath: vi.fn((p) => `/dist/${p}`), + }, +})); + +vi.mock('../init/env-config', () => ({ + objToEnv: vi.fn(() => 'MOCKED_ENV_CONTENT'), +})); + +import inquirer from 'inquirer'; +import { copySync } from 'fs-extra'; +import { writeFileSync } from 'fs'; +import { + getFeatureConfigure, + getAuthConfigure, + createNextNestjsServer, +} from '../init/server-nestjs-next'; +import utils from '../utils'; + +const mockPrompt = vi.mocked(inquirer.prompt); +const mockCopySync = vi.mocked(copySync); +const mockWriteFileSync = vi.mocked(writeFileSync); + +describe('getFeatureConfigure', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call inquirer.prompt with confirm question for preview', async () => { + mockPrompt.mockResolvedValue({ preview: true }); + + const result = await getFeatureConfigure(); + + expect(mockPrompt).toHaveBeenCalledTimes(1); + const question = mockPrompt.mock.calls[0][0] as any[]; + expect(question).toHaveLength(1); + expect(question[0].type).toBe('confirm'); + expect(question[0].name).toBe('preview'); + expect(question[0].default).toBe(false); + expect(result).toEqual({ preview: true }); + }); + + it('should return preview false by default', async () => { + mockPrompt.mockResolvedValue({ preview: false }); + + const result = await getFeatureConfigure(); + + expect(result.preview).toBe(false); + }); +}); + +describe('getAuthConfigure', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call inquirer.prompt with auth questions', async () => { + mockPrompt.mockResolvedValue({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 'my-secret' }, + }); + + const result = await getAuthConfigure(); + + expect(mockPrompt).toHaveBeenCalledTimes(1); + const questions = mockPrompt.mock.calls[0][0] as any[]; + expect(questions).toHaveLength(8); + expect(questions[0].name).toBe('session_limit'); + expect(questions[1].name).toBe('accessTokenTTL'); + expect(questions[2].name).toBe('refreshTokenTTL'); + expect(questions[3].name).toBe('apiTokenTTL'); + expect(questions[4].name).toBe('jwt.mode'); + expect(questions[5].name).toBe('jwt.secret'); + expect(questions[6].name).toBe('jwt.publicKeyPath'); + expect(questions[7].name).toBe('jwt.privateKeyPath'); + }); + + it('should return auth config with secret mode', async () => { + const authResult = { + session_limit: 2, + accessTokenTTL: 600, + refreshTokenTTL: 172800, + apiTokenTTL: 1209600, + jwt: { mode: 'secret', secret: 'test-secret' }, + }; + mockPrompt.mockResolvedValue(authResult); + + const result = await getAuthConfigure(); + + expect(result.session_limit).toBe(2); + expect(result.accessTokenTTL).toBe(600); + expect(result.refreshTokenTTL).toBe(172800); + expect(result.apiTokenTTL).toBe(1209600); + expect(result.jwt.mode).toBe('secret'); + expect(result.jwt.secret).toBe('test-secret'); + }); + + it('should return auth config with local-key mode', async () => { + const authResult = { + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { + mode: 'local-key', + publicKeyPath: '/path/to/public.pem', + privateKeyPath: '/path/to/private.pem', + }, + }; + mockPrompt.mockResolvedValue(authResult); + + const result = await getAuthConfigure(); + + expect(result.jwt.mode).toBe('local-key'); + expect(result.jwt.publicKeyPath).toBe('/path/to/public.pem'); + expect(result.jwt.privateKeyPath).toBe('/path/to/private.pem'); + }); + + it('should have correct defaults for auth questions', async () => { + mockPrompt.mockResolvedValue({}); + + await getAuthConfigure(); + + const questions = mockPrompt.mock.calls[0][0] as any[]; + expect(questions[0].default).toBe(1); + expect(questions[1].default).toBe(300); + expect(questions[2].default).toBe(86400); + expect(questions[3].default).toBe(604800); + expect(questions[4].default).toBe('secret'); + }); + + it('jwt.secret question should only show when mode is secret', async () => { + mockPrompt.mockResolvedValue({}); + + await getAuthConfigure(); + + const questions = mockPrompt.mock.calls[0][0] as any[]; + const secretQuestion = questions[5]; + expect(secretQuestion.name).toBe('jwt.secret'); + expect(secretQuestion.when({ jwt: { mode: 'secret' } })).toBe(true); + expect(secretQuestion.when({ jwt: { mode: 'local-key' } })).toBe(false); + }); + + it('jwt.publicKeyPath and jwt.privateKeyPath questions should only show when mode is local-key', async () => { + mockPrompt.mockResolvedValue({}); + + await getAuthConfigure(); + + const questions = mockPrompt.mock.calls[0][0] as any[]; + const pubKeyQuestion = questions[6]; + const privKeyQuestion = questions[7]; + + expect(pubKeyQuestion.name).toBe('jwt.publicKeyPath'); + expect(pubKeyQuestion.when({ jwt: { mode: 'local-key' } })).toBe(true); + expect(pubKeyQuestion.when({ jwt: { mode: 'secret' } })).toBe(false); + + expect(privKeyQuestion.name).toBe('jwt.privateKeyPath'); + expect(privKeyQuestion.when({ jwt: { mode: 'local-key' } })).toBe(true); + expect(privKeyQuestion.when({ jwt: { mode: 'secret' } })).toBe(false); + }); +}); + +describe('createNextNestjsServer', () => { + const baseAnswers = { + name: 'my-project', + description: 'test', + framework: 'tinyvue', + serverFramework: ServerFrameworks.NestJsNext, + lowcodeEngine: '' as const, + serverConfirm: true, + dialect: 'mysql', + host: 'db-host', + port: 3307, + database: 'mydb', + username: 'admin', + password: 'secret', + redisHost: 'redis-host', + redisPort: 6380, + buildTool: BuildTool.Vite, + vueVersion: 'tinyvue' as const, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should copy template to dist path excluding node_modules', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 'test-secret' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + expect(mockCopySync).toHaveBeenCalledWith( + '/template/nest-next', + '/dist/my-project/nest', + expect.objectContaining({ + filter: expect.any(Function), + }) + ); + + const filterFn = mockCopySync.mock.calls[0][2].filter as Function; + expect(filterFn('some/node_modules/file')).toBe(false); + expect(filterFn('some/src/file')).toBe(true); + }); + + it('should write config.json with correct database and redis config', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 'test-secret' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + expect(mockWriteFileSync).toHaveBeenCalledWith( + '/dist/my-project/nest/configs/config.json', + expect.any(String) + ); + + const writtenContent = JSON.parse( + mockWriteFileSync.mock.calls[0][1] as string + ); + expect(writtenContent.database).toEqual({ + host: 'db-host', + port: 3307, + user: 'admin', + password: 'secret', + dbName: 'mydb', + }); + expect(writtenContent.redis).toEqual({ + host: 'redis-host', + port: 6380, + user: 'root', + password: 'root', + db: 0, + }); + }); + + it('should write config.json with feature config', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 'test-secret' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: true }); + + await createNextNestjsServer(baseAnswers); + + const writtenContent = JSON.parse( + mockWriteFileSync.mock.calls[0][1] as string + ); + expect(writtenContent.feature.preview).toBe(true); + }); + + it('should write config.json with auth config in secret mode', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 2, + accessTokenTTL: 600, + refreshTokenTTL: 172800, + apiTokenTTL: 1209600, + jwt: { mode: 'secret', secret: 'my-jwt-secret' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + const writtenContent = JSON.parse( + mockWriteFileSync.mock.calls[0][1] as string + ); + expect(writtenContent.auth.session_limit).toBe(2); + expect(writtenContent.auth.accessTokenTTL).toBe(600); + expect(writtenContent.auth.refreshTokenTTL).toBe(172800); + expect(writtenContent.auth.apiTokenTTL).toBe(1209600); + expect(writtenContent.auth.jwt.mode).toBe('secret'); + expect(writtenContent.auth.jwt.secret).toBe('my-jwt-secret'); + }); + + it('should write config.json with auth config in local-key mode', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { + mode: 'local-key', + publicKeyPath: '/keys/public.pem', + privateKeyPath: '/keys/private.pem', + }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + const writtenContent = JSON.parse( + mockWriteFileSync.mock.calls[0][1] as string + ); + expect(writtenContent.auth.jwt.mode).toBe('local-key'); + expect(writtenContent.auth.jwt.publicKeyPath).toBe('/keys/public.pem'); + expect(writtenContent.auth.jwt.privateKeyPath).toBe('/keys/private.pem'); + }); + + it('should use utils.getTemplatePath and utils.getDistPath with correct arguments', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 's' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + expect(utils.getTemplatePath).toHaveBeenCalledWith('nest-next'); + expect(utils.getDistPath).toHaveBeenCalledWith('my-project/nest'); + expect(utils.getDistPath).toHaveBeenCalledWith( + 'my-project/nest/configs/config.json' + ); + }); + + it('should handle undefined optional fields gracefully', async () => { + const minimalAnswers = { + ...baseAnswers, + host: undefined, + port: undefined, + username: undefined, + password: undefined, + redisHost: undefined, + redisPort: undefined, + database: undefined, + }; + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 's' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(minimalAnswers); + + const writtenContent = JSON.parse( + mockWriteFileSync.mock.calls[0][1] as string + ); + expect(writtenContent.database.host).toBeUndefined(); + expect(writtenContent.database.port).toBeUndefined(); + expect(writtenContent.database.user).toBeUndefined(); + expect(writtenContent.database.password).toBeUndefined(); + expect(writtenContent.database.dbName).toBeUndefined(); + expect(writtenContent.redis.host).toBeUndefined(); + expect(writtenContent.redis.port).toBeUndefined(); + }); + + it('should write formatted JSON (2-space indent)', async () => { + mockPrompt.mockResolvedValueOnce({ + session_limit: 1, + accessTokenTTL: 300, + refreshTokenTTL: 86400, + apiTokenTTL: 604800, + jwt: { mode: 'secret', secret: 's' }, + }); + mockPrompt.mockResolvedValueOnce({ preview: false }); + + await createNextNestjsServer(baseAnswers); + + const writtenContent = mockWriteFileSync.mock.calls[0][1] as string; + expect(writtenContent).toContain(' '); + expect(writtenContent).toContain('\n'); + const parsed = JSON.parse(writtenContent); + expect(parsed).toBeDefined(); + }); +}); diff --git a/src/lib/__tests__/server-nestjs.test.ts b/src/lib/__tests__/server-nestjs.test.ts new file mode 100644 index 00000000..a3373cb5 --- /dev/null +++ b/src/lib/__tests__/server-nestjs.test.ts @@ -0,0 +1,175 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ServerFrameworks, BuildTool } from '../interfaces'; + +vi.mock('fs-extra', () => ({ + copySync: vi.fn(), +})); + +vi.mock('fs', () => ({ + readFileSync: vi.fn(), + writeFileSync: vi.fn(), +})); + +vi.mock('../utils', () => ({ + default: { + getTemplatePath: vi.fn((p) => `/template/${p}`), + getDistPath: vi.fn((p) => `/dist/${p}`), + }, +})); + +vi.mock('../init/env-config', () => ({ + objToEnv: vi.fn(() => 'MOCKED_ENV_CONTENT'), +})); + +import { copySync } from 'fs-extra'; +import { readFileSync, writeFileSync } from 'fs'; +import { createNestJsServer } from '../init/server-nestjs'; +import { objToEnv } from '../init/env-config'; +import utils from '../utils'; + +const mockCopySync = vi.mocked(copySync); +const mockReadFileSync = vi.mocked(readFileSync); +const mockWriteFileSync = vi.mocked(writeFileSync); +const mockObjToEnv = vi.mocked(objToEnv); + +describe('createNestJsServer', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const baseAnswers = { + name: 'my-project', + description: 'test', + framework: 'tinyvue', + serverFramework: ServerFrameworks.NestJs, + lowcodeEngine: '' as const, + serverConfirm: true, + dialect: 'mysql', + host: 'db-host', + port: 3307, + database: 'mydb', + username: 'admin', + password: 'secret', + redisHost: 'redis-host', + redisPort: 6380, + buildTool: BuildTool.Vite, + vueVersion: 'tinyvue' as const, + }; + + it('should copy template to dist path', () => { + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(baseAnswers); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${ServerFrameworks.NestJs}`, + `/dist/my-project/${ServerFrameworks.NestJs}` + ); + }); + + it('should write .env file using objToEnv', () => { + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(baseAnswers); + + expect(mockObjToEnv).toHaveBeenCalled(); + expect(mockWriteFileSync).toHaveBeenCalledWith( + expect.stringContaining('.env'), + 'MOCKED_ENV_CONTENT' + ); + }); + + it('should write docker-compose.yml with updated environment', () => { + const yamlContent = `services:\n mysql:\n environment:\n key: value\n`; + mockReadFileSync.mockReturnValue(Buffer.from(yamlContent)); + + createNestJsServer(baseAnswers); + + const writeCalls = mockWriteFileSync.mock.calls.filter((call) => + (call[0] as string).includes('docker-compose.yml') + ); + expect(writeCalls.length).toBe(1); + }); + + it('should use default values when optional fields are missing', () => { + const minimalAnswers = { + ...baseAnswers, + host: undefined, + port: undefined, + username: undefined, + password: undefined, + redisHost: undefined, + redisPort: undefined, + }; + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(minimalAnswers); + + const envCall = mockObjToEnv.mock.calls[0][0] as Record; + expect(envCall.DATABASE_HOST).toBe('localhost'); + expect(envCall.DATABASE_PORT).toBe(3306); + expect(envCall.DATABASE_USERNAME).toBe('root'); + expect(envCall.DATABASE_PASSWORD).toBe('root'); + expect(envCall.REDIS_HOST).toBe('localhost'); + expect(envCall.REDIS_PORT).toBe(6379); + }); + + it('should use provided values for database and redis config', () => { + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(baseAnswers); + + const envCall = mockObjToEnv.mock.calls[0][0] as Record; + expect(envCall.DATABASE_HOST).toBe('db-host'); + expect(envCall.DATABASE_PORT).toBe(3307); + expect(envCall.DATABASE_USERNAME).toBe('admin'); + expect(envCall.DATABASE_PASSWORD).toBe('secret'); + expect(envCall.DATABASE_NAME).toBe('mydb'); + expect(envCall.REDIS_HOST).toBe('redis-host'); + expect(envCall.REDIS_PORT).toBe(6380); + }); + + it('should set DATABASE fields to false when dialect is empty', () => { + const noDialectAnswers = { + ...baseAnswers, + dialect: '', + }; + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(noDialectAnswers); + + const envCall = mockObjToEnv.mock.calls[0][0] as Record; + expect(envCall.DATABASE_HOST).toBeFalsy(); + expect(envCall.DATABASE_PORT).toBeFalsy(); + expect(envCall.DATABASE_USERNAME).toBeFalsy(); + expect(envCall.DATABASE_PASSWORD).toBeFalsy(); + expect(envCall.DATABASE_NAME).toBeFalsy(); + }); + + it('should set fixed config values', () => { + mockReadFileSync.mockReturnValue( + Buffer.from('services:\n mysql:\n environment:\n {}') + ); + + createNestJsServer(baseAnswers); + + const envCall = mockObjToEnv.mock.calls[0][0] as Record; + expect(envCall.DATABASE_SYNCHRONIZE).toBe(false); + expect(envCall.DATABASE_AUTOLOADENTITIES).toBe(true); + expect(envCall.AUTH_SECRET).toBe('secret'); + expect(envCall.REDIS_SECONDS).toBe(7200); + expect(envCall.EXPIRES_IN).toBe('2h'); + expect(envCall.PAGINATION_PAGE).toBe(1); + expect(envCall.PAGINATION_LIMIT).toBe(10); + }); +}); diff --git a/src/lib/__tests__/server-springboot.test.ts b/src/lib/__tests__/server-springboot.test.ts new file mode 100644 index 00000000..6303f7bb --- /dev/null +++ b/src/lib/__tests__/server-springboot.test.ts @@ -0,0 +1,223 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ServerFrameworks, BuildTool } from '../interfaces'; + +vi.mock('fs-extra', () => ({ + copySync: vi.fn(), +})); + +vi.mock('fs', () => ({ + existsSync: vi.fn(), +})); + +vi.mock('properties-parser', () => ({ + createEditor: vi.fn(), +})); + +vi.mock('@opentiny/cli-devkit', () => ({ + logs: () => ({ + error: vi.fn(), + success: vi.fn(), + info: vi.fn(), + }), +})); + +vi.mock('../utils', () => ({ + default: { + getTemplatePath: vi.fn((p) => `/template/${p}`), + getDistPath: vi.fn((p) => `/dist/${p}`), + }, +})); + +import { copySync } from 'fs-extra'; +import { existsSync } from 'fs'; +import { createEditor } from 'properties-parser'; +import { createSpringBootServer } from '../init/server-springboot'; +import utils from '../utils'; + +const mockCopySync = vi.mocked(copySync); +const mockExistsSync = vi.mocked(existsSync); +const mockCreateEditor = vi.mocked(createEditor); + +describe('createSpringBootServer', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const baseAnswers = { + name: 'my-project', + description: 'test', + framework: 'tinyvue', + serverFramework: ServerFrameworks.SpringBoot, + lowcodeEngine: '' as const, + serverConfirm: true, + dialect: 'mysql', + host: 'db-host', + port: 3307, + database: 'mydb', + username: 'admin', + password: 'secret', + redisHost: 'redis-host', + redisPort: 6380, + buildTool: BuildTool.Vite, + vueVersion: 'tinyvue' as const, + }; + + it('should copy template to dist path', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockCopySync).toHaveBeenCalledWith( + `/template/${ServerFrameworks.SpringBoot}`, + `/dist/my-project/${ServerFrameworks.SpringBoot}` + ); + }); + + it('should not proceed if properties file does not exist', () => { + mockExistsSync.mockReturnValue(false); + + createSpringBootServer(baseAnswers); + + expect(mockCreateEditor).not.toHaveBeenCalled(); + }); + + it('should create editor and save when properties file exists', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockCreateEditor).toHaveBeenCalledOnce(); + expect(mockEditor.save).toHaveBeenCalledOnce(); + }); + + it('should set server.port in properties', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('server.port', '3000'); + }); + + it('should set datasource properties with provided values', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('spring.datasource.username', 'admin'); + expect(mockEditor.set).toHaveBeenCalledWith('spring.datasource.password', 'secret'); + expect(mockEditor.set).toHaveBeenCalledWith( + 'spring.datasource.url', + expect.stringContaining('db-host') + ); + expect(mockEditor.set).toHaveBeenCalledWith( + 'spring.datasource.url', + expect.stringContaining('3307') + ); + expect(mockEditor.set).toHaveBeenCalledWith( + 'spring.datasource.url', + expect.stringContaining('mydb') + ); + }); + + it('should use default values when optional fields are missing', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + const minimalAnswers = { + ...baseAnswers, + host: undefined, + port: undefined, + username: undefined, + password: undefined, + database: undefined, + redisHost: undefined, + redisPort: undefined, + }; + + createSpringBootServer(minimalAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('spring.datasource.username', 'root'); + expect(mockEditor.set).toHaveBeenCalledWith('spring.datasource.password', 'root'); + expect(mockEditor.set).toHaveBeenCalledWith( + 'spring.datasource.url', + expect.stringContaining('localhost') + ); + expect(mockEditor.set).toHaveBeenCalledWith( + 'spring.datasource.url', + expect.stringContaining('3306') + ); + expect(mockEditor.set).toHaveBeenCalledWith('spring.data.redis.host', 'localhost'); + expect(mockEditor.set).toHaveBeenCalledWith('spring.data.redis.port', '6379'); + }); + + it('should set redis properties', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('spring.data.redis.host', 'redis-host'); + expect(mockEditor.set).toHaveBeenCalledWith('spring.data.redis.port', '6380'); + }); + + it('should set mybatis-plus properties', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('mybatis-plus.mapper-locations', 'classpath:mappers/*.xml'); + expect(mockEditor.set).toHaveBeenCalledWith('mybatis-plus.type-aliases-package', 'com.TinyPro.entity.po'); + expect(mockEditor.set).toHaveBeenCalledWith('mybatis-plus.configuration.map-underscore-to-camel-case', 'true'); + }); + + it('should set JPA properties', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('spring.jpa.hibernate.ddl-auto', 'update'); + expect(mockEditor.set).toHaveBeenCalledWith('spring.jpa.database-platform', 'org.hibernate.dialect.MySQL8Dialect'); + }); + + it('should set JWT secret', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + expect(mockEditor.set).toHaveBeenCalledWith('jwt.secret', '0Zi4SA=='); + }); + + it('should build JDBC URL with correct format', () => { + mockExistsSync.mockReturnValue(true); + const mockEditor = { set: vi.fn(), save: vi.fn() }; + mockCreateEditor.mockReturnValue(mockEditor as any); + + createSpringBootServer(baseAnswers); + + const urlCall = mockEditor.set.mock.calls.find( + (call: any[]) => call[0] === 'spring.datasource.url' + ); + expect(urlCall).toBeDefined(); + const url = urlCall![1]; + expect(url).toMatch(/^jdbc:mysql:\/\//); + expect(url).toContain('allowMultiQueries=true'); + expect(url).toContain('useUnicode=true'); + expect(url).toContain('useSSL=false'); + }); +}); diff --git a/src/lib/init.ts b/src/lib/init.ts index fa40633e..a4bdf761 100644 --- a/src/lib/init.ts +++ b/src/lib/init.ts @@ -1,660 +1,20 @@ -import { parse, stringify } from 'yaml'; -import * as path from 'path'; -import * as dotenv from 'dotenv'; -import { copySync } from 'fs-extra'; -import chalk from 'chalk'; -import spawn from 'cross-spawn'; -import inquirer, { QuestionCollection } from 'inquirer'; -import { cliConfig, logs, fs } from '@opentiny/cli-devkit'; -import { createEditor } from 'properties-parser'; -import { - buildCommand, - buildConfigs, - BuildTool, - devCommand, - devDependencies, - LowcodeEngine, - ProjectInfo, - removedCommand, - removeDependencies, - ServerFrameworks, - VueVersion, -} from './interfaces'; -import utils from './utils'; -import { existsSync, readFileSync, rmSync, writeFileSync } from 'fs'; +import { logs } from '@opentiny/cli-devkit'; +import { getProjectInfo } from './init/questions'; +import { createProjectSync } from './init/project'; -const log = logs('tiny-toolkit-pro'); -const VUE_TEMPLATE_PATH = 'tinyvue'; -const DEFAULT_PROJECT_NAME = 'tiny-pro'; - -/** - * 询问创建项目的描述,使用的技术栈 - * - * @returns object { description: 项目描述,framework: 框架, name: 项目名称 ,serverFramework:使用技术栈, dialect:数据库,DB_host:数据库地址,DB_port:数据库端口,database:数据库名称,username:数据库用户名,password:数据库密码,} - */ -const getProjectInfo = (): Promise => { - const question: QuestionCollection = [ - { - type: 'input', - name: 'name', - message: '请输入项目名称:', - default: DEFAULT_PROJECT_NAME, - // 必填校验 - validate: (input: string) => Boolean(input), - }, - { - type: 'input', - name: 'description', - message: '请输入项目描述:', - default: '基于TinyPro套件创建的中后台系统', - }, - { - type: 'list', - name: 'framework', - message: '请选择您希望使用的客户端技术栈:', - choices: [{ name: 'vue', value: VUE_TEMPLATE_PATH }], - default: VUE_TEMPLATE_PATH, - prefix: '*', - }, - { - type: 'list', - name: 'serverFramework', - message: '请选择您希望使用的服务端技术栈:', - choices: [ - // { name: 'Egg.js', value: ServerFrameworks.EggJs }, - { name: 'SpringBoot', value: ServerFrameworks.SpringBoot }, - { name: 'Nest.js', value: ServerFrameworks.NestJs }, - { name: '暂不配置', value: ServerFrameworks.Skip }, - ], - default: ServerFrameworks.NestJs, - prefix: '*', - when: (answers) => answers.framework === VUE_TEMPLATE_PATH, - }, - { - type: 'list', - name: 'lowcodeEngine', - message: '是否集成低代码设计器:', - choices: [ - { name: '是,集成低代码设计器', value: LowcodeEngine.Include }, - { name: '否,暂不集成', value: LowcodeEngine.Skip }, - ], - default: LowcodeEngine.Skip, - prefix: '*', - when: (answers) => answers.framework === VUE_TEMPLATE_PATH, - }, - { - type: 'list', - name: 'buildTool', - message: '请选择你想要的构建工具: ', - choices: [ - { name: 'Vite', value: BuildTool.Vite }, - { name: 'Webpack', value: BuildTool.Webpack }, - { name: 'Rspack', value: BuildTool.Rspack }, - { name: 'Farm', value: BuildTool.Farm }, - ], - default: BuildTool.Vite, - prefix: '*', - }, - { - type: 'list', - name: 'serverConfirm', - message: - '请确保已安装数据库服务(参考文档 https://www.opentiny.design/tiny-cli/docs/toolkits/pro#database):', - choices: [ - { name: '已完成数据库服务安装,开始配置', value: true }, - { name: '暂不配置服务端', value: false }, - ], - prefix: '*', - when: (answers) => - answers.framework === VUE_TEMPLATE_PATH && - answers.serverFramework !== ServerFrameworks.Skip, - }, - { - type: 'input', - name: 'redisHost', - message: '请输入Redis地址:', - default: 'localhost', - prefix: '*', - when: (answers) => answers.serverConfirm, - }, - { - type: 'input', - name: 'redisPort', - message: '请输入Redis端口:', - default: 6379, - prefix: '*', - when: (answers) => answers.serverConfirm, - }, - { - type: 'list', - name: 'dialect', - message: '请选择数据库类型:', - choices: [ - { name: 'MySql', value: 'mysql' }, - { name: '暂不配置', value: '' }, - ], - default: 'mysql', - prefix: '*', - when: (answers) => answers.serverConfirm, - }, - { - type: 'input', - name: 'host', - message: '请输入数据库地址:', - default: 'localhost', - prefix: '*', - when: (answers) => answers.dialect, - }, - { - type: 'input', - name: 'port', - message: '请输入数据库端口:', - default: 3306, - prefix: '*', - when: (answers) => answers.host, - }, - { - type: 'input', - name: 'database', - message: '请输入数据库名称:', - prefix: '*', - validate: (input: string) => Boolean(input), - when: (answers) => answers.host, - }, - { - type: 'input', - name: 'username', - message: '请输入登录用户名:', - default: 'root', - prefix: '*', - when: (answers) => answers.host, - }, - { - type: 'password', - name: 'password', - message: '请输入密码:', - prefix: '*', - when: (answers) => answers.host, - }, - ]; - return inquirer.prompt(question); -}; - -/** - * 同步创建低代码设计器项目文件目录、文件 - * @answers 询问客户端问题的选择值 - */ -const createLowcodeDesignerSync = (answers: ProjectInfo) => { - const { name, lowcodeEngine } = answers; - if (lowcodeEngine !== LowcodeEngine.Include) { - return; - } - - // 复制低代码设计器相关目录 - const lowcodeFrom = utils.getTemplatePath(lowcodeEngine); - const lowcodeTo = utils.getDistPath(`${name}/${lowcodeEngine}`); - - copySync(lowcodeFrom, lowcodeTo); - log.success('低代码设计器模板复制成功'); -}; - -/** - * 同步创建服务端项目文件目录、文件 - * @answers 询问客户端问题的选择值 - * @dbAnswers 询问服务端配置的选择值 - */ -const createServerSync = (answers: ProjectInfo) => { - const { name, serverFramework } = answers; - // 复制服务端相关目录 - const serverFrom = utils.getTemplatePath(`${serverFramework}`); - const serverTo = utils.getDistPath(`${name}/${serverFramework}`); - if (serverFramework === ServerFrameworks.SpringBoot) { - console.log('springboot的服务端配置'); - // 拷贝 SpringBoot 模板代码到目标目录 - copySync(serverFrom, serverTo); - - // ===== 1. 定义统一的配置对象(从用户输入中提取,不写死)===== - const config = { - DB_HOST: answers.host ?? 'localhost', - DB_PORT: Number(answers.port ?? 3306), - DB_USERNAME: answers.username ?? 'root', - DB_PASSWORD: answers.password ?? 'root', - DB_NAME: answers.database ?? 'mydb', - DB_SYNCHRONIZE: false, - DB_AUTOLOADENTITIES: true, - AUTH_SECRET: 'secret', - REDIS_SECONDS: 7200, - REDIS_HOST: answers.redisHost ?? 'localhost', - REDIS_PORT: Number(answers.redisPort ?? 6379), - EXPIRES_IN: '2h', - PAGINATION_PAGE: 1, - PAGINATION_LIMIT: 10, - SERVER_PORT: 3000, // 可以改为 answers.serverPort || 3000,如果将来要支持动态端口 - }; - - // ===== 2. 拷贝模板后,设置 Spring Boot 配置文件路径 ===== - const propertiesFilePath = path.join( - serverTo, - 'src/main/resources/application.properties' - ); - - if (!existsSync(propertiesFilePath)) { - log.error(`❌ 未找到 Spring Boot 配置文件:${propertiesFilePath}`); - return; - } - - if (!existsSync(propertiesFilePath)) { - log.error(`❌ 未找到 Spring Boot 配置文件:${propertiesFilePath}`); - return; - } - - // ✅ 1. 使用 createEditor 读取 properties 文件 - const editor = createEditor(propertiesFilePath); - - // ✅ 3. 使用 editor.set(...) 方法逐一设置每一个配置项 - editor.set('server.port', config.SERVER_PORT.toString()); // 动态端口 - - editor.set( - 'spring.datasource.url', - `jdbc:mysql://${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false` - ); - editor.set('spring.datasource.username', config.DB_USERNAME); - editor.set('spring.datasource.password', config.DB_PASSWORD); - editor.set( - 'spring.datasource.driver-class-name', - 'com.mysql.cj.jdbc.Driver' - ); - - // HikariCP 配置 - editor.set('spring.datasource.hikari.pool-name', 'HikariCPDatasource'); - editor.set('spring.datasource.hikari.minimum-idle', '5'); - editor.set('spring.datasource.hikari.idle-timeout', '180000'); - editor.set('spring.datasource.hikari.maximum-pool-size', '10'); - editor.set('spring.datasource.hikari.auto-commit', 'true'); - editor.set('spring.datasource.hikari.max-lifetime', '180000'); - editor.set('spring.datasource.hikari.connection-timeout', '30000'); - - // MyBatis-Plus - editor.set('mybatis-plus.mapper-locations', 'classpath:mappers/*.xml'); - editor.set('mybatis-plus.type-aliases-package', 'com.TinyPro.entity.po'); - editor.set( - 'mybatis-plus.configuration.map-underscore-to-camel-case', - 'true' - ); - editor.set( - 'mybatis-plus.configuration.default-enum-type-handler', - 'org.apache.ibatis.type.EnumOrdinalTypeHandler' - ); - - // JPA (如同时使用) - editor.set('spring.jpa.hibernate.ddl-auto', 'update'); - editor.set( - 'spring.jpa.database-platform', - 'org.hibernate.dialect.MySQL8Dialect' - ); - editor.set( - 'spring.jpa.properties.hibernate.dialect', - 'org.hibernate.dialect.MySQL8Dialect' - ); - editor.set( - 'spring.jpa.properties.hibernate.dialect.storage_engine', - 'innodb' - ); - editor.set( - 'spring.jpa.properties.hibernate.globally_quoted_identifiers', - 'true' - ); - editor.set( - 'spring.jpa.hibernate.naming.physical-strategy', - 'org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl' - ); - - // JWT - editor.set('jwt.secret', '0Zi4SA=='); - - // Redis - editor.set('spring.data.redis.host', config.REDIS_HOST); - editor.set('spring.data.redis.port', config.REDIS_PORT.toString()); - - // ✅ 4. 保存修改后的内容到原 .properties 文件 - editor.save(propertiesFilePath); - - log.success(`✅ Spring Boot 配置文件已更新:${propertiesFilePath}`); - } else if (serverFramework === ServerFrameworks.NestJs) { - const config = { - DATABASE_HOST: answers.dialect && (answers.host ?? 'localhost'), - DATABASE_PORT: answers.dialect && Number(answers.port ?? 3306), - DATABASE_USERNAME: answers.dialect && (answers.username ?? 'root'), - DATABASE_PASSWORD: answers.dialect && (answers.password ?? 'root'), - DATABASE_NAME: answers.dialect && answers.database, - DATABASE_SYNCHRONIZE: false, - DATABASE_AUTOLOADENTITIES: true, - AUTH_SECRET: 'secret', - REDIS_SECONDS: 7200, - REDIS_HOST: answers.redisHost ?? 'localhost', - REDIS_PORT: Number(answers.redisPort ?? 6379), - EXPIRES_IN: '2h', - PAGINATION_PAGE: 1, - PAGINATION_LIMIT: 10, - }; - const envStr = objToEnv(config); - const overwriteDockerComposeConfig = { - // @see https://hub.docker.com/_/mysql - // This variable is mandatory and specifies the password that will be set for the MySQL root superuser account. - MYSQL_ROOT_PASSWORD: config.DATABASE_PASSWORD, - MYSQL_DATABASE: config.DATABASE_NAME, - MYSQL_USER: - config.DATABASE_USERNAME === 'root' ? undefined : config.DATABASE_NAME, - MYSQL_PASSWORD: - config.DATABASE_USERNAME === 'root' - ? undefined - : config.DATABASE_PASSWORD, - }; - copySync(serverFrom, serverTo); - writeFileSync(path.join(serverTo, '.env'), envStr); - const dockerComposeYaml = readFileSync( - path.join(serverTo, 'docker-compose.yml') - ).toString(); - const yaml = parse(dockerComposeYaml); - const { services } = yaml; - const mysql = services.mysql; - mysql.environment = overwriteDockerComposeConfig; - writeFileSync(path.join(serverTo, 'docker-compose.yml'), stringify(yaml)); - } -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const objToEnv = (obj: Record) => { - return Object.entries(obj) - .map(([key, value]) => { - const v = typeof value === 'string' ? `'${value}'` : value; - return [key, '=', v].join(' '); - }) - .join('\n'); -}; +export { getProjectInfo } from './init/questions'; +export { objToEnv } from './init/env-config'; +export { packageJsonProcess } from './init/package-json'; +export { createLowcodeDesignerSync, createServerSync, createProjectSync, installDependencies } from './init/project'; -const packageJsonProcess = ( - buildTool: BuildTool, - packages: Record>, - currentPath: string -) => { - const match = (pattern: RegExp, items: Array) => { - return items.filter((item) => pattern.test(item)); - }; - const removeDeps = () => { - const devDeps = devDependencies[buildTool]; - devDeps.forEach((devDep) => { - if (typeof devDep === 'string') { - packages.devDependencies[devDep] = undefined; - } - if (devDep instanceof RegExp) { - const deps = match(devDep, Object.keys(packages.devDependencies)); - if (!deps.length) { - return; - } - deps.forEach((dep) => { - if (packages.devDependencies[dep]) { - packages.devDependencies[dep] = undefined; - } - }); - } - }); - const dependencies = removeDependencies[buildTool]; - dependencies.forEach((dep: string | RegExp) => { - if (typeof dep === 'string') { - if (!packages.dependencies[dep]) { - return; - } - packages.dependencies[dep] = undefined; - } - if (dep instanceof RegExp) { - const keys = match(dep, Object.keys(packages.devDependencies)); - keys.forEach((key) => { - if (!packages.dependencies[key]) { - return; - } - packages.dependencies[key] = undefined; - }); - } - }); - }; - const replaceScript = (name: string, command: string | undefined) => { - packages.scripts[name] = command; - }; - const removeScripts = () => { - const scripts = removedCommand; - scripts.forEach((script) => { - replaceScript(script, undefined); - }); - }; - const replaceBuildCommand = () => { - const command = buildCommand[buildTool]; - replaceScript('build', command); - }; - const replaceDevCommand = () => { - const command = devCommand[buildTool]; - replaceScript('start', command); - }; - const remove = () => { - const removedPaths = buildConfigs[buildTool]; - const paths = removedPaths - .filter((removedPath) => existsSync(path.join(currentPath, removedPath))) - .map((p) => path.join(currentPath, p)); - if (!paths.length) { - return; - } - let willRemovePath = ''; - try { - paths.forEach((removePath) => { - willRemovePath = removePath; - rmSync(removePath, { recursive: true, force: true }); - }); - } catch { - log.error(`删除${willRemovePath}错误`); - } - }; - removeDeps(); - removeScripts(); - replaceBuildCommand(); - replaceDevCommand(); - return remove; -}; - -/** - * 同步创建客户端项目文件目录、文件 - * @answers 询问客户端问题的选择值 - * @dbAnswers 询问服务端配置的选择值 - */ -const createProjectSync = (answers: ProjectInfo) => { - const { - description, - name, - serverConfirm, - buildTool, - serverFramework, - lowcodeEngine, - } = answers; - const templatePath = VueVersion.Vue3; - // 模板来源目录 - const from = utils.getTemplatePath(templatePath); - // 复制模板的目标目录 - const to = utils.getDistPath(serverConfirm ? `${name}/web` : name); - fs.copyTpl(from, to); - // 将项目名称、描述写入 package.json中 - if (serverFramework === ServerFrameworks.NestJs) { - try { - const packageJsonPath = path.join(to, 'package.json'); - let packageJson = JSON.parse( - fs.readFileSync(packageJsonPath, { encoding: 'utf8' }) - ); - packageJson = { ...packageJson, name, description }; - const remove = packageJsonProcess(buildTool, packageJson, to); - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), { - encoding: 'utf8', - }); - remove(); - } catch (e) { - log.error(e); - log.error('配置项目信息创建失败'); - } - } - - // 配置环境变量 - try { - const envPath = path.join(to, 'dev.env'); - const envContent = fs.readFileSync(envPath, { encoding: 'utf8' }); - let updatedEnvContent = envContent; - - // 如果不对接服务端,删除VITE_MOCK_IGNORE配置 - if (!serverConfirm) { - const envConfig = dotenv.parse(envContent); - delete envConfig.VITE_MOCK_IGNORE; - updatedEnvContent = Object.keys(envConfig) - .map((key) => `${key} = ${envConfig[key]}`) - .join('\n'); - } - - // 根据用户选择设置低代码设计器环境变量 - const lowcodeEnabled = lowcodeEngine === LowcodeEngine.Include; - updatedEnvContent = updatedEnvContent.replace( - /VITE_LOWCODE_DESIGNER_ENABLED=false/, - `VITE_LOWCODE_DESIGNER_ENABLED=${lowcodeEnabled}` - ); - - fs.writeFileSync(envPath, updatedEnvContent); - log.success(`低代码设计器环境变量已设置为: ${lowcodeEnabled}`); - } catch (e) { - log.error(e); - log.error('配置环境变量失败'); - log.info('请手动配置env信息'); - } - - if (serverConfirm) { - // 如果对接服务端,执行文件复制及相关配置( WIP: 后台接口暂未全量完成,部分接口还是使用mock ) - createServerSync(answers); - } - - // 如果选择了低代码设计器,复制低代码设计器模板 - createLowcodeDesignerSync(answers); -}; - -// 安装依赖 -export const installDependencies = (answers: ProjectInfo) => { - const prefix = cliConfig.getBinName(); - const { name, serverFramework, serverConfirm, lowcodeEngine } = answers; - // egg服务端 安装依赖并启动 - if (serverConfirm && serverFramework === ServerFrameworks.EggJs) { - log.info('正在安装服务端 npm 依赖,安装过程需要几十秒,请耐心等待...'); - const installServiceResult = spawn.sync('npm', ['install'], { - cwd: `${name}/${serverFramework}/`, - stdio: 'inherit', - }); - if (installServiceResult.status === 0) { - log.success('服务端 npm 依赖安装成功'); - } else { - throw new Error(installServiceResult.error); - } - } - // npm 依赖安装 - log.info('正在安装客户端 npm 依赖,安装过程需要几十秒,请耐心等待...'); - const installClientResult = spawn.sync('npm', ['install'], { - cwd: serverConfirm ? `${name}/web` : `${name}/`, - stdio: 'inherit', - }); - if (installClientResult.status === 0) { - log.success('客户端 npm 依赖安装成功'); - } else { - throw new Error(installClientResult.error); - } - - // 低代码设计器依赖安装 - if (lowcodeEngine && lowcodeEngine === LowcodeEngine.Include) { - log.info( - '正在安装低代码设计器 npm 依赖,安装过程需要几十秒,请耐心等待...' - ); - const installLowcodeResult = spawn.sync('npm', ['install'], { - cwd: `${name}/${lowcodeEngine}/`, - stdio: 'inherit', - }); - if (installLowcodeResult.status === 0) { - log.success('低代码设计器 npm 依赖安装成功'); - } else { - throw new Error(installLowcodeResult.error); - } - } - - /* prettier-ignore-start */ - console.log( - chalk.yellow( - '\n--------------------初始化成功,请按下面提示进行操作--------------------\n' - ) - ); - - if (serverConfirm) { - console.log( - chalk.green( - `${chalk.yellow( - `$ cd ${name}/web && npm run start` - )} # 开启web开发环境` - ) - ); - console.log( - chalk.green( - `${chalk.yellow( - serverFramework === ServerFrameworks.SpringBoot - ? `请查看 ${name}/${serverFramework}/README_CN.md ` - : `$ cd ${name}/${serverFramework} && npm run dev` - )} # 开启server开发环境` - ) - ); - } else { - console.log( - chalk.green( - `${chalk.yellow( - `$ cd ${name} && ${prefix} start` - )} # 可一键开启项目开发环境` - ) - ); - } - - // 低代码设计器启动提示 - if (lowcodeEngine && lowcodeEngine === LowcodeEngine.Include) { - console.log( - chalk.green( - `${chalk.yellow( - `$ cd ${name}/${lowcodeEngine} && npm run dev` - )} # 开启低代码设计器开发环境` - ) - ); - } - - console.log( - chalk.green( - `${chalk.yellow(`$ ${prefix} help`)} # 可查看当前套件的详细帮助` - ) - ); - console.log( - chalk.green( - `\n建议将现有初始化的代码提交一次到master分支, 方便后续切换到 ${chalk.yellow( - 'daily/x.y.z' - )} 分支进行开发` - ) - ); - console.log( - chalk.yellow( - '\n-------------------- 技术支持:官方小助手微信opentiny-official --------------------\n' - ) - ); -}; +const log = logs('tiny-toolkit-pro'); export default async () => { - // 拷贝模板到当前目录 - let projectInfo: ProjectInfo; + let projectInfo; try { - // 创建项目文件夹及文件 projectInfo = await getProjectInfo(); - createProjectSync(projectInfo); + await createProjectSync(projectInfo); } catch (e) { log.error(e); log.error('项目模板创建失败'); diff --git a/src/lib/init/env-config.ts b/src/lib/init/env-config.ts new file mode 100644 index 00000000..ad18e280 --- /dev/null +++ b/src/lib/init/env-config.ts @@ -0,0 +1,9 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const objToEnv = (obj: Record) => { + return Object.entries(obj) + .map(([key, value]) => { + const v = typeof value === 'string' ? `'${value}'` : value; + return [key, '=', v].join(' '); + }) + .join('\n'); +}; diff --git a/src/lib/init/package-json.ts b/src/lib/init/package-json.ts new file mode 100644 index 00000000..c1a66b3a --- /dev/null +++ b/src/lib/init/package-json.ts @@ -0,0 +1,108 @@ +import * as path from 'path'; +import { logs } from '@opentiny/cli-devkit'; +import { existsSync, rmSync } from 'fs'; +import { + buildCommand, + buildConfigs, + BuildTool, + devCommand, + devDependencies, + removedCommand, + removeDependencies, +} from '../interfaces'; + +const log = logs('tiny-toolkit-pro'); + +const matchByPattern = (pattern: RegExp, items: string[]) => + items.filter((item) => pattern.test(item)); + +const removeMatchingDeps = ( + deps: (string | RegExp)[], + target: Record +) => { + deps.forEach((dep) => { + if (typeof dep === 'string') { + if (target[dep]) { + target[dep] = undefined; + } + return; + } + if (dep instanceof RegExp) { + matchByPattern(dep, Object.keys(target)).forEach((key) => { + if (target[key]) { + target[key] = undefined; + } + }); + } + }); +}; + +const removeDevDeps = ( + buildTool: BuildTool, + packages: Record> +) => { + removeMatchingDeps( + devDependencies[buildTool], + packages.devDependencies as Record + ); + removeMatchingDeps( + removeDependencies[buildTool], + packages.dependencies as Record + ); +}; + +const replaceScript = ( + packages: Record>, + name: string, + command: string | undefined +) => { + packages.scripts[name] = command; +}; + +const removeObsoleteScripts = ( + packages: Record> +) => { + removedCommand.forEach((script) => + replaceScript(packages, script, undefined) + ); +}; + +const applyBuildToolScripts = ( + buildTool: BuildTool, + packages: Record> +) => { + replaceScript(packages, 'build', buildCommand[buildTool]); + replaceScript(packages, 'start', devCommand[buildTool]); +}; + +const removeBuildConfigFiles = (buildTool: BuildTool, currentPath: string) => { + const paths = buildConfigs[buildTool] + .filter((removedPath) => existsSync(path.join(currentPath, removedPath))) + .map((p) => path.join(currentPath, p)); + + if (!paths.length) { + return; + } + + let willRemovePath = ''; + try { + paths.forEach((removePath) => { + willRemovePath = removePath; + rmSync(removePath, { recursive: true, force: true }); + }); + } catch { + log.error(`删除${willRemovePath}错误`); + } +}; + +export const packageJsonProcess = ( + buildTool: BuildTool, + packages: Record>, + currentPath: string +) => { + removeDevDeps(buildTool, packages); + removeObsoleteScripts(packages); + applyBuildToolScripts(buildTool, packages); + + return () => removeBuildConfigFiles(buildTool, currentPath); +}; diff --git a/src/lib/init/project.ts b/src/lib/init/project.ts new file mode 100644 index 00000000..406da22c --- /dev/null +++ b/src/lib/init/project.ts @@ -0,0 +1,217 @@ +import * as path from 'path'; +import * as dotenv from 'dotenv'; +import { copySync } from 'fs-extra'; +import chalk from 'chalk'; +import spawn from 'cross-spawn'; +import { cliConfig, logs, fs } from '@opentiny/cli-devkit'; +import { + LowcodeEngine, + ProjectInfo, + ServerFrameworks, + VueVersion, +} from '../interfaces'; +import utils from '../utils'; +import { createSpringBootServer } from './server-springboot'; +import { createNestJsServer } from './server-nestjs'; +import { packageJsonProcess } from './package-json'; +import { createNextNestjsServer } from './server-nestjs-next'; + +const log = logs('tiny-toolkit-pro'); + +export const createLowcodeDesignerSync = (answers: ProjectInfo) => { + const { name, lowcodeEngine } = answers; + if (lowcodeEngine !== LowcodeEngine.Include) { + return; + } + + const lowcodeFrom = utils.getTemplatePath(lowcodeEngine); + const lowcodeTo = utils.getDistPath(`${name}/${lowcodeEngine}`); + + copySync(lowcodeFrom, lowcodeTo); + log.success('低代码设计器模板复制成功'); +}; + +export const createServerSync = async (answers: ProjectInfo) => { + const { serverFramework } = answers; + + if (serverFramework === ServerFrameworks.SpringBoot) { + createSpringBootServer(answers); + } else if (serverFramework === ServerFrameworks.NestJs) { + createNestJsServer(answers); + } else if (serverFramework === ServerFrameworks.NestJsNext) { + await createNextNestjsServer(answers); + } +}; + +const configurePackageJson = (answers: ProjectInfo, to: string) => { + const { description, name, buildTool, serverFramework } = answers; + + if (serverFramework !== ServerFrameworks.NestJs) { + return; + } + + try { + const packageJsonPath = path.join(to, 'package.json'); + let packageJson = JSON.parse( + fs.readFileSync(packageJsonPath, { encoding: 'utf8' }) + ); + packageJson = { ...packageJson, name, description }; + const remove = packageJsonProcess(buildTool, packageJson, to); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), { + encoding: 'utf8', + }); + remove(); + } catch (e) { + log.error(e); + log.error('配置项目信息创建失败'); + } +}; + +const configureEnvFile = (answers: ProjectInfo, to: string) => { + const { serverConfirm, lowcodeEngine } = answers; + + try { + const envPath = path.join(to, 'dev.env'); + const envContent = fs.readFileSync(envPath, { encoding: 'utf8' }); + let updatedEnvContent = envContent; + + if (!serverConfirm) { + const envConfig = dotenv.parse(envContent); + delete envConfig.VITE_MOCK_IGNORE; + updatedEnvContent = Object.keys(envConfig) + .map((key) => `${key} = ${envConfig[key]}`) + .join('\n'); + } + + const lowcodeEnabled = lowcodeEngine === LowcodeEngine.Include; + updatedEnvContent = updatedEnvContent.replace( + /VITE_LOWCODE_DESIGNER_ENABLED=false/, + `VITE_LOWCODE_DESIGNER_ENABLED=${lowcodeEnabled}` + ); + + fs.writeFileSync(envPath, updatedEnvContent); + log.success(`低代码设计器环境变量已设置为: ${lowcodeEnabled}`); + } catch (e) { + log.error(e); + log.error('配置环境变量失败'); + log.info('请手动配置env信息'); + } +}; + +export const createProjectSync = async (answers: ProjectInfo) => { + const { name, serverConfirm } = answers; + const templatePath = VueVersion.Vue3; + const from = utils.getTemplatePath(templatePath); + const to = utils.getDistPath(serverConfirm ? `${name}/web` : name); + + if (serverConfirm) { + await createServerSync(answers); + } + copySync(from, to, { + filter: (src) => { + return !src.includes('node_modules') + } + }) + + configurePackageJson(answers, to); + configureEnvFile(answers, to); + + createLowcodeDesignerSync(answers); +}; + +const runNpmInstall = (cwd: string, label: string) => { + log.info(`正在安装${label} npm 依赖,安装过程需要几十秒,请耐心等待...`); + const result = spawn.sync('npm', ['install'], { + cwd, + stdio: 'inherit', + }); + if (result.status === 0) { + log.success(`${label} npm 依赖安装成功`); + } else { + throw new Error(result.error); + } +}; + +const printSuccessMessage = (answers: ProjectInfo) => { + const prefix = cliConfig.getBinName(); + const { name, serverFramework, serverConfirm, lowcodeEngine } = answers; + + /* prettier-ignore-start */ + console.log( + chalk.yellow( + '\n--------------------初始化成功,请按下面提示进行操作--------------------\n' + ) + ); + + if (serverConfirm) { + console.log( + chalk.green( + `${chalk.yellow( + `$ cd ${name}/web && npm run start` + )} # 开启web开发环境` + ) + ); + console.log( + chalk.green( + `${chalk.yellow( + serverFramework === ServerFrameworks.SpringBoot + ? `请查看 ${name}/${serverFramework}/README_CN.md ` + : `$ cd ${name}/${serverFramework} && npm run dev` + )} # 开启server开发环境` + ) + ); + } else { + console.log( + chalk.green( + `${chalk.yellow( + `$ cd ${name} && ${prefix} start` + )} # 可一键开启项目开发环境` + ) + ); + } + + if (lowcodeEngine && lowcodeEngine === LowcodeEngine.Include) { + console.log( + chalk.green( + `${chalk.yellow( + `$ cd ${name}/${lowcodeEngine} && npm run dev` + )} # 开启低代码设计器开发环境` + ) + ); + } + + console.log( + chalk.green( + `${chalk.yellow(`$ ${prefix} help`)} # 可查看当前套件的详细帮助` + ) + ); + console.log( + chalk.green( + `\n建议将现有初始化的代码提交一次到master分支, 方便后续切换到 ${chalk.yellow( + 'daily/x.y.z' + )} 分支进行开发` + ) + ); + console.log( + chalk.yellow( + '\n-------------------- 技术支持:官方小助手微信opentiny-official --------------------\n' + ) + ); + /* prettier-ignore-end */ +}; + +export const installDependencies = (answers: ProjectInfo) => { + const { name, serverFramework, serverConfirm, lowcodeEngine } = answers; + + if (serverConfirm && serverFramework === ServerFrameworks.EggJs) { + runNpmInstall(`${name}/${serverFramework}/`, '服务端'); + } + + runNpmInstall(serverConfirm ? `${name}/web` : `${name}/`, '客户端'); + + if (lowcodeEngine && lowcodeEngine === LowcodeEngine.Include) { + runNpmInstall(`${name}/${lowcodeEngine}/`, '低代码设计器'); + } + + printSuccessMessage(answers); +}; diff --git a/src/lib/init/questions.ts b/src/lib/init/questions.ts new file mode 100644 index 00000000..c64c5af7 --- /dev/null +++ b/src/lib/init/questions.ts @@ -0,0 +1,157 @@ +import inquirer, { QuestionCollection } from 'inquirer'; +import { + BuildTool, + LowcodeEngine, + ProjectInfo, + ServerFrameworks, +} from '../interfaces'; + +const VUE_TEMPLATE_PATH = 'tinyvue'; +const DEFAULT_PROJECT_NAME = 'tiny-pro'; + +export const getProjectInfo = (): Promise => { + const question: QuestionCollection = [ + { + type: 'input', + name: 'name', + message: '请输入项目名称:', + default: DEFAULT_PROJECT_NAME, + validate: (input: string) => Boolean(input), + }, + { + type: 'input', + name: 'description', + message: '请输入项目描述:', + default: '基于TinyPro套件创建的中后台系统', + }, + { + type: 'list', + name: 'framework', + message: '请选择您希望使用的客户端技术栈:', + choices: [{ name: 'vue', value: VUE_TEMPLATE_PATH }], + default: VUE_TEMPLATE_PATH, + prefix: '*', + }, + { + type: 'list', + name: 'serverFramework', + message: '请选择您希望使用的服务端技术栈:', + choices: [ + { name: 'SpringBoot', value: ServerFrameworks.SpringBoot }, + { name: 'Nest.js', value: ServerFrameworks.NestJs }, + { name: 'Nest.js Next', value: ServerFrameworks.NestJsNext }, + { name: '暂不配置', value: ServerFrameworks.Skip }, + ], + default: ServerFrameworks.NestJs, + prefix: '*', + when: (answers) => answers.framework === VUE_TEMPLATE_PATH, + }, + { + type: 'list', + name: 'lowcodeEngine', + message: '是否集成低代码设计器:', + choices: [ + { name: '是,集成低代码设计器', value: LowcodeEngine.Include }, + { name: '否,暂不集成', value: LowcodeEngine.Skip }, + ], + default: LowcodeEngine.Skip, + prefix: '*', + when: (answers) => answers.framework === VUE_TEMPLATE_PATH, + }, + { + type: 'list', + name: 'buildTool', + message: '请选择你想要的构建工具: ', + choices: [ + { name: 'Vite', value: BuildTool.Vite }, + { name: 'Webpack', value: BuildTool.Webpack }, + { name: 'Rspack', value: BuildTool.Rspack }, + { name: 'Farm', value: BuildTool.Farm }, + ], + default: BuildTool.Vite, + prefix: '*', + }, + { + type: 'list', + name: 'serverConfirm', + message: + '请确保已安装数据库服务(参考文档 https://www.opentiny.design/tiny-cli/docs/toolkits/pro#database):', + choices: [ + { name: '已完成数据库服务安装,开始配置', value: true }, + { name: '暂不配置服务端', value: false }, + ], + prefix: '*', + when: (answers) => + answers.framework === VUE_TEMPLATE_PATH && + answers.serverFramework !== ServerFrameworks.Skip, + }, + { + type: 'input', + name: 'redisHost', + message: '请输入Redis地址:', + default: 'localhost', + prefix: '*', + when: (answers) => answers.serverConfirm, + }, + { + type: 'input', + name: 'redisPort', + message: '请输入Redis端口:', + default: 6379, + prefix: '*', + when: (answers) => answers.serverConfirm, + }, + { + type: 'list', + name: 'dialect', + message: '请选择数据库类型:', + choices: [ + { name: 'MySql', value: 'mysql' }, + { name: '暂不配置', value: '' }, + ], + default: 'mysql', + prefix: '*', + when: (answers) => answers.serverConfirm, + }, + { + type: 'input', + name: 'host', + message: '请输入数据库地址:', + default: 'localhost', + prefix: '*', + when: (answers) => answers.dialect, + }, + { + type: 'input', + name: 'port', + message: '请输入数据库端口:', + default: 3306, + prefix: '*', + when: (answers) => answers.host, + }, + { + type: 'input', + name: 'database', + message: '请输入数据库名称:', + prefix: '*', + validate: (input: string) => Boolean(input), + when: (answers) => answers.host, + }, + { + type: 'input', + name: 'username', + message: '请输入登录用户名:', + default: 'root', + prefix: '*', + when: (answers) => answers.host, + }, + { + type: 'password', + name: 'password', + message: '请输入密码:', + prefix: '*', + when: (answers) => answers.host, + }, + ]; + return inquirer.prompt(question); +}; diff --git a/src/lib/init/server-nestjs-next.ts b/src/lib/init/server-nestjs-next.ts new file mode 100644 index 00000000..1c5073c6 --- /dev/null +++ b/src/lib/init/server-nestjs-next.ts @@ -0,0 +1,199 @@ +import inquirer, { QuestionCollection } from "inquirer"; +import { ProjectInfo } from "../interfaces"; +import utils from "../utils"; +import { writeFileSync } from "fs"; +import { objToEnv } from "./env-config"; +import { copySync } from "fs-extra"; + +interface DatabaseConfig { + /** 数据库主机地址 */ + host: string; + /** 数据库端口 */ + port: number; + /** 数据库用户名 */ + user: string; + /** 数据库密码 */ + password: string; + /** 数据库名称 */ + dbName: string; +} + +interface RedisConfigure { + /** Redis主机地址 */ + host: string; + /** Redis端口 */ + port: number; + /** Redis用户名 */ + user: string; + /** Redis密码 */ + password: string; + /** Redis数据库编号 */ + db: number; +} + +interface FeatureConfigure { + /** 预览功能开关 */ + preview: boolean; +} + +/** + * Swagger 文档配置(可选) + */ +interface SwaggerConfigure { + /** Swagger文档标题 */ + title?: string; + /** API版本号 */ + version?: string; + /** Swagger文档描述 */ + description?: string; +} + +/** + * JWT 密钥模式(使用固定密钥) + */ +interface SecretConfigure { + /** JWT模式:使用密钥 */ + mode: 'secret'; + /** JWT密钥 */ + secret: string; +} + +/** + * JWT 本地密钥文件模式(使用公钥/私钥文件) + */ +interface LocalKeyConfigure { + /** JWT模式:使用本地密钥文件 */ + mode: 'local-key'; + /** 公钥文件路径 */ + publicKeyPath: string; + /** 私钥文件路径 */ + privateKeyPath: string; +} + +/** + * 认证配置 + */ +interface AuthConfigure { + /** + * 设备限制数量(已废弃) + * @deprecated + */ + device_limit?: number; + /** 会话限制数量 */ + session_limit: number; + /** 访问令牌过期时间(秒) */ + accessTokenTTL: number; + /** 刷新令牌过期时间(秒) */ + refreshTokenTTL: number; + /** API令牌过期时间(秒) */ + apiTokenTTL: number; + /** JWT 配置,支持密钥或本地密钥文件两种模式 */ + jwt: SecretConfigure | LocalKeyConfigure; +} + +export const getFeatureConfigure = () => { + const question:QuestionCollection = [ + { + type: 'confirm', + name: 'preview', + default: false, + message: '是否开启预览功能 (如果开启会关闭写入和删除功能)', + } + ] + return inquirer.prompt(question); +} +export const getAuthConfigure = () => { + const question: QuestionCollection = [ + { + type: 'number', + name: 'session_limit', + default: 1, + message: '会话限制数量', + prefix: '*' + }, + { + type: 'number', + name: 'accessTokenTTL', + default: 300, + message: '访问令牌过期时间(秒)', + prefix: '*' + }, + { + type: 'number', + name: 'refreshTokenTTL', + default: 86400, + message: '刷新令牌过期时间(秒)', + prefix: '*' + }, + { + type: 'number', + name: 'apiTokenTTL', + default: 604800, + message: 'API令牌过期时间(秒)', + prefix: '*' + }, + { + type: 'list', + name: 'jwt.mode', + message: 'JWT模式', + default: 'secret', + choices: ['secret', 'local-key'], + prefix: '*' + }, + { + type: 'input', + name: 'jwt.secret', + message: 'JWT密钥', + prefix: '*', + when(answers) { + return answers.jwt.mode === 'secret' + }, + }, + { + type: 'input', + name: 'jwt.publicKeyPath', + message: '公钥文件路径', + prefix: '*', + when(answers) { + return answers.jwt.mode === 'local-key' + }, + }, + { + type: 'input', + name: 'jwt.privateKeyPath', + message: '私钥文件路径', + prefix: '*', + when(answers) { + return answers.jwt.mode === 'local-key' + }, + }, + ] + return inquirer.prompt(question); +} + +export const createNextNestjsServer = async (answer: ProjectInfo) => { + const { name, host, port, database, username, password, redisHost, redisPort } = answer; + const from = utils.getTemplatePath('nest-next'); + const to = utils.getDistPath(`${name}/nest`); + const configPath = utils.getDistPath(`${name}/nest/configs/config.json`); + const authConfigure = {auth: await getAuthConfigure()}; + const featureConfigure = {feature: await getFeatureConfigure()}; + + const config = { + database: { + host, port, user: username, password, dbName: database, + }, + redis:{ + host: redisHost, port: redisPort, + user: 'root', password: 'root', db: 0, + }, + feature: { + preview: featureConfigure.feature.preview + }, + ...authConfigure + }; + copySync(from, to, {filter: (src) =>{ + return !src.includes('node_modules') + }}); + writeFileSync(configPath, JSON.stringify(config, null, 2)); +} diff --git a/src/lib/init/server-nestjs.ts b/src/lib/init/server-nestjs.ts new file mode 100644 index 00000000..5d43767b --- /dev/null +++ b/src/lib/init/server-nestjs.ts @@ -0,0 +1,72 @@ +import * as path from 'path'; +import { parse, stringify } from 'yaml'; +import { copySync } from 'fs-extra'; +import { readFileSync, writeFileSync } from 'fs'; +import { ServerFrameworks, ProjectInfo } from '../interfaces'; +import utils from '../utils'; +import { objToEnv } from './env-config'; + +interface NestJsConfig { + DATABASE_HOST: string | false; + DATABASE_PORT: number | false; + DATABASE_USERNAME: string | false; + DATABASE_PASSWORD: string | false; + DATABASE_NAME: string | false; + DATABASE_SYNCHRONIZE: boolean; + DATABASE_AUTOLOADENTITIES: boolean; + AUTH_SECRET: string; + REDIS_SECONDS: number; + REDIS_HOST: string; + REDIS_PORT: number; + EXPIRES_IN: string; + PAGINATION_PAGE: number; + PAGINATION_LIMIT: number; +} + +const buildNestJsConfig = (answers: ProjectInfo): NestJsConfig => ({ + DATABASE_HOST: answers.dialect && (answers.host ?? 'localhost'), + DATABASE_PORT: answers.dialect && Number(answers.port ?? 3306), + DATABASE_USERNAME: answers.dialect && (answers.username ?? 'root'), + DATABASE_PASSWORD: answers.dialect && (answers.password ?? 'root'), + DATABASE_NAME: answers.dialect && answers.database, + DATABASE_SYNCHRONIZE: false, + DATABASE_AUTOLOADENTITIES: true, + AUTH_SECRET: 'secret', + REDIS_SECONDS: 7200, + REDIS_HOST: answers.redisHost ?? 'localhost', + REDIS_PORT: Number(answers.redisPort ?? 6379), + EXPIRES_IN: '2h', + PAGINATION_PAGE: 1, + PAGINATION_LIMIT: 10, +}); + +const overwriteDockerComposeEnvironment = (config: NestJsConfig) => ({ + MYSQL_ROOT_PASSWORD: config.DATABASE_PASSWORD, + MYSQL_DATABASE: config.DATABASE_NAME, + MYSQL_USER: + config.DATABASE_USERNAME === 'root' ? undefined : config.DATABASE_NAME, + MYSQL_PASSWORD: + config.DATABASE_USERNAME === 'root' + ? undefined + : config.DATABASE_PASSWORD, +}); + +export const createNestJsServer = (answers: ProjectInfo) => { + const { name } = answers; + const serverFrom = utils.getTemplatePath(ServerFrameworks.NestJs); + const serverTo = utils.getDistPath(`${name}/${ServerFrameworks.NestJs}`); + + const config = buildNestJsConfig(answers); + const envStr = objToEnv(config); + const dockerComposeConfig = overwriteDockerComposeEnvironment(config); + + copySync(serverFrom, serverTo); + writeFileSync(path.join(serverTo, '.env'), envStr); + + const dockerComposeYaml = readFileSync( + path.join(serverTo, 'docker-compose.yml') + ).toString(); + const yaml = parse(dockerComposeYaml); + yaml.services.mysql.environment = dockerComposeConfig; + writeFileSync(path.join(serverTo, 'docker-compose.yml'), stringify(yaml)); +}; diff --git a/src/lib/init/server-springboot.ts b/src/lib/init/server-springboot.ts new file mode 100644 index 00000000..4dd21f8f --- /dev/null +++ b/src/lib/init/server-springboot.ts @@ -0,0 +1,122 @@ +import * as path from 'path'; +import { copySync } from 'fs-extra'; +import { logs } from '@opentiny/cli-devkit'; +import { createEditor } from 'properties-parser'; +import { existsSync } from 'fs'; +import { ServerFrameworks, ProjectInfo } from '../interfaces'; +import utils from '../utils'; + +const log = logs('tiny-toolkit-pro'); + +const SPRINGBOOT_PROPERTIES_RELATIVE = + 'src/main/resources/application.properties'; + +interface SpringBootConfig { + DB_HOST: string; + DB_PORT: number; + DB_USERNAME: string; + DB_PASSWORD: string; + DB_NAME: string; + DB_SYNCHRONIZE: boolean; + DB_AUTOLOADENTITIES: boolean; + AUTH_SECRET: string; + REDIS_SECONDS: number; + REDIS_HOST: string; + REDIS_PORT: number; + EXPIRES_IN: string; + PAGINATION_PAGE: number; + PAGINATION_LIMIT: number; + SERVER_PORT: number; +} + +const buildSpringBootConfig = (answers: ProjectInfo): SpringBootConfig => ({ + DB_HOST: answers.host ?? 'localhost', + DB_PORT: Number(answers.port ?? 3306), + DB_USERNAME: answers.username ?? 'root', + DB_PASSWORD: answers.password ?? 'root', + DB_NAME: answers.database ?? 'mydb', + DB_SYNCHRONIZE: false, + DB_AUTOLOADENTITIES: true, + AUTH_SECRET: 'secret', + REDIS_SECONDS: 7200, + REDIS_HOST: answers.redisHost ?? 'localhost', + REDIS_PORT: Number(answers.redisPort ?? 6379), + EXPIRES_IN: '2h', + PAGINATION_PAGE: 1, + PAGINATION_LIMIT: 10, + SERVER_PORT: 3000, +}); + +const buildJdbcUrl = (config: SpringBootConfig) => + `jdbc:mysql://${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false`; + +const applyPropertiesConfig = ( + editor: ReturnType, + config: SpringBootConfig +) => { + editor.set('server.port', config.SERVER_PORT.toString()); + + editor.set('spring.datasource.url', buildJdbcUrl(config)); + editor.set('spring.datasource.username', config.DB_USERNAME); + editor.set('spring.datasource.password', config.DB_PASSWORD); + editor.set('spring.datasource.driver-class-name', 'com.mysql.cj.jdbc.Driver'); + + editor.set('spring.datasource.hikari.pool-name', 'HikariCPDatasource'); + editor.set('spring.datasource.hikari.minimum-idle', '5'); + editor.set('spring.datasource.hikari.idle-timeout', '180000'); + editor.set('spring.datasource.hikari.maximum-pool-size', '10'); + editor.set('spring.datasource.hikari.auto-commit', 'true'); + editor.set('spring.datasource.hikari.max-lifetime', '180000'); + editor.set('spring.datasource.hikari.connection-timeout', '30000'); + + editor.set('mybatis-plus.mapper-locations', 'classpath:mappers/*.xml'); + editor.set('mybatis-plus.type-aliases-package', 'com.TinyPro.entity.po'); + editor.set('mybatis-plus.configuration.map-underscore-to-camel-case', 'true'); + editor.set( + 'mybatis-plus.configuration.default-enum-type-handler', + 'org.apache.ibatis.type.EnumOrdinalTypeHandler' + ); + + editor.set('spring.jpa.hibernate.ddl-auto', 'update'); + editor.set('spring.jpa.database-platform', 'org.hibernate.dialect.MySQL8Dialect'); + editor.set( + 'spring.jpa.properties.hibernate.dialect', + 'org.hibernate.dialect.MySQL8Dialect' + ); + editor.set('spring.jpa.properties.hibernate.dialect.storage_engine', 'innodb'); + editor.set( + 'spring.jpa.properties.hibernate.globally_quoted_identifiers', + 'true' + ); + editor.set( + 'spring.jpa.hibernate.naming.physical-strategy', + 'org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl' + ); + + editor.set('jwt.secret', '0Zi4SA=='); + + editor.set('spring.data.redis.host', config.REDIS_HOST); + editor.set('spring.data.redis.port', config.REDIS_PORT.toString()); +}; + +export const createSpringBootServer = (answers: ProjectInfo) => { + const { name } = answers; + const serverFrom = utils.getTemplatePath(ServerFrameworks.SpringBoot); + const serverTo = utils.getDistPath(`${name}/${ServerFrameworks.SpringBoot}`); + + copySync(serverFrom, serverTo); + + const config = buildSpringBootConfig(answers); + const propertiesFilePath = path.join(serverTo, SPRINGBOOT_PROPERTIES_RELATIVE); + + if (!existsSync(propertiesFilePath)) { + log.error(`❌ 未找到 Spring Boot 配置文件:${propertiesFilePath}`); + return; + } + + const editor = createEditor(propertiesFilePath); + applyPropertiesConfig(editor, config); + editor.save(propertiesFilePath); + + log.success(`✅ Spring Boot 配置文件已更新:${propertiesFilePath}`); +}; diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 90d2569b..b5663a18 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -19,6 +19,7 @@ export enum ServerFrameworks { SpringCloud = 'springCloud', Skip = '', SpringBoot = 'springboot', + NestJsNext = 'nestJsNext', } /** diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..4500d17a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vitest/config' +export default defineConfig({ + test:{ + include: ['./src/**/*.{test,spec}(*.ts|js|jsx|tsx|vue)'] + } +})