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
410 lines • 18.8 kB
JavaScript
export class JavaScriptErrorDetectionEngine {
page = null;
errors = {
scriptErrors: [],
moduleConflicts: [],
htmlErrors: [],
consoleErrors: [],
networkErrors: [],
suggestions: []
};
isMonitoring = false;
attach(page) {
this.page = page;
}
async startMonitoring() {
if (!this.page) {
throw new Error('No page attached');
}
this.isMonitoring = true;
this.errors = {
scriptErrors: [],
moduleConflicts: [],
htmlErrors: [],
consoleErrors: [],
networkErrors: [],
suggestions: []
};
// Monitor console errors
this.page.on('console', (msg) => {
if (msg.type() === 'error') {
const error = {
type: msg.type(),
text: msg.text(),
stack: msg.location()?.url ? `at ${msg.location()?.url}:${msg.location()?.lineNumber}:${msg.location()?.columnNumber}` : undefined,
timestamp: Date.now()
};
this.errors.consoleErrors.push(error);
this.analyzeConsoleError(error);
}
});
// Monitor page errors (uncaught exceptions)
this.page.on('pageerror', (error) => {
const scriptError = {
type: 'runtime',
severity: 'error',
message: error.message,
stackTrace: error.stack,
timestamp: Date.now()
};
// Extract file and line info from stack
const stackMatch = error.stack?.match(/at\s+.*?\s+\(?(.*?):(\d+):(\d+)\)?/);
if (stackMatch) {
scriptError.file = stackMatch[1];
scriptError.line = parseInt(stackMatch[2]);
scriptError.column = parseInt(stackMatch[3]);
}
this.errors.scriptErrors.push(scriptError);
this.analyzeScriptError(scriptError);
});
// Monitor network failures
this.page.on('requestfailed', (request) => {
const error = {
url: request.url(),
status: 0,
method: request.method(),
timestamp: Date.now()
};
this.errors.networkErrors.push(error);
// Check if it's a script loading error
if (request.url().endsWith('.js') || request.url().endsWith('.mjs')) {
this.errors.scriptErrors.push({
type: 'network',
severity: 'error',
message: `Failed to load script: ${request.url()}`,
timestamp: Date.now(),
suggestion: 'Check if the script URL is correct and the server is responding'
});
}
});
// Inject error detection code
await this.injectErrorDetection();
}
async stopMonitoring() {
this.isMonitoring = false;
// Remove listeners
if (this.page) {
this.page.removeAllListeners('console');
this.page.removeAllListeners('pageerror');
this.page.removeAllListeners('requestfailed');
}
}
async injectErrorDetection() {
if (!this.page)
return;
await this.page.addInitScript(() => {
// Create error detection namespace
window.__errorDetection = {
scriptErrors: [],
moduleConflicts: [],
htmlErrors: [],
// Override error handling
captureError: function (error, context) {
const errorInfo = {
type: 'runtime',
message: error.message,
stack: error.stack,
timestamp: Date.now(),
context
};
window.__errorDetection.scriptErrors.push(errorInfo);
// Send to parent
window.postMessage({
type: '__errorDetection',
error: errorInfo
}, '*');
},
// Check for module conflicts
checkModuleSystem: function () {
// Detect if ES modules are being used in a CommonJS context
const scripts = document.querySelectorAll('script');
scripts.forEach(script => {
if (script.type === 'module' && typeof module !== 'undefined') {
const conflict = {
file: script.src || 'inline script',
expectedType: 'module',
actualType: 'commonjs',
suggestion: 'Parent package.json may have "type": "module" causing conflicts with CommonJS libraries'
};
window.__errorDetection.moduleConflicts.push(conflict);
}
});
},
// Validate HTML for common errors
validateHTML: function () {
// Check for self-closing script tags
const scriptTags = document.getElementsByTagName('script');
Array.from(scriptTags).forEach(script => {
// Check if script tag has self-closing syntax in HTML
if (script.outerHTML.includes('/>')) {
window.__errorDetection.htmlErrors.push({
type: 'malformed_tag',
element: 'script',
suggestion: 'Script tags should not be self-closing. Use <script></script> instead of <script />',
code: script.outerHTML
});
}
});
// Check for other malformed tags
const allElements = document.querySelectorAll('*');
allElements.forEach(element => {
// Check for invalid nesting
if (element.tagName === 'P' && element.querySelector('div, p, h1, h2, h3, h4, h5, h6')) {
window.__errorDetection.htmlErrors.push({
type: 'invalid_nesting',
element: element.tagName,
suggestion: `<${element.tagName.toLowerCase()}> cannot contain block-level elements`,
code: element.outerHTML.substring(0, 100) + '...'
});
}
});
}
};
// Override dynamic import to catch module errors
const originalImport = window.import;
if (originalImport) {
window.import = function (specifier) {
return originalImport.call(this, specifier).catch((error) => {
window.__errorDetection.captureError(error, {
type: 'module_import',
specifier
});
throw error;
});
};
}
// Monitor script loading errors
window.addEventListener('error', (event) => {
if (event.target && event.target.tagName === 'SCRIPT') {
const script = event.target;
window.__errorDetection.scriptErrors.push({
type: 'network',
message: `Failed to load script: ${script.src}`,
file: script.src,
timestamp: Date.now()
});
}
}, true);
// Run initial checks
setTimeout(() => {
window.__errorDetection.checkModuleSystem();
window.__errorDetection.validateHTML();
}, 100);
});
// Listen for error messages
await this.page.evaluate(() => {
window.addEventListener('message', (event) => {
if (event.data && event.data.type === '__errorDetection') {
// Store for later retrieval
window.__errorDetectionEvents = window.__errorDetectionEvents || [];
window.__errorDetectionEvents.push(event.data.error);
}
});
});
}
analyzeConsoleError(error) {
const errorText = error.text.toLowerCase();
// Check for module-related errors
if (errorText.includes('cannot use import statement') ||
errorText.includes('unexpected token import') ||
errorText.includes('syntaxerror: import')) {
this.errors.moduleConflicts.push({
file: error.stack?.split(':')[0] || 'unknown',
expectedType: 'commonjs',
actualType: 'module',
suggestion: 'This file is using ES6 imports but is being loaded as CommonJS. Check your package.json "type" field.'
});
this.errors.suggestions.push('Consider adding "type": "commonjs" to package.json or converting imports to require()');
}
// Check for export errors
if (errorText.includes('exports is not defined') ||
errorText.includes('module is not defined')) {
this.errors.moduleConflicts.push({
file: error.stack?.split(':')[0] || 'unknown',
expectedType: 'module',
actualType: 'commonjs',
suggestion: 'CommonJS code is running in an ES module context. Check parent package.json for "type": "module"'
});
this.errors.suggestions.push('Remove "type": "module" from package.json or use .cjs extension for CommonJS files');
}
// Check for malformed HTML errors
if (errorText.includes('unexpected token <') ||
errorText.includes('invalid or unexpected token')) {
this.errors.suggestions.push('Check for malformed HTML tags, especially self-closing script tags');
}
}
analyzeScriptError(error) {
// Add specific suggestions based on error patterns
if (error.message.includes('Cannot read property') ||
error.message.includes('Cannot read properties')) {
error.suggestion = 'Add null/undefined checks before accessing properties';
}
if (error.message.includes('is not a function')) {
error.suggestion = 'Verify the variable is a function before calling it';
}
if (error.message.includes('Maximum call stack')) {
error.suggestion = 'Check for infinite recursion or circular dependencies';
}
}
async detectCommonIssues() {
if (!this.page)
return;
// Check for common HTML issues
const htmlIssues = await this.page.evaluate(() => {
const issues = [];
// Check for self-closing script tags
const scripts = Array.from(document.scripts);
scripts.forEach(script => {
if (script.outerHTML.includes('/>')) {
issues.push({
type: 'malformed_tag',
element: 'script',
file: script.src || 'inline',
suggestion: 'Script tags cannot be self-closing in HTML. Use <script></script>',
code: script.outerHTML
});
}
});
// Check for scripts with wrong type
scripts.forEach(script => {
if (script.type && script.type !== 'module' &&
script.type !== 'text/javascript' &&
script.type !== 'application/javascript') {
issues.push({
type: 'invalid_attribute',
element: 'script',
file: script.src || 'inline',
suggestion: `Invalid script type "${script.type}". Use "module" for ES modules or omit for regular scripts.`,
code: script.outerHTML
});
}
});
return issues;
});
htmlIssues.forEach(issue => {
this.errors.htmlErrors.push(issue);
});
// Check package.json module type conflicts
await this.checkModuleTypeConflicts();
}
async checkModuleTypeConflicts() {
if (!this.page)
return;
const conflicts = await this.page.evaluate(() => {
const conflicts = [];
// Check if we're in a module context but using CommonJS
if (typeof module !== 'undefined' && typeof exports !== 'undefined') {
// We're in CommonJS context
const scripts = document.querySelectorAll('script[type="module"]');
if (scripts.length > 0) {
conflicts.push({
file: 'package.json',
expectedType: 'commonjs',
actualType: 'module',
suggestion: 'You have ES modules in your HTML but the environment is CommonJS. This can cause conflicts.'
});
}
}
// Check for mixed module systems
const hasESModules = document.querySelector('script[type="module"]') !== null;
const hasCommonJS = Array.from(document.scripts).some(script => script.textContent?.includes('require(') ||
script.textContent?.includes('module.exports'));
if (hasESModules && hasCommonJS) {
conflicts.push({
file: 'mixed',
expectedType: 'module',
actualType: 'commonjs',
suggestion: 'Mixing ES modules and CommonJS can cause issues. Consider using one module system consistently.'
});
}
return conflicts;
});
conflicts.forEach(conflict => {
this.errors.moduleConflicts.push(conflict);
});
}
async getErrorReport() {
// Get any errors captured in the page
if (this.page) {
const pageErrors = await this.page.evaluate(() => {
return {
scriptErrors: window.__errorDetection?.scriptErrors || [],
moduleConflicts: window.__errorDetection?.moduleConflicts || [],
htmlErrors: window.__errorDetection?.htmlErrors || [],
events: window.__errorDetectionEvents || []
};
});
// Merge page errors with our collected errors
if (pageErrors.scriptErrors.length > 0) {
this.errors.scriptErrors.push(...pageErrors.scriptErrors);
}
if (pageErrors.moduleConflicts.length > 0) {
this.errors.moduleConflicts.push(...pageErrors.moduleConflicts);
}
if (pageErrors.htmlErrors.length > 0) {
this.errors.htmlErrors.push(...pageErrors.htmlErrors);
}
}
// Generate final suggestions
this.generateSuggestions();
return this.errors;
}
generateSuggestions() {
// Clear existing suggestions
this.errors.suggestions = [];
// Module conflict suggestions
if (this.errors.moduleConflicts.length > 0) {
const hasParentModuleType = this.errors.moduleConflicts.some(c => c.suggestion.includes('parent package.json'));
if (hasParentModuleType) {
this.errors.suggestions.push('1. Check parent package.json for "type": "module" - this affects all .js files', '2. Use .mjs extension for ES modules or .cjs for CommonJS files', '3. Or remove "type": "module" from package.json to use CommonJS by default');
}
}
// HTML error suggestions
if (this.errors.htmlErrors.some(e => e.type === 'malformed_tag' && e.element === 'script')) {
this.errors.suggestions.push('Fix malformed script tags: Change <script /> to <script></script>');
}
// Script loading error suggestions
if (this.errors.scriptErrors.some(e => e.type === 'network')) {
this.errors.suggestions.push('Check that all script URLs are correct and accessible', 'Verify CORS settings if loading scripts from different domains');
}
// Runtime error suggestions
const runtimeErrors = this.errors.scriptErrors.filter(e => e.type === 'runtime');
if (runtimeErrors.length > 5) {
this.errors.suggestions.push('Multiple runtime errors detected - check for missing dependencies or initialization order issues');
}
}
async validateScriptTags() {
if (!this.page)
return [];
return await this.page.evaluate(() => {
const errors = [];
const scripts = document.getElementsByTagName('script');
Array.from(scripts).forEach((script, index) => {
// Get the original HTML to check for self-closing syntax
const htmlString = script.outerHTML;
// Check for self-closing script tags
if (htmlString.endsWith('/>')) {
errors.push({
type: 'malformed_tag',
element: 'script',
line: index + 1, // Approximate line number
suggestion: 'Script tags must not be self-closing. Change <script .../> to <script ...></script>',
code: htmlString
});
}
// Check for empty src with self-closing
if (!script.src && !script.textContent?.trim() && htmlString.includes('/>')) {
errors.push({
type: 'malformed_tag',
element: 'script',
line: index + 1,
suggestion: 'Empty script tags should use <script></script> syntax, not <script />',
code: htmlString
});
}
});
return errors;
});
}
}
//# sourceMappingURL=javascript-error-detection-engine.js.map