diff --git a/Dockerfile b/Dockerfile index ff82f1e4..941f6400 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM node:22-alpine AS builder WORKDIR /app COPY src/ ./src/ -COPY package.json package-lock.json tsconfig.json vite.config.ts ./ +COPY package.json package-lock.json tsconfig.json vite.config.ts vite.config.ui.ts ./ RUN --mount=type=cache,target=/root/.npm npm ci diff --git a/package-lock.json b/package-lock.json index 68ce4602..b2f31016 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,11 @@ "license": "MIT", "dependencies": { "@bugsnag/js": "^8.2.0", - "@modelcontextprotocol/sdk": "^1.15.0", + "@modelcontextprotocol/ext-apps": "^1.0.1", + "@modelcontextprotocol/sdk": "^1.26.0", "node-cache": "^5.1.2", + "react": "^19.2.4", + "react-dom": "^19.2.4", "swagger-client": "^3.35.6", "vite": "^7.3.1", "zod": "^4" @@ -23,6 +26,9 @@ "@biomejs/biome": "^2.2.4", "@types/js-yaml": "^4.0.9", "@types/node": "^22", + "@types/react": "^19.2.13", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.3", "@vitest/coverage-v8": "^3.2.4", "globals": "^16.2.0", "shx": "^0.3.4", @@ -45,6 +51,185 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -65,14 +250,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -81,6 +290,38 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime-corejs3": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", @@ -93,10 +334,44 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -755,9 +1030,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", - "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -805,6 +1080,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -833,13 +1119,56 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.1.tgz", + "integrity": "sha512-rAPzBbB5GNgYk216paQjGKUgbNXSy/yeR95c0ni6Y4uvhWI2AeF+ztEOqQFLBMQy/MPM+02pbVK1HaQmQjMwYQ==", + "hasInstallScript": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "^1.2.21", + "@oven/bun-darwin-x64": "^1.2.21", + "@oven/bun-darwin-x64-baseline": "^1.2.21", + "@oven/bun-linux-aarch64": "^1.2.21", + "@oven/bun-linux-aarch64-musl": "^1.2.21", + "@oven/bun-linux-x64": "^1.2.21", + "@oven/bun-linux-x64-baseline": "^1.2.21", + "@oven/bun-linux-x64-musl": "^1.2.21", + "@oven/bun-linux-x64-musl-baseline": "^1.2.21", + "@oven/bun-windows-x64": "^1.2.21", + "@oven/bun-windows-x64-baseline": "^1.2.21", + "@rollup/rollup-darwin-arm64": "^4.53.3", + "@rollup/rollup-darwin-x64": "^4.53.3", + "@rollup/rollup-linux-arm64-gnu": "^4.53.3", + "@rollup/rollup-linux-x64-gnu": "^4.53.3", + "@rollup/rollup-win32-arm64-msvc": "^4.53.3", + "@rollup/rollup-win32-x64-msvc": "^4.53.3" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.24.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -847,14 +1176,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -872,6 +1202,149 @@ } } }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.8.tgz", + "integrity": "sha512-hPERz4IgXCM6Y6GdEEsJAFceyJMt29f3HlFzsvE/k+TQjChRhar6S+JggL35b9VmFfsdxyCOOTPqgnSrdV0etA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.8.tgz", + "integrity": "sha512-SaWIxsRQYiT/eA60bqA4l8iNO7cJ6YD8ie82RerRp9voceBxPIZiwX4y20cTKy5qNaSGr9LxfYq7vDywTipiog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.8.tgz", + "integrity": "sha512-ArHVWpCRZI3vGLoN2/8ud8Kzqlgn1Gv+fNw+pMB9x18IzgAEhKxFxsWffnoaH21amam4tAOhpeewRIgdNtB0Cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.8.tgz", + "integrity": "sha512-rq0nNckobtS+ONoB95/Frfqr8jCtmSjjjEZlN4oyUx0KEBV11Vj4v3cDVaWzuI34ryL8FCog3HaqjfKn8R82Tw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.8.tgz", + "integrity": "sha512-HvJmhrfipL7GtuqFz6xNpmf27NGcCOMwCalPjNR6fvkLpe8A7Z1+QbxKKjOglelmlmZc3Vi2TgDUtxSqfqOToQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.8.tgz", + "integrity": "sha512-YDgqVx1MI8E0oDbCEUSkAMBKKGnUKfaRtMdLh9Bjhu7JQacQ/ZCpxwi4HPf5Q0O1TbWRrdxGw2tA2Ytxkn7s1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.8.tgz", + "integrity": "sha512-3IkS3TuVOzMqPW6Gg9/8FEoKF/rpKZ9DZUfNy9GQ54+k4PGcXpptU3+dy8D4iDFCt4qe6bvoiAOdM44OOsZ+Wg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.8.tgz", + "integrity": "sha512-o7Jm5zL4aw9UBs3BcZLVbgGm2V4F10MzAQAV+ziKzoEfYmYtvDqRVxgKEq7BzUOVy4LgfrfwzEXw5gAQGRrhQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl-baseline": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.8.tgz", + "integrity": "sha512-5g8XJwHhcTh8SGoKO7pR54ILYDbuFkGo+68DOMTiVB5eLxuLET+Or/camHgk4QWp3nUS5kNjip4G8BE8i0rHVQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.8.tgz", + "integrity": "sha512-UDI3rowMm/tI6DIynpE4XqrOhr+1Ztk1NG707Wxv2nygup+anTswgCwjfjgmIe78LdoRNFrux2GpeolhQGW6vQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.8.tgz", + "integrity": "sha512-K6qBUKAZLXsjAwFxGTG87dsWlDjyDl2fqjJr7+x7lmv2m+aSEzmLOK+Z5pSvGkpjBp3LXV35UUgj8G0UTd0pPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -883,6 +1356,13 @@ "node": ">=14" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", @@ -1845,6 +2325,51 @@ "node": ">=12.20.0" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1895,6 +2420,47 @@ "types-ramda": "^0.30.1" } }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", + "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.2", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", @@ -2173,10 +2739,20 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -2185,7 +2761,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -2208,6 +2784,40 @@ "concat-map": "0.0.1" } }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -2265,6 +2875,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -2362,6 +2993,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -2418,6 +3056,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2499,6 +3144,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2626,6 +3278,16 @@ "@esbuild/win32-x64": "0.27.2" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2726,10 +3388,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -2928,6 +3593,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3062,11 +3737,10 @@ } }, "node_modules/hono": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", - "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -3099,9 +3773,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -3142,6 +3816,15 @@ "node": ">= 0.10" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3293,6 +3976,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3305,6 +4001,19 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3582,6 +4291,13 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3892,6 +4608,37 @@ "node": ">= 0.10" } }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -4000,6 +4747,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -4672,6 +5425,37 @@ "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", "license": "MIT" }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5016,6 +5800,13 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/zod": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", diff --git a/package.json b/package.json index 2094fe3c..349f8804 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "mcpServerName": "SmartBear MCP Server" }, "scripts": { - "build": "vite build && shx chmod +x dist/*.js", + "build": "vite build && npm run ui && shx chmod +x dist/*.js", + "ui": "vite build --config vite.config.ui.ts", + "ui:dev": "vite --config vite.config.ui.ts", "lint": "biome lint .", "lint:fix": "biome lint . --fix", "format": "biome format . --write", @@ -48,8 +50,11 @@ }, "dependencies": { "@bugsnag/js": "^8.2.0", - "@modelcontextprotocol/sdk": "^1.15.0", + "@modelcontextprotocol/ext-apps": "^1.0.1", + "@modelcontextprotocol/sdk": "^1.26.0", "node-cache": "^5.1.2", + "react": "^19.2.4", + "react-dom": "^19.2.4", "swagger-client": "^3.35.6", "vite": "^7.3.1", "zod": "^4" @@ -58,6 +63,9 @@ "@biomejs/biome": "^2.2.4", "@types/js-yaml": "^4.0.9", "@types/node": "^22", + "@types/react": "^19.2.13", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.3", "@vitest/coverage-v8": "^3.2.4", "globals": "^16.2.0", "shx": "^0.3.4", diff --git a/src/bugsnag/client.ts b/src/bugsnag/client.ts index 029df8d8..7faa4044 100644 --- a/src/bugsnag/client.ts +++ b/src/bugsnag/client.ts @@ -3,11 +3,11 @@ import type { CacheService } from "../common/cache"; import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info"; import type { SmartBearMcpServer } from "../common/server"; import { ToolError } from "../common/tools"; -import type { +import { Client, - GetInputFunction, - RegisterResourceFunction, - RegisterToolsFunction, + type GetInputFunction, + type RegisterResourceFunction, + type RegisterToolsFunction, } from "../common/types"; import { type Build, @@ -21,7 +21,7 @@ import { ProjectAPI, type Release, type TraceField, -} from "./client/api/index"; +} from "./client/api"; import { type FilterObject, toUrlSearchParams } from "./client/filters"; import { toolInputParameters } from "./input-schemas"; @@ -67,7 +67,7 @@ const ConfigurationSchema = z.object({ endpoint: z.url().describe("BugSnag endpoint URL").optional(), }); -export class BugsnagClient implements Client { +export class BugsnagClient extends Client { private cache?: CacheService; private projectApiKey?: string; private configuredProjectApiKey?: string; @@ -443,6 +443,11 @@ export class BugsnagClient implements Client { hints: [ "Project IDs from this list can be used with other tools when no project API key is configured", ], + _meta: { + ui: { + resourceUri: this.createAppUri("list-projects"), + }, + }, }, async (args, _extra) => { const params = listProjectsInputSchema.parse(args); @@ -1702,5 +1707,7 @@ export class BugsnagClient implements Client { ], }; }); + + this.registerUIResource(register); } } diff --git a/src/bugsnag/ui/AppContext.ts b/src/bugsnag/ui/AppContext.ts new file mode 100644 index 00000000..18d65187 --- /dev/null +++ b/src/bugsnag/ui/AppContext.ts @@ -0,0 +1,12 @@ +import type { App } from "@modelcontextprotocol/ext-apps"; +import { createContext, useContext } from "react"; + +export const AppContext = createContext(undefined); + +export function useApp() { + const app = useContext(AppContext); + if (!app) { + throw new Error("useApp must be used within an AppContext.Provider"); + } + return app; +} diff --git a/src/bugsnag/ui/ListProjects.css b/src/bugsnag/ui/ListProjects.css new file mode 100644 index 00000000..4db6a951 --- /dev/null +++ b/src/bugsnag/ui/ListProjects.css @@ -0,0 +1,58 @@ +.list-projects { + display: flex; + flex-direction: column; + gap: 0.5rem; + + input { + padding: 0.2rem; + } + + .project-list { + list-style: none; + padding: 0; + margin: 0; + + li { + details { + padding: 0.75rem; + margin-bottom: 0.1rem; + border-radius: 2px; + color: #212121; + background: white; + + summary { + cursor: pointer; + } + + &:hover { + background: #ededfc; + } + + .message, + .error-list { + margin-top: 0.5rem; + } + } + } + } + + .error-list { + padding: 0; + list-style: none; + + li { + margin: 0 0 0.3rem; + padding: 0.5rem; + border-radius: 3px; + background: #ffd7d7; + } + + .error-header { + font-weight: bold; + } + .error-message { + margin-top: 0.25rem; + color: #5a5959; + } + } +} diff --git a/src/bugsnag/ui/ListProjects.tsx b/src/bugsnag/ui/ListProjects.tsx new file mode 100644 index 00000000..fad6cf93 --- /dev/null +++ b/src/bugsnag/ui/ListProjects.tsx @@ -0,0 +1,108 @@ +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { Suspense, use, useMemo, useState } from "react"; +import { getToolResult } from "../../commonUi/util"; +import type { ErrorApiView, Project } from "../client/api"; +import "./ListProjects.css"; +import { useApp } from "./AppContext"; + +export default function ListProjects(props: { data: CallToolResult }) { + const { data } = props; + + const projects = useMemo( + () => getToolResult<{ data: Project[]; count: number }>(data), + [data], + ); + + const [searchTerm, setSearchTerm] = useState(""); + + return ( +
+ setSearchTerm(e.target.value)} + /> + +
+ ); +} + +interface ErrorResult { + data: ErrorApiView[]; + next_url?: string; + data_count?: number; + total_count?: number; +} + +function ProjectListItem(props: { project: Project }) { + const { id, name } = props.project; + + const app = useApp(); + const [projectErrorsResource, setProjectErrorsResource] = + useState>(); + + /** + * When expanded, load the top project errors + * When collapsed, clear the errors + */ + const handleToggle = (event: React.ToggleEvent) => { + if (event.newState === "open") { + setProjectErrorsResource( + app + .callServerTool({ + name: "bugsnag_list_project_errors", + arguments: { projectId: id }, + }) + .then((result) => getToolResult(result)), + ); + } else { + setProjectErrorsResource(undefined); + } + }; + + return ( +
  • +
    + {name} + Loading...}> + {projectErrorsResource && ( + + )} + +
    +
  • + ); +} + +function ProjectErrors(props: { resource: Promise }) { + const errors = use(props.resource).data; + + if (errors.length === 0) { + return
    No errors found for this project.
    ; + } + + return ( +
      + {errors.map((e) => ( +
    • +
      + {e.error_class} {e.context} +
      +
      {e.message}
      +
    • + ))} +
    + ); +} diff --git a/src/bugsnag/ui/README.md b/src/bugsnag/ui/README.md new file mode 100644 index 00000000..aa55a13d --- /dev/null +++ b/src/bugsnag/ui/README.md @@ -0,0 +1,21 @@ +# MCP App POC + +See https://modelcontextprotocol.io/docs/extensions/apps + +When running the MCP server locally from the built dist dir. The app will be served by the MCP server. +The HTML file is read directly once and cached and assets are served from /assets. + +### Local development +To improve the development experience, you can run the vite dev server, and start the MCP server with UI_DEV=true. +This will cause the MCP server to proxy the html from the dev server and the dev server will handle the assets. +This allows hot reloading to work and a far better experience. + +``` +$ UI_DEV=1 node dist/server.js +``` + +To use the basic-host app for testing, see https://modelcontextprotocol.io/docs/extensions/apps#testing-with-the-basic-host. + +``` +$ SERVERS='["http://localhost:3000/mcp"]' npm start +``` diff --git a/src/bugsnag/ui/app.html b/src/bugsnag/ui/app.html new file mode 100644 index 00000000..f7bb15bf --- /dev/null +++ b/src/bugsnag/ui/app.html @@ -0,0 +1,12 @@ + + + + + + BugSnag MCP App + + +
    + + + diff --git a/src/bugsnag/ui/app.tsx b/src/bugsnag/ui/app.tsx new file mode 100644 index 00000000..5ffc96d8 --- /dev/null +++ b/src/bugsnag/ui/app.tsx @@ -0,0 +1,26 @@ +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { lazy } from "react"; +import "./ListProjects.css"; +import { createMcpApp } from "../../commonUi/util"; + +const ListProjects = lazy(() => import("./ListProjects")); + +createMcpApp({ + name: "BugSnag MCP App", + version: "0.0.1", + RootComponent: Router, +}); + +/** + * Based on the tool that was called, render the appropriate component. + * + * The routes should be imported lazily. + */ +function Router({ toolId, data }: { toolId: string; data: CallToolResult }) { + switch (toolId) { + case "list-projects": + return ; + default: + throw new Error(`Unknown tool ID: ${toolId}`); + } +} diff --git a/src/common/server.ts b/src/common/server.ts index aa676cc1..1139076d 100644 --- a/src/common/server.ts +++ b/src/common/server.ts @@ -90,6 +90,7 @@ export class SmartBearMcpServer extends McpServer { inputSchema: this.getInputSchema(params), outputSchema: this.getOutputSchema(params), annotations: this.getAnnotations(toolTitle, params), + ...(params._meta && { _meta: params._meta }), }, async (args: any, extra: any) => { try { @@ -156,7 +157,10 @@ export class SmartBearMcpServer extends McpServer { if (client.registerResources) { client.registerResources((name, path, cb) => { - const url = `${client.toolPrefix}://${name}/${path}`; + const url = + typeof path === "string" + ? `${client.toolPrefix}://${name}/${path}` + : path.uri; return super.registerResource( name, new ResourceTemplate(url, { diff --git a/src/common/transport-http.ts b/src/common/transport-http.ts index ed1bb40e..e9e4c902 100644 --- a/src/common/transport-http.ts +++ b/src/common/transport-http.ts @@ -1,7 +1,9 @@ import { randomUUID } from "node:crypto"; +import { createReadStream, existsSync } from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import { createServer } from "node:http"; - +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; @@ -36,6 +38,7 @@ export async function runHttpMode() { "Content-Type", "Authorization", "MCP-Session-Id", // Required for StreamableHTTP + "MCP-Protocol-Version", "x-custom-auth-headers", // used by mcp-inspector ...allowedAuthHeaders, ].join(", "); @@ -47,6 +50,7 @@ export async function runHttpMode() { if (allowedOrigins.includes(origin)) { res.setHeader("Access-Control-Allow-Origin", origin); } + res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader( "Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS", @@ -77,6 +81,34 @@ export async function runHttpMode() { return; } + // Serve static assets from dist/assets/ + if (req.method === "GET" && url.pathname.startsWith("/assets/")) { + const distDir = join( + fileURLToPath(new URL(".", import.meta.url)), + "../", + ); + const filePath = join(distDir, url.pathname); + + // Prevent directory traversal attacks + if (!filePath.startsWith(distDir) || !existsSync(filePath)) { + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not found"); + return; + } + + const ext = url.pathname.split(".").pop(); + const contentType = + ext === "css" + ? "text/css" + : ext === "js" + ? "application/javascript" + : "text/plain"; + + res.writeHead(200, { "Content-Type": contentType }); + createReadStream(filePath).pipe(res); + return; + } + // LEGACY SSE ENDPOINT (for backwards compatibility) if (req.method === "GET" && url.pathname === "/sse") { await handleLegacySseRequest(req, res, transports); diff --git a/src/common/types.ts b/src/common/types.ts index 131e9349..a112e938 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,3 +1,11 @@ +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + type McpUiResourceMeta, + type McpUiToolMeta, + RESOURCE_MIME_TYPE, +} from "@modelcontextprotocol/ext-apps"; import type { PromptCallback, ReadResourceTemplateCallback, @@ -40,6 +48,9 @@ export interface ToolParams { destructive?: boolean; idempotent?: boolean; openWorld?: boolean; + _meta?: { + ui?: McpUiToolMeta; + }; } export interface PromptParams { @@ -59,7 +70,11 @@ export type RegisterToolsFunction = ( export type RegisterResourceFunction = ( name: string, - path: string, + path: + | string + | { + uri: string; + } /* replace the default uri construction with a full uri string (may include template variables) */, cb: ReadResourceTemplateCallback, ) => RegisteredResourceTemplate; @@ -87,30 +102,105 @@ export type Parameters = Array<{ constraints?: string[]; }>; -export interface Client { +export abstract class Client { /** Human-readable name for the client - usually the product name - used to prefix tool names */ - name: string; + abstract name: string; /** Prefix for tool IDs */ - toolPrefix: string; + abstract toolPrefix: string; /** Prefix for configuration (environment variables and http headers) */ - configPrefix: string; + abstract configPrefix: string; /** * Zod schema defining configuration fields for this client * Field names must use snake case to ensure they are mapped to environment variables and HTTP headers correctly. * e.g., `config.my_property` would refer to the environment variable `TOOL_MY_PROPERTY`, http header `Tool-My-Property` */ - config: ZodObject<{ + abstract config: ZodObject<{ [key: string]: ZodType; }>; /** * Configure the client with the given server and configuration */ - configure: (server: SmartBearMcpServer, config: any) => Promise; - isConfigured: () => boolean; - registerTools( + abstract configure(server: SmartBearMcpServer, config: any): Promise; + abstract isConfigured(): boolean; + abstract registerTools( register: RegisterToolsFunction, getInput: GetInputFunction, ): Promise; registerResources?(register: RegisterResourceFunction): void; registerPrompts?(register: RegisterPromptFunction): void; + + protected createAppUri(tool: string = "{tool}") { + return `ui://${this.toolPrefix}/app/${tool}`; + } + + protected registerUIResource( + register: RegisterResourceFunction, + config?: { + name?: string; + uri?: string; + /** + * Path to html entry relative to the src directory + * @default `${toolPrefix}/ui/app.html` + * @example "bugsnag/ui/app.html" + */ + filePath?: string; + /** + * Not required if filePath is provided + * @default "app.html" + */ + htmlFile?: string; + }, + ) { + const { + name = `${this.toolPrefix}-ui`, + uri = this.createAppUri(), + htmlFile = "app.html", + filePath = `${this.toolPrefix}/ui/${htmlFile}`, + } = config || {}; + + let html: string; + const isDev = process.env.UI_DEV; + const toolPlaceholder = "{{tool}}"; + + register(name, { uri }, async (uri, variables, _extra) => { + if (isDev || !html) { + html = await (isDev + ? // always re-fetch from the vite dev server + fetch(`http://localhost:3001/${filePath}`).then((res) => res.text()) + : // only read the file once when served from the dist folder rather than the vite dev server + readFile( + join(dirname(fileURLToPath(import.meta.url)), "..", filePath), + "utf-8", + )); + + if (!html.includes(toolPlaceholder)) { + throw new Error( + `expected meta tag mcp-tool-id with content placeholder ${toolPlaceholder} but was not found`, + ); + } + } + + return { + contents: [ + { + uri: uri.href, + mimeType: RESOURCE_MIME_TYPE, + text: html.replace(toolPlaceholder, variables.tool as string), + _meta: { + ui: { + csp: { + resourceDomains: isDev + ? ["http://localhost:3001"] + : ["http://localhost:3000"], + connectDomains: isDev + ? ["http://localhost:3001", "ws://localhost:3001"] + : [], + }, + } satisfies McpUiResourceMeta, + }, + }, + ], + }; + }); + } } diff --git a/src/commonUi/util.tsx b/src/commonUi/util.tsx new file mode 100644 index 00000000..f00beee4 --- /dev/null +++ b/src/commonUi/util.tsx @@ -0,0 +1,61 @@ +import { App } from "@modelcontextprotocol/ext-apps"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { type ComponentType, Suspense } from "react"; +import { createRoot } from "react-dom/client"; +import { AppContext } from "../bugsnag/ui/AppContext"; + +interface McpAppConfig { + name: string; + version: string; + RootComponent: ComponentType<{ toolId: string; data: CallToolResult }>; +} + +/** + * Helper function to set up an MCP app with React + * + * A meta tag with the tool id is expected mcp-tool-id. + */ +export function createMcpApp(config: McpAppConfig) { + const { name, version, RootComponent } = config; + + const app = new App({ name, version }); + + const contentDiv = document.getElementById("container"); + if (!contentDiv) { + throw new Error("Could not find container element"); + } + const reactRoot = createRoot(contentDiv); + + app.connect(); + + app.ontoolresult = (data) => { + // get the tool that was initially called and rendered the app + const toolId = document + .querySelector('meta[name="mcp-tool-id"]') + ?.getAttribute("content"); + if (!toolId) { + throw new Error("Could not find mcp-tool-id meta tag"); + } + + reactRoot.render( + + + + + , + ); + }; +} + +/** + * Get the result of a tool call, assuming the content is text and contains JSON + */ +export function getToolResult(toolResult: CallToolResult): T { + const content = toolResult.content.find((c) => c.type === "text"); + if (!content) { + throw new Error( + `Expected text content, but got ${toolResult.content[0].type}`, + ); + } + return JSON.parse(content.text) as T; +} diff --git a/src/index.ts b/src/index.ts index d0a9ba5c..5f302475 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import Bugsnag from "./common/bugsnag"; import "./common/register-clients"; // Register all available clients +import { spawn } from "node:child_process"; import { runHttpMode } from "./common/transport-http"; import { runStdioMode } from "./common/transport-stdio"; @@ -27,6 +28,14 @@ async function main() { ); process.exit(1); } + + if (process.env.UI_DEV) { + // Run the vite dev server alongside the MCP server + spawn("npm", ["run", "ui:dev", "--", "--clearScreen=false"], { + stdio: "inherit", + shell: true, + }); + } } try { diff --git a/src/tests/unit/bugsnag/client.test.ts b/src/tests/unit/bugsnag/client.test.ts index b68be823..aceec6f7 100644 --- a/src/tests/unit/bugsnag/client.test.ts +++ b/src/tests/unit/bugsnag/client.test.ts @@ -158,6 +158,9 @@ describe("BugsnagClient", () => { mockConsole.log.mockClear(); mockConsole.info.mockClear(); mockConsole.debug.mockClear(); + + client = await createConfiguredClient("test-token", "test-project-key"); + clientWithNoApiKey = await createConfiguredClient("test-token"); }); afterEach(() => { @@ -771,10 +774,6 @@ describe("BugsnagClient", () => { }); describe("API methods", async () => { - beforeEach(async () => { - client = await createConfiguredClient("test-token", "test-project-key"); - }); - describe("getProjects", () => { const mockOrg = getMockOrganization("org-1", "Test Org"); const mockProjects = [getMockProject("proj-1", "Project 1")]; @@ -929,9 +928,6 @@ describe("BugsnagClient", () => { beforeEach(async () => { registerToolsSpy = vi.fn(); getInputFunctionSpy = vi.fn(); - - client = await createConfiguredClient("test-token", "test-project-key"); - clientWithNoApiKey = await createConfiguredClient("test-token"); }); describe("Setting the current project", () => { @@ -2909,5 +2905,27 @@ describe("BugsnagClient", () => { expect(result.contents[0].text).toBe(JSON.stringify(mockEvent)); }); }); + + describe("List projects UI", () => { + it("should return the resource html", async () => { + const mockProjects = [getMockProject("proj-1", "Project 1")]; + + mockCache.get.mockReturnValueOnce(mockProjects); + + client.registerResources(registerResourcesSpy); + const resourceHandler = registerResourcesSpy.mock.calls[1][2]; + + const result = await resourceHandler( + { href: "ui://bugsnag/app/list-projects" }, + { tool: "list-projects" }, + ); + + expect(result.contents[0].uri).toBe("ui://bugsnag/app/list-projects"); + expect(result.contents[0].text).toMatch(//); + expect(result.contents[0].text).toContain( + '', + ); + }); + }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 498b92b3..2fe133e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", + "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true, diff --git a/vite.config.ui.ts b/vite.config.ui.ts new file mode 100644 index 00000000..59b6338e --- /dev/null +++ b/vite.config.ui.ts @@ -0,0 +1,52 @@ +import { dirname, join, resolve } from "node:path"; +import { resolve as resolveUrl } from "node:url"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// are we running the dev server or doing a build +const isDev = !process.argv.includes("build"); +const port = isDev ? 3001 : 3000; +const base = `http://localhost:${port}/`; + +/** + * Modify the HTML so the imports/src/hrefs are absolute urls to the dev server. + * As the MCP server reads and serves the HTML statically + */ +function absoluteUrls() { + return { + name: "absolute-urls", + transformIndexHtml: { + order: "post", + handler(html: string, context) { + const path = dirname(context.path); + return html + .replace( + /(src|href)="([^/].*?)"/g, + (...m) => `${m[1]}="${resolveUrl(base, join(path, m[2]))}"`, + ) + .replace(/(src|href)="\//g, `$1="${base}`) + .replace(/(from ['"])\//g, `$1${base}`); + }, + }, + }; +} + +export default defineConfig({ + plugins: [react(), ...(isDev ? [absoluteUrls()] : [])], + root: resolve(__dirname, "src"), + base: `${base}`, + server: { + hmr: true, + port, + allowedHosts: ["http://localhost:8081"], + }, + build: { + outDir: resolve(__dirname, "dist"), + emptyOutDir: false, + rollupOptions: { + input: [resolve(__dirname, "src/bugsnag/ui/app.html")], + }, + minify: false, + target: "es2020", + }, +});