clean up deps: remove unused, update zod v4, replace chalk with picocolors

This commit is contained in:
Tommy D. Rossi
2026-01-15 13:32:54 +01:00
parent 423365caae
commit cda69383f0
3 changed files with 307 additions and 293 deletions
+4 -6
View File
@@ -32,6 +32,7 @@
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@vitest/ui": "^4.0.8", "@vitest/ui": "^4.0.8",
"bippy": "^0.5.27", "bippy": "^0.5.27",
"devtools-protocol": "^0.0.1568893",
"image-size": "^2.0.2", "image-size": "^2.0.2",
"mcp-extension": "workspace:*", "mcp-extension": "workspace:*",
"vite-node": "^5.0.0", "vite-node": "^5.0.0",
@@ -40,22 +41,19 @@
"dependencies": { "dependencies": {
"@hono/node-server": "^1.19.6", "@hono/node-server": "^1.19.6",
"@hono/node-ws": "^1.2.0", "@hono/node-ws": "^1.2.0",
"@modelcontextprotocol/sdk": "^1.21.1", "@modelcontextprotocol/sdk": "^1.25.2",
"cac": "^6.7.14", "cac": "^6.7.14",
"chalk": "^5.6.2",
"colord": "^2.9.3",
"devtools-protocol": "^0.0.1543509",
"diff": "^8.0.2", "diff": "^8.0.2",
"hono": "^4.10.6", "hono": "^4.10.6",
"kill-port-process": "^3.2.1", "kill-port-process": "^3.2.1",
"picocolors": "^1.1.1",
"playwright-core": "^1.56.1", "playwright-core": "^1.56.1",
"posthtml": "^0.16.7", "posthtml": "^0.16.7",
"posthtml-beautify": "^0.7.0", "posthtml-beautify": "^0.7.0",
"string-dedent": "^3.0.2", "string-dedent": "^3.0.2",
"strip-ansi": "^7.1.2", "strip-ansi": "^7.1.2",
"user-agents": "^1.1.669",
"ws": "^8.18.3", "ws": "^8.18.3",
"zod": "^3" "zod": "^4.3.5"
}, },
"optionalDependencies": { "optionalDependencies": {
"sharp": "^0.34.5" "sharp": "^0.34.5"
+34 -34
View File
@@ -6,7 +6,7 @@ import type { WSContext } from 'hono/ws'
import type { Protocol } from './cdp-types.js' import type { Protocol } from './cdp-types.js'
import type { CDPCommand, CDPResponseBase, CDPEventBase, CDPEventFor, RelayServerEvents } from './cdp-types.js' import type { CDPCommand, CDPResponseBase, CDPEventBase, CDPEventFor, RelayServerEvents } from './cdp-types.js'
import type { ExtensionMessage, ExtensionEventMessage } from './protocol.js' import type { ExtensionMessage, ExtensionEventMessage } from './protocol.js'
import chalk from 'chalk' import pc from 'picocolors'
import { EventEmitter } from 'node:events' import { EventEmitter } from 'node:events'
import { VERSION } from './utils.js' import { VERSION } from './utils.js'
@@ -150,17 +150,17 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
} }
const detailsStr = details.length > 0 ? ` ${chalk.gray(details.join(', '))}` : '' const detailsStr = details.length > 0 ? ` ${pc.gray(details.join(', '))}` : ''
if (direction === 'from-playwright') { if (direction === 'from-playwright') {
const clientLabel = clientId ? chalk.blue(`[${clientId}]`) : '' const clientLabel = clientId ? pc.blue(`[${clientId}]`) : ''
logger?.log(chalk.cyan('← Playwright'), clientLabel + ':', method + detailsStr) logger?.log(pc.cyan('← Playwright'), clientLabel + ':', method + detailsStr)
} else if (direction === 'from-extension') { } else if (direction === 'from-extension') {
logger?.log(chalk.yellow('← Extension:'), method + detailsStr) logger?.log(pc.yellow('← Extension:'), method + detailsStr)
} else if (direction === 'to-playwright') { } else if (direction === 'to-playwright') {
const color = source === 'server' ? chalk.magenta : chalk.green const color = source === 'server' ? pc.magenta : pc.green
const sourceLabel = source === 'server' ? chalk.gray(' (server-generated)') : '' const sourceLabel = source === 'server' ? pc.gray(' (server-generated)') : ''
const clientLabel = clientId ? chalk.blue(`[${clientId}]`) : chalk.blue('[ALL]') const clientLabel = clientId ? pc.blue(`[${clientId}]`) : pc.blue('[ALL]')
logger?.log(color('→ Playwright'), clientLabel + ':', method + detailsStr + sourceLabel) logger?.log(color('→ Playwright'), clientLabel + ':', method + detailsStr + sourceLabel)
} }
} }
@@ -246,7 +246,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
try { try {
logger?.log(chalk.blue('Auto-creating initial tab for Playwright client')) logger?.log(pc.blue('Auto-creating initial tab for Playwright client'))
const result = await sendToExtension({ method: 'createInitialTab', timeout: 10000 }) as { const result = await sendToExtension({ method: 'createInitialTab', timeout: 10000 }) as {
success: boolean success: boolean
tabId: number tabId: number
@@ -259,7 +259,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
targetId: result.targetInfo.targetId, targetId: result.targetInfo.targetId,
targetInfo: result.targetInfo targetInfo: result.targetInfo
}) })
logger?.log(chalk.blue(`Auto-created tab, now have ${connectedTargets.size} targets, url: ${result.targetInfo.url}`)) logger?.log(pc.blue(`Auto-created tab, now have ${connectedTargets.size} targets, url: ${result.targetInfo.url}`))
} }
} catch (e) { } catch (e) {
logger?.error('Failed to auto-create initial tab:', e) logger?.error('Failed to auto-create initial tab:', e)
@@ -377,7 +377,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
emitter.off('cdp:event', handler) emitter.off('cdp:event', handler)
logger?.log(chalk.yellow(`IMPORTANT: Runtime.enable timed out waiting for main frame executionContextCreated (sessionId: ${sessionId}). This may cause pages to not be visible immediately.`)) logger?.log(pc.yellow(`IMPORTANT: Runtime.enable timed out waiting for main frame executionContextCreated (sessionId: ${sessionId}). This may cause pages to not be visible immediately.`))
resolve() resolve()
}, 3000) }, 3000)
emitter.on('cdp:event', handler) emitter.on('cdp:event', handler)
@@ -500,7 +500,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
try { try {
const { level, args } = await c.req.json() const { level, args } = await c.req.json()
const logFn = (logger as any)?.[level] || logger?.log const logFn = (logger as any)?.[level] || logger?.log
const prefix = chalk.red(`[MCP] [${level.toUpperCase()}]`) const prefix = pc.red(`[MCP] [${level.toUpperCase()}]`)
logFn?.(prefix, ...args) logFn?.(prefix, ...args)
return c.json({ ok: true }) return c.json({ ok: true })
} catch { } catch {
@@ -520,11 +520,11 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
if (origin.startsWith('chrome-extension://')) { if (origin.startsWith('chrome-extension://')) {
const extensionId = origin.replace('chrome-extension://', '') const extensionId = origin.replace('chrome-extension://', '')
if (!OUR_EXTENSION_IDS.includes(extensionId)) { if (!OUR_EXTENSION_IDS.includes(extensionId)) {
logger?.log(chalk.red(`Rejecting /cdp WebSocket from unknown extension: ${extensionId}`)) logger?.log(pc.red(`Rejecting /cdp WebSocket from unknown extension: ${extensionId}`))
return c.text('Forbidden', 403) return c.text('Forbidden', 403)
} }
} else { } else {
logger?.log(chalk.red(`Rejecting /cdp WebSocket from origin: ${origin}`)) logger?.log(pc.red(`Rejecting /cdp WebSocket from origin: ${origin}`))
return c.text('Forbidden', 403) return c.text('Forbidden', 403)
} }
} }
@@ -543,14 +543,14 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
return { return {
async onOpen(_event, ws) { async onOpen(_event, ws) {
if (playwrightClients.has(clientId)) { if (playwrightClients.has(clientId)) {
logger?.log(chalk.red(`Rejecting duplicate client ID: ${clientId}`)) logger?.log(pc.red(`Rejecting duplicate client ID: ${clientId}`))
ws.close(1000, 'Client ID already connected') ws.close(1000, 'Client ID already connected')
return return
} }
// Add client first so it can receive Target.attachedToTarget events // Add client first so it can receive Target.attachedToTarget events
playwrightClients.set(clientId, { id: clientId, ws }) playwrightClients.set(clientId, { id: clientId, ws })
logger?.log(chalk.green(`Playwright client connected: ${clientId} (${playwrightClients.size} total) (extension? ${!!extensionWs}) (${connectedTargets.size} pages)`)) logger?.log(pc.green(`Playwright client connected: ${clientId} (${playwrightClients.size} total) (extension? ${!!extensionWs}) (${connectedTargets.size} pages)`))
}, },
async onMessage(event, ws) { async onMessage(event, ws) {
@@ -607,9 +607,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
} satisfies CDPEventFor<'Target.attachedToTarget'> } satisfies CDPEventFor<'Target.attachedToTarget'>
if (!target.targetInfo.url) { if (!target.targetInfo.url) {
logger?.error(chalk.red('[Server] WARNING: Target.attachedToTarget sent with empty URL!'), JSON.stringify(attachedPayload)) logger?.error(pc.red('[Server] WARNING: Target.attachedToTarget sent with empty URL!'), JSON.stringify(attachedPayload))
} }
logger?.log(chalk.magenta('[Server] Target.attachedToTarget full payload:'), JSON.stringify(attachedPayload)) logger?.log(pc.magenta('[Server] Target.attachedToTarget full payload:'), JSON.stringify(attachedPayload))
sendToPlaywright({ sendToPlaywright({
message: attachedPayload, message: attachedPayload,
clientId, clientId,
@@ -634,9 +634,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
} satisfies CDPEventFor<'Target.targetCreated'> } satisfies CDPEventFor<'Target.targetCreated'>
if (!target.targetInfo.url) { if (!target.targetInfo.url) {
logger?.error(chalk.red('[Server] WARNING: Target.targetCreated sent with empty URL!'), JSON.stringify(targetCreatedPayload)) logger?.error(pc.red('[Server] WARNING: Target.targetCreated sent with empty URL!'), JSON.stringify(targetCreatedPayload))
} }
logger?.log(chalk.magenta('[Server] Target.targetCreated full payload:'), JSON.stringify(targetCreatedPayload)) logger?.log(pc.magenta('[Server] Target.targetCreated full payload:'), JSON.stringify(targetCreatedPayload))
sendToPlaywright({ sendToPlaywright({
message: targetCreatedPayload, message: targetCreatedPayload,
clientId, clientId,
@@ -661,9 +661,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} }
} satisfies CDPEventFor<'Target.attachedToTarget'> } satisfies CDPEventFor<'Target.attachedToTarget'>
if (!target.targetInfo.url) { if (!target.targetInfo.url) {
logger?.error(chalk.red('[Server] WARNING: Target.attachedToTarget (from attachToTarget) sent with empty URL!'), JSON.stringify(attachedPayload)) logger?.error(pc.red('[Server] WARNING: Target.attachedToTarget (from attachToTarget) sent with empty URL!'), JSON.stringify(attachedPayload))
} }
logger?.log(chalk.magenta('[Server] Target.attachedToTarget (from attachToTarget) payload:'), JSON.stringify(attachedPayload)) logger?.log(pc.magenta('[Server] Target.attachedToTarget (from attachToTarget) payload:'), JSON.stringify(attachedPayload))
sendToPlaywright({ sendToPlaywright({
message: attachedPayload, message: attachedPayload,
clientId, clientId,
@@ -689,7 +689,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
onClose() { onClose() {
playwrightClients.delete(clientId) playwrightClients.delete(clientId)
logger?.log(chalk.yellow(`Playwright client disconnected: ${clientId} (${playwrightClients.size} remaining)`)) logger?.log(pc.yellow(`Playwright client disconnected: ${clientId} (${playwrightClients.size} remaining)`))
}, },
onError(event) { onError(event) {
@@ -707,7 +707,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
const isLocalhost = remoteAddress === '127.0.0.1' || remoteAddress === '::1' const isLocalhost = remoteAddress === '127.0.0.1' || remoteAddress === '::1'
if (!isLocalhost) { if (!isLocalhost) {
logger?.log(chalk.red(`Rejecting /extension WebSocket from remote IP: ${remoteAddress}`)) logger?.log(pc.red(`Rejecting /extension WebSocket from remote IP: ${remoteAddress}`))
return c.text('Forbidden - Extension must be local', 403) return c.text('Forbidden - Extension must be local', 403)
} }
@@ -716,13 +716,13 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
// is coming from our specific Chrome Extension, not a malicious website. // is coming from our specific Chrome Extension, not a malicious website.
const origin = c.req.header('origin') const origin = c.req.header('origin')
if (!origin || !origin.startsWith('chrome-extension://')) { if (!origin || !origin.startsWith('chrome-extension://')) {
logger?.log(chalk.red(`Rejecting /extension WebSocket: origin must be chrome-extension://, got: ${origin || 'none'}`)) logger?.log(pc.red(`Rejecting /extension WebSocket: origin must be chrome-extension://, got: ${origin || 'none'}`))
return c.text('Forbidden', 403) return c.text('Forbidden', 403)
} }
const extensionId = origin.replace('chrome-extension://', '') const extensionId = origin.replace('chrome-extension://', '')
if (!OUR_EXTENSION_IDS.includes(extensionId)) { if (!OUR_EXTENSION_IDS.includes(extensionId)) {
logger?.log(chalk.red(`Rejecting /extension WebSocket from unknown extension: ${extensionId}`)) logger?.log(pc.red(`Rejecting /extension WebSocket from unknown extension: ${extensionId}`))
return c.text('Forbidden', 403) return c.text('Forbidden', 403)
} }
@@ -731,7 +731,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
return { return {
onOpen(_event, ws) { onOpen(_event, ws) {
if (extensionWs) { if (extensionWs) {
logger?.log(chalk.yellow('Closing existing extension connection to replace with new one')) logger?.log(pc.yellow('Closing existing extension connection to replace with new one'))
extensionWs.close(4001, 'Extension Replaced') extensionWs.close(4001, 'Extension Replaced')
// Clear state from the old connection to prevent leaks // Clear state from the old connection to prevent leaks
@@ -781,7 +781,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
} else if (message.method === 'log') { } else if (message.method === 'log') {
const { level, args } = message.params const { level, args } = message.params
const logFn = (logger as any)?.[level] || logger?.log const logFn = (logger as any)?.[level] || logger?.log
const prefix = chalk.yellow(`[Extension] [${level.toUpperCase()}]`) const prefix = pc.yellow(`[Extension] [${level.toUpperCase()}]`)
logFn?.(prefix, ...args) logFn?.(prefix, ...args)
} else { } else {
const extensionEvent = message as ExtensionEventMessage const extensionEvent = message as ExtensionEventMessage
@@ -809,14 +809,14 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
// These targets can't be properly controlled through chrome.debugger API // These targets can't be properly controlled through chrome.debugger API
// and cause issues when Playwright tries to initialize them (issue #14) // and cause issues when Playwright tries to initialize them (issue #14)
if (isRestrictedTarget(targetParams.targetInfo)) { if (isRestrictedTarget(targetParams.targetInfo)) {
logger?.log(chalk.gray(`[Server] Ignoring restricted target: ${targetParams.targetInfo.type} (${targetParams.targetInfo.url})`)) logger?.log(pc.gray(`[Server] Ignoring restricted target: ${targetParams.targetInfo.type} (${targetParams.targetInfo.url})`))
return return
} }
if (!targetParams.targetInfo.url) { if (!targetParams.targetInfo.url) {
logger?.error(chalk.red('[Extension] WARNING: Target.attachedToTarget received with empty URL!'), JSON.stringify({ method, params: targetParams, sessionId })) logger?.error(pc.red('[Extension] WARNING: Target.attachedToTarget received with empty URL!'), JSON.stringify({ method, params: targetParams, sessionId }))
} }
logger?.log(chalk.yellow('[Extension] Target.attachedToTarget full payload:'), JSON.stringify({ method, params: targetParams, sessionId })) logger?.log(pc.yellow('[Extension] Target.attachedToTarget full payload:'), JSON.stringify({ method, params: targetParams, sessionId }))
// Check if we already sent this target to clients (e.g., from Target.setAutoAttach response) // Check if we already sent this target to clients (e.g., from Target.setAutoAttach response)
const alreadyConnected = connectedTargets.has(targetParams.sessionId) const alreadyConnected = connectedTargets.has(targetParams.sessionId)
@@ -854,7 +854,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
for (const [sid, target] of connectedTargets.entries()) { for (const [sid, target] of connectedTargets.entries()) {
if (target.targetId === crashParams.targetId) { if (target.targetId === crashParams.targetId) {
connectedTargets.delete(sid) connectedTargets.delete(sid)
logger?.log(chalk.red('[Server] Target crashed, removing:'), crashParams.targetId) logger?.log(pc.red('[Server] Target crashed, removing:'), crashParams.targetId)
break break
} }
} }
@@ -892,7 +892,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
url: frameParams.frame.url, url: frameParams.frame.url,
title: frameParams.frame.name || target.targetInfo.title, title: frameParams.frame.name || target.targetInfo.title,
} }
logger?.log(chalk.magenta('[Server] Updated target URL from Page.frameNavigated:'), frameParams.frame.url) logger?.log(pc.magenta('[Server] Updated target URL from Page.frameNavigated:'), frameParams.frame.url)
} }
} }
@@ -913,7 +913,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
...target.targetInfo, ...target.targetInfo,
url: navParams.url, url: navParams.url,
} }
logger?.log(chalk.magenta('[Server] Updated target URL from Page.navigatedWithinDocument:'), navParams.url) logger?.log(pc.magenta('[Server] Updated target URL from Page.navigatedWithinDocument:'), navParams.url)
} }
} }
+269 -253
View File
File diff suppressed because it is too large Load Diff