diff --git a/.gitignore b/.gitignore index ed9ac34c8..5c04304cc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ yarn-error.log* *.pem __tmp__* + +# Kiro +.kiro diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bdf85c02..9b3b7fff9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: devDependencies: '@vitest/coverage-istanbul': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/coverage-v8': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) cross-env: specifier: ^10.0.0 version: 10.1.0 @@ -34,7 +34,7 @@ importers: version: 5.8.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) apps/angular-demo: dependencies: @@ -74,16 +74,16 @@ importers: devDependencies: '@analogjs/platform': specifier: ^1.16.0 - version: 1.21.1(287dab2532c0a46f545da8517a0c6b85) + version: 1.21.1(4e1cfceabe4441216fa8caeb750315bf) '@analogjs/vite-plugin-angular': specifier: ^1.15.1 - version: 1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea) + version: 1.21.1(ca2412d8d54a48024b26274b1afb4262) '@analogjs/vitest-angular': specifier: ^1.19.1 - version: 1.21.1(@analogjs/vite-plugin-angular@1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + version: 1.21.1(@analogjs/vite-plugin-angular@1.21.1(ca2412d8d54a48024b26274b1afb4262))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) '@angular-devkit/build-angular': specifier: ^19.2.7 - version: 19.2.17(7fdcaa5f2779c6513c6c836caffc78c6) + version: 19.2.17(dd76d4b60b356817648b2bd05786fa69) '@angular-eslint/builder': specifier: ^19.3.0 version: 19.8.1(chokidar@4.0.3)(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) @@ -119,19 +119,19 @@ importers: version: 8.31.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) vite: specifier: ^6.3.5 - version: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + version: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) apps/docs: dependencies: '@analogjs/astro-angular': specifier: ^1.19.4 - version: 1.21.1(a1fb33e30a2d76b40dc05f3e51d2d419) + version: 1.21.1(8340524aa471727c3a62c90fd01bfc19) '@angular-devkit/build-angular': specifier: ^19.2.7 - version: 19.2.17(75d1d303c97e53e0b25d3f3a17ea0d55) + version: 19.2.17(3cf9bc6a7ac3c58cbd9aecf92abcce28) '@angular/animations': specifier: ^19.2.7 version: 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)) @@ -167,7 +167,7 @@ importers: version: 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@astrojs/starlight': specifier: ^0.35.2 - version: 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + version: 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) '@expressive-code/plugin-collapsible-sections': specifier: ^0.41.3 version: 0.41.3 @@ -176,13 +176,13 @@ importers: version: 0.41.3 '@tailwindcss/vite': specifier: ^4.1.16 - version: 4.1.16(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 4.1.16(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@yeskunall/astro-umami': specifier: ~0.0.7 - version: 0.0.7(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + version: 0.0.7(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) astro: specifier: ^5.6.1 - version: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) + version: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) elkjs: specifier: ^0.10.0 version: 0.10.2 @@ -200,10 +200,10 @@ importers: version: 0.34.4 starlight-auto-sidebar: specifier: ^0.1.2 - version: 0.1.2(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1))) + version: 0.1.2(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1))) starlight-typedoc: specifier: ^0.21.3 - version: 0.21.3(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.9.0(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3)) + version: 0.21.3(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.9.0(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3)) tailwindcss: specifier: ^4.1.16 version: 4.1.16 @@ -313,16 +313,16 @@ importers: devDependencies: '@analogjs/platform': specifier: ^1.16.0 - version: 1.21.1(d77eb5863fcbb68848ddadbb97622640) + version: 1.21.1(8a4b224be30e938949e9ddb5f7c1ffb9) '@analogjs/vite-plugin-angular': specifier: ^1.15.1 - version: 1.21.1(8f84d0321c197e4c7650c3c4450c6208) + version: 1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3) '@analogjs/vitest-angular': specifier: ^1.19.1 - version: 1.21.1(@analogjs/vite-plugin-angular@1.21.1(8f84d0321c197e4c7650c3c4450c6208))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 1.21.1(@analogjs/vite-plugin-angular@1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@angular-devkit/build-angular': specifier: ^19.2.8 - version: 19.2.17(75d1d303c97e53e0b25d3f3a17ea0d55) + version: 19.2.17(3cf9bc6a7ac3c58cbd9aecf92abcce28) '@angular-eslint/builder': specifier: ^19.3.0 version: 19.8.1(chokidar@4.0.3)(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) @@ -340,7 +340,7 @@ importers: version: 7.53.1(@types/node@24.6.2) '@nx/vite': specifier: ^20.8.0 - version: 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) angular-eslint: specifier: 19.3.0 version: 19.3.0(chokidar@4.0.3)(eslint@9.36.0(jiti@2.6.1))(typescript-eslint@8.31.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3))(typescript@5.8.3) @@ -370,13 +370,38 @@ importers: version: 8.31.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3) vite: specifier: ^6.3.5 - version: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + version: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + version: 5.1.4(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + + tools/mcp-server: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^0.5.0 + version: 0.5.0 + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 + devDependencies: + '@types/node': + specifier: ^18.0.0 + version: 18.19.130 + fast-check: + specifier: ^3.0.0 + version: 3.23.2 + tsx: + specifier: ^4.0.0 + version: 4.21.0 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: latest + version: 4.0.18(@types/node@18.19.130)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) packages: @@ -1450,6 +1475,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} @@ -1462,6 +1493,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.10': resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} @@ -1474,6 +1511,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.10': resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} @@ -1486,6 +1529,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} @@ -1498,6 +1547,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.10': resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} @@ -1510,6 +1565,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} @@ -1522,6 +1583,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.10': resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} @@ -1534,6 +1601,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} @@ -1546,6 +1619,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.10': resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} @@ -1558,6 +1637,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.10': resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} @@ -1570,6 +1655,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.10': resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} @@ -1582,6 +1673,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.10': resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} @@ -1594,6 +1691,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.10': resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} @@ -1606,6 +1709,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.10': resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} @@ -1618,6 +1727,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.10': resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} @@ -1630,6 +1745,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.10': resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} @@ -1642,6 +1763,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.10': resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} @@ -1654,6 +1781,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} @@ -1666,6 +1799,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.10': resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} @@ -1678,6 +1817,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.10': resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} @@ -1690,12 +1835,24 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.10': resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} @@ -1708,6 +1865,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} @@ -1720,6 +1883,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.10': resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} @@ -1732,6 +1901,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.10': resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} @@ -1744,6 +1919,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2247,6 +2428,9 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@modelcontextprotocol/sdk@0.5.0': + resolution: {integrity: sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -3062,6 +3246,9 @@ packages: '@speed-highlight/core@1.2.7': resolution: {integrity: sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -3253,6 +3440,9 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@24.6.2': resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==} @@ -3417,6 +3607,9 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -3428,21 +3621,47 @@ packages: vite: optional: true + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@volar/kit@2.4.23': resolution: {integrity: sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ==} peerDependencies: @@ -3940,6 +4159,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -4574,6 +4797,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -4746,9 +4974,17 @@ packages: exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -4935,6 +5171,9 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@4.13.1: + resolution: {integrity: sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -4997,6 +5236,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5133,6 +5376,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} @@ -5309,6 +5556,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5777,6 +6028,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -6363,6 +6617,9 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} @@ -6757,6 +7014,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -6781,6 +7041,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -6935,6 +7199,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-url-loader@5.0.0: resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} engines: {node: '>=12'} @@ -7085,6 +7352,10 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + select-hose@2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} @@ -7331,6 +7602,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -7373,6 +7647,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -7504,6 +7782,10 @@ packages: tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -7516,6 +7798,10 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -7592,6 +7878,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + tuf-js@3.1.0: resolution: {integrity: sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -7701,6 +7992,9 @@ packages: unctx@2.4.1: resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@7.13.0: resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==} @@ -8009,6 +8303,40 @@ packages: jsdom: optional: true + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + 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.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + 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/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + volar-service-css@0.0.62: resolution: {integrity: sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==} peerDependencies: @@ -8400,10 +8728,10 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@analogjs/astro-angular@1.21.1(a1fb33e30a2d76b40dc05f3e51d2d419)': + '@analogjs/astro-angular@1.21.1(8340524aa471727c3a62c90fd01bfc19)': dependencies: - '@analogjs/vite-plugin-angular': 1.21.1(8f84d0321c197e4c7650c3c4450c6208) - '@angular-devkit/build-angular': 19.2.17(75d1d303c97e53e0b25d3f3a17ea0d55) + '@analogjs/vite-plugin-angular': 1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3) + '@angular-devkit/build-angular': 19.2.17(3cf9bc6a7ac3c58cbd9aecf92abcce28) '@angular/animations': 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)) '@angular/common': 19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/compiler': 19.2.15 @@ -8419,19 +8747,19 @@ snapshots: transitivePeerDependencies: - '@angular/build' - '@analogjs/platform@1.21.1(287dab2532c0a46f545da8517a0c6b85)': + '@analogjs/platform@1.21.1(4e1cfceabe4441216fa8caeb750315bf)': dependencies: - '@analogjs/vite-plugin-angular': 1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea) + '@analogjs/vite-plugin-angular': 1.21.1(ca2412d8d54a48024b26274b1afb4262) '@analogjs/vite-plugin-nitro': 1.21.1(encoding@0.1.13) marked: 15.0.12 marked-gfm-heading-id: 4.1.2(marked@15.0.12) marked-mangle: 1.1.11(marked@15.0.12) nitropack: 2.12.6(encoding@0.1.13) - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) - vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) optionalDependencies: '@nx/devkit': 20.8.2(nx@20.8.2) - '@nx/vite': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + '@nx/vite': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) transitivePeerDependencies: - '@angular-devkit/build-angular' - '@angular/build' @@ -8464,19 +8792,19 @@ snapshots: - uploadthing - xml2js - '@analogjs/platform@1.21.1(d77eb5863fcbb68848ddadbb97622640)': + '@analogjs/platform@1.21.1(8a4b224be30e938949e9ddb5f7c1ffb9)': dependencies: - '@analogjs/vite-plugin-angular': 1.21.1(8f84d0321c197e4c7650c3c4450c6208) + '@analogjs/vite-plugin-angular': 1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3) '@analogjs/vite-plugin-nitro': 1.21.1(encoding@0.1.13) marked: 15.0.12 marked-gfm-heading-id: 4.1.2(marked@15.0.12) marked-mangle: 1.1.11(marked@15.0.12) nitropack: 2.12.6(encoding@0.1.13) - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) optionalDependencies: '@nx/devkit': 20.8.2(nx@20.8.2) - '@nx/vite': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@nx/vite': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) transitivePeerDependencies: - '@angular-devkit/build-angular' - '@angular/build' @@ -8509,21 +8837,21 @@ snapshots: - uploadthing - xml2js - '@analogjs/vite-plugin-angular@1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea)': + '@analogjs/vite-plugin-angular@1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3)': dependencies: ts-morph: 21.0.1 vfile: 6.0.3 optionalDependencies: - '@angular-devkit/build-angular': 19.2.17(7fdcaa5f2779c6513c6c836caffc78c6) - '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.1) + '@angular-devkit/build-angular': 19.2.17(3cf9bc6a7ac3c58cbd9aecf92abcce28) + '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.6)(tailwindcss@4.1.16)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) - '@analogjs/vite-plugin-angular@1.21.1(8f84d0321c197e4c7650c3c4450c6208)': + '@analogjs/vite-plugin-angular@1.21.1(ca2412d8d54a48024b26274b1afb4262)': dependencies: ts-morph: 21.0.1 vfile: 6.0.3 optionalDependencies: - '@angular-devkit/build-angular': 19.2.17(75d1d303c97e53e0b25d3f3a17ea0d55) - '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.6)(tailwindcss@4.1.16)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) + '@angular-devkit/build-angular': 19.2.17(dd76d4b60b356817648b2bd05786fa69) + '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) '@analogjs/vite-plugin-nitro@1.21.1(encoding@0.1.13)': dependencies: @@ -8562,17 +8890,17 @@ snapshots: - uploadthing - xml2js - '@analogjs/vitest-angular@1.21.1(@analogjs/vite-plugin-angular@1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))': + '@analogjs/vitest-angular@1.21.1(@analogjs/vite-plugin-angular@1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@analogjs/vite-plugin-angular': 1.21.1(2c9ccd32bcc231d9b5a28ac80bb0c1ea) + '@analogjs/vite-plugin-angular': 1.21.1(0f5bcb90d62e89bf2bf9cb779b4e25d3) '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - '@analogjs/vitest-angular@1.21.1(@analogjs/vite-plugin-angular@1.21.1(8f84d0321c197e4c7650c3c4450c6208))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@analogjs/vitest-angular@1.21.1(@analogjs/vite-plugin-angular@1.21.1(ca2412d8d54a48024b26274b1afb4262))(@angular-devkit/architect@0.1902.17(chokidar@4.0.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@analogjs/vite-plugin-angular': 1.21.1(8f84d0321c197e4c7650c3c4450c6208) + '@analogjs/vite-plugin-angular': 1.21.1(ca2412d8d54a48024b26274b1afb4262) '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) '@angular-devkit/architect@0.1902.17(chokidar@4.0.3)': dependencies: @@ -8581,13 +8909,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@19.2.17(75d1d303c97e53e0b25d3f3a17ea0d55)': + '@angular-devkit/build-angular@19.2.17(3cf9bc6a7ac3c58cbd9aecf92abcce28)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.17(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0(esbuild@0.25.4)))(webpack@5.98.0(esbuild@0.25.4)) '@angular-devkit/core': 19.2.17(chokidar@4.0.3) - '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.1) + '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -8600,7 +8928,7 @@ snapshots: '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.4)) @@ -8668,13 +8996,13 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-angular@19.2.17(7fdcaa5f2779c6513c6c836caffc78c6)': + '@angular-devkit/build-angular@19.2.17(dd76d4b60b356817648b2bd05786fa69)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.17(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0(esbuild@0.25.4)))(webpack@5.98.0(esbuild@0.25.4)) '@angular-devkit/core': 19.2.17(chokidar@4.0.3) - '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.1) + '@angular/build': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -8687,7 +9015,7 @@ snapshots: '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.4)) @@ -8860,7 +9188,7 @@ snapshots: '@angular/core': 19.2.15(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 - '@angular/build@19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.1)': + '@angular/build@19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.2)(tailwindcss@4.1.16)(terser@5.39.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) @@ -8871,7 +9199,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) '@inquirer/confirm': 5.1.6(@types/node@24.6.2) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) beasties: 0.3.2 browserslist: 4.26.3 esbuild: 0.25.4 @@ -8889,7 +9217,7 @@ snapshots: semver: 7.7.1 source-map-support: 0.5.21 typescript: 5.8.3 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) watchpack: 2.4.2 optionalDependencies: '@angular/platform-server': 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) @@ -8911,7 +9239,7 @@ snapshots: - tsx - yaml - '@angular/build@19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.6)(tailwindcss@4.1.16)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)': + '@angular/build@19.2.17(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@types/node@24.6.2)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(ng-packagr@19.2.2(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(tailwindcss@4.1.16)(tslib@2.8.1)(typescript@5.8.3))(postcss@8.5.6)(tailwindcss@4.1.16)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.17(chokidar@4.0.3) @@ -8922,7 +9250,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) '@inquirer/confirm': 5.1.6(@types/node@24.6.2) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) beasties: 0.3.2 browserslist: 4.26.3 esbuild: 0.25.4 @@ -8940,7 +9268,7 @@ snapshots: semver: 7.7.1 source-map-support: 0.5.21 typescript: 5.8.3 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) watchpack: 2.4.2 optionalDependencies: '@angular/platform-server': 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.15(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) @@ -9155,12 +9483,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.6(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1))': + '@astrojs/mdx@4.3.6(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1))': dependencies: '@astrojs/markdown-remark': 6.3.7 '@mdx-js/mdx': 3.1.1 acorn: 8.15.0 - astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -9184,17 +9512,17 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.25.76 - '@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1))': + '@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1))': dependencies: '@astrojs/markdown-remark': 6.3.7 - '@astrojs/mdx': 4.3.6(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/mdx': 4.3.6(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) '@astrojs/sitemap': 3.6.0 '@pagefind/default-ui': 1.4.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) - astro-expressive-code: 0.41.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) + astro-expressive-code: 0.41.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -10643,153 +10971,231 @@ snapshots: '@esbuild/aix-ppc64@0.25.4': optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + '@esbuild/android-arm64@0.25.10': optional: true '@esbuild/android-arm64@0.25.4': optional: true + '@esbuild/android-arm64@0.27.2': + optional: true + '@esbuild/android-arm@0.25.10': optional: true '@esbuild/android-arm@0.25.4': optional: true + '@esbuild/android-arm@0.27.2': + optional: true + '@esbuild/android-x64@0.25.10': optional: true '@esbuild/android-x64@0.25.4': optional: true + '@esbuild/android-x64@0.27.2': + optional: true + '@esbuild/darwin-arm64@0.25.10': optional: true '@esbuild/darwin-arm64@0.25.4': optional: true + '@esbuild/darwin-arm64@0.27.2': + optional: true + '@esbuild/darwin-x64@0.25.10': optional: true '@esbuild/darwin-x64@0.25.4': optional: true + '@esbuild/darwin-x64@0.27.2': + optional: true + '@esbuild/freebsd-arm64@0.25.10': optional: true '@esbuild/freebsd-arm64@0.25.4': optional: true + '@esbuild/freebsd-arm64@0.27.2': + optional: true + '@esbuild/freebsd-x64@0.25.10': optional: true '@esbuild/freebsd-x64@0.25.4': optional: true + '@esbuild/freebsd-x64@0.27.2': + optional: true + '@esbuild/linux-arm64@0.25.10': optional: true '@esbuild/linux-arm64@0.25.4': optional: true + '@esbuild/linux-arm64@0.27.2': + optional: true + '@esbuild/linux-arm@0.25.10': optional: true '@esbuild/linux-arm@0.25.4': optional: true + '@esbuild/linux-arm@0.27.2': + optional: true + '@esbuild/linux-ia32@0.25.10': optional: true '@esbuild/linux-ia32@0.25.4': optional: true + '@esbuild/linux-ia32@0.27.2': + optional: true + '@esbuild/linux-loong64@0.25.10': optional: true '@esbuild/linux-loong64@0.25.4': optional: true + '@esbuild/linux-loong64@0.27.2': + optional: true + '@esbuild/linux-mips64el@0.25.10': optional: true '@esbuild/linux-mips64el@0.25.4': optional: true + '@esbuild/linux-mips64el@0.27.2': + optional: true + '@esbuild/linux-ppc64@0.25.10': optional: true '@esbuild/linux-ppc64@0.25.4': optional: true + '@esbuild/linux-ppc64@0.27.2': + optional: true + '@esbuild/linux-riscv64@0.25.10': optional: true '@esbuild/linux-riscv64@0.25.4': optional: true + '@esbuild/linux-riscv64@0.27.2': + optional: true + '@esbuild/linux-s390x@0.25.10': optional: true '@esbuild/linux-s390x@0.25.4': optional: true + '@esbuild/linux-s390x@0.27.2': + optional: true + '@esbuild/linux-x64@0.25.10': optional: true '@esbuild/linux-x64@0.25.4': optional: true + '@esbuild/linux-x64@0.27.2': + optional: true + '@esbuild/netbsd-arm64@0.25.10': optional: true '@esbuild/netbsd-arm64@0.25.4': optional: true + '@esbuild/netbsd-arm64@0.27.2': + optional: true + '@esbuild/netbsd-x64@0.25.10': optional: true '@esbuild/netbsd-x64@0.25.4': optional: true + '@esbuild/netbsd-x64@0.27.2': + optional: true + '@esbuild/openbsd-arm64@0.25.10': optional: true '@esbuild/openbsd-arm64@0.25.4': optional: true + '@esbuild/openbsd-arm64@0.27.2': + optional: true + '@esbuild/openbsd-x64@0.25.10': optional: true '@esbuild/openbsd-x64@0.25.4': optional: true + '@esbuild/openbsd-x64@0.27.2': + optional: true + '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/openharmony-arm64@0.27.2': + optional: true + '@esbuild/sunos-x64@0.25.10': optional: true '@esbuild/sunos-x64@0.25.4': optional: true + '@esbuild/sunos-x64@0.27.2': + optional: true + '@esbuild/win32-arm64@0.25.10': optional: true '@esbuild/win32-arm64@0.25.4': optional: true + '@esbuild/win32-arm64@0.27.2': + optional: true + '@esbuild/win32-ia32@0.25.10': optional: true '@esbuild/win32-ia32@0.25.4': optional: true + '@esbuild/win32-ia32@0.27.2': + optional: true + '@esbuild/win32-x64@0.25.10': optional: true '@esbuild/win32-x64@0.25.4': optional: true + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.1))': dependencies: eslint: 9.36.0(jiti@2.6.1) @@ -11309,6 +11715,12 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@modelcontextprotocol/sdk@0.5.0': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.2 + zod: 3.25.76 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -11563,7 +11975,7 @@ snapshots: '@nx/nx-win32-x64-msvc@20.8.2': optional: true - '@nx/vite@20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))': + '@nx/vite@20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@nx/devkit': 20.8.2(nx@20.8.2) '@nx/js': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2) @@ -11573,8 +11985,8 @@ snapshots: minimatch: 9.0.3 semver: 7.7.2 tsconfig-paths: 4.2.0 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -11586,7 +11998,7 @@ snapshots: - verdaccio optional: true - '@nx/vite@20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@nx/vite@20.8.2(@babel/traverse@7.28.4)(nx@20.8.2)(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@nx/devkit': 20.8.2(nx@20.8.2) '@nx/js': 20.8.2(@babel/traverse@7.28.4)(nx@20.8.2) @@ -11596,8 +12008,8 @@ snapshots: minimatch: 9.0.3 semver: 7.7.2 tsconfig-paths: 4.2.0 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -12061,6 +12473,8 @@ snapshots: '@speed-highlight/core@1.2.7': {} + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -12126,12 +12540,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16 '@tailwindcss/oxide-win32-x64-msvc': 4.1.16 - '@tailwindcss/vite@4.1.16(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@tailwindcss/vite@4.1.16(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.16 '@tailwindcss/oxide': 4.1.16 tailwindcss: 4.1.16 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) '@ts-morph/common@0.22.0': dependencies: @@ -12156,11 +12570,11 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/bonjour@3.5.13': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/chai@5.2.2': dependencies: @@ -12169,11 +12583,11 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.6 - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/connect@3.4.38': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/debug@4.1.12': dependencies: @@ -12201,7 +12615,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 0.17.5 @@ -12215,7 +12629,7 @@ snapshots: '@types/fontkit@2.0.8': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/hast@3.0.4': dependencies: @@ -12225,7 +12639,7 @@ snapshots: '@types/http-proxy@1.17.16': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/js-yaml@4.0.9': {} @@ -12247,13 +12661,18 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/node@17.0.45': {} + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@24.6.2': dependencies: undici-types: 7.13.0 + optional: true '@types/parse-json@4.0.2': {} @@ -12267,12 +12686,12 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 17.0.45 + '@types/node': 18.19.130 '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/serve-index@1.9.4': dependencies: @@ -12281,12 +12700,12 @@ snapshots: '@types/serve-static@1.15.8': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/send': 0.17.5 '@types/sockjs@0.3.36': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@types/unist@2.0.11': {} @@ -12294,7 +12713,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 '@typescript-eslint/eslint-plugin@8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.8.3)': dependencies: @@ -12458,15 +12877,15 @@ snapshots: - rollup - supports-color - '@vitejs/plugin-basic-ssl@1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) - '@vitejs/plugin-basic-ssl@1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.4.3 @@ -12478,11 +12897,11 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -12497,7 +12916,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -12509,48 +12928,87 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1))': + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.2 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + + '@vitest/mocker@4.0.18(vite@6.3.6(@types/node@18.19.130)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.3.6(@types/node@18.19.130)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.19 pathe: 2.0.3 + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/spy@4.0.18': {} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + '@volar/kit@2.4.23(typescript@5.8.3)': dependencies: '@volar/language-service': 2.4.23 @@ -12688,9 +13146,9 @@ snapshots: js-yaml: 3.14.1 tslib: 2.8.1 - '@yeskunall/astro-umami@0.0.7(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1))': + '@yeskunall/astro-umami@0.0.7(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1))': dependencies: - astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) '@zkochan/js-yaml@0.0.7': dependencies: @@ -12885,12 +13343,12 @@ snapshots: transitivePeerDependencies: - supports-color - astro-expressive-code@0.41.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)): + astro-expressive-code@0.41.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)): dependencies: - astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) rehype-expressive-code: 0.41.3 - astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1): + astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.13.0 '@astrojs/internal-helpers': 0.7.3 @@ -12946,8 +13404,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.17.1(db0@0.3.4)(ioredis@5.8.0) vfile: 6.0.3 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -13291,6 +13749,8 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -13873,6 +14333,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.4 '@esbuild/win32-x64': 0.25.4 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -14104,8 +14593,16 @@ snapshots: exsolve@1.0.7: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + extend@3.0.2: {} + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -14300,6 +14797,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.13.1: + dependencies: + resolve-pkg-maps: 1.0.0 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -14366,6 +14867,13 @@ snapshots: graphemer@1.4.0: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -14642,6 +15150,14 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-parser-js@0.5.10: {} http-proxy-agent@7.0.2: @@ -14804,6 +15320,8 @@ snapshots: is-docker@3.0.0: {} + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -14934,7 +15452,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 24.6.2 + '@types/node': 18.19.130 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -15266,6 +15784,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: dependencies: '@babel/parser': 7.28.4 @@ -16282,6 +16804,8 @@ snapshots: obuf@1.1.2: {} + obug@2.1.1: {} + ofetch@1.4.1: dependencies: destr: 2.0.5 @@ -16699,6 +17223,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -16722,6 +17248,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -16948,6 +17481,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve-url-loader@5.0.0: dependencies: adjust-sourcemap-loader: 4.0.0 @@ -17137,6 +17672,11 @@ snapshots: scule@1.3.0: {} + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + select-hose@2.0.0: {} selfsigned@2.4.1: @@ -17447,14 +17987,14 @@ snapshots: standard-as-callback@2.1.0: {} - starlight-auto-sidebar@0.1.2(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1))): + starlight-auto-sidebar@0.1.2(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1))): dependencies: - '@astrojs/starlight': 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/starlight': 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) github-slugger: 2.0.0 - starlight-typedoc@0.21.3(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.9.0(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3)): + starlight-typedoc@0.21.3(@astrojs/starlight@0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.9.0(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3)): dependencies: - '@astrojs/starlight': 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/starlight': 0.35.3(astro@5.14.1(@types/node@24.6.2)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.8.0)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.52.3)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1)) github-slugger: 2.0.0 typedoc: 0.28.13(typescript@5.8.3) typedoc-plugin-markdown: 4.9.0(typedoc@0.28.13(typescript@5.8.3)) @@ -17465,6 +18005,8 @@ snapshots: statuses@2.0.2: {} + std-env@3.10.0: {} + std-env@3.9.0: {} stream-replace-string@2.0.0: {} @@ -17518,6 +18060,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom-string@1.0.0: {} + strip-bom@3.0.0: {} strip-final-newline@3.0.0: {} @@ -17650,6 +18194,8 @@ snapshots: tinyexec@1.0.1: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -17659,6 +18205,8 @@ snapshots: tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@4.0.4: {} tldts-core@6.1.86: {} @@ -17716,6 +18264,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.1 + optionalDependencies: + fsevents: 2.3.3 + tuf-js@3.1.0: dependencies: '@tufjs/models': 3.0.1 @@ -17817,7 +18372,10 @@ snapshots: magic-string: 0.30.19 unplugin: 2.3.10 - undici-types@7.13.0: {} + undici-types@5.26.5: {} + + undici-types@7.13.0: + optional: true unenv@2.0.0-rc.21: dependencies: @@ -18036,13 +18594,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1): + vite-node@3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -18057,13 +18615,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vite-node@3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -18078,18 +18636,37 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.8.3) optionalDependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1): + vite@6.3.6(@types/node@18.19.130)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 18.19.130 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.4.1 + lightningcss: 1.30.2 + sass: 1.93.2 + terser: 5.44.0 + tsx: 4.21.0 + yaml: 2.8.1 + + vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -18105,9 +18682,10 @@ snapshots: lightningcss: 1.30.2 sass: 1.85.0 terser: 5.39.0 + tsx: 4.21.0 yaml: 2.8.1 - vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.44.0)(yaml@2.8.1): + vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -18123,10 +18701,11 @@ snapshots: lightningcss: 1.30.2 sass: 1.85.0 terser: 5.44.0 + tsx: 4.21.0 yaml: 2.8.1 optional: true - vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -18142,21 +18721,22 @@ snapshots: lightningcss: 1.30.2 sass: 1.93.2 terser: 5.44.0 + tsx: 4.21.0 yaml: 2.8.1 - vitefu@1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)): + vitefu@1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)): optionalDependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) - vitefu@1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)): + vitefu@1.1.1(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)): optionalDependencies: - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -18174,8 +18754,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.2.2)(lightningcss@1.30.2)(sass@1.85.0)(terser@5.39.0)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -18195,11 +18775,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -18217,8 +18797,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 6.3.6(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.6.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -18238,6 +18818,44 @@ snapshots: - tsx - yaml + vitest@4.0.18(@types/node@18.19.130)(jiti@2.6.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@6.3.6(@types/node@18.19.130)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.3.6(@types/node@18.19.130)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 18.19.130 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + volar-service-css@0.0.62(@volar/language-service@2.4.23): dependencies: vscode-css-languageservice: 6.3.8 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e9b0dad63..f3768ac70 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - 'apps/*' - 'packages/*' + - 'tools/*' diff --git a/tools/mcp-server/.gitignore b/tools/mcp-server/.gitignore new file mode 100644 index 000000000..9384fa4e0 --- /dev/null +++ b/tools/mcp-server/.gitignore @@ -0,0 +1,26 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ +*.tsbuildinfo + +# Test coverage +coverage/ + +# Environment files +.env +.env.local + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +pnpm-debug.log* diff --git a/tools/mcp-server/README.md b/tools/mcp-server/README.md new file mode 100644 index 000000000..a05c1edd0 --- /dev/null +++ b/tools/mcp-server/README.md @@ -0,0 +1,242 @@ +# ng-diagram MCP Server + +> **MCP server that enables AI assistants to search ng-diagram documentation** + +An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that provides intelligent documentation search for the ng-diagram library. Connect it to AI assistants like Claude, Cursor, or any MCP-compatible tool to get instant access to ng-diagram documentation. + +## What is This? + +This server allows AI assistants to search through ng-diagram's documentation and return relevant results with direct links. Instead of manually browsing docs, you can ask your AI assistant questions like: + +- "How do I create custom nodes in ng-diagram?" +- "Show me examples of node rotation" +- "How does the palette component work?" + +The AI will search the documentation and provide you with relevant pages and direct links. + +## How It Works + +```mermaid +graph LR + A[AI Assistant] -->|Search Query| B[MCP Server] + B -->|Indexes| C[ng-diagram Docs] + B -->|Returns Results| A + A -->|Provides Links| D[User] + D -->|Clicks Link| E[Official Docs] +``` + +**Flow:** + +1. AI assistant sends a search query to the MCP server +2. Server searches indexed ng-diagram documentation +3. Returns relevant results with titles, descriptions, and URLs +4. AI provides you with clickable links to official documentation + +## Current Usage (Internal) + +**Who can use it now:** ng-diagram maintainers and contributors + +This server is currently configured to run locally for the ng-diagram development team. It indexes the documentation from the monorepo and provides search capabilities during development. + +### Setup for Development + +1. **Install dependencies:** + + ```bash + cd tools/mcp-server + pnpm install + ``` + +2. **Build the server:** + + ```bash + pnpm build + ``` + +3. **Configure your MCP client** (e.g., Claude Desktop, Cursor, Kiro): + + Add to your MCP configuration file: + + ```json + { + "mcpServers": { + "ng-diagram-docs": { + "command": "node", + "args": ["/absolute/path/to/ng-diagram/tools/mcp-server/dist/index.js"], + "cwd": "/absolute/path/to/ng-diagram" + } + } + } + ``` + +4. **Restart your AI assistant** to load the server + +5. **Test it:** Ask your AI assistant to search ng-diagram documentation! + +## Future Vision (Public Release) + +### For Library Consumers + +In the future, ng-diagram users will be able to install and use this MCP server without cloning the repository: + +```bash +# Future: Install via npm +npm install -g @ng-diagram/mcp-server + +# Or use with npx +npx @ng-diagram/mcp-server +``` + +Then configure it in your AI assistant to get instant documentation access while building your Angular diagrams. + +## Roadmap + +### Phase 1: MVP (Current) ✅ + +- [x] Basic documentation search +- [x] Multi-word query support +- [x] Full URL generation to official docs +- [x] Integration with MCP-compatible tools + +### Phase 2: Enhanced Search + +- [ ] Synonym support (e.g., "setup" → "installation") +- [ ] Better ranking with TF-IDF +- [ ] Search analytics to improve results +- [ ] Fuzzy matching for typos + +### Phase 3: Public Distribution + +- [ ] Publish to npm as standalone package +- [ ] Configuration options for custom doc sites +- [ ] Support for multiple documentation versions +- [ ] HTTP transport option (in addition to stdio) + +### Phase 4: Advanced Features + +- [ ] Semantic search with embeddings (RAG) +- [ ] Code example extraction +- [ ] Interactive API explorer +- [ ] Integration with GitHub Copilot + +## API Reference + +### Tool: `search_docs` + +Search through ng-diagram documentation. + +**Parameters:** + +- `query` (string, required): Search query +- `limit` (number, optional): Max results to return (default: 10) + +**Response:** + +```typescript +{ + results: Array<{ + title: string; // Document title + description?: string; // Document description + excerpt: string; // Relevant text snippet + url: string; // Full URL to documentation + }>; +} +``` + +**Example:** + +```json +{ + "query": "custom nodes", + "limit": 3 +} +``` + +Returns: + +```json +{ + "results": [ + { + "title": "Custom Nodes", + "description": "How to create and implement custom nodes in ngDiagram", + "excerpt": "...create custom node components with any Angular template...", + "url": "https://www.ngdiagram.dev/docs/guides/nodes/custom-nodes" + } + ] +} +``` + +## Development + +### Project Structure + +``` +tools/mcp-server/ +├── src/ +│ ├── services/ # Core business logic +│ │ ├── indexer.ts # Documentation indexing +│ │ └── search.ts # Search engine +│ ├── tools/ # MCP tool implementations +│ │ └── search-docs/ # Search tool handler +│ ├── types/ # TypeScript definitions +│ ├── server.ts # MCP server +│ └── index.ts # Entry point +├── tests/ # Test files +└── dist/ # Build output +``` + +### Commands + +```bash +# Development +pnpm dev # Run with auto-reload + +# Testing +pnpm test # Run all tests +pnpm test:coverage # Run with coverage + +# Building +pnpm build # Compile TypeScript +``` + +### Architecture + +```mermaid +graph TD + A[MCP Client] -->|stdio| B[MCP Server] + B --> C[Documentation Indexer] + B --> D[Search Engine] + C -->|Scans| E[Markdown Files] + C -->|Extracts| F[Frontmatter] + C -->|Builds| G[In-Memory Index] + D -->|Queries| G + D -->|Ranks| H[Search Results] + H -->|Returns| B +``` + +**Components:** + +- **MCP Server**: Handles protocol communication via stdio +- **Documentation Indexer**: Scans `.md`/`.mdx` files, extracts metadata +- **Search Engine**: Multi-tier matching (exact phrase → multi-word → single word) +- **Tool Handler**: Validates input, formats output + +## Contributing + +This is part of the ng-diagram monorepo. Contributions are welcome! + +1. Make changes in `tools/mcp-server/` +2. Run tests: `pnpm test` +3. Build: `pnpm build` +4. Test with your AI assistant + +## License + +PoC implemented by [Pawel Kubiak](https://pawelkubiak.dev/about) + +MIT - Part of the [ng-diagram](https://github.com/synergycodes/ng-diagram) project + +--- + +**Built with ❤️ by the Synergy Codes team** diff --git a/tools/mcp-server/package.json b/tools/mcp-server/package.json new file mode 100644 index 000000000..ae961958d --- /dev/null +++ b/tools/mcp-server/package.json @@ -0,0 +1,33 @@ +{ + "name": "@ng-diagram/mcp-server", + "version": "0.1.0", + "description": "MCP server for ng-diagram documentation search", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "dev": "tsx src/index.ts", + "build": "tsc", + "test": "vitest --run", + "test:watch": "vitest" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "documentation", + "search" + ], + "author": "Paweł Kubiak", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "gray-matter": "^4.0.3" + }, + "devDependencies": { + "@types/node": "^18.0.0", + "typescript": "^5.8.3", + "vitest": "latest", + "fast-check": "^3.0.0", + "tsx": "^4.0.0" + } +} diff --git a/tools/mcp-server/src/index.ts b/tools/mcp-server/src/index.ts new file mode 100644 index 000000000..597a07cc1 --- /dev/null +++ b/tools/mcp-server/src/index.ts @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +/** + * Entry point for the ng-diagram MCP server + * Initializes and starts the MCP server with documentation search capabilities + */ + +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; +import { NgDiagramMCPServer } from './server.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Main function to start the MCP server + */ +async function main(): Promise { + try { + // Resolve documentation path relative to the repository root + // From tools/mcp-server/src -> ../../../apps/docs/src/content/docs + const docsPath = resolve(__dirname, '../../../apps/docs/src/content/docs'); + + const server = new NgDiagramMCPServer({ + name: 'ng-diagram-docs', + version: '0.1.0', + docsPath, + baseUrl: 'https://www.ngdiagram.dev', + }); + + await server.start(); + } catch (error) { + console.error('[MCP Server] Fatal error during startup:'); + if (error instanceof Error) { + console.error(` Error: ${error.message}`); + if (error.stack) { + console.error(` Stack: ${error.stack}`); + } + } else { + console.error(` Unknown error: ${error}`); + } + + process.exit(1); + } +} + +main(); diff --git a/tools/mcp-server/src/server.ts b/tools/mcp-server/src/server.ts new file mode 100644 index 000000000..aca5f2987 --- /dev/null +++ b/tools/mcp-server/src/server.ts @@ -0,0 +1,155 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + type CallToolRequest, + type ListToolsRequest, +} from '@modelcontextprotocol/sdk/types.js'; +import { DocumentationIndexer } from './services/indexer.js'; +import { SearchEngine } from './services/search.js'; +import { SEARCH_DOCS_TOOL, createSearchDocsHandler, type SearchDocsInput } from './tools/search-docs/index.js'; +import type { MCPServerConfig } from './types/index.js'; + +export class NgDiagramMCPServer { + private config: MCPServerConfig; + private server: Server; + private indexer: DocumentationIndexer; + private searchEngine: SearchEngine | null = null; + private isRunning = false; + + constructor(config: MCPServerConfig) { + this.config = config; + + this.server = new Server( + { + name: config.name, + version: config.version, + }, + { + capabilities: { + tools: {}, + }, + } + ); + + this.indexer = new DocumentationIndexer({ + docsPath: config.docsPath, + extensions: ['.md', '.mdx'], + baseUrl: config.baseUrl, + }); + + this.server.onerror = (error) => { + console.error('[MCP Server Error]:', error); + }; + + process.on('SIGINT', () => { + this.shutdown(); + }); + + process.on('SIGTERM', () => { + this.shutdown(); + }); + } + + /** + * Starts the MCP server + * Initializes the documentation index and starts listening for requests + */ + async start(): Promise { + try { + console.log(`[MCP Server] Starting ${this.config.name} v${this.config.version}...`); + + // Build documentation index + console.log(`[MCP Server] Indexing documentation from: ${this.config.docsPath}`); + const documents = await this.indexer.buildIndex(); + console.log(`[MCP Server] Indexed ${documents.length} documents`); + + // Initialize search engine + this.searchEngine = new SearchEngine(documents); + + // Register tools + this.registerTools(); + + // Start server with stdio transport + const transport = new StdioServerTransport(); + await this.server.connect(transport); + + this.isRunning = true; + console.log('[MCP Server] Server started successfully'); + } catch (error) { + console.error('[MCP Server] Failed to start server:', error instanceof Error ? error.message : error); + throw error; + } + } + + private registerTools(): void { + if (!this.searchEngine) { + throw new Error('Search engine not initialized. Call start() first.'); + } + + const searchHandler = createSearchDocsHandler(this.searchEngine); + + this.server.setRequestHandler(ListToolsRequestSchema, async (_request: ListToolsRequest) => { + return { + tools: [SEARCH_DOCS_TOOL], + }; + }); + + this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { + const { name, arguments: args } = request.params; + + if (name === 'search_docs') { + try { + const result = await searchHandler(args as unknown as SearchDocsInput); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + error: error instanceof Error ? error.message : 'Unknown error occurred', + }, + null, + 2 + ), + }, + ], + isError: true, + }; + } + } + + throw new Error(`Unknown tool: ${name}`); + }); + + console.log('[MCP Server] Registered tool: search_docs'); + } + + private shutdown(): void { + if (!this.isRunning) { + return; + } + + console.log('[MCP Server] Shutting down...'); + this.isRunning = false; + + this.server.close(); + + console.log('[MCP Server] Server stopped'); + process.exit(0); + } + + isServerRunning(): boolean { + return this.isRunning; + } +} diff --git a/tools/mcp-server/src/services/indexer.ts b/tools/mcp-server/src/services/indexer.ts new file mode 100644 index 000000000..9e4bfe94d --- /dev/null +++ b/tools/mcp-server/src/services/indexer.ts @@ -0,0 +1,153 @@ +import { readdir, readFile } from 'fs/promises'; +import matter from 'gray-matter'; +import { basename, extname, join, relative } from 'path'; +import type { DocumentMetadata, IndexerConfig } from '../types/index.js'; + +export class DocumentationIndexer { + private config: IndexerConfig; + + constructor(config: IndexerConfig) { + this.config = config; + } + + /** + * Build the documentation index by scanning and processing all files + * @returns Array of indexed document metadata + */ + async buildIndex(): Promise { + try { + const filePaths = await this.scanDirectory(this.config.docsPath); + const documents: DocumentMetadata[] = []; + + for (const filePath of filePaths) { + try { + const doc = await this.processFile(filePath); + if (doc) { + documents.push(doc); + } + } catch (error) { + console.warn(`Failed to process file ${filePath}:`, error instanceof Error ? error.message : error); + } + } + + return documents; + } catch (error) { + console.error( + `Failed to build index from ${this.config.docsPath}:`, + error instanceof Error ? error.message : error + ); + return []; + } + } + + /** + * Recursively scan directory for documentation files + * @param dir Directory to scan + * @returns Array of file paths matching configured extensions + */ + private async scanDirectory(dir: string): Promise { + const files: string[] = []; + + try { + const entries = await readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = join(dir, entry.name); + + if (entry.isDirectory()) { + const subFiles = await this.scanDirectory(fullPath); + files.push(...subFiles); + } else if (entry.isFile()) { + const ext = extname(entry.name); + if (this.config.extensions.includes(ext)) { + files.push(fullPath); + } + } + } + } catch (error) { + console.warn(`Failed to scan directory ${dir}:`, error instanceof Error ? error.message : error); + } + + return files; + } + + /** + * Process a single documentation file + * @param filePath Absolute path to the file + * @returns Document metadata or null if processing fails + */ + private async processFile(filePath: string): Promise { + try { + const content = await readFile(filePath, 'utf-8'); + const { title, description } = this.extractFrontmatter(content); + const relativePath = relative(this.config.docsPath, filePath); + const url = this.generateUrl(relativePath); + + return { + path: relativePath, + title: title || this.getFilenameAsTitle(filePath), + description, + content, + url, + }; + } catch (error) { + console.warn(`Failed to read file ${filePath}:`, error instanceof Error ? error.message : error); + return null; + } + } + + /** + * Extract frontmatter metadata from file content + * @param content File content + * @returns Extracted title and description + */ + private extractFrontmatter(content: string): { title?: string; description?: string } { + try { + const parsed = matter(content); + return { + title: parsed.data.title, + description: parsed.data.description, + }; + } catch (error) { + console.warn('Failed to parse frontmatter:', error instanceof Error ? error.message : error); + return {}; + } + } + + /** + * Generate documentation URL from file path + * @param filePath Relative file path from docs root + * @returns Full documentation URL + */ + private generateUrl(filePath: string): string { + // Remove file extension + let urlPath = filePath.replace(/\.(md|mdx)$/, ''); + + // Convert backslashes to forward slashes (Windows compatibility) + urlPath = urlPath.replace(/\\/g, '/'); + + // Handle index files + if (urlPath.endsWith('/index') || urlPath === 'index') { + urlPath = urlPath.replace(/\/index$/, '').replace(/^index$/, ''); + } + + // Build full URL with base URL + const path = urlPath ? `/docs/${urlPath}` : '/docs'; + return `${this.config.baseUrl}${path}`; + } + + /** + * Get filename without extension as fallback title + * @param filePath File path + * @returns Filename without extension + */ + private getFilenameAsTitle(filePath: string): string { + const filename = basename(filePath, extname(filePath)); + // Convert kebab-case or snake_case to Title Case + return filename + .replace(/[-_]/g, ' ') + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } +} diff --git a/tools/mcp-server/src/services/search.ts b/tools/mcp-server/src/services/search.ts new file mode 100644 index 000000000..ef6c1b621 --- /dev/null +++ b/tools/mcp-server/src/services/search.ts @@ -0,0 +1,284 @@ +import type { DocumentMetadata, SearchMatch, SearchQuery, SearchResult } from '../types/index.js'; + +const SCORE_WEIGHTS = { + title: 100, + description: 50, + path: 25, + content: 10, +} as const; + +const EXCERPT_CONTEXT_LENGTH = 150; + +export class SearchEngine { + private documents: DocumentMetadata[]; + + constructor(documents: DocumentMetadata[]) { + this.documents = documents; + } + + search(query: SearchQuery): SearchResult[] { + const { query: searchQuery, limit = 10 } = query; + + const matches: SearchMatch[] = []; + for (const doc of this.documents) { + const match = this.matchDocument(doc, searchQuery); + if (match) { + matches.push(match); + } + } + + const rankedMatches = this.rankResults(matches); + + return rankedMatches.slice(0, limit).map((match) => this.toSearchResult(match, searchQuery)); + } + + private matchDocument(doc: DocumentMetadata, query: string): SearchMatch | null { + const lowerQuery = query.toLowerCase(); + const queryWords = lowerQuery.split(/\s+/).filter((word) => word.length > 0); + + // Try exact phrase match first (highest priority) + const exactMatch = this.matchExactPhrase(doc, lowerQuery); + if (exactMatch) { + return exactMatch; + } + + // Try multi-word match (match all words, but not necessarily as a phrase) + if (queryWords.length > 1) { + const multiWordMatch = this.matchMultipleWords(doc, queryWords); + if (multiWordMatch) { + return multiWordMatch; + } + } + + // Try single word match (any word matches) + const singleWordMatch = this.matchAnyWord(doc, queryWords); + if (singleWordMatch) { + return singleWordMatch; + } + + return null; + } + + /** + * Match exact phrase in document + */ + private matchExactPhrase(doc: DocumentMetadata, lowerQuery: string): SearchMatch | null { + if (doc.title.toLowerCase().includes(lowerQuery)) { + return { + document: doc, + score: SCORE_WEIGHTS.title, + matchLocation: 'title', + }; + } + + if (doc.description && doc.description.toLowerCase().includes(lowerQuery)) { + return { + document: doc, + score: SCORE_WEIGHTS.description, + matchLocation: 'description', + }; + } + + if (doc.path.toLowerCase().includes(lowerQuery)) { + return { + document: doc, + score: SCORE_WEIGHTS.path, + matchLocation: 'path', + }; + } + + if (doc.content.toLowerCase().includes(lowerQuery)) { + return { + document: doc, + score: SCORE_WEIGHTS.content, + matchLocation: 'content', + }; + } + + return null; + } + + /** + * Match all query words (but not necessarily as a phrase) + * Scores based on how many words match and where they match + */ + private matchMultipleWords(doc: DocumentMetadata, queryWords: string[]): SearchMatch | null { + const lowerTitle = doc.title.toLowerCase(); + const lowerDescription = doc.description?.toLowerCase() || ''; + const lowerPath = doc.path.toLowerCase(); + const lowerContent = doc.content.toLowerCase(); + + let titleMatches = 0; + let descriptionMatches = 0; + let pathMatches = 0; + let contentMatches = 0; + + for (const word of queryWords) { + if (lowerTitle.includes(word)) titleMatches++; + if (lowerDescription.includes(word)) descriptionMatches++; + if (lowerPath.includes(word)) pathMatches++; + if (lowerContent.includes(word)) contentMatches++; + } + + const totalWords = queryWords.length; + + // Require at least 50% of words to match + const minMatches = Math.ceil(totalWords * 0.5); + + if (titleMatches >= minMatches) { + const matchRatio = titleMatches / totalWords; + return { + document: doc, + score: SCORE_WEIGHTS.title * matchRatio * 0.8, // 80% of exact match score + matchLocation: 'title', + }; + } + + if (descriptionMatches >= minMatches) { + const matchRatio = descriptionMatches / totalWords; + return { + document: doc, + score: SCORE_WEIGHTS.description * matchRatio * 0.8, + matchLocation: 'description', + }; + } + + if (pathMatches >= minMatches) { + const matchRatio = pathMatches / totalWords; + return { + document: doc, + score: SCORE_WEIGHTS.path * matchRatio * 0.8, + matchLocation: 'path', + }; + } + + if (contentMatches >= minMatches) { + const matchRatio = contentMatches / totalWords; + return { + document: doc, + score: SCORE_WEIGHTS.content * matchRatio * 0.8, + matchLocation: 'content', + }; + } + + return null; + } + + /** + * Match any single word from the query + * Lowest priority, but ensures we return something relevant + */ + private matchAnyWord(doc: DocumentMetadata, queryWords: string[]): SearchMatch | null { + const lowerTitle = doc.title.toLowerCase(); + const lowerDescription = doc.description?.toLowerCase() || ''; + const lowerPath = doc.path.toLowerCase(); + const lowerContent = doc.content.toLowerCase(); + + for (const word of queryWords) { + // Skip very short words (less than 3 characters) to avoid noise + if (word.length < 3) continue; + + if (lowerTitle.includes(word)) { + return { + document: doc, + score: SCORE_WEIGHTS.title * 0.5, // 50% of exact match score + matchLocation: 'title', + }; + } + + if (lowerDescription.includes(word)) { + return { + document: doc, + score: SCORE_WEIGHTS.description * 0.5, + matchLocation: 'description', + }; + } + + if (lowerPath.includes(word)) { + return { + document: doc, + score: SCORE_WEIGHTS.path * 0.5, + matchLocation: 'path', + }; + } + + if (lowerContent.includes(word)) { + return { + document: doc, + score: SCORE_WEIGHTS.content * 0.5, + matchLocation: 'content', + }; + } + } + + return null; + } + + private rankResults(matches: SearchMatch[]): SearchMatch[] { + return matches.sort((a, b) => { + if (a.score !== b.score) { + return b.score - a.score; + } + return a.document.title.localeCompare(b.document.title); + }); + } + + private extractExcerpt(content: string, query: string, contextLength: number): string { + const lowerContent = content.toLowerCase(); + const lowerQuery = query.toLowerCase(); + + // Try to find the exact query first + let matchIndex = lowerContent.indexOf(lowerQuery); + + // If exact query not found, try to find the first word from the query + if (matchIndex === -1) { + const queryWords = lowerQuery.split(/\s+/).filter((word) => word.length > 2); + for (const word of queryWords) { + matchIndex = lowerContent.indexOf(word); + if (matchIndex !== -1) break; + } + } + + if (matchIndex === -1) { + return ''; + } + + const startIndex = Math.max(0, matchIndex - contextLength); + const endIndex = Math.min(content.length, matchIndex + query.length + contextLength); + + let excerpt = content.substring(startIndex, endIndex); + + if (startIndex > 0) { + const firstSpace = excerpt.indexOf(' '); + if (firstSpace !== -1) { + excerpt = excerpt.substring(firstSpace + 1); + } + } + + if (endIndex < content.length) { + const lastSpace = excerpt.lastIndexOf(' '); + if (lastSpace !== -1) { + excerpt = excerpt.substring(0, lastSpace); + } + } + + const prefix = startIndex > 0 ? '...' : ''; + const suffix = endIndex < content.length ? '...' : ''; + + return `${prefix}${excerpt.trim()}${suffix}`; + } + + private toSearchResult(match: SearchMatch, query: string): SearchResult { + const { document, matchLocation } = match; + + const excerpt = + matchLocation === 'content' ? this.extractExcerpt(document.content, query, EXCERPT_CONTEXT_LENGTH) : ''; + + return { + title: document.title, + description: document.description, + excerpt, + url: document.url, + }; + } +} diff --git a/tools/mcp-server/src/tools/search-docs/handler.ts b/tools/mcp-server/src/tools/search-docs/handler.ts new file mode 100644 index 000000000..ebcd0782f --- /dev/null +++ b/tools/mcp-server/src/tools/search-docs/handler.ts @@ -0,0 +1,26 @@ +import type { SearchEngine } from '../../services/search.js'; +import type { SearchQuery } from '../../types/index.js'; +import type { SearchDocsInput, SearchDocsOutput } from './tool.types.js'; +import { validateInput } from './tool.validator.js'; + +export function createSearchDocsHandler(searchEngine: SearchEngine) { + return async (input: SearchDocsInput): Promise => { + try { + validateInput(input); + + const searchQuery: SearchQuery = { + query: input.query.trim(), + limit: input.limit ?? 10, + }; + + return { + results: searchEngine.search(searchQuery), + }; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Search failed: ${error.message}`); + } + throw new Error('Search failed: Unknown error occurred'); + } + }; +} diff --git a/tools/mcp-server/src/tools/search-docs/index.ts b/tools/mcp-server/src/tools/search-docs/index.ts new file mode 100644 index 000000000..d2170039d --- /dev/null +++ b/tools/mcp-server/src/tools/search-docs/index.ts @@ -0,0 +1,8 @@ +/** + * Search docs tool exports + */ + +export { createSearchDocsHandler } from './handler.js'; +export { SEARCH_DOCS_TOOL } from './tool.config.js'; +export type { SearchDocsInput, SearchDocsOutput } from './tool.types.js'; +export { validateInput } from './tool.validator.js'; diff --git a/tools/mcp-server/src/tools/search-docs/tool.config.ts b/tools/mcp-server/src/tools/search-docs/tool.config.ts new file mode 100644 index 000000000..601662cb5 --- /dev/null +++ b/tools/mcp-server/src/tools/search-docs/tool.config.ts @@ -0,0 +1,21 @@ +export const SEARCH_DOCS_TOOL = { + name: 'search_docs', + description: + 'Search through ng-diagram documentation. Supports exact phrases, multi-word queries, and individual keywords. Best results with specific terms like "palette", "node rotation", "custom edge", "quick start", etc.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: + 'Search query to find relevant documentation. Use specific keywords for best results (e.g., "palette", "rotation", "edges"). Multi-word queries will match documents containing most of the words.', + }, + limit: { + type: 'number', + description: 'Maximum number of results to return (default: 10)', + default: 10, + }, + }, + required: ['query'], + }, +} as const; diff --git a/tools/mcp-server/src/tools/search-docs/tool.types.ts b/tools/mcp-server/src/tools/search-docs/tool.types.ts new file mode 100644 index 000000000..fe28d31a0 --- /dev/null +++ b/tools/mcp-server/src/tools/search-docs/tool.types.ts @@ -0,0 +1,19 @@ +import type { SearchResult } from '../../types/index.js'; + +/** + * Input schema for the search_docs tool + */ +export interface SearchDocsInput { + /** Search query string */ + query: string; + /** Maximum number of results to return (default: 10) */ + limit?: number; +} + +/** + * Output schema for the search_docs tool + */ +export interface SearchDocsOutput { + /** Array of search results */ + results: SearchResult[]; +} diff --git a/tools/mcp-server/src/tools/search-docs/tool.validator.ts b/tools/mcp-server/src/tools/search-docs/tool.validator.ts new file mode 100644 index 000000000..6fc074271 --- /dev/null +++ b/tools/mcp-server/src/tools/search-docs/tool.validator.ts @@ -0,0 +1,15 @@ +import type { SearchDocsInput } from './tool.types.js'; + +export function validateInput(input: SearchDocsInput): void { + if (!input.query) { + throw new Error('Query parameter is required'); + } + + if (input.query.trim().length === 0) { + throw new Error('Query parameter cannot be empty'); + } + + if (typeof input.limit === 'string' || Number(input.limit) < 0) { + throw new Error('Limit parameter must be a non-negative number'); + } +} diff --git a/tools/mcp-server/src/types/config.types.ts b/tools/mcp-server/src/types/config.types.ts new file mode 100644 index 000000000..d1d135584 --- /dev/null +++ b/tools/mcp-server/src/types/config.types.ts @@ -0,0 +1,29 @@ +/** + * Configuration-related type definitions + */ + +/** + * Configuration for the documentation indexer + */ +export interface IndexerConfig { + /** Path to the documentation directory */ + docsPath: string; + /** File extensions to index (e.g., ['.md', '.mdx']) */ + extensions: string[]; + /** Base URL for the documentation site (e.g., 'https://www.ngdiagram.dev') */ + baseUrl: string; +} + +/** + * Configuration for the MCP server + */ +export interface MCPServerConfig { + /** Server name */ + name: string; + /** Server version */ + version: string; + /** Path to the documentation directory */ + docsPath: string; + /** Base URL for the documentation site */ + baseUrl: string; +} diff --git a/tools/mcp-server/src/types/document.types.ts b/tools/mcp-server/src/types/document.types.ts new file mode 100644 index 000000000..b629179a2 --- /dev/null +++ b/tools/mcp-server/src/types/document.types.ts @@ -0,0 +1,19 @@ +/** + * Document-related type definitions + */ + +/** + * Metadata for a single documentation file + */ +export interface DocumentMetadata { + /** Relative path from docs root (e.g., "guides/palette.mdx") */ + path: string; + /** Document title from frontmatter or filename */ + title: string; + /** Document description from frontmatter (optional) */ + description?: string; + /** Full text content of the document */ + content: string; + /** Documentation URL path (e.g., "/docs/guides/palette") */ + url: string; +} diff --git a/tools/mcp-server/src/types/index.ts b/tools/mcp-server/src/types/index.ts new file mode 100644 index 000000000..df95e4174 --- /dev/null +++ b/tools/mcp-server/src/types/index.ts @@ -0,0 +1,7 @@ +/** + * Central export point for all type definitions + */ + +export type { IndexerConfig, MCPServerConfig } from './config.types.js'; +export type { DocumentMetadata } from './document.types.js'; +export type { SearchMatch, SearchQuery, SearchResult } from './search.types.js'; diff --git a/tools/mcp-server/src/types/search.types.ts b/tools/mcp-server/src/types/search.types.ts new file mode 100644 index 000000000..45e6ea4ef --- /dev/null +++ b/tools/mcp-server/src/types/search.types.ts @@ -0,0 +1,41 @@ +/** + * Search-related type definitions + */ + +import type { DocumentMetadata } from './document.types.js'; + +/** + * Search query parameters + */ +export interface SearchQuery { + /** Search query string */ + query: string; + /** Maximum number of results to return (default: 10) */ + limit?: number; +} + +/** + * Search result returned to the user + */ +export interface SearchResult { + /** Document title */ + title: string; + /** Document description (if available) */ + description?: string; + /** Text snippet showing match context */ + excerpt: string; + /** Full documentation URL */ + url: string; +} + +/** + * Internal search match with scoring information + */ +export interface SearchMatch { + /** The matched document */ + document: DocumentMetadata; + /** Relevance score for ranking */ + score: number; + /** Location where the match was found */ + matchLocation: 'title' | 'description' | 'content' | 'path'; +} diff --git a/tools/mcp-server/tests/indexer.test.ts b/tools/mcp-server/tests/indexer.test.ts new file mode 100644 index 000000000..08fc7813f --- /dev/null +++ b/tools/mcp-server/tests/indexer.test.ts @@ -0,0 +1,359 @@ +/** + * Unit tests for DocumentationIndexer + */ + +import { mkdir, rm, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { DocumentationIndexer } from '../src/services/indexer.js'; +import type { IndexerConfig } from '../src/types/index.js'; + +describe('DocumentationIndexer', () => { + const testDir = join(process.cwd(), 'tests', 'fixtures', 'test-docs'); + let indexer: DocumentationIndexer; + + beforeEach(async () => { + // Create test directory structure + await mkdir(testDir, { recursive: true }); + }); + + afterEach(async () => { + // Clean up test directory + await rm(testDir, { recursive: true, force: true }); + }); + + describe('frontmatter extraction', () => { + it('should extract title and description from valid YAML frontmatter', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + const content = `--- +title: Test Document +description: This is a test description +--- + +# Content here`; + + await writeFile(join(testDir, 'test.md'), content, 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + expect(documents[0].title).toBe('Test Document'); + expect(documents[0].description).toBe('This is a test description'); + }); + + it('should fallback to filename when frontmatter is missing', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + const content = `# Just content without frontmatter`; + + await writeFile(join(testDir, 'my-test-file.md'), content, 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + expect(documents[0].title).toBe('My Test File'); + expect(documents[0].description).toBeUndefined(); + }); + + it('should handle malformed YAML frontmatter gracefully', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + const content = `--- +title: Test Document +description: [invalid yaml: { +--- + +# Content here`; + + await writeFile(join(testDir, 'malformed.md'), content, 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + // Should fallback to filename when frontmatter parsing fails + expect(documents[0].title).toBe('Malformed'); + }); + }); + + describe('URL generation', () => { + it('should generate URL from simple file path', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'guide.md'), '# Guide', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].url).toBe('https://www.ngdiagram.dev/docs/guide'); + }); + + it('should generate URL from nested file path', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + const nestedDir = join(testDir, 'guides', 'advanced'); + await mkdir(nestedDir, { recursive: true }); + await writeFile(join(nestedDir, 'palette.md'), '# Palette', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].url).toBe('https://www.ngdiagram.dev/docs/guides/advanced/palette'); + }); + + it('should handle index.md files correctly', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'index.md'), '# Index', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].url).toBe('https://www.ngdiagram.dev/docs'); + }); + + it('should handle nested index.md files correctly', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + const nestedDir = join(testDir, 'guides'); + await mkdir(nestedDir, { recursive: true }); + await writeFile(join(nestedDir, 'index.md'), '# Guides Index', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].url).toBe('https://www.ngdiagram.dev/docs/guides'); + }); + + it('should handle .mdx extension correctly', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.mdx'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'component.mdx'), '# Component', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].url).toBe('https://www.ngdiagram.dev/docs/component'); + }); + }); + + describe('file extension filtering', () => { + it('should only index .md files when configured', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'doc1.md'), '# Doc 1', 'utf-8'); + await writeFile(join(testDir, 'doc2.mdx'), '# Doc 2', 'utf-8'); + await writeFile(join(testDir, 'doc3.txt'), '# Doc 3', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + expect(documents[0].path).toBe('doc1.md'); + }); + + it('should only index .mdx files when configured', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.mdx'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'doc1.md'), '# Doc 1', 'utf-8'); + await writeFile(join(testDir, 'doc2.mdx'), '# Doc 2', 'utf-8'); + await writeFile(join(testDir, 'doc3.txt'), '# Doc 3', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + expect(documents[0].path).toBe('doc2.mdx'); + }); + + it('should index both .md and .mdx files when configured', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'doc1.md'), '# Doc 1', 'utf-8'); + await writeFile(join(testDir, 'doc2.mdx'), '# Doc 2', 'utf-8'); + await writeFile(join(testDir, 'doc3.txt'), '# Doc 3', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(2); + const paths = documents.map((d) => d.path).sort(); + expect(paths).toEqual(['doc1.md', 'doc2.mdx']); + }); + + it('should not index files with other extensions', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'readme.txt'), '# Readme', 'utf-8'); + await writeFile(join(testDir, 'config.json'), '{}', 'utf-8'); + await writeFile(join(testDir, 'script.js'), 'console.log("test")', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(0); + }); + }); + + describe('error handling', () => { + it('should handle missing documentation directory', async () => { + const config: IndexerConfig = { + docsPath: join(testDir, 'non-existent'), + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md', '.mdx'], + }; + indexer = new DocumentationIndexer(config); + + const documents = await indexer.buildIndex(); + + expect(documents).toEqual([]); + }); + + it('should skip unreadable files and continue indexing', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'good.md'), '# Good', 'utf-8'); + await writeFile(join(testDir, 'also-good.md'), '# Also Good', 'utf-8'); + + const documents = await indexer.buildIndex(); + + // Both files should be indexed successfully + expect(documents).toHaveLength(2); + }); + + it('should handle empty files', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + await writeFile(join(testDir, 'empty.md'), '', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(1); + expect(documents[0].title).toBe('Empty'); + expect(documents[0].content).toBe(''); + }); + }); + + describe('content preservation', () => { + it('should preserve full document content', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + const content = `--- +title: Full Document +--- + +# Heading + +This is a paragraph with **bold** and *italic* text. + +## Subheading + +- List item 1 +- List item 2 + +\`\`\`typescript +const code = 'example'; +\`\`\` +`; + + await writeFile(join(testDir, 'full.md'), content, 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents[0].content).toBe(content); + }); + }); + + describe('recursive directory scanning', () => { + it('should scan nested directories recursively', async () => { + const config: IndexerConfig = { + docsPath: testDir, + baseUrl: 'https://www.ngdiagram.dev', + extensions: ['.md'], + }; + indexer = new DocumentationIndexer(config); + + // Create nested structure + await mkdir(join(testDir, 'level1', 'level2', 'level3'), { recursive: true }); + await writeFile(join(testDir, 'root.md'), '# Root', 'utf-8'); + await writeFile(join(testDir, 'level1', 'doc1.md'), '# Doc 1', 'utf-8'); + await writeFile(join(testDir, 'level1', 'level2', 'doc2.md'), '# Doc 2', 'utf-8'); + await writeFile(join(testDir, 'level1', 'level2', 'level3', 'doc3.md'), '# Doc 3', 'utf-8'); + + const documents = await indexer.buildIndex(); + + expect(documents).toHaveLength(4); + const paths = documents.map((d) => d.path).sort(); + expect(paths).toContain('root.md'); + expect(paths).toContain('level1/doc1.md'); + expect(paths).toContain('level1/level2/doc2.md'); + expect(paths).toContain('level1/level2/level3/doc3.md'); + }); + }); +}); diff --git a/tools/mcp-server/tests/search-docs.test.ts b/tools/mcp-server/tests/search-docs.test.ts new file mode 100644 index 000000000..0b3d4f6af --- /dev/null +++ b/tools/mcp-server/tests/search-docs.test.ts @@ -0,0 +1,337 @@ +/** + * Unit tests for search_docs tool handler + */ + +import { beforeEach, describe, expect, it } from 'vitest'; +import { SearchEngine } from '../src/services/search.js'; +import { createSearchDocsHandler, SEARCH_DOCS_TOOL } from '../src/tools/search-docs/index.js'; +import type { SearchDocsInput } from '../src/tools/search-docs/tool.types.js'; +import type { DocumentMetadata } from '../src/types/index.js'; + +describe('search_docs tool', () => { + let testDocuments: DocumentMetadata[]; + let searchEngine: SearchEngine; + let handler: ReturnType; + + beforeEach(() => { + // Create test documents + testDocuments = [ + { + path: 'guides/palette.md', + title: 'Palette Guide', + description: 'Learn how to use the palette component', + content: 'The palette allows you to drag and drop nodes onto the canvas.', + url: '/docs/guides/palette', + }, + { + path: 'intro/quick-start.md', + title: 'Quick Start', + description: 'Get started quickly with ng-diagram', + content: 'Install the package using npm install ng-diagram.', + url: '/docs/intro/quick-start', + }, + { + path: 'api/components.md', + title: 'Components API', + description: 'API reference for all components', + content: 'This document describes the available Angular components in the library.', + url: '/docs/api/components', + }, + ]; + + searchEngine = new SearchEngine(testDocuments); + handler = createSearchDocsHandler(searchEngine); + }); + + describe('tool schema definition', () => { + it('should have correct tool name', () => { + expect(SEARCH_DOCS_TOOL.name).toBe('search_docs'); + }); + + it('should have a description', () => { + expect(SEARCH_DOCS_TOOL.description).toBeDefined(); + expect(SEARCH_DOCS_TOOL.description.length).toBeGreaterThan(0); + }); + + it('should define input schema with query parameter', () => { + expect(SEARCH_DOCS_TOOL.inputSchema.properties.query).toBeDefined(); + expect(SEARCH_DOCS_TOOL.inputSchema.properties.query.type).toBe('string'); + }); + + it('should define input schema with limit parameter', () => { + expect(SEARCH_DOCS_TOOL.inputSchema.properties.limit).toBeDefined(); + expect(SEARCH_DOCS_TOOL.inputSchema.properties.limit.type).toBe('number'); + expect(SEARCH_DOCS_TOOL.inputSchema.properties.limit.default).toBe(10); + }); + + it('should mark query as required', () => { + expect(SEARCH_DOCS_TOOL.inputSchema.required).toContain('query'); + }); + + it('should not mark limit as required', () => { + expect(SEARCH_DOCS_TOOL.inputSchema.required).not.toContain('limit'); + }); + }); + + describe('input validation - empty query rejection', () => { + it('should reject empty string query', async () => { + const input: SearchDocsInput = { query: '' }; + + await expect(handler(input)).rejects.toThrow('Query parameter is required'); + }); + + it('should reject whitespace-only query', async () => { + const input: SearchDocsInput = { query: ' ' }; + + await expect(handler(input)).rejects.toThrow('Query parameter cannot be empty'); + }); + + it('should reject query with tabs and spaces', async () => { + const input: SearchDocsInput = { query: '\t \t ' }; + + await expect(handler(input)).rejects.toThrow('Query parameter cannot be empty'); + }); + + it('should reject query with newlines', async () => { + const input: SearchDocsInput = { query: '\n\n' }; + + await expect(handler(input)).rejects.toThrow('Query parameter cannot be empty'); + }); + }); + + describe('successful search with results', () => { + it('should return results for valid query', async () => { + const input: SearchDocsInput = { query: 'palette' }; + + const output = await handler(input); + + expect(output.results).toBeDefined(); + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Palette Guide'); + }); + + it('should return multiple results when multiple matches exist', async () => { + const input: SearchDocsInput = { query: 'the' }; + + const output = await handler(input); + + expect(output.results).toBeDefined(); + expect(output.results.length).toBeGreaterThan(1); + }); + + it('should return results with all required fields', async () => { + const input: SearchDocsInput = { query: 'Quick Start' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + const result = output.results[0]; + expect(result).toHaveProperty('title'); + expect(result).toHaveProperty('description'); + expect(result).toHaveProperty('excerpt'); + expect(result).toHaveProperty('url'); + }); + + it('should trim whitespace from query', async () => { + const input: SearchDocsInput = { query: ' palette ' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Palette Guide'); + }); + + it('should handle case-insensitive search', async () => { + const input: SearchDocsInput = { query: 'PALETTE' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Palette Guide'); + }); + }); + + describe('successful search with no results', () => { + it('should return empty results array when no matches found', async () => { + const input: SearchDocsInput = { query: 'nonexistent-term-xyz' }; + + const output = await handler(input); + + expect(output.results).toBeDefined(); + expect(output.results).toEqual([]); + }); + + it('should return empty results for query not in any document', async () => { + const input: SearchDocsInput = { query: 'quantum-physics' }; + + const output = await handler(input); + + expect(output.results).toEqual([]); + }); + + it('should not throw error when no results found', async () => { + const input: SearchDocsInput = { query: 'nonexistent' }; + + await expect(handler(input)).resolves.toBeDefined(); + }); + }); + + describe('limit parameter handling', () => { + it('should use default limit of 10 when not provided', async () => { + const input: SearchDocsInput = { query: 'the' }; + + const output = await handler(input); + + // Should not exceed default limit + expect(output.results.length).toBeLessThanOrEqual(10); + }); + + it('should respect custom limit when provided', async () => { + const input: SearchDocsInput = { query: 'the', limit: 1 }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + }); + + it('should handle limit of 0', async () => { + const input: SearchDocsInput = { query: 'palette', limit: 0 }; + + const output = await handler(input); + + expect(output.results).toHaveLength(0); + }); + + it('should handle large limit values', async () => { + const input: SearchDocsInput = { query: 'the', limit: 100 }; + + const output = await handler(input); + + // Should return all matches (less than 100) + expect(output.results.length).toBeLessThanOrEqual(100); + expect(output.results.length).toBeGreaterThan(0); + }); + + it('should limit results to specified number', async () => { + const input: SearchDocsInput = { query: 'the', limit: 2 }; + + const output = await handler(input); + + expect(output.results).toHaveLength(2); + }); + }); + + describe('error handling for search failures', () => { + it('should wrap errors with meaningful message', async () => { + // Create a handler with a broken search engine + const brokenEngine = { + search: () => { + throw new Error('Database connection failed'); + }, + } as unknown as SearchEngine; + + const brokenHandler = createSearchDocsHandler(brokenEngine); + const input: SearchDocsInput = { query: 'test' }; + + await expect(brokenHandler(input)).rejects.toThrow('Search failed: Database connection failed'); + }); + + it('should handle unknown errors gracefully', async () => { + // Create a handler that throws non-Error object + const brokenEngine = { + search: () => { + throw 'String error'; + }, + } as unknown as SearchEngine; + + const brokenHandler = createSearchDocsHandler(brokenEngine); + const input: SearchDocsInput = { query: 'test' }; + + await expect(brokenHandler(input)).rejects.toThrow('Search failed: Unknown error occurred'); + }); + + it('should handle validation errors', async () => { + const input: SearchDocsInput = { query: '', limit: 5 }; + + await expect(handler(input)).rejects.toThrow(); + }); + + it('should handle invalid limit parameter', async () => { + const input = { query: 'test', limit: -1 }; + + await expect(handler(input as SearchDocsInput)).rejects.toThrow('Limit parameter must be a non-negative number'); + }); + + it('should handle non-number limit parameter', async () => { + const input = { query: 'test', limit: 'invalid' }; + + await expect(handler(input as any)).rejects.toThrow('Limit parameter must be a non-negative number'); + }); + }); + + describe('integration with SearchEngine', () => { + it('should pass query to search engine correctly', async () => { + const input: SearchDocsInput = { query: 'Components API' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Components API'); + }); + + it('should pass limit to search engine correctly', async () => { + const input: SearchDocsInput = { query: 'the', limit: 1 }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + }); + + it('should return search results in correct format', async () => { + const input: SearchDocsInput = { query: 'palette' }; + + const output = await handler(input); + + expect(output).toHaveProperty('results'); + expect(Array.isArray(output.results)).toBe(true); + }); + }); + + describe('edge cases', () => { + it('should handle special characters in query', async () => { + const input: SearchDocsInput = { query: 'ng-diagram' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Quick Start'); + }); + + it('should handle very long queries', async () => { + const input: SearchDocsInput = { query: 'a'.repeat(1000) }; + + const output = await handler(input); + + expect(output.results).toEqual([]); + }); + + it('should handle queries with multiple words', async () => { + const input: SearchDocsInput = { query: 'drag and drop' }; + + const output = await handler(input); + + expect(output.results).toHaveLength(1); + expect(output.results[0].title).toBe('Palette Guide'); + }); + + it('should handle empty search engine', async () => { + const emptyEngine = new SearchEngine([]); + const emptyHandler = createSearchDocsHandler(emptyEngine); + const input: SearchDocsInput = { query: 'test' }; + + const output = await emptyHandler(input); + + expect(output.results).toEqual([]); + }); + }); +}); diff --git a/tools/mcp-server/tests/search.test.ts b/tools/mcp-server/tests/search.test.ts new file mode 100644 index 000000000..68c685d72 --- /dev/null +++ b/tools/mcp-server/tests/search.test.ts @@ -0,0 +1,471 @@ +/** + * Unit tests for SearchEngine + */ + +import { beforeEach, describe, expect, it } from 'vitest'; +import { SearchEngine } from '../src/services/search.js'; +import type { DocumentMetadata } from '../src/types/index.js'; + +describe('SearchEngine', () => { + let testDocuments: DocumentMetadata[]; + let searchEngine: SearchEngine; + + beforeEach(() => { + // Create test documents with various content + testDocuments = [ + { + path: 'guides/palette.md', + title: 'Palette Guide', + description: 'Learn how to use the palette component', + content: 'The palette allows you to drag and drop nodes onto the canvas.', + url: '/docs/guides/palette', + }, + { + path: 'intro/quick-start.md', + title: 'Quick Start', + description: 'Get started quickly with ng-diagram', + content: 'Install the package using npm install ng-diagram.', + url: '/docs/intro/quick-start', + }, + { + path: 'api/components.md', + title: 'Components API', + description: 'API reference for all components', + content: 'This document describes the available Angular components in the library.', + url: '/docs/api/components', + }, + { + path: 'guides/nodes.md', + title: 'Working with Nodes', + description: 'Understanding node behavior', + content: 'Nodes are the fundamental building blocks of your diagram.', + url: '/docs/guides/nodes', + }, + { + path: 'examples/custom-node.md', + title: 'Custom Node Example', + content: 'This example shows how to create a custom node template.', + url: '/docs/examples/custom-node', + }, + ]; + + searchEngine = new SearchEngine(testDocuments); + }); + + describe('exact match in title', () => { + it('should find document with exact title match', () => { + const results = searchEngine.search({ query: 'Palette Guide' }); + + // Should find the exact match first (highest score) + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Palette Guide'); + }); + + it('should find document with partial title match', () => { + const results = searchEngine.search({ query: 'Quick' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Quick Start'); + }); + + it('should find multiple documents with title matches', () => { + const results = searchEngine.search({ query: 'Node' }); + + expect(results.length).toBeGreaterThanOrEqual(2); + const titles = results.map((r) => r.title); + expect(titles).toContain('Working with Nodes'); + expect(titles).toContain('Custom Node Example'); + }); + }); + + describe('exact match in description', () => { + it('should find document with exact description match', () => { + const results = searchEngine.search({ query: 'API reference' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Components API'); + expect(results[0].description).toBe('API reference for all components'); + }); + + it('should find document with partial description match', () => { + const results = searchEngine.search({ query: 'palette component' }); + + // Should find at least the palette guide + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Palette Guide'); + }); + }); + + describe('exact match in content', () => { + it('should find document with exact content match', () => { + const results = searchEngine.search({ query: 'npm install' }); + + expect(results).toHaveLength(1); + expect(results[0].title).toBe('Quick Start'); + }); + + it('should find document with partial content match', () => { + const results = searchEngine.search({ query: 'Angular components' }); + + expect(results).toHaveLength(1); + expect(results[0].title).toBe('Components API'); + }); + + it('should extract excerpt for content matches', () => { + const results = searchEngine.search({ query: 'building blocks' }); + + expect(results).toHaveLength(1); + expect(results[0].excerpt).toContain('building blocks'); + expect(results[0].excerpt.length).toBeGreaterThan(0); + }); + }); + + describe('case-insensitive matching', () => { + it('should match query in lowercase', () => { + const results = searchEngine.search({ query: 'palette guide' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Palette Guide'); + }); + + it('should match query in uppercase', () => { + const results = searchEngine.search({ query: 'PALETTE GUIDE' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Palette Guide'); + }); + + it('should match query in mixed case', () => { + const results = searchEngine.search({ query: 'PaLeTtE gUiDe' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + expect(results[0].title).toBe('Palette Guide'); + }); + + it('should match content case-insensitively', () => { + const lowerResults = searchEngine.search({ query: 'angular' }); + const upperResults = searchEngine.search({ query: 'ANGULAR' }); + const mixedResults = searchEngine.search({ query: 'AnGuLaR' }); + + expect(lowerResults).toHaveLength(1); + expect(upperResults).toHaveLength(1); + expect(mixedResults).toHaveLength(1); + expect(lowerResults[0].title).toBe(upperResults[0].title); + expect(lowerResults[0].title).toBe(mixedResults[0].title); + }); + }); + + describe('limit parameter enforcement', () => { + it('should return all results when limit is not specified', () => { + const results = searchEngine.search({ query: 'the' }); + + // "the" appears in multiple documents + expect(results.length).toBeGreaterThan(1); + }); + + it('should limit results to specified number', () => { + const results = searchEngine.search({ query: 'the', limit: 2 }); + + expect(results).toHaveLength(2); + }); + + it('should respect limit of 1', () => { + const results = searchEngine.search({ query: 'node', limit: 1 }); + + expect(results).toHaveLength(1); + }); + + it('should return fewer results if matches are less than limit', () => { + const results = searchEngine.search({ query: 'Palette Guide', limit: 10 }); + + // With multi-word matching, may find more than 1 but should be limited + expect(results.length).toBeLessThanOrEqual(10); + expect(results.length).toBeGreaterThanOrEqual(1); + // Exact match should be first + expect(results[0].title).toBe('Palette Guide'); + }); + + it('should handle limit of 0', () => { + const results = searchEngine.search({ query: 'node', limit: 0 }); + + expect(results).toHaveLength(0); + }); + }); + + describe('excerpt extraction with match context', () => { + it('should extract excerpt with surrounding context', () => { + const results = searchEngine.search({ query: 'drag and drop' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + // Find the result with the excerpt (content match) + const resultWithExcerpt = results.find((r) => r.excerpt && r.excerpt.length > 0); + expect(resultWithExcerpt).toBeDefined(); + expect(resultWithExcerpt!.excerpt).toContain('drag'); + expect(resultWithExcerpt!.excerpt).toContain('drop'); + }); + + it('should add ellipsis when content is truncated at start', () => { + const longContent = 'A'.repeat(200) + ' drag and drop ' + 'B'.repeat(200); + const docs = [ + { + path: 'test.md', + title: 'Test', + content: longContent, + url: '/docs/test', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'drag and drop' }); + + expect(results[0].excerpt).toMatch(/^\.\.\./); + }); + + it('should add ellipsis when content is truncated at end', () => { + const longContent = 'A'.repeat(200) + ' drag and drop ' + 'B'.repeat(200); + const docs = [ + { + path: 'test.md', + title: 'Test', + content: longContent, + url: '/docs/test', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'drag and drop' }); + + expect(results[0].excerpt).toMatch(/\.\.\.$/); + }); + + it('should not add ellipsis for short content', () => { + const results = searchEngine.search({ query: 'npm install' }); + + expect(results[0].excerpt).not.toMatch(/^\.\.\./); + expect(results[0].excerpt).not.toMatch(/\.\.\.$/); + }); + + it('should return empty excerpt for title matches', () => { + const results = searchEngine.search({ query: 'Palette Guide' }); + + expect(results[0].excerpt).toBe(''); + }); + + it('should return empty excerpt for description matches', () => { + const results = searchEngine.search({ query: 'API reference' }); + + expect(results[0].excerpt).toBe(''); + }); + }); + + describe('empty results for non-matching queries', () => { + it('should return empty array when no matches found', () => { + const results = searchEngine.search({ query: 'nonexistent-term-xyz' }); + + expect(results).toEqual([]); + }); + + it('should return empty array for query not in any field', () => { + const results = searchEngine.search({ query: 'quantum-physics' }); + + expect(results).toEqual([]); + }); + + it('should handle special characters in non-matching query', () => { + const results = searchEngine.search({ query: '@#$%^&*()' }); + + expect(results).toEqual([]); + }); + }); + + describe('ranking order (title > description > content)', () => { + it('should rank title matches higher than description matches', () => { + const docs = [ + { + path: 'doc1.md', + title: 'Other Document', + description: 'This describes palette functionality', + content: 'Some content here', + url: '/docs/doc1', + }, + { + path: 'doc2.md', + title: 'Palette Guide', + description: 'A guide to something', + content: 'More content', + url: '/docs/doc2', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'palette' }); + + expect(results).toHaveLength(2); + expect(results[0].title).toBe('Palette Guide'); // Title match first + expect(results[1].title).toBe('Other Document'); // Description match second + }); + + it('should rank description matches higher than content matches', () => { + const docs = [ + { + path: 'doc1.md', + title: 'Document One', + description: 'Some description', + content: 'This content mentions palette functionality', + url: '/docs/doc1', + }, + { + path: 'doc2.md', + title: 'Document Two', + description: 'Guide about palette usage', + content: 'Other content', + url: '/docs/doc2', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'palette' }); + + expect(results).toHaveLength(2); + expect(results[0].title).toBe('Document Two'); // Description match first + expect(results[1].title).toBe('Document One'); // Content match second + }); + + it('should rank title > description > content in same query', () => { + const docs = [ + { + path: 'doc1.md', + title: 'Something', + description: 'Description', + content: 'Content with diagram keyword', + url: '/docs/doc1', + }, + { + path: 'doc2.md', + title: 'Other', + description: 'Description about diagram', + content: 'Content', + url: '/docs/doc2', + }, + { + path: 'doc3.md', + title: 'Diagram Guide', + description: 'Description', + content: 'Content', + url: '/docs/doc3', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'diagram' }); + + expect(results).toHaveLength(3); + expect(results[0].title).toBe('Diagram Guide'); // Title match (score 100) + expect(results[1].title).toBe('Other'); // Description match (score 50) + expect(results[2].title).toBe('Something'); // Content match (score 10) + }); + + it('should sort alphabetically by title when scores are equal', () => { + const docs = [ + { + path: 'doc1.md', + title: 'Zebra Guide', + description: 'Guide about zebras', + content: 'Content', + url: '/docs/doc1', + }, + { + path: 'doc2.md', + title: 'Apple Guide', + description: 'Guide about apples', + content: 'Content', + url: '/docs/doc2', + }, + { + path: 'doc3.md', + title: 'Mango Guide', + description: 'Guide about mangos', + content: 'Content', + url: '/docs/doc3', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'guide' }); + + expect(results).toHaveLength(3); + expect(results[0].title).toBe('Apple Guide'); + expect(results[1].title).toBe('Mango Guide'); + expect(results[2].title).toBe('Zebra Guide'); + }); + }); + + describe('search result format', () => { + it('should include all required fields in search result', () => { + const results = searchEngine.search({ query: 'Palette' }); + + expect(results).toHaveLength(1); + expect(results[0]).toHaveProperty('title'); + expect(results[0]).toHaveProperty('description'); + expect(results[0]).toHaveProperty('excerpt'); + expect(results[0]).toHaveProperty('url'); + }); + + it('should handle documents without description', () => { + const results = searchEngine.search({ query: 'Custom Node Example' }); + + expect(results.length).toBeGreaterThanOrEqual(1); + // Find the exact match + const exactMatch = results.find((r) => r.title === 'Custom Node Example'); + expect(exactMatch).toBeDefined(); + expect(exactMatch!.description).toBeUndefined(); + }); + + it('should preserve original document data', () => { + const results = searchEngine.search({ query: 'Quick Start' }); + + expect(results[0].title).toBe('Quick Start'); + expect(results[0].url).toBe('/docs/intro/quick-start'); + }); + }); + + describe('edge cases', () => { + it('should handle empty document array', () => { + const engine = new SearchEngine([]); + const results = engine.search({ query: 'test' }); + + expect(results).toEqual([]); + }); + + it('should handle query with only whitespace', () => { + const results = searchEngine.search({ query: ' ' }); + + // Whitespace-only query should not match anything + expect(results).toEqual([]); + }); + + it('should handle very long queries', () => { + const longQuery = 'a'.repeat(1000); + const results = searchEngine.search({ query: longQuery }); + + expect(results).toEqual([]); + }); + + it('should handle documents with empty content', () => { + const docs = [ + { + path: 'empty.md', + title: 'Empty Document', + content: '', + url: '/docs/empty', + }, + ]; + const engine = new SearchEngine(docs); + + const results = engine.search({ query: 'Empty' }); + + expect(results).toHaveLength(1); + expect(results[0].excerpt).toBe(''); + }); + }); +}); diff --git a/tools/mcp-server/tests/server.integration.test.ts b/tools/mcp-server/tests/server.integration.test.ts new file mode 100644 index 000000000..72e48e287 --- /dev/null +++ b/tools/mcp-server/tests/server.integration.test.ts @@ -0,0 +1,630 @@ +/** + * Integration tests for NgDiagramMCPServer + * Tests server initialization, tool registration, and full flow + */ + +import { mkdir, rm, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { NgDiagramMCPServer } from '../src/server.js'; +import type { MCPServerConfig } from '../src/types/index.js'; + +describe('NgDiagramMCPServer Integration Tests', () => { + const testDocsDir = join(process.cwd(), 'tests', 'fixtures', 'integration-docs'); + let server: NgDiagramMCPServer; + + beforeEach(async () => { + // Create test documentation directory + await mkdir(testDocsDir, { recursive: true }); + }); + + afterEach(async () => { + // Clean up test directory + await rm(testDocsDir, { recursive: true, force: true }); + }); + + describe('server initialization with valid documentation directory', () => { + it('should initialize server with valid configuration', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should start server and build index with valid documentation', async () => { + // Create test documentation files + await writeFile( + join(testDocsDir, 'guide.md'), + `--- +title: Test Guide +description: A test guide document +--- + +# Test Guide + +This is test content.`, + 'utf-8' + ); + + await writeFile( + join(testDocsDir, 'api.mdx'), + `--- +title: API Reference +--- + +# API Reference + +API documentation here.`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Start should complete without errors + // Note: We can't fully test stdio transport without mocking, + // but we can verify the indexing and setup phase + await expect( + (async () => { + try { + // We'll test the initialization up to the point before stdio connection + // by checking that the server can be created and configured + const testServer = new NgDiagramMCPServer(config); + expect(testServer).toBeDefined(); + expect(testServer.isServerRunning()).toBe(false); + } catch (error) { + throw error; + } + })() + ).resolves.not.toThrow(); + }); + + it('should initialize with nested documentation structure', async () => { + // Create nested directory structure + const guidesDir = join(testDocsDir, 'guides'); + const apiDir = join(testDocsDir, 'api'); + await mkdir(guidesDir, { recursive: true }); + await mkdir(apiDir, { recursive: true }); + + await writeFile(join(guidesDir, 'intro.md'), '# Introduction', 'utf-8'); + await writeFile(join(apiDir, 'components.md'), '# Components', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should handle empty documentation directory', async () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Should not throw even with empty directory + expect(server).toBeDefined(); + }); + }); + + describe('server initialization with missing documentation directory', () => { + it('should handle non-existent documentation directory gracefully', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: join(testDocsDir, 'non-existent'), + }; + + // Server should be created even if directory doesn't exist + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should initialize with empty index when directory is missing', async () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: join(testDocsDir, 'missing-dir'), + }; + + server = new NgDiagramMCPServer(config); + + // Server should be created successfully + expect(server).toBeDefined(); + expect(server.isServerRunning()).toBe(false); + }); + + it('should handle invalid path gracefully', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: '/invalid/path/that/does/not/exist', + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + }); + + describe('server configuration', () => { + it('should accept custom server name', () => { + const config: MCPServerConfig = { + name: 'custom-mcp-server', + version: '2.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should accept custom version', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '0.0.1-alpha', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should store configuration correctly', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Server should be initialized with the config + expect(server).toBeDefined(); + }); + }); + + describe('server lifecycle', () => { + it('should report not running before start', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + expect(server.isServerRunning()).toBe(false); + }); + + it('should handle multiple server instances', () => { + const config1: MCPServerConfig = { + name: 'server-1', + version: '1.0.0', + docsPath: testDocsDir, + }; + + const config2: MCPServerConfig = { + name: 'server-2', + version: '1.0.0', + docsPath: testDocsDir, + }; + + const server1 = new NgDiagramMCPServer(config1); + const server2 = new NgDiagramMCPServer(config2); + + expect(server1).toBeDefined(); + expect(server2).toBeDefined(); + expect(server1).not.toBe(server2); + }); + }); + + describe('error handling during initialization', () => { + it('should handle files with invalid frontmatter', async () => { + await writeFile( + join(testDocsDir, 'invalid.md'), + `--- +title: Test +invalid: [broken yaml: { +--- + +Content`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + // Should not throw even with invalid frontmatter + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should handle empty files', async () => { + await writeFile(join(testDocsDir, 'empty.md'), '', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should handle mixed valid and invalid files', async () => { + await writeFile(join(testDocsDir, 'valid.md'), '# Valid Document', 'utf-8'); + await writeFile( + join(testDocsDir, 'invalid.md'), + `--- +broken yaml +---`, + 'utf-8' + ); + await writeFile(join(testDocsDir, 'also-valid.md'), '# Another Valid Document', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + }); + + describe('documentation indexing integration', () => { + it('should index multiple file types', async () => { + await writeFile(join(testDocsDir, 'doc1.md'), '# Markdown Document', 'utf-8'); + await writeFile(join(testDocsDir, 'doc2.mdx'), '# MDX Document', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + expect(server).toBeDefined(); + }); + + it('should handle large documentation sets', async () => { + // Create multiple documents + for (let i = 0; i < 50; i++) { + await writeFile( + join(testDocsDir, `doc${i}.md`), + `--- +title: Document ${i} +--- + +# Document ${i} + +Content for document ${i}.`, + 'utf-8' + ); + } + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + expect(server).toBeDefined(); + }); + + it('should handle deeply nested directory structures', async () => { + const deepPath = join(testDocsDir, 'level1', 'level2', 'level3', 'level4'); + await mkdir(deepPath, { recursive: true }); + await writeFile(join(deepPath, 'deep.md'), '# Deep Document', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + expect(server).toBeDefined(); + }); + }); + + describe('server robustness', () => { + it('should handle special characters in file names', async () => { + await writeFile(join(testDocsDir, 'file-with-dashes.md'), '# Dashed File', 'utf-8'); + await writeFile(join(testDocsDir, 'file_with_underscores.md'), '# Underscored File', 'utf-8'); + await writeFile(join(testDocsDir, 'file.with.dots.md'), '# Dotted File', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should handle unicode characters in content', async () => { + await writeFile( + join(testDocsDir, 'unicode.md'), + `--- +title: Unicode Test +--- + +# Unicode Content + +This has émojis 🎉 and spëcial çharacters.`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + + it('should handle very long file content', async () => { + const longContent = 'a'.repeat(100000); + await writeFile( + join(testDocsDir, 'long.md'), + `--- +title: Long Document +--- + +${longContent}`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + }); + }); + + describe('tool registration verification', () => { + it('should have search_docs tool available after initialization', async () => { + await writeFile( + join(testDocsDir, 'test.md'), + `--- +title: Test Document +--- + +# Test + +Content here.`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Server should be created with tool registration capability + expect(server).toBeDefined(); + }); + + it('should register tools only after start is called', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Before start, server should not be running + expect(server.isServerRunning()).toBe(false); + }); + + it('should fail to register tools before indexing', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Server is created but not started, so tools are not registered yet + expect(server.isServerRunning()).toBe(false); + }); + }); + + describe('full flow integration', () => { + it('should complete full initialization flow with valid docs', async () => { + // Create comprehensive test documentation + await writeFile( + join(testDocsDir, 'intro.md'), + `--- +title: Introduction +description: Getting started with the library +--- + +# Introduction + +Welcome to the documentation.`, + 'utf-8' + ); + + await writeFile( + join(testDocsDir, 'guide.md'), + `--- +title: User Guide +description: Complete user guide +--- + +# User Guide + +Learn how to use the features.`, + 'utf-8' + ); + + const guidesDir = join(testDocsDir, 'guides'); + await mkdir(guidesDir, { recursive: true }); + await writeFile( + join(guidesDir, 'advanced.md'), + `--- +title: Advanced Topics +--- + +# Advanced Topics + +Advanced usage patterns.`, + 'utf-8' + ); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + // Create server + server = new NgDiagramMCPServer(config); + expect(server).toBeDefined(); + + // Verify initial state + expect(server.isServerRunning()).toBe(false); + }); + + it('should handle complete flow with empty documentation', async () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + // Create server with empty docs + server = new NgDiagramMCPServer(config); + expect(server).toBeDefined(); + expect(server.isServerRunning()).toBe(false); + }); + + it('should handle complete flow with missing directory', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: join(testDocsDir, 'does-not-exist'), + }; + + // Create server with non-existent directory + server = new NgDiagramMCPServer(config); + expect(server).toBeDefined(); + expect(server.isServerRunning()).toBe(false); + }); + + it('should maintain state consistency throughout lifecycle', async () => { + await writeFile(join(testDocsDir, 'doc.md'), '# Document', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + // Create server + server = new NgDiagramMCPServer(config); + + // Initial state + expect(server.isServerRunning()).toBe(false); + + // Server should remain in consistent state + expect(server).toBeDefined(); + expect(server.isServerRunning()).toBe(false); + }); + }); + + describe('stdio transport integration', () => { + it('should be configured for stdio transport', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Server should be created with stdio transport capability + expect(server).toBeDefined(); + }); + + it('should handle server creation without starting transport', () => { + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + // Create server but don't start (which would connect stdio) + expect(() => { + server = new NgDiagramMCPServer(config); + }).not.toThrow(); + + expect(server.isServerRunning()).toBe(false); + }); + + it('should prepare for stdio communication', async () => { + await writeFile(join(testDocsDir, 'test.md'), '# Test', 'utf-8'); + + const config: MCPServerConfig = { + name: 'test-server', + version: '1.0.0', + docsPath: testDocsDir, + }; + + server = new NgDiagramMCPServer(config); + + // Server should be ready for stdio transport + expect(server).toBeDefined(); + }); + }); +}); diff --git a/tools/mcp-server/tsconfig.json b/tools/mcp-server/tsconfig.json new file mode 100644 index 000000000..8a15cdca1 --- /dev/null +++ b/tools/mcp-server/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowJs": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +}