UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

195 lines 7.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CdpBreakpointOperations = void 0; const debug_session_1 = require("./debug-session"); /** * Handles CDP breakpoint operations * Maps file paths to script IDs and manages breakpoint lifecycle via CDP */ class CdpBreakpointOperations { constructor(inspector) { this.inspector = inspector; this.scripts = new Map(); // Listen for script parsed events to build script ID mapping this.inspector.on('Debugger.scriptParsed', (params) => { if (params.url && params.scriptId) { this.scripts.set(params.url, { scriptId: params.scriptId, url: params.url, }); } }); } /** * Set a breakpoint via CDP * @param breakpoint Breakpoint to set * @returns CDP breakpoint ID if successful */ async setBreakpoint(breakpoint) { // Handle logpoints specially if (breakpoint.type === debug_session_1.BreakpointType.LOGPOINT) { return this.setLogpoint(breakpoint); } // Handle function breakpoints specially if (breakpoint.type === debug_session_1.BreakpointType.FUNCTION) { return this.setFunctionBreakpoint(breakpoint); } try { // Try to set breakpoint by URL (works for most cases) const result = await this.inspector.send('Debugger.setBreakpointByUrl', { lineNumber: breakpoint.line - 1, // CDP uses 0-indexed lines url: `file://${breakpoint.file}`, columnNumber: 0, condition: breakpoint.condition || undefined, }); return result.breakpointId; } catch (error) { // If setting by URL fails, try to find the script ID const scriptInfo = this.findScriptByFile(breakpoint.file); if (scriptInfo) { try { const result = await this.inspector.send('Debugger.setBreakpoint', { location: { scriptId: scriptInfo.scriptId, lineNumber: breakpoint.line - 1, columnNumber: 0, }, condition: breakpoint.condition || undefined, }); return result.breakpointId; } catch (innerError) { console.error(`Failed to set breakpoint at ${breakpoint.file}:${breakpoint.line}`, innerError); return undefined; } } console.error(`Failed to set breakpoint at ${breakpoint.file}:${breakpoint.line}`, error); return undefined; } } /** * Set a logpoint via CDP * Logpoints are implemented as conditional breakpoints that log and return false * @param breakpoint Logpoint to set * @returns CDP breakpoint ID if successful */ async setLogpoint(breakpoint) { if (!breakpoint.logMessage) { console.error('Logpoint requires a log message'); return undefined; } // Convert log message template to a CDP condition // The condition evaluates the log message and returns false to not pause const logCondition = this.createLogpointCondition(breakpoint.logMessage); try { const result = await this.inspector.send('Debugger.setBreakpointByUrl', { lineNumber: breakpoint.line - 1, url: `file://${breakpoint.file}`, columnNumber: 0, condition: logCondition, }); return result.breakpointId; } catch (error) { console.error(`Failed to set logpoint at ${breakpoint.file}:${breakpoint.line}`, error); return undefined; } } /** * Create a CDP condition for a logpoint * The condition logs the message and returns false to not pause execution * @param logMessage Log message template with {variable} interpolation * @returns CDP condition string */ createLogpointCondition(logMessage) { // Replace {variable} with variable evaluation // Example: "Value is {x}" becomes "console.log('Value is', x), false" // Extract variables from the message (anything in curly braces) const variables = []; const messageWithPlaceholders = logMessage.replace(/\{([^}]+)\}/g, (_, varName) => { variables.push(varName.trim()); return '%s'; }); // Build the console.log call if (variables.length === 0) { // No variables, just log the message return `console.log(${JSON.stringify(logMessage)}), false`; } else { // Build console.log with message and variables const varList = variables.join(', '); return `console.log(${JSON.stringify(messageWithPlaceholders)}, ${varList}), false`; } } /** * Set a function breakpoint via CDP * Function breakpoints pause when a function with the given name is called * @param breakpoint Function breakpoint to set * @returns CDP breakpoint ID if successful */ async setFunctionBreakpoint(breakpoint) { if (!breakpoint.functionName) { console.error('Function breakpoint requires a function name'); return undefined; } // Function breakpoints in CDP are implemented using instrumentation breakpoints // We need to find all functions matching the name and set breakpoints on them // For now, we'll return undefined as this requires more complex implementation // involving script parsing and function location detection console.warn('Function breakpoints are not yet fully implemented in CDP operations'); return undefined; } /** * Remove a breakpoint via CDP * @param cdpBreakpointId CDP breakpoint ID * @returns True if successful */ async removeBreakpoint(cdpBreakpointId) { try { await this.inspector.send('Debugger.removeBreakpoint', { breakpointId: cdpBreakpointId, }); return true; } catch (error) { console.error(`Failed to remove breakpoint ${cdpBreakpointId}`, error); return false; } } /** * Find a script by file path * Handles both exact matches and partial matches (e.g., relative vs absolute paths) */ findScriptByFile(filePath) { // Try exact match first const exactMatch = this.scripts.get(`file://${filePath}`); if (exactMatch) { return exactMatch; } // Try to find by filename (handles relative paths) const fileName = filePath.split('/').pop(); if (fileName) { for (const [url, scriptInfo] of this.scripts.entries()) { if (url.endsWith(fileName) || url.includes(filePath)) { return scriptInfo; } } } return undefined; } /** * Get all known scripts */ getScripts() { return Array.from(this.scripts.values()); } /** * Clear the script cache */ clearScripts() { this.scripts.clear(); } } exports.CdpBreakpointOperations = CdpBreakpointOperations; //# sourceMappingURL=cdp-breakpoint-operations.js.map