@stati/core 1.3.2 → 1.5.0

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.
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Error overlay utility for displaying pretty errors in development mode.
3
+ * Provides a user-friendly interface for template rendering and build errors.
4
+ */
5
+ /**
6
+ * Creates a styled error overlay HTML that displays comprehensive error information.
7
+ *
8
+ * @param error - The error details to display
9
+ * @param requestPath - The path that triggered the error
10
+ * @returns HTML string for the error overlay
11
+ */
12
+ export function createErrorOverlay(error, requestPath) {
13
+ const timestamp = new Date().toLocaleTimeString();
14
+ return `<!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ <title>Stati Development Error</title>
20
+ <style>
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
29
+ background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
30
+ color: #ffffff;
31
+ line-height: 1.6;
32
+ min-height: 100vh;
33
+ overflow-y: auto;
34
+ }
35
+
36
+ .error-overlay {
37
+ min-height: 100vh;
38
+ display: flex;
39
+ flex-direction: column;
40
+ }
41
+
42
+ .error-header {
43
+ background: linear-gradient(90deg, #ff6b6b 0%, #ee5a52 100%);
44
+ padding: 24px 32px;
45
+ box-shadow: 0 4px 20px rgba(255, 107, 107, 0.3);
46
+ }
47
+
48
+ .error-title {
49
+ font-size: 24px;
50
+ font-weight: 600;
51
+ margin-bottom: 8px;
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 12px;
55
+ }
56
+
57
+ .error-icon {
58
+ width: 32px;
59
+ height: 32px;
60
+ background: rgba(255, 255, 255, 0.2);
61
+ border-radius: 50%;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ font-size: 18px;
66
+ }
67
+
68
+ .error-subtitle {
69
+ font-size: 14px;
70
+ opacity: 0.9;
71
+ font-weight: 400;
72
+ }
73
+
74
+ .error-content {
75
+ flex: 1;
76
+ padding: 32px;
77
+ max-width: 1200px;
78
+ margin: 0 auto;
79
+ width: 100%;
80
+ }
81
+
82
+ .error-section {
83
+ background: rgba(255, 255, 255, 0.05);
84
+ border-radius: 12px;
85
+ padding: 24px;
86
+ margin-bottom: 24px;
87
+ border: 1px solid rgba(255, 255, 255, 0.1);
88
+ }
89
+
90
+ .section-title {
91
+ font-size: 18px;
92
+ font-weight: 600;
93
+ margin-bottom: 16px;
94
+ color: #ff6b6b;
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 8px;
98
+ }
99
+
100
+ .error-message {
101
+ background: rgba(255, 107, 107, 0.1);
102
+ border: 1px solid rgba(255, 107, 107, 0.3);
103
+ border-radius: 8px;
104
+ padding: 16px;
105
+ font-size: 16px;
106
+ line-height: 1.5;
107
+ margin-bottom: 16px;
108
+ }
109
+
110
+ .error-location {
111
+ display: flex;
112
+ gap: 16px;
113
+ margin-bottom: 16px;
114
+ flex-wrap: wrap;
115
+ }
116
+
117
+ .location-item {
118
+ background: rgba(255, 255, 255, 0.1);
119
+ padding: 8px 12px;
120
+ border-radius: 6px;
121
+ font-size: 14px;
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 6px;
125
+ }
126
+
127
+ .location-label {
128
+ color: #888;
129
+ font-weight: 500;
130
+ }
131
+
132
+ .location-value {
133
+ color: #ffffff;
134
+ font-weight: 600;
135
+ }
136
+
137
+ .code-block {
138
+ background: #1a1a1a;
139
+ border: 1px solid rgba(255, 255, 255, 0.1);
140
+ border-radius: 8px;
141
+ overflow: hidden;
142
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
143
+ }
144
+
145
+ .code-header {
146
+ background: rgba(255, 255, 255, 0.05);
147
+ padding: 12px 16px;
148
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
149
+ font-size: 14px;
150
+ color: #888;
151
+ }
152
+
153
+ .code-content {
154
+ padding: 0;
155
+ overflow-x: auto;
156
+ }
157
+
158
+ .code-line {
159
+ display: flex;
160
+ align-items: center;
161
+ padding: 0 16px;
162
+ min-height: 24px;
163
+ font-size: 14px;
164
+ }
165
+
166
+ .code-line:hover {
167
+ background: rgba(255, 255, 255, 0.05);
168
+ }
169
+
170
+ .line-number {
171
+ color: #666;
172
+ margin-right: 16px;
173
+ min-width: 40px;
174
+ text-align: right;
175
+ font-weight: 500;
176
+ }
177
+
178
+ .line-error {
179
+ background: rgba(255, 107, 107, 0.2);
180
+ border-left: 4px solid #ff6b6b;
181
+ }
182
+
183
+ .line-error .line-number {
184
+ color: #ff6b6b;
185
+ font-weight: 600;
186
+ }
187
+
188
+ .line-context {
189
+ background: rgba(255, 255, 255, 0.03);
190
+ }
191
+
192
+ .suggestions {
193
+ background: rgba(52, 211, 153, 0.1);
194
+ border: 1px solid rgba(52, 211, 153, 0.3);
195
+ border-radius: 8px;
196
+ padding: 16px;
197
+ }
198
+
199
+ .suggestions-title {
200
+ color: #34d399;
201
+ font-weight: 600;
202
+ margin-bottom: 12px;
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 8px;
206
+ }
207
+
208
+ .suggestion-item {
209
+ background: rgba(52, 211, 153, 0.05);
210
+ border-radius: 6px;
211
+ padding: 12px;
212
+ margin-bottom: 8px;
213
+ border-left: 3px solid #34d399;
214
+ }
215
+
216
+ .suggestion-item:last-child {
217
+ margin-bottom: 0;
218
+ }
219
+
220
+ .stack-trace {
221
+ background: #1a1a1a;
222
+ border: 1px solid rgba(255, 255, 255, 0.1);
223
+ border-radius: 8px;
224
+ padding: 16px;
225
+ font-size: 13px;
226
+ line-height: 1.4;
227
+ overflow-x: auto;
228
+ white-space: pre-wrap;
229
+ color: #ccc;
230
+ }
231
+
232
+ .footer {
233
+ background: rgba(255, 255, 255, 0.05);
234
+ padding: 16px 32px;
235
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
236
+ display: flex;
237
+ justify-content: space-between;
238
+ align-items: center;
239
+ font-size: 14px;
240
+ color: #888;
241
+ }
242
+
243
+ .footer-info {
244
+ display: flex;
245
+ gap: 24px;
246
+ }
247
+
248
+ .refresh-button {
249
+ background: #ff6b6b;
250
+ color: white;
251
+ border: none;
252
+ padding: 8px 16px;
253
+ border-radius: 6px;
254
+ font-weight: 500;
255
+ cursor: pointer;
256
+ transition: background 0.2s;
257
+ }
258
+
259
+ .refresh-button:hover {
260
+ background: #ee5a52;
261
+ }
262
+
263
+ .type-badge {
264
+ background: rgba(255, 107, 107, 0.2);
265
+ color: #ff6b6b;
266
+ padding: 4px 8px;
267
+ border-radius: 4px;
268
+ font-size: 12px;
269
+ font-weight: 600;
270
+ text-transform: uppercase;
271
+ letter-spacing: 0.5px;
272
+ }
273
+
274
+ .type-template { background: rgba(147, 51, 234, 0.2); color: #a855f7; }
275
+ .type-build { background: rgba(245, 101, 101, 0.2); color: #f56565; }
276
+ .type-markdown { background: rgba(59, 130, 246, 0.2); color: #3b82f6; }
277
+ .type-config { background: rgba(251, 191, 36, 0.2); color: #fbbf24; }
278
+
279
+ @media (max-width: 768px) {
280
+ .error-content {
281
+ padding: 16px;
282
+ }
283
+
284
+ .error-header {
285
+ padding: 16px 24px;
286
+ }
287
+
288
+ .error-title {
289
+ font-size: 20px;
290
+ }
291
+
292
+ .error-location {
293
+ flex-direction: column;
294
+ }
295
+ }
296
+ </style>
297
+ </head>
298
+ <body>
299
+ <div class="error-overlay">
300
+ <div class="error-header">
301
+ <div class="error-title">
302
+ <div class="error-icon">⚠️</div>
303
+ Stati Development Error
304
+ <span class="type-badge type-${error.type}">${error.type}</span>
305
+ </div>
306
+ <div class="error-subtitle">
307
+ ${error.type === 'template'
308
+ ? 'Template rendering failed'
309
+ : error.type === 'build'
310
+ ? 'Build process failed'
311
+ : error.type === 'markdown'
312
+ ? 'Markdown processing failed'
313
+ : 'Configuration error'} • ${timestamp}
314
+ </div>
315
+ </div>
316
+
317
+ <div class="error-content">
318
+ <div class="error-section">
319
+ <div class="section-title">
320
+ 🚨 Error Details
321
+ </div>
322
+ <div class="error-message">
323
+ ${escapeHtml(error.message)}
324
+ </div>
325
+
326
+ ${error.file || error.line
327
+ ? `
328
+ <div class="error-location">
329
+ ${error.file
330
+ ? `<div class="location-item">
331
+ <span class="location-label">File:</span>
332
+ <span class="location-value">${escapeHtml(error.file)}</span>
333
+ </div>`
334
+ : ''}
335
+ ${error.line
336
+ ? `<div class="location-item">
337
+ <span class="location-label">Line:</span>
338
+ <span class="location-value">${error.line}${error.column ? `:${error.column}` : ''}</span>
339
+ </div>`
340
+ : ''}
341
+ <div class="location-item">
342
+ <span class="location-label">Route:</span>
343
+ <span class="location-value">${escapeHtml(requestPath)}</span>
344
+ </div>
345
+ </div>
346
+ `
347
+ : ''}
348
+ </div>
349
+
350
+ ${error.code || error.context
351
+ ? `
352
+ <div class="error-section">
353
+ <div class="section-title">
354
+ 📄 Code Context
355
+ </div>
356
+ <div class="code-block">
357
+ ${error.file ? `<div class="code-header">${escapeHtml(error.file)}</div>` : ''}
358
+ <div class="code-content">
359
+ ${formatCodeContext(error)}
360
+ </div>
361
+ </div>
362
+ </div>
363
+ `
364
+ : ''}
365
+
366
+ ${error.suggestions && error.suggestions.length > 0
367
+ ? `
368
+ <div class="error-section">
369
+ <div class="suggestions">
370
+ <div class="suggestions-title">
371
+ 💡 Suggestions
372
+ </div>
373
+ ${error.suggestions
374
+ .map((suggestion) => `
375
+ <div class="suggestion-item">
376
+ ${escapeHtml(suggestion)}
377
+ </div>
378
+ `)
379
+ .join('')}
380
+ </div>
381
+ </div>
382
+ `
383
+ : ''}
384
+
385
+ ${error.stack
386
+ ? `
387
+ <div class="error-section">
388
+ <div class="section-title">
389
+ 🔍 Stack Trace
390
+ </div>
391
+ <div class="stack-trace">${escapeHtml(error.stack)}</div>
392
+ </div>
393
+ `
394
+ : ''}
395
+ </div>
396
+
397
+ <div class="footer">
398
+ <div class="footer-info">
399
+ <span>Stati Development Server</span>
400
+ <span>•</span>
401
+ <span>Fix the error and save to automatically reload</span>
402
+ </div>
403
+ <button class="refresh-button" onclick="window.location.reload()">
404
+ Refresh
405
+ </button>
406
+ </div>
407
+ </div>
408
+
409
+ <script>
410
+ // Auto-refresh when files change (if WebSocket connection exists)
411
+ if (typeof WebSocket !== 'undefined') {
412
+ try {
413
+ const ws = new WebSocket('ws://localhost:3000/__ws');
414
+ ws.onmessage = function(event) {
415
+ const data = JSON.parse(event.data);
416
+ if (data.type === 'reload') {
417
+ window.location.reload();
418
+ }
419
+ };
420
+ } catch (e) {
421
+ // WebSocket connection failed, that's okay
422
+ }
423
+ }
424
+
425
+ // Keyboard shortcut to refresh (Ctrl/Cmd + R)
426
+ document.addEventListener('keydown', function(e) {
427
+ if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
428
+ e.preventDefault();
429
+ window.location.reload();
430
+ }
431
+ });
432
+ </script>
433
+ </body>
434
+ </html>`;
435
+ }
436
+ /**
437
+ * Escapes HTML characters in a string for safe display.
438
+ */
439
+ function escapeHtml(text) {
440
+ return text
441
+ .replace(/&/g, '&amp;')
442
+ .replace(/</g, '&lt;')
443
+ .replace(/>/g, '&gt;')
444
+ .replace(/"/g, '&quot;')
445
+ .replace(/'/g, '&#39;');
446
+ }
447
+ /**
448
+ * Formats code context with line numbers and highlighting.
449
+ */
450
+ function formatCodeContext(error) {
451
+ if (!error.context && !error.code) {
452
+ return '<div class="code-line"><span class="line-number">-</span>No code context available</div>';
453
+ }
454
+ const lines = [];
455
+ const errorLine = error.line || 0;
456
+ let currentLine = errorLine;
457
+ if (error.context?.before) {
458
+ currentLine = errorLine - error.context.before.length;
459
+ error.context.before.forEach((line) => {
460
+ lines.push(`<div class="code-line line-context">
461
+ <span class="line-number">${currentLine}</span>
462
+ <span class="line-content">${escapeHtml(line)}</span>
463
+ </div>`);
464
+ currentLine++;
465
+ });
466
+ }
467
+ if (error.code) {
468
+ lines.push(`<div class="code-line line-error">
469
+ <span class="line-number">${errorLine}</span>
470
+ <span class="line-content">${escapeHtml(error.code)}</span>
471
+ </div>`);
472
+ currentLine = errorLine + 1;
473
+ }
474
+ if (error.context?.after) {
475
+ error.context.after.forEach((line) => {
476
+ lines.push(`<div class="code-line line-context">
477
+ <span class="line-number">${currentLine}</span>
478
+ <span class="line-content">${escapeHtml(line)}</span>
479
+ </div>`);
480
+ currentLine++;
481
+ });
482
+ }
483
+ return lines.join('');
484
+ }
485
+ /**
486
+ * Parses error information from various error types to create structured ErrorDetails.
487
+ */
488
+ export function parseErrorDetails(error, type, filePath) {
489
+ const details = {
490
+ type,
491
+ message: error.message,
492
+ };
493
+ if (error.stack) {
494
+ details.stack = error.stack;
495
+ }
496
+ // Try to extract file and line information from the stack trace
497
+ if (error.stack) {
498
+ const stackLines = error.stack.split('\n');
499
+ for (const line of stackLines) {
500
+ // Look for file paths in the stack trace
501
+ const match = line.match(/at\s+.*\((.+):(\d+):(\d+)\)/);
502
+ if (match && match[1] && match[2] && match[3] && !match[1].includes('node_modules')) {
503
+ details.file = match[1];
504
+ details.line = parseInt(match[2], 10);
505
+ details.column = parseInt(match[3], 10);
506
+ break;
507
+ }
508
+ }
509
+ }
510
+ // Override with provided file path if available
511
+ if (filePath) {
512
+ details.file = filePath;
513
+ }
514
+ // Add type-specific suggestions
515
+ details.suggestions = getErrorSuggestions(error, type);
516
+ return details;
517
+ }
518
+ /**
519
+ * Provides helpful suggestions based on error type and message.
520
+ */
521
+ function getErrorSuggestions(error, type) {
522
+ const suggestions = [];
523
+ const message = error.message.toLowerCase();
524
+ if (type === 'template') {
525
+ if (message.includes('not found') || message.includes('cannot resolve')) {
526
+ suggestions.push('Check if the template file exists in the correct location');
527
+ suggestions.push('Verify the template file path is correct in your layout or include statement');
528
+ suggestions.push('Ensure the template file has the correct .eta extension');
529
+ }
530
+ if (message.includes('syntax') || message.includes('unexpected token')) {
531
+ suggestions.push('Check for missing or extra brackets, quotes, or commas in your template');
532
+ suggestions.push('Verify that all Eta template syntax is correctly formatted');
533
+ suggestions.push('Look for unclosed tags or expressions');
534
+ }
535
+ if (message.includes('undefined') || message.includes('null')) {
536
+ suggestions.push('Check if all required data is being passed to the template');
537
+ suggestions.push('Add conditional checks for optional data in your template');
538
+ suggestions.push('Verify that page frontMatter and content are properly structured');
539
+ }
540
+ }
541
+ else if (type === 'build') {
542
+ if (message.includes('permission') || message.includes('eacces')) {
543
+ suggestions.push('Check file and directory permissions');
544
+ suggestions.push('Ensure Stati has write access to the output directory');
545
+ }
546
+ if (message.includes('space') || message.includes('enospc')) {
547
+ suggestions.push('Free up disk space on your system');
548
+ suggestions.push('Check if the output directory has sufficient space');
549
+ }
550
+ }
551
+ else if (type === 'markdown') {
552
+ if (message.includes('front-matter') || message.includes('yaml')) {
553
+ suggestions.push('Check the YAML front-matter syntax for errors');
554
+ suggestions.push('Ensure front-matter is properly enclosed in --- markers');
555
+ suggestions.push('Verify that all YAML values are properly quoted if they contain special characters');
556
+ }
557
+ }
558
+ // General suggestions
559
+ suggestions.push('Save the file and the page will automatically reload');
560
+ suggestions.push('Check the Stati documentation for more help');
561
+ return suggestions;
562
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Creates a development-mode Proxy for the partials object that throws errors
3
+ * when accessing non-existent partials instead of returning undefined
4
+ */
5
+ export declare function createValidatingPartialsProxy(partials: Record<string, string>): Record<string, string>;
6
+ //# sourceMappingURL=partial-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"partial-validation.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partial-validation.ts"],"names":[],"mappings":"AA4FA;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmDxB"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Creates inline error overlay HTML for missing partials
3
+ */
4
+ function createInlineErrorOverlay(partialName, suggestions) {
5
+ const suggestionText = suggestions.length > 0
6
+ ? `Did you mean: ${suggestions.map((s) => `"${s}"`).join(', ')}?`
7
+ : 'No similar partials found';
8
+ return `
9
+ <!-- Stati Development Error Overlay -->
10
+ <div style="
11
+ position: fixed;
12
+ top: 20px;
13
+ right: 20px;
14
+ z-index: 999999;
15
+ background: #dc2626;
16
+ color: white;
17
+ padding: 16px;
18
+ border-radius: 8px;
19
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
+ font-size: 14px;
22
+ line-height: 1.4;
23
+ max-width: 400px;
24
+ border: 2px solid #fca5a5;
25
+ ">
26
+ <div style="font-weight: 600; margin-bottom: 8px; font-size: 16px;">
27
+ ⚠️ Template Error
28
+ </div>
29
+ <div style="margin-bottom: 8px;">
30
+ <strong>Partial "${partialName}" not found</strong>
31
+ </div>
32
+ <div style="color: #fca5a5; font-size: 13px;">
33
+ ${suggestionText}
34
+ </div>
35
+ <div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #fca5a5; font-size: 12px; opacity: 0.8;">
36
+ Fix the partial name in your template to dismiss this error
37
+ </div>
38
+ </div>
39
+ <!-- End Stati Error Overlay -->`;
40
+ }
41
+ /**
42
+ * Finds similar partial names to suggest when a partial is not found
43
+ */
44
+ function findSimilarPartialNames(targetName, availableNames) {
45
+ if (availableNames.length === 0)
46
+ return [];
47
+ // Simple similarity check based on common prefixes and substrings
48
+ const target = targetName.toLowerCase();
49
+ const suggestions = availableNames
50
+ .map((name) => {
51
+ const candidate = name.toLowerCase();
52
+ let score = 0;
53
+ // Exact match gets highest score
54
+ if (candidate === target)
55
+ return { name, score: 1000 };
56
+ // Check if one is contained in the other
57
+ if (candidate.includes(target) || target.includes(candidate)) {
58
+ score += 100;
59
+ }
60
+ // Check common prefix length
61
+ let prefixLen = 0;
62
+ for (let i = 0; i < Math.min(target.length, candidate.length); i++) {
63
+ if (target[i] === candidate[i]) {
64
+ prefixLen++;
65
+ }
66
+ else {
67
+ break;
68
+ }
69
+ }
70
+ score += prefixLen * 10;
71
+ // Check common characters
72
+ const targetChars = new Set(target);
73
+ const candidateChars = new Set(candidate);
74
+ const commonChars = [...targetChars].filter((char) => candidateChars.has(char));
75
+ score += commonChars.length;
76
+ return { name, score };
77
+ })
78
+ .filter((item) => item.score > 0) // Only include items with some similarity
79
+ .sort((a, b) => b.score - a.score) // Sort by score descending
80
+ .slice(0, 3) // Top 3 suggestions
81
+ .map((item) => item.name);
82
+ return suggestions;
83
+ }
84
+ /**
85
+ * Creates a development-mode Proxy for the partials object that throws errors
86
+ * when accessing non-existent partials instead of returning undefined
87
+ */
88
+ export function createValidatingPartialsProxy(partials) {
89
+ // In production, return partials as-is
90
+ // Only skip validation if explicitly set to production
91
+ if (process.env.NODE_ENV === 'production') {
92
+ return partials;
93
+ }
94
+ return new Proxy(partials, {
95
+ get(target, prop, receiver) {
96
+ // Allow normal object operations
97
+ if (typeof prop === 'symbol') {
98
+ return Reflect.get(target, prop, receiver);
99
+ }
100
+ const propName = String(prop);
101
+ // Check if the property exists
102
+ if (propName in target) {
103
+ return target[propName];
104
+ }
105
+ // Special case: allow accessing length, toString, etc.
106
+ if (propName in Object.prototype || propName === 'length') {
107
+ return Reflect.get(target, prop, receiver);
108
+ }
109
+ // Property doesn't exist - return error overlay HTML instead of throwing
110
+ const availablePartials = Object.keys(target);
111
+ const suggestions = findSimilarPartialNames(propName, availablePartials);
112
+ // Special case: throw error if no partials are available at all
113
+ if (availablePartials.length === 0) {
114
+ throw new Error('No partials are available');
115
+ }
116
+ // In development, render an inline error overlay
117
+ return createInlineErrorOverlay(propName, suggestions);
118
+ },
119
+ has(target, prop) {
120
+ return prop in target;
121
+ },
122
+ ownKeys(target) {
123
+ return Reflect.ownKeys(target);
124
+ },
125
+ getOwnPropertyDescriptor(target, prop) {
126
+ return Reflect.getOwnPropertyDescriptor(target, prop);
127
+ },
128
+ });
129
+ }