Merge pull request #10 from modelcontextprotocol/e2e-testing
E2E testing
This commit is contained in:
parent
6a3130a075
commit
ace3220fca
8 changed files with 11726 additions and 17 deletions
111
test/README.md
Normal file
111
test/README.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Integration Test Suite
|
||||
|
||||
This directory contains integration tests for the use-mcp project that test the complete flow:
|
||||
|
||||
1. Building the use-mcp library
|
||||
2. Building the inspector example
|
||||
3. Starting MCP server(s)
|
||||
4. Testing browser connections to MCP servers
|
||||
|
||||
## Setup
|
||||
|
||||
Install test dependencies:
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
Run all integration tests (shows browser window):
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Run tests headlessly (no browser window):
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm test:headless
|
||||
```
|
||||
|
||||
Run tests in watch mode (re-runs on file changes):
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm test:watch
|
||||
```
|
||||
|
||||
Run tests with interactive UI:
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm test:ui
|
||||
```
|
||||
|
||||
Debug hanging processes:
|
||||
|
||||
```bash
|
||||
cd test
|
||||
pnpm test:debug
|
||||
```
|
||||
|
||||
## Test Architecture
|
||||
|
||||
- **Global Setup** (`setup/global-setup.ts`):
|
||||
- Builds use-mcp library and inspector
|
||||
- Starts hono-mcp server on port 9901
|
||||
- Starts static file server for inspector dist files
|
||||
|
||||
- **Global Teardown** (`setup/global-teardown.ts`):
|
||||
- Stops all servers and cleans up resources
|
||||
|
||||
- **Integration Tests** (`integration/mcp-connection.test.ts`):
|
||||
- Uses Playwright to automate browser interactions
|
||||
- Tests connection to MCP servers defined in `MCP_SERVERS` array
|
||||
- Verifies successful connections and tool availability
|
||||
- Logs debug information on failures
|
||||
|
||||
## Adding New MCP Servers
|
||||
|
||||
To test additional MCP servers, add them to the `MCP_SERVERS` array in the test file:
|
||||
|
||||
```typescript
|
||||
const MCP_SERVERS = [
|
||||
{
|
||||
name: 'hono-mcp',
|
||||
url: 'http://localhost:9901/mcp',
|
||||
expectedTools: 1,
|
||||
},
|
||||
{
|
||||
name: 'new-server',
|
||||
url: 'http://localhost:9902/mcp',
|
||||
expectedTools: 2,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Make sure to also update the global setup to start the new server process.
|
||||
|
||||
## Test Output
|
||||
|
||||
Successful connections will log:
|
||||
- ✅ Connection success message
|
||||
- 📋 List of available tools with count
|
||||
|
||||
Failed connections will log:
|
||||
- ❌ Failure message
|
||||
- 🐛 Debug log from the inspector interface
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If tests fail:
|
||||
|
||||
1. Check that all required dependencies are installed
|
||||
2. Verify that ports 9901 and 8000+ are available
|
||||
3. Look at the debug log output for MCP connection errors
|
||||
4. Check browser console logs for JavaScript errors
|
||||
5. Run tests with `--headed` flag to see browser interactions
|
||||
223
test/integration/mcp-connection.test.ts
Normal file
223
test/integration/mcp-connection.test.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
|
||||
import { chromium, Browser, Page } from 'playwright'
|
||||
import { readFileSync } from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const testDir = join(__dirname, '..')
|
||||
const testStateFile = join(testDir, 'node_modules/.cache/use-mcp-tests/test-state.json')
|
||||
|
||||
// Get MCP servers to test (ports determined at runtime)
|
||||
function getMCPServers() {
|
||||
try {
|
||||
const stateData = readFileSync(testStateFile, 'utf-8')
|
||||
const state = JSON.parse(stateData)
|
||||
|
||||
if (!state.honoPort) {
|
||||
throw new Error('hono-mcp port not found in test state')
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'hono-mcp',
|
||||
url: `http://localhost:${state.honoPort}/mcp`,
|
||||
expectedTools: 1, // Minimum expected tools count
|
||||
},
|
||||
// Add more servers here as they become available
|
||||
// {
|
||||
// name: 'another-server',
|
||||
// url: 'http://localhost:9902/mcp',
|
||||
// expectedTools: 1,
|
||||
// },
|
||||
]
|
||||
} catch (error) {
|
||||
throw new Error(`Test environment not properly initialized: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function connectToMCPServer(page: Page, serverUrl: string): Promise<{ success: boolean; tools: string[]; debugLog: string }> {
|
||||
// Navigate to the inspector
|
||||
const stateData = readFileSync(testStateFile, 'utf-8')
|
||||
const state = JSON.parse(stateData)
|
||||
|
||||
if (!state.staticPort) {
|
||||
throw new Error('Static server port not available - state: ' + JSON.stringify(state))
|
||||
}
|
||||
const staticPort = state.staticPort
|
||||
|
||||
await page.goto(`http://localhost:${staticPort}`)
|
||||
|
||||
// Wait for the page to load
|
||||
await page.waitForSelector('input[placeholder="Enter MCP server URL"]', { timeout: 10000 })
|
||||
|
||||
// Enter the server URL
|
||||
const urlInput = page.locator('input[placeholder="Enter MCP server URL"]')
|
||||
await urlInput.fill(serverUrl)
|
||||
|
||||
// Click connect button
|
||||
const connectButton = page.locator('button:has-text("Connect")')
|
||||
await connectButton.click()
|
||||
|
||||
// Wait for connection attempt to complete (max 10 seconds)
|
||||
await page.waitForTimeout(1000) // Initial wait
|
||||
|
||||
// Check for connection status
|
||||
let attempts = 0
|
||||
const maxAttempts = 20 // 10 seconds total (500ms * 20)
|
||||
let isConnected = false
|
||||
|
||||
while (attempts < maxAttempts && !isConnected) {
|
||||
try {
|
||||
// Check if status badge shows "Connected"
|
||||
const statusBadge = page.locator('.px-2.py-1.rounded-full')
|
||||
if ((await statusBadge.count()) > 0) {
|
||||
const statusText = await statusBadge.textContent({ timeout: 500 })
|
||||
if (statusText?.toLowerCase().includes('connected')) {
|
||||
isConnected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if tools count is > 0
|
||||
const toolsHeader = page.locator('h3:has-text("Available Tools")')
|
||||
if ((await toolsHeader.count()) > 0) {
|
||||
const toolsText = await toolsHeader.textContent()
|
||||
if (toolsText && /\d+/.test(toolsText)) {
|
||||
const toolsCount = parseInt(toolsText.match(/\d+/)?.[0] || '0')
|
||||
if (toolsCount > 0) {
|
||||
isConnected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue waiting
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
attempts++
|
||||
}
|
||||
|
||||
// Extract available tools
|
||||
const tools: string[] = []
|
||||
try {
|
||||
// Look for tool cards in the tools container
|
||||
const toolCards = page.locator('.bg-white.rounded.border')
|
||||
const toolCount = await toolCards.count()
|
||||
|
||||
for (let i = 0; i < toolCount; i++) {
|
||||
const toolNameElement = toolCards.nth(i).locator('h4.font-bold.text-base.text-black')
|
||||
const toolName = await toolNameElement.textContent()
|
||||
if (toolName?.trim()) {
|
||||
tools.push(toolName.trim())
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not extract tools list:', e)
|
||||
}
|
||||
|
||||
// Extract debug log
|
||||
let debugLog = ''
|
||||
try {
|
||||
const debugContainer = page.locator('.h-32.overflow-y-auto.font-mono.text-xs')
|
||||
if ((await debugContainer.count()) > 0) {
|
||||
debugLog = (await debugContainer.first().textContent()) || ''
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not extract debug log:', e)
|
||||
}
|
||||
|
||||
return {
|
||||
success: isConnected,
|
||||
tools,
|
||||
debugLog,
|
||||
}
|
||||
}
|
||||
|
||||
describe('MCP Connection Integration Tests', () => {
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
|
||||
beforeAll(async () => {
|
||||
const headless = process.env.HEADLESS === 'true'
|
||||
console.log(`🌐 Launching browser in ${headless ? 'headless' : 'headed'} mode`)
|
||||
browser = await chromium.launch({ headless })
|
||||
}, 30000)
|
||||
|
||||
afterAll(async () => {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
// Force cleanup before Vitest exits - don't throw errors
|
||||
const state = globalThis.__INTEGRATION_TEST_STATE__
|
||||
try {
|
||||
if (state?.honoServer && !state.honoServer.killed) {
|
||||
console.log('🔥 Force cleanup before test exit...')
|
||||
state.honoServer.kill('SIGKILL')
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors - process might already be dead
|
||||
}
|
||||
|
||||
try {
|
||||
if (state?.staticServer) {
|
||||
state.staticServer.close()
|
||||
state.staticServer.closeAllConnections?.()
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
|
||||
// Enable console logging for debugging
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
console.error(`Browser error: ${msg.text()}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (page) {
|
||||
await page.close()
|
||||
await page.context().close()
|
||||
}
|
||||
})
|
||||
|
||||
test('should connect to all MCP servers and retrieve tools', async () => {
|
||||
const servers = getMCPServers()
|
||||
|
||||
for (const server of servers) {
|
||||
console.log(`\n🔗 Testing connection to ${server.name} at ${server.url}`)
|
||||
|
||||
const result = await connectToMCPServer(page, server.url)
|
||||
|
||||
if (result.success) {
|
||||
console.log(`✅ Successfully connected to ${server.name}`)
|
||||
console.log(`📋 Available tools (${result.tools.length}):`)
|
||||
result.tools.forEach((tool, index) => {
|
||||
console.log(` ${index + 1}. ${tool}`)
|
||||
})
|
||||
|
||||
// Verify connection success
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.tools.length).toBeGreaterThanOrEqual(server.expectedTools)
|
||||
} else {
|
||||
console.log(`❌ Failed to connect to ${server.name}`)
|
||||
if (result.debugLog) {
|
||||
console.log(`🐛 Debug log:`)
|
||||
console.log(result.debugLog)
|
||||
}
|
||||
|
||||
// Fail the test with detailed information
|
||||
throw new Error(`Failed to connect to ${server.name}. Debug log: ${result.debugLog}`)
|
||||
}
|
||||
}
|
||||
}, 45000)
|
||||
})
|
||||
21
test/package.json
Normal file
21
test/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "use-mcp-integration-tests",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest --run",
|
||||
"test:headless": "HEADLESS=true vitest --run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:debug": "VITEST_REPORTER=hanging-process vitest --run"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "^1.49.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^2.1.8",
|
||||
"@vitest/ui": "^2.1.8",
|
||||
"typescript": "^5.8.2",
|
||||
"@types/node": "^20.17.10"
|
||||
}
|
||||
}
|
||||
971
test/pnpm-lock.yaml
generated
Normal file
971
test/pnpm-lock.yaml
generated
Normal file
|
|
@ -0,0 +1,971 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
playwright:
|
||||
specifier: ^1.49.0
|
||||
version: 1.53.1
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.17.10
|
||||
version: 20.19.2
|
||||
'@vitest/ui':
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.9(vitest@2.1.9)
|
||||
typescript:
|
||||
specifier: ^5.8.2
|
||||
version: 5.8.3
|
||||
vitest:
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.9(@types/node@20.19.2)(@vitest/ui@2.1.9)
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.1':
|
||||
resolution: {integrity: sha512-mBLKRHc7Ffw/hObYb9+cunuGNjshQk+vZdwZBJoqiysK/mW3Jq0UXosq8aIhMnLevANhR9yoYfdUEOHg6M9y0g==}
|
||||
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.44.1':
|
||||
resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.44.1':
|
||||
resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.44.1':
|
||||
resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.44.1':
|
||||
resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.44.1':
|
||||
resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.44.1':
|
||||
resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.44.1':
|
||||
resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.44.1':
|
||||
resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.44.1':
|
||||
resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.44.1':
|
||||
resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.44.1':
|
||||
resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.44.1':
|
||||
resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.44.1':
|
||||
resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.44.1':
|
||||
resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.44.1':
|
||||
resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/node@20.19.2':
|
||||
resolution: {integrity: sha512-9pLGGwdzOUBDYi0GNjM97FIA+f92fqSke6joWeBjWXllfNxZBs7qeMF7tvtOIsbY45xkWkxrdwUfUf3MnQa9gA==}
|
||||
|
||||
'@vitest/expect@2.1.9':
|
||||
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||
|
||||
'@vitest/mocker@2.1.9':
|
||||
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
|
||||
|
||||
'@vitest/runner@2.1.9':
|
||||
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
|
||||
|
||||
'@vitest/snapshot@2.1.9':
|
||||
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
|
||||
|
||||
'@vitest/spy@2.1.9':
|
||||
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
|
||||
|
||||
'@vitest/ui@2.1.9':
|
||||
resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==}
|
||||
peerDependencies:
|
||||
vitest: 2.1.9
|
||||
|
||||
'@vitest/utils@2.1.9':
|
||||
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
chai@5.2.0:
|
||||
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
check-error@2.1.1:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
deep-eql@5.0.2:
|
||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
esbuild@0.21.5:
|
||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
expect-type@1.2.1:
|
||||
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
fdir@6.4.6:
|
||||
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
flatted@3.3.3:
|
||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
loupe@3.1.4:
|
||||
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
|
||||
pathval@2.0.1:
|
||||
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.2:
|
||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
playwright-core@1.53.1:
|
||||
resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.53.1:
|
||||
resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
rollup@4.44.1:
|
||||
resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
sirv@3.0.1:
|
||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
std-env@3.9.0:
|
||||
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyglobby@0.2.14:
|
||||
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@1.1.1:
|
||||
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
||||
tinyrainbow@1.2.0:
|
||||
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@3.0.2:
|
||||
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
totalist@3.0.1:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
vite-node@2.1.9:
|
||||
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite@5.4.19:
|
||||
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
sass-embedded: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
vitest@2.1.9:
|
||||
resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': 2.1.9
|
||||
'@vitest/ui': 2.1.9
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.1': {}
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.44.1':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@20.19.2':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@vitest/expect@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
'@vitest/utils': 2.1.9
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/mocker@2.1.9(vite@5.4.19(@types/node@20.19.2))':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 5.4.19(@types/node@20.19.2)
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/runner@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/utils': 2.1.9
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/snapshot@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
magic-string: 0.30.17
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/spy@2.1.9':
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/ui@2.1.9(vitest@2.1.9)':
|
||||
dependencies:
|
||||
'@vitest/utils': 2.1.9
|
||||
fflate: 0.8.2
|
||||
flatted: 3.3.3
|
||||
pathe: 1.1.2
|
||||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.14
|
||||
tinyrainbow: 1.2.0
|
||||
vitest: 2.1.9(@types/node@20.19.2)(@vitest/ui@2.1.9)
|
||||
|
||||
'@vitest/utils@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
loupe: 3.1.4
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
chai@5.2.0:
|
||||
dependencies:
|
||||
assertion-error: 2.0.1
|
||||
check-error: 2.1.1
|
||||
deep-eql: 5.0.2
|
||||
loupe: 3.1.4
|
||||
pathval: 2.0.1
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
deep-eql@5.0.2: {}
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
esbuild@0.21.5:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.21.5
|
||||
'@esbuild/android-arm': 0.21.5
|
||||
'@esbuild/android-arm64': 0.21.5
|
||||
'@esbuild/android-x64': 0.21.5
|
||||
'@esbuild/darwin-arm64': 0.21.5
|
||||
'@esbuild/darwin-x64': 0.21.5
|
||||
'@esbuild/freebsd-arm64': 0.21.5
|
||||
'@esbuild/freebsd-x64': 0.21.5
|
||||
'@esbuild/linux-arm': 0.21.5
|
||||
'@esbuild/linux-arm64': 0.21.5
|
||||
'@esbuild/linux-ia32': 0.21.5
|
||||
'@esbuild/linux-loong64': 0.21.5
|
||||
'@esbuild/linux-mips64el': 0.21.5
|
||||
'@esbuild/linux-ppc64': 0.21.5
|
||||
'@esbuild/linux-riscv64': 0.21.5
|
||||
'@esbuild/linux-s390x': 0.21.5
|
||||
'@esbuild/linux-x64': 0.21.5
|
||||
'@esbuild/netbsd-x64': 0.21.5
|
||||
'@esbuild/openbsd-x64': 0.21.5
|
||||
'@esbuild/sunos-x64': 0.21.5
|
||||
'@esbuild/win32-arm64': 0.21.5
|
||||
'@esbuild/win32-ia32': 0.21.5
|
||||
'@esbuild/win32-x64': 0.21.5
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
expect-type@1.2.1: {}
|
||||
|
||||
fdir@6.4.6(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
flatted@3.3.3: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
loupe@3.1.4: {}
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.1
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
pathe@1.1.2: {}
|
||||
|
||||
pathval@2.0.1: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.2: {}
|
||||
|
||||
playwright-core@1.53.1: {}
|
||||
|
||||
playwright@1.53.1:
|
||||
dependencies:
|
||||
playwright-core: 1.53.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
rollup@4.44.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.44.1
|
||||
'@rollup/rollup-android-arm64': 4.44.1
|
||||
'@rollup/rollup-darwin-arm64': 4.44.1
|
||||
'@rollup/rollup-darwin-x64': 4.44.1
|
||||
'@rollup/rollup-freebsd-arm64': 4.44.1
|
||||
'@rollup/rollup-freebsd-x64': 4.44.1
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.44.1
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.44.1
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-arm64-musl': 4.44.1
|
||||
'@rollup/rollup-linux-loongarch64-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.44.1
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-x64-gnu': 4.44.1
|
||||
'@rollup/rollup-linux-x64-musl': 4.44.1
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.44.1
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.44.1
|
||||
'@rollup/rollup-win32-x64-msvc': 4.44.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
sirv@3.0.1:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.29
|
||||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@3.9.0: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyglobby@0.2.14:
|
||||
dependencies:
|
||||
fdir: 6.4.6(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinypool@1.1.1: {}
|
||||
|
||||
tinyrainbow@1.2.0: {}
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
typescript@5.8.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
vite-node@2.1.9(@types/node@20.19.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.1
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 1.1.2
|
||||
vite: 5.4.19(@types/node@20.19.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vite@5.4.19(@types/node@20.19.2):
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.5.6
|
||||
rollup: 4.44.1
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@2.1.9(@types/node@20.19.2)(@vitest/ui@2.1.9):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.9
|
||||
'@vitest/mocker': 2.1.9(vite@5.4.19(@types/node@20.19.2))
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
'@vitest/runner': 2.1.9
|
||||
'@vitest/snapshot': 2.1.9
|
||||
'@vitest/spy': 2.1.9
|
||||
'@vitest/utils': 2.1.9
|
||||
chai: 5.2.0
|
||||
debug: 4.4.1
|
||||
expect-type: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 1.1.2
|
||||
std-env: 3.9.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 0.3.2
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.4.19(@types/node@20.19.2)
|
||||
vite-node: 2.1.9(@types/node@20.19.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.2
|
||||
'@vitest/ui': 2.1.9(vitest@2.1.9)
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
- lightningcss
|
||||
- msw
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
339
test/setup/global-setup.ts
Normal file
339
test/setup/global-setup.ts
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
import { spawn, ChildProcess } from 'child_process'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { writeFileSync, mkdirSync, readFileSync } from 'fs'
|
||||
import { createServer, Server } from 'http'
|
||||
import { parse } from 'url'
|
||||
import { extname } from 'path'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = join(__dirname, '../..')
|
||||
const testDir = join(__dirname, '..')
|
||||
const cacheDir = join(testDir, 'node_modules/.cache/use-mcp-tests')
|
||||
const testStateFile = join(cacheDir, 'test-state.json')
|
||||
|
||||
interface GlobalState {
|
||||
honoServer?: ChildProcess
|
||||
staticServer?: Server
|
||||
staticPort?: number
|
||||
honoPort?: number
|
||||
processGroupId?: number
|
||||
allChildProcesses?: Set<number>
|
||||
}
|
||||
|
||||
declare global {
|
||||
var __INTEGRATION_TEST_STATE__: GlobalState
|
||||
}
|
||||
|
||||
function waitForOutput(process: ChildProcess, targetOutput: string, timeout = 30000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(new Error(`Timeout waiting for: ${targetOutput}`))
|
||||
}, timeout)
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
const output = data.toString()
|
||||
console.log(`[server output] ${output}`)
|
||||
if (output.includes(targetOutput)) {
|
||||
clearTimeout(timer)
|
||||
process.stdout?.off('data', onData)
|
||||
process.stderr?.off('data', onData)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout?.on('data', onData)
|
||||
process.stderr?.on('data', onData)
|
||||
})
|
||||
}
|
||||
|
||||
async function checkPortAvailable(port: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const server = require('net').createServer()
|
||||
|
||||
server.listen(port, () => {
|
||||
server.close(() => {
|
||||
// Wait a bit for the port to fully close
|
||||
setTimeout(() => resolve(true), 100)
|
||||
})
|
||||
})
|
||||
|
||||
server.on('error', (err: any) => {
|
||||
console.log(`Port ${port} check failed: ${err.message}`)
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function checkPortNotUsedByWrangler(port: number): Promise<boolean> {
|
||||
// First check if port is available at network level
|
||||
const networkAvailable = await checkPortAvailable(port)
|
||||
if (!networkAvailable) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Then check if there are wrangler processes using this port
|
||||
return new Promise((resolve) => {
|
||||
const { spawn } = require('child_process')
|
||||
const lsof = spawn('lsof', ['-ti', `:${port}`])
|
||||
|
||||
let output = ''
|
||||
lsof.stdout?.on('data', (data: Buffer) => {
|
||||
output += data.toString()
|
||||
})
|
||||
|
||||
lsof.on('close', () => {
|
||||
if (output.trim()) {
|
||||
// Port is in use
|
||||
resolve(false)
|
||||
} else {
|
||||
// Port is free
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
|
||||
lsof.on('error', () => {
|
||||
// lsof failed, assume port is available
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function findAvailablePortFromBase(basePort: number): Promise<number> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
for (let port = basePort; port < basePort + 20; port++) {
|
||||
if (await checkPortNotUsedByWrangler(port)) {
|
||||
resolve(port)
|
||||
return
|
||||
}
|
||||
}
|
||||
reject(new Error(`No available ports found starting from ${basePort}. Please stop any existing wrangler/workerd processes.`))
|
||||
})
|
||||
}
|
||||
|
||||
function runCommand(command: string, args: string[], cwd: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
})
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`Command failed with code ${code}: ${command} ${args.join(' ')}`))
|
||||
}
|
||||
})
|
||||
|
||||
child.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
function findAvailablePort(startPort = 8000): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
const server = createServer()
|
||||
server
|
||||
.listen(startPort, () => {
|
||||
const port = (server.address() as any)?.port
|
||||
server.close(() => resolve(port))
|
||||
})
|
||||
.on('error', () => {
|
||||
resolve(findAvailablePort(startPort + 1))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default async function globalSetup() {
|
||||
console.log('🔧 Setting up integration test environment...')
|
||||
|
||||
const state: GlobalState = {
|
||||
allChildProcesses: new Set<number>(),
|
||||
}
|
||||
globalThis.__INTEGRATION_TEST_STATE__ = state
|
||||
|
||||
// Set up signal handlers for cleanup
|
||||
const cleanup = () => {
|
||||
// Kill all tracked child processes
|
||||
if (state.allChildProcesses) {
|
||||
for (const pid of state.allChildProcesses) {
|
||||
try {
|
||||
process.kill(pid, 'SIGTERM')
|
||||
setTimeout(() => {
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL')
|
||||
} catch (e) {
|
||||
// Process already dead, ignore
|
||||
}
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
// Process already dead, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also try process group cleanup as backup
|
||||
if (state.processGroupId) {
|
||||
try {
|
||||
process.kill(-state.processGroupId, 'SIGTERM')
|
||||
setTimeout(() => {
|
||||
try {
|
||||
process.kill(-state.processGroupId!, 'SIGKILL')
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
// Ignore errors - process group may not exist
|
||||
}
|
||||
}
|
||||
|
||||
if (state.staticServer) {
|
||||
state.staticServer.close()
|
||||
state.staticServer.closeAllConnections?.()
|
||||
}
|
||||
}
|
||||
|
||||
process.on('SIGINT', cleanup)
|
||||
process.on('SIGTERM', cleanup)
|
||||
process.on('exit', cleanup)
|
||||
|
||||
try {
|
||||
// Step 1: Build use-mcp library
|
||||
console.log('📦 Building use-mcp library...')
|
||||
await runCommand('pnpm', ['build'], rootDir)
|
||||
|
||||
// Step 2: Build inspector example
|
||||
console.log('📦 Building inspector example...')
|
||||
const inspectorDir = join(rootDir, 'examples/inspector')
|
||||
await runCommand('pnpm', ['build'], inspectorDir)
|
||||
|
||||
// Step 3: Find available port and start hono-mcp server
|
||||
console.log('🔍 Finding available port starting from 9901...')
|
||||
const honoPort = await findAvailablePortFromBase(9901)
|
||||
console.log(`📍 Using port ${honoPort} for hono-mcp server`)
|
||||
|
||||
console.log('🚀 Starting hono-mcp server...')
|
||||
const honoDir = join(rootDir, 'examples/servers/hono-mcp')
|
||||
const honoServer = spawn('pnpm', ['dev', `--port=${honoPort}`], {
|
||||
cwd: honoDir,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
detached: false, // Keep in same process group initially
|
||||
})
|
||||
|
||||
// Track all child processes
|
||||
if (honoServer.pid) {
|
||||
state.allChildProcesses!.add(honoServer.pid)
|
||||
}
|
||||
|
||||
// Store the process group ID and port
|
||||
state.processGroupId = honoServer.pid
|
||||
state.honoPort = honoPort
|
||||
state.honoServer = honoServer
|
||||
|
||||
honoServer.stdout?.on('data', (data) => {
|
||||
console.log(`[hono-mcp] ${data.toString()}`)
|
||||
})
|
||||
|
||||
honoServer.stderr?.on('data', (data) => {
|
||||
console.log(`[hono-mcp] ${data.toString()}`)
|
||||
})
|
||||
|
||||
// Track when the process exits to remove it from our tracking
|
||||
honoServer.on('exit', () => {
|
||||
if (honoServer.pid) {
|
||||
state.allChildProcesses!.delete(honoServer.pid)
|
||||
}
|
||||
})
|
||||
|
||||
// Wait for hono server to be ready
|
||||
await waitForOutput(honoServer, 'Ready on')
|
||||
state.honoServer = honoServer
|
||||
|
||||
// Step 4: Start simple static file server for inspector
|
||||
console.log('🌐 Starting static file server for inspector...')
|
||||
const inspectorDistDir = join(inspectorDir, 'dist')
|
||||
const staticPort = await findAvailablePort(8000)
|
||||
|
||||
const staticServer = createServer((req, res) => {
|
||||
const pathname = parse(req.url || '').pathname || '/'
|
||||
let filePath = join(inspectorDistDir, pathname === '/' ? 'index.html' : pathname)
|
||||
|
||||
// Always disable keep-alive
|
||||
res.setHeader('Connection', 'close')
|
||||
|
||||
try {
|
||||
const data = readFileSync(filePath)
|
||||
const ext = extname(filePath)
|
||||
const contentType =
|
||||
{
|
||||
'.html': 'text/html',
|
||||
'.js': 'application/javascript',
|
||||
'.css': 'text/css',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
}[ext] || 'text/plain'
|
||||
|
||||
res.writeHead(200, { 'Content-Type': contentType })
|
||||
res.end(data)
|
||||
} catch (e) {
|
||||
// Fallback to index.html for SPA routing
|
||||
try {
|
||||
const indexData = readFileSync(join(inspectorDistDir, 'index.html'))
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end(indexData)
|
||||
} catch (e2) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
res.end('Not Found')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Configure for immediate shutdown
|
||||
staticServer.keepAliveTimeout = 0
|
||||
staticServer.headersTimeout = 1
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
staticServer.listen(staticPort, () => {
|
||||
console.log(`📁 Static server running on http://localhost:${staticPort}`)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
state.staticServer = staticServer
|
||||
state.staticPort = staticPort
|
||||
|
||||
// Write state to file for tests to read
|
||||
mkdirSync(cacheDir, { recursive: true })
|
||||
writeFileSync(
|
||||
testStateFile,
|
||||
JSON.stringify(
|
||||
{
|
||||
honoPort: state.honoPort,
|
||||
staticPort: state.staticPort,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
console.log('✅ Integration test environment ready!')
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to set up integration test environment:', error)
|
||||
|
||||
// Cleanup on failure
|
||||
if (state.honoServer) {
|
||||
state.honoServer.kill()
|
||||
}
|
||||
if (state.staticServer) {
|
||||
state.staticServer.close()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
174
test/setup/global-teardown.ts
Normal file
174
test/setup/global-teardown.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
function isProcessRunning(pid: number): boolean {
|
||||
try {
|
||||
// Sending signal 0 checks if process exists without actually sending a signal
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function findChildProcesses(parentPid: number): Promise<number[]> {
|
||||
return new Promise((resolve) => {
|
||||
const { spawn } = require('child_process')
|
||||
// Use ps to find all child processes
|
||||
const ps = spawn('ps', ['-o', 'pid,ppid', '-ax'])
|
||||
|
||||
let output = ''
|
||||
ps.stdout?.on('data', (data: Buffer) => {
|
||||
output += data.toString()
|
||||
})
|
||||
|
||||
ps.on('close', () => {
|
||||
const lines = output.split('\n')
|
||||
const children: number[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.trim().match(/^(\d+)\s+(\d+)/)
|
||||
if (match) {
|
||||
const pid = parseInt(match[1])
|
||||
const ppid = parseInt(match[2])
|
||||
if (ppid === parentPid) {
|
||||
children.push(pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(children)
|
||||
})
|
||||
|
||||
ps.on('error', () => {
|
||||
resolve([])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function killProcessSafely(pid: number, name: string = 'process'): Promise<void> {
|
||||
if (!isProcessRunning(pid)) {
|
||||
console.log(`✅ ${name} (${pid}) already stopped`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🛑 Sending SIGTERM to ${name} (${pid})`)
|
||||
process.kill(pid, 'SIGTERM')
|
||||
|
||||
// Wait up to 3 seconds for graceful shutdown
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
if (!isProcessRunning(pid)) {
|
||||
console.log(`✅ ${name} (${pid}) gracefully stopped`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Force kill if still running
|
||||
if (isProcessRunning(pid)) {
|
||||
console.log(`⚡ Force killing ${name} (${pid})`)
|
||||
process.kill(pid, 'SIGKILL')
|
||||
|
||||
// Give it a moment to die
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
if (isProcessRunning(pid)) {
|
||||
console.warn(`⚠️ ${name} (${pid}) still running after SIGKILL`)
|
||||
} else {
|
||||
console.log(`✅ ${name} (${pid}) force killed`)
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ESRCH') {
|
||||
console.log(`✅ ${name} (${pid}) already stopped`)
|
||||
} else {
|
||||
console.warn(`Error killing ${name} (${pid}):`, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function globalTeardown() {
|
||||
console.log('🧹 Cleaning up integration test environment...')
|
||||
|
||||
const state = globalThis.__INTEGRATION_TEST_STATE__
|
||||
|
||||
// Kill all tracked child processes first
|
||||
if (state?.allChildProcesses && state.allChildProcesses.size > 0) {
|
||||
console.log(`🛑 Cleaning up ${state.allChildProcesses.size} tracked child processes...`)
|
||||
|
||||
// Find all child processes of our tracked processes too
|
||||
const allPidsToKill = new Set<number>()
|
||||
|
||||
for (const pid of state.allChildProcesses) {
|
||||
allPidsToKill.add(pid)
|
||||
|
||||
// Find children of this process
|
||||
const children = await findChildProcesses(pid)
|
||||
for (const childPid of children) {
|
||||
allPidsToKill.add(childPid)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🛑 Found ${allPidsToKill.size} total processes to clean up (including children)`)
|
||||
|
||||
const killPromises = Array.from(allPidsToKill).map((pid) => killProcessSafely(pid, 'process'))
|
||||
|
||||
await Promise.all(killPromises)
|
||||
}
|
||||
|
||||
// Also clean up by process group as backup
|
||||
if (state?.processGroupId && isProcessRunning(state.processGroupId)) {
|
||||
console.log('🛑 Cleaning up process group as backup...')
|
||||
|
||||
try {
|
||||
// Send SIGTERM to the entire process group first
|
||||
console.log(`💀 Sending SIGTERM to process group ${state.processGroupId}`)
|
||||
process.kill(-state.processGroupId, 'SIGTERM')
|
||||
|
||||
// Wait a bit for graceful shutdown
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
// Check if any processes in the group are still running and force kill if needed
|
||||
try {
|
||||
process.kill(-state.processGroupId, 0) // Check if group exists
|
||||
console.log(`⚡ Sending SIGKILL to process group ${state.processGroupId}`)
|
||||
process.kill(-state.processGroupId, 'SIGKILL')
|
||||
} catch (e: any) {
|
||||
if (e.code !== 'ESRCH') {
|
||||
console.warn('Error force-killing process group:', e.message)
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.code !== 'ESRCH') {
|
||||
console.warn('Error terminating process group:', e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state?.staticServer) {
|
||||
console.log('🛑 Stopping static file server...')
|
||||
await new Promise<void>((resolve) => {
|
||||
state.staticServer?.close((err) => {
|
||||
if (err) {
|
||||
console.warn('Warning closing static server:', err.message)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
// Force close all keep-alive connections
|
||||
state.staticServer?.closeAllConnections?.()
|
||||
}
|
||||
|
||||
// Clear references
|
||||
if (state) {
|
||||
state.honoServer = undefined
|
||||
state.staticServer = undefined
|
||||
state.processGroupId = undefined
|
||||
}
|
||||
|
||||
// Force garbage collection if available
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
|
||||
console.log('✅ Cleanup complete!')
|
||||
}
|
||||
17
test/tsconfig.json
Normal file
17
test/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"]
|
||||
}
|
||||
10
test/vitest.config.ts
Normal file
10
test/vitest.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globalSetup: ['./setup/global-setup.ts'],
|
||||
globalTeardown: ['./setup/global-teardown.ts'],
|
||||
testTimeout: 60000, // 60 second timeout for integration tests
|
||||
reporters: process.env.VITEST_REPORTER === 'hanging-process' ? ['hanging-process'] : ['default'],
|
||||
},
|
||||
})
|
||||
Loading…
Reference in a new issue