mastercontroller 1.2.12 → 1.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
- // version 1.0.20 - improved console/error logging with syntax error code frames
3
- var master = require('./MasterControl');
2
+ // version 1.0.21 - improved console/error logging with syntax error code frames
3
+ var master = require('../MasterControl');
4
4
  var winston = require('winston');
5
5
  var fileserver = require('fs');
6
6
  const { request } = require('http');
@@ -0,0 +1,487 @@
1
+ /**
2
+ * MasterErrorHandler - Comprehensive error handling system
3
+ * Provides formatted error messages with helpful suggestions and documentation links
4
+ * Version: 1.0.0
5
+ */
6
+
7
+ const path = require('path');
8
+
9
+ // ANSI color codes for terminal output
10
+ const colors = {
11
+ reset: '\x1b[0m',
12
+ bright: '\x1b[1m',
13
+ dim: '\x1b[2m',
14
+ red: '\x1b[31m',
15
+ green: '\x1b[32m',
16
+ yellow: '\x1b[33m',
17
+ blue: '\x1b[34m',
18
+ magenta: '\x1b[35m',
19
+ cyan: '\x1b[36m',
20
+ white: '\x1b[37m',
21
+ bgRed: '\x1b[41m',
22
+ bgYellow: '\x1b[43m',
23
+ };
24
+
25
+ // Error code definitions
26
+ const ERROR_CODES = {
27
+ MC_ERR_EVENT_HANDLER_NOT_FOUND: {
28
+ title: 'Event Handler Not Found',
29
+ docsPath: '/docs/events#handler-not-found',
30
+ severity: 'error'
31
+ },
32
+ MC_ERR_EVENT_SYNTAX_INVALID: {
33
+ title: 'Invalid @event Syntax',
34
+ docsPath: '/docs/events#syntax',
35
+ severity: 'error'
36
+ },
37
+ MC_ERR_COMPONENT_RENDER_FAILED: {
38
+ title: 'Component Render Failed',
39
+ docsPath: '/docs/ssr#render-errors',
40
+ severity: 'error'
41
+ },
42
+ MC_ERR_TEMPRENDER_MISSING: {
43
+ title: 'Missing tempRender() Method',
44
+ docsPath: '/docs/components#temprender',
45
+ severity: 'warning'
46
+ },
47
+ MC_ERR_DUPLICATE_ELEMENT: {
48
+ title: 'Duplicate Custom Element Registration',
49
+ docsPath: '/docs/components#duplicate-names',
50
+ severity: 'warning'
51
+ },
52
+ MC_ERR_HYDRATION_MISMATCH: {
53
+ title: 'Hydration Mismatch Detected',
54
+ docsPath: '/docs/hydration#mismatches',
55
+ severity: 'warning'
56
+ },
57
+ MC_ERR_SLOW_RENDER: {
58
+ title: 'Slow Component Render',
59
+ docsPath: '/docs/performance',
60
+ severity: 'warning'
61
+ },
62
+ MC_ERR_MANIFEST_PARSE: {
63
+ title: 'Event Manifest Parse Error',
64
+ docsPath: '/docs/events#manifest-errors',
65
+ severity: 'error'
66
+ },
67
+ MC_ERR_MODULE_LOAD: {
68
+ title: 'Module Load Failed',
69
+ docsPath: '/docs/troubleshooting#module-errors',
70
+ severity: 'error'
71
+ }
72
+ };
73
+
74
+ /**
75
+ * Levenshtein distance for "Did you mean?" suggestions
76
+ */
77
+ function levenshteinDistance(str1, str2) {
78
+ const len1 = str1.length;
79
+ const len2 = str2.length;
80
+ const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
81
+
82
+ for (let i = 0; i <= len1; i++) matrix[i][0] = i;
83
+ for (let j = 0; j <= len2; j++) matrix[0][j] = j;
84
+
85
+ for (let i = 1; i <= len1; i++) {
86
+ for (let j = 1; j <= len2; j++) {
87
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
88
+ matrix[i][j] = Math.min(
89
+ matrix[i - 1][j] + 1,
90
+ matrix[i][j - 1] + 1,
91
+ matrix[i - 1][j - 1] + cost
92
+ );
93
+ }
94
+ }
95
+
96
+ return matrix[len1][len2];
97
+ }
98
+
99
+ /**
100
+ * Find similar strings for suggestions
101
+ */
102
+ function findSimilarStrings(target, candidates, maxSuggestions = 3) {
103
+ if (!target || !candidates || candidates.length === 0) return [];
104
+
105
+ const withDistances = candidates
106
+ .map(candidate => ({
107
+ value: candidate,
108
+ distance: levenshteinDistance(target.toLowerCase(), candidate.toLowerCase())
109
+ }))
110
+ .filter(item => item.distance <= 3) // Only suggest if reasonably close
111
+ .sort((a, b) => a.distance - b.distance);
112
+
113
+ return withDistances.slice(0, maxSuggestions).map(item => item.value);
114
+ }
115
+
116
+ /**
117
+ * Extract line number from stack trace
118
+ */
119
+ function extractLineNumber(stack, filePath) {
120
+ if (!stack || !filePath) return null;
121
+
122
+ const lines = stack.split('\n');
123
+ for (const line of lines) {
124
+ if (line.includes(filePath)) {
125
+ const match = line.match(/:(\d+):(\d+)/);
126
+ if (match) return parseInt(match[1], 10);
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Get relative path from project root
134
+ */
135
+ function getRelativePath(absolutePath) {
136
+ if (!absolutePath) return null;
137
+ const cwd = process.cwd();
138
+ return path.relative(cwd, absolutePath);
139
+ }
140
+
141
+ class MasterControllerError extends Error {
142
+ constructor(options = {}) {
143
+ super(options.message || 'An error occurred');
144
+
145
+ this.name = 'MasterControllerError';
146
+ this.code = options.code || 'MC_ERR_UNKNOWN';
147
+ this.component = options.component || null;
148
+ this.file = options.file || null;
149
+ this.line = options.line || null;
150
+ this.handler = options.handler || null;
151
+ this.expected = options.expected || null;
152
+ this.suggestions = options.suggestions || [];
153
+ this.details = options.details || null;
154
+ this.context = options.context || {};
155
+ this.originalError = options.originalError || null;
156
+
157
+ // Get error metadata from code
158
+ this.metadata = ERROR_CODES[this.code] || {
159
+ title: 'Unknown Error',
160
+ docsPath: '/docs',
161
+ severity: 'error'
162
+ };
163
+
164
+ // Build docs URL
165
+ this.docsUrl = options.docsUrl || this._buildDocsUrl();
166
+
167
+ // Extract line number from stack if not provided
168
+ if (!this.line && this.originalError && this.originalError.stack && this.file) {
169
+ this.line = extractLineNumber(this.originalError.stack, this.file);
170
+ }
171
+
172
+ Error.captureStackTrace(this, this.constructor);
173
+ }
174
+
175
+ _buildDocsUrl() {
176
+ const baseUrl = process.env.MASTER_DOCS_URL || 'https://mastercontroller.dev';
177
+ return baseUrl + this.metadata.docsPath;
178
+ }
179
+
180
+ /**
181
+ * Format error for terminal output with colors
182
+ */
183
+ format() {
184
+ const { bright, red, yellow, cyan, blue, green, dim, reset } = colors;
185
+ const isError = this.metadata.severity === 'error';
186
+ const icon = isError ? '❌' : '⚠️';
187
+ const titleColor = isError ? red : yellow;
188
+
189
+ let output = '\n';
190
+ output += `${titleColor}${bright}${icon} MasterController ${isError ? 'Error' : 'Warning'}: ${this.metadata.title}${reset}\n`;
191
+ output += `${dim}${'─'.repeat(80)}${reset}\n\n`;
192
+
193
+ // Component info
194
+ if (this.component) {
195
+ output += `${cyan}Component:${reset} <${this.component}>\n`;
196
+ }
197
+
198
+ // File location
199
+ if (this.file) {
200
+ const relativePath = getRelativePath(this.file);
201
+ const location = this.line ? `${relativePath}:${this.line}` : relativePath;
202
+ output += `${cyan}Location:${reset} ${location}\n`;
203
+ }
204
+
205
+ // Handler details
206
+ if (this.handler) {
207
+ output += `${cyan}Handler:${reset} ${this.handler}`;
208
+ if (this.expected) {
209
+ output += ` ${dim}(expected: ${this.expected})${reset}`;
210
+ }
211
+ output += '\n';
212
+ }
213
+
214
+ // Main message
215
+ if (this.message) {
216
+ output += `\n${this.message}\n`;
217
+ }
218
+
219
+ // Details
220
+ if (this.details) {
221
+ output += `\n${dim}${this.details}${reset}\n`;
222
+ }
223
+
224
+ // Suggestions
225
+ if (this.suggestions && this.suggestions.length > 0) {
226
+ output += `\n${green}${bright}Did you mean?${reset}\n`;
227
+ this.suggestions.forEach(suggestion => {
228
+ output += ` ${green}→${reset} ${suggestion}\n`;
229
+ });
230
+ }
231
+
232
+ // Fix instructions
233
+ if (this.file && this.line) {
234
+ output += `\n${blue}${bright}Fix:${reset} Check ${getRelativePath(this.file)}:${this.line}\n`;
235
+ }
236
+
237
+ // Original error stack (in development)
238
+ if (this.originalError && process.env.NODE_ENV !== 'production') {
239
+ output += `\n${dim}Original Error:${reset}\n${dim}${this.originalError.stack}${reset}\n`;
240
+ }
241
+
242
+ // Documentation link
243
+ output += `\n${blue}${bright}Learn more:${reset} ${this.docsUrl}\n`;
244
+ output += `${dim}${'─'.repeat(80)}${reset}\n`;
245
+
246
+ return output;
247
+ }
248
+
249
+ /**
250
+ * Format error for HTML output (development error page)
251
+ */
252
+ toHTML() {
253
+ const isError = this.metadata.severity === 'error';
254
+ const bgColor = isError ? '#fee' : '#fffbeb';
255
+ const borderColor = isError ? '#f87171' : '#fbbf24';
256
+ const iconColor = isError ? '#dc2626' : '#f59e0b';
257
+
258
+ const relativePath = this.file ? getRelativePath(this.file) : '';
259
+ const location = this.line ? `${relativePath}:${this.line}` : relativePath;
260
+
261
+ return `
262
+ <!DOCTYPE html>
263
+ <html lang="en">
264
+ <head>
265
+ <meta charset="UTF-8">
266
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
267
+ <title>MasterController ${isError ? 'Error' : 'Warning'}</title>
268
+ <style>
269
+ * { margin: 0; padding: 0; box-sizing: border-box; }
270
+ body {
271
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
272
+ background: #f9fafb;
273
+ padding: 20px;
274
+ color: #1f2937;
275
+ }
276
+ .container {
277
+ max-width: 900px;
278
+ margin: 0 auto;
279
+ background: white;
280
+ border-radius: 8px;
281
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
282
+ overflow: hidden;
283
+ }
284
+ .header {
285
+ background: ${bgColor};
286
+ border-left: 4px solid ${borderColor};
287
+ padding: 24px;
288
+ }
289
+ .header h1 {
290
+ font-size: 24px;
291
+ font-weight: 700;
292
+ color: ${iconColor};
293
+ margin-bottom: 8px;
294
+ }
295
+ .header .subtitle {
296
+ font-size: 18px;
297
+ color: #374151;
298
+ font-weight: 600;
299
+ }
300
+ .content {
301
+ padding: 24px;
302
+ }
303
+ .section {
304
+ margin-bottom: 24px;
305
+ }
306
+ .section-title {
307
+ font-size: 14px;
308
+ font-weight: 600;
309
+ color: #6b7280;
310
+ text-transform: uppercase;
311
+ letter-spacing: 0.05em;
312
+ margin-bottom: 8px;
313
+ }
314
+ .section-content {
315
+ font-size: 16px;
316
+ color: #1f2937;
317
+ line-height: 1.6;
318
+ }
319
+ .code {
320
+ background: #f3f4f6;
321
+ padding: 12px 16px;
322
+ border-radius: 6px;
323
+ font-family: 'Courier New', monospace;
324
+ font-size: 14px;
325
+ overflow-x: auto;
326
+ border-left: 3px solid #3b82f6;
327
+ }
328
+ .suggestions {
329
+ list-style: none;
330
+ }
331
+ .suggestions li {
332
+ background: #ecfdf5;
333
+ padding: 8px 12px;
334
+ margin-bottom: 8px;
335
+ border-radius: 4px;
336
+ border-left: 3px solid #10b981;
337
+ }
338
+ .suggestions li:before {
339
+ content: '→ ';
340
+ color: #10b981;
341
+ font-weight: bold;
342
+ }
343
+ .link {
344
+ display: inline-block;
345
+ background: #3b82f6;
346
+ color: white;
347
+ padding: 10px 20px;
348
+ border-radius: 6px;
349
+ text-decoration: none;
350
+ font-weight: 600;
351
+ margin-top: 16px;
352
+ }
353
+ .link:hover {
354
+ background: #2563eb;
355
+ }
356
+ .stack {
357
+ background: #1f2937;
358
+ color: #f3f4f6;
359
+ padding: 16px;
360
+ border-radius: 6px;
361
+ font-family: 'Courier New', monospace;
362
+ font-size: 12px;
363
+ overflow-x: auto;
364
+ max-height: 300px;
365
+ overflow-y: auto;
366
+ }
367
+ </style>
368
+ </head>
369
+ <body>
370
+ <div class="container">
371
+ <div class="header">
372
+ <h1>${isError ? '❌ Error' : '⚠️ Warning'}</h1>
373
+ <div class="subtitle">${this.escapeHtml(this.metadata.title)}</div>
374
+ </div>
375
+
376
+ <div class="content">
377
+ ${this.component ? `
378
+ <div class="section">
379
+ <div class="section-title">Component</div>
380
+ <div class="section-content code">&lt;${this.escapeHtml(this.component)}&gt;</div>
381
+ </div>
382
+ ` : ''}
383
+
384
+ ${this.file ? `
385
+ <div class="section">
386
+ <div class="section-title">Location</div>
387
+ <div class="section-content code">${this.escapeHtml(location)}</div>
388
+ </div>
389
+ ` : ''}
390
+
391
+ ${this.handler ? `
392
+ <div class="section">
393
+ <div class="section-title">Handler</div>
394
+ <div class="section-content code">${this.escapeHtml(this.handler)}${this.expected ? ` <span style="color: #6b7280;">(expected: ${this.escapeHtml(this.expected)})</span>` : ''}</div>
395
+ </div>
396
+ ` : ''}
397
+
398
+ ${this.message ? `
399
+ <div class="section">
400
+ <div class="section-title">Message</div>
401
+ <div class="section-content">${this.escapeHtml(this.message)}</div>
402
+ </div>
403
+ ` : ''}
404
+
405
+ ${this.details ? `
406
+ <div class="section">
407
+ <div class="section-title">Details</div>
408
+ <div class="section-content">${this.escapeHtml(this.details)}</div>
409
+ </div>
410
+ ` : ''}
411
+
412
+ ${this.suggestions && this.suggestions.length > 0 ? `
413
+ <div class="section">
414
+ <div class="section-title">Did you mean?</div>
415
+ <ul class="suggestions">
416
+ ${this.suggestions.map(s => `<li>${this.escapeHtml(s)}</li>`).join('')}
417
+ </ul>
418
+ </div>
419
+ ` : ''}
420
+
421
+ ${this.originalError && process.env.NODE_ENV !== 'production' ? `
422
+ <div class="section">
423
+ <div class="section-title">Stack Trace</div>
424
+ <pre class="stack">${this.escapeHtml(this.originalError.stack || '')}</pre>
425
+ </div>
426
+ ` : ''}
427
+
428
+ <div class="section">
429
+ <a href="${this.docsUrl}" class="link" target="_blank">View Documentation →</a>
430
+ </div>
431
+ </div>
432
+ </div>
433
+ </body>
434
+ </html>
435
+ `.trim();
436
+ }
437
+
438
+ /**
439
+ * Format error for JSON logging
440
+ */
441
+ toJSON() {
442
+ return {
443
+ name: this.name,
444
+ code: this.code,
445
+ message: this.message,
446
+ severity: this.metadata.severity,
447
+ component: this.component,
448
+ file: this.file ? getRelativePath(this.file) : null,
449
+ line: this.line,
450
+ handler: this.handler,
451
+ expected: this.expected,
452
+ suggestions: this.suggestions,
453
+ details: this.details,
454
+ context: this.context,
455
+ docsUrl: this.docsUrl,
456
+ timestamp: new Date().toISOString(),
457
+ stack: this.stack,
458
+ originalError: this.originalError ? {
459
+ message: this.originalError.message,
460
+ stack: this.originalError.stack
461
+ } : null
462
+ };
463
+ }
464
+
465
+ /**
466
+ * Escape HTML for safe output
467
+ */
468
+ escapeHtml(str) {
469
+ if (!str) return '';
470
+ return String(str)
471
+ .replace(/&/g, '&amp;')
472
+ .replace(/</g, '&lt;')
473
+ .replace(/>/g, '&gt;')
474
+ .replace(/"/g, '&quot;')
475
+ .replace(/'/g, '&#039;');
476
+ }
477
+ }
478
+
479
+ // Export utilities
480
+ module.exports = {
481
+ MasterControllerError,
482
+ ERROR_CODES,
483
+ findSimilarStrings,
484
+ levenshteinDistance,
485
+ getRelativePath,
486
+ extractLineNumber
487
+ };