UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

634 lines (540 loc) • 20.6 kB
import { Page } from 'playwright'; export interface MetaFrameworkInfo { framework: 'remix' | 'astro' | 'nuxt' | 'qwik' | 'solidjs' | 'sveltekit' | null; version?: string; buildTool?: 'vite' | 'webpack' | 'esbuild' | 'rollup'; features: string[]; } export interface ViteHMREvent { type: 'update' | 'full-reload' | 'error' | 'custom'; timestamp: Date; files?: string[]; error?: string; } export interface RemixLoaderData { route: string; loader: string; duration: number; size: number; cached: boolean; } export interface AstroIsland { component: string; props: any; hydrationDirective?: 'load' | 'idle' | 'visible' | 'media' | 'only'; loading: 'eager' | 'lazy'; } export interface QwikResumabilityInfo { serializedState: number; qrlCount: number; symbolsLoaded: number; resumeTime: number; } export class MetaFrameworkDebugEngine { private page?: Page; private viteHMREvents: ViteHMREvent[] = []; private remixLoaderData: RemixLoaderData[] = []; private astroIslands: AstroIsland[] = []; private frameworkInfo?: MetaFrameworkInfo; async attachToPage(page: Page): Promise<void> { this.page = page; // Detect framework first this.frameworkInfo = await this.detectMetaFramework(page); // Inject framework-specific monitoring if (this.frameworkInfo) { await this.injectFrameworkMonitoring(this.frameworkInfo.framework!); } // Setup Vite HMR monitoring if using Vite if (this.frameworkInfo?.buildTool === 'vite') { await this.setupViteHMRMonitoring(); } } async detectMetaFramework(page: Page): Promise<MetaFrameworkInfo> { return await page.evaluate(() => { const info: MetaFrameworkInfo = { framework: null, features: [] }; // Remix detection if ((window as any).__remixContext || (window as any).__remixManifest) { info.framework = 'remix'; info.features.push('loader', 'action', 'error-boundaries'); if ((window as any).__remixContext?.future) { info.features.push(...Object.keys((window as any).__remixContext.future)); } } // Astro detection else if (document.querySelector('astro-island') || (window as any).__ASTRO__) { info.framework = 'astro'; info.features.push('islands', 'partial-hydration'); if ((window as any).__ASTRO_DEV_TOOLBAR__) { info.features.push('dev-toolbar'); } } // Nuxt 3 detection else if ((window as any).__NUXT__ || (window as any).$nuxt) { info.framework = 'nuxt'; info.version = (window as any).__NUXT__?.version || '3.x'; info.features.push('auto-imports', 'file-routing', 'nitro-server'); if ((window as any).__NUXT__?.isHydrating === false) { info.features.push('hydrated'); } } // Qwik detection else if ((window as any).qwikloader || document.querySelector('[q\\:base]')) { info.framework = 'qwik'; info.features.push('resumability', 'lazy-loading', 'fine-grained-reactivity'); } // SolidJS detection else if ((window as any)._$HY || document.querySelector('[data-hk]')) { info.framework = 'solidjs'; info.features.push('fine-grained-reactivity', 'no-virtual-dom'); } // SvelteKit detection else if ((window as any).__sveltekit__ || document.querySelector('[data-sveltekit]')) { info.framework = 'sveltekit'; info.features.push('file-routing', 'load-functions', 'form-actions'); } // Detect build tool if ((window as any).__vite_plugin_react_preamble_installed__ || document.querySelector('script[type="module"][src*="/@vite"]')) { info.buildTool = 'vite'; } else if ((window as any).webpackChunkName) { info.buildTool = 'webpack'; } return info; }); } private async injectFrameworkMonitoring(framework: string): Promise<void> { if (!this.page) return; switch (framework) { case 'remix': await this.injectRemixMonitoring(); break; case 'astro': await this.injectAstroMonitoring(); break; case 'nuxt': await this.injectNuxtMonitoring(); break; case 'qwik': await this.injectQwikMonitoring(); break; case 'solidjs': await this.injectSolidJSMonitoring(); break; case 'sveltekit': await this.injectSvelteKitMonitoring(); break; } } private async injectRemixMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__REMIX_DEBUG__ = { loaderData: [], actionData: [], transitions: [], errors: [], init: function() { // Monitor loader execution if ((window as any).__remixContext) { const originalFetch = window.fetch; window.fetch = async function(...args) { const startTime = Date.now(); const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url; // Track data requests if (url.includes('_data=')) { const response = await originalFetch.apply(this, args); const duration = Date.now() - startTime; (window as any).__REMIX_DEBUG__.loaderData.push({ url, duration, status: response.status, timestamp: new Date().toISOString() }); return response; } return originalFetch.apply(this, args); }; } // Monitor navigation if ((window as any).RemixBrowser) { console.log('Remix navigation monitoring enabled'); } } }; (window as any).__REMIX_DEBUG__.init(); }); } private async injectAstroMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__ASTRO_DEBUG__ = { islands: [], hydrationEvents: [], init: function() { // Monitor Astro islands const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node instanceof Element && node.tagName === 'ASTRO-ISLAND') { const island = { uid: node.getAttribute('uid'), component: node.getAttribute('component-url'), props: node.getAttribute('props'), hydrate: node.getAttribute('client:load') !== null ? 'load' : node.getAttribute('client:idle') !== null ? 'idle' : node.getAttribute('client:visible') !== null ? 'visible' : node.getAttribute('client:media') !== null ? 'media' : 'none', timestamp: new Date().toISOString() }; this.islands.push(island); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); // Monitor hydration const originalLog = console.log; console.log = function(...args) { const message = args.join(' '); if (message.includes('[astro]') || message.includes('hydrat')) { (window as any).__ASTRO_DEBUG__.hydrationEvents.push({ message, timestamp: new Date().toISOString() }); } return originalLog.apply(console, args); }; } }; (window as any).__ASTRO_DEBUG__.init(); }); } private async injectNuxtMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__NUXT_DEBUG__ = { payloads: [], errors: [], composables: new Set(), init: function() { const nuxt = (window as any).__NUXT__ || (window as any).$nuxt; if (!nuxt) return; // Monitor payloads if (nuxt.payload) { this.payloads.push({ data: Object.keys(nuxt.payload.data || {}), state: Object.keys(nuxt.payload.state || {}), timestamp: new Date().toISOString() }); } // Monitor composable usage const composableNames = ['useState', 'useFetch', 'useAsyncData', 'useLazyFetch']; composableNames.forEach(name => { if ((window as any)[name]) { this.composables.add(name); } }); // Monitor errors if (nuxt.error) { this.errors.push({ error: nuxt.error, timestamp: new Date().toISOString() }); } } }; // Initialize after Nuxt is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => (window as any).__NUXT_DEBUG__.init(), 100); }); } else { setTimeout(() => (window as any).__NUXT_DEBUG__.init(), 100); } }); } private async injectQwikMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__QWIK_DEBUG__ = { resumabilityInfo: null, qrlLoads: [], init: function() { // Monitor QRL loading const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name.includes('.js') && entry.name.includes('q-')) { this.qrlLoads.push({ url: entry.name, duration: entry.duration, size: (entry as any).transferSize || 0, timestamp: new Date().toISOString() }); } } }); observer.observe({ entryTypes: ['resource'] }); // Check resumability state const checkResumability = () => { const container = document.querySelector('[q\\:base]'); if (container) { const state = container.getAttribute('q:state'); this.resumabilityInfo = { hasState: !!state, stateSize: state ? state.length : 0, qrlCount: document.querySelectorAll('[on\\:click]').length, timestamp: new Date().toISOString() }; } }; setTimeout(checkResumability, 1000); } }; (window as any).__QWIK_DEBUG__.init(); }); } private async injectSolidJSMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__SOLID_DEBUG__ = { reactivityUpdates: [], componentCount: 0, init: function() { // Count hydrated components this.componentCount = document.querySelectorAll('[data-hk]').length; // Monitor reactivity if dev mode if ((window as any)._$HY) { console.log('SolidJS reactivity monitoring enabled'); } } }; (window as any).__SOLID_DEBUG__.init(); }); } private async injectSvelteKitMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__SVELTEKIT_DEBUG__ = { navigations: [], forms: [], init: function() { // Monitor client-side navigation if ((window as any).__sveltekit__) { console.log('SvelteKit navigation monitoring enabled'); } } }; (window as any).__SVELTEKIT_DEBUG__.init(); }); } private async setupViteHMRMonitoring(): Promise<void> { if (!this.page) return; await this.page.addInitScript(() => { (window as any).__VITE_HMR_DEBUG__ = { events: [], init: function() { // Listen for Vite HMR events if ((window as any).__vite_plugin_react_preamble_installed__) { console.log('Vite HMR monitoring enabled'); // Monitor import.meta.hot if ((import.meta as any).hot) { (import.meta as any).hot.on('vite:beforeUpdate', (payload: any) => { this.events.push({ type: 'update', payload, timestamp: new Date().toISOString() }); }); (import.meta as any).hot.on('vite:error', (payload: any) => { this.events.push({ type: 'error', payload, timestamp: new Date().toISOString() }); }); } } } }; // Vite may not be ready immediately setTimeout(() => (window as any).__VITE_HMR_DEBUG__.init(), 100); }); } // Remix specific methods async getRemixLoaderData(): Promise<RemixLoaderData[]> { if (!this.page || this.frameworkInfo?.framework !== 'remix') return []; const data = await this.page.evaluate(() => { return (window as any).__REMIX_DEBUG__?.loaderData || []; }); return data.map((d: any) => ({ ...d, timestamp: new Date(d.timestamp) })); } async getRemixRouteModules(): Promise<any> { if (!this.page || this.frameworkInfo?.framework !== 'remix') return null; return await this.page.evaluate(() => { const context = (window as any).__remixContext; if (!context) return null; return { routes: Object.keys(context.routeModules || {}), currentRoute: context.matches?.[context.matches.length - 1]?.route.id, manifest: context.manifest ? { version: context.manifest.version, routes: Object.keys(context.manifest.routes || {}) } : null }; }); } // Astro specific methods async getAstroIslands(): Promise<AstroIsland[]> { if (!this.page || this.frameworkInfo?.framework !== 'astro') return []; const islands = await this.page.evaluate(() => { const islandElements = document.querySelectorAll('astro-island'); return Array.from(islandElements).map(island => ({ uid: island.getAttribute('uid'), component: island.getAttribute('component-url') || '', props: JSON.parse(island.getAttribute('props') || '{}'), hydrationDirective: ( island.hasAttribute('client:load') ? 'load' : island.hasAttribute('client:idle') ? 'idle' : island.hasAttribute('client:visible') ? 'visible' : island.hasAttribute('client:media') ? 'media' : island.hasAttribute('client:only') ? 'only' : undefined ) as 'load' | 'idle' | 'visible' | 'media' | 'only' | undefined, loading: island.hasAttribute('client:load') ? 'eager' : 'lazy' as 'eager' | 'lazy' })); }); return islands; } // Nuxt specific methods async getNuxtPayload(): Promise<any> { if (!this.page || this.frameworkInfo?.framework !== 'nuxt') return null; return await this.page.evaluate(() => { const nuxt = (window as any).__NUXT__ || (window as any).$nuxt; if (!nuxt) return null; return { isHydrating: nuxt.isHydrating, data: nuxt.payload?.data ? Object.keys(nuxt.payload.data) : [], state: nuxt.payload?.state ? Object.keys(nuxt.payload.state) : [], error: nuxt.error, serverRendered: nuxt.serverRendered !== false }; }); } // Qwik specific methods async getQwikResumability(): Promise<QwikResumabilityInfo | null> { if (!this.page || this.frameworkInfo?.framework !== 'qwik') return null; return await this.page.evaluate(() => { const container = document.querySelector('[q\\:base]'); if (!container) return null; const state = container.getAttribute('q:state'); const info = (window as any).__QWIK_DEBUG__?.resumabilityInfo; return { serializedState: state ? state.length : 0, qrlCount: document.querySelectorAll('[on\\:click]').length, symbolsLoaded: (window as any).__QWIK_DEBUG__?.qrlLoads?.length || 0, resumeTime: 0 // Would need performance timing }; }); } // Vite HMR methods async getViteHMREvents(): Promise<ViteHMREvent[]> { if (!this.page || this.frameworkInfo?.buildTool !== 'vite') return []; const events = await this.page.evaluate(() => { return (window as any).__VITE_HMR_DEBUG__?.events || []; }); return events.map((e: any) => ({ ...e, timestamp: new Date(e.timestamp) })); } // Generic problem detection async detectMetaFrameworkProblems(): Promise<Array<{ problem: string; severity: 'low' | 'medium' | 'high'; description: string; solution: string; }>> { const problems: Array<{ problem: string; severity: 'low' | 'medium' | 'high'; description: string; solution: string; }> = []; if (!this.frameworkInfo?.framework) { return problems; } switch (this.frameworkInfo.framework) { case 'remix': const loaderData = await this.getRemixLoaderData(); const slowLoaders = loaderData.filter(l => l.duration > 1000); if (slowLoaders.length > 0) { problems.push({ problem: 'Slow Remix Loaders', severity: 'medium' as const, description: `${slowLoaders.length} loader functions take over 1 second`, solution: 'Optimize database queries, add caching, or use defer() for non-critical data' }); } break; case 'astro': const islands = await this.getAstroIslands(); const eagerIslands = islands.filter(i => i.hydrationDirective === 'load'); if (eagerIslands.length > 5) { problems.push({ problem: 'Too Many Eager Islands', severity: 'medium' as const, description: `${eagerIslands.length} components hydrate immediately on page load`, solution: 'Use client:idle or client:visible for non-critical components' }); } break; case 'nuxt': const payload = await this.getNuxtPayload(); if (payload?.data?.length > 20) { problems.push({ problem: 'Large Nuxt Payload', severity: 'medium' as const, description: `Payload contains ${payload.data.length} data keys`, solution: 'Consider lazy-loading data or paginating large datasets' }); } break; case 'qwik': const resumability = await this.getQwikResumability(); if (resumability && resumability.serializedState > 100000) { problems.push({ problem: 'Large Qwik Serialized State', severity: 'high' as const, description: `Serialized state is ${(resumability.serializedState / 1024).toFixed(2)}KB`, solution: 'Reduce component state or move data to server-side stores' }); } break; } // Vite HMR issues if (this.frameworkInfo.buildTool === 'vite') { const hmrEvents = await this.getViteHMREvents(); const errors = hmrEvents.filter(e => e.type === 'error'); if (errors.length > 0) { problems.push({ problem: 'Vite HMR Errors', severity: 'high' as const, description: `${errors.length} HMR errors detected during development`, solution: 'Check console for specific errors and ensure proper HMR boundaries' }); } } return problems; } getFrameworkInfo(): MetaFrameworkInfo | undefined { return this.frameworkInfo; } }