juxscript 1.1.156 → 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 +27 -160
- package/machinery/errors.js +64 -333
- 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,32 +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
|
|
|
324
|
+
let codeBody = v.originalContent || v.content;
|
|
325
|
+
const originalLines = codeBody.split('\n');
|
|
326
|
+
codeBody = this._stripImportsAndExports(codeBody);
|
|
327
|
+
|
|
377
328
|
sourceSnapshot[v.file] = {
|
|
378
329
|
name: v.name,
|
|
379
330
|
file: v.file,
|
|
380
|
-
|
|
381
|
-
lines: (v.originalContent || v.content).split('\n'),
|
|
331
|
+
lines: originalLines,
|
|
382
332
|
functionName
|
|
383
333
|
};
|
|
384
334
|
|
|
385
|
-
let codeBody = v.originalContent || v.content;
|
|
386
|
-
codeBody = this._stripImportsAndExports(codeBody);
|
|
387
|
-
|
|
388
335
|
entry += `\nasync function ${functionName}() {\n${codeBody}\n}\n`;
|
|
389
336
|
|
|
390
|
-
// Generate route path
|
|
391
337
|
const routePath = v.name
|
|
392
338
|
.toLowerCase()
|
|
393
339
|
.replace(/\\/g, '/')
|
|
@@ -404,18 +350,17 @@ export class JuxCompiler {
|
|
|
404
350
|
routeToFunctionMap.set(`/${routePath}`, functionName);
|
|
405
351
|
});
|
|
406
352
|
|
|
407
|
-
// ✅ Generate router
|
|
408
353
|
entry += this._generateRouter(routeToFunctionMap);
|
|
409
354
|
|
|
410
355
|
this._sourceSnapshot = sourceSnapshot;
|
|
411
356
|
this._validationIssues = [];
|
|
412
357
|
|
|
358
|
+
// Embed source snapshot for runtime error overlay
|
|
359
|
+
entry += `\nwindow.__juxSources = ${JSON.stringify(sourceSnapshot)};\n`;
|
|
360
|
+
|
|
413
361
|
return entry;
|
|
414
362
|
}
|
|
415
363
|
|
|
416
|
-
/**
|
|
417
|
-
* Strip imports and exports from code, keeping the actual logic
|
|
418
|
-
*/
|
|
419
364
|
_stripImportsAndExports(code) {
|
|
420
365
|
try {
|
|
421
366
|
const ast = acorn.parse(code, {
|
|
@@ -424,7 +369,6 @@ export class JuxCompiler {
|
|
|
424
369
|
locations: true
|
|
425
370
|
});
|
|
426
371
|
|
|
427
|
-
// Remove imports and exports
|
|
428
372
|
const nodesToRemove = ast.body.filter(node =>
|
|
429
373
|
node.type === 'ImportDeclaration' ||
|
|
430
374
|
node.type === 'ExportNamedDeclaration' ||
|
|
@@ -436,7 +380,6 @@ export class JuxCompiler {
|
|
|
436
380
|
return code.trim();
|
|
437
381
|
}
|
|
438
382
|
|
|
439
|
-
// Remove nodes in reverse order
|
|
440
383
|
let result = code;
|
|
441
384
|
let offset = 0;
|
|
442
385
|
|
|
@@ -444,21 +387,17 @@ export class JuxCompiler {
|
|
|
444
387
|
let start = node.start - offset;
|
|
445
388
|
let end = node.end - offset;
|
|
446
389
|
|
|
447
|
-
// If it's an export with a declaration, keep the declaration
|
|
448
390
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
449
|
-
// Keep the declaration part, remove just "export"
|
|
450
391
|
const declarationStart = node.declaration.start - offset;
|
|
451
392
|
result = result.substring(0, start) + result.substring(declarationStart);
|
|
452
393
|
offset += (declarationStart - start);
|
|
453
394
|
} else if (node.type === 'ExportDefaultDeclaration') {
|
|
454
|
-
// Remove "export default", keep the expression
|
|
455
395
|
const exportKeyword = result.substring(start, end).match(/export\s+default\s+/);
|
|
456
396
|
if (exportKeyword) {
|
|
457
397
|
result = result.substring(0, start) + result.substring(start + exportKeyword[0].length);
|
|
458
398
|
offset += exportKeyword[0].length;
|
|
459
399
|
}
|
|
460
400
|
} else {
|
|
461
|
-
// Remove the entire node
|
|
462
401
|
result = result.substring(0, start) + result.substring(end);
|
|
463
402
|
offset += (end - start);
|
|
464
403
|
}
|
|
@@ -488,17 +427,11 @@ export class JuxCompiler {
|
|
|
488
427
|
}
|
|
489
428
|
fs.mkdirSync(this.distDir, { recursive: true });
|
|
490
429
|
|
|
491
|
-
// ✅ Copy public folder if exists
|
|
492
430
|
this.copyPublicFolder();
|
|
493
431
|
|
|
494
432
|
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
495
433
|
console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
|
|
496
434
|
|
|
497
|
-
// ❌ REMOVE: No need to copy files to dist - everything goes in bundle
|
|
498
|
-
// const juxDistDir = path.join(this.distDir, 'jux');
|
|
499
|
-
// fs.mkdirSync(juxDistDir, { recursive: true });
|
|
500
|
-
// ... copying logic removed ...
|
|
501
|
-
|
|
502
435
|
const entryContent = this.generateEntryPoint(views, dataModules, sharedModules);
|
|
503
436
|
|
|
504
437
|
const entryPath = path.join(this.distDir, 'entry.js');
|
|
@@ -508,12 +441,6 @@ export class JuxCompiler {
|
|
|
508
441
|
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
509
442
|
console.log(`📸 Source snapshot written`);
|
|
510
443
|
|
|
511
|
-
// ✅ Also write to dist root so the error overlay can fetch it via /__jux_sources.json
|
|
512
|
-
const distSnapshotPath = path.join(this.config.distDir, '__jux_sources.json');
|
|
513
|
-
if (distSnapshotPath !== snapshotPath) {
|
|
514
|
-
fs.writeFileSync(distSnapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
515
|
-
}
|
|
516
|
-
|
|
517
444
|
const validation = this.reportValidationIssues();
|
|
518
445
|
if (!validation.isValid) {
|
|
519
446
|
console.log('🛑 BUILD FAILED\n');
|
|
@@ -540,12 +467,10 @@ export class JuxCompiler {
|
|
|
540
467
|
plugins: [{
|
|
541
468
|
name: 'juxscript-resolver',
|
|
542
469
|
setup: (build) => {
|
|
543
|
-
// Resolve juxscript
|
|
544
470
|
build.onResolve({ filter: /^juxscript$/ }, () => ({
|
|
545
471
|
path: juxscriptPath
|
|
546
472
|
}));
|
|
547
473
|
|
|
548
|
-
// ✅ Force axios to resolve from project's node_modules
|
|
549
474
|
build.onResolve({ filter: /^axios$/ }, () => {
|
|
550
475
|
const projectRoot = process.cwd();
|
|
551
476
|
const axiosPath = path.resolve(projectRoot, 'node_modules/axios/dist/esm/axios.js');
|
|
@@ -559,30 +484,22 @@ export class JuxCompiler {
|
|
|
559
484
|
return null;
|
|
560
485
|
});
|
|
561
486
|
|
|
562
|
-
// ✅ Resolve .jux file imports - with detailed logging
|
|
563
487
|
build.onResolve({ filter: /\.jux$/ }, (args) => {
|
|
564
488
|
console.log(`🔍 Resolving: ${args.path} from ${args.importer}`);
|
|
565
489
|
|
|
566
|
-
// Skip if already resolved
|
|
567
490
|
if (path.isAbsolute(args.path)) {
|
|
568
491
|
return { path: args.path };
|
|
569
492
|
}
|
|
570
493
|
|
|
571
|
-
// Handle relative imports
|
|
572
494
|
if (args.path.startsWith('.')) {
|
|
573
495
|
const importer = args.importer || entryPath;
|
|
574
496
|
const importerDir = path.dirname(importer);
|
|
575
497
|
const resolvedPath = path.resolve(importerDir, args.path);
|
|
576
498
|
|
|
577
|
-
console.log(` Importer dir: ${importerDir}`);
|
|
578
|
-
console.log(` Resolved to: ${resolvedPath}`);
|
|
579
|
-
console.log(` Exists: ${fs.existsSync(resolvedPath)}`);
|
|
580
|
-
|
|
581
499
|
if (fs.existsSync(resolvedPath)) {
|
|
582
500
|
return { path: resolvedPath };
|
|
583
501
|
} else {
|
|
584
502
|
console.error(`❌ Could not resolve ${args.path} from ${importer}`);
|
|
585
|
-
console.error(` Tried: ${resolvedPath}`);
|
|
586
503
|
}
|
|
587
504
|
}
|
|
588
505
|
|
|
@@ -625,8 +542,7 @@ export class JuxCompiler {
|
|
|
625
542
|
<title>JUX Application</title>
|
|
626
543
|
${generateErrorCollector({
|
|
627
544
|
enabled: process.env.NODE_ENV !== 'production',
|
|
628
|
-
maxErrors: 50
|
|
629
|
-
position: 'bottom-right'
|
|
545
|
+
maxErrors: 50
|
|
630
546
|
})}
|
|
631
547
|
<script type="module" src="/bundle.js"></script>
|
|
632
548
|
</head>
|
|
@@ -645,9 +561,6 @@ export class JuxCompiler {
|
|
|
645
561
|
return { success: true, errors: [], warnings: validation.warnings };
|
|
646
562
|
}
|
|
647
563
|
|
|
648
|
-
/**
|
|
649
|
-
* Generate router code
|
|
650
|
-
*/
|
|
651
564
|
_generateRouter(routeToFunctionMap) {
|
|
652
565
|
let router = `\n// --- ROUTER ---\n`;
|
|
653
566
|
router += `const routes = {\n`;
|
|
@@ -661,14 +574,12 @@ export class JuxCompiler {
|
|
|
661
574
|
router += `function route(path) {
|
|
662
575
|
const renderFn = routes[path] || routes['/'];
|
|
663
576
|
if (renderFn) {
|
|
664
|
-
// Clear main content area
|
|
665
577
|
const appMain = document.getElementById('appmain-content');
|
|
666
578
|
if (appMain) appMain.innerHTML = '';
|
|
667
579
|
|
|
668
580
|
const app = document.getElementById('app');
|
|
669
581
|
if (app) app.innerHTML = '';
|
|
670
582
|
|
|
671
|
-
// Call render function
|
|
672
583
|
if (typeof renderFn === 'function') {
|
|
673
584
|
renderFn();
|
|
674
585
|
}
|
|
@@ -678,15 +589,12 @@ export class JuxCompiler {
|
|
|
678
589
|
}
|
|
679
590
|
}
|
|
680
591
|
|
|
681
|
-
// Initial route
|
|
682
592
|
window.addEventListener('DOMContentLoaded', () => {
|
|
683
593
|
route(window.location.pathname);
|
|
684
594
|
});
|
|
685
595
|
|
|
686
|
-
// Handle navigation
|
|
687
596
|
window.addEventListener('popstate', () => route(window.location.pathname));
|
|
688
597
|
|
|
689
|
-
// Intercept link clicks for SPA navigation
|
|
690
598
|
document.addEventListener('click', (e) => {
|
|
691
599
|
const link = e.target.closest('a[href]');
|
|
692
600
|
if (link) {
|
|
@@ -699,7 +607,6 @@ document.addEventListener('click', (e) => {
|
|
|
699
607
|
}
|
|
700
608
|
});
|
|
701
609
|
|
|
702
|
-
// Expose router for manual navigation
|
|
703
610
|
window.navigateTo = (path) => {
|
|
704
611
|
window.history.pushState({}, '', path);
|
|
705
612
|
route(path);
|
|
@@ -709,14 +616,10 @@ window.navigateTo = (path) => {
|
|
|
709
616
|
return router;
|
|
710
617
|
}
|
|
711
618
|
|
|
712
|
-
/**
|
|
713
|
-
* Report validation issues
|
|
714
|
-
*/
|
|
715
619
|
reportValidationIssues() {
|
|
716
620
|
const errors = [];
|
|
717
621
|
const warnings = [];
|
|
718
622
|
|
|
719
|
-
// Check if there are any validation issues collected
|
|
720
623
|
if (this._validationIssues && this._validationIssues.length > 0) {
|
|
721
624
|
this._validationIssues.forEach(issue => {
|
|
722
625
|
if (issue.type === 'error') {
|
|
@@ -727,7 +630,6 @@ window.navigateTo = (path) => {
|
|
|
727
630
|
});
|
|
728
631
|
}
|
|
729
632
|
|
|
730
|
-
// Log warnings
|
|
731
633
|
if (warnings.length > 0) {
|
|
732
634
|
console.log('\n⚠️ Warnings:\n');
|
|
733
635
|
warnings.forEach(w => {
|
|
@@ -735,7 +637,6 @@ window.navigateTo = (path) => {
|
|
|
735
637
|
});
|
|
736
638
|
}
|
|
737
639
|
|
|
738
|
-
// Log errors
|
|
739
640
|
if (errors.length > 0) {
|
|
740
641
|
console.log('\n❌ Errors:\n');
|
|
741
642
|
errors.forEach(e => {
|
|
@@ -743,24 +644,16 @@ window.navigateTo = (path) => {
|
|
|
743
644
|
});
|
|
744
645
|
}
|
|
745
646
|
|
|
746
|
-
return {
|
|
747
|
-
isValid: errors.length === 0,
|
|
748
|
-
errors,
|
|
749
|
-
warnings
|
|
750
|
-
};
|
|
647
|
+
return { isValid: errors.length === 0, errors, warnings };
|
|
751
648
|
}
|
|
752
649
|
|
|
753
|
-
/**
|
|
754
|
-
* Copy public folder contents to dist
|
|
755
|
-
*/
|
|
756
650
|
copyPublicFolder() {
|
|
757
|
-
// ✅ Use configured public path or resolve from paths object
|
|
758
651
|
const publicSrc = this.paths.public
|
|
759
652
|
? this.paths.public
|
|
760
653
|
: path.resolve(process.cwd(), this.publicDir);
|
|
761
654
|
|
|
762
655
|
if (!fs.existsSync(publicSrc)) {
|
|
763
|
-
return;
|
|
656
|
+
return;
|
|
764
657
|
}
|
|
765
658
|
|
|
766
659
|
console.log('📦 Copying public assets...');
|
|
@@ -773,30 +666,23 @@ window.navigateTo = (path) => {
|
|
|
773
666
|
}
|
|
774
667
|
}
|
|
775
668
|
|
|
776
|
-
/**
|
|
777
|
-
* Recursively copy directory contents
|
|
778
|
-
*/
|
|
779
669
|
_copyDirRecursive(src, dest, depth = 0) {
|
|
780
670
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
781
671
|
|
|
782
672
|
entries.forEach(entry => {
|
|
783
|
-
// Skip hidden files and directories
|
|
784
673
|
if (entry.name.startsWith('.')) return;
|
|
785
674
|
|
|
786
675
|
const srcPath = path.join(src, entry.name);
|
|
787
676
|
const destPath = path.join(dest, entry.name);
|
|
788
677
|
|
|
789
678
|
if (entry.isDirectory()) {
|
|
790
|
-
// Create directory and recurse
|
|
791
679
|
if (!fs.existsSync(destPath)) {
|
|
792
680
|
fs.mkdirSync(destPath, { recursive: true });
|
|
793
681
|
}
|
|
794
682
|
this._copyDirRecursive(srcPath, destPath, depth + 1);
|
|
795
683
|
} else {
|
|
796
|
-
// Copy file
|
|
797
684
|
fs.copyFileSync(srcPath, destPath);
|
|
798
685
|
|
|
799
|
-
// Log files at root level only
|
|
800
686
|
if (depth === 0) {
|
|
801
687
|
const ext = path.extname(entry.name);
|
|
802
688
|
const icon = this._getFileIcon(ext);
|
|
@@ -806,41 +692,22 @@ window.navigateTo = (path) => {
|
|
|
806
692
|
});
|
|
807
693
|
}
|
|
808
694
|
|
|
809
|
-
/**
|
|
810
|
-
* Get icon for file type
|
|
811
|
-
*/
|
|
812
695
|
_getFileIcon(ext) {
|
|
813
696
|
const icons = {
|
|
814
|
-
'.html': '📄',
|
|
815
|
-
'.
|
|
816
|
-
'.
|
|
817
|
-
'.
|
|
818
|
-
'.png': '🖼️',
|
|
819
|
-
'.jpg': '🖼️',
|
|
820
|
-
'.jpeg': '🖼️',
|
|
821
|
-
'.gif': '🖼️',
|
|
822
|
-
'.svg': '🎨',
|
|
823
|
-
'.ico': '🔖',
|
|
824
|
-
'.woff': '🔤',
|
|
825
|
-
'.woff2': '🔤',
|
|
826
|
-
'.ttf': '🔤',
|
|
827
|
-
'.eot': '🔤'
|
|
697
|
+
'.html': '📄', '.css': '🎨', '.js': '📜', '.json': '📋',
|
|
698
|
+
'.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️',
|
|
699
|
+
'.svg': '🎨', '.ico': '🔖',
|
|
700
|
+
'.woff': '🔤', '.woff2': '🔤', '.ttf': '🔤', '.eot': '🔤'
|
|
828
701
|
};
|
|
829
702
|
return icons[ext.toLowerCase()] || '📦';
|
|
830
703
|
}
|
|
831
704
|
|
|
832
|
-
/**
|
|
833
|
-
* ✅ Generate valid JavaScript identifier from file path
|
|
834
|
-
* Example: abc/aaa.jux -> abc_aaa
|
|
835
|
-
* Example: menus/main.jux -> menus_main
|
|
836
|
-
* Example: pages/blog/post.jux -> pages_blog_post
|
|
837
|
-
*/
|
|
838
705
|
_generateNameFromPath(filepath) {
|
|
839
706
|
return filepath
|
|
840
|
-
.replace(/\.jux$/, '')
|
|
841
|
-
.replace(/[\/\\]/g, '_')
|
|
842
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
843
|
-
.replace(/_+/g, '_')
|
|
844
|
-
.replace(/^_|_$/g, '');
|
|
707
|
+
.replace(/\.jux$/, '')
|
|
708
|
+
.replace(/[\/\\]/g, '_')
|
|
709
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
710
|
+
.replace(/_+/g, '_')
|
|
711
|
+
.replace(/^_|_$/g, '');
|
|
845
712
|
}
|
|
846
|
-
}
|
|
713
|
+
}
|
package/machinery/errors.js
CHANGED
|
@@ -1,378 +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
|
-
|
|
48
|
-
// Fetch bundle to split into lines
|
|
49
|
-
var xhr2 = new XMLHttpRequest();
|
|
50
|
-
xhr2.open('GET', '/bundle.js', true);
|
|
51
|
-
xhr2.onload = function() {
|
|
52
|
-
try { __bundleLines = xhr2.responseText.split('\\n'); } catch(e) {}
|
|
53
|
-
check();
|
|
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
15
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
var
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
var match = line.match(/async\\s+function\\s+(renderJux\\d+)/);
|
|
72
|
-
if (match) {
|
|
73
|
-
funcName = match[1];
|
|
74
|
-
funcStartLine = i + 1; // 1-based
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
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];
|
|
77
23
|
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
78
26
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Find the source entry that maps to this function
|
|
82
|
-
var sourceEntry = null;
|
|
83
|
-
for (var key in __sources) {
|
|
84
|
-
if (__sources[key].functionName === funcName) {
|
|
85
|
-
sourceEntry = __sources[key];
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!sourceEntry) return null;
|
|
91
|
-
|
|
92
|
-
// Calculate approximate original line
|
|
93
|
-
// bundleLine is inside the function body, funcStartLine is the function declaration
|
|
94
|
-
// +1 because function body starts after the declaration line
|
|
95
|
-
var offsetInFunc = bundleLine - funcStartLine - 1;
|
|
96
|
-
var originalLines = sourceEntry.lines || [];
|
|
97
|
-
var approxLine = Math.max(0, Math.min(offsetInFunc, originalLines.length - 1));
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
file: sourceEntry.file,
|
|
101
|
-
name: sourceEntry.name,
|
|
102
|
-
functionName: funcName,
|
|
103
|
-
originalLine: approxLine + 1,
|
|
104
|
-
originalCode: originalLines[approxLine] || '',
|
|
105
|
-
// Provide surrounding context (3 lines before/after)
|
|
106
|
-
context: originalLines.slice(
|
|
107
|
-
Math.max(0, approxLine - 3),
|
|
108
|
-
Math.min(originalLines.length, approxLine + 4)
|
|
109
|
-
),
|
|
110
|
-
contextStartLine: Math.max(0, approxLine - 3) + 1,
|
|
111
|
-
highlightLine: approxLine + 1
|
|
112
|
-
};
|
|
27
|
+
function escapeHtml(s) {
|
|
28
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
113
29
|
}
|
|
114
30
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
var
|
|
119
|
-
|
|
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>';
|
|
120
36
|
for (var i = 0; i < lines.length; i++) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
frames.push({
|
|
125
|
-
raw: lines[i].trim(),
|
|
126
|
-
func: m[1] || '(anonymous)',
|
|
127
|
-
line: parseInt(m[2], 10),
|
|
128
|
-
col: parseInt(m[3], 10)
|
|
129
|
-
});
|
|
130
|
-
}
|
|
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>';
|
|
131
40
|
}
|
|
132
|
-
|
|
41
|
+
html += '</div>';
|
|
42
|
+
return html;
|
|
133
43
|
}
|
|
134
44
|
|
|
135
45
|
function createOverlay() {
|
|
136
46
|
__overlay = document.createElement('div');
|
|
137
|
-
__overlay.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
'padding:0',
|
|
151
|
-
'margin:0',
|
|
152
|
-
'box-sizing:border-box'
|
|
153
|
-
].join(';'));
|
|
154
|
-
|
|
155
|
-
var container = document.createElement('div');
|
|
156
|
-
container.setAttribute('style', [
|
|
157
|
-
'max-width:960px',
|
|
158
|
-
'margin:40px auto',
|
|
159
|
-
'padding:24px 32px',
|
|
160
|
-
'border:2px solid #ff5555',
|
|
161
|
-
'border-radius:8px',
|
|
162
|
-
'background:#1a1a1a'
|
|
163
|
-
].join(';'));
|
|
164
|
-
|
|
165
|
-
var header = document.createElement('div');
|
|
166
|
-
header.setAttribute('style', [
|
|
167
|
-
'display:flex',
|
|
168
|
-
'justify-content:space-between',
|
|
169
|
-
'align-items:center',
|
|
170
|
-
'margin-bottom:16px',
|
|
171
|
-
'padding-bottom:12px',
|
|
172
|
-
'border-bottom:1px solid #333'
|
|
173
|
-
].join(';'));
|
|
174
|
-
|
|
175
|
-
var title = document.createElement('div');
|
|
176
|
-
title.setAttribute('style', 'color:#ff5555;font-size:16px;font-weight:bold;');
|
|
177
|
-
title.textContent = 'Runtime Error';
|
|
178
|
-
header.appendChild(title);
|
|
179
|
-
|
|
180
|
-
var actions = document.createElement('div');
|
|
181
|
-
actions.setAttribute('style', 'display:flex;gap:8px;');
|
|
182
|
-
|
|
183
|
-
var clearBtn = document.createElement('button');
|
|
184
|
-
clearBtn.textContent = 'Clear';
|
|
185
|
-
clearBtn.setAttribute('style', [
|
|
186
|
-
'background:transparent',
|
|
187
|
-
'border:1px solid #555',
|
|
188
|
-
'color:#aaa',
|
|
189
|
-
'padding:4px 12px',
|
|
190
|
-
'border-radius:4px',
|
|
191
|
-
'cursor:pointer',
|
|
192
|
-
'font-family:monospace',
|
|
193
|
-
'font-size:12px'
|
|
194
|
-
].join(';'));
|
|
195
|
-
clearBtn.addEventListener('click', clearErrors);
|
|
196
|
-
|
|
197
|
-
var closeBtn = document.createElement('button');
|
|
198
|
-
closeBtn.textContent = 'Close';
|
|
199
|
-
closeBtn.setAttribute('style', [
|
|
200
|
-
'background:transparent',
|
|
201
|
-
'border:1px solid #555',
|
|
202
|
-
'color:#aaa',
|
|
203
|
-
'padding:4px 12px',
|
|
204
|
-
'border-radius:4px',
|
|
205
|
-
'cursor:pointer',
|
|
206
|
-
'font-family:monospace',
|
|
207
|
-
'font-size:12px'
|
|
208
|
-
].join(';'));
|
|
209
|
-
closeBtn.addEventListener('click', function() {
|
|
210
|
-
__overlay.style.display = 'none';
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
actions.appendChild(clearBtn);
|
|
214
|
-
actions.appendChild(closeBtn);
|
|
215
|
-
header.appendChild(actions);
|
|
216
|
-
container.appendChild(header);
|
|
217
|
-
|
|
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);
|
|
218
60
|
__list = document.createElement('div');
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
__overlay.appendChild(container);
|
|
61
|
+
c.appendChild(__list); __overlay.appendChild(c);
|
|
222
62
|
document.body.appendChild(__overlay);
|
|
223
63
|
}
|
|
224
64
|
|
|
225
|
-
function
|
|
226
|
-
__errors
|
|
227
|
-
|
|
228
|
-
if (__overlay) __overlay.style.display = 'none';
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function renderSourceContext(resolved) {
|
|
232
|
-
if (!resolved) return '';
|
|
233
|
-
|
|
234
|
-
var lines = resolved.context || [];
|
|
235
|
-
var startLine = resolved.contextStartLine || 1;
|
|
236
|
-
var highlight = resolved.highlightLine || -1;
|
|
237
|
-
|
|
238
|
-
var html = '<div style="margin-top:8px;padding:8px;background:#111;border:1px solid #333;border-radius:4px;">';
|
|
239
|
-
html += '<div style="color:#ff5555;font-size:12px;margin-bottom:6px;font-weight:bold;">' +
|
|
240
|
-
escapeHtml(resolved.file) + ':' + resolved.originalLine +
|
|
241
|
-
'</div>';
|
|
242
|
-
|
|
243
|
-
for (var i = 0; i < lines.length; i++) {
|
|
244
|
-
var lineNum = startLine + i;
|
|
245
|
-
var isHighlight = lineNum === highlight;
|
|
246
|
-
var bg = isHighlight ? 'background:#3a1a1a;' : '';
|
|
247
|
-
var color = isHighlight ? 'color:#ff8888;' : 'color:#888;';
|
|
248
|
-
var numColor = isHighlight ? 'color:#ff5555;' : 'color:#555;';
|
|
249
|
-
var marker = isHighlight ? '>' : ' ';
|
|
250
|
-
|
|
251
|
-
html += '<div style="' + bg + 'padding:1px 8px;white-space:pre;">' +
|
|
252
|
-
'<span style="' + numColor + 'display:inline-block;width:40px;text-align:right;margin-right:12px;user-select:none;">' +
|
|
253
|
-
marker + ' ' + lineNum +
|
|
254
|
-
'</span>' +
|
|
255
|
-
'<span style="' + color + '">' + escapeHtml(lines[i]) + '</span>' +
|
|
256
|
-
'</div>';
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
html += '</div>';
|
|
260
|
-
return html;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function addError(type, message, source, line, col, stack) {
|
|
264
|
-
if (__errors.length >= __maxErrors) __errors.shift();
|
|
265
|
-
|
|
266
|
-
var entry = {
|
|
267
|
-
type: type,
|
|
268
|
-
message: String(message || ''),
|
|
269
|
-
source: source || '',
|
|
270
|
-
line: line || 0,
|
|
271
|
-
col: col || 0,
|
|
272
|
-
stack: stack || '',
|
|
273
|
-
time: new Date().toLocaleTimeString()
|
|
274
|
-
};
|
|
275
|
-
__errors.push(entry);
|
|
276
|
-
|
|
65
|
+
function addError(type, message, stack) {
|
|
66
|
+
if (__errors.length >= ${maxErrors}) __errors.shift();
|
|
67
|
+
__errors.push({ type: type, message: message, stack: stack });
|
|
277
68
|
if (!__overlay) createOverlay();
|
|
278
69
|
__overlay.style.display = 'block';
|
|
279
70
|
|
|
280
71
|
var item = document.createElement('div');
|
|
281
|
-
item.setAttribute('style',
|
|
282
|
-
'padding:12px 16px',
|
|
283
|
-
'margin-bottom:8px',
|
|
284
|
-
'background:#222',
|
|
285
|
-
'border-left:3px solid #ff5555',
|
|
286
|
-
'border-radius:4px'
|
|
287
|
-
].join(';'));
|
|
72
|
+
item.setAttribute('style','padding:12px 16px;margin-bottom:8px;background:#222;border-left:3px solid #ff5555;border-radius:4px;');
|
|
288
73
|
|
|
289
|
-
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>';
|
|
290
77
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
typeLabel +
|
|
294
|
-
'<span style="float:right;">' + entry.time + '</span>' +
|
|
295
|
-
'</div>' +
|
|
296
|
-
'<div style="color:#ff8888;font-size:14px;white-space:pre-wrap;word-break:break-word;font-weight:bold;">' +
|
|
297
|
-
escapeHtml(entry.message) +
|
|
298
|
-
'</div>';
|
|
78
|
+
var src = findSource(stack);
|
|
79
|
+
if (src) item.innerHTML += renderSource(src);
|
|
299
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
|
+
}
|
|
300
88
|
__list.appendChild(item);
|
|
301
|
-
|
|
302
|
-
// Resolve source location asynchronously
|
|
303
|
-
loadSources(function() {
|
|
304
|
-
var frames = parseStack(entry.stack);
|
|
305
|
-
var resolved = null;
|
|
306
|
-
|
|
307
|
-
// Try each frame until we find one that maps to a .jux source
|
|
308
|
-
for (var i = 0; i < frames.length; i++) {
|
|
309
|
-
var r = resolveSourceLocation(frames[i].line);
|
|
310
|
-
if (r) {
|
|
311
|
-
resolved = r;
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Also try the direct line/col from the error itself
|
|
317
|
-
if (!resolved && entry.line) {
|
|
318
|
-
resolved = resolveSourceLocation(entry.line);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (resolved) {
|
|
322
|
-
// Add source context block
|
|
323
|
-
var sourceBlock = document.createElement('div');
|
|
324
|
-
sourceBlock.innerHTML = renderSourceContext(resolved);
|
|
325
|
-
item.appendChild(sourceBlock);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Add raw stack (collapsed) if present
|
|
329
|
-
if (entry.stack) {
|
|
330
|
-
var stackBlock = document.createElement('details');
|
|
331
|
-
stackBlock.setAttribute('style', 'margin-top:8px;');
|
|
332
|
-
stackBlock.innerHTML =
|
|
333
|
-
'<summary style="cursor:pointer;color:#555;font-size:12px;user-select:none;">Raw stack trace</summary>' +
|
|
334
|
-
'<pre style="margin:4px 0 0;padding:8px;font-size:12px;color:#666;' +
|
|
335
|
-
'white-space:pre-wrap;background:#111;border-radius:4px;' +
|
|
336
|
-
'border:1px solid #333;max-height:200px;overflow-y:auto;">' +
|
|
337
|
-
escapeHtml(entry.stack) +
|
|
338
|
-
'</pre>';
|
|
339
|
-
item.appendChild(stackBlock);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
89
|
}
|
|
343
90
|
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// --- Hooks ---
|
|
349
|
-
|
|
350
|
-
window.onerror = function(msg, source, line, col, err) {
|
|
351
|
-
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 : '');
|
|
352
93
|
};
|
|
353
|
-
|
|
354
94
|
window.addEventListener('unhandledrejection', function(e) {
|
|
355
|
-
var
|
|
356
|
-
|
|
357
|
-
var stack = reason instanceof Error ? reason.stack : '';
|
|
358
|
-
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 : '');
|
|
359
97
|
});
|
|
360
|
-
|
|
361
|
-
var _origConsoleError = console.error;
|
|
98
|
+
var _ce = console.error;
|
|
362
99
|
console.error = function() {
|
|
363
|
-
var
|
|
364
|
-
|
|
365
|
-
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);
|
|
366
102
|
}).join(' ');
|
|
367
|
-
addError('console', msg);
|
|
368
|
-
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
window.__juxErrors = {
|
|
372
|
-
list: function() { return __errors.slice(); },
|
|
373
|
-
clear: clearErrors,
|
|
374
|
-
count: function() { return __errors.length; }
|
|
103
|
+
addError('console', msg, '');
|
|
104
|
+
_ce.apply(console, arguments);
|
|
375
105
|
};
|
|
106
|
+
window.__juxErrors = { list: function() { return __errors.slice(); }, count: function() { return __errors.length; } };
|
|
376
107
|
})();
|
|
377
108
|
</script>`;
|
|
378
109
|
}
|