juxscript 1.1.157 → 1.1.158
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.
- package/machinery/compiler4.js +22 -197
- package/machinery/errors.js +64 -338
- package/package.json +1 -1
package/machinery/compiler4.js
CHANGED
|
@@ -14,30 +14,25 @@ export class JuxCompiler {
|
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.srcDir = config.srcDir || './jux';
|
|
16
16
|
this.distDir = config.distDir || './.jux-dist';
|
|
17
|
-
this.publicDir = config.publicDir || './public';
|
|
17
|
+
this.publicDir = config.publicDir || './public';
|
|
18
18
|
this.defaults = config.defaults || {};
|
|
19
19
|
this.paths = config.paths || {};
|
|
20
20
|
this._juxscriptExports = null;
|
|
21
21
|
this._juxscriptPath = null;
|
|
22
|
-
this._renderFunctionCounter = 0;
|
|
22
|
+
this._renderFunctionCounter = 0;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Locate juxscript package - simplified resolution
|
|
27
|
-
*/
|
|
28
25
|
findJuxscriptPath() {
|
|
29
26
|
if (this._juxscriptPath) return this._juxscriptPath;
|
|
30
27
|
|
|
31
28
|
const projectRoot = process.cwd();
|
|
32
29
|
|
|
33
|
-
// Priority 1: User's node_modules (when used as dependency)
|
|
34
30
|
const userPath = path.resolve(projectRoot, 'node_modules/juxscript/index.js');
|
|
35
31
|
if (fs.existsSync(userPath)) {
|
|
36
32
|
this._juxscriptPath = userPath;
|
|
37
33
|
return userPath;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
// Priority 2: Package root (when developing juxscript itself)
|
|
41
36
|
const packageRoot = path.resolve(__dirname, '..');
|
|
42
37
|
const devPath = path.resolve(packageRoot, 'index.js');
|
|
43
38
|
if (fs.existsSync(devPath)) {
|
|
@@ -45,7 +40,6 @@ export class JuxCompiler {
|
|
|
45
40
|
return devPath;
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
// Priority 3: Sibling in monorepo
|
|
49
43
|
const monoPath = path.resolve(projectRoot, '../jux/index.js');
|
|
50
44
|
if (fs.existsSync(monoPath)) {
|
|
51
45
|
this._juxscriptPath = monoPath;
|
|
@@ -55,9 +49,6 @@ export class JuxCompiler {
|
|
|
55
49
|
return null;
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
/**
|
|
59
|
-
* ✅ Recursively scan for .jux and .js files in srcDir and subdirectories
|
|
60
|
-
*/
|
|
61
52
|
scanFiles() {
|
|
62
53
|
const views = [], dataModules = [], sharedModules = [];
|
|
63
54
|
|
|
@@ -77,26 +68,13 @@ export class JuxCompiler {
|
|
|
77
68
|
const relativePath = path.relative(this.srcDir, fullPath);
|
|
78
69
|
const name = relativePath.replace(/\.[^/.]+$/, '');
|
|
79
70
|
|
|
80
|
-
// Check if it has exports (module) or is executable code (view)
|
|
81
71
|
const hasExports = /export\s+(default|const|function|class|{)/.test(content);
|
|
82
72
|
|
|
83
73
|
if (file.includes('data')) {
|
|
84
|
-
dataModules.push({
|
|
85
|
-
name,
|
|
86
|
-
file: relativePath,
|
|
87
|
-
content,
|
|
88
|
-
originalContent: content
|
|
89
|
-
});
|
|
74
|
+
dataModules.push({ name, file: relativePath, content, originalContent: content });
|
|
90
75
|
} else if (hasExports) {
|
|
91
|
-
|
|
92
|
-
sharedModules.push({
|
|
93
|
-
name,
|
|
94
|
-
file: relativePath,
|
|
95
|
-
content,
|
|
96
|
-
originalContent: content
|
|
97
|
-
});
|
|
76
|
+
sharedModules.push({ name, file: relativePath, content, originalContent: content });
|
|
98
77
|
} else {
|
|
99
|
-
// .jux files without exports are views - use AST to extract imports
|
|
100
78
|
let wrappedContent;
|
|
101
79
|
try {
|
|
102
80
|
const ast = acorn.parse(content, {
|
|
@@ -108,7 +86,6 @@ export class JuxCompiler {
|
|
|
108
86
|
const imports = [];
|
|
109
87
|
let lastImportEnd = 0;
|
|
110
88
|
|
|
111
|
-
// Collect imports and track where they end
|
|
112
89
|
for (const node of ast.body) {
|
|
113
90
|
if (node.type === 'ImportDeclaration') {
|
|
114
91
|
imports.push(content.substring(node.start, node.end));
|
|
@@ -116,10 +93,8 @@ export class JuxCompiler {
|
|
|
116
93
|
}
|
|
117
94
|
}
|
|
118
95
|
|
|
119
|
-
// Get the rest of the code (everything after imports)
|
|
120
96
|
const restOfCode = content.substring(lastImportEnd).trim();
|
|
121
97
|
|
|
122
|
-
// Build wrapped content
|
|
123
98
|
wrappedContent = [
|
|
124
99
|
...imports,
|
|
125
100
|
'',
|
|
@@ -129,17 +104,11 @@ export class JuxCompiler {
|
|
|
129
104
|
].join('\n');
|
|
130
105
|
|
|
131
106
|
} catch (parseError) {
|
|
132
|
-
// Fallback: if parsing fails, just wrap the whole thing
|
|
133
107
|
console.warn(`⚠️ Could not parse ${relativePath}, using basic wrapping`);
|
|
134
108
|
wrappedContent = `export default async function() {\n${content}\n}`;
|
|
135
109
|
}
|
|
136
110
|
|
|
137
|
-
views.push({
|
|
138
|
-
name,
|
|
139
|
-
file: relativePath,
|
|
140
|
-
content: wrappedContent,
|
|
141
|
-
originalContent: content
|
|
142
|
-
});
|
|
111
|
+
views.push({ name, file: relativePath, content: wrappedContent, originalContent: content });
|
|
143
112
|
}
|
|
144
113
|
}
|
|
145
114
|
}
|
|
@@ -222,7 +191,6 @@ export class JuxCompiler {
|
|
|
222
191
|
}
|
|
223
192
|
});
|
|
224
193
|
|
|
225
|
-
// Default known facades/components if load fails
|
|
226
194
|
const knownComponents = this._juxscriptExports || ['element', 'input', 'buttonGroup'];
|
|
227
195
|
|
|
228
196
|
walk(ast, {
|
|
@@ -245,15 +213,11 @@ export class JuxCompiler {
|
|
|
245
213
|
return issues;
|
|
246
214
|
}
|
|
247
215
|
|
|
248
|
-
/**
|
|
249
|
-
* Generate entry point - inline EVERYTHING
|
|
250
|
-
*/
|
|
251
216
|
generateEntryPoint(views, dataModules, sharedModules) {
|
|
252
217
|
let entry = `// Auto-generated JUX entry point\n\n`;
|
|
253
218
|
const sourceSnapshot = {};
|
|
254
219
|
|
|
255
|
-
|
|
256
|
-
const allImports = new Map(); // Map<resolvedPath, Set<importedNames>>
|
|
220
|
+
const allImports = new Map();
|
|
257
221
|
const allFiles = [...views, ...sharedModules, ...dataModules];
|
|
258
222
|
|
|
259
223
|
allFiles.forEach(file => {
|
|
@@ -267,28 +231,22 @@ export class JuxCompiler {
|
|
|
267
231
|
locations: true
|
|
268
232
|
});
|
|
269
233
|
|
|
270
|
-
// Extract import statements
|
|
271
234
|
ast.body.filter(node => node.type === 'ImportDeclaration').forEach(node => {
|
|
272
235
|
const importPath = node.source.value;
|
|
273
236
|
|
|
274
|
-
// ✅ Resolve the import path
|
|
275
237
|
let resolvedImportPath;
|
|
276
238
|
if (importPath.startsWith('.')) {
|
|
277
|
-
// Relative import
|
|
278
239
|
const absolutePath = path.resolve(path.dirname(filePath), importPath);
|
|
279
240
|
resolvedImportPath = path.relative(this.srcDir, absolutePath);
|
|
280
241
|
resolvedImportPath = resolvedImportPath.replace(/\\/g, '/');
|
|
281
242
|
} else {
|
|
282
|
-
// Module import like 'juxscript', 'axios'
|
|
283
243
|
resolvedImportPath = importPath;
|
|
284
244
|
}
|
|
285
245
|
|
|
286
|
-
// Initialize Set for this path
|
|
287
246
|
if (!allImports.has(resolvedImportPath)) {
|
|
288
247
|
allImports.set(resolvedImportPath, new Set());
|
|
289
248
|
}
|
|
290
249
|
|
|
291
|
-
// Add imported names
|
|
292
250
|
node.specifiers.forEach(spec => {
|
|
293
251
|
if (spec.type === 'ImportSpecifier') {
|
|
294
252
|
allImports.get(resolvedImportPath).add(spec.imported.name);
|
|
@@ -302,16 +260,13 @@ export class JuxCompiler {
|
|
|
302
260
|
}
|
|
303
261
|
});
|
|
304
262
|
|
|
305
|
-
// ✅ Filter out imports that point to our own files (these will be inlined)
|
|
306
263
|
const externalImports = new Map();
|
|
307
264
|
allImports.forEach((names, resolvedPath) => {
|
|
308
|
-
// Only include if it's NOT a file in our src directory
|
|
309
265
|
if (!resolvedPath.endsWith('.js') && !resolvedPath.endsWith('.jux')) {
|
|
310
266
|
externalImports.set(resolvedPath, names);
|
|
311
267
|
}
|
|
312
268
|
});
|
|
313
269
|
|
|
314
|
-
// ✅ Write external imports only (juxscript, axios, etc.)
|
|
315
270
|
externalImports.forEach((names, importPath) => {
|
|
316
271
|
const namedImports = Array.from(names).filter(n => !n.startsWith('default:'));
|
|
317
272
|
const defaultImports = Array.from(names).filter(n => n.startsWith('default:')).map(n => n.split(':')[1]);
|
|
@@ -329,7 +284,6 @@ export class JuxCompiler {
|
|
|
329
284
|
|
|
330
285
|
entry += '\n';
|
|
331
286
|
|
|
332
|
-
// ✅ Inline shared modules as constants/functions
|
|
333
287
|
entry += `// --- SHARED MODULES (INLINED) ---\n`;
|
|
334
288
|
sharedModules.forEach(m => {
|
|
335
289
|
sourceSnapshot[m.file] = {
|
|
@@ -339,14 +293,12 @@ export class JuxCompiler {
|
|
|
339
293
|
lines: m.content.split('\n')
|
|
340
294
|
};
|
|
341
295
|
|
|
342
|
-
// Remove imports and exports, just keep the code
|
|
343
296
|
let code = m.originalContent || m.content;
|
|
344
297
|
code = this._stripImportsAndExports(code);
|
|
345
298
|
|
|
346
299
|
entry += `\n// From: ${m.file}\n${code}\n`;
|
|
347
300
|
});
|
|
348
301
|
|
|
349
|
-
// ✅ Inline data modules
|
|
350
302
|
entry += `\n// --- DATA MODULES (INLINED) ---\n`;
|
|
351
303
|
dataModules.forEach(m => {
|
|
352
304
|
sourceSnapshot[m.file] = {
|
|
@@ -362,39 +314,26 @@ export class JuxCompiler {
|
|
|
362
314
|
entry += `\n// From: ${m.file}\n${code}\n`;
|
|
363
315
|
});
|
|
364
316
|
|
|
365
|
-
// ✅ Expose everything to window (since we removed exports)
|
|
366
|
-
//entry += `\n// Expose to window\n`;
|
|
367
|
-
//entry += `Object.assign(window, { jux, state, registry, stateHistory, Link, link, navLink, externalLink, layer, overlay, VStack, HStack, ZStack, vstack, hstack, zstack });\n`;
|
|
368
|
-
|
|
369
317
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
370
318
|
|
|
371
319
|
const routeToFunctionMap = new Map();
|
|
372
320
|
|
|
373
|
-
// ✅ Process views: strip imports, inline as functions
|
|
374
321
|
views.forEach((v, index) => {
|
|
375
322
|
const functionName = `renderJux${index}`;
|
|
376
323
|
|
|
377
324
|
let codeBody = v.originalContent || v.content;
|
|
378
325
|
const originalLines = codeBody.split('\n');
|
|
379
326
|
codeBody = this._stripImportsAndExports(codeBody);
|
|
380
|
-
const strippedLines = codeBody.split('\n');
|
|
381
|
-
|
|
382
|
-
// Build a line map: for each stripped line, find its original line number
|
|
383
|
-
const lineMap = this._buildLineMap(originalLines, strippedLines);
|
|
384
327
|
|
|
385
328
|
sourceSnapshot[v.file] = {
|
|
386
329
|
name: v.name,
|
|
387
330
|
file: v.file,
|
|
388
|
-
content: v.originalContent || v.content,
|
|
389
331
|
lines: originalLines,
|
|
390
|
-
strippedLines: strippedLines,
|
|
391
|
-
lineMap: lineMap,
|
|
392
332
|
functionName
|
|
393
333
|
};
|
|
394
334
|
|
|
395
335
|
entry += `\nasync function ${functionName}() {\n${codeBody}\n}\n`;
|
|
396
336
|
|
|
397
|
-
// Generate route path
|
|
398
337
|
const routePath = v.name
|
|
399
338
|
.toLowerCase()
|
|
400
339
|
.replace(/\\/g, '/')
|
|
@@ -411,18 +350,17 @@ export class JuxCompiler {
|
|
|
411
350
|
routeToFunctionMap.set(`/${routePath}`, functionName);
|
|
412
351
|
});
|
|
413
352
|
|
|
414
|
-
// ✅ Generate router
|
|
415
353
|
entry += this._generateRouter(routeToFunctionMap);
|
|
416
354
|
|
|
417
355
|
this._sourceSnapshot = sourceSnapshot;
|
|
418
356
|
this._validationIssues = [];
|
|
419
357
|
|
|
358
|
+
// Embed source snapshot for runtime error overlay
|
|
359
|
+
entry += `\nwindow.__juxSources = ${JSON.stringify(sourceSnapshot)};\n`;
|
|
360
|
+
|
|
420
361
|
return entry;
|
|
421
362
|
}
|
|
422
363
|
|
|
423
|
-
/**
|
|
424
|
-
* Strip imports and exports from code, keeping the actual logic
|
|
425
|
-
*/
|
|
426
364
|
_stripImportsAndExports(code) {
|
|
427
365
|
try {
|
|
428
366
|
const ast = acorn.parse(code, {
|
|
@@ -431,7 +369,6 @@ export class JuxCompiler {
|
|
|
431
369
|
locations: true
|
|
432
370
|
});
|
|
433
371
|
|
|
434
|
-
// Remove imports and exports
|
|
435
372
|
const nodesToRemove = ast.body.filter(node =>
|
|
436
373
|
node.type === 'ImportDeclaration' ||
|
|
437
374
|
node.type === 'ExportNamedDeclaration' ||
|
|
@@ -443,7 +380,6 @@ export class JuxCompiler {
|
|
|
443
380
|
return code.trim();
|
|
444
381
|
}
|
|
445
382
|
|
|
446
|
-
// Remove nodes in reverse order
|
|
447
383
|
let result = code;
|
|
448
384
|
let offset = 0;
|
|
449
385
|
|
|
@@ -451,21 +387,17 @@ export class JuxCompiler {
|
|
|
451
387
|
let start = node.start - offset;
|
|
452
388
|
let end = node.end - offset;
|
|
453
389
|
|
|
454
|
-
// If it's an export with a declaration, keep the declaration
|
|
455
390
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
456
|
-
// Keep the declaration part, remove just "export"
|
|
457
391
|
const declarationStart = node.declaration.start - offset;
|
|
458
392
|
result = result.substring(0, start) + result.substring(declarationStart);
|
|
459
393
|
offset += (declarationStart - start);
|
|
460
394
|
} else if (node.type === 'ExportDefaultDeclaration') {
|
|
461
|
-
// Remove "export default", keep the expression
|
|
462
395
|
const exportKeyword = result.substring(start, end).match(/export\s+default\s+/);
|
|
463
396
|
if (exportKeyword) {
|
|
464
397
|
result = result.substring(0, start) + result.substring(start + exportKeyword[0].length);
|
|
465
398
|
offset += exportKeyword[0].length;
|
|
466
399
|
}
|
|
467
400
|
} else {
|
|
468
|
-
// Remove the entire node
|
|
469
401
|
result = result.substring(0, start) + result.substring(end);
|
|
470
402
|
offset += (end - start);
|
|
471
403
|
}
|
|
@@ -495,17 +427,11 @@ export class JuxCompiler {
|
|
|
495
427
|
}
|
|
496
428
|
fs.mkdirSync(this.distDir, { recursive: true });
|
|
497
429
|
|
|
498
|
-
// ✅ Copy public folder if exists
|
|
499
430
|
this.copyPublicFolder();
|
|
500
431
|
|
|
501
432
|
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
502
433
|
console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
|
|
503
434
|
|
|
504
|
-
// ❌ REMOVE: No need to copy files to dist - everything goes in bundle
|
|
505
|
-
// const juxDistDir = path.join(this.distDir, 'jux');
|
|
506
|
-
// fs.mkdirSync(juxDistDir, { recursive: true });
|
|
507
|
-
// ... copying logic removed ...
|
|
508
|
-
|
|
509
435
|
const entryContent = this.generateEntryPoint(views, dataModules, sharedModules);
|
|
510
436
|
|
|
511
437
|
const entryPath = path.join(this.distDir, 'entry.js');
|
|
@@ -515,12 +441,6 @@ export class JuxCompiler {
|
|
|
515
441
|
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
516
442
|
console.log(`📸 Source snapshot written`);
|
|
517
443
|
|
|
518
|
-
// ✅ Also write to dist root so the error overlay can fetch it via /__jux_sources.json
|
|
519
|
-
const distSnapshotPath = path.join(this.config.distDir, '__jux_sources.json');
|
|
520
|
-
if (distSnapshotPath !== snapshotPath) {
|
|
521
|
-
fs.writeFileSync(distSnapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
444
|
const validation = this.reportValidationIssues();
|
|
525
445
|
if (!validation.isValid) {
|
|
526
446
|
console.log('🛑 BUILD FAILED\n');
|
|
@@ -547,12 +467,10 @@ export class JuxCompiler {
|
|
|
547
467
|
plugins: [{
|
|
548
468
|
name: 'juxscript-resolver',
|
|
549
469
|
setup: (build) => {
|
|
550
|
-
// Resolve juxscript
|
|
551
470
|
build.onResolve({ filter: /^juxscript$/ }, () => ({
|
|
552
471
|
path: juxscriptPath
|
|
553
472
|
}));
|
|
554
473
|
|
|
555
|
-
// ✅ Force axios to resolve from project's node_modules
|
|
556
474
|
build.onResolve({ filter: /^axios$/ }, () => {
|
|
557
475
|
const projectRoot = process.cwd();
|
|
558
476
|
const axiosPath = path.resolve(projectRoot, 'node_modules/axios/dist/esm/axios.js');
|
|
@@ -566,30 +484,22 @@ export class JuxCompiler {
|
|
|
566
484
|
return null;
|
|
567
485
|
});
|
|
568
486
|
|
|
569
|
-
// ✅ Resolve .jux file imports - with detailed logging
|
|
570
487
|
build.onResolve({ filter: /\.jux$/ }, (args) => {
|
|
571
488
|
console.log(`🔍 Resolving: ${args.path} from ${args.importer}`);
|
|
572
489
|
|
|
573
|
-
// Skip if already resolved
|
|
574
490
|
if (path.isAbsolute(args.path)) {
|
|
575
491
|
return { path: args.path };
|
|
576
492
|
}
|
|
577
493
|
|
|
578
|
-
// Handle relative imports
|
|
579
494
|
if (args.path.startsWith('.')) {
|
|
580
495
|
const importer = args.importer || entryPath;
|
|
581
496
|
const importerDir = path.dirname(importer);
|
|
582
497
|
const resolvedPath = path.resolve(importerDir, args.path);
|
|
583
498
|
|
|
584
|
-
console.log(` Importer dir: ${importerDir}`);
|
|
585
|
-
console.log(` Resolved to: ${resolvedPath}`);
|
|
586
|
-
console.log(` Exists: ${fs.existsSync(resolvedPath)}`);
|
|
587
|
-
|
|
588
499
|
if (fs.existsSync(resolvedPath)) {
|
|
589
500
|
return { path: resolvedPath };
|
|
590
501
|
} else {
|
|
591
502
|
console.error(`❌ Could not resolve ${args.path} from ${importer}`);
|
|
592
|
-
console.error(` Tried: ${resolvedPath}`);
|
|
593
503
|
}
|
|
594
504
|
}
|
|
595
505
|
|
|
@@ -632,8 +542,7 @@ export class JuxCompiler {
|
|
|
632
542
|
<title>JUX Application</title>
|
|
633
543
|
${generateErrorCollector({
|
|
634
544
|
enabled: process.env.NODE_ENV !== 'production',
|
|
635
|
-
maxErrors: 50
|
|
636
|
-
position: 'bottom-right'
|
|
545
|
+
maxErrors: 50
|
|
637
546
|
})}
|
|
638
547
|
<script type="module" src="/bundle.js"></script>
|
|
639
548
|
</head>
|
|
@@ -652,9 +561,6 @@ export class JuxCompiler {
|
|
|
652
561
|
return { success: true, errors: [], warnings: validation.warnings };
|
|
653
562
|
}
|
|
654
563
|
|
|
655
|
-
/**
|
|
656
|
-
* Generate router code
|
|
657
|
-
*/
|
|
658
564
|
_generateRouter(routeToFunctionMap) {
|
|
659
565
|
let router = `\n// --- ROUTER ---\n`;
|
|
660
566
|
router += `const routes = {\n`;
|
|
@@ -668,14 +574,12 @@ export class JuxCompiler {
|
|
|
668
574
|
router += `function route(path) {
|
|
669
575
|
const renderFn = routes[path] || routes['/'];
|
|
670
576
|
if (renderFn) {
|
|
671
|
-
// Clear main content area
|
|
672
577
|
const appMain = document.getElementById('appmain-content');
|
|
673
578
|
if (appMain) appMain.innerHTML = '';
|
|
674
579
|
|
|
675
580
|
const app = document.getElementById('app');
|
|
676
581
|
if (app) app.innerHTML = '';
|
|
677
582
|
|
|
678
|
-
// Call render function
|
|
679
583
|
if (typeof renderFn === 'function') {
|
|
680
584
|
renderFn();
|
|
681
585
|
}
|
|
@@ -685,15 +589,12 @@ export class JuxCompiler {
|
|
|
685
589
|
}
|
|
686
590
|
}
|
|
687
591
|
|
|
688
|
-
// Initial route
|
|
689
592
|
window.addEventListener('DOMContentLoaded', () => {
|
|
690
593
|
route(window.location.pathname);
|
|
691
594
|
});
|
|
692
595
|
|
|
693
|
-
// Handle navigation
|
|
694
596
|
window.addEventListener('popstate', () => route(window.location.pathname));
|
|
695
597
|
|
|
696
|
-
// Intercept link clicks for SPA navigation
|
|
697
598
|
document.addEventListener('click', (e) => {
|
|
698
599
|
const link = e.target.closest('a[href]');
|
|
699
600
|
if (link) {
|
|
@@ -706,7 +607,6 @@ document.addEventListener('click', (e) => {
|
|
|
706
607
|
}
|
|
707
608
|
});
|
|
708
609
|
|
|
709
|
-
// Expose router for manual navigation
|
|
710
610
|
window.navigateTo = (path) => {
|
|
711
611
|
window.history.pushState({}, '', path);
|
|
712
612
|
route(path);
|
|
@@ -716,14 +616,10 @@ window.navigateTo = (path) => {
|
|
|
716
616
|
return router;
|
|
717
617
|
}
|
|
718
618
|
|
|
719
|
-
/**
|
|
720
|
-
* Report validation issues
|
|
721
|
-
*/
|
|
722
619
|
reportValidationIssues() {
|
|
723
620
|
const errors = [];
|
|
724
621
|
const warnings = [];
|
|
725
622
|
|
|
726
|
-
// Check if there are any validation issues collected
|
|
727
623
|
if (this._validationIssues && this._validationIssues.length > 0) {
|
|
728
624
|
this._validationIssues.forEach(issue => {
|
|
729
625
|
if (issue.type === 'error') {
|
|
@@ -734,7 +630,6 @@ window.navigateTo = (path) => {
|
|
|
734
630
|
});
|
|
735
631
|
}
|
|
736
632
|
|
|
737
|
-
// Log warnings
|
|
738
633
|
if (warnings.length > 0) {
|
|
739
634
|
console.log('\n⚠️ Warnings:\n');
|
|
740
635
|
warnings.forEach(w => {
|
|
@@ -742,7 +637,6 @@ window.navigateTo = (path) => {
|
|
|
742
637
|
});
|
|
743
638
|
}
|
|
744
639
|
|
|
745
|
-
// Log errors
|
|
746
640
|
if (errors.length > 0) {
|
|
747
641
|
console.log('\n❌ Errors:\n');
|
|
748
642
|
errors.forEach(e => {
|
|
@@ -750,24 +644,16 @@ window.navigateTo = (path) => {
|
|
|
750
644
|
});
|
|
751
645
|
}
|
|
752
646
|
|
|
753
|
-
return {
|
|
754
|
-
isValid: errors.length === 0,
|
|
755
|
-
errors,
|
|
756
|
-
warnings
|
|
757
|
-
};
|
|
647
|
+
return { isValid: errors.length === 0, errors, warnings };
|
|
758
648
|
}
|
|
759
649
|
|
|
760
|
-
/**
|
|
761
|
-
* Copy public folder contents to dist
|
|
762
|
-
*/
|
|
763
650
|
copyPublicFolder() {
|
|
764
|
-
// ✅ Use configured public path or resolve from paths object
|
|
765
651
|
const publicSrc = this.paths.public
|
|
766
652
|
? this.paths.public
|
|
767
653
|
: path.resolve(process.cwd(), this.publicDir);
|
|
768
654
|
|
|
769
655
|
if (!fs.existsSync(publicSrc)) {
|
|
770
|
-
return;
|
|
656
|
+
return;
|
|
771
657
|
}
|
|
772
658
|
|
|
773
659
|
console.log('📦 Copying public assets...');
|
|
@@ -780,30 +666,23 @@ window.navigateTo = (path) => {
|
|
|
780
666
|
}
|
|
781
667
|
}
|
|
782
668
|
|
|
783
|
-
/**
|
|
784
|
-
* Recursively copy directory contents
|
|
785
|
-
*/
|
|
786
669
|
_copyDirRecursive(src, dest, depth = 0) {
|
|
787
670
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
788
671
|
|
|
789
672
|
entries.forEach(entry => {
|
|
790
|
-
// Skip hidden files and directories
|
|
791
673
|
if (entry.name.startsWith('.')) return;
|
|
792
674
|
|
|
793
675
|
const srcPath = path.join(src, entry.name);
|
|
794
676
|
const destPath = path.join(dest, entry.name);
|
|
795
677
|
|
|
796
678
|
if (entry.isDirectory()) {
|
|
797
|
-
// Create directory and recurse
|
|
798
679
|
if (!fs.existsSync(destPath)) {
|
|
799
680
|
fs.mkdirSync(destPath, { recursive: true });
|
|
800
681
|
}
|
|
801
682
|
this._copyDirRecursive(srcPath, destPath, depth + 1);
|
|
802
683
|
} else {
|
|
803
|
-
// Copy file
|
|
804
684
|
fs.copyFileSync(srcPath, destPath);
|
|
805
685
|
|
|
806
|
-
// Log files at root level only
|
|
807
686
|
if (depth === 0) {
|
|
808
687
|
const ext = path.extname(entry.name);
|
|
809
688
|
const icon = this._getFileIcon(ext);
|
|
@@ -813,76 +692,22 @@ window.navigateTo = (path) => {
|
|
|
813
692
|
});
|
|
814
693
|
}
|
|
815
694
|
|
|
816
|
-
/**
|
|
817
|
-
* Get icon for file type
|
|
818
|
-
*/
|
|
819
695
|
_getFileIcon(ext) {
|
|
820
696
|
const icons = {
|
|
821
|
-
'.html': '📄',
|
|
822
|
-
'.
|
|
823
|
-
'.
|
|
824
|
-
'.
|
|
825
|
-
'.png': '🖼️',
|
|
826
|
-
'.jpg': '🖼️',
|
|
827
|
-
'.jpeg': '🖼️',
|
|
828
|
-
'.gif': '🖼️',
|
|
829
|
-
'.svg': '🎨',
|
|
830
|
-
'.ico': '🔖',
|
|
831
|
-
'.woff': '🔤',
|
|
832
|
-
'.woff2': '🔤',
|
|
833
|
-
'.ttf': '🔤',
|
|
834
|
-
'.eot': '🔤'
|
|
697
|
+
'.html': '📄', '.css': '🎨', '.js': '📜', '.json': '📋',
|
|
698
|
+
'.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️',
|
|
699
|
+
'.svg': '🎨', '.ico': '🔖',
|
|
700
|
+
'.woff': '🔤', '.woff2': '🔤', '.ttf': '🔤', '.eot': '🔤'
|
|
835
701
|
};
|
|
836
702
|
return icons[ext.toLowerCase()] || '📦';
|
|
837
703
|
}
|
|
838
704
|
|
|
839
|
-
/**
|
|
840
|
-
* ✅ Generate valid JavaScript identifier from file path
|
|
841
|
-
* Example: abc/aaa.jux -> abc_aaa
|
|
842
|
-
* Example: menus/main.jux -> menus_main
|
|
843
|
-
* Example: pages/blog/post.jux -> pages_blog_post
|
|
844
|
-
*/
|
|
845
705
|
_generateNameFromPath(filepath) {
|
|
846
706
|
return filepath
|
|
847
|
-
.replace(/\.jux$/, '')
|
|
848
|
-
.replace(/[\/\\]/g, '_')
|
|
849
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
850
|
-
.replace(/_+/g, '_')
|
|
851
|
-
.replace(/^_|_$/g, '');
|
|
707
|
+
.replace(/\.jux$/, '')
|
|
708
|
+
.replace(/[\/\\]/g, '_')
|
|
709
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
710
|
+
.replace(/_+/g, '_')
|
|
711
|
+
.replace(/^_|_$/g, '');
|
|
852
712
|
}
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* Build a mapping from stripped line index to original source line number.
|
|
856
|
-
* For each line in strippedLines, find the best matching line in originalLines.
|
|
857
|
-
*/
|
|
858
|
-
_buildLineMap(originalLines, strippedLines) {
|
|
859
|
-
const lineMap = [];
|
|
860
|
-
let searchFrom = 0;
|
|
861
|
-
|
|
862
|
-
for (let i = 0; i < strippedLines.length; i++) {
|
|
863
|
-
const stripped = strippedLines[i].trim();
|
|
864
|
-
if (!stripped) {
|
|
865
|
-
// Empty line — estimate position
|
|
866
|
-
lineMap.push(searchFrom < originalLines.length ? searchFrom + 1 : originalLines.length);
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
let found = false;
|
|
871
|
-
for (let j = searchFrom; j < originalLines.length; j++) {
|
|
872
|
-
if (originalLines[j].trim() === stripped) {
|
|
873
|
-
lineMap.push(j + 1); // 1-based
|
|
874
|
-
searchFrom = j + 1;
|
|
875
|
-
found = true;
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (!found) {
|
|
881
|
-
// Fallback: use last known position
|
|
882
|
-
lineMap.push(searchFrom < originalLines.length ? searchFrom + 1 : originalLines.length);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
return lineMap;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
713
|
+
}
|
package/machinery/errors.js
CHANGED
|
@@ -1,383 +1,109 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Resolves bundle line numbers back to original .jux source using __jux_sources.json.
|
|
5
|
-
*
|
|
6
|
-
* @param {Object} options
|
|
7
|
-
* @param {boolean} [options.enabled=true]
|
|
8
|
-
* @param {number} [options.maxErrors=50]
|
|
9
|
-
* @returns {string} Inline script to inject into HTML
|
|
2
|
+
* Injectable client-side error overlay.
|
|
3
|
+
* Matches renderJuxN in stack traces to show original .jux source.
|
|
10
4
|
*/
|
|
11
5
|
export function generateErrorCollector(options = {}) {
|
|
12
|
-
const {
|
|
13
|
-
enabled = true,
|
|
14
|
-
maxErrors = 50
|
|
15
|
-
} = options;
|
|
16
|
-
|
|
6
|
+
const { enabled = true, maxErrors = 50 } = options;
|
|
17
7
|
if (!enabled) return '';
|
|
18
8
|
|
|
19
9
|
return `
|
|
20
10
|
<script>
|
|
21
11
|
(function() {
|
|
22
12
|
var __errors = [];
|
|
23
|
-
var __maxErrors = ${maxErrors};
|
|
24
13
|
var __overlay = null;
|
|
25
14
|
var __list = null;
|
|
26
|
-
var __sources = null;
|
|
27
|
-
var __sourcesFetched = false;
|
|
28
|
-
var __bundleLines = null;
|
|
29
|
-
|
|
30
|
-
// Fetch source map + bundle on first error
|
|
31
|
-
function loadSources(cb) {
|
|
32
|
-
if (__sourcesFetched) return cb();
|
|
33
|
-
__sourcesFetched = true;
|
|
34
|
-
|
|
35
|
-
var done = 0;
|
|
36
|
-
function check() { if (++done >= 2) cb(); }
|
|
37
|
-
|
|
38
|
-
// Fetch source snapshot
|
|
39
|
-
var xhr1 = new XMLHttpRequest();
|
|
40
|
-
xhr1.open('GET', '/__jux_sources.json', true);
|
|
41
|
-
xhr1.onload = function() {
|
|
42
|
-
try { __sources = JSON.parse(xhr1.responseText); } catch(e) {}
|
|
43
|
-
check();
|
|
44
|
-
};
|
|
45
|
-
xhr1.onerror = check;
|
|
46
|
-
xhr1.send();
|
|
47
15
|
|
|
48
|
-
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
xhr2.onerror = check;
|
|
56
|
-
xhr2.send();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Given a bundle line number, find which view function it belongs to
|
|
60
|
-
// and map back to the original .jux source line
|
|
61
|
-
function resolveSourceLocation(bundleLine) {
|
|
62
|
-
if (!__sources || !__bundleLines || !bundleLine) return null;
|
|
63
|
-
|
|
64
|
-
var funcName = null;
|
|
65
|
-
var funcStartLine = 0;
|
|
66
|
-
for (var i = bundleLine - 1; i >= 0; i--) {
|
|
67
|
-
var line = __bundleLines[i];
|
|
68
|
-
if (!line) continue;
|
|
69
|
-
var match = line.match(/async\\s+function\\s+(renderJux\\d+)/);
|
|
70
|
-
if (match) {
|
|
71
|
-
funcName = match[1];
|
|
72
|
-
funcStartLine = i + 1;
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
16
|
+
function findSource(stack) {
|
|
17
|
+
var sources = window.__juxSources;
|
|
18
|
+
if (!sources || !stack) return null;
|
|
19
|
+
var m = stack.match(/renderJux\\d+/);
|
|
20
|
+
if (!m) return null;
|
|
21
|
+
for (var key in sources) {
|
|
22
|
+
if (sources[key].functionName === m[0]) return sources[key];
|
|
75
23
|
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
76
26
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
var sourceEntry = null;
|
|
80
|
-
for (var key in __sources) {
|
|
81
|
-
if (__sources[key].functionName === funcName) {
|
|
82
|
-
sourceEntry = __sources[key];
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!sourceEntry) return null;
|
|
88
|
-
|
|
89
|
-
// Index into the function body (0-based)
|
|
90
|
-
var offsetInFunc = bundleLine - funcStartLine - 1;
|
|
91
|
-
var lineMap = sourceEntry.lineMap || [];
|
|
92
|
-
var originalLines = sourceEntry.lines || [];
|
|
93
|
-
|
|
94
|
-
// Use lineMap for accurate resolution
|
|
95
|
-
var originalLineNum;
|
|
96
|
-
if (lineMap.length > 0 && offsetInFunc >= 0 && offsetInFunc < lineMap.length) {
|
|
97
|
-
originalLineNum = lineMap[offsetInFunc]; // already 1-based
|
|
98
|
-
} else {
|
|
99
|
-
// Fallback: direct offset
|
|
100
|
-
originalLineNum = Math.max(1, Math.min(offsetInFunc + 1, originalLines.length));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
var lineIndex = originalLineNum - 1;
|
|
104
|
-
var contextRadius = 3;
|
|
105
|
-
var contextStart = Math.max(0, lineIndex - contextRadius);
|
|
106
|
-
var contextEnd = Math.min(originalLines.length, lineIndex + contextRadius + 1);
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
file: sourceEntry.file,
|
|
110
|
-
name: sourceEntry.name,
|
|
111
|
-
functionName: funcName,
|
|
112
|
-
originalLine: originalLineNum,
|
|
113
|
-
originalCode: originalLines[lineIndex] || '',
|
|
114
|
-
context: originalLines.slice(contextStart, contextEnd),
|
|
115
|
-
contextStartLine: contextStart + 1,
|
|
116
|
-
highlightLine: originalLineNum
|
|
117
|
-
};
|
|
27
|
+
function escapeHtml(s) {
|
|
28
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
118
29
|
}
|
|
119
30
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
var
|
|
124
|
-
|
|
31
|
+
function renderSource(src) {
|
|
32
|
+
if (!src) return '';
|
|
33
|
+
var lines = src.lines || [];
|
|
34
|
+
var html = '<div style="margin-top:8px;padding:8px;background:#111;border:1px solid #333;border-radius:4px;">';
|
|
35
|
+
html += '<div style="color:#ff5555;font-size:12px;margin-bottom:6px;font-weight:bold;">' + escapeHtml(src.file) + '</div>';
|
|
125
36
|
for (var i = 0; i < lines.length; i++) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
frames.push({
|
|
130
|
-
raw: lines[i].trim(),
|
|
131
|
-
func: m[1] || '(anonymous)',
|
|
132
|
-
line: parseInt(m[2], 10),
|
|
133
|
-
col: parseInt(m[3], 10)
|
|
134
|
-
});
|
|
135
|
-
}
|
|
37
|
+
html += '<div style="padding:1px 8px;white-space:pre;">' +
|
|
38
|
+
'<span style="color:#555;display:inline-block;width:32px;text-align:right;margin-right:12px;user-select:none;">' + (i+1) + '</span>' +
|
|
39
|
+
'<span style="color:#ccc;">' + escapeHtml(lines[i]) + '</span></div>';
|
|
136
40
|
}
|
|
137
|
-
|
|
41
|
+
html += '</div>';
|
|
42
|
+
return html;
|
|
138
43
|
}
|
|
139
44
|
|
|
140
45
|
function createOverlay() {
|
|
141
46
|
__overlay = document.createElement('div');
|
|
142
|
-
__overlay.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'padding:0',
|
|
156
|
-
'margin:0',
|
|
157
|
-
'box-sizing:border-box'
|
|
158
|
-
].join(';'));
|
|
159
|
-
|
|
160
|
-
var container = document.createElement('div');
|
|
161
|
-
container.setAttribute('style', [
|
|
162
|
-
'max-width:960px',
|
|
163
|
-
'margin:40px auto',
|
|
164
|
-
'padding:24px 32px',
|
|
165
|
-
'border:2px solid #ff5555',
|
|
166
|
-
'border-radius:8px',
|
|
167
|
-
'background:#1a1a1a'
|
|
168
|
-
].join(';'));
|
|
169
|
-
|
|
170
|
-
var header = document.createElement('div');
|
|
171
|
-
header.setAttribute('style', [
|
|
172
|
-
'display:flex',
|
|
173
|
-
'justify-content:space-between',
|
|
174
|
-
'align-items:center',
|
|
175
|
-
'margin-bottom:16px',
|
|
176
|
-
'padding-bottom:12px',
|
|
177
|
-
'border-bottom:1px solid #333'
|
|
178
|
-
].join(';'));
|
|
179
|
-
|
|
180
|
-
var title = document.createElement('div');
|
|
181
|
-
title.setAttribute('style', 'color:#ff5555;font-size:16px;font-weight:bold;');
|
|
182
|
-
title.textContent = 'Runtime Error';
|
|
183
|
-
header.appendChild(title);
|
|
184
|
-
|
|
185
|
-
var actions = document.createElement('div');
|
|
186
|
-
actions.setAttribute('style', 'display:flex;gap:8px;');
|
|
187
|
-
|
|
188
|
-
var clearBtn = document.createElement('button');
|
|
189
|
-
clearBtn.textContent = 'Clear';
|
|
190
|
-
clearBtn.setAttribute('style', [
|
|
191
|
-
'background:transparent',
|
|
192
|
-
'border:1px solid #555',
|
|
193
|
-
'color:#aaa',
|
|
194
|
-
'padding:4px 12px',
|
|
195
|
-
'border-radius:4px',
|
|
196
|
-
'cursor:pointer',
|
|
197
|
-
'font-family:monospace',
|
|
198
|
-
'font-size:12px'
|
|
199
|
-
].join(';'));
|
|
200
|
-
clearBtn.addEventListener('click', clearErrors);
|
|
201
|
-
|
|
202
|
-
var closeBtn = document.createElement('button');
|
|
203
|
-
closeBtn.textContent = 'Close';
|
|
204
|
-
closeBtn.setAttribute('style', [
|
|
205
|
-
'background:transparent',
|
|
206
|
-
'border:1px solid #555',
|
|
207
|
-
'color:#aaa',
|
|
208
|
-
'padding:4px 12px',
|
|
209
|
-
'border-radius:4px',
|
|
210
|
-
'cursor:pointer',
|
|
211
|
-
'font-family:monospace',
|
|
212
|
-
'font-size:12px'
|
|
213
|
-
].join(';'));
|
|
214
|
-
closeBtn.addEventListener('click', function() {
|
|
215
|
-
__overlay.style.display = 'none';
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
actions.appendChild(clearBtn);
|
|
219
|
-
actions.appendChild(closeBtn);
|
|
220
|
-
header.appendChild(actions);
|
|
221
|
-
container.appendChild(header);
|
|
222
|
-
|
|
47
|
+
__overlay.setAttribute('style','position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:99999;background:rgba(0,0,0,0.85);color:#f8f8f8;font-family:monospace;font-size:14px;overflow-y:auto;');
|
|
48
|
+
var c = document.createElement('div');
|
|
49
|
+
c.setAttribute('style','max-width:960px;margin:40px auto;padding:24px 32px;border:2px solid #ff5555;border-radius:8px;background:#1a1a1a;');
|
|
50
|
+
var h = document.createElement('div');
|
|
51
|
+
h.setAttribute('style','display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #333;');
|
|
52
|
+
var t = document.createElement('div');
|
|
53
|
+
t.setAttribute('style','color:#ff5555;font-size:16px;font-weight:bold;');
|
|
54
|
+
t.textContent = 'Runtime Error';
|
|
55
|
+
var btn = document.createElement('button');
|
|
56
|
+
btn.textContent = 'Close';
|
|
57
|
+
btn.setAttribute('style','background:transparent;border:1px solid #555;color:#aaa;padding:4px 12px;border-radius:4px;cursor:pointer;font-family:monospace;font-size:12px;');
|
|
58
|
+
btn.addEventListener('click', function() { __overlay.style.display = 'none'; });
|
|
59
|
+
h.appendChild(t); h.appendChild(btn); c.appendChild(h);
|
|
223
60
|
__list = document.createElement('div');
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
__overlay.appendChild(container);
|
|
61
|
+
c.appendChild(__list); __overlay.appendChild(c);
|
|
227
62
|
document.body.appendChild(__overlay);
|
|
228
63
|
}
|
|
229
64
|
|
|
230
|
-
function
|
|
231
|
-
__errors
|
|
232
|
-
|
|
233
|
-
if (__overlay) __overlay.style.display = 'none';
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function renderSourceContext(resolved) {
|
|
237
|
-
if (!resolved) return '';
|
|
238
|
-
|
|
239
|
-
var lines = resolved.context || [];
|
|
240
|
-
var startLine = resolved.contextStartLine || 1;
|
|
241
|
-
var highlight = resolved.highlightLine || -1;
|
|
242
|
-
|
|
243
|
-
var html = '<div style="margin-top:8px;padding:8px;background:#111;border:1px solid #333;border-radius:4px;">';
|
|
244
|
-
html += '<div style="color:#ff5555;font-size:12px;margin-bottom:6px;font-weight:bold;">' +
|
|
245
|
-
escapeHtml(resolved.file) + ':' + resolved.originalLine +
|
|
246
|
-
'</div>';
|
|
247
|
-
|
|
248
|
-
for (var i = 0; i < lines.length; i++) {
|
|
249
|
-
var lineNum = startLine + i;
|
|
250
|
-
var isHighlight = lineNum === highlight;
|
|
251
|
-
var bg = isHighlight ? 'background:#3a1a1a;' : '';
|
|
252
|
-
var color = isHighlight ? 'color:#ff8888;' : 'color:#888;';
|
|
253
|
-
var numColor = isHighlight ? 'color:#ff5555;' : 'color:#555;';
|
|
254
|
-
var marker = isHighlight ? '>' : ' ';
|
|
255
|
-
|
|
256
|
-
html += '<div style="' + bg + 'padding:1px 8px;white-space:pre;">' +
|
|
257
|
-
'<span style="' + numColor + 'display:inline-block;width:40px;text-align:right;margin-right:12px;user-select:none;">' +
|
|
258
|
-
marker + ' ' + lineNum +
|
|
259
|
-
'</span>' +
|
|
260
|
-
'<span style="' + color + '">' + escapeHtml(lines[i]) + '</span>' +
|
|
261
|
-
'</div>';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
html += '</div>';
|
|
265
|
-
return html;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function addError(type, message, source, line, col, stack) {
|
|
269
|
-
if (__errors.length >= __maxErrors) __errors.shift();
|
|
270
|
-
|
|
271
|
-
var entry = {
|
|
272
|
-
type: type,
|
|
273
|
-
message: String(message || ''),
|
|
274
|
-
source: source || '',
|
|
275
|
-
line: line || 0,
|
|
276
|
-
col: col || 0,
|
|
277
|
-
stack: stack || '',
|
|
278
|
-
time: new Date().toLocaleTimeString()
|
|
279
|
-
};
|
|
280
|
-
__errors.push(entry);
|
|
281
|
-
|
|
65
|
+
function addError(type, message, stack) {
|
|
66
|
+
if (__errors.length >= ${maxErrors}) __errors.shift();
|
|
67
|
+
__errors.push({ type: type, message: message, stack: stack });
|
|
282
68
|
if (!__overlay) createOverlay();
|
|
283
69
|
__overlay.style.display = 'block';
|
|
284
70
|
|
|
285
71
|
var item = document.createElement('div');
|
|
286
|
-
item.setAttribute('style',
|
|
287
|
-
'padding:12px 16px',
|
|
288
|
-
'margin-bottom:8px',
|
|
289
|
-
'background:#222',
|
|
290
|
-
'border-left:3px solid #ff5555',
|
|
291
|
-
'border-radius:4px'
|
|
292
|
-
].join(';'));
|
|
72
|
+
item.setAttribute('style','padding:12px 16px;margin-bottom:8px;background:#222;border-left:3px solid #ff5555;border-radius:4px;');
|
|
293
73
|
|
|
294
|
-
var
|
|
74
|
+
var label = type === 'error' ? 'Error' : type === 'rejection' ? 'Unhandled Rejection' : 'console.error';
|
|
75
|
+
item.innerHTML = '<div style="color:#888;font-size:12px;margin-bottom:4px;">' + label + '</div>' +
|
|
76
|
+
'<div style="color:#ff8888;font-size:14px;white-space:pre-wrap;word-break:break-word;font-weight:bold;">' + escapeHtml(message) + '</div>';
|
|
295
77
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
typeLabel +
|
|
299
|
-
'<span style="float:right;">' + entry.time + '</span>' +
|
|
300
|
-
'</div>' +
|
|
301
|
-
'<div style="color:#ff8888;font-size:14px;white-space:pre-wrap;word-break:break-word;font-weight:bold;">' +
|
|
302
|
-
escapeHtml(entry.message) +
|
|
303
|
-
'</div>';
|
|
78
|
+
var src = findSource(stack);
|
|
79
|
+
if (src) item.innerHTML += renderSource(src);
|
|
304
80
|
|
|
81
|
+
if (stack) {
|
|
82
|
+
var d = document.createElement('details');
|
|
83
|
+
d.setAttribute('style','margin-top:8px;');
|
|
84
|
+
d.innerHTML = '<summary style="cursor:pointer;color:#555;font-size:12px;">Stack trace</summary>' +
|
|
85
|
+
'<pre style="margin:4px 0 0;padding:8px;font-size:12px;color:#666;white-space:pre-wrap;background:#111;border:1px solid #333;border-radius:4px;max-height:200px;overflow-y:auto;">' + escapeHtml(stack) + '</pre>';
|
|
86
|
+
item.appendChild(d);
|
|
87
|
+
}
|
|
305
88
|
__list.appendChild(item);
|
|
306
|
-
|
|
307
|
-
// Resolve source location asynchronously
|
|
308
|
-
loadSources(function() {
|
|
309
|
-
var frames = parseStack(entry.stack);
|
|
310
|
-
var resolved = null;
|
|
311
|
-
|
|
312
|
-
// Try each frame until we find one that maps to a .jux source
|
|
313
|
-
for (var i = 0; i < frames.length; i++) {
|
|
314
|
-
var r = resolveSourceLocation(frames[i].line);
|
|
315
|
-
if (r) {
|
|
316
|
-
resolved = r;
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Also try the direct line/col from the error itself
|
|
322
|
-
if (!resolved && entry.line) {
|
|
323
|
-
resolved = resolveSourceLocation(entry.line);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (resolved) {
|
|
327
|
-
// Add source context block
|
|
328
|
-
var sourceBlock = document.createElement('div');
|
|
329
|
-
sourceBlock.innerHTML = renderSourceContext(resolved);
|
|
330
|
-
item.appendChild(sourceBlock);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Add raw stack (collapsed) if present
|
|
334
|
-
if (entry.stack) {
|
|
335
|
-
var stackBlock = document.createElement('details');
|
|
336
|
-
stackBlock.setAttribute('style', 'margin-top:8px;');
|
|
337
|
-
stackBlock.innerHTML =
|
|
338
|
-
'<summary style="cursor:pointer;color:#555;font-size:12px;user-select:none;">Raw stack trace</summary>' +
|
|
339
|
-
'<pre style="margin:4px 0 0;padding:8px;font-size:12px;color:#666;' +
|
|
340
|
-
'white-space:pre-wrap;background:#111;border-radius:4px;' +
|
|
341
|
-
'border:1px solid #333;max-height:200px;overflow-y:auto;">' +
|
|
342
|
-
escapeHtml(entry.stack) +
|
|
343
|
-
'</pre>';
|
|
344
|
-
item.appendChild(stackBlock);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
89
|
}
|
|
348
90
|
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// --- Hooks ---
|
|
354
|
-
|
|
355
|
-
window.onerror = function(msg, source, line, col, err) {
|
|
356
|
-
addError('error', msg, source, line, col, err && err.stack ? err.stack : '');
|
|
91
|
+
window.onerror = function(msg, src, line, col, err) {
|
|
92
|
+
addError('error', msg, err && err.stack ? err.stack : '');
|
|
357
93
|
};
|
|
358
|
-
|
|
359
94
|
window.addEventListener('unhandledrejection', function(e) {
|
|
360
|
-
var
|
|
361
|
-
|
|
362
|
-
var stack = reason instanceof Error ? reason.stack : '';
|
|
363
|
-
addError('rejection', msg, '', 0, 0, stack);
|
|
95
|
+
var r = e.reason;
|
|
96
|
+
addError('rejection', r instanceof Error ? r.message : String(r), r instanceof Error ? r.stack : '');
|
|
364
97
|
});
|
|
365
|
-
|
|
366
|
-
var _origConsoleError = console.error;
|
|
98
|
+
var _ce = console.error;
|
|
367
99
|
console.error = function() {
|
|
368
|
-
var
|
|
369
|
-
|
|
370
|
-
return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
|
|
100
|
+
var msg = Array.prototype.slice.call(arguments).map(function(a) {
|
|
101
|
+
return typeof a === 'object' ? JSON.stringify(a) : String(a);
|
|
371
102
|
}).join(' ');
|
|
372
|
-
addError('console', msg);
|
|
373
|
-
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
window.__juxErrors = {
|
|
377
|
-
list: function() { return __errors.slice(); },
|
|
378
|
-
clear: clearErrors,
|
|
379
|
-
count: function() { return __errors.length; }
|
|
103
|
+
addError('console', msg, '');
|
|
104
|
+
_ce.apply(console, arguments);
|
|
380
105
|
};
|
|
106
|
+
window.__juxErrors = { list: function() { return __errors.slice(); }, count: function() { return __errors.length; } };
|
|
381
107
|
})();
|
|
382
108
|
</script>`;
|
|
383
109
|
}
|