@leftium/gg 0.0.33 → 0.0.34
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/README.md +117 -36
- package/dist/GgConsole.svelte +12 -0
- package/dist/GgConsole.svelte.d.ts +4 -0
- package/dist/debug/browser.d.ts +10 -0
- package/dist/debug/browser.js +102 -0
- package/dist/debug/common.d.ts +41 -0
- package/dist/debug/common.js +191 -0
- package/dist/debug/index.d.ts +9 -0
- package/dist/debug/index.js +11 -0
- package/dist/debug/node.d.ts +10 -0
- package/dist/debug/node.js +137 -0
- package/dist/eruda/loader.js +0 -11
- package/dist/eruda/plugin.js +213 -25
- package/dist/eruda/types.d.ts +0 -5
- package/dist/gg-call-sites-plugin.d.ts +84 -0
- package/dist/gg-call-sites-plugin.js +496 -114
- package/dist/gg.d.ts +7 -0
- package/dist/gg.js +75 -51
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/vite.d.ts +37 -0
- package/dist/vite.js +46 -0
- package/package.json +20 -12
- package/dist/debug-bundled.d.ts +0 -2
- package/dist/debug-bundled.js +0 -3
- package/dist/debug.d.ts +0 -2
- package/dist/debug.js +0 -15
- package/patches/debug@4.4.3.patch +0 -35
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific debug implementation.
|
|
3
|
+
*
|
|
4
|
+
* Output: process.stderr via util.formatWithOptions.
|
|
5
|
+
* Persistence: process.env.DEBUG
|
|
6
|
+
* Format (patched): +123ms namespace message (ANSI colored)
|
|
7
|
+
*/
|
|
8
|
+
import { setup, humanize } from './common.js';
|
|
9
|
+
import tty from 'tty';
|
|
10
|
+
import util from 'util';
|
|
11
|
+
/**
|
|
12
|
+
* Basic ANSI colors (6) — used when 256-color support is not detected.
|
|
13
|
+
* Extended 256-color palette matches debug@4 for color-hash stability.
|
|
14
|
+
*/
|
|
15
|
+
const basicColors = [6, 2, 3, 4, 5, 1];
|
|
16
|
+
const extendedColors = [
|
|
17
|
+
20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45,
|
|
18
|
+
56, 57, 62, 63, 68, 69, 74, 75, 76, 77, 78, 79, 80, 81,
|
|
19
|
+
92, 93, 98, 99, 112, 113, 128, 129, 134, 135, 148, 149,
|
|
20
|
+
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
|
|
21
|
+
172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201,
|
|
22
|
+
202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221
|
|
23
|
+
];
|
|
24
|
+
/** Detect 256-color support via supports-color (optional) or heuristic */
|
|
25
|
+
function detectColors() {
|
|
26
|
+
try {
|
|
27
|
+
// Try supports-color if available (same as debug@4)
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const supportsColor = require('supports-color');
|
|
30
|
+
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
31
|
+
return extendedColors;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Not installed — fall through
|
|
36
|
+
}
|
|
37
|
+
return basicColors;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build inspectOpts from DEBUG_* environment variables.
|
|
41
|
+
* Supports: DEBUG_COLORS, DEBUG_DEPTH, DEBUG_SHOW_HIDDEN, DEBUG_HIDE_DATE
|
|
42
|
+
*/
|
|
43
|
+
const inspectOpts = Object.keys(process.env)
|
|
44
|
+
.filter((key) => /^debug_/i.test(key))
|
|
45
|
+
.reduce((obj, key) => {
|
|
46
|
+
const prop = key
|
|
47
|
+
.substring(6)
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.replace(/_([a-z])/g, (_, k) => k.toUpperCase());
|
|
50
|
+
let val = process.env[key];
|
|
51
|
+
if (/^(yes|on|true|enabled)$/i.test(val))
|
|
52
|
+
val = true;
|
|
53
|
+
else if (/^(no|off|false|disabled)$/i.test(val))
|
|
54
|
+
val = false;
|
|
55
|
+
else if (val === 'null')
|
|
56
|
+
val = null;
|
|
57
|
+
else
|
|
58
|
+
val = Number(val);
|
|
59
|
+
obj[prop] = val;
|
|
60
|
+
return obj;
|
|
61
|
+
}, {});
|
|
62
|
+
function useColors() {
|
|
63
|
+
return 'colors' in inspectOpts
|
|
64
|
+
? Boolean(inspectOpts.colors)
|
|
65
|
+
: tty.isatty(process.stderr.fd);
|
|
66
|
+
}
|
|
67
|
+
function getDate() {
|
|
68
|
+
if (inspectOpts.hideDate)
|
|
69
|
+
return '';
|
|
70
|
+
return new Date().toISOString() + ' ';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format args with ANSI colors and gg's patched prefix order:
|
|
74
|
+
* +123ms namespace message
|
|
75
|
+
*/
|
|
76
|
+
function formatArgs(args) {
|
|
77
|
+
const name = this.namespace;
|
|
78
|
+
const useCol = this.useColors;
|
|
79
|
+
if (useCol) {
|
|
80
|
+
const c = Number(this.color);
|
|
81
|
+
const colorCode = '\u001B[3' + (c < 8 ? String(c) : '8;5;' + c);
|
|
82
|
+
const h = ('+' + humanize(this.diff)).padStart(6);
|
|
83
|
+
const prefix = `${colorCode};1m${h} ${name} \u001B[0m`;
|
|
84
|
+
args[0] = prefix + String(args[0]).split('\n').join('\n' + prefix);
|
|
85
|
+
// Append empty color reset (preserves arg count parity from original debug)
|
|
86
|
+
args.push(colorCode + '' + '\u001B[0m');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
args[0] = getDate() + name + ' ' + args[0];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function log(...args) {
|
|
93
|
+
process.stderr.write(util.formatWithOptions(inspectOpts, ...args) + '\n');
|
|
94
|
+
}
|
|
95
|
+
function save(namespaces) {
|
|
96
|
+
if (namespaces) {
|
|
97
|
+
process.env.DEBUG = namespaces;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
delete process.env.DEBUG;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function load() {
|
|
104
|
+
return process.env.DEBUG || '';
|
|
105
|
+
}
|
|
106
|
+
function init(instance) {
|
|
107
|
+
// Each instance gets its own inspectOpts copy (for per-instance color override)
|
|
108
|
+
instance.inspectOpts = { ...inspectOpts };
|
|
109
|
+
}
|
|
110
|
+
const env = {
|
|
111
|
+
formatArgs,
|
|
112
|
+
save,
|
|
113
|
+
load,
|
|
114
|
+
useColors,
|
|
115
|
+
colors: detectColors(),
|
|
116
|
+
log,
|
|
117
|
+
init,
|
|
118
|
+
formatters: {
|
|
119
|
+
/** %o → util.inspect, single line */
|
|
120
|
+
o(v) {
|
|
121
|
+
const opts = this.inspectOpts || {};
|
|
122
|
+
opts.colors = this.useColors;
|
|
123
|
+
return util.inspect(v, opts)
|
|
124
|
+
.split('\n')
|
|
125
|
+
.map((str) => str.trim())
|
|
126
|
+
.join(' ');
|
|
127
|
+
},
|
|
128
|
+
/** %O → util.inspect, multi-line */
|
|
129
|
+
O(v) {
|
|
130
|
+
const opts = this.inspectOpts || {};
|
|
131
|
+
opts.colors = this.useColors;
|
|
132
|
+
return util.inspect(v, opts);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const debug = setup(env);
|
|
137
|
+
export default debug;
|
package/dist/eruda/loader.js
CHANGED
|
@@ -79,17 +79,6 @@ export async function loadEruda(options) {
|
|
|
79
79
|
// Ensure tool is always visible in case user customizes
|
|
80
80
|
tool: ['console', 'elements', 'network', 'resources', 'info', 'snippets', 'sources']
|
|
81
81
|
});
|
|
82
|
-
// Auto-enable localStorage.debug if requested and unset
|
|
83
|
-
if (options.autoEnable !== false) {
|
|
84
|
-
try {
|
|
85
|
-
if (!localStorage.getItem('debug')) {
|
|
86
|
-
localStorage.setItem('debug', 'gg:*');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// localStorage might not be available
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
82
|
// Register gg plugin
|
|
94
83
|
// Import gg and pass it to the plugin directly
|
|
95
84
|
const { gg, runGgDiagnostics } = await import('../gg.js');
|
package/dist/eruda/plugin.js
CHANGED
|
@@ -19,8 +19,12 @@ export function createGgPlugin(options, gg) {
|
|
|
19
19
|
let filterExpanded = false;
|
|
20
20
|
let filterPattern = '';
|
|
21
21
|
const enabledNamespaces = new Set();
|
|
22
|
+
// Last rendered entries (for hover tooltip arg lookup)
|
|
23
|
+
let renderedEntries = [];
|
|
22
24
|
// Settings UI state
|
|
23
25
|
let settingsExpanded = false;
|
|
26
|
+
// Filter pattern persistence key (independent of localStorage.debug)
|
|
27
|
+
const FILTER_KEY = 'gg-filter';
|
|
24
28
|
// Namespace click action: 'open' uses Vite dev middleware, 'copy' copies formatted string, 'open-url' navigates to URI
|
|
25
29
|
const NS_ACTION_KEY = 'gg-ns-action';
|
|
26
30
|
const EDITOR_BIN_KEY = 'gg-editor-bin';
|
|
@@ -78,13 +82,6 @@ export function createGgPlugin(options, gg) {
|
|
|
78
82
|
};
|
|
79
83
|
let nsClickAction = localStorage.getItem(NS_ACTION_KEY) || (DEV ? 'open' : 'copy');
|
|
80
84
|
let editorBin = localStorage.getItem(EDITOR_BIN_KEY) || '';
|
|
81
|
-
// Separate format strings for copy vs URL modes (each persisted independently)
|
|
82
|
-
// Migrate from old single-key format (gg-editor-format) if present
|
|
83
|
-
const legacyFormat = localStorage.getItem('gg-editor-format');
|
|
84
|
-
if (legacyFormat && !localStorage.getItem(COPY_FORMAT_KEY)) {
|
|
85
|
-
localStorage.setItem(COPY_FORMAT_KEY, legacyFormat);
|
|
86
|
-
localStorage.removeItem('gg-editor-format');
|
|
87
|
-
}
|
|
88
85
|
let copyFormat = localStorage.getItem(COPY_FORMAT_KEY) || copyPresets['Raw path'];
|
|
89
86
|
let urlFormat = localStorage.getItem(URL_FORMAT_KEY) || uriPresets['VS Code'];
|
|
90
87
|
let projectRoot = localStorage.getItem(PROJECT_ROOT_KEY) || '';
|
|
@@ -107,24 +104,27 @@ export function createGgPlugin(options, gg) {
|
|
|
107
104
|
name: 'GG',
|
|
108
105
|
init($container) {
|
|
109
106
|
$el = $container;
|
|
107
|
+
// Load filter state BEFORE registering _onLog hook, because setting _onLog
|
|
108
|
+
// triggers replay of earlyLogBuffer and each entry checks filterPattern
|
|
109
|
+
filterPattern = localStorage.getItem(FILTER_KEY) || 'gg:*';
|
|
110
110
|
// Register the capture hook on gg
|
|
111
111
|
if (gg) {
|
|
112
112
|
gg._onLog = (entry) => {
|
|
113
|
+
// Track namespaces for filter UI update (check BEFORE pushing to buffer)
|
|
114
|
+
const hadNamespace = getAllCapturedNamespaces().includes(entry.namespace);
|
|
113
115
|
buffer.push(entry);
|
|
114
116
|
// Add new namespace to enabledNamespaces if it matches the current pattern
|
|
115
117
|
const effectivePattern = filterPattern || 'gg:*';
|
|
116
118
|
if (namespaceMatchesPattern(entry.namespace, effectivePattern)) {
|
|
117
119
|
enabledNamespaces.add(entry.namespace);
|
|
118
120
|
}
|
|
119
|
-
// Update filter UI if
|
|
120
|
-
if (
|
|
121
|
+
// Update filter UI if new namespace appeared (updates button summary count)
|
|
122
|
+
if (!hadNamespace) {
|
|
121
123
|
renderFilterUI();
|
|
122
124
|
}
|
|
123
125
|
renderLogs();
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
|
-
// Load initial filter state
|
|
127
|
-
filterPattern = localStorage.getItem('debug') || '';
|
|
128
128
|
// Probe for openInEditorPlugin (status 222) and auto-populate $ROOT in dev mode
|
|
129
129
|
if (DEV) {
|
|
130
130
|
fetch('/__open-in-editor?file=+')
|
|
@@ -420,21 +420,38 @@ export function createGgPlugin(options, gg) {
|
|
|
420
420
|
word-break: break-word;
|
|
421
421
|
padding: 4px 0;
|
|
422
422
|
position: relative;
|
|
423
|
+
-webkit-user-select: text !important;
|
|
424
|
+
user-select: text !important;
|
|
425
|
+
cursor: text;
|
|
426
|
+
}
|
|
427
|
+
.gg-log-content * {
|
|
428
|
+
-webkit-user-select: text !important;
|
|
429
|
+
user-select: text !important;
|
|
430
|
+
}
|
|
431
|
+
.gg-log-diff, .gg-log-ns {
|
|
432
|
+
-webkit-user-select: text !important;
|
|
433
|
+
user-select: text !important;
|
|
423
434
|
}
|
|
424
|
-
|
|
435
|
+
.gg-details, .gg-details * {
|
|
436
|
+
-webkit-user-select: text !important;
|
|
437
|
+
user-select: text !important;
|
|
438
|
+
cursor: text;
|
|
439
|
+
}
|
|
440
|
+
/* Fast custom tooltip for src expression on primitive-only rows (no expandable objects) */
|
|
425
441
|
.gg-log-content[data-src] {
|
|
426
442
|
cursor: help;
|
|
427
443
|
}
|
|
428
|
-
.gg-
|
|
429
|
-
|
|
444
|
+
/* Show icon only on primitive rows (no .gg-expand child) */
|
|
445
|
+
.gg-log-content[data-src]:not(:has(.gg-expand))::before {
|
|
446
|
+
content: '\uD83D\uDD0D';
|
|
430
447
|
font-size: 10px;
|
|
431
448
|
margin-right: 4px;
|
|
432
449
|
opacity: 0.4;
|
|
433
450
|
}
|
|
434
|
-
.gg-log-content[data-src]:hover::before {
|
|
451
|
+
.gg-log-content[data-src]:not(:has(.gg-expand)):hover::before {
|
|
435
452
|
opacity: 1;
|
|
436
453
|
}
|
|
437
|
-
.gg-log-content[data-src]::after {
|
|
454
|
+
.gg-log-content[data-src]:not(:has(.gg-expand))::after {
|
|
438
455
|
content: attr(data-src);
|
|
439
456
|
position: absolute;
|
|
440
457
|
top: 100%;
|
|
@@ -454,9 +471,56 @@ export function createGgPlugin(options, gg) {
|
|
|
454
471
|
overflow: hidden;
|
|
455
472
|
text-overflow: ellipsis;
|
|
456
473
|
}
|
|
457
|
-
.gg-log-content[data-src]:hover::after {
|
|
474
|
+
.gg-log-content[data-src]:not(:has(.gg-expand)):hover::after {
|
|
475
|
+
opacity: 1;
|
|
476
|
+
}
|
|
477
|
+
/* Expression icon inline with expandable object labels */
|
|
478
|
+
.gg-src-icon {
|
|
479
|
+
font-size: 10px;
|
|
480
|
+
margin-right: 2px;
|
|
481
|
+
opacity: 0.4;
|
|
482
|
+
cursor: pointer;
|
|
483
|
+
}
|
|
484
|
+
.gg-expand:hover .gg-src-icon,
|
|
485
|
+
.gg-src-icon:hover {
|
|
458
486
|
opacity: 1;
|
|
459
487
|
}
|
|
488
|
+
/* Hover tooltip for expandable objects/arrays */
|
|
489
|
+
.gg-hover-tooltip {
|
|
490
|
+
display: none;
|
|
491
|
+
position: fixed;
|
|
492
|
+
background: #1e1e1e;
|
|
493
|
+
color: #d4d4d4;
|
|
494
|
+
font-size: 11px;
|
|
495
|
+
font-family: monospace;
|
|
496
|
+
padding: 8px 10px;
|
|
497
|
+
border-radius: 4px;
|
|
498
|
+
white-space: pre;
|
|
499
|
+
pointer-events: none;
|
|
500
|
+
z-index: 100000;
|
|
501
|
+
max-width: min(90vw, 500px);
|
|
502
|
+
max-height: 300px;
|
|
503
|
+
overflow: auto;
|
|
504
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
505
|
+
line-height: 1.4;
|
|
506
|
+
}
|
|
507
|
+
.gg-hover-tooltip-src {
|
|
508
|
+
color: #9cdcfe;
|
|
509
|
+
font-style: italic;
|
|
510
|
+
margin-bottom: 4px;
|
|
511
|
+
padding-bottom: 4px;
|
|
512
|
+
border-bottom: 1px solid #444;
|
|
513
|
+
}
|
|
514
|
+
/* Expression header inside expanded details */
|
|
515
|
+
.gg-details-src {
|
|
516
|
+
color: #555;
|
|
517
|
+
font-style: italic;
|
|
518
|
+
font-family: monospace;
|
|
519
|
+
font-size: 11px;
|
|
520
|
+
margin-bottom: 6px;
|
|
521
|
+
padding-bottom: 4px;
|
|
522
|
+
border-bottom: 1px solid #ddd;
|
|
523
|
+
}
|
|
460
524
|
.gg-filter-panel {
|
|
461
525
|
background: #f5f5f5;
|
|
462
526
|
padding: 10px;
|
|
@@ -683,7 +747,7 @@ export function createGgPlugin(options, gg) {
|
|
|
683
747
|
}
|
|
684
748
|
function applyPatternFromInput(value) {
|
|
685
749
|
filterPattern = value;
|
|
686
|
-
localStorage.setItem(
|
|
750
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
687
751
|
// Sync enabledNamespaces from the new pattern
|
|
688
752
|
const allNamespaces = getAllCapturedNamespaces();
|
|
689
753
|
enabledNamespaces.clear();
|
|
@@ -746,7 +810,7 @@ export function createGgPlugin(options, gg) {
|
|
|
746
810
|
filterPattern = `gg:*,${exclusions}`;
|
|
747
811
|
enabledNamespaces.clear();
|
|
748
812
|
}
|
|
749
|
-
localStorage.setItem(
|
|
813
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
750
814
|
renderFilterUI();
|
|
751
815
|
renderLogs();
|
|
752
816
|
return;
|
|
@@ -893,6 +957,37 @@ export function createGgPlugin(options, gg) {
|
|
|
893
957
|
else if (nsClickAction === 'copy' || nsClickAction === 'open-url') {
|
|
894
958
|
optionsSection = renderFormatSection(nsClickAction === 'open-url');
|
|
895
959
|
}
|
|
960
|
+
// Native Console section
|
|
961
|
+
const currentDebugValue = localStorage.getItem('debug');
|
|
962
|
+
const debugDisplay = currentDebugValue !== null ? `'${escapeHtml(currentDebugValue)}'` : 'not set';
|
|
963
|
+
const currentFilter = filterPattern || 'gg:*';
|
|
964
|
+
const debugMatchesFilter = currentDebugValue === currentFilter;
|
|
965
|
+
const debugIncludesGg = currentDebugValue !== null &&
|
|
966
|
+
(currentDebugValue.includes('gg:') || currentDebugValue === '*');
|
|
967
|
+
const nativeConsoleSection = `
|
|
968
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #ddd;">
|
|
969
|
+
<div class="gg-settings-label">Native Console Output</div>
|
|
970
|
+
<div style="font-size: 11px; opacity: 0.7; margin-bottom: 8px;">
|
|
971
|
+
gg messages always appear in this GG panel. To also see them in the browser's native console, set <code>localStorage.debug</code> below.
|
|
972
|
+
For server-side: <code>DEBUG=gg:* npm run dev</code>
|
|
973
|
+
</div>
|
|
974
|
+
<div style="font-family: monospace; font-size: 12px; margin-bottom: 6px;">
|
|
975
|
+
localStorage.debug = ${debugDisplay}
|
|
976
|
+
${debugIncludesGg ? '<span style="color: green;">✅</span>' : '<span style="color: #999;">⚫ gg:* not included</span>'}
|
|
977
|
+
</div>
|
|
978
|
+
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
|
979
|
+
<button class="gg-sync-debug-btn" style="padding: 4px 10px; font-size: 12px; cursor: pointer; border: 1px solid #ccc; border-radius: 3px; background: ${debugMatchesFilter ? '#e8e8e8' : '#fff'};"${debugMatchesFilter ? ' disabled' : ''}>
|
|
980
|
+
${debugMatchesFilter ? 'In sync' : `Set to '${escapeHtml(currentFilter)}'`}
|
|
981
|
+
</button>
|
|
982
|
+
<button class="gg-clear-debug-btn" style="padding: 4px 10px; font-size: 12px; cursor: pointer; border: 1px solid #ccc; border-radius: 3px; background: #fff;"${currentDebugValue === null ? ' disabled' : ''}>
|
|
983
|
+
Clear
|
|
984
|
+
</button>
|
|
985
|
+
</div>
|
|
986
|
+
<div style="font-size: 10px; opacity: 0.5; margin-top: 4px;">
|
|
987
|
+
Changes take effect on next page reload.
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
`;
|
|
896
991
|
settingsPanel.innerHTML = `
|
|
897
992
|
${callSitesWarning}
|
|
898
993
|
<div class="gg-settings-label">When namespace clicked:</div>
|
|
@@ -911,6 +1006,7 @@ export function createGgPlugin(options, gg) {
|
|
|
911
1006
|
</label>
|
|
912
1007
|
</div>
|
|
913
1008
|
${optionsSection}
|
|
1009
|
+
${nativeConsoleSection}
|
|
914
1010
|
`;
|
|
915
1011
|
}
|
|
916
1012
|
else {
|
|
@@ -970,7 +1066,7 @@ export function createGgPlugin(options, gg) {
|
|
|
970
1066
|
target.blur();
|
|
971
1067
|
}
|
|
972
1068
|
});
|
|
973
|
-
// Preset button clicks + editor bin button clicks
|
|
1069
|
+
// Preset button clicks + editor bin button clicks + native console buttons
|
|
974
1070
|
settingsPanel.addEventListener('click', (e) => {
|
|
975
1071
|
const target = e.target;
|
|
976
1072
|
if (target.classList.contains('gg-preset-btn')) {
|
|
@@ -988,6 +1084,17 @@ export function createGgPlugin(options, gg) {
|
|
|
988
1084
|
renderSettingsUI();
|
|
989
1085
|
}
|
|
990
1086
|
}
|
|
1087
|
+
// Native Console: sync localStorage.debug to current gg-filter
|
|
1088
|
+
if (target.classList.contains('gg-sync-debug-btn')) {
|
|
1089
|
+
const currentFilter = localStorage.getItem(FILTER_KEY) || 'gg:*';
|
|
1090
|
+
localStorage.setItem('debug', currentFilter);
|
|
1091
|
+
renderSettingsUI();
|
|
1092
|
+
}
|
|
1093
|
+
// Native Console: clear localStorage.debug
|
|
1094
|
+
if (target.classList.contains('gg-clear-debug-btn')) {
|
|
1095
|
+
localStorage.removeItem('debug');
|
|
1096
|
+
renderSettingsUI();
|
|
1097
|
+
}
|
|
991
1098
|
});
|
|
992
1099
|
}
|
|
993
1100
|
function wireUpButtons() {
|
|
@@ -1127,7 +1234,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1127
1234
|
else {
|
|
1128
1235
|
soloNamespace(namespace);
|
|
1129
1236
|
}
|
|
1130
|
-
localStorage.setItem(
|
|
1237
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1131
1238
|
renderFilterUI();
|
|
1132
1239
|
renderLogs();
|
|
1133
1240
|
return;
|
|
@@ -1138,7 +1245,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1138
1245
|
if (!namespace)
|
|
1139
1246
|
return;
|
|
1140
1247
|
soloNamespace(namespace);
|
|
1141
|
-
localStorage.setItem(
|
|
1248
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1142
1249
|
renderFilterUI();
|
|
1143
1250
|
renderLogs();
|
|
1144
1251
|
return;
|
|
@@ -1150,11 +1257,75 @@ export function createGgPlugin(options, gg) {
|
|
|
1150
1257
|
filterPattern = 'gg:*';
|
|
1151
1258
|
enabledNamespaces.clear();
|
|
1152
1259
|
getAllCapturedNamespaces().forEach((ns) => enabledNamespaces.add(ns));
|
|
1153
|
-
localStorage.setItem(
|
|
1260
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1154
1261
|
renderFilterUI();
|
|
1155
1262
|
renderLogs();
|
|
1156
1263
|
}
|
|
1157
1264
|
});
|
|
1265
|
+
// Hover tooltip for expandable objects/arrays.
|
|
1266
|
+
// The tooltip div is re-created after each renderLogs() call
|
|
1267
|
+
// since logContainer.html() destroys children. Event listeners query it dynamically.
|
|
1268
|
+
containerEl.addEventListener('mouseover', (e) => {
|
|
1269
|
+
const target = e.target?.closest?.('.gg-expand');
|
|
1270
|
+
if (!target)
|
|
1271
|
+
return;
|
|
1272
|
+
const entryIdx = target.getAttribute('data-entry');
|
|
1273
|
+
const argIdx = target.getAttribute('data-arg');
|
|
1274
|
+
if (entryIdx === null || argIdx === null)
|
|
1275
|
+
return;
|
|
1276
|
+
const entry = renderedEntries[Number(entryIdx)];
|
|
1277
|
+
if (!entry)
|
|
1278
|
+
return;
|
|
1279
|
+
const arg = entry.args[Number(argIdx)];
|
|
1280
|
+
if (arg === undefined)
|
|
1281
|
+
return;
|
|
1282
|
+
const tip = containerEl.querySelector('.gg-hover-tooltip');
|
|
1283
|
+
if (!tip)
|
|
1284
|
+
return;
|
|
1285
|
+
const srcExpr = target.getAttribute('data-src');
|
|
1286
|
+
// Build tooltip content using DOM API (safe, no HTML injection)
|
|
1287
|
+
tip.textContent = '';
|
|
1288
|
+
if (srcExpr) {
|
|
1289
|
+
const srcDiv = document.createElement('div');
|
|
1290
|
+
srcDiv.className = 'gg-hover-tooltip-src';
|
|
1291
|
+
srcDiv.textContent = srcExpr;
|
|
1292
|
+
tip.appendChild(srcDiv);
|
|
1293
|
+
}
|
|
1294
|
+
const pre = document.createElement('pre');
|
|
1295
|
+
pre.style.margin = '0';
|
|
1296
|
+
pre.textContent = JSON.stringify(arg, null, 2);
|
|
1297
|
+
tip.appendChild(pre);
|
|
1298
|
+
tip.style.display = 'block';
|
|
1299
|
+
// Position below the hovered element using viewport coords (fixed positioning)
|
|
1300
|
+
const targetRect = target.getBoundingClientRect();
|
|
1301
|
+
let left = targetRect.left;
|
|
1302
|
+
let top = targetRect.bottom + 4;
|
|
1303
|
+
// Keep tooltip within viewport
|
|
1304
|
+
const tipRect = tip.getBoundingClientRect();
|
|
1305
|
+
if (left + tipRect.width > window.innerWidth) {
|
|
1306
|
+
left = window.innerWidth - tipRect.width - 8;
|
|
1307
|
+
}
|
|
1308
|
+
if (left < 4)
|
|
1309
|
+
left = 4;
|
|
1310
|
+
// If tooltip would go below viewport, show above instead
|
|
1311
|
+
if (top + tipRect.height > window.innerHeight) {
|
|
1312
|
+
top = targetRect.top - tipRect.height - 4;
|
|
1313
|
+
}
|
|
1314
|
+
tip.style.left = `${left}px`;
|
|
1315
|
+
tip.style.top = `${top}px`;
|
|
1316
|
+
});
|
|
1317
|
+
containerEl.addEventListener('mouseout', (e) => {
|
|
1318
|
+
const target = e.target?.closest?.('.gg-expand');
|
|
1319
|
+
if (!target)
|
|
1320
|
+
return;
|
|
1321
|
+
// Only hide if we're not moving to another child of the same .gg-expand
|
|
1322
|
+
const related = e.relatedTarget;
|
|
1323
|
+
if (related?.closest?.('.gg-expand') === target)
|
|
1324
|
+
return;
|
|
1325
|
+
const tip = containerEl.querySelector('.gg-hover-tooltip');
|
|
1326
|
+
if (tip)
|
|
1327
|
+
tip.style.display = 'none';
|
|
1328
|
+
});
|
|
1158
1329
|
expanderAttached = true;
|
|
1159
1330
|
}
|
|
1160
1331
|
function wireUpResize() {
|
|
@@ -1214,6 +1385,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1214
1385
|
const allEntries = buffer.getEntries();
|
|
1215
1386
|
// Apply filtering
|
|
1216
1387
|
const entries = allEntries.filter((entry) => enabledNamespaces.has(entry.namespace));
|
|
1388
|
+
renderedEntries = entries;
|
|
1217
1389
|
const countText = entries.length === allEntries.length
|
|
1218
1390
|
? `${entries.length} entries`
|
|
1219
1391
|
: `${entries.length} / ${allEntries.length} entries`;
|
|
@@ -1230,6 +1402,8 @@ export function createGgPlugin(options, gg) {
|
|
|
1230
1402
|
// Format each arg individually - objects are expandable
|
|
1231
1403
|
let argsHTML = '';
|
|
1232
1404
|
let detailsHTML = '';
|
|
1405
|
+
// Source expression for this entry (used in hover tooltips and expanded details)
|
|
1406
|
+
const srcExpr = entry.src?.trim() && !/^['"`]/.test(entry.src) ? escapeHtml(entry.src) : '';
|
|
1233
1407
|
if (entry.args.length > 0) {
|
|
1234
1408
|
argsHTML = entry.args
|
|
1235
1409
|
.map((arg, argIdx) => {
|
|
@@ -1238,9 +1412,14 @@ export function createGgPlugin(options, gg) {
|
|
|
1238
1412
|
const preview = Array.isArray(arg) ? `Array(${arg.length})` : 'Object';
|
|
1239
1413
|
const jsonStr = escapeHtml(JSON.stringify(arg, null, 2));
|
|
1240
1414
|
const uniqueId = `${index}-${argIdx}`;
|
|
1415
|
+
// Expression header inside expanded details
|
|
1416
|
+
const srcHeader = srcExpr ? `<div class="gg-details-src">${srcExpr}</div>` : '';
|
|
1241
1417
|
// Store details separately to render after the row
|
|
1242
|
-
detailsHTML += `<div class="gg-details" data-index="${uniqueId}" style="display: none; margin: 4px 0 8px 0; padding: 8px; background: #f8f8f8; border-left: 3px solid ${color}; font-size: 11px; overflow-x: auto;"
|
|
1243
|
-
|
|
1418
|
+
detailsHTML += `<div class="gg-details" data-index="${uniqueId}" style="display: none; margin: 4px 0 8px 0; padding: 8px; background: #f8f8f8; border-left: 3px solid ${color}; font-size: 11px; overflow-x: auto;">${srcHeader}<pre style="margin: 0;">${jsonStr}</pre></div>`;
|
|
1419
|
+
// data-entry/data-arg for hover tooltip lookup, data-src for expression context
|
|
1420
|
+
const srcAttr = srcExpr ? ` data-src="${srcExpr}"` : '';
|
|
1421
|
+
const srcIcon = srcExpr ? `<span class="gg-src-icon">\uD83D\uDD0D</span>` : '';
|
|
1422
|
+
return `<span style="color: #888; cursor: pointer; text-decoration: underline;" class="gg-expand" data-index="${uniqueId}" data-entry="${index}" data-arg="${argIdx}"${srcAttr}>${srcIcon}${preview}</span>`;
|
|
1244
1423
|
}
|
|
1245
1424
|
else {
|
|
1246
1425
|
// Parse ANSI codes first, then convert URLs to clickable links
|
|
@@ -1295,6 +1474,13 @@ export function createGgPlugin(options, gg) {
|
|
|
1295
1474
|
})
|
|
1296
1475
|
.join('')}</div>`;
|
|
1297
1476
|
logContainer.html(logsHTML);
|
|
1477
|
+
// Append hover tooltip div (destroyed by .html() each render, so re-create)
|
|
1478
|
+
const containerDom = logContainer.get(0);
|
|
1479
|
+
if (containerDom) {
|
|
1480
|
+
const tip = document.createElement('div');
|
|
1481
|
+
tip.className = 'gg-hover-tooltip';
|
|
1482
|
+
containerDom.appendChild(tip);
|
|
1483
|
+
}
|
|
1298
1484
|
// Re-wire expanders after rendering
|
|
1299
1485
|
wireUpExpanders();
|
|
1300
1486
|
// Auto-scroll to bottom
|
|
@@ -1326,6 +1512,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1326
1512
|
*/
|
|
1327
1513
|
function stripAnsi(text) {
|
|
1328
1514
|
// Remove all ANSI escape codes
|
|
1515
|
+
// eslint-disable-next-line no-control-regex
|
|
1329
1516
|
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
1330
1517
|
}
|
|
1331
1518
|
// Standard ANSI 3/4-bit color palette
|
|
@@ -1376,6 +1563,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1376
1563
|
*/
|
|
1377
1564
|
function parseAnsiToHtml(text) {
|
|
1378
1565
|
// ANSI escape sequence regex
|
|
1566
|
+
// eslint-disable-next-line no-control-regex
|
|
1379
1567
|
const ansiRegex = /\x1b\[([0-9;]+)m/g;
|
|
1380
1568
|
let html = '';
|
|
1381
1569
|
let lastIndex = 0;
|
package/dist/eruda/types.d.ts
CHANGED
|
@@ -12,11 +12,6 @@ export interface GgErudaOptions {
|
|
|
12
12
|
* @default 2000
|
|
13
13
|
*/
|
|
14
14
|
maxEntries?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Auto-enable localStorage.debug = 'gg:*' if unset
|
|
17
|
-
* @default true
|
|
18
|
-
*/
|
|
19
|
-
autoEnable?: boolean;
|
|
20
15
|
/**
|
|
21
16
|
* Additional Eruda options passed to eruda.init()
|
|
22
17
|
* @default {}
|
|
@@ -11,6 +11,33 @@ export interface GgCallSitesPluginOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
srcRootPattern?: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* A range of source code that contains JS expressions, tagged with its context.
|
|
16
|
+
* - 'script': inside a `<script>` block — use object literal `{...}` syntax
|
|
17
|
+
* - 'template': inside a template expression `{...}` or event handler — use `gg._o()` syntax
|
|
18
|
+
*/
|
|
19
|
+
export interface CodeRange {
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
context: 'script' | 'template';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A function scope range, mapping a byte range to the enclosing function name.
|
|
26
|
+
* Built from the estree AST during `collectCodeRanges()`.
|
|
27
|
+
*/
|
|
28
|
+
export interface FunctionScope {
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
name: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result of `collectCodeRanges()` — code ranges plus function scope info.
|
|
35
|
+
*/
|
|
36
|
+
export interface SvelteCodeInfo {
|
|
37
|
+
ranges: CodeRange[];
|
|
38
|
+
/** Function scopes extracted from the estree AST, sorted by start position. */
|
|
39
|
+
functionScopes: FunctionScope[];
|
|
40
|
+
}
|
|
14
41
|
/**
|
|
15
42
|
* Vite plugin that rewrites `gg(...)` and `gg.ns(...)` calls to
|
|
16
43
|
* `gg._ns({ns, file, line, col}, ...)` at build time. This gives each call
|
|
@@ -29,3 +56,60 @@ export interface GgCallSitesPluginOptions {
|
|
|
29
56
|
* });
|
|
30
57
|
*/
|
|
31
58
|
export default function ggCallSitesPlugin(options?: GgCallSitesPluginOptions): Plugin;
|
|
59
|
+
/**
|
|
60
|
+
* Parse JavaScript/TypeScript code using acorn to extract function scopes.
|
|
61
|
+
* Returns function scope ranges for accurate function name detection in .js/.ts files.
|
|
62
|
+
* Uses @sveltejs/acorn-typescript plugin to handle TypeScript syntax.
|
|
63
|
+
*
|
|
64
|
+
* For .svelte files, use `collectCodeRanges()` instead (which uses svelte.parse()).
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseJavaScript(code: string): FunctionScope[];
|
|
67
|
+
/**
|
|
68
|
+
* Use `svelte.parse()` to collect all code ranges and function scopes in a .svelte file.
|
|
69
|
+
*
|
|
70
|
+
* Code ranges identify where JS expressions live:
|
|
71
|
+
* - `<script>` blocks (context: 'script')
|
|
72
|
+
* - Template expressions: `{expr}`, `onclick={expr}`, `bind:value={expr}`,
|
|
73
|
+
* `class:name={expr}`, `{#if expr}`, `{#each expr}`, etc. (context: 'template')
|
|
74
|
+
*
|
|
75
|
+
* Function scopes are extracted from the estree AST in script blocks, mapping
|
|
76
|
+
* byte ranges to enclosing function names. This replaces regex-based function
|
|
77
|
+
* detection for .svelte files.
|
|
78
|
+
*
|
|
79
|
+
* Text nodes (prose) are NOT included, so `gg()` in `<p>text gg()</p>` is never transformed.
|
|
80
|
+
*/
|
|
81
|
+
export declare function collectCodeRanges(code: string): SvelteCodeInfo;
|
|
82
|
+
/**
|
|
83
|
+
* Find the innermost enclosing function name for a byte position
|
|
84
|
+
* using the pre-built function scope map.
|
|
85
|
+
* Returns empty string if not inside any named function.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findEnclosingFunctionFromScopes(pos: number, scopes: FunctionScope[]): string;
|
|
88
|
+
/**
|
|
89
|
+
* Escape a string for embedding as a single-quoted JS string literal.
|
|
90
|
+
*/
|
|
91
|
+
export declare function escapeForString(s: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Transform gg() and gg.ns() calls in source code to gg._ns({ns, file, line, col, src}, ...) calls.
|
|
94
|
+
*
|
|
95
|
+
* Handles:
|
|
96
|
+
* - bare gg(expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
|
|
97
|
+
* - gg.ns('label', expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
|
|
98
|
+
* - label supports template variables: $NS, $FN, $FILE, $LINE, $COL
|
|
99
|
+
* - plain label (no variables) is used as-is (no auto @fn append)
|
|
100
|
+
* - gg.enable, gg.disable, gg.clearPersist, gg._onLog, gg._ns → left untouched
|
|
101
|
+
* - gg inside strings and comments → left untouched
|
|
102
|
+
*
|
|
103
|
+
* For .svelte files, `svelteInfo` (from `collectCodeRanges()`) determines which
|
|
104
|
+
* positions contain JS code and provides AST-based function scope detection.
|
|
105
|
+
* Script ranges use `{...}` object literal syntax; template ranges use `gg._o()`
|
|
106
|
+
* function-call syntax (no braces in Svelte markup). Positions outside any code
|
|
107
|
+
* range (e.g. prose text) are skipped.
|
|
108
|
+
*
|
|
109
|
+
* For .js/.ts files, `jsFunctionScopes` (from `parseJavaScript()`) provides
|
|
110
|
+
* AST-based function scope detection (no regex fallback).
|
|
111
|
+
*/
|
|
112
|
+
export declare function transformGgCalls(code: string, shortPath: string, filePath: string, svelteInfo?: SvelteCodeInfo, jsFunctionScopes?: FunctionScope[]): {
|
|
113
|
+
code: string;
|
|
114
|
+
map: null;
|
|
115
|
+
} | null;
|