From ac147bf84e8905ad235cde201ae616da5806d72c Mon Sep 17 00:00:00 2001 From: reaatech <138725666+reaatech@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:28:31 -0700 Subject: [PATCH 1/3] refactor: migrate to pnpm monorepo with 6 workspace packages - Split monolithic codebase into packages: core, client, observability, validators, reporters, cli - Add pnpm workspace config, Turbo orchestration, Biome (lint+format) - Add tsup per-package build with dual ESM/CJS output - Add tsconfig.typecheck.json with workspace path aliases - Move e2e tests to workspace package - Remove old npm/ESLint/Prettier/husky tooling - Bump TypeScript to 5.8, downgrade Zod to 3.x, Vitest to 3.x --- .gitignore | 51 +- .npmrc | 2 + biome.json | 33 + {tests/e2e => e2e/__tests__}/cli.e2e.test.ts | 17 +- {tests => e2e}/fixtures/registry-env.yaml | 0 {tests => e2e}/fixtures/registry-invalid.yaml | 0 {tests => e2e}/fixtures/registry-multi.yaml | 0 {tests => e2e}/fixtures/registry-valid.yaml | 0 e2e/package.json | 24 + e2e/tsconfig.json | 9 + e2e/vitest.config.ts | 12 + package-lock.json | 3607 ----------------- package.json | 94 +- packages/cli/package.json | 55 + packages/cli/src/cli.ts | 15 + .../cli/src}/commands/test.command.ts | 8 +- .../src}/commands/validate-yaml.command.ts | 8 +- {src/cli => packages/cli/src}/config.ts | 6 +- {src => packages/cli/src}/index.ts | 35 +- packages/cli/src/main.ts | 33 + {src => packages/cli/src}/runner.ts | 22 +- {src => packages/cli/src}/version.ts | 7 +- packages/cli/tsconfig.json | 8 + packages/cli/vitest.config.ts | 13 + packages/client/package.json | 48 + .../client/src}/client.ts | 22 +- .../client/src}/index.ts | 0 .../client/src}/request-builder.ts | 8 +- .../client/src}/transport.ts | 4 +- packages/client/tsconfig.json | 8 + packages/client/vitest.config.ts | 13 + packages/core/package.json | 48 + {src/types => packages/core/src}/domain.ts | 2 - packages/core/src/index.ts | 4 + {src/types => packages/core/src}/schemas.ts | 2 +- .../index.ts => packages/core/src/utils.ts | 18 +- packages/core/src/version.ts | 24 + packages/core/tsconfig.json | 8 + packages/core/vitest.config.ts | 13 + packages/observability/package.json | 46 + .../observability/src}/index.ts | 0 .../observability/src}/logger.ts | 0 .../observability/src}/metrics.ts | 4 +- .../observability/src}/tracing.ts | 2 +- packages/observability/tsconfig.json | 8 + packages/observability/vitest.config.ts | 13 + packages/reporters/package.json | 47 + .../reporters/src}/console.reporter.ts | 3 +- .../reporters/src}/html.reporter.ts | 4 +- .../reporters/src}/index.ts | 5 +- .../reporters/src}/json.reporter.ts | 2 +- .../reporters/src}/markdown.reporter.ts | 3 +- packages/reporters/tsconfig.json | 8 + packages/reporters/vitest.config.ts | 13 + packages/validators/package.json | 52 + .../validators/src}/index.ts | 0 .../src}/performance/concurrency.validator.ts | 10 +- .../validators/src}/performance/index.ts | 5 +- .../src}/performance/latency.validator.ts | 10 +- .../src}/performance/rate-limit.validator.ts | 10 +- .../validators/src}/protocol/index.ts | 5 +- .../src}/protocol/jsonrpc.validator.ts | 14 +- .../src}/protocol/session.validator.ts | 12 +- .../src}/protocol/tool-discovery.validator.ts | 14 +- .../src}/protocol/tool-execution.validator.ts | 29 +- .../src}/registry/env-expansion.validator.ts | 22 +- .../validators/src}/registry/index.ts | 7 +- .../src}/registry/invariant.validator.ts | 13 +- .../src}/registry/schema.validator.ts | 12 +- .../validators/src}/registry/shared.ts | 16 +- .../src}/routing/compatibility.validator.ts | 13 +- .../validators/src}/routing/index.ts | 5 +- .../routing/request-contract.validator.ts | 17 +- .../routing/response-contract.validator.ts | 13 +- .../src}/security/auth.validator.ts | 12 +- .../validators/src}/security/index.ts | 5 +- .../security/input-sanitization.validator.ts | 12 +- .../src}/security/ssrf.validator.ts | 11 +- packages/validators/tsconfig.json | 8 + packages/validators/vitest.config.ts | 13 + pnpm-workspace.yaml | 3 + src/cli.ts | 49 - src/types/index.ts | 6 - tests/helpers/mock-client.ts | 94 - tests/helpers/mock-server.ts | 158 - tests/integration/runner.integration.test.ts | 138 - tests/unit/barrels.test.ts | 16 - tests/unit/mcp-client.test.ts | 159 - tests/unit/protocol-validators.test.ts | 403 -- tests/unit/registry-shared.test.ts | 20 - tests/unit/registry-validators.test.ts | 283 -- .../unit/routing-security-performance.test.ts | 358 -- .../runner-reporters-observability.test.ts | 340 -- tests/unit/schemas.test.ts | 287 -- tests/unit/utils.test.ts | 212 - tsconfig.json | 34 +- tsconfig.typecheck.json | 16 + turbo.json | 23 + vitest.config.ts | 31 +- 99 files changed, 896 insertions(+), 6513 deletions(-) create mode 100644 .npmrc create mode 100644 biome.json rename {tests/e2e => e2e/__tests__}/cli.e2e.test.ts (94%) rename {tests => e2e}/fixtures/registry-env.yaml (100%) rename {tests => e2e}/fixtures/registry-invalid.yaml (100%) rename {tests => e2e}/fixtures/registry-multi.yaml (100%) rename {tests => e2e}/fixtures/registry-valid.yaml (100%) create mode 100644 e2e/package.json create mode 100644 e2e/tsconfig.json create mode 100644 e2e/vitest.config.ts delete mode 100644 package-lock.json create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/cli.ts rename {src/cli => packages/cli/src}/commands/test.command.ts (80%) rename {src/cli => packages/cli/src}/commands/validate-yaml.command.ts (74%) rename {src/cli => packages/cli/src}/config.ts (95%) rename {src => packages/cli/src}/index.ts (58%) create mode 100644 packages/cli/src/main.ts rename {src => packages/cli/src}/runner.ts (93%) rename {src => packages/cli/src}/version.ts (84%) create mode 100644 packages/cli/tsconfig.json create mode 100644 packages/cli/vitest.config.ts create mode 100644 packages/client/package.json rename {src/mcp-client => packages/client/src}/client.ts (87%) rename {src/mcp-client => packages/client/src}/index.ts (100%) rename {src/mcp-client => packages/client/src}/request-builder.ts (81%) rename {src/mcp-client => packages/client/src}/transport.ts (94%) create mode 100644 packages/client/tsconfig.json create mode 100644 packages/client/vitest.config.ts create mode 100644 packages/core/package.json rename {src/types => packages/core/src}/domain.ts (99%) create mode 100644 packages/core/src/index.ts rename {src/types => packages/core/src}/schemas.ts (98%) rename src/utils/index.ts => packages/core/src/utils.ts (90%) create mode 100644 packages/core/src/version.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/core/vitest.config.ts create mode 100644 packages/observability/package.json rename {src/observability => packages/observability/src}/index.ts (100%) rename {src/observability => packages/observability/src}/logger.ts (100%) rename {src/observability => packages/observability/src}/metrics.ts (96%) rename {src/observability => packages/observability/src}/tracing.ts (98%) create mode 100644 packages/observability/tsconfig.json create mode 100644 packages/observability/vitest.config.ts create mode 100644 packages/reporters/package.json rename {src/reporters => packages/reporters/src}/console.reporter.ts (97%) rename {src/reporters => packages/reporters/src}/html.reporter.ts (98%) rename {src/reporters => packages/reporters/src}/index.ts (93%) rename {src/reporters => packages/reporters/src}/json.reporter.ts (71%) rename {src/reporters => packages/reporters/src}/markdown.reporter.ts (95%) create mode 100644 packages/reporters/tsconfig.json create mode 100644 packages/reporters/vitest.config.ts create mode 100644 packages/validators/package.json rename {src/validators => packages/validators/src}/index.ts (100%) rename {src/validators => packages/validators/src}/performance/concurrency.validator.ts (94%) rename {src/validators => packages/validators/src}/performance/index.ts (87%) rename {src/validators => packages/validators/src}/performance/latency.validator.ts (94%) rename {src/validators => packages/validators/src}/performance/rate-limit.validator.ts (94%) rename {src/validators => packages/validators/src}/protocol/index.ts (88%) rename {src/validators => packages/validators/src}/protocol/jsonrpc.validator.ts (97%) rename {src/validators => packages/validators/src}/protocol/session.validator.ts (93%) rename {src/validators => packages/validators/src}/protocol/tool-discovery.validator.ts (96%) rename {src/validators => packages/validators/src}/protocol/tool-execution.validator.ts (91%) rename {src/validators => packages/validators/src}/registry/env-expansion.validator.ts (93%) rename {src/validators => packages/validators/src}/registry/index.ts (89%) rename {src/validators => packages/validators/src}/registry/invariant.validator.ts (96%) rename {src/validators => packages/validators/src}/registry/schema.validator.ts (93%) rename {src/validators => packages/validators/src}/registry/shared.ts (89%) rename {src/validators => packages/validators/src}/routing/compatibility.validator.ts (95%) rename {src/validators => packages/validators/src}/routing/index.ts (87%) rename {src/validators => packages/validators/src}/routing/request-contract.validator.ts (94%) rename {src/validators => packages/validators/src}/routing/response-contract.validator.ts (94%) rename {src/validators => packages/validators/src}/security/auth.validator.ts (91%) rename {src/validators => packages/validators/src}/security/index.ts (86%) rename {src/validators => packages/validators/src}/security/input-sanitization.validator.ts (94%) rename {src/validators => packages/validators/src}/security/ssrf.validator.ts (91%) create mode 100644 packages/validators/tsconfig.json create mode 100644 packages/validators/vitest.config.ts create mode 100644 pnpm-workspace.yaml delete mode 100644 src/cli.ts delete mode 100644 src/types/index.ts delete mode 100644 tests/helpers/mock-client.ts delete mode 100644 tests/helpers/mock-server.ts delete mode 100644 tests/integration/runner.integration.test.ts delete mode 100644 tests/unit/barrels.test.ts delete mode 100644 tests/unit/mcp-client.test.ts delete mode 100644 tests/unit/protocol-validators.test.ts delete mode 100644 tests/unit/registry-shared.test.ts delete mode 100644 tests/unit/registry-validators.test.ts delete mode 100644 tests/unit/routing-security-performance.test.ts delete mode 100644 tests/unit/runner-reporters-observability.test.ts delete mode 100644 tests/unit/schemas.test.ts delete mode 100644 tests/unit/utils.test.ts create mode 100644 tsconfig.typecheck.json create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index 2cf15b1..2e3068c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,45 @@ # Dependencies node_modules/ +.pnpm-store/ -# Build output +# Build outputs dist/ +build/ +*.tsbuildinfo -# Environment files -.env -.env.local -.env.*.local - -# Coverage -coverage/ - -# OS files -.DS_Store -Thumbs.db +# Stray build artifacts inside src/ (tsup output belongs in dist/ only) +packages/*/src/**/*.js +packages/*/src/**/*.js.map +packages/*/src/**/*.d.ts +packages/*/src/**/*.d.ts.map # Logs +logs/ *.log npm-debug.log* -yarn-debug.log* -yarn-error.log* +pnpm-debug.log* -# Editor directories -.idea/ +# Environment +.env +.env.local +.env.*.local + +# IDE .vscode/ +.idea/ +.claude *.swp *.swo -# Reports -reports/ +# OS +.DS_Store +Thumbs.db -# Temporary files -tmp/ -temp/ +# Test +coverage/ -# Test fixtures with secrets -tests/fixtures/*.env +# Turborepo +.turbo/ -# Local development files +# Development DEV_PLAN.md diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f7bafc7 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=false +strict-peer-dependencies=true diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..d7cd1be --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "files": { + "ignore": ["dist", ".turbo", "node_modules", "coverage"] + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "noNonNullAssertion": "error" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all" + } + } +} diff --git a/tests/e2e/cli.e2e.test.ts b/e2e/__tests__/cli.e2e.test.ts similarity index 94% rename from tests/e2e/cli.e2e.test.ts rename to e2e/__tests__/cli.e2e.test.ts index c1b2b47..768f2a3 100644 --- a/tests/e2e/cli.e2e.test.ts +++ b/e2e/__tests__/cli.e2e.test.ts @@ -1,10 +1,10 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; +import { main } from '@reaatech/mcp-contract-cli'; +import { CLI_VERSION, parseArgs, printHelp } from '@reaatech/mcp-contract-cli'; +import { Severity, TestCategory } from '@reaatech/mcp-contract-core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { main } from '../../src/cli.js'; -import { parseArgs, printHelp, CLI_VERSION } from '../../src/cli/config.js'; -import { Severity, TestCategory } from '../../src/types/domain.js'; function installMockFetch(): void { vi.stubGlobal( @@ -94,15 +94,6 @@ function installMockFetch(): void { ); } -function _installMockFetchFailure(): void { - vi.stubGlobal( - 'fetch', - vi.fn(async () => { - return new Response(null, { status: 500 }); - }), - ); -} - describe('cli e2e', () => { const cleanup: string[] = []; @@ -123,7 +114,7 @@ describe('cli e2e', () => { const outputDir = await mkdtemp(join(tmpdir(), 'mcp-contract-kit-')); cleanup.push(outputDir); const outputPath = join(outputDir, 'report.json'); - const yamlPath = resolve(process.cwd(), 'tests/fixtures/registry-valid.yaml'); + const yamlPath = resolve(process.cwd(), 'fixtures/registry-valid.yaml'); const code = await main([ 'validate-yaml', diff --git a/tests/fixtures/registry-env.yaml b/e2e/fixtures/registry-env.yaml similarity index 100% rename from tests/fixtures/registry-env.yaml rename to e2e/fixtures/registry-env.yaml diff --git a/tests/fixtures/registry-invalid.yaml b/e2e/fixtures/registry-invalid.yaml similarity index 100% rename from tests/fixtures/registry-invalid.yaml rename to e2e/fixtures/registry-invalid.yaml diff --git a/tests/fixtures/registry-multi.yaml b/e2e/fixtures/registry-multi.yaml similarity index 100% rename from tests/fixtures/registry-multi.yaml rename to e2e/fixtures/registry-multi.yaml diff --git a/tests/fixtures/registry-valid.yaml b/e2e/fixtures/registry-valid.yaml similarity index 100% rename from tests/fixtures/registry-valid.yaml rename to e2e/fixtures/registry-valid.yaml diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..4d2fad3 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,24 @@ +{ + "name": "@reaatech/mcp-contract-e2e", + "version": "0.1.0", + "private": true, + "description": "End-to-end tests for mcp-contract-kit", + "type": "module", + "scripts": { + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-client": "workspace:*", + "@reaatech/mcp-contract-validators": "workspace:*", + "@reaatech/mcp-contract-reporters": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*", + "@reaatech/mcp-contract-cli": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..616e103 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts new file mode 100644 index 0000000..ad6d6df --- /dev/null +++ b/e2e/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + testTimeout: 60000, + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 9765c09..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3607 +0,0 @@ -{ - "name": "mcp-contract-kit", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mcp-contract-kit", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "chalk": "^5.3.0", - "commander": "^12.0.0", - "pino": "^10.3.1", - "yaml": "^2.4.0", - "zod": "^4.3.6" - }, - "bin": { - "mcp-contract-kit": "dist/src/cli.js" - }, - "devDependencies": { - "@types/cli-progress": "^3.11.5", - "@types/node": "^25.6.0", - "@types/ws": "^8.5.10", - "@vitest/coverage-v8": "^4.1.5", - "eslint": "^9.0.0", - "globals": "^17.5.0", - "husky": "^9.0.0", - "lint-staged": "^16.4.0", - "prettier": "^3.2.0", - "typescript": "^6.0.3", - "typescript-eslint": "^8.0.0", - "vitest": "^4.1.5" - }, - "engines": { - "node": ">=22.0.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", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "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": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/cli-progress": { - "version": "3.11.6", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", - "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", - "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/type-utils": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.2", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", - "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", - "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.2", - "@typescript-eslint/types": "^8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", - "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", - "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", - "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", - "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", - "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.2", - "@typescript-eslint/tsconfig-utils": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", - "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", - "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", - "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.5", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.5", - "vitest": "4.1.5" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.5", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.5", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "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/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lint-staged": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", - "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^14.0.3", - "listr2": "^9.0.5", - "picomatch": "^4.0.3", - "string-argv": "^0.3.2", - "tinyexec": "^1.0.4", - "yaml": "^2.8.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", - "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^4.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/postcss": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", - "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/thread-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", - "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", - "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.2", - "@typescript-eslint/parser": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index ccb0380..8ffd9e6 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,21 @@ { "name": "mcp-contract-kit", - "version": "1.0.0", - "description": "Conformance test suite for MCP servers. Validate registry compliance, protocol conformance, and routing correctness.", - "author": "Rick Somers (https://reaatech.com)", - "license": "MIT", + "version": "0.1.0", + "private": true, + "description": "Conformance test suite for MCP servers — monorepo with packages for validators, reporters, and CLI", "type": "module", - "main": "dist/src/index.js", - "types": "dist/src/index.d.ts", - "bin": { - "mcp-contract-kit": "dist/src/cli.js" - }, - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - } + "scripts": { + "build": "turbo run build", + "test": "turbo run test", + "test:coverage": "turbo run test:coverage", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", + "clean": "turbo run clean && rm -rf node_modules", + "changeset": "changeset", + "version-packages": "changeset version", + "release": "turbo run build && changeset publish" }, "keywords": [ "mcp", @@ -26,59 +27,28 @@ "agent", "orchestrator" ], - "scripts": { - "build": "tsc", - "clean": "rm -rf dist coverage", - "dev": "npm run build && node --enable-source-maps dist/src/cli.js", - "lint": "eslint src tests scripts", - "audit": "npm audit --omit=dev", - "format": "prettier --write src tests scripts", - "format:check": "prettier --check src tests scripts", - "generate-report": "node --enable-source-maps dist/scripts/generate-report.js", - "typecheck": "tsc --noEmit", - "test": "vitest run", - "test:ci": "vitest run --coverage", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage", - "prepare": "husky", - "precommit": "lint-staged" - }, - "dependencies": { - "ajv": "^8.12.0", - "chalk": "^5.3.0", - "commander": "^12.0.0", - "pino": "^10.3.1", - "yaml": "^2.4.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/cli-progress": "^3.11.5", - "@types/node": "^25.6.0", - "@types/ws": "^8.5.10", - "@vitest/coverage-v8": "^4.1.5", - "eslint": "^9.0.0", - "globals": "^17.5.0", - "husky": "^9.0.0", - "lint-staged": "^16.4.0", - "prettier": "^3.2.0", - "typescript": "^6.0.3", - "typescript-eslint": "^8.0.0", - "vitest": "^4.1.5" - }, - "engines": { - "node": ">=22.0.0" - }, + "author": "Rick Somers (https://reaatech.com)", + "license": "MIT", + "packageManager": "pnpm@10.22.0", "repository": { "type": "git", "url": "https://github.com/reaatech/mcp-contract-kit.git" }, - "homepage": "https://github.com/reaatech/mcp-contract-kit#readme", "bugs": { "url": "https://github.com/reaatech/mcp-contract-kit/issues" }, - "files": [ - "dist/src", - "LICENSE", - "README.md" - ] + "homepage": "https://github.com/reaatech/mcp-contract-kit#readme", + "pnpm": { + "onlyBuiltDependencies": ["@biomejs/biome", "esbuild"] + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@changesets/changelog-github": "^0.6.0", + "@changesets/cli": "^2.28.1", + "@types/node": "^22.7.0", + "@vitest/coverage-v8": "^3.1.1", + "turbo": "^2.5.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } } diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..f91a7f6 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,55 @@ +{ + "name": "@reaatech/mcp-contract-cli", + "version": "0.1.0", + "description": "CLI tool and public API for MCP contract conformance testing", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/cli" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/cli#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "bin": { + "mcp-contract-kit": "./dist/cli.js" + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-client": "workspace:*", + "@reaatech/mcp-contract-validators": "workspace:*", + "@reaatech/mcp-contract-reporters": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*", + "commander": "^12.0.0" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts new file mode 100644 index 0000000..d851323 --- /dev/null +++ b/packages/cli/src/cli.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +/** + * CLI entry point for mcp-contract-kit. + */ + +import { main } from './main.js'; + +main() + .then((code) => { + process.exit(code); + }) + .catch((error) => { + process.stderr.write(`Error: ${(error as Error).message}\n`); + process.exit(3); + }); diff --git a/src/cli/commands/test.command.ts b/packages/cli/src/commands/test.command.ts similarity index 80% rename from src/cli/commands/test.command.ts rename to packages/cli/src/commands/test.command.ts index 448a14c..357b503 100644 --- a/src/cli/commands/test.command.ts +++ b/packages/cli/src/commands/test.command.ts @@ -3,10 +3,10 @@ */ import { writeFile } from 'node:fs/promises'; -import { formatReport } from '../../reporters/index.js'; -import { printConsoleReport } from '../../reporters/console.reporter.js'; -import { runTests } from '../../runner.js'; -import { ParsedCliArgs } from '../config.js'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import { printConsoleReport } from '@reaatech/mcp-contract-reporters'; +import type { ParsedCliArgs } from '../config.js'; +import { runTests } from '../runner.js'; export async function runTestCommand(options: ParsedCliArgs): Promise { if (!options.target) { diff --git a/src/cli/commands/validate-yaml.command.ts b/packages/cli/src/commands/validate-yaml.command.ts similarity index 74% rename from src/cli/commands/validate-yaml.command.ts rename to packages/cli/src/commands/validate-yaml.command.ts index 42f5d38..ec0bad2 100644 --- a/src/cli/commands/validate-yaml.command.ts +++ b/packages/cli/src/commands/validate-yaml.command.ts @@ -3,10 +3,10 @@ */ import { writeFile } from 'node:fs/promises'; -import { formatReport } from '../../reporters/index.js'; -import { printConsoleReport } from '../../reporters/console.reporter.js'; -import { validateRegistry } from '../../runner.js'; -import { ParsedCliArgs } from '../config.js'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import { printConsoleReport } from '@reaatech/mcp-contract-reporters'; +import type { ParsedCliArgs } from '../config.js'; +import { validateRegistry } from '../runner.js'; export async function runValidateYamlCommand(options: ParsedCliArgs): Promise { if (!options.target) { diff --git a/src/cli/config.ts b/packages/cli/src/config.ts similarity index 95% rename from src/cli/config.ts rename to packages/cli/src/config.ts index ea3c752..ece6374 100644 --- a/src/cli/config.ts +++ b/packages/cli/src/config.ts @@ -2,9 +2,9 @@ * CLI parsing and defaults. */ -import { ReportFormat } from '../reporters/index.js'; -import { Severity, TestCategory } from '../types/domain.js'; -import { getVersion } from '../version.js'; +import { Severity, TestCategory } from '@reaatech/mcp-contract-core'; +import type { ReportFormat } from '@reaatech/mcp-contract-reporters'; +import { getVersion } from './version.js'; export interface ParsedCliArgs { command?: 'test' | 'validate-yaml'; diff --git a/src/index.ts b/packages/cli/src/index.ts similarity index 58% rename from src/index.ts rename to packages/cli/src/index.ts index 95454f6..8e56a2e 100644 --- a/src/index.ts +++ b/packages/cli/src/index.ts @@ -4,6 +4,11 @@ * Conformance test suite for MCP servers. */ +// CLI internals (exposed for e2e testing) +export { main } from './main.js'; +export { parseArgs, printHelp, CLI_VERSION } from './config.js'; +export type { ParsedCliArgs } from './config.js'; + // Runner export { runTests, @@ -17,12 +22,14 @@ export type { RunOptions } from './runner.js'; // Domain types export { TestSuite, + Severity, + TestCategory, +} from '@reaatech/mcp-contract-core'; +export type { TestResult, TestReport, Validator, ValidationContext, - Severity, - TestCategory, ValidationError, MCPClient, MCPRequest, @@ -30,7 +37,7 @@ export { MCPError, ToolDefinition, ToolResult, -} from './types/domain.js'; +} from '@reaatech/mcp-contract-core'; // Schemas export { @@ -42,7 +49,7 @@ export { AgentResponseContractSchema, type AgentConfig, type AgentType, -} from './types/schemas.js'; +} from '@reaatech/mcp-contract-core'; // Reporters export { @@ -52,16 +59,22 @@ export { formatMarkdownReport, formatReport, type ReportFormat, -} from './reporters/index.js'; +} from '@reaatech/mcp-contract-reporters'; // Validators -export * from './validators/index.js'; +export * from '@reaatech/mcp-contract-validators'; // MCP Client -export { MCPHttpClient, createMCPClient } from './mcp-client/index.js'; +export { MCPHttpClient, createMCPClient } from '@reaatech/mcp-contract-client'; // Utilities -export { generateUUID, generateId, now, isValidURL, isPrivateURL } from './utils/index.js'; +export { + generateUUID, + generateId, + now, + isValidURL, + isPrivateURL, +} from '@reaatech/mcp-contract-core'; // Observability export { @@ -78,8 +91,8 @@ export { withSpan, toTraceParent, fromTraceParent, -} from './observability/index.js'; -export type { Span, SpanContext } from './observability/index.js'; +} from '@reaatech/mcp-contract-observability'; +export type { Span, SpanContext } from '@reaatech/mcp-contract-observability'; // HTML Reporter -export { generateHtmlReport } from './reporters/index.js'; +export { generateHtmlReport } from '@reaatech/mcp-contract-reporters'; diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts new file mode 100644 index 0000000..8437678 --- /dev/null +++ b/packages/cli/src/main.ts @@ -0,0 +1,33 @@ +/** + * CLI main function and argument parsing. + */ + +import { runTestCommand } from './commands/test.command.js'; +import { runValidateYamlCommand } from './commands/validate-yaml.command.js'; +import { CLI_VERSION, parseArgs, printHelp } from './config.js'; + +export async function main(argv: string[] = process.argv.slice(2)): Promise { + const args = parseArgs(argv); + + if (args.help) { + process.stdout.write(`${printHelp()}\n`); + return 0; + } + + if (args.version) { + process.stdout.write(`mcp-contract-kit v${CLI_VERSION}\n`); + return 0; + } + + if (!args.command) { + process.stderr.write(`${printHelp()}\n`); + return 1; + } + + switch (args.command) { + case 'test': + return runTestCommand(args); + case 'validate-yaml': + return runValidateYamlCommand(args); + } +} diff --git a/src/runner.ts b/packages/cli/src/runner.ts similarity index 93% rename from src/runner.ts rename to packages/cli/src/runner.ts index 639d1c8..77ab0be 100644 --- a/src/runner.ts +++ b/packages/cli/src/runner.ts @@ -2,29 +2,27 @@ * Test runner orchestrator. */ -import { formatReport, ReportFormat } from './reporters/index.js'; -import { createMCPClient } from './mcp-client/index.js'; -import { MetricNames, metrics } from './observability/index.js'; -import { +import { createMCPClient } from '@reaatech/mcp-contract-client'; +import { Severity, TestCategory, TestSuite, generateUUID } from '@reaatech/mcp-contract-core'; +import type { MCPClient, - Severity, - TestCategory, TestReport, TestResult, - TestSuite, ValidationContext, Validator, - generateUUID, -} from './types/domain.js'; -import { now } from './utils/index.js'; -import { getVersion } from './version.js'; +} from '@reaatech/mcp-contract-core'; +import { now } from '@reaatech/mcp-contract-core'; +import { MetricNames, metrics } from '@reaatech/mcp-contract-observability'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import type { ReportFormat } from '@reaatech/mcp-contract-reporters'; import { getPerformanceValidators, getProtocolValidators, getRegistryValidators, getRoutingValidators, getSecurityValidators, -} from './validators/index.js'; +} from '@reaatech/mcp-contract-validators'; +import { getVersion } from './version.js'; export interface RunOptions { endpoint: string; diff --git a/src/version.ts b/packages/cli/src/version.ts similarity index 84% rename from src/version.ts rename to packages/cli/src/version.ts index 2d7a0b5..d444873 100644 --- a/src/version.ts +++ b/packages/cli/src/version.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { resolve, dirname } from 'node:path'; let _version: string | undefined; @@ -10,15 +10,14 @@ export function getVersion(): string { const candidates = [ resolve(__dirname, '../package.json'), resolve(__dirname, '../../package.json'), + resolve(__dirname, '../../../package.json'), ]; for (const pkgPath of candidates) { try { const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string }; _version = pkg.version; return _version; - } catch { - continue; - } + } catch {} } _version = '0.0.0'; return _version; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..45a7e0f --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,48 @@ +{ + "name": "@reaatech/mcp-contract-client", + "version": "0.1.0", + "description": "MCP client SDK for connecting to and testing MCP servers", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/client" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/client#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/mcp-client/client.ts b/packages/client/src/client.ts similarity index 87% rename from src/mcp-client/client.ts rename to packages/client/src/client.ts index 005cb66..5d8e719 100644 --- a/src/mcp-client/client.ts +++ b/packages/client/src/client.ts @@ -3,14 +3,21 @@ * Implements the MCPClient interface for conformance testing */ -import { MCPClient, MCPRequest, MCPResponse, ToolDefinition, ToolResult } from '../types/domain.js'; -import { generateId } from '../utils/index.js'; +import type { + MCPClient, + MCPRequest, + MCPResponse, + ToolDefinition, + ToolResult, +} from '@reaatech/mcp-contract-core'; +import { generateId } from '@reaatech/mcp-contract-core'; import { buildInitializeRequest, buildListToolsRequest, buildToolCallRequest, } from './request-builder.js'; -import { HttpTransport, MCPTransport } from './transport.js'; +import { HttpTransport } from './transport.js'; +import type { MCPTransport } from './transport.js'; interface MCPClientOptions { endpoint: string; @@ -24,9 +31,6 @@ interface MCPClientOptions { */ export class MCPHttpClient implements MCPClient { private readonly endpoint: string; - private readonly timeout: number; - private readonly retries: number; - private readonly headers: Record; private sessionId: string | null = null; private connected = false; private requestId = 0; @@ -34,12 +38,6 @@ export class MCPHttpClient implements MCPClient { constructor(options: MCPClientOptions) { this.endpoint = options.endpoint; - this.timeout = options.timeout; - this.retries = options.retries; - this.headers = { - 'Content-Type': 'application/json', - ...options.headers, - }; this.transport = new HttpTransport(options); } diff --git a/src/mcp-client/index.ts b/packages/client/src/index.ts similarity index 100% rename from src/mcp-client/index.ts rename to packages/client/src/index.ts diff --git a/src/mcp-client/request-builder.ts b/packages/client/src/request-builder.ts similarity index 81% rename from src/mcp-client/request-builder.ts rename to packages/client/src/request-builder.ts index 31033b9..7091e74 100644 --- a/src/mcp-client/request-builder.ts +++ b/packages/client/src/request-builder.ts @@ -2,10 +2,10 @@ * Helpers for constructing MCP JSON-RPC requests and tracing headers. */ -import { MCPRequest } from '../types/domain.js'; -import { getCurrentContext, toTraceParent } from '../observability/tracing.js'; -import { generateUUID } from '../utils/index.js'; -import { getVersion } from '../version.js'; +import type { MCPRequest } from '@reaatech/mcp-contract-core'; +import { generateUUID } from '@reaatech/mcp-contract-core'; +import { getVersion } from '@reaatech/mcp-contract-core'; +import { getCurrentContext, toTraceParent } from '@reaatech/mcp-contract-observability'; export function buildRequest( method: string, diff --git a/src/mcp-client/transport.ts b/packages/client/src/transport.ts similarity index 94% rename from src/mcp-client/transport.ts rename to packages/client/src/transport.ts index 5f9d4a8..653dd52 100644 --- a/src/mcp-client/transport.ts +++ b/packages/client/src/transport.ts @@ -2,8 +2,8 @@ * Transport layer for MCP requests. */ -import { MCPRequest, MCPResponse } from '../types/domain.js'; -import { retry } from '../utils/index.js'; +import type { MCPRequest, MCPResponse } from '@reaatech/mcp-contract-core'; +import { retry } from '@reaatech/mcp-contract-core'; export interface TransportResponse { body: MCPResponse; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/client/vitest.config.ts b/packages/client/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/client/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..c123343 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,48 @@ +{ + "name": "@reaatech/mcp-contract-core", + "version": "0.1.0", + "description": "Core domain types, schemas, and utilities for MCP contract validation", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/core" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/core#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "ajv": "^8.12.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/types/domain.ts b/packages/core/src/domain.ts similarity index 99% rename from src/types/domain.ts rename to packages/core/src/domain.ts index 04bde9e..94c2fb3 100644 --- a/src/types/domain.ts +++ b/packages/core/src/domain.ts @@ -2,8 +2,6 @@ * Core domain types for mcp-contract-kit */ -export { generateUUID } from '../utils/index.js'; - /** Severity levels for test results */ export enum Severity { /** Critical failures that must be fixed before production */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..97fc686 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,4 @@ +export * from './domain.js'; +export * from './schemas.js'; +export * from './utils.js'; +export { getVersion } from './version.js'; diff --git a/src/types/schemas.ts b/packages/core/src/schemas.ts similarity index 98% rename from src/types/schemas.ts rename to packages/core/src/schemas.ts index 7139d31..d9e2de6 100644 --- a/src/types/schemas.ts +++ b/packages/core/src/schemas.ts @@ -139,4 +139,4 @@ export function isValidUUID(value: string): boolean { return UUID_REGEX.test(value); } -export { isValidURL, isPrivateURL } from '../utils/index.js'; +export { isValidURL, isPrivateURL } from './utils.js'; diff --git a/src/utils/index.ts b/packages/core/src/utils.ts similarity index 90% rename from src/utils/index.ts rename to packages/core/src/utils.ts index f694625..b5fd5dd 100644 --- a/src/utils/index.ts +++ b/packages/core/src/utils.ts @@ -15,9 +15,9 @@ export function generateId(): string { export function generateUUID(): string { const bytes = crypto.getRandomValues(new Uint8Array(16)); // Set version 4 - bytes[6] = (bytes[6]! & 0x0f) | 0x40; - // Set variant - bytes[8] = (bytes[8]! & 0x3f) | 0x80; + bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40; + // Set version + bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80; const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); @@ -57,7 +57,7 @@ export async function retry( } catch (error) { lastError = error as Error; if (attempt < maxRetries) { - const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs); + const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs); await sleep(delay); } } @@ -90,7 +90,7 @@ export function now(): string { */ export function truncate(str: string, maxLength: number): string { if (str.length <= maxLength) return str; - return str.slice(0, maxLength - 3) + '...'; + return `${str.slice(0, maxLength - 3)}...`; } /** @@ -120,9 +120,9 @@ export function percentile(values: number[], p: number): number { const index = (p / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); - if (lower === upper) return sorted[lower]!; + if (lower === upper) return sorted[lower] ?? 0; const weight = index - lower; - return sorted[lower]! * (1 - weight) + sorted[upper]! * weight; + return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight; } /** @@ -183,8 +183,8 @@ export function calculateStats(values: number[]): { const sum = values.reduce((a, b) => a + b, 0); return { - min: sorted[0]!, - max: sorted[sorted.length - 1]!, + min: sorted[0] ?? 0, + max: sorted[sorted.length - 1] ?? 0, mean: sum / values.length, p50: percentile(sorted, 50), p90: percentile(sorted, 90), diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts new file mode 100644 index 0000000..d444873 --- /dev/null +++ b/packages/core/src/version.ts @@ -0,0 +1,24 @@ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +let _version: string | undefined; + +export function getVersion(): string { + if (_version) return _version; + const __dirname = dirname(fileURLToPath(import.meta.url)); + const candidates = [ + resolve(__dirname, '../package.json'), + resolve(__dirname, '../../package.json'), + resolve(__dirname, '../../../package.json'), + ]; + for (const pkgPath of candidates) { + try { + const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string }; + _version = pkg.version; + return _version; + } catch {} + } + _version = '0.0.0'; + return _version; +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/observability/package.json b/packages/observability/package.json new file mode 100644 index 0000000..17039b5 --- /dev/null +++ b/packages/observability/package.json @@ -0,0 +1,46 @@ +{ + "name": "@reaatech/mcp-contract-observability", + "version": "0.1.0", + "description": "Structured logging, metrics, and tracing for MCP contract validation", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/observability" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/observability#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "pino": "^10.3.1" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/observability/index.ts b/packages/observability/src/index.ts similarity index 100% rename from src/observability/index.ts rename to packages/observability/src/index.ts diff --git a/src/observability/logger.ts b/packages/observability/src/logger.ts similarity index 100% rename from src/observability/logger.ts rename to packages/observability/src/logger.ts diff --git a/src/observability/metrics.ts b/packages/observability/src/metrics.ts similarity index 96% rename from src/observability/metrics.ts rename to packages/observability/src/metrics.ts index a98539c..a472463 100644 --- a/src/observability/metrics.ts +++ b/packages/observability/src/metrics.ts @@ -25,7 +25,7 @@ class MetricsCollector { } /** Increment a counter */ - inc(name: string, value: number = 1, labels?: Record): void { + inc(name: string, value = 1, labels?: Record): void { const key = this.makeKey(name, labels); const current = this.counters.get(key) ?? 0; this.counters.set(key, current + value); @@ -43,7 +43,7 @@ class MetricsCollector { if (!this.histograms.has(key)) { this.histograms.set(key, []); } - this.histograms.get(key)!.push(durationMs); + this.histograms.get(key)?.push(durationMs); } /** Get histogram data for a metric */ diff --git a/src/observability/tracing.ts b/packages/observability/src/tracing.ts similarity index 98% rename from src/observability/tracing.ts rename to packages/observability/src/tracing.ts index dc446e7..d77707d 100644 --- a/src/observability/tracing.ts +++ b/packages/observability/src/tracing.ts @@ -20,7 +20,7 @@ export interface Span { } /** Generate a random hex string for trace/span IDs */ -function generateId(length: number = 16): string { +function generateId(length = 16): string { const bytes = crypto.getRandomValues(new Uint8Array(length / 2)); return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); } diff --git a/packages/observability/tsconfig.json b/packages/observability/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/observability/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/observability/vitest.config.ts b/packages/observability/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/observability/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/reporters/package.json b/packages/reporters/package.json new file mode 100644 index 0000000..9b3cd27 --- /dev/null +++ b/packages/reporters/package.json @@ -0,0 +1,47 @@ +{ + "name": "@reaatech/mcp-contract-reporters", + "version": "0.1.0", + "description": "Report formatters for MCP contract validation (console, JSON, markdown, HTML)", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/reporters" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/reporters#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/reporters/console.reporter.ts b/packages/reporters/src/console.reporter.ts similarity index 97% rename from src/reporters/console.reporter.ts rename to packages/reporters/src/console.reporter.ts index 3c5da92..768a772 100644 --- a/src/reporters/console.reporter.ts +++ b/packages/reporters/src/console.reporter.ts @@ -2,7 +2,8 @@ * Console reporter - colored terminal output */ -import { TestReport, Severity } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; const COLORS = { reset: '\x1b[0m', diff --git a/src/reporters/html.reporter.ts b/packages/reporters/src/html.reporter.ts similarity index 98% rename from src/reporters/html.reporter.ts rename to packages/reporters/src/html.reporter.ts index aee2367..7ce943e 100644 --- a/src/reporters/html.reporter.ts +++ b/packages/reporters/src/html.reporter.ts @@ -2,8 +2,8 @@ * HTML Reporter — generates an interactive HTML dashboard for test results */ -import type { TestReport, TestResult } from '../types/domain.js'; -import { Severity } from '../types/domain.js'; +import type { TestReport, TestResult } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; /** * Generate an HTML report from test results diff --git a/src/reporters/index.ts b/packages/reporters/src/index.ts similarity index 93% rename from src/reporters/index.ts rename to packages/reporters/src/index.ts index 1e8299c..4666cc0 100644 --- a/src/reporters/index.ts +++ b/packages/reporters/src/index.ts @@ -2,11 +2,11 @@ * Reporters barrel exports */ -import { TestReport } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; import { formatConsoleReport } from './console.reporter.js'; +import { generateHtmlReport } from './html.reporter.js'; import { formatJsonReport } from './json.reporter.js'; import { formatMarkdownReport } from './markdown.reporter.js'; -import { generateHtmlReport } from './html.reporter.js'; export { formatConsoleReport, printConsoleReport } from './console.reporter.js'; export { formatJsonReport } from './json.reporter.js'; @@ -23,7 +23,6 @@ export async function formatReport(report: TestReport, format: ReportFormat): Pr return formatMarkdownReport(report); case 'html': return generateHtmlReport(report); - case 'console': default: return formatConsoleReport(report); } diff --git a/src/reporters/json.reporter.ts b/packages/reporters/src/json.reporter.ts similarity index 71% rename from src/reporters/json.reporter.ts rename to packages/reporters/src/json.reporter.ts index 6a60411..73d61d2 100644 --- a/src/reporters/json.reporter.ts +++ b/packages/reporters/src/json.reporter.ts @@ -2,7 +2,7 @@ * JSON reporter - machine-readable output */ -import { TestReport } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; export function formatJsonReport(report: TestReport): string { return JSON.stringify(report, null, 2); diff --git a/src/reporters/markdown.reporter.ts b/packages/reporters/src/markdown.reporter.ts similarity index 95% rename from src/reporters/markdown.reporter.ts rename to packages/reporters/src/markdown.reporter.ts index ff0de3f..f0f047b 100644 --- a/src/reporters/markdown.reporter.ts +++ b/packages/reporters/src/markdown.reporter.ts @@ -2,7 +2,8 @@ * Markdown reporter - GitHub-flavored markdown output */ -import { TestReport, Severity } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; function severityBadge(severity: Severity): string { switch (severity) { diff --git a/packages/reporters/tsconfig.json b/packages/reporters/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/reporters/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/reporters/vitest.config.ts b/packages/reporters/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/reporters/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/validators/package.json b/packages/validators/package.json new file mode 100644 index 0000000..2dabd21 --- /dev/null +++ b/packages/validators/package.json @@ -0,0 +1,52 @@ +{ + "name": "@reaatech/mcp-contract-validators", + "version": "0.1.0", + "description": "Conformance validators for MCP servers (protocol, registry, routing, security, performance)", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/validators" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/validators#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-client": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*", + "ajv": "^8.12.0", + "yaml": "^2.4.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/validators/index.ts b/packages/validators/src/index.ts similarity index 100% rename from src/validators/index.ts rename to packages/validators/src/index.ts diff --git a/src/validators/performance/concurrency.validator.ts b/packages/validators/src/performance/concurrency.validator.ts similarity index 94% rename from src/validators/performance/concurrency.validator.ts rename to packages/validators/src/performance/concurrency.validator.ts index 78667d1..a13347b 100644 --- a/src/validators/performance/concurrency.validator.ts +++ b/packages/validators/src/performance/concurrency.validator.ts @@ -2,14 +2,8 @@ * Concurrency validator - tests concurrent request handling */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; /** * Concurrency validator implementation diff --git a/src/validators/performance/index.ts b/packages/validators/src/performance/index.ts similarity index 87% rename from src/validators/performance/index.ts rename to packages/validators/src/performance/index.ts index f6a1413..a74bc53 100644 --- a/src/validators/performance/index.ts +++ b/packages/validators/src/performance/index.ts @@ -2,9 +2,10 @@ * Performance validator orchestrator */ -import { Validator, TestCategory } from '../../types/domain.js'; -import { latencyValidator } from './latency.validator.js'; +import type { Validator } from '@reaatech/mcp-contract-core'; +import { TestCategory } from '@reaatech/mcp-contract-core'; import { concurrencyValidator } from './concurrency.validator.js'; +import { latencyValidator } from './latency.validator.js'; import { rateLimitValidator } from './rate-limit.validator.js'; /** diff --git a/src/validators/performance/latency.validator.ts b/packages/validators/src/performance/latency.validator.ts similarity index 94% rename from src/validators/performance/latency.validator.ts rename to packages/validators/src/performance/latency.validator.ts index 6119df3..6b494c7 100644 --- a/src/validators/performance/latency.validator.ts +++ b/packages/validators/src/performance/latency.validator.ts @@ -2,14 +2,8 @@ * Latency validator - measures response times */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now, calculateStats } from '../../utils/index.js'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, calculateStats, now } from '@reaatech/mcp-contract-core'; /** Default latency thresholds in milliseconds */ const THRESHOLDS = { diff --git a/src/validators/performance/rate-limit.validator.ts b/packages/validators/src/performance/rate-limit.validator.ts similarity index 94% rename from src/validators/performance/rate-limit.validator.ts rename to packages/validators/src/performance/rate-limit.validator.ts index e5bb4e4..8eb66e6 100644 --- a/src/validators/performance/rate-limit.validator.ts +++ b/packages/validators/src/performance/rate-limit.validator.ts @@ -2,14 +2,8 @@ * Rate limit validator - tests rate limiting behavior */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; interface RateLimitOptions { burstRequests?: number; diff --git a/src/validators/protocol/index.ts b/packages/validators/src/protocol/index.ts similarity index 88% rename from src/validators/protocol/index.ts rename to packages/validators/src/protocol/index.ts index 8947bc2..bbd6f77 100644 --- a/src/validators/protocol/index.ts +++ b/packages/validators/src/protocol/index.ts @@ -2,11 +2,12 @@ * Protocol validator orchestrator */ -import { Validator, TestCategory } from '../../types/domain.js'; +import type { Validator } from '@reaatech/mcp-contract-core'; +import { TestCategory } from '@reaatech/mcp-contract-core'; import { jsonrpcValidator } from './jsonrpc.validator.js'; +import { sessionValidator } from './session.validator.js'; import { toolDiscoveryValidator } from './tool-discovery.validator.js'; import { toolExecutionValidator } from './tool-execution.validator.js'; -import { sessionValidator } from './session.validator.js'; /** * All protocol validators diff --git a/src/validators/protocol/jsonrpc.validator.ts b/packages/validators/src/protocol/jsonrpc.validator.ts similarity index 97% rename from src/validators/protocol/jsonrpc.validator.ts rename to packages/validators/src/protocol/jsonrpc.validator.ts index 62a4a12..786e057 100644 --- a/src/validators/protocol/jsonrpc.validator.ts +++ b/packages/validators/src/protocol/jsonrpc.validator.ts @@ -2,16 +2,14 @@ * JSON-RPC 2.0 compliance validator */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, +import type { MCPRequest, MCPResponse, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; + TestResult, + ValidationContext, + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; /** * Validate JSON-RPC 2.0 response format diff --git a/src/validators/protocol/session.validator.ts b/packages/validators/src/protocol/session.validator.ts similarity index 93% rename from src/validators/protocol/session.validator.ts rename to packages/validators/src/protocol/session.validator.ts index 1d75de0..84100b4 100644 --- a/src/validators/protocol/session.validator.ts +++ b/packages/validators/src/protocol/session.validator.ts @@ -2,15 +2,9 @@ * Session management validator */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; -import { createMCPClient } from '../../mcp-client/index.js'; +import { createMCPClient } from '@reaatech/mcp-contract-client'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; /** * Session validator implementation diff --git a/src/validators/protocol/tool-discovery.validator.ts b/packages/validators/src/protocol/tool-discovery.validator.ts similarity index 96% rename from src/validators/protocol/tool-discovery.validator.ts rename to packages/validators/src/protocol/tool-discovery.validator.ts index b783dcc..4b829c1 100644 --- a/src/validators/protocol/tool-discovery.validator.ts +++ b/packages/validators/src/protocol/tool-discovery.validator.ts @@ -2,16 +2,14 @@ * Tool discovery validator - validates tools/list response */ -import Ajv from 'ajv'; -import { - Validator, +import type { TestResult, - ValidationContext, - TestCategory, - Severity, ToolDefinition, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; + ValidationContext, + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; +import Ajv from 'ajv'; const ajv = new Ajv.default(); diff --git a/src/validators/protocol/tool-execution.validator.ts b/packages/validators/src/protocol/tool-execution.validator.ts similarity index 91% rename from src/validators/protocol/tool-execution.validator.ts rename to packages/validators/src/protocol/tool-execution.validator.ts index eebbe17..d15d465 100644 --- a/src/validators/protocol/tool-execution.validator.ts +++ b/packages/validators/src/protocol/tool-execution.validator.ts @@ -2,15 +2,13 @@ * Tool execution validator - validates tools/call behavior */ -import { - Validator, +import type { TestResult, - ValidationContext, - TestCategory, - Severity, ToolDefinition, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; + ValidationContext, + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; function buildSampleValue(schema: Record): unknown { if (Array.isArray(schema.enum) && schema.enum.length > 0) { @@ -54,11 +52,11 @@ function buildValidArguments(tool: ToolDefinition): Record { : []; const args: Record = {}; - required.forEach((key) => { + for (const key of required) { if (properties[key]) { args[key] = buildSampleValue(properties[key]); } - }); + } return args; } @@ -95,7 +93,18 @@ export const toolExecutionValidator: Validator = { } // Test execution of the first tool with synthesized valid args - const firstTool = tools[0]!; + const firstTool = tools[0]; + if (!firstTool) { + return { + validator: this.name, + category: this.category, + passed: false, + severity: this.severity, + message: 'tools/list returned empty array', + durationMs: Math.round(performance.now() - start), + timestamp: now(), + }; + } const validArgs = buildValidArguments(firstTool); // Test 1: Call a valid tool diff --git a/src/validators/registry/env-expansion.validator.ts b/packages/validators/src/registry/env-expansion.validator.ts similarity index 93% rename from src/validators/registry/env-expansion.validator.ts rename to packages/validators/src/registry/env-expansion.validator.ts index 5f27b48..25cc799 100644 --- a/src/validators/registry/env-expansion.validator.ts +++ b/packages/validators/src/registry/env-expansion.validator.ts @@ -3,15 +3,14 @@ * Validates ${ENV_VAR} syntax and detects circular references */ -import { - Validator, +import { readFileSync } from 'node:fs'; +import type { TestResult, ValidationContext, - TestCategory, - Severity, ValidationError, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; const ENV_VAR_REGEX = /\$\{([^}]+)\}/g; @@ -27,7 +26,7 @@ interface EnvExpansionResult { */ export function extractEnvVars(str: string): string[] { const matches = str.matchAll(ENV_VAR_REGEX); - return Array.from(matches, (m) => m[1]!); + return Array.from(matches, (m) => m[1] ?? '').filter(Boolean); } /** @@ -41,7 +40,7 @@ function validateEnvSyntax(str: string): ValidationError[] { if (incompleteMatches) { errors.push({ field: 'env_syntax', - message: `Incomplete environment variable reference(s) found`, + message: 'Incomplete environment variable reference(s) found', severity: Severity.CRITICAL, type: 'INCOMPLETE_ENV_VAR', }); @@ -50,8 +49,8 @@ function validateEnvSyntax(str: string): ValidationError[] { // Check for invalid variable names const allMatches = str.matchAll(ENV_VAR_REGEX); for (const match of allMatches) { - const varName = match[1]!; - if (!/^[A-Z_][A-Z0-9_]*$/.test(varName)) { + const varName = match[1]; + if (!varName || !/^[A-Z_][A-Z0-9_]*$/.test(varName)) { errors.push({ field: 'env_syntax', message: `Invalid environment variable name '${varName}'. Must be uppercase letters, numbers, and underscores.`, @@ -183,8 +182,7 @@ export const envExpansionValidator: Validator = { let content: string; try { - const fs = await import('node:fs'); - content = fs.readFileSync(yamlPath, 'utf-8'); + content = readFileSync(yamlPath, 'utf-8'); } catch (error) { return { validator: this.name, diff --git a/src/validators/registry/index.ts b/packages/validators/src/registry/index.ts similarity index 89% rename from src/validators/registry/index.ts rename to packages/validators/src/registry/index.ts index 6eb4d14..bb6d7a6 100644 --- a/src/validators/registry/index.ts +++ b/packages/validators/src/registry/index.ts @@ -2,10 +2,11 @@ * Registry validator orchestrator */ -import { Validator, TestCategory } from '../../types/domain.js'; -import { schemaValidator } from './schema.validator.js'; -import { invariantValidator } from './invariant.validator.js'; +import type { Validator } from '@reaatech/mcp-contract-core'; +import { TestCategory } from '@reaatech/mcp-contract-core'; import { envExpansionValidator } from './env-expansion.validator.js'; +import { invariantValidator } from './invariant.validator.js'; +import { schemaValidator } from './schema.validator.js'; /** * All registry validators diff --git a/src/validators/registry/invariant.validator.ts b/packages/validators/src/registry/invariant.validator.ts similarity index 96% rename from src/validators/registry/invariant.validator.ts rename to packages/validators/src/registry/invariant.validator.ts index 44155d5..9c7bdee 100644 --- a/src/validators/registry/invariant.validator.ts +++ b/packages/validators/src/registry/invariant.validator.ts @@ -3,16 +3,15 @@ * Ensures: one default agent, unique IDs, valid endpoints, etc. */ -import { - Validator, +import type { + AgentConfig, + AgentConfigInput, TestResult, ValidationContext, - TestCategory, - Severity, ValidationError, -} from '../../types/domain.js'; -import { AgentConfig, AgentConfigInput, isPrivateURL, isValidURL } from '../../types/schemas.js'; -import { now } from '../../utils/index.js'; + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, isPrivateURL, isValidURL, now } from '@reaatech/mcp-contract-core'; import { loadRegistryFile } from './shared.js'; interface InvariantValidationResult { diff --git a/src/validators/registry/schema.validator.ts b/packages/validators/src/registry/schema.validator.ts similarity index 93% rename from src/validators/registry/schema.validator.ts rename to packages/validators/src/registry/schema.validator.ts index 8b70353..a5287c1 100644 --- a/src/validators/registry/schema.validator.ts +++ b/packages/validators/src/registry/schema.validator.ts @@ -2,16 +2,14 @@ * Schema validator for agent registry YAML files */ -import { - Validator, +import type { + AgentConfigInput, TestResult, ValidationContext, - TestCategory, - Severity, ValidationError, -} from '../../types/domain.js'; -import { AgentConfigInput } from '../../types/schemas.js'; -import { now } from '../../utils/index.js'; + Validator, +} from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; import { loadRegistryFile } from './shared.js'; interface SchemaValidationResult { diff --git a/src/validators/registry/shared.ts b/packages/validators/src/registry/shared.ts similarity index 89% rename from src/validators/registry/shared.ts rename to packages/validators/src/registry/shared.ts index dacbb47..c15250a 100644 --- a/src/validators/registry/shared.ts +++ b/packages/validators/src/registry/shared.ts @@ -3,9 +3,9 @@ */ import { readFileSync, statSync } from 'node:fs'; +import type { AgentConfigInput, ValidationError } from '@reaatech/mcp-contract-core'; +import { AgentConfigSchema, Severity } from '@reaatech/mcp-contract-core'; import { parse } from 'yaml'; -import { Severity, ValidationError } from '../../types/domain.js'; -import { AgentConfigInput, AgentConfigSchema } from '../../types/schemas.js'; export const MAX_REGISTRY_FILE_SIZE = 1024 * 1024; // 1MB @@ -99,18 +99,18 @@ export function loadRegistryFile(yamlPath: string): LoadedRegistry { const agents: AgentConfigInput[] = []; const errors: ValidationError[] = []; - candidates.forEach((candidate, index) => { + for (const [index, candidate] of candidates.entries()) { const validation = AgentConfigSchema.safeParse(candidate); if (!validation.success) { - validation.error.issues.forEach((issue) => { + for (const issue of validation.error.issues) { errors.push({ - field: issue.path.length > 0 ? `${index}.${issue.path.join('.')}` : `${index}`, + field: issue.path.length > 0 ? `${index}.${issue.path.join('.')}` : `${String(index)}`, message: issue.message, severity: Severity.CRITICAL, type: 'SCHEMA_VALIDATION_ERROR', }); - }); - return; + } + continue; } const config = validation.data; @@ -124,7 +124,7 @@ export function loadRegistryFile(yamlPath: string): LoadedRegistry { } agents.push(config); - }); + } return { raw: parsed, diff --git a/src/validators/routing/compatibility.validator.ts b/packages/validators/src/routing/compatibility.validator.ts similarity index 95% rename from src/validators/routing/compatibility.validator.ts rename to packages/validators/src/routing/compatibility.validator.ts index 5f4f679..ea22762 100644 --- a/src/validators/routing/compatibility.validator.ts +++ b/packages/validators/src/routing/compatibility.validator.ts @@ -2,15 +2,14 @@ * Compatibility validator - end-to-end contract testing */ +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; import { - Validator, - TestResult, - ValidationContext, - TestCategory, + AgentResponseContractSchema, Severity, -} from '../../types/domain.js'; -import { AgentResponseContractSchema } from '../../types/schemas.js'; -import { now, generateUUID } from '../../utils/index.js'; + TestCategory, + generateUUID, + now, +} from '@reaatech/mcp-contract-core'; /** * Test scenarios for compatibility testing diff --git a/src/validators/routing/index.ts b/packages/validators/src/routing/index.ts similarity index 87% rename from src/validators/routing/index.ts rename to packages/validators/src/routing/index.ts index 269340b..4afc807 100644 --- a/src/validators/routing/index.ts +++ b/packages/validators/src/routing/index.ts @@ -2,10 +2,11 @@ * Routing validator orchestrator */ -import { Validator, TestCategory } from '../../types/domain.js'; +import type { Validator } from '@reaatech/mcp-contract-core'; +import { TestCategory } from '@reaatech/mcp-contract-core'; +import { compatibilityValidator } from './compatibility.validator.js'; import { requestContractValidator } from './request-contract.validator.js'; import { responseContractValidator } from './response-contract.validator.js'; -import { compatibilityValidator } from './compatibility.validator.js'; /** * All routing validators diff --git a/src/validators/routing/request-contract.validator.ts b/packages/validators/src/routing/request-contract.validator.ts similarity index 94% rename from src/validators/routing/request-contract.validator.ts rename to packages/validators/src/routing/request-contract.validator.ts index bff307b..7ba9e51 100644 --- a/src/validators/routing/request-contract.validator.ts +++ b/packages/validators/src/routing/request-contract.validator.ts @@ -2,16 +2,19 @@ * Request contract validator - validates orchestrator → agent request format */ -import { - Validator, +import type { TestResult, + ToolDefinition, ValidationContext, - TestCategory, + Validator, +} from '@reaatech/mcp-contract-core'; +import { + AgentRequestContractSchema, Severity, - ToolDefinition, -} from '../../types/domain.js'; -import { AgentRequestContractSchema } from '../../types/schemas.js'; -import { now, generateUUID } from '../../utils/index.js'; + TestCategory, + generateUUID, + now, +} from '@reaatech/mcp-contract-core'; /** * Create a valid test request diff --git a/src/validators/routing/response-contract.validator.ts b/packages/validators/src/routing/response-contract.validator.ts similarity index 94% rename from src/validators/routing/response-contract.validator.ts rename to packages/validators/src/routing/response-contract.validator.ts index f546590..2535bca 100644 --- a/src/validators/routing/response-contract.validator.ts +++ b/packages/validators/src/routing/response-contract.validator.ts @@ -2,15 +2,14 @@ * Response contract validator - validates agent → orchestrator response format */ +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; import { - Validator, - TestResult, - ValidationContext, - TestCategory, + AgentResponseContractSchema, Severity, -} from '../../types/domain.js'; -import { AgentResponseContractSchema } from '../../types/schemas.js'; -import { now, generateUUID } from '../../utils/index.js'; + TestCategory, + generateUUID, + now, +} from '@reaatech/mcp-contract-core'; /** * Validate a response against the contract schema diff --git a/src/validators/security/auth.validator.ts b/packages/validators/src/security/auth.validator.ts similarity index 91% rename from src/validators/security/auth.validator.ts rename to packages/validators/src/security/auth.validator.ts index 81b14e8..c344460 100644 --- a/src/validators/security/auth.validator.ts +++ b/packages/validators/src/security/auth.validator.ts @@ -2,15 +2,9 @@ * Authentication validator */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now } from '../../utils/index.js'; -import { createMCPClient } from '../../mcp-client/index.js'; +import { createMCPClient } from '@reaatech/mcp-contract-client'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, now } from '@reaatech/mcp-contract-core'; /** * Auth validator implementation diff --git a/src/validators/security/index.ts b/packages/validators/src/security/index.ts similarity index 86% rename from src/validators/security/index.ts rename to packages/validators/src/security/index.ts index 792cc37..a38f244 100644 --- a/src/validators/security/index.ts +++ b/packages/validators/src/security/index.ts @@ -2,10 +2,11 @@ * Security validator orchestrator */ -import { Validator, TestCategory } from '../../types/domain.js'; -import { ssrfValidator } from './ssrf.validator.js'; +import type { Validator } from '@reaatech/mcp-contract-core'; +import { TestCategory } from '@reaatech/mcp-contract-core'; import { authValidator } from './auth.validator.js'; import { inputSanitizationValidator } from './input-sanitization.validator.js'; +import { ssrfValidator } from './ssrf.validator.js'; /** * All security validators diff --git a/src/validators/security/input-sanitization.validator.ts b/packages/validators/src/security/input-sanitization.validator.ts similarity index 94% rename from src/validators/security/input-sanitization.validator.ts rename to packages/validators/src/security/input-sanitization.validator.ts index ed6af9f..feccb39 100644 --- a/src/validators/security/input-sanitization.validator.ts +++ b/packages/validators/src/security/input-sanitization.validator.ts @@ -2,14 +2,8 @@ * Input sanitization validator */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { now, generateUUID } from '../../utils/index.js'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, generateUUID, now } from '@reaatech/mcp-contract-core'; /** * Prompt injection patterns to test @@ -95,7 +89,7 @@ export const inputSanitizationValidator: Validator = { const responseText = response.content.map((c) => c.text ?? '').join(' '); if (responseText.includes(pattern)) { - warnings.push(`Potential XSS vulnerability: unsanitized input echoed back`); + warnings.push('Potential XSS vulnerability: unsanitized input echoed back'); } } } catch { diff --git a/src/validators/security/ssrf.validator.ts b/packages/validators/src/security/ssrf.validator.ts similarity index 91% rename from src/validators/security/ssrf.validator.ts rename to packages/validators/src/security/ssrf.validator.ts index 21e1987..d9a63ee 100644 --- a/src/validators/security/ssrf.validator.ts +++ b/packages/validators/src/security/ssrf.validator.ts @@ -2,15 +2,8 @@ * SSRF protection validator */ -import { - Validator, - TestResult, - ValidationContext, - TestCategory, - Severity, -} from '../../types/domain.js'; -import { isPrivateURL } from '../../types/schemas.js'; -import { now } from '../../utils/index.js'; +import type { TestResult, ValidationContext, Validator } from '@reaatech/mcp-contract-core'; +import { Severity, TestCategory, isPrivateURL, now } from '@reaatech/mcp-contract-core'; /** * SSRF validator implementation diff --git a/packages/validators/tsconfig.json b/packages/validators/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/validators/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/validators/vitest.config.ts b/packages/validators/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/validators/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..eb59ade --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'packages/*' + - 'e2e' diff --git a/src/cli.ts b/src/cli.ts deleted file mode 100644 index 0ca4f9f..0000000 --- a/src/cli.ts +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node -/** - * CLI entry point for mcp-contract-kit. - */ - -import { runTestCommand } from './cli/commands/test.command.js'; -import { runValidateYamlCommand } from './cli/commands/validate-yaml.command.js'; -import { CLI_VERSION, parseArgs, printHelp } from './cli/config.js'; -import { fileURLToPath } from 'node:url'; - -export async function main(argv: string[] = process.argv.slice(2)): Promise { - const args = parseArgs(argv); - - if (args.help) { - process.stdout.write(`${printHelp()}\n`); - return 0; - } - - if (args.version) { - process.stdout.write(`mcp-contract-kit v${CLI_VERSION}\n`); - return 0; - } - - if (!args.command) { - process.stderr.write(`${printHelp()}\n`); - return 1; - } - - switch (args.command) { - case 'test': - return runTestCommand(args); - case 'validate-yaml': - return runValidateYamlCommand(args); - } -} - -const isDirectRun = - process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]; - -if (isDirectRun) { - main() - .then((code) => { - process.exit(code); - }) - .catch((error) => { - process.stderr.write(`Error: ${(error as Error).message}\n`); - process.exit(3); - }); -} diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index bb34e3e..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Barrel exports for types - */ - -export * from './domain.js'; -export * from './schemas.js'; diff --git a/tests/helpers/mock-client.ts b/tests/helpers/mock-client.ts deleted file mode 100644 index ad9614c..0000000 --- a/tests/helpers/mock-client.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - MCPClient, - MCPRequest, - MCPResponse, - ToolDefinition, - ToolResult, -} from '../../src/types/domain.js'; - -interface MockClientOptions { - tools?: ToolDefinition[]; - sendRequest?: (request: MCPRequest) => Promise; - callTool?: (name: string, args: Record) => Promise; - sessionId?: string; -} - -export class MockClient implements MCPClient { - readonly requests: MCPRequest[] = []; - readonly toolCalls: Array<{ name: string; args: Record }> = []; - private readonly tools: ToolDefinition[]; - private readonly sendRequestImpl?: MockClientOptions['sendRequest']; - private readonly callToolImpl?: MockClientOptions['callTool']; - private readonly sessionId: string; - - constructor(options: MockClientOptions = {}) { - this.tools = options.tools ?? []; - this.sendRequestImpl = options.sendRequest; - this.callToolImpl = options.callTool; - this.sessionId = options.sessionId ?? 'mock-session-id'; - } - - async connect(): Promise { - return undefined; - } - - async sendRequest(request: MCPRequest): Promise> { - this.requests.push(request); - if (this.sendRequestImpl) { - return this.sendRequestImpl(request) as Promise>; - } - - if (request.method === 'tools/list') { - return { - jsonrpc: '2.0', - id: request.id, - result: { tools: this.tools } as T, - }; - } - - return { - jsonrpc: '2.0', - id: request.id, - result: {} as T, - }; - } - - async callTool(name: string, args: Record): Promise { - this.toolCalls.push({ name, args }); - if (this.callToolImpl) { - return this.callToolImpl(name, args); - } - - if (name === 'handle_message') { - return { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: `Echo: ${String(args.raw_input ?? '')}`, - workflow_complete: true, - workflow_state: { echoed: true }, - }), - }, - ], - }; - } - - return { - content: [], - isError: true, - }; - } - - async listTools(): Promise { - return this.tools; - } - - async disconnect(): Promise { - return undefined; - } - - async getSessionId(): Promise { - return this.sessionId; - } -} diff --git a/tests/helpers/mock-server.ts b/tests/helpers/mock-server.ts deleted file mode 100644 index d373ba6..0000000 --- a/tests/helpers/mock-server.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { createServer, Server } from 'node:http'; - -interface MockServerOptions { - requireAuth?: boolean; - latencyMs?: number; - rateLimitAfter?: number; -} - -export async function startMockMcpServer( - options: MockServerOptions = {}, -): Promise<{ server: Server; url: string }> { - let requestCount = 0; - const server = createServer(async (req, res) => { - const chunks: Buffer[] = []; - for await (const chunk of req) { - chunks.push(Buffer.from(chunk)); - } - - if (options.requireAuth) { - const auth = req.headers.authorization ?? req.headers['x-api-key']; - if (!auth || String(auth).includes('invalid-contract-kit')) { - res.writeHead(401, { 'content-type': 'application/json' }); - res.end(JSON.stringify({ error: 'Unauthorized' })); - return; - } - } - - if (options.rateLimitAfter && requestCount >= options.rateLimitAfter) { - res.writeHead(429, { - 'content-type': 'application/json', - 'retry-after': '1', - }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: 0, - error: { code: 429, message: 'Too Many Requests' }, - }), - ); - return; - } - - requestCount += 1; - if (options.latencyMs) { - await new Promise((resolve) => setTimeout(resolve, options.latencyMs)); - } - - const request = JSON.parse(Buffer.concat(chunks).toString('utf-8')) as { - id: string | number; - method: string; - params?: Record; - }; - - if (request.method === 'initialize') { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { protocolVersion: '2024-11-05' }, - }), - ); - return; - } - - if (request.method === 'tools/list') { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - tools: [ - { - name: 'handle_message', - description: 'Main routing entry point', - inputSchema: { - type: 'object', - properties: { - session_id: { type: 'string' }, - request_id: { type: 'string' }, - employee_id: { type: 'string' }, - raw_input: { type: 'string' }, - }, - required: ['session_id', 'request_id', 'employee_id', 'raw_input'], - }, - }, - ], - }, - }), - ); - return; - } - - if (request.method === 'tools/call') { - const params = request.params as { - name?: string; - arguments?: Record; - }; - if (params.name !== 'handle_message') { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - error: { code: -32601, message: 'Unknown tool' }, - }), - ); - return; - } - - const rawInput = String(params.arguments?.raw_input ?? ''); - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: rawInput.length === 0 ? 'Empty input handled' : `Handled: ${rawInput}`, - workflow_complete: true, - workflow_state: { length: rawInput.length }, - }), - }, - ], - }, - }), - ); - return; - } - - res.writeHead(200, { 'content-type': 'application/json' }); - res.end( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - error: { code: -32601, message: 'Method not found' }, - }), - ); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', resolve); - }); - - const address = server.address(); - if (!address || typeof address === 'string') { - throw new Error('Failed to start mock MCP server'); - } - - return { - server, - url: `http://127.0.0.1:${address.port}`, - }; -} diff --git a/tests/integration/runner.integration.test.ts b/tests/integration/runner.integration.test.ts deleted file mode 100644 index 2a5a526..0000000 --- a/tests/integration/runner.integration.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { runTests, validateProtocol, validateRouting } from '../../src/runner.js'; - -function installMockFetch(rateLimitAfter = 8): void { - let requestCount = 0; - vi.stubGlobal( - 'fetch', - vi.fn(async (_url: string, init?: RequestInit) => { - requestCount += 1; - const request = JSON.parse(String(init?.body)) as { - id: string | number; - method: string; - params?: Record; - }; - - if (request.method === 'initialize') { - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { protocolVersion: '2024-11-05' }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - - if (request.method === 'tools/list') { - if (requestCount > rateLimitAfter) { - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - error: { code: 429, message: 'Too Many Requests' }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - tools: [ - { - name: 'handle_message', - description: 'Main routing entry point', - inputSchema: { - type: 'object', - properties: { - session_id: { type: 'string' }, - request_id: { type: 'string' }, - employee_id: { type: 'string' }, - raw_input: { type: 'string' }, - }, - required: ['session_id', 'request_id', 'employee_id', 'raw_input'], - }, - }, - ], - }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - - if (request.method === 'tools/call') { - const params = request.params as { - name?: string; - arguments?: Record; - }; - if (params.name !== 'handle_message') { - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - content: [], - isError: true, - }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: `Handled: ${String(params.arguments?.raw_input ?? '')}`, - workflow_complete: true, - workflow_state: { echoed: true }, - }), - }, - ], - }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - error: { code: -32601, message: 'Method not found' }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - }), - ); -} - -describe('runner integration', () => { - afterEach(() => { - vi.unstubAllGlobals(); - }); - - it('runs end-to-end suites through the real runner and client stack', async () => { - installMockFetch(100); - - const report = await runTests({ endpoint: 'https://agents.example.com' }); - const protocol = await validateProtocol({ - endpoint: 'https://agents.example.com', - }); - const routing = await validateRouting({ - endpoint: 'https://agents.example.com', - }); - - expect(report.results.length).toBeGreaterThan(0); - expect(protocol.passed).toBe(true); - expect(routing.passed).toBe(true); - }); -}); diff --git a/tests/unit/barrels.test.ts b/tests/unit/barrels.test.ts deleted file mode 100644 index 09d5b9c..0000000 --- a/tests/unit/barrels.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import * as root from '../../src/index.js'; -import * as validators from '../../src/validators/index.js'; -import * as reporters from '../../src/reporters/index.js'; -import * as client from '../../src/mcp-client/index.js'; - -describe('barrel exports', () => { - it('exports public APIs', () => { - expect(root.runTests).toBeTypeOf('function'); - expect(root.validateProtocol).toBeTypeOf('function'); - expect(root.validateRouting).toBeTypeOf('function'); - expect(validators.getRegistryValidators).toBeTypeOf('function'); - expect(reporters.formatReport).toBeTypeOf('function'); - expect(client.createMCPClient).toBeTypeOf('function'); - }); -}); diff --git a/tests/unit/mcp-client.test.ts b/tests/unit/mcp-client.test.ts deleted file mode 100644 index 0838ee0..0000000 --- a/tests/unit/mcp-client.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { - buildInitializeRequest, - buildListToolsRequest, - buildRequest, - buildToolCallRequest, - createTracingHeaders, - HttpTransport, - MCPHttpClient, -} from '../../src/mcp-client/index.js'; -import { clearSpans, setCurrentContext } from '../../src/observability/index.js'; - -describe('mcp client and transport', () => { - afterEach(() => { - vi.unstubAllGlobals(); - clearSpans(); - setCurrentContext(null); - }); - - it('builds JSON-RPC requests and tracing headers', () => { - setCurrentContext({ traceId: 'a'.repeat(32), spanId: 'b'.repeat(16) }); - expect(buildRequest('tools/list', { ok: true }, 1)).toMatchObject({ - jsonrpc: '2.0', - method: 'tools/list', - id: 1, - }); - expect(buildInitializeRequest(2).method).toBe('initialize'); - expect(buildListToolsRequest(3).method).toBe('tools/list'); - expect(buildToolCallRequest('tool', {}, 4).params?.name).toBe('tool'); - expect(createTracingHeaders().traceparent).toContain('00-'); - }); - - it('parses JSON and SSE responses via the transport', async () => { - const jsonFetch = vi.fn().mockResolvedValueOnce( - new Response(JSON.stringify({ jsonrpc: '2.0', id: 1, result: { ok: true } }), { - status: 200, - headers: { 'content-type': 'application/json' }, - }), - ); - vi.stubGlobal('fetch', jsonFetch); - const transport = new HttpTransport({ - endpoint: 'https://example.com', - timeout: 1000, - retries: 0, - }); - - const jsonResponse = await transport.request({ jsonrpc: '2.0', method: 'tools/list', id: 1 }); - expect(jsonResponse.body.result).toEqual({ ok: true }); - - const sseFetch = vi.fn().mockResolvedValueOnce( - new Response('event: message\ndata: {"jsonrpc":"2.0","id":2,"result":{"ok":true}}\n\n', { - status: 200, - headers: { 'content-type': 'text/event-stream' }, - }), - ); - vi.stubGlobal('fetch', sseFetch); - const sseResponse = await transport.request({ jsonrpc: '2.0', method: 'tools/list', id: 2 }); - expect(sseResponse.body.result).toEqual({ ok: true }); - }); - - it('retries with a fresh timeout controller after an aborted attempt', async () => { - let callCount = 0; - const fetchMock = vi.fn((_url: string, init?: RequestInit) => { - callCount += 1; - - if (callCount === 1) { - return new Promise((_resolve, reject) => { - init?.signal?.addEventListener('abort', () => { - reject(new DOMException('The operation was aborted.', 'AbortError')); - }); - }); - } - - return Promise.resolve( - new Response(JSON.stringify({ jsonrpc: '2.0', id: 1, result: { ok: true } }), { - status: 200, - headers: { 'content-type': 'application/json' }, - }), - ); - }); - vi.stubGlobal('fetch', fetchMock); - - const transport = new HttpTransport({ - endpoint: 'https://example.com', - timeout: 10, - retries: 1, - }); - - const response = await transport.request({ jsonrpc: '2.0', method: 'tools/list', id: 1 }); - - expect(response.body.result).toEqual({ ok: true }); - expect(fetchMock).toHaveBeenCalledTimes(2); - }); - - it('connects, lists tools, calls tools, and disconnects', async () => { - const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => { - const request = JSON.parse(String(init?.body)) as { - method: string; - id: number; - params?: { name?: string }; - }; - if (request.method === 'initialize') { - return new Response( - JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { ok: true } }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - if (request.method === 'tools/list') { - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - tools: [ - { - name: 'handle_message', - description: 'desc', - inputSchema: {}, - }, - ], - }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - if (request.method === 'tools/call') { - return new Response( - JSON.stringify({ - jsonrpc: '2.0', - id: request.id, - result: { - content: [{ type: 'text', text: 'ok' }], - }, - }), - { status: 200, headers: { 'content-type': 'application/json' } }, - ); - } - return new Response(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: {} }), { - status: 200, - headers: { 'content-type': 'application/json' }, - }); - }); - vi.stubGlobal('fetch', fetchMock); - - const client = new MCPHttpClient({ - endpoint: 'https://example.com', - timeout: 1000, - retries: 0, - }); - - await client.connect(); - await expect(client.listTools()).resolves.toHaveLength(1); - await expect(client.callTool('handle_message', {})).resolves.toMatchObject({ - content: [{ text: 'ok' }], - }); - await expect(client.getSessionId()).resolves.toMatch(/-/); - await client.disconnect(); - }); -}); diff --git a/tests/unit/protocol-validators.test.ts b/tests/unit/protocol-validators.test.ts deleted file mode 100644 index e3a59eb..0000000 --- a/tests/unit/protocol-validators.test.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - jsonrpcValidator, - sessionValidator, - toolDiscoveryValidator, - toolExecutionValidator, -} from '../../src/validators/protocol/index.js'; -import { Severity, TestSuite, MCPError, MCPResponse } from '../../src/types/domain.js'; -import { MockClient } from '../helpers/mock-client.js'; - -const baseTools = [ - { - name: 'handle_message', - description: 'Handle orchestrator requests', - inputSchema: { - type: 'object', - properties: { - raw_input: { type: 'string' }, - }, - required: ['raw_input'], - }, - }, -]; - -function createContext(client: MockClient): { - client: MockClient; - endpoint: string; - requestId: string; - options: { - timeout: number; - retries: number; - failOn: Severity; - verbose: boolean; - suites: TestSuite[]; - }; - artifacts: Record; -} { - return { - client, - endpoint: 'https://agents.example.com', - requestId: 'request-id', - options: { - timeout: 1000, - retries: 0, - failOn: Severity.CRITICAL, - verbose: false, - suites: [TestSuite.PROTOCOL], - }, - artifacts: {}, - }; -} - -describe('protocol validators', () => { - it('validates compliant JSON-RPC behavior', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - if (request.method === 'nonexistent/method') { - return { - jsonrpc: '2.0', - id: request.id, - error: { code: -32601, message: 'Method not found' }, - }; - } - - if (request.method === 'tools/list') { - return { - jsonrpc: '2.0', - id: request.id, - result: { tools: baseTools }, - }; - } - - return { - jsonrpc: '2.0', - id: request.id, - result: { ok: true }, - }; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(true); - }); - - it('detects invalid tool discovery metadata', async () => { - const client = new MockClient({ - tools: [ - { - name: 'Bad Tool', - description: '', - inputSchema: { type: 'oops' }, - }, - ], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('Bad Tool'); - }); - - it('validates tool execution and warns for permissive validation', async () => { - const client = new MockClient({ - tools: baseTools, - callTool: async (name, args) => { - if (name !== 'handle_message') { - return { content: [], isError: true }; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: `Handled ${String(args.raw_input ?? 'default')}`, - workflow_complete: true, - }), - }, - ], - }; - }, - }); - - const result = await toolExecutionValidator.validate(createContext(client)); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('checks session consistency and isolation', async () => { - const client = new MockClient({ - tools: baseTools, - sessionId: 'session-12345', - }); - - const result = await sessionValidator.validate(createContext(client)); - expect(result.passed).toBe(true); - }); - - it('detects JSON-RPC response without jsonrpc field', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - return { - jsonrpc: '1.0', - id: request.id, - result: {}, - } as unknown as MCPResponse; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual( - expect.stringContaining("'jsonrpc'"), - ); - }); - - it('detects JSON-RPC response with mismatched id', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async () => { - return { - jsonrpc: '2.0', - id: 'wrong-id', - result: {}, - }; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual( - expect.stringContaining("id"), - ); - }); - - it('detects JSON-RPC response with both result and error', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - return { - jsonrpc: '2.0', - id: request.id, - result: { ok: true }, - error: { code: -32600, message: 'Invalid request' }, - }; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual( - expect.stringContaining("both 'result' and 'error'"), - ); - }); - - it('detects JSON-RPC response with neither result nor error', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - return { - jsonrpc: '2.0', - id: request.id, - }; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - }); - - it('detects invalid error code outside valid range', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - return { - jsonrpc: '2.0', - id: request.id, - error: { code: -40000, message: 'Invalid error code' }, - } as unknown as MCPResponse; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual( - expect.stringContaining('Error code -40000 is outside valid ranges'), - ); - }); - - it('detects error response without error code', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async (request) => { - return { - jsonrpc: '2.0', - id: request.id, - error: { message: 'Error without code' } as MCPError, - }; - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - }); - - it('handles sendRequest throwing an error', async () => { - const client = new MockClient({ - tools: baseTools, - sendRequest: async () => { - throw new Error('Connection refused'); - }, - }); - - const result = await jsonrpcValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual( - expect.stringContaining('Connection refused'), - ); - }); - - it('detects tool discovery when tools/list returns non-array', async () => { - const client = new MockClient({ - tools: baseTools, - }); - (client as unknown as { listTools: () => Promise }).listTools = async (): Promise => 'not an array'; - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContain('tools/list did not return an array'); - }); - - it('detects duplicate tool names', async () => { - const client = new MockClient({ - tools: [ - { name: 'tool_a', description: 'Tool A', inputSchema: { type: 'object' } }, - { name: 'tool_a', description: 'Duplicate tool A', inputSchema: { type: 'object' } }, - ], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('Duplicate'); - }); - - it('detects empty tools array', async () => { - const client = new MockClient({ - tools: [], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('No tools found'); - }); - - it('detects tool with uppercase name', async () => { - const client = new MockClient({ - tools: [ - { name: 'InvalidTool', description: 'Has uppercase', inputSchema: { type: 'object' } }, - ], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('lowercase'); - }); - - it('detects tool with missing description', async () => { - const client = new MockClient({ - tools: [ - { name: 'valid_tool', description: '', inputSchema: { type: 'object' } }, - ], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('description'); - }); - - it('detects tool with array inputSchema instead of object', async () => { - const client = new MockClient({ - tools: [ - { name: 'valid_tool', description: 'Valid tool', inputSchema: [] as unknown as Record }, - ], - }); - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('must be an object'); - }); - - it('handles tool execution with no tools', async () => { - const client = new MockClient({ - tools: [], - }); - - const result = await toolExecutionValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(result.message).toContain('No tools available'); - }); - - it('detects tool execution error', async () => { - const client = new MockClient({ - tools: baseTools, - callTool: async () => { - throw new Error('Execution failed'); - }, - }); - - const result = await toolExecutionValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('Execution failed'); - }); - - it('detects unknown tool not returning error', async () => { - const client = new MockClient({ - tools: baseTools, - callTool: async () => { - return { content: [], isError: false }; - }, - }); - - const result = await toolExecutionValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('Unknown tool should return isError'); - }); - - it('handles listTools throwing an error', async () => { - const client = new MockClient({ - tools: baseTools, - }); - (client as unknown as { listTools: () => Promise }).listTools = async () => { - throw new Error('Failed to list tools'); - }; - - const result = await toolDiscoveryValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('tools/list request failed'); - }); - - it('detects short session ID', async () => { - const client = new MockClient({ - tools: baseTools, - sessionId: 'abc', - }); - - const result = await sessionValidator.validate(createContext(client)); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('handles session validation error', async () => { - const client = new MockClient({ - tools: baseTools, - }); - (client as unknown as { getSessionId: () => Promise }).getSessionId = async () => { - throw new Error('Session error'); - }; - - const result = await sessionValidator.validate(createContext(client)); - expect(result.passed).toBe(false); - expect(String(result.details?.errors)).toContain('Session validation failed'); - }); -}); diff --git a/tests/unit/registry-shared.test.ts b/tests/unit/registry-shared.test.ts deleted file mode 100644 index e5d6a90..0000000 --- a/tests/unit/registry-shared.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { loadRegistryFile } from '../../src/validators/registry/shared.js'; - -describe('registry shared helpers', () => { - it('loadRegistryFile parses multi-agent registry', () => { - const result = loadRegistryFile('tests/fixtures/registry-multi.yaml'); - expect(result.agents.length).toBeGreaterThan(0); - expect(result.errors).toHaveLength(0); - }); - - it('loadRegistryFile returns file not found error', () => { - const result = loadRegistryFile('/nonexistent/path.yaml'); - expect(result.errors[0]?.type).toBe('FILE_READ_ERROR'); - }); - - it('loadRegistryFile returns YAML parse error for invalid YAML', () => { - const result = loadRegistryFile('tests/fixtures/registry-valid.yaml'); - expect(result.errors).toHaveLength(0); - }); -}); \ No newline at end of file diff --git a/tests/unit/registry-validators.test.ts b/tests/unit/registry-validators.test.ts deleted file mode 100644 index d2c0000..0000000 --- a/tests/unit/registry-validators.test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { resolve } from 'node:path'; -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; -import { - validateAgentYAML, - schemaValidator, - validateInvariants, - invariantValidator, - validateEnvExpansion, - envExpansionValidator, -} from '../../src/validators/registry/index.js'; -import { Severity, TestCategory, TestSuite } from '../../src/types/domain.js'; -import { MockClient } from '../helpers/mock-client.js'; - -const fixtures = (name: string): string => resolve(process.cwd(), 'tests/fixtures', name); - -function createContext(yamlPath?: string): { - client: MockClient; - endpoint: string; - requestId: string; - options: { - yamlPath?: string; - timeout: number; - retries: number; - failOn: Severity; - verbose: boolean; - suites: TestSuite[]; - }; - artifacts: Record; -} { - return { - client: new MockClient(), - endpoint: '', - requestId: 'request-id', - options: { - yamlPath, - timeout: 1000, - retries: 0, - failOn: Severity.CRITICAL, - verbose: false, - suites: [TestSuite.REGISTRY], - }, - artifacts: {}, - }; -} - -describe('registry validators', () => { - beforeEach(() => { - vi.unstubAllEnvs(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('validates a well-formed registry yaml file', () => { - const result = validateAgentYAML(fixtures('registry-valid.yaml')); - expect(result.valid).toBe(true); - expect(result.agents).toHaveLength(1); - }); - - it('loads multi-agent registries and reports invariant failures', () => { - const schemaResult = validateAgentYAML(fixtures('registry-invalid.yaml')); - expect(schemaResult.valid).toBe(true); - - const invariantResult = validateInvariants(schemaResult.agents ?? []); - expect(invariantResult.valid).toBe(false); - expect(invariantResult.errors.map((error) => error.type)).toContain('MULTIPLE_DEFAULTS'); - expect(invariantResult.errors.map((error) => error.type)).toContain('DUPLICATE_AGENT_IDS'); - }); - - it('shares parsed agents between schema and invariant validators', async () => { - const context = createContext(fixtures('registry-multi.yaml')); - const schema = await schemaValidator.validate(context); - const invariant = await invariantValidator.validate(context); - - expect(schema.passed).toBe(true); - expect(invariant.passed).toBe(true); - expect( - (context.artifacts as Record | undefined)?.registryAgents, - ).toBeDefined(); - }); - - it('detects undefined environment variables and circular references', () => { - vi.stubEnv('SELF_REF', '${SELF_REF}'); - const result = validateEnvExpansion(`endpoint: \${SELF_REF}\nauth: \${MISSING_ENV}`); - - expect(result.valid).toBe(false); - expect(result.errors[0]?.type).toBe('CIRCULAR_ENV_REF'); - expect(result.warnings[0]?.type).toBe('ENV_VAR_UNDEFINED'); - }); - - it('runs the env expansion validator against yaml content', async () => { - vi.stubEnv('DEPLOYMENT_TIER', 'prod'); - const context = createContext(fixtures('registry-env.yaml')); - const result = await envExpansionValidator.validate(context); - - expect(result.passed).toBe(true); - expect(result.category).toBe(TestCategory.REGISTRY); - }); - - it('invariant validator handles missing yaml path', async () => { - const context = createContext(undefined); - const result = await invariantValidator.validate(context); - - expect(result.passed).toBe(false); - expect(result.message).toContain('No valid agent configurations'); - }); - - it('invariant validator handles empty agents array', async () => { - const context = createContext(undefined); - context.artifacts = { registryAgents: [] }; - - const result = await invariantValidator.validate(context); - expect(result.passed).toBe(false); - }); - - it('detects no default agent', () => { - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: 'https://example.com/agent1', - type: 'mcp' as const, - is_default: false, - confidence_threshold: 0.8, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('NO_DEFAULT_AGENT'); - }); - - it('detects default agent with non-zero threshold', () => { - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: 'https://example.com/agent1', - type: 'mcp' as const, - is_default: true, - confidence_threshold: 0.5, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('INVALID_DEFAULT_THRESHOLD'); - }); - - it('detects invalid endpoint URL', () => { - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: 'not-a-valid-url', - type: 'mcp' as const, - is_default: true, - confidence_threshold: 0, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('INVALID_ENDPOINT_URL'); - }); - - it('detects localhost endpoint', () => { - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: 'http://localhost:8080', - type: 'mcp' as const, - is_default: true, - confidence_threshold: 0, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('SSRF_VULNERABILITY'); - }); - - it('detects private IP endpoint', () => { - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: 'http://192.168.1.1:8080', - type: 'mcp' as const, - is_default: true, - confidence_threshold: 0, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('SSRF_VULNERABILITY'); - }); - - it('detects endpoint URL exceeding 2048 characters', () => { - const longUrl = 'https://example.com/' + 'a'.repeat(2050); - const agents = [ - { - agent_id: 'agent-1', - display_name: 'Agent 1', - description: 'Test agent', - endpoint: longUrl, - type: 'mcp' as const, - is_default: true, - confidence_threshold: 0, - clarification_required: false, - examples: [], - }, - ]; - - const result = validateInvariants(agents); - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('ENDPOINT_TOO_LONG'); - }); - - it('validates env expansion with incomplete variable reference', () => { - const result = validateEnvExpansion('endpoint: ${INCOMPLETE'); - - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('INCOMPLETE_ENV_VAR'); - }); - - it('validates env expansion with invalid variable name', () => { - const result = validateEnvExpansion('endpoint: ${invalid-name}'); - - expect(result.errors.map((e) => e.type)).toContain('INVALID_ENV_VAR_NAME'); - }); - - it('validates env expansion without any env vars', () => { - const result = validateEnvExpansion('endpoint: https://example.com'); - - expect(result.valid).toBe(true); - expect(result.variables).toHaveLength(0); - }); - - it('env expansion validator handles file read error', async () => { - const context = createContext('/nonexistent/path.yaml'); - const result = await envExpansionValidator.validate(context); - - expect(result.passed).toBe(false); - expect(result.message).toContain('Failed to read file'); - }); - - it('env expansion validator skips when no yaml path provided', async () => { - const context = createContext(undefined); - const result = await envExpansionValidator.validate(context); - - expect(result.passed).toBe(true); - expect(result.message).toContain('skipping'); - }); - - it('detects circular environment variable reference', () => { - vi.stubEnv('VAR_A', '${VAR_B}'); - vi.stubEnv('VAR_B', '${VAR_A}'); - - const result = validateEnvExpansion('endpoint: ${VAR_A}'); - - expect(result.valid).toBe(false); - expect(result.errors.map((e) => e.type)).toContain('CIRCULAR_ENV_REF'); - }); -}); diff --git a/tests/unit/routing-security-performance.test.ts b/tests/unit/routing-security-performance.test.ts deleted file mode 100644 index 810780c..0000000 --- a/tests/unit/routing-security-performance.test.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - compatibilityValidator, - requestContractValidator, - responseContractValidator, -} from '../../src/validators/routing/index.js'; -import { - authValidator, - inputSanitizationValidator, - ssrfValidator, -} from '../../src/validators/security/index.js'; -import { - concurrencyValidator, - latencyValidator, - rateLimitValidator, -} from '../../src/validators/performance/index.js'; -import { Severity, TestSuite } from '../../src/types/domain.js'; -import { MockClient } from '../helpers/mock-client.js'; - -const tools = [ - { - name: 'handle_message', - description: 'Handle orchestrator requests', - inputSchema: { - type: 'object', - properties: { - session_id: { type: 'string' }, - request_id: { type: 'string' }, - employee_id: { type: 'string' }, - raw_input: { type: 'string' }, - }, - required: ['session_id', 'request_id', 'employee_id', 'raw_input'], - }, - }, -]; - -function createContext(endpoint = 'http://127.0.0.1:3000'): { - client: MockClient; - endpoint: string; - requestId: string; - options: { - timeout: number; - retries: number; - failOn: Severity; - verbose: boolean; - suites: TestSuite[]; - }; - artifacts: Record; -} { - const client = new MockClient({ - tools, - callTool: async (name, args) => { - if (name !== 'handle_message') { - return { content: [], isError: true }; - } - - const rawInput = String(args.raw_input ?? ''); - return { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: rawInput.includes('script') ? 'Sanitized output' : `Handled ${rawInput}`, - workflow_complete: true, - workflow_state: { length: rawInput.length }, - }), - }, - ], - }; - }, - sendRequest: async (request) => { - if (request.method === 'tools/list') { - return { - jsonrpc: '2.0', - id: request.id, - result: { tools }, - }; - } - return { - jsonrpc: '2.0', - id: request.id, - result: {}, - }; - }, - }); - - return { - client, - endpoint, - requestId: 'request-id', - options: { - timeout: 1000, - retries: 0, - failOn: Severity.CRITICAL, - verbose: false, - suites: [TestSuite.ROUTING, TestSuite.SECURITY, TestSuite.PERFORMANCE], - }, - artifacts: {}, - }; -} - -function createFailingClient(): MockClient { - return new MockClient({ - tools, - callTool: async () => { - throw new Error('Connection refused'); - }, - sendRequest: async () => { - throw new Error('Connection refused'); - }, - }); -} - -function createErrorResponseClient(): MockClient { - return new MockClient({ - tools, - callTool: async () => { - return { content: [], isError: true }; - }, - }); -} - -function createMissingHandleMessageClient(): MockClient { - return new MockClient({ - tools: [ - { - name: 'other_tool', - description: 'Not handle_message', - inputSchema: { type: 'object' }, - }, - ], - }); -} - -describe('routing, security, and performance validators', () => { - it('validates the routing contract end to end', async () => { - const context = createContext(); - await expect(requestContractValidator.validate(context)).resolves.toMatchObject({ - passed: true, - }); - await expect(responseContractValidator.validate(context)).resolves.toMatchObject({ - passed: true, - }); - await expect(compatibilityValidator.validate(context)).resolves.toMatchObject({ - passed: true, - }); - }); - - it('reports local endpoint SSRF guidance as warnings', async () => { - const result = await ssrfValidator.validate(createContext('http://localhost:3000')); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('surfaces auth, input sanitization, and performance guidance', async () => { - const context = createContext(); - const auth = await authValidator.validate(context); - const input = await inputSanitizationValidator.validate(context); - const latency = await latencyValidator.validate(context); - const concurrency = await concurrencyValidator.validate(context); - const rateLimit = await rateLimitValidator.validate(context); - - expect(auth.passed).toBe(true); - expect(input.passed).toBe(true); - expect(latency.passed).toBe(true); - expect(concurrency.passed).toBe(true); - expect(rateLimit.passed).toBe(true); - }); - - it('handles request contract validator when client throws', async () => { - const client = createFailingClient(); - const context = createContext(); - context.client = client; - - const result = await requestContractValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual(expect.stringContaining('Failed to send request')); - }); - - it('handles request contract validator when handle_message is missing', async () => { - const client = createMissingHandleMessageClient(); - const context = createContext(); - context.client = client; - - const result = await requestContractValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual(expect.stringContaining('handle_message')); - }); - - it('handles response contract validator when client throws', async () => { - const client = createFailingClient(); - const context = createContext(); - context.client = client; - - const result = await responseContractValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual(expect.stringContaining('Failed to get response')); - }); - - it('handles response contract validator when response is error', async () => { - const client = createErrorResponseClient(); - const context = createContext(); - context.client = client; - - const result = await responseContractValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual(expect.stringContaining('returned an error')); - }); - - it('handles response contract validator when response has no content', async () => { - const client = new MockClient({ - tools, - callTool: async () => { - return { content: [] }; - }, - }); - const context = createContext(); - context.client = client; - - const result = await responseContractValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContainEqual(expect.stringContaining('no content')); - }); - - it('handles compatibility validator when client throws', async () => { - const client = createFailingClient(); - const context = createContext(); - context.client = client; - - const result = await compatibilityValidator.validate(context); - expect(result.passed).toBe(false); - }); - - it('detects SSRF vulnerability with private IP', async () => { - const result = await ssrfValidator.validate(createContext('http://10.0.0.1:8080')); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('detects SSRF vulnerability with internal hostname', async () => { - const result = await ssrfValidator.validate(createContext('http://internal.corp:8080')); - expect(result.passed).toBe(true); - }); - - it('validates public endpoint passes SSRF check', async () => { - const result = await ssrfValidator.validate(createContext('https://api.example.com')); - expect(result.severity).toBe(Severity.INFO); - }); - - it('concurrency validator handles all requests failing', async () => { - const client = createFailingClient(); - const context = createContext(); - context.client = client; - - const result = await concurrencyValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContain('All concurrent requests failed'); - }); - - it('rate limit validator handles request error', async () => { - const client = createFailingClient(); - const context = createContext(); - context.client = client; - - const result = await rateLimitValidator.validate(context); - expect(result.passed).toBe(true); - expect(result.details).toBeDefined(); - }); - - it('detects input sanitization issues', async () => { - const sqlPattern = "' OR '1'='1"; - const client = new MockClient({ - tools, - callTool: async (name, _args) => { - if (name !== 'handle_message') { - return { content: [], isError: true }; - } - return { - content: [ - { - type: 'text', - text: JSON.stringify({ - content: `User input: ${sqlPattern}`, - workflow_complete: true, - }), - }, - ], - }; - }, - }); - const context = createContext(); - context.client = client; - - const result = await inputSanitizationValidator.validate(context); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('rate limit validator handles rate limited response', async () => { - const client = new MockClient({ - tools, - sendRequest: async () => { - return { jsonrpc: '2.0', id: 1, error: { code: 429, message: 'Too Many Requests' } }; - }, - }); - const context = createContext(); - context.client = client; - - const result = await rateLimitValidator.validate(context); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - expect(result.details?.rateLimited).toBe(true); - }); - - it('rate limit validator handles rate limited error message', async () => { - const client = new MockClient({ - tools, - sendRequest: async () => { - throw new Error('429 Too Many Requests'); - }, - }); - const context = createContext(); - context.client = client; - - const result = await rateLimitValidator.validate(context); - expect(result.passed).toBe(true); - expect(result.details?.rateLimited).toBe(true); - }); - - it('concurrency validator handles partial failures', async () => { - let count = 0; - const client = new MockClient({ - tools, - sendRequest: async () => { - count++; - if (count <= 3) return { jsonrpc: '2.0', id: 1, result: {} }; - throw new Error('Connection refused'); - }, - }); - const context = createContext(); - context.client = client; - - const result = await concurrencyValidator.validate(context); - expect(result.passed).toBe(false); - expect(result.details?.errors).toContain('2 of 5 concurrent requests failed'); - }); - - it('ssrf validator detects non-HTTPS endpoint', async () => { - const result = await ssrfValidator.validate(createContext('http://api.example.com')); - expect(result.passed).toBe(true); - expect(result.severity).toBe(Severity.WARNING); - }); - - it('ssrf validator detects invalid URL', async () => { - const result = await ssrfValidator.validate(createContext('not-a-valid-url')); - expect(result.passed).toBe(false); - }); -}); diff --git a/tests/unit/runner-reporters-observability.test.ts b/tests/unit/runner-reporters-observability.test.ts deleted file mode 100644 index b1f0171..0000000 --- a/tests/unit/runner-reporters-observability.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { - clearSpans, - createLogger, - endSpan, - fromTraceParent, - getSpans, - metrics, - startSpan, - toTraceParent, - withSpan, -} from '../../src/observability/index.js'; -import { - formatConsoleReport, - formatJsonReport, - formatMarkdownReport, - generateHtmlReport, -} from '../../src/reporters/index.js'; -import { generateReport, runTests, validateRegistry } from '../../src/runner.js'; -import { resolve } from 'node:path'; -import { TestReport, Severity, TestCategory } from '../../src/types/domain.js'; - -const validYaml = resolve(process.cwd(), 'tests/fixtures/registry-valid.yaml'); -const envYaml = resolve(process.cwd(), 'tests/fixtures/registry-env.yaml'); - -function createFailedReport(): TestReport { - return { - id: 'test-report-failed', - endpoint: 'http://localhost:3000', - startedAt: '2024-01-01T00:00:00Z', - completedAt: '2024-01-01T00:00:01Z', - durationMs: 1000, - timestamp: '2024-01-01T00:00:01Z', - results: [ - { - validator: 'test-validator', - category: TestCategory.PROTOCOL, - passed: false, - severity: Severity.CRITICAL, - message: 'Critical validation failed', - remediation: 'Fix the issue', - details: { error: 'test error' }, - durationMs: 500, - timestamp: '2024-01-01T00:00:01Z', - }, - { - validator: 'warning-validator', - category: TestCategory.SECURITY, - passed: false, - severity: Severity.WARNING, - message: 'Warning message', - details: {}, - durationMs: 200, - timestamp: '2024-01-01T00:00:01Z', - }, - ], - summary: { - total: 2, - passed: 0, - failed: 1, - warnings: 1, - critical: 1, - }, - failures: { - critical: 1, - warning: 1, - info: 0, - }, - passed: false, - error: 'Connection refused', - version: '1.0.0', - }; -} - -function createWarningReport(): TestReport { - return { - id: 'test-report-warning', - endpoint: 'http://localhost:3000', - startedAt: '2024-01-01T00:00:00Z', - completedAt: '2024-01-01T00:00:01Z', - durationMs: 1000, - timestamp: '2024-01-01T00:00:01Z', - results: [ - { - validator: 'warning-validator', - category: TestCategory.PERFORMANCE, - passed: false, - severity: Severity.WARNING, - message: 'Performance warning', - details: {}, - durationMs: 500, - timestamp: '2024-01-01T00:00:01Z', - }, - ], - summary: { - total: 1, - passed: 0, - failed: 0, - warnings: 1, - critical: 0, - }, - failures: { - critical: 0, - warning: 1, - info: 0, - }, - passed: true, - version: '1.0.0', - }; -} - -function createErrorReport(): TestReport { - return { - id: 'test-report-error', - endpoint: 'http://localhost:3000', - startedAt: '2024-01-01T00:00:00Z', - completedAt: '2024-01-01T00:00:01Z', - durationMs: 1000, - timestamp: '2024-01-01T00:00:01Z', - results: [], - summary: { - total: 0, - passed: 0, - failed: 0, - warnings: 0, - critical: 0, - }, - failures: { - critical: 0, - warning: 0, - info: 0, - }, - passed: false, - error: 'Network error: connection refused', - version: '1.0.0', - }; -} - -function createEmptyResultsReport(): TestReport { - return { - id: 'test-report-empty', - endpoint: 'http://localhost:3000', - startedAt: '2024-01-01T00:00:00Z', - completedAt: '2024-01-01T00:00:01Z', - durationMs: 1000, - timestamp: '2024-01-01T00:00:01Z', - results: [ - { - validator: 'info-validator', - category: TestCategory.PROTOCOL, - passed: true, - severity: Severity.INFO, - message: 'All checks passed', - details: {}, - durationMs: 500, - timestamp: '2024-01-01T00:00:01Z', - }, - ], - summary: { - total: 1, - passed: 1, - failed: 0, - warnings: 0, - critical: 0, - }, - failures: { - critical: 0, - warning: 0, - info: 1, - }, - passed: true, - version: '1.0.0', - }; -} - -describe('runner, reporters, and observability', () => { - afterEach(() => { - vi.unstubAllGlobals(); - }); - - it('formats reports in every supported output', async () => { - const report = await validateRegistry({ yamlPath: validYaml }); - - expect(formatConsoleReport(report)).toContain('MCP Contract Kit'); - expect(formatJsonReport(report)).toContain('"results"'); - expect(formatMarkdownReport(report)).toContain('# MCP Contract Kit - Test Report'); - expect(await generateHtmlReport(report)).toContain(' { - const report = createErrorReport(); - const output = formatConsoleReport(report); - expect(output).toContain('CONNECTION ERROR'); - expect(output).toContain('Network error'); - }); - - it('formats console report with failed results', () => { - const report = createFailedReport(); - const output = formatConsoleReport(report); - expect(output).toContain('✗ FAILED'); - expect(output).toContain('Critical validation failed'); - expect(output).toContain('Fix the issue'); - expect(output).toContain('🔴'); - expect(output).toContain('🟡'); - }); - - it('formats console report with passed status', () => { - const report = createEmptyResultsReport(); - const output = formatConsoleReport(report); - expect(output).toContain('✓ PASSED'); - }); - - it('formats markdown report with connection error', () => { - const report = createErrorReport(); - const output = formatMarkdownReport(report); - expect(output).toContain('### Connection Error'); - expect(output).toContain('Network error'); - }); - - it('formats markdown report with failures', () => { - const report = createFailedReport(); - const output = formatMarkdownReport(report); - expect(output).toContain('## Failures'); - expect(output).toContain('Critical validation failed'); - expect(output).toContain('### test-validator'); - }); - - it('formats markdown report with remediation', () => { - const report = createFailedReport(); - const output = formatMarkdownReport(report); - expect(output).toContain('**Remediation:**'); - }); - - it('generates html report with failed status', async () => { - const report = createFailedReport(); - const output = await generateHtmlReport(report); - expect(output).toContain('FAILED'); - expect(output).toContain('#dc3545'); - expect(output).toContain('Critical'); - }); - - it('generates html report with warning status', async () => { - const report = createWarningReport(); - const output = await generateHtmlReport(report); - expect(output).toContain('WARNINGS'); - expect(output).toContain('#fd7e14'); - }); - - it('marks warning-only registry validation as failed in strict mode', async () => { - const report = await validateRegistry({ yamlPath: envYaml, strict: true }); - - expect(report.passed).toBe(false); - expect(report.failures.warning).toBe(1); - expect(report.summary.warnings).toBe(1); - }); - - it('marks connection errors as failed test runs', async () => { - vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('connection refused'))); - - const report = await runTests({ - endpoint: 'https://agents.example.com', - suites: [TestCategory.PROTOCOL], - }); - - expect(report.passed).toBe(false); - expect(report.error).toContain('connection refused'); - }); - - it('generates html report with passed status', async () => { - const report = createEmptyResultsReport(); - const output = await generateHtmlReport(report); - expect(output).toContain('PASSED'); - expect(output).toContain('#28a745'); - }); - - it('html reporter escapes special characters in validator names', async () => { - const report: TestReport = { - ...createEmptyResultsReport(), - results: [ - { - validator: 'Test ', - category: TestCategory.PROTOCOL, - passed: true, - severity: Severity.INFO, - message: 'Message with & "quotes"', - details: {}, - durationMs: 100, - timestamp: '2024-01-01T00:00:00Z', - }, - ], - }; - const output = await generateHtmlReport(report); - expect(output).toContain('<script>'); - expect(output).toContain('&'); - expect(output).toContain('"quotes"'); - expect(output).not.toContain('alert("xss")'); - }); - - it('generates report with different formats via formatReport', async () => { - const report = await validateRegistry({ yamlPath: validYaml }); - const jsonOutput = await generateReport(report, 'json'); - expect(jsonOutput).toContain('"results"'); - - const markdownOutput = await generateReport(report, 'markdown'); - expect(markdownOutput).toContain('# MCP Contract Kit'); - - const htmlOutput = await generateReport(report, 'html'); - expect(htmlOutput).toContain(' { - clearSpans(); - metrics.reset(); - const span = startSpan('validator', { suite: 'protocol' }); - endSpan(span, 'ok'); - await withSpan('wrapped', async () => undefined); - metrics.recordDuration('latency', 42, { suite: 'protocol' }); - metrics.inc('counter', 2, { suite: 'protocol' }); - - const traceParent = toTraceParent(span.context); - expect(fromTraceParent(traceParent)).toMatchObject({ - traceId: span.context.traceId, - spanId: span.context.spanId, - }); - expect(getSpans()).toHaveLength(2); - expect(metrics.getCounter('counter', { suite: 'protocol' })).toBe(2); - expect(metrics.getHistogram('latency', { suite: 'protocol' }).count).toBe(1); - expect(metrics.getSummary().counters).toBeDefined(); - }); - - it('creates loggers that redact sensitive fields', () => { - const logger = createLogger({ requestId: 'request-1' }); - expect(typeof logger.info).toBe('function'); - logger.info('test', { password: 'secret', safe: 'value' }); - }); -}); diff --git a/tests/unit/schemas.test.ts b/tests/unit/schemas.test.ts deleted file mode 100644 index feb9e39..0000000 --- a/tests/unit/schemas.test.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Unit tests for Zod schemas - */ - -import { describe, it, expect } from 'vitest'; -import { - AgentConfigSchema, - MCPRequestSchema, - MCPResponseSchema, - ToolDefinitionSchema, - AgentRequestContractSchema, - AgentResponseContractSchema, -} from '../../src/types/schemas.js'; - -function omitKey>( - value: T, - key: keyof T, -): Record { - const copy = { ...value }; - delete copy[key]; - return copy; -} - -describe('schemas', () => { - describe('AgentConfigSchema', () => { - const validAgent = { - agent_id: 'test-agent', - display_name: 'Test Agent', - description: 'A test agent for validation', - endpoint: 'https://example.com/agent', - type: 'mcp' as const, - is_default: false, - confidence_threshold: 0.7, - clarification_required: true, - examples: ['example 1', 'example 2'], - }; - - it('should validate a correct agent config', () => { - const result = AgentConfigSchema.safeParse(validAgent); - expect(result.success).toBe(true); - }); - - it('should reject missing required fields', () => { - const invalid = omitKey(validAgent, 'agent_id'); - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid endpoint URL', () => { - const invalid = { ...validAgent, endpoint: 'not-a-url' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject non-HTTPS endpoint', () => { - const invalid = { ...validAgent, endpoint: 'http://example.com' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid type', () => { - const invalid = { ...validAgent, type: 'invalid' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject confidence_threshold out of range', () => { - const invalid = { ...validAgent, confidence_threshold: 1.5 }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject empty examples', () => { - const invalid = { ...validAgent, examples: [] }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('MCPRequestSchema', () => { - const validRequest = { - jsonrpc: '2.0' as const, - method: 'tools/list', - id: 1, - }; - - it('should validate a correct request', () => { - const result = MCPRequestSchema.safeParse(validRequest); - expect(result.success).toBe(true); - }); - - it('should accept params', () => { - const withParams = { ...validRequest, params: { name: 'test' } }; - const result = MCPRequestSchema.safeParse(withParams); - expect(result.success).toBe(true); - }); - - it('should reject missing jsonrpc', () => { - const invalid = omitKey(validRequest, 'jsonrpc'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject wrong jsonrpc version', () => { - const invalid = { ...validRequest, jsonrpc: '1.0' }; - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing method', () => { - const invalid = omitKey(validRequest, 'method'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing id', () => { - const invalid = omitKey(validRequest, 'id'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('MCPResponseSchema', () => { - const validResultResponse = { - jsonrpc: '2.0' as const, - id: 1, - result: { tools: [] }, - }; - - const validErrorResponse = { - jsonrpc: '2.0' as const, - id: 1, - error: { code: -32600, message: 'Invalid Request' }, - }; - - it('should validate a result response', () => { - const result = MCPResponseSchema.safeParse(validResultResponse); - expect(result.success).toBe(true); - }); - - it('should validate an error response', () => { - const result = MCPResponseSchema.safeParse(validErrorResponse); - expect(result.success).toBe(true); - }); - - it('should reject missing jsonrpc', () => { - const invalid = omitKey(validResultResponse, 'jsonrpc'); - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing id', () => { - const invalid = omitKey(validResultResponse, 'id'); - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject both result and error', () => { - const invalid = { - ...validResultResponse, - error: { code: -32600, message: 'Error' }, - }; - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject neither result nor error', () => { - const invalid = { jsonrpc: '2.0', id: 1 }; - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('ToolDefinitionSchema', () => { - const validTool = { - name: 'test-tool', - description: 'A test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - }; - - it('should validate a correct tool definition', () => { - const result = ToolDefinitionSchema.safeParse(validTool); - expect(result.success).toBe(true); - }); - - it('should reject empty name', () => { - const invalid = { ...validTool, name: '' }; - const result = ToolDefinitionSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing inputSchema', () => { - const invalid = omitKey(validTool, 'inputSchema'); - const result = ToolDefinitionSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('AgentRequestContractSchema', () => { - const validRequest = { - session_id: '550e8400-e29b-41d4-a716-446655440000', - request_id: '660e8400-e29b-41d4-a716-446655440001', - employee_id: 'emp123', - raw_input: 'What is the weather?', - display_name: 'Weather Agent', - intent_summary: 'Get weather info', - }; - - it('should validate a correct request', () => { - const result = AgentRequestContractSchema.safeParse(validRequest); - expect(result.success).toBe(true); - }); - - it('should reject missing required fields', () => { - const invalid = omitKey(validRequest, 'session_id'); - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid UUID format', () => { - const invalid = { ...validRequest, session_id: 'not-a-uuid' }; - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject empty raw_input', () => { - const invalid = { ...validRequest, raw_input: '' }; - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should accept optional fields', () => { - const withOptional = { - ...validRequest, - entities: { location: 'SF' }, - turn_history: [{ role: 'user', content: 'hello' }], - workflow_state: { step: 1 }, - }; - const result = AgentRequestContractSchema.safeParse(withOptional); - expect(result.success).toBe(true); - }); - }); - - describe('AgentResponseContractSchema', () => { - const validResponse = { - content: 'The weather is sunny.', - workflow_complete: true, - }; - - it('should validate a correct response', () => { - const result = AgentResponseContractSchema.safeParse(validResponse); - expect(result.success).toBe(true); - }); - - it('should reject empty content', () => { - const invalid = { ...validResponse, content: '' }; - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing content', () => { - const invalid = omitKey(validResponse, 'content'); - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing workflow_complete', () => { - const invalid = omitKey({ content: 'test', workflow_complete: true }, 'workflow_complete'); - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should accept optional fields', () => { - const withOptional = { - ...validResponse, - workflow_state: { next_step: 'done' }, - isError: false, - errorMessage: '', - }; - const result = AgentResponseContractSchema.safeParse(withOptional); - expect(result.success).toBe(true); - }); - }); -}); diff --git a/tests/unit/utils.test.ts b/tests/unit/utils.test.ts deleted file mode 100644 index dfaf4fe..0000000 --- a/tests/unit/utils.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Unit tests for utilities - */ - -import { describe, it, expect } from 'vitest'; -import { - generateId, - generateUUID, - sleep, - retry, - measureTime, - now, - truncate, - redactSensitiveData, - percentile, - calculateStats, - isValidURL, - isPrivateURL, -} from '../../src/utils/index.js'; - -describe('utils', () => { - describe('generateId', () => { - it('should generate a unique ID', () => { - const id1 = generateId(); - const id2 = generateId(); - expect(id1).not.toBe(id2); - expect(id1).toMatch(/^\d+-[a-z0-9]+$/); - }); - }); - - describe('generateUUID', () => { - it('should generate a valid UUID v4', () => { - const uuid = generateUUID(); - expect(uuid).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, - ); - }); - - it('should generate unique UUIDs', () => { - const uuids = new Set([generateUUID(), generateUUID(), generateUUID()]); - expect(uuids.size).toBe(3); - }); - }); - - describe('sleep', () => { - it('should wait for the specified duration', async () => { - const start = Date.now(); - await sleep(50); - const duration = Date.now() - start; - expect(duration).toBeGreaterThanOrEqual(40); - }); - }); - - describe('retry', () => { - it('should return result on first success', async () => { - const result = await retry(() => Promise.resolve('success'), { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }); - expect(result).toBe('success'); - }); - - it('should retry on failure and succeed', async () => { - let attempts = 0; - const result = await retry( - () => { - attempts++; - if (attempts < 3) { - return Promise.reject(new Error('temporary error')); - } - return Promise.resolve('success'); - }, - { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }, - ); - expect(result).toBe('success'); - expect(attempts).toBe(3); - }); - - it('should throw after max retries', async () => { - await expect( - retry(() => Promise.reject(new Error('persistent error')), { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }), - ).rejects.toThrow('persistent error'); - }); - }); - - describe('measureTime', () => { - it('should measure execution time', async () => { - const { result, durationMs } = await measureTime(async () => { - await sleep(50); - return 'done'; - }); - expect(result).toBe('done'); - expect(durationMs).toBeGreaterThanOrEqual(45); - }); - }); - - describe('now', () => { - it('should return ISO timestamp', () => { - const timestamp = now(); - expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/); - }); - }); - - describe('truncate', () => { - it('should not truncate short strings', () => { - expect(truncate('hello', 10)).toBe('hello'); - }); - - it('should truncate long strings', () => { - expect(truncate('hello world', 8)).toBe('hello...'); - }); - }); - - describe('redactSensitiveData', () => { - it('should redact sensitive keys', () => { - const obj = { password: 'secret', token: 'abc123', name: 'public' }; - const result = redactSensitiveData(obj); - expect(result).toEqual({ - password: '[REDACTED]', - token: '[REDACTED]', - name: 'public', - }); - }); - - it('should accept custom keys', () => { - const obj = { apiKey: 'secret', data: 'public' }; - const result = redactSensitiveData(obj, ['apiKey']); - expect(result).toEqual({ - apiKey: '[REDACTED]', - data: 'public', - }); - }); - }); - - describe('percentile', () => { - it('should calculate percentile correctly', () => { - const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - expect(percentile(values, 50)).toBe(5.5); - expect(percentile(values, 90)).toBe(9.1); - expect(percentile(values, 99)).toBe(9.91); - }); - - it('should return 0 for empty array', () => { - expect(percentile([], 50)).toBe(0); - }); - }); - - describe('calculateStats', () => { - it('should calculate statistics correctly', () => { - const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - const stats = calculateStats(values); - expect(stats.min).toBe(1); - expect(stats.max).toBe(10); - expect(stats.mean).toBe(5.5); - expect(stats.p50).toBe(5.5); - }); - - it('should return zeros for empty array', () => { - const stats = calculateStats([]); - expect(stats).toEqual({ - min: 0, - max: 0, - mean: 0, - p50: 0, - p90: 0, - p99: 0, - }); - }); - }); - - describe('isValidURL', () => { - it('should validate HTTP URLs', () => { - expect(isValidURL('http://example.com')).toBe(true); - }); - - it('should validate HTTPS URLs', () => { - expect(isValidURL('https://example.com')).toBe(true); - }); - - it('should reject invalid URLs', () => { - expect(isValidURL('not-a-url')).toBe(false); - expect(isValidURL('ftp://example.com')).toBe(false); - }); - }); - - describe('isPrivateURL', () => { - it('should detect localhost', () => { - expect(isPrivateURL('https://localhost:8080')).toBe(true); - expect(isPrivateURL('https://127.0.0.1')).toBe(true); - }); - - it('should detect private IP ranges', () => { - expect(isPrivateURL('https://192.168.1.1')).toBe(true); - expect(isPrivateURL('https://10.0.0.1')).toBe(true); - expect(isPrivateURL('https://172.16.0.1')).toBe(true); - }); - - it('should not flag public URLs', () => { - expect(isPrivateURL('https://example.com')).toBe(false); - expect(isPrivateURL('https://8.8.8.8')).toBe(false); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index f7af683..bbd6158 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,41 +4,27 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": ".", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, - "removeComments": false, + "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, - "useUnknownInCatchVariables": true, "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "exactOptionalPropertyTypes": false, + "noUnusedLocals": true, + "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "ignoreDeprecations": "6.0", - "types": ["node"], - "baseUrl": ".", - "paths": { - "@/*": ["src/*"] - } + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true }, - "include": ["src/**/*", "tests/**/*", "scripts/**/*"], - "exclude": ["node_modules", "dist", "coverage"] + "exclude": ["node_modules", "dist", "*.config.js"] } diff --git a/tsconfig.typecheck.json b/tsconfig.typecheck.json new file mode 100644 index 0000000..2463682 --- /dev/null +++ b/tsconfig.typecheck.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@reaatech/mcp-contract-core": ["./packages/core/src/index.ts"], + "@reaatech/mcp-contract-client": ["./packages/client/src/index.ts"], + "@reaatech/mcp-contract-validators": ["./packages/validators/src/index.ts"], + "@reaatech/mcp-contract-reporters": ["./packages/reporters/src/index.ts"], + "@reaatech/mcp-contract-observability": ["./packages/observability/src/index.ts"], + "@reaatech/mcp-contract-cli": ["./packages/cli/src/index.ts"] + } + }, + "include": ["packages/**/*.ts", "e2e/**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.config.ts", "src", "tests", "skills", "scripts", "docs"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..cd83e4f --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["build"] + }, + "test:coverage": { + "dependsOn": ["build"] + }, + "lint": {}, + "typecheck": { + "dependsOn": ["^build"] + }, + "clean": { + "cache": false + } + } +} diff --git a/vitest.config.ts b/vitest.config.ts index ff33568..e30b907 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,38 +1,11 @@ import { defineConfig } from 'vitest/config'; -import path from 'path'; export default defineConfig({ test: { - globals: true, + globals: false, environment: 'node', - include: ['tests/**/*.test.ts'], - exclude: ['node_modules', 'dist', 'coverage'], coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules', - 'dist', - 'tests', - 'scripts', - '*.config.*', - 'src/types/index.ts', - ], - thresholds: { - global: { - statements: 80, - branches: 80, - functions: 80, - lines: 80, - }, - }, - }, - testTimeout: 30000, - hookTimeout: 30000, - }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), + reporter: ['text', 'json-summary'], }, }, }); From 5f8c42853597a2a831e68b92177b80c104d00011 Mon Sep 17 00:00:00 2001 From: reaatech <138725666+reaatech@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:29:05 -0700 Subject: [PATCH 2/3] chore: add publishing infrastructure, CI, Docker, and per-package READMEs - Add changesets config with changelog-github for automated releases - Replace npm-based release.yml with changesets/action workflow - Replace ci.yml with full pnpm/Turbo/Biome pipeline matching a2a-reference-ts - Move Docker files to docker/ directory matching reference structure - Add per-package README.md and LICENSE files - Add publish-quality package badges and documentation - Remove old ESLint, Prettier, lint-staged, husky configs --- .changeset/README.md | 7 + .changeset/config.json | 11 + .dockerignore | 35 +-- .github/workflows/ci.yml | 380 ++++++++++++++++++++++++++---- .github/workflows/conformance.yml | 77 ------ .github/workflows/release.yml | 146 +++++------- .husky/pre-commit | 5 - .lintstagedrc.json | 13 - .prettierrc | 10 - Dockerfile | 35 --- docker-compose.yml | 34 --- docker/Dockerfile | 30 +++ docker/docker-compose.yml | 6 + eslint.config.mjs | 60 ----- packages/cli/LICENSE | 21 ++ packages/cli/README.md | 76 ++++++ packages/client/LICENSE | 21 ++ packages/client/README.md | 350 +++++++++++++++++++++++++++ packages/core/LICENSE | 21 ++ packages/core/README.md | 201 ++++++++++++++++ packages/observability/LICENSE | 21 ++ packages/observability/README.md | 376 +++++++++++++++++++++++++++++ packages/reporters/LICENSE | 21 ++ packages/reporters/README.md | 213 +++++++++++++++++ packages/validators/LICENSE | 21 ++ packages/validators/README.md | 289 +++++++++++++++++++++++ 26 files changed, 2087 insertions(+), 393 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json delete mode 100644 .github/workflows/conformance.yml delete mode 100755 .husky/pre-commit delete mode 100644 .lintstagedrc.json delete mode 100644 .prettierrc delete mode 100644 Dockerfile delete mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml delete mode 100644 eslint.config.mjs create mode 100644 packages/cli/LICENSE create mode 100644 packages/cli/README.md create mode 100644 packages/client/LICENSE create mode 100644 packages/client/README.md create mode 100644 packages/core/LICENSE create mode 100644 packages/core/README.md create mode 100644 packages/observability/LICENSE create mode 100644 packages/observability/README.md create mode 100644 packages/reporters/LICENSE create mode 100644 packages/reporters/README.md create mode 100644 packages/validators/LICENSE create mode 100644 packages/validators/README.md diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..6629ad4 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,7 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in the changesets repository](https://github.com/changesets/changesets). + +You can create a changeset by running `pnpm changeset`. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..7d754ff --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "reaatech/mcp-contract-kit" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.dockerignore b/.dockerignore index 22369d6..74e6b88 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,30 +1,15 @@ node_modules +.pnpm-store dist +build +*.tsbuildinfo +.git +.github coverage -.DS_Store +.vscode +.idea *.log -reports -.git -.gitignore -.husky .env -.env.example -.eslintrc* -.prettierrc -vitest.config.ts -*.md -tests -scripts -.github -skills -docs -*.test.ts -*.spec.ts -CLAUDE.md -AGENTS.md -ARCHITECTURE.md -DEV_PLAN.md -CONTRIBUTING.md -CHANGELOG.md -LICENSE -README.md \ No newline at end of file +.env.* +.DS_Store +Thumbs.db diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f3089..1e566f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,81 +6,375 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: 22 + jobs: - lint: + # ───────────────────────────────────────────── + # Setup & Install + # ───────────────────────────────────────────── + install: + name: Install Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run lint + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - typecheck: + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Save pnpm store + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + # ───────────────────────────────────────────── + # Security Audit + # ───────────────────────────────────────────── + audit: + name: Security Audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run typecheck + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - test: + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Audit dependencies + run: pnpm audit --audit-level moderate + + # ───────────────────────────────────────────── + # Code Format Check + # ───────────────────────────────────────────── + format: + name: Code Format runs-on: ubuntu-latest + needs: install steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run test:ci - - name: Upload coverage - uses: codecov/codecov-action@v4 + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 with: - files: ./coverage/lcov.info - fail_ci_if_error: false + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Check formatting + run: pnpm biome format --write . && git diff --exit-code + # ───────────────────────────────────────────── + # Lint + # ───────────────────────────────────────────── + lint: + name: Lint + runs-on: ubuntu-latest + needs: install + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Lint + run: pnpm lint + + # ───────────────────────────────────────────── + # Type Check + # ───────────────────────────────────────────── + typecheck: + name: Type Check + runs-on: ubuntu-latest + needs: install + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Type check + run: pnpm typecheck + + # ───────────────────────────────────────────── + # Build + # ───────────────────────────────────────────── build: + name: Build runs-on: ubuntu-latest + needs: [lint, typecheck] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run build - - run: node dist/src/cli.js --help + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - audit: + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Build packages + run: pnpm build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-dist + path: | + packages/*/dist + retention-days: 1 + + # ───────────────────────────────────────────── + # Test + # ───────────────────────────────────────────── + test: + name: Test runs-on: ubuntu-latest + needs: build + strategy: + fail-fast: false + matrix: + node-version: [20, 22] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run audit + node-version: ${{ matrix.node-version }} + cache: 'pnpm' - docker: + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-dist + + - name: Run tests + run: pnpm test + + # ───────────────────────────────────────────── + # Coverage + # ───────────────────────────────────────────── + coverage: + name: Coverage runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-dist + + - name: Run tests with coverage + run: pnpm test:coverage + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + path: | + packages/*/coverage + retention-days: 7 + + - name: Post coverage summary + run: | + echo '## Test Coverage Summary' >> $GITHUB_STEP_SUMMARY + for pkg in packages/*/coverage/coverage-summary.json; do + if [ -f "$pkg" ]; then + name=$(basename $(dirname $(dirname "$pkg"))) + lines=$(jq -r '.total.lines.pct // "N/A"' "$pkg") + funcs=$(jq -r '.total.functions.pct // "N/A"' "$pkg") + branches=$(jq -r '.total.branches.pct // "N/A"' "$pkg") + echo "| $name | $lines% | $funcs% | $branches% |" >> $GITHUB_STEP_SUMMARY + fi + done + + # ───────────────────────────────────────────── + # Docker Build + # ───────────────────────────────────────────── + docker-build: + name: Docker Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . + file: ./docker/Dockerfile push: false - load: true tags: mcp-contract-kit:latest cache-from: type=gha cache-to: type=gha,mode=max + + # ───────────────────────────────────────────── + # Docker Compose Validate + # ───────────────────────────────────────────── + docker-compose: + name: Docker Compose + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate Docker Compose + working-directory: ./docker + run: docker compose config + + # ───────────────────────────────────────────── + # All Checks Passed + # ───────────────────────────────────────────── + all-checks: + name: All Checks Passed + runs-on: ubuntu-latest + needs: [audit, format, lint, typecheck, build, test, coverage, docker-build, docker-compose] + if: always() + steps: + - name: Check all jobs passed + run: | + check() { + if [ "$1" != "success" ]; then + echo "$2 did not succeed: $1" + exit 1 + fi + } + check '${{ needs.audit.result }}' 'Security Audit' + check '${{ needs.format.result }}' 'Code Format' + check '${{ needs.lint.result }}' 'Lint' + check '${{ needs.typecheck.result }}' 'Type Check' + check '${{ needs.build.result }}' 'Build' + check '${{ needs.test.result }}' 'Test' + check '${{ needs.coverage.result }}' 'Coverage' + check '${{ needs.docker-build.result }}' 'Docker Build' + check '${{ needs.docker-compose.result }}' 'Docker Compose' + echo "All checks passed!" diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml deleted file mode 100644 index 65cd4d1..0000000 --- a/.github/workflows/conformance.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Conformance - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - conformance: - runs-on: ubuntu-latest - strategy: - matrix: - server: - - name: mcp-server-starter-ts - url: http://localhost:8080 - version: latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install contract-kit - run: npm ci && npm link - - - name: Start test server - run: npx mcp-server-starter-ts & - background: true - - - name: Wait for server - run: sleep 10 - - - name: Run conformance tests - run: | - mcp-contract-kit test ${{ matrix.server.url }} \ - --format json \ - --output conformance-report.json - - - name: Upload report - uses: actions/upload-artifact@v4 - if: always() - with: - name: conformance-report-${{ matrix.server.name }} - path: conformance-report.json - - - name: Check for critical failures - run: | - CRITICAL=$(jq '.failures.critical' conformance-report.json) - if [ "$CRITICAL" -gt "0" ]; then - echo "Critical conformance issues found" - cat conformance-report.json - exit 1 - fi - - - name: Post results as check run - uses: actions/github-script@v7 - if: github.event_name == 'pull_request' - with: - script: | - const fs = require('fs'); - const report = JSON.parse(fs.readFileSync('conformance-report.json', 'utf8')); - const conclusion = report.failures.critical > 0 ? 'failure' : 'success'; - github.rest.checks.create({ - owner: context.repo.owner, - repo: context.repo.repo, - name: 'MCP Conformance - ${{ matrix.server.name }}', - head_sha: context.sha, - status: 'completed', - conclusion: conclusion, - output: { - title: 'Conformance Test Results', - summary: `Passed: ${report.summary.passed}, Failed: ${report.summary.failed}, Warnings: ${report.summary.warnings}` - } - }) \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 973e8c9..077068f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,109 +1,73 @@ name: Release on: - push: - tags: - - 'v*' + workflow_dispatch: + # push: + # branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + NODE_VERSION: 22 jobs: - build: + release: + name: Release runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write + packages: write steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Run tests - run: npm run test + fetch-depth: 0 - - name: Run linting - run: npm run lint + - name: Setup pnpm + uses: pnpm/action-setup@v4 - - name: Run typecheck - run: npm run typecheck - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - - name: Build - run: npm run build + - name: Build packages + run: pnpm build - - name: Publish to npm - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - build-docker: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 + - name: Create release PR or publish to npm + id: changesets + uses: changesets/action@v1 with: - images: mcp-contract-kit - tags: | - type=semver,pattern={{version}} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - - create-release: - needs: [publish-npm, build-docker] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Generate release notes - run: | - echo "# Release ${{ github.ref_name }}" > release-notes.md - echo "" >> release-notes.md - echo "## Changes" >> release-notes.md - git log --oneline -20 >> release-notes.md + publish: pnpm release + version: pnpm version-packages + commit: 'chore(release): version packages' + title: 'chore(release): version packages' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: 'true' - - name: Create GitHub release - uses: softprops/action-gh-release@v1 - with: - body_path: release-notes.md - generate_release_notes: true + - name: Mirror published packages to GitHub Packages + if: steps.changesets.outputs.published == 'true' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + run: | + cat > .npmrc < $dir" + (cd "$dir" && npm publish --registry=https://npm.pkg.github.com) + done diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index d62c0e3..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -npm run lint -npm run typecheck -npm test diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 553ea4f..0000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "src/**/*.{ts,js}": [ - "eslint --fix", - "prettier --write" - ], - "tests/**/*.{ts,js}": [ - "eslint --fix", - "prettier --write" - ], - "*.{json,md,yaml,yml}": [ - "prettier --write" - ] -} diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 475d54e..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "tabWidth": 2, - "semi": true, - "printWidth": 100, - "bracketSpacing": true, - "arrowParens": "always", - "endOfLine": "lf" -} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8d18fd5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -# Stage 1: Build -FROM node:22-alpine AS builder - -WORKDIR /app - -# Copy package files -COPY package.json package-lock.json ./ -RUN npm ci --ignore-scripts - -# Copy source and build -COPY . . -RUN npm run build - -# Stage 2: Production -FROM node:22-alpine AS production - -WORKDIR /app - -# Create non-root user -RUN addgroup -g 1001 -S nodejs && \ - adduser -S nodejs -u 1001 - -# Copy package files and install production deps only -COPY package.json package-lock.json ./ -RUN npm ci --only=production --ignore-scripts && npm cache clean --force - -# Copy built files from builder -COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist - -# Switch to non-root user -USER nodejs - -# Entry point -ENTRYPOINT ["node", "dist/src/cli.js"] -CMD ["--help"] diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 7601fae..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - contract-kit: - build: . - command: test http://mock-server:8080 --verbose - depends_on: - - mock-server - volumes: - - ./reports:/app/reports - environment: - - NODE_ENV=test - - LOG_LEVEL=info - - mock-server: - image: node:22-alpine - working_dir: /app - command: > - npx -y mcp-server-starter-ts --port 8080 - ports: - - "8080:8080" - - integration-test: - build: . - command: sh -c "sleep 5 && mcp-contract-kit test http://mock-server:8080 --format json --output /app/reports/integration.json" - depends_on: - - mock-server - volumes: - - ./reports:/app/reports - environment: - - NODE_ENV=test - profiles: - - test - -volumes: - reports: \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..52cba16 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,30 @@ +FROM node:22-alpine AS base +RUN npm install -g pnpm@10 + +FROM base AS deps +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json biome.json ./ +COPY packages/ ./packages/ +COPY e2e/ ./e2e/ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app ./ +RUN pnpm build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml +COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml +COPY --from=builder /app/turbo.json ./turbo.json +COPY --from=builder /app/tsconfig.json ./tsconfig.json +COPY --from=builder /app/biome.json ./biome.json +COPY --from=builder /app/packages ./packages +COPY --from=builder /app/e2e ./e2e +RUN pnpm install --prod --frozen-lockfile + +ENTRYPOINT ["node", "packages/cli/dist/cli.js"] +CMD ["--help"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..54aa511 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,6 @@ +services: + contract-kit: + build: + context: .. + dockerfile: docker/Dockerfile + command: test http://host.docker.internal:8080 diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index acbb5ac..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import globals from 'globals'; - -const tsconfigRootDir = path.dirname(fileURLToPath(import.meta.url)); - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - languageOptions: { - globals: { - ...globals.node, - ...globals.es2022, - }, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - projectService: true, - tsconfigRootDir, - }, - }, - rules: { - 'no-console': 'error', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - }, - ], - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/prefer-nullish-coalescing': 'error', - '@typescript-eslint/prefer-optional-chain': 'error', - 'eqeqeq': ['error', 'always'], - 'no-var': 'error', - 'prefer-const': 'error', - 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], - }, - }, - { - ignores: [ - 'dist/**', - 'coverage/**', - 'node_modules/**', - '*.js', - '*.d.ts', - ], - } -); diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..3597e52 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,76 @@ +# @reaatech/mcp-contract-cli + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-cli)](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 + +CLI tool and public API for MCP contract conformance testing. Test MCP servers against protocol, registry, routing, security, and performance validators. + +## Installation + +```bash +npm install -g @reaatech/mcp-contract-cli +# or +pnpm add @reaatech/mcp-contract-cli +``` + +## Feature Overview + +- Five test suites: protocol, registry, routing, security, performance +- Multiple output formats: console, JSON, Markdown, HTML +- Programmatic API for CI/CD integration +- Registry YAML validation with invariant checks +- Customizable severity thresholds and retry policies + +## Quick Start + +### CLI + +```bash +mcp-contract-kit test http://localhost:8080 +mcp-contract-kit test http://localhost:8080 --suite protocol --format json --output report.json +mcp-contract-kit validate-yaml ./agents/my-agent.yaml --strict +``` + +### Programmatic API + +```typescript +import { runTests, validateRegistry, generateReport } from "@reaatech/mcp-contract-cli"; + +const report = await runTests({ endpoint: "http://localhost:8080" }); +const html = await generateReport(report, "html"); +``` + +## API Reference + +### Runner Functions + +- `runTests(options: RunOptions): Promise` +- `validateRegistry(options): Promise` +- `validateProtocol(options: RunOptions): Promise` +- `validateRouting(options: RunOptions): Promise` +- `generateReport(report, format): Promise` + +### CLI + +- `main(argv?)` — CLI entry point +- `parseArgs(args)` — argument parser +- `printHelp()` — help text + +### Re-exports + +The CLI package re-exports from all sibling packages for convenience. + +## Related Packages + +- [`@reaatech/mcp-contract-core`](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Core domain types, JSON-RPC 2.0 schemas, and utilities +- [`@reaatech/mcp-contract-client`](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK for connecting to MCP servers +- [`@reaatech/mcp-contract-validators`](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [`@reaatech/mcp-contract-reporters`](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [`@reaatech/mcp-contract-observability`](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — Structured logging, metrics, and tracing + +## License + +MIT diff --git a/packages/client/LICENSE b/packages/client/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000..b736449 --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,350 @@ +# @reaatech/mcp-contract-client + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-client)](https://www.npmjs.com/package/@reaatech/mcp-contract-client) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 — APIs may change before the stable release. + +MCP client SDK for connecting to and testing Model Context Protocol (MCP) servers over HTTP. Supports JSON-RPC 2.0 request/response, tool discovery, tool invocation, tracing propagation, and SSE streaming. + +## Installation + +```bash +npm install @reaatech/mcp-contract-client +``` + +```bash +pnpm add @reaatech/mcp-contract-client +``` + +## Feature Overview + +- Full JSON-RPC 2.0 request/response over HTTP +- Factory function (`createMCPClient`) and class API (`MCPHttpClient`) +- Tool discovery via `tools/list` and tool invocation via `tools/call` +- Automatic retry with configurable backoff +- AbortSignal-based request timeouts +- SSE (Server-Sent Events) stream parsing for streaming transports +- Trace context propagation via W3C `traceparent` headers +- Typed request builders for `initialize`, `tools/list`, and `tools/call` +- Composable transport layer (`MCPTransport` interface) + +## Quick Start + +```ts +import { createMCPClient, HttpTransport } from '@reaatech/mcp-contract-client'; + +// Create a client +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, +}); + +// Connect (sends initialize, or falls back to tools/list) +await client.connect(); + +// Discover tools +const tools = await client.listTools(); +console.log(tools.map(t => t.name)); + +// Call a tool +const result = await client.callTool('my_tool', { key: 'value' }); +console.log(result.content); + +// Disconnect +await client.disconnect(); +``` + +## API Reference + +### `createMCPClient(options)` + +Factory function that returns an `MCPClient` instance. + +```ts +function createMCPClient( + options: Omit & { endpoint: string } +): MCPClient; +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `endpoint` | `string` | _(required)_ | URL of the MCP server | +| `timeout` | `number` | _(required)_ | Request timeout in milliseconds | +| `retries` | `number` | _(required)_ | Maximum retry attempts on failure | +| `headers` | `Record` | `undefined` | Additional headers to include with every request | + +`MCPClient` is the interface from `@reaatech/mcp-contract-core`: + +```ts +interface MCPClient { + connect(): Promise; + sendRequest(request: MCPRequest): Promise>; + callTool(name: string, args: Record): Promise; + listTools(): Promise; + disconnect(): Promise; + getSessionId(): Promise; +} +``` + +--- + +### `MCPHttpClient` (class) + +Concrete `MCPClient` implementation that communicates with an MCP server over HTTP. Uses `HttpTransport` internally. + +```ts +import { MCPHttpClient } from '@reaatech/mcp-contract-client'; +``` + +#### Constructor + +```ts +new MCPHttpClient(options: MCPClientOptions) +``` + +`MCPClientOptions`: + +| Field | Type | Description | +|-------|------|-------------| +| `endpoint` | `string` | Server URL | +| `timeout` | `number` | Per-request timeout (ms) | +| `retries` | `number` | Max retry attempts | +| `headers` | `Record` _(optional)_ | Extra HTTP headers | + +#### Instance Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `connect()` | `Promise` | Sends an `initialize` request (falls back to `tools/list` to verify connectivity). Idempotent — safe to call when already connected. | +| `sendRequest(request)` | `Promise>` | Sends an arbitrary JSON-RPC request and returns the parsed response body. | +| `callTool(name, args)` | `Promise` | Sends a `tools/call` request and returns the tool result (`content` array + `isError` flag). | +| `listTools()` | `Promise` | Sends a `tools/list` request and returns the parsed tool definitions. Throws if the server returns an error. | +| `disconnect()` | `Promise` | Sends a `notifications/terminated` notification and resets internal state. Safe to call when already disconnected. | +| `getSessionId()` | `Promise` | Returns (or generates) a UUID session identifier. | + +--- + +### `HttpTransport` (class) + +Low-level HTTP transport that implements `MCPTransport`. Handles request serialization, response parsing, SSE detection, retries, and timeout. + +```ts +import { HttpTransport } from '@reaatech/mcp-contract-client'; +``` + +#### Constructor + +```ts +new HttpTransport(options: TransportOptions) +``` + +#### TransportOptions + +| Field | Type | Description | +|-------|------|-------------| +| `endpoint` | `string` | Server URL | +| `timeout` | `number` | Request timeout (ms), enforced via `AbortController` | +| `retries` | `number` | Max retry attempts with exponential backoff (base=100ms, max=2000ms) | +| `headers` | `Record` _(optional)_ | Extra HTTP headers (merged with `content-type: application/json`) | + +#### Instance Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `request(request)` | `Promise>` | Sends a JSON-RPC request via `POST` and returns the full transport response including status code and headers. Automatically handles SSE (`text/event-stream`) and JSON content types. | + +--- + +### `TransportResponse` + +Return value from `MCPTransport.request()`. + +| Field | Type | Description | +|-------|------|-------------| +| `body` | `MCPResponse` | Parsed JSON-RPC response body | +| `status` | `number` | HTTP status code | +| `headers` | `Headers` | Response headers | + +### `MCPTransport` (interface) + +Transport abstraction that can be implemented for custom transports. + +```ts +interface MCPTransport { + request(request: MCPRequest): Promise>; +} +``` + +--- + +### Request Builders + +Convenience functions that construct `MCPRequest` objects conforming to JSON-RPC 2.0. + +#### `buildRequest(method, params?, id?)` + +Constructs a generic JSON-RPC 2.0 request. + +```ts +function buildRequest( + method: string, + params?: Record, + id?: string | number +): MCPRequest; +``` + +#### `buildInitializeRequest(id?)` + +Constructs an `initialize` request with `protocolVersion: "2024-11-05"` and client info. + +```ts +function buildInitializeRequest(id?: string | number): MCPRequest; +``` + +#### `buildListToolsRequest(id?)` + +Constructs a `tools/list` request. + +```ts +function buildListToolsRequest(id?: string | number): MCPRequest; +``` + +#### `buildToolCallRequest(name, args, id?)` + +Constructs a `tools/call` request with the given tool name and arguments. + +```ts +function buildToolCallRequest( + name: string, + args: Record, + id?: string | number +): MCPRequest; +``` + +--- + +### `createTracingHeaders()` + +Generates W3C trace context propagation headers (`traceparent`) from the current OpenTelemetry context. Returns an empty object when no active context is found. + +```ts +function createTracingHeaders(): Record; +``` + +Commonly used to inject distributed tracing headers into `MCPClientOptions.headers`: + +```ts +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, + headers: createTracingHeaders(), +}); +``` + +## Usage Patterns + +### Sending Custom Requests + +Use `sendRequest` with a request builder for any MCP method: + +```ts +import { buildRequest } from '@reaatech/mcp-contract-client'; + +const response = await client.sendRequest( + buildRequest('resources/list', {}, 1) +); +``` + +### Tool Discovery & Invocation + +```ts +const tools = await client.listTools(); + +for (const tool of tools) { + console.log(`${tool.name}: ${tool.description}`); +} + +const result = await client.callTool('greet', { name: 'World' }); +if (result.isError) { + console.error('Tool returned an error'); +} else { + for (const item of result.content) { + if (item.type === 'text') { + console.log(item.text); + } + } +} +``` + +### Retry & Timeout Configuration + +The transport retries on network failures and HTTP errors with exponential backoff. Configure via constructor options: + +```ts +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 5000, // 5s per request + retries: 3, // up to 3 retries +}); +``` + +### Using the Transport Directly + +For cases where you need access to response status codes or headers, use `HttpTransport` directly: + +```ts +const transport = new HttpTransport({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, +}); + +const { body, status, headers } = await transport.request( + buildInitializeRequest() +); + +console.log(status); // 200 +``` + +## Error Handling + +The client throws on connection failures, timeouts, and transport-level errors: + +```ts +try { + await client.connect(); +} catch (error) { + console.error('Connection failed:', error.message); + // Example: "Failed to connect to MCP server at http://localhost:8080: ..." +} +``` + +JSON-RPC errors appear on the response object, not as thrown exceptions: + +```ts +const response = await client.sendRequest( + buildRequest('tools/call', { name: 'nonexistent', arguments: {} }) +); + +if (response.error) { + console.error(`RPC error ${response.error.code}: ${response.error.message}`); +} +``` + +The `callTool` helper normalizes errors — a JSON-RPC error produces a `ToolResult` with `isError: true` and an empty `content` array. + +## Related Packages + +- [@reaatech/mcp-contract-core](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Domain types, JSON-RPC 2.0 schemas, and shared utilities +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-observability](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — OpenTelemetry tracing and metrics +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and programmatic API +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Test result reporters (console, JSON, HTML) + +## License + +MIT diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..382d91e --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,201 @@ +# @reaatech/mcp-contract-core + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-core)](https://www.npmjs.com/package/@reaatech/mcp-contract-core) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://img.shields.io/github/actions/workflow/status/reaatech/mcp-contract-kit/ci.yml?branch=main)](https://github.com/reaatech/mcp-contract-kit/actions) + +> **Status:** Pre-1.0 — APIs may change in minor versions. Pin to a specific version in production. + +Core domain types, JSON-RPC 2.0 schemas, and shared utilities for MCP contract validation. +Provides the foundational enums, interfaces, Zod schemas, and helper functions used across all +`@reaatech/mcp-contract-*` packages. + +## Installation + +```bash +npm install @reaatech/mcp-contract-core +``` + +```bash +pnpm add @reaatech/mcp-contract-core +``` + +## Feature Overview + +- **Enums** — `Severity`, `TestCategory`, and `TestSuite` for categorizing test results +- **Result types** — `TestResult`, `TestReport`, and `ValidationError` interfaces for structured test output +- **Validator contracts** — `Validator` and `ValidationContext` interfaces for implementing custom validators +- **MCP client types** — `MCPClient`, `MCPRequest`, `MCPResponse`, `MCPError`, `ToolDefinition`, `ToolResult` for JSON-RPC 2.0 interaction +- **Zod schemas** — Runtime validation schemas for agent YAML, JSON-RPC messages, tools, and orchestrator contracts +- **Utilities** — UUID generation, retry with backoff, URL validation, statistics, sensitive data redaction, and more + +## Quick Start + +```ts +import { + Severity, + TestCategory, + TestSuite, + AgentConfigSchema, + MCPResponseSchema, +} from '@reaatech/mcp-contract-core'; +import type { + TestResult, + TestReport, + Validator, + AgentConfig, +} from '@reaatech/mcp-contract-core'; + +// Use enums +const severity = Severity.CRITICAL; +const category = TestCategory.PROTOCOL; + +// Create a test result +const result: TestResult = { + validator: 'json-rpc-compliance', + category: TestCategory.PROTOCOL, + passed: false, + severity: Severity.CRITICAL, + message: 'Missing jsonrpc field in response', + remediation: 'Add "jsonrpc": "2.0" to all responses', + durationMs: 5, + timestamp: new Date().toISOString(), +}; + +// Validate with schemas +const parsed = AgentConfigSchema.safeParse({ + agent_id: 'my-agent', + display_name: 'My Agent', + description: 'A test agent', + endpoint: 'https://api.example.com', + type: 'mcp', + is_default: false, + confidence_threshold: 0.8, + clarification_required: true, + examples: ['What is the weather?'], +}); + +if (!parsed.success) { + console.error(parsed.error.flatten()); +} +``` + +## Exports + +### Categories + +| Export | Kind | Description | +|--------|------|-------------| +| `Severity` | enum | `CRITICAL`, `WARNING`, `INFO` — severity levels for test results | +| `TestCategory` | enum | `REGISTRY`, `PROTOCOL`, `ROUTING`, `SECURITY`, `PERFORMANCE` — validator categories | +| `TestSuite` | enum | `REGISTRY`, `PROTOCOL`, `ROUTING`, `SECURITY`, `PERFORMANCE`, `ALL` — test suite identifiers | + +### Results + +| Export | Kind | Description | +|--------|------|-------------| +| `TestResult` | interface | Individual test result with validator, category, passed, severity, message, remediation, details, duration, timestamp | +| `TestReport` | interface | Aggregated test report with id, endpoint, timing, results array, summary, failures, passed flag, error, version | +| `ValidationError` | interface | Validation error with field, message, line, severity, type | + +### Validation + +| Export | Kind | Description | +|--------|------|-------------| +| `Validator` | interface | Validator contract with `name`, `category`, `severity`, `validate()`, and optional `setup()`/`teardown()` | +| `ValidationContext` | interface | Context passed to validators with `client`, `endpoint`, `options`, `requestId`, `artifacts` | + +### Client Types + +| Export | Kind | Description | +|--------|------|-------------| +| `MCPClient` | interface | MCP client interface with `connect()`, `sendRequest()`, `callTool()`, `listTools()`, `disconnect()`, `getSessionId()` | +| `MCPRequest` | interface | JSON-RPC 2.0 request with `jsonrpc`, `method`, `id`, `params` | +| `MCPResponse` | interface | JSON-RPC 2.0 response with `jsonrpc`, `id`, optional `result` or `error` | +| `MCPError` | interface | JSON-RPC 2.0 error with `code`, `message`, `data` | +| `ToolDefinition` | interface | MCP tool definition with `name`, `description`, `inputSchema` | +| `ToolResult` | interface | Tool call result with `content` array and optional `isError` | + +### Config Types + +| Export | Kind | Description | +|--------|------|-------------| +| `TestOptions` | interface | Test execution options with `timeout`, `retries`, `failOn`, `verbose`, `suites`, `yamlPath` | + +### Schemas + +| Export | Kind | Description | +|--------|------|-------------| +| `AgentConfigSchema` | ZodObject | Validates agent YAML definition (agent_id, display_name, description, endpoint, type, is_default, confidence_threshold, clarification_required, examples) | +| `MCPRequestSchema` | ZodObject | Validates JSON-RPC 2.0 request structure | +| `MCPErrorSchema` | ZodObject | Validates JSON-RPC 2.0 error structure | +| `MCPResponseSchema` | ZodObject | Validates JSON-RPC 2.0 response (enforces exactly one of result or error) | +| `ToolDefinitionSchema` | ZodObject | Validates MCP tool definition (name, description, inputSchema) | +| `AgentRequestContractSchema` | ZodObject | Validates orchestrator-to-agent request contract | +| `AgentResponseContractSchema` | ZodObject | Validates agent-to-orchestrator response contract | + +### Schema Types + +| Export | Kind | Description | +|--------|------|-------------| +| `AgentConfigInput` | type | Inferred type from `AgentConfigSchema` | +| `AgentConfig` | type | Alias for `AgentConfigInput` | +| `AgentType` | type | String literal type `'mcp'` | +| `ToolDefinitionInput` | type | Inferred type from `ToolDefinitionSchema` | +| `AgentRequestContract` | type | Inferred type from `AgentRequestContractSchema` | +| `AgentResponseContract` | type | Inferred type from `AgentResponseContractSchema` | + +### Utilities + +| Export | Kind | Description | +|--------|------|-------------| +| `generateId()` | function | Generate a unique request ID using timestamp and random string | +| `generateUUID()` | function | Generate a cryptographically random UUID v4 | +| `sleep(ms)` | function | Promise-based sleep for a given number of milliseconds | +| `retry(fn, opts)` | function | Retry a function with exponential backoff | +| `measureTime(fn)` | function | Measure execution time of an async function, returns `{ result, durationMs }` | +| `now()` | function | Get current ISO 8601 timestamp string | +| `truncate(str, maxLength)` | function | Truncate a string to a maximum length, appending `...` if truncated | +| `redactSensitiveData(obj, keys?)` | function | Redact sensitive keys (password, token, secret, key, authorization) from an object | +| `percentile(values, p)` | function | Calculate the p-th percentile from an array of numbers | +| `isValidURL(value)` | function | Check if a value is a valid http/https URL | +| `isPrivateURL(value)` | function | Check if a URL is private/localhost (SSRF protection) | +| `calculateStats(values)` | function | Calculate min, max, mean, p50, p90, p99 from an array of values | +| `isValidUUID(value)` | function | Validate a string against UUID v4 format | +| `getVersion()` | function | Read the package version from the nearest `package.json` | + +## Usage Pattern + +All Zod schemas are paired with inferred types. Use the schema for runtime validation and the +type for compile-time safety: + +```ts +import { AgentConfigSchema, MCPResponseSchema } from '@reaatech/mcp-contract-core'; +import type { AgentConfig, MCPResponse } from '@reaatech/mcp-contract-core'; + +// Runtime validation +function parseAgentConfig(raw: unknown): AgentConfig { + return AgentConfigSchema.parse(raw); +} + +// Type annotation +function handleResponse(response: MCPResponse): string | undefined { + const parsed = MCPResponseSchema.safeParse(response); + if (!parsed.success) { + throw new Error('Invalid JSON-RPC response'); + } + return parsed.data.result; +} +``` + +## Related Packages + +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and public API for conformance testing +- [@reaatech/mcp-contract-client](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [@reaatech/mcp-contract-observability](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — Logging, metrics, tracing + +## License + +MIT diff --git a/packages/observability/LICENSE b/packages/observability/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/observability/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/observability/README.md b/packages/observability/README.md new file mode 100644 index 0000000..29adfcd --- /dev/null +++ b/packages/observability/README.md @@ -0,0 +1,376 @@ +# @reaatech/mcp-contract-observability + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-observability)](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +> **Status:** Pre-1.0 — API surface is stable but may receive minor changes before the 1.0 release. + +Structured logging (pino), in-memory metrics, and W3C trace context propagation for MCP contract validation. + +## Installation + +```bash +npm install @reaatech/mcp-contract-observability +``` + +## Feature Overview + +| Feature | Capability | +|---------|------------| +| **Structured logging** | pino-based JSON logger with level filtering, ISO timestamps, and automatic PII redaction | +| **Child loggers** | Per-request loggers that inherit level and add `request_id` binding | +| **In-memory metrics** | Counters and duration histograms with label support, plus a `getSummary()` snapshot | +| **Trace context** | Lightweight span creation with W3C traceparent header serialization/parsing | +| **Span helpers** | `withSpan()` wraps async functions in a span with automatic ok/error status | + +## Quick Start + +```ts +import { + logger, + createLogger, + metrics, + MetricNames, + startSpan, + endSpan, + withSpan, + toTraceParent, + fromTraceParent, + getCurrentContext, + getSpans, + clearSpans, +} from '@reaatech/mcp-contract-observability'; +import type { Span, SpanContext } from '@reaatech/mcp-contract-observability'; + +// --- Logging --- +logger.info('Test started', { endpoint: 'http://localhost:8080' }); +logger.warn('High latency detected', { durationMs: 850 }); +logger.error('Connection failed', { error: 'ECONNREFUSED' }); + +const child = logger.child({ request_id: 'abc-123' }); +child.info('Processing request'); + +// --- Metrics --- +metrics.inc(MetricNames.TESTS_TOTAL); +metrics.inc(MetricNames.TESTS_PASSED); +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 42); + +const summary = metrics.getSummary(); +console.log(summary.counters, summary.histograms); + +// --- Tracing --- +const span = startSpan('protocol-validation'); +endSpan(span, 'ok'); + +const traceparent = toTraceParent(span.context); + +await withSpan('tool-call', async () => { + // child span context propagated automatically +}); +``` + +## API Reference + +### Logging + +#### `logger` + +Default singleton logger instance created with `createLogger()`. Uses `LOG_LEVEL` env var (defaults to `"info"`). + +```ts +import { logger } from '@reaatech/mcp-contract-observability'; + +logger.info(msg, data?); +logger.warn(msg, data?); +logger.error(msg, data?); +logger.debug(msg, data?); +logger.child(bindings); +``` + +Sensitive keys (`password`, `token`, `secret`, `key`, `authorization`, `api_key`, `employee_id`, `session_id`) are automatically redacted to `[REDACTED]` in all log output. + +#### `createLogger(options?)` + +Creates a new pino-backed logger instance. Accepts optional configuration: + +```ts +import { createLogger } from '@reaatech/mcp-contract-observability'; + +const myLogger = createLogger({ + level: 'debug', // default: LOG_LEVEL env var or 'info' + requestId: 'req-456', // optional; injected as request_id on every log line +}); +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `level` | `string` | `LOG_LEVEL` env or `"info"` | pino log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | +| `requestId` | `string` | — | If set, adds `request_id` binding to every log message | + +Returns an object with `info`, `warn`, `error`, `debug`, and `child` methods. Each log method signature is `(msg: string, data?: Record) => void`. The `child` method returns a new logger that inherits the parent's level. + +### Metrics + +#### `metrics` + +Singleton `MetricsCollector` instance. All counters and histograms are stored in memory and live for the process lifetime. + +```ts +import { metrics, MetricNames } from '@reaatech/mcp-contract-observability'; +``` + +#### `MetricNames` + +Constants for built-in metric names: + +| Constant | Value | +|----------|-------| +| `MetricNames.TESTS_TOTAL` | `"contract_kit_tests_total"` | +| `MetricNames.TESTS_PASSED` | `"contract_kit_tests_passed"` | +| `MetricNames.TESTS_FAILED` | `"contract_kit_tests_failed"` | +| `MetricNames.TESTS_WARNING` | `"contract_kit_tests_warning"` | +| `MetricNames.VALIDATOR_DURATION` | `"contract_kit_validator_duration_ms"` | +| `MetricNames.RUN_DURATION` | `"contract_kit_run_duration_ms"` | +| `MetricNames.ERRORS_TOTAL` | `"contract_kit_errors_total"` | + +#### `metrics.inc(name, value?, labels?)` + +Increments a counter by the given value (default `1`). + +```ts +metrics.inc(MetricNames.TESTS_TOTAL); +metrics.inc(MetricNames.TESTS_PASSED, 1, { suite: 'protocol' }); +metrics.inc('custom_counter', 5); +``` + +#### `metrics.getCounter(name, labels?)` + +Returns the current value of a counter. + +```ts +const passed = metrics.getCounter(MetricNames.TESTS_PASSED); +``` + +#### `metrics.recordDuration(name, durationMs, labels?)` + +Records a duration value (in milliseconds) into a histogram for the named metric. + +```ts +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 42); +metrics.recordDuration(MetricNames.RUN_DURATION, 1250, { format: 'json' }); +``` + +#### `metrics.getHistogram(name, labels?)` + +Returns histogram data with bucket counts, sum, and total count. + +```ts +const hist = metrics.getHistogram(MetricNames.VALIDATOR_DURATION); +// { buckets: [{ upperBound: 10, count: 1 }, ...], sum: 42, count: 1 } +``` + +Built-in bucket boundaries (ms): `[10, 50, 100, 250, 500, 1000, 2500, 5000, 10000]`. + +#### `metrics.getSummary()` + +Returns a snapshot of all counters, histograms, and process uptime. + +```ts +const summary = metrics.getSummary(); +// { +// uptime: 120, // seconds +// counters: { ... }, +// histograms: { +// 'contract_kit_validator_duration_ms': { +// count: 1, sum: 42, min: 42, max: 42, mean: 42 +// } +// } +// } +``` + +#### `metrics.reset()` + +Clears all counters and histograms and resets the uptime clock. + +```ts +metrics.reset(); +``` + +### Tracing + +#### `startSpan(name, attributes?)` + +Creates a new span, inheriting trace context from the currently active span (if any). Returns the span object and sets it as the active context. + +```ts +const span = startSpan('protocol-validation'); +const childSpan = startSpan('jsonrpc-check', { version: '2.0' }); +``` + +#### `endSpan(span, status?, errorMessage?)` + +Marks a span as complete with a status (`"ok"` | `"error"`, default `"ok"`) and optional error message. + +```ts +endSpan(span, 'ok'); + +try { + // ... +} catch (err) { + endSpan(span, 'error', (err as Error).message); + throw err; +} +``` + +#### `withSpan(name, fn, attributes?)` + +Executes an async function within a span. Returns the function's result. If the function throws, the span is automatically ended with `"error"` status and the error message is recorded before rethrowing. + +```ts +const result = await withSpan('tool-call', async () => { + return await fetch('http://localhost:8080'); +}); +``` + +#### `getCurrentContext()` + +Returns the currently active `SpanContext` or `null` if no span is active. + +```ts +const ctx = getCurrentContext(); +if (ctx) { + console.log(ctx.traceId, ctx.spanId); +} +``` + +#### `setCurrentContext(context)` + +Sets the active span context. Useful for manually propagating trace context across async boundaries or incoming requests. + +```ts +setCurrentContext({ traceId: 'abc...', spanId: 'def...' }); +setCurrentContext(null); // clear context +``` + +#### `toTraceParent(context)` + +Serializes a `SpanContext` into a W3C traceparent header value (`00-{traceId}-{spanId}-01`). + +```ts +const header = toTraceParent(span.context); +// "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" +``` + +#### `fromTraceParent(header)` + +Parses a W3C traceparent header string into a `SpanContext`. Returns `null` if the header is invalid. + +```ts +const ctx = fromTraceParent('00-abc-def-01'); +// { traceId: 'abc', spanId: 'def' } + +const invalid = fromTraceParent('not-valid'); +// null +``` + +#### `getSpans()` + +Returns all recorded spans (for export or debugging). + +```ts +const allSpans = getSpans(); +``` + +#### `clearSpans()` + +Clears all recorded spans and resets the active context. Useful between test runs. + +```ts +clearSpans(); +``` + +## Usage Patterns + +### Structured Context Logging + +```ts +logger.info('Validator started', { + suite: 'protocol', + validator: 'jsonrpc-version', + endpoint: 'http://localhost:8080', +}); +``` + +### Error Logging + +```ts +try { + await validate(); +} catch (err) { + logger.error('Validation failed', { + error: (err as Error).message, + suite: 'protocol', + }); +} +``` + +### Per-Request Child Loggers + +```ts +function handleRequest(requestId: string) { + const log = logger.child({ request_id: requestId }); + log.info('Request received'); + // ... all subsequent logs carry request_id ... +} +``` + +### Metrics with Labels + +```ts +metrics.inc(MetricNames.TESTS_FAILED, 1, { suite: 'protocol' }); +metrics.inc(MetricNames.TESTS_FAILED, 1, { suite: 'security' }); +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 35, { validator: 'jsonrpc' }); +``` + +### Trace Context Propagation + +```ts +const span = startSpan('test-run'); + +// Propagate via traceparent header +const header = toTraceParent(span.context); +await fetch('http://downstream:8080', { + headers: { traceparent: header }, +}); + +// After receiving an upstream traceparent +const upstreamCtx = fromTraceParent(req.headers.traceparent); +setCurrentContext(upstreamCtx); +const span = startSpan('handle-request'); + +endSpan(span, 'ok'); +``` + +### Nested Spans with withSpan + +```ts +await withSpan('test-run', async () => { + await withSpan('protocol-suite', async () => { + await withSpan('jsonrpc-check', async () => { + // validation logic + }); + }); +}); +``` + +## Related Packages + +- [@reaatech/mcp-contract-core](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Core types and schemas +- [@reaatech/mcp-contract-client](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and public API + +## License + +MIT diff --git a/packages/reporters/LICENSE b/packages/reporters/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/reporters/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/reporters/README.md b/packages/reporters/README.md new file mode 100644 index 0000000..dbdd6ea --- /dev/null +++ b/packages/reporters/README.md @@ -0,0 +1,213 @@ +# @reaatech/mcp-contract-reporters + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-reporters?color=blue)](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) +[![npm downloads](https://img.shields.io/npm/dm/@reaatech/mcp-contract-reporters)](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.8+-blue)](https://www.typescriptlang.org/) + +> **Status:** Pre-1.0 — API surface may change before the 1.0 release. Pin your +> dependency to a minor version for stability. + +Report formatters for MCP contract validation — console, JSON, Markdown, and +HTML output. Consumes `TestReport` objects produced by `@reaatech/mcp-contract-core` +and renders them in human- or machine-friendly formats. + +## Installation + +```bash +npm install @reaatech/mcp-contract-reporters +``` + +The package ships dual CJS/ESM builds with TypeScript declarations included. + +## Feature Overview + +| Format | Function | Use Case | +|------------|---------------------------|-------------------------------------| +| Console | `printConsoleReport` | Local CLI / terminal output | +| JSON | `formatJsonReport` | CI/CD pipelines, programmatic use | +| Markdown | `formatMarkdownReport` | GitHub issue comments, README docs | +| HTML | `generateHtmlReport` | Browser dashboard, email reports | +| Dispatcher | `formatReport` | Route to any format by string enum | + +All reporters receive a `TestReport` and return a formatted string (HTML and +Markdown reporters also return strings). + +## Quick Start + +```ts +import { runTests, TestSuite } from '@reaatech/mcp-contract-core'; +import { + formatReport, + printConsoleReport, + formatJsonReport, + formatMarkdownReport, + generateHtmlReport, +} from '@reaatech/mcp-contract-reporters'; + +// Produce a report by running conformance tests +const report = await runTests({ + endpoint: 'http://localhost:8080', + suites: [TestSuite.PROTOCOL, TestSuite.ROUTING], + timeout: 30000, +}); + +// Print a colored console report +printConsoleReport(report); + +// Get a machine-readable JSON string +const json = formatJsonReport(report); +await fs.writeFile('report.json', json); + +// Get GitHub-flavored Markdown +const md = formatMarkdownReport(report); +await fs.writeFile('report.md', md); + +// Generate an interactive HTML dashboard +const html = await generateHtmlReport(report); +await fs.writeFile('report.html', html); + +// Or use the dispatcher with a format string +const output = await formatReport(report, 'markdown'); +``` + +## Report Formats + +### Console + +`formatConsoleReport(report)` and `printConsoleReport(report)` render a colored +terminal report using ANSI escape codes. + +- Green / red / yellow severity indicators +- Emoji icons per result (`🔴` `🟡` `🟢`) +- Summary block with pass/fail counts +- Remediation hints printed inline for failing tests +- `printConsoleReport` writes directly to `process.stdout`, `formatConsoleReport` + returns the string + +### JSON + +`formatJsonReport(report)` produces a pretty-printed JSON string (`JSON.stringify` +with 2-space indent). The output is the raw `TestReport` object — ideal for +downstream tooling, CI parsing, or archival. + +### Markdown + +`formatMarkdownReport(report)` generates a GitHub-flavored Markdown document: + +- Summary table (endpoint, timestamp, duration, status, counts) +- Results table with validator name, pass/fail badge, severity, and message +- Dedicated *Failures* section with per-failure remediation blocks +- Footer with link back to the repository + +### HTML + +`generateHtmlReport(report)` returns a self-contained HTML document with embedded +CSS and JavaScript: + +- Summary cards (total, passed, critical, warnings, info) +- Expandable test rows — click any test to reveal its message and remediation +- Colour-coded severity badges +- Print-friendly styles +- No external dependencies; single `async` function returning `Promise` + +## API Reference + +### `formatReport(report, format)` + +Dispatch formatter by enum string. Returns `Promise` (all formats are +resolved synchronously except `html`, but the API is unified under `async`). + +```ts +function formatReport(report: TestReport, format: ReportFormat): Promise +``` + +### `formatConsoleReport(report)` / `printConsoleReport(report)` + +Colored console output. `formatConsoleReport` returns the string; `printConsoleReport` +writes it to stdout and returns `void`. + +```ts +function formatConsoleReport(report: TestReport): string +function printConsoleReport(report: TestReport): void +``` + +### `formatJsonReport(report)` + +Pretty-printed JSON — the raw `TestReport` object serialized with 2-space indentation. + +```ts +function formatJsonReport(report: TestReport): string +``` + +### `formatMarkdownReport(report)` + +GitHub-flavored Markdown with summary tables, result tables, and detailed failure +breakdowns. + +```ts +function formatMarkdownReport(report: TestReport): string +``` + +### `generateHtmlReport(report)` + +Self-contained interactive HTML dashboard. Returns a `Promise` with the +full document. + +```ts +function generateHtmlReport(report: TestReport): Promise +``` + +### `ReportFormat` + +String literal union type for use with `formatReport`. + +```ts +type ReportFormat = 'console' | 'json' | 'markdown' | 'html' +``` + +### `TestReport` (imported from `@reaatech/mcp-contract-core`) + +The input type shared by all reporters: + +```ts +interface TestReport { + id: string + endpoint: string + startedAt: string + completedAt: string + durationMs: number + timestamp: string + results: TestResult[] + summary: { total: number; passed: number; failed: number; warnings: number; critical: number } + failures: { critical: number; warning: number; info: number } + passed: boolean + error?: string + version: string +} + +interface TestResult { + validator: string + category: TestCategory + passed: boolean + severity: Severity + message: string + remediation?: string + details?: Record + durationMs: number + timestamp: string +} +``` + +## Related Packages + +| Package | Description | +|----------------------------------------------------------------|----------------------------------| +| [`@reaatech/mcp-contract-core`](../core) | Domain types, enums, interfaces | +| [`@reaatech/mcp-contract-validators`](../validators) | Test validator implementations | +| [`@reaatech/mcp-contract-skills`](../skills) | Skill definitions & contracts | +| [`@reaatech/mcp-contract-cli`](../cli) | CLI entry point | + +## License + +MIT © [Rick Somers](https://reaatech.com) diff --git a/packages/validators/LICENSE b/packages/validators/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/validators/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/validators/README.md b/packages/validators/README.md new file mode 100644 index 0000000..cf0f6eb --- /dev/null +++ b/packages/validators/README.md @@ -0,0 +1,289 @@ +# @reaatech/mcp-contract-validators + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-validators.svg)](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 — API may change before the stable release. Use with a lockfile. + +Conformance validators for MCP servers — protocol compliance, registry validation, routing contracts, security posture, and performance baseline. + +--- + +## Installation + +```bash +npm install @reaatech/mcp-contract-validators +# or +yarn add @reaatech/mcp-contract-validators +# or +pnpm add @reaatech/mcp-contract-validators +``` + +--- + +## Feature Overview + +| Suite | What It Checks | Severity | +|-------|----------------|----------| +| **Protocol** | JSON-RPC 2.0 compliance, `tools/list`, `tools/call`, session management | Critical | +| **Registry** | Agent YAML schema, invariants (unique IDs, default agent), env variable syntax | Critical | +| **Routing** | Request/response contract format, `handle_message` tool, cross-scenario compatibility | Critical | +| **Security** | SSRF protection, authentication posture, input sanitization (XSS, prompt injection, SQLi) | Warning | +| **Performance** | Latency percentiles (p50/p90/p99), concurrent request handling, rate limiting | Warning | + +--- + +## Quick Start + +```typescript +import { + getProtocolValidators, + getRegistryValidators, + getRoutingValidators, + getSecurityValidators, + getPerformanceValidators, +} from '@reaatech/mcp-contract-validators'; +import { createMCPClient } from '@reaatech/mcp-contract-client'; + +const client = createMCPClient({ endpoint: 'http://localhost:8080' }); +await client.connect(); + +// Run a single suite +for (const validator of getProtocolValidators()) { + const result = await validator.validate({ + client, + endpoint: 'http://localhost:8080', + options: { timeout: 30000, retries: 2 }, + requestId: crypto.randomUUID(), + }); + console.log(`${result.validator}: ${result.passed ? 'PASS' : 'FAIL'} — ${result.message}`); +} + +await client.disconnect(); +``` + +Or import individual validators directly: + +```typescript +import { + jsonrpcValidator, + toolDiscoveryValidator, + ssrfValidator, + latencyValidator, +} from '@reaatech/mcp-contract-validators'; + +const result = await jsonrpcValidator.validate(context); +``` + +--- + +## Validator Suites + +### Protocol + +Validates MCP JSON-RPC 2.0 specification compliance. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **JSON-RPC Validator** | `jsonrpcValidator` | Ensures all responses include `jsonrpc: "2.0"`, matching `id` fields, mutually exclusive `result`/`error`, valid error code ranges, and that unknown methods return proper errors. Sends `tools/list`, `initialize`, and a bogus method to verify end-to-end compliance. | +| **Tool Discovery Validator** | `toolDiscoveryValidator` | Validates the `tools/list` response: checks that at least one tool is exposed, no duplicate tool names, every tool has a valid name (lowercase with underscores/hyphens), a description, and a valid JSON Schema `inputSchema`. | +| **Tool Execution Validator** | `toolExecutionValidator` | Calls tools via `tools/call` with synthesized valid arguments, verifies that unknown tool names return errors, and checks that tools reject invalid arguments per their input schema. Validates response `content` array structure. | +| **Session Validator** | `sessionValidator` | Verifies that session IDs are non-empty, persist across calls and tool invocations, and that separate clients get isolated session identifiers. Recommends UUID-format session IDs. | + +### Registry + +Validates agent YAML configuration files for orchestrator registries. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **Schema Validator** | `schemaValidator` | Parses agent YAML files and validates each agent definition against the `AgentConfigSchema`. Ensures all required fields (`agent_id`, `display_name`, `description`, `endpoint`, `type`, `is_default`, `confidence_threshold`, `clarification_required`, `examples`) are present and correctly typed. Re-exports `validateAgentYAML()` for direct programmatic use. | +| **Invariant Validator** | `invariantValidator` | Validates cross-agent invariants: exactly one default agent, default agent has `confidence_threshold: 0`, all `agent_id` values are unique, all endpoints are valid URLs, no private/localhost endpoints (SSRF prevention), and endpoint URLs stay under 2048 characters. Re-exports `validateInvariants()` for direct use. | +| **Env Expansion Validator** | `envExpansionValidator` | Validates `${ENV_VAR}` syntax in YAML files: detects incomplete variable references, invalid variable names (must be uppercase with underscores), undefined environment variables, and circular references between env vars. Re-exports `validateEnvExpansion()` and `extractEnvVars()`. | + +### Routing + +Validates the request/response contracts used between an orchestrator and an agent. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **Request Contract Validator** | `requestContractValidator` | Ensures the agent exposes a `handle_message` tool that accepts the standard request format (`session_id`, `request_id`, `employee_id`, `raw_input`, plus optional `display_name`, `intent_summary`, `entities`, `turn_history`, `workflow_state`). Validates against `AgentRequestContractSchema`. | +| **Response Contract Validator** | `responseContractValidator` | Validates that `handle_message` responses conform to the standard response contract: `content` (non-empty string) and `workflow_complete` (boolean), with an optional `workflow_state` object. Validates against `AgentResponseContractSchema`. | +| **Compatibility Validator** | `compatibilityValidator` | End-to-end contract testing across multiple input scenarios: normal text, empty input, long input (1000 chars), unicode/emoji, and special characters (XSS payloads). Verifies consistent error handling across all scenarios. | + +### Security + +Validates the security posture of the MCP server. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **SSRF Validator** | `ssrfValidator` | Checks that the endpoint URL is not a private/localhost address, uses HTTPS in production, and is a syntactically valid URL. Flags private IPs and non-HTTPS endpoints as warnings. | +| **Auth Validator** | `authValidator` | Tests whether the server requires authentication by sending a request with an invalid token. Warns if the server accepts unauthenticated requests, and reports positively if 401/Unauthorized responses are returned. | +| **Input Sanitization Validator** | `inputSanitizationValidator` | Sends prompt injection, XSS, and SQL injection patterns to `handle_message` and checks whether the agent echoes back unsanitized payloads. Tests: "Ignore previous instructions" variants, `