@leftium/gg 0.0.49 → 0.0.51
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 +108 -2
- package/dist/debug/browser.d.ts +1 -1
- package/dist/debug/browser.js +14 -6
- package/dist/debug/node.js +5 -3
- package/dist/eruda/buffer.d.ts +4 -0
- package/dist/eruda/buffer.js +16 -0
- package/dist/eruda/loader.js +58 -0
- package/dist/eruda/plugin.d.ts +5 -1
- package/dist/eruda/plugin.js +1219 -379
- package/dist/eruda/types.d.ts +20 -1
- package/dist/gg-call-sites-plugin.js +11 -4
- package/dist/gg-file-sink-plugin.d.ts +6 -0
- package/dist/gg-file-sink-plugin.js +394 -0
- package/dist/gg.d.ts +3 -0
- package/dist/gg.js +160 -62
- package/dist/open-in-editor.js +1 -1
- package/dist/pattern.d.ts +23 -0
- package/dist/pattern.js +41 -0
- package/dist/vite.d.ts +12 -2
- package/dist/vite.js +7 -1
- package/package.json +17 -17
package/dist/gg.js
CHANGED
|
@@ -16,13 +16,13 @@ function createGgDebugger(namespace) {
|
|
|
16
16
|
if (originalFormatArgs) {
|
|
17
17
|
originalFormatArgs.call(dbg, args);
|
|
18
18
|
}
|
|
19
|
-
//
|
|
20
|
-
const nsMatch = dbg.namespace.match(/^
|
|
21
|
-
const callpoint = nsMatch ? nsMatch[1] : dbg.namespace
|
|
19
|
+
// Pad the namespace for aligned console output (strip URL suffix if present)
|
|
20
|
+
const nsMatch = dbg.namespace.match(/^([^h]+?)(?:http|$)/);
|
|
21
|
+
const callpoint = nsMatch ? nsMatch[1] : dbg.namespace;
|
|
22
22
|
const paddedCallpoint = callpoint.padEnd(maxCallpointLength, ' ');
|
|
23
23
|
// Replace the namespace in the formatted string with padded version
|
|
24
24
|
if (typeof args[0] === 'string') {
|
|
25
|
-
args[0] = args[0].replace(dbg.namespace,
|
|
25
|
+
args[0] = args[0].replace(dbg.namespace, paddedCallpoint);
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
return dbg;
|
|
@@ -35,10 +35,6 @@ const isCloudflareWorker = () => {
|
|
|
35
35
|
'caches' in globalThis &&
|
|
36
36
|
typeof globalWithWorkerAPIs.WebSocketPair !== 'undefined');
|
|
37
37
|
};
|
|
38
|
-
// Check if we're in CloudFlare Workers and warn early
|
|
39
|
-
if (isCloudflareWorker()) {
|
|
40
|
-
console.warn('gg: CloudFlare not supported.');
|
|
41
|
-
}
|
|
42
38
|
// Lazy-load Node.js modules to avoid top-level await (Safari compatibility).
|
|
43
39
|
// The imports start immediately but don't block module evaluation.
|
|
44
40
|
let httpModule = null;
|
|
@@ -105,27 +101,29 @@ void getServerPort().then((p) => {
|
|
|
105
101
|
* Determines if gg should be enabled based on environment and runtime triggers.
|
|
106
102
|
*
|
|
107
103
|
* Priority order:
|
|
108
|
-
* 1.
|
|
109
|
-
* 2.
|
|
110
|
-
* 3.
|
|
111
|
-
* 4. PROD mode → requires runtime trigger (?gg URL param or localStorage)
|
|
104
|
+
* 1. ENV override takes absolute precedence
|
|
105
|
+
* 2. DEV mode → always enabled
|
|
106
|
+
* 3. PROD mode → requires runtime trigger (?gg URL param, localStorage, or GG_ENABLED=true)
|
|
112
107
|
*/
|
|
113
108
|
function isGgEnabled() {
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// ENV hard-disable takes absolute precedence
|
|
118
|
-
// Allows completely removing gg from production builds
|
|
109
|
+
// ENV override takes absolute precedence
|
|
110
|
+
// GG_ENABLED=false: completely removes gg (even in DEV)
|
|
111
|
+
// GG_ENABLED=true: force-enables gg (even in PROD, e.g. Vercel deployments)
|
|
119
112
|
if (BROWSER) {
|
|
120
|
-
if (typeof import.meta.env?.VITE_GG_ENABLED === 'string'
|
|
121
|
-
import.meta.env.VITE_GG_ENABLED === 'false')
|
|
122
|
-
|
|
113
|
+
if (typeof import.meta.env?.VITE_GG_ENABLED === 'string') {
|
|
114
|
+
if (import.meta.env.VITE_GG_ENABLED === 'false')
|
|
115
|
+
return false;
|
|
116
|
+
if (import.meta.env.VITE_GG_ENABLED === 'true')
|
|
117
|
+
return true;
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
else {
|
|
126
121
|
if (process?.env?.GG_ENABLED === 'false') {
|
|
127
122
|
return false;
|
|
128
123
|
}
|
|
124
|
+
if (process?.env?.GG_ENABLED === 'true') {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
129
127
|
}
|
|
130
128
|
// Development - always enabled (unless ENV explicitly disabled above)
|
|
131
129
|
if (DEV)
|
|
@@ -213,7 +211,7 @@ function openInEditorUrl(fileName, line, col) {
|
|
|
213
211
|
return url;
|
|
214
212
|
}
|
|
215
213
|
export function gg(...args) {
|
|
216
|
-
if (!ggConfig.enabled
|
|
214
|
+
if (!ggConfig.enabled) {
|
|
217
215
|
// Return a no-op chain that skips logging
|
|
218
216
|
return new GgChain(args[0], args, { ns: '' }, true);
|
|
219
217
|
}
|
|
@@ -235,11 +233,11 @@ export function gg(...args) {
|
|
|
235
233
|
* <OpenInEditorLink gg={gg.here()} />
|
|
236
234
|
*/
|
|
237
235
|
gg.here = function () {
|
|
238
|
-
if (!ggConfig.enabled
|
|
236
|
+
if (!ggConfig.enabled) {
|
|
239
237
|
return { fileName: '', functionName: '', url: '' };
|
|
240
238
|
}
|
|
241
239
|
const callpoint = resolveCallpoint(3);
|
|
242
|
-
const namespace =
|
|
240
|
+
const namespace = callpoint;
|
|
243
241
|
// Log the call-site info
|
|
244
242
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
245
243
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
@@ -307,12 +305,10 @@ export class GgChain {
|
|
|
307
305
|
#args;
|
|
308
306
|
#options;
|
|
309
307
|
#flushed = false;
|
|
310
|
-
#disabled;
|
|
311
308
|
constructor(value, args, options, disabled = false) {
|
|
312
309
|
this.#value = value;
|
|
313
310
|
this.#args = args;
|
|
314
311
|
this.#options = options;
|
|
315
|
-
this.#disabled = disabled;
|
|
316
312
|
if (!disabled) {
|
|
317
313
|
// Auto-flush on microtask if not flushed synchronously by .v or another trigger
|
|
318
314
|
queueMicrotask(() => this.#flush());
|
|
@@ -384,14 +380,67 @@ export class GgChain {
|
|
|
384
380
|
*
|
|
385
381
|
* Handles namespace resolution, debug output, capture hook, and return value.
|
|
386
382
|
*/
|
|
383
|
+
/**
|
|
384
|
+
* Format a single value for the flat `msg` string written to the file sink
|
|
385
|
+
* (and stored on CapturedEntry.message). Produces readable output for the
|
|
386
|
+
* common types that String() handles poorly:
|
|
387
|
+
*
|
|
388
|
+
* - plain objects / arrays → compact JSON (depth-limited, no circular crash)
|
|
389
|
+
* - DOM nodes → "tag#id.firstClass" summary
|
|
390
|
+
* - everything else → String() as before
|
|
391
|
+
*
|
|
392
|
+
* This mirrors what `debug`'s %o/%O formatters do and what Node's util.inspect
|
|
393
|
+
* does by default — display formatting, not round-trip serialization.
|
|
394
|
+
*/
|
|
395
|
+
function formatValue(v) {
|
|
396
|
+
if (v === null)
|
|
397
|
+
return 'null';
|
|
398
|
+
if (v === undefined)
|
|
399
|
+
return 'undefined';
|
|
400
|
+
if (typeof v === 'string')
|
|
401
|
+
return v;
|
|
402
|
+
if (typeof v !== 'object' && typeof v !== 'function')
|
|
403
|
+
return String(v);
|
|
404
|
+
// DOM nodes: "div#id.firstClass" summary
|
|
405
|
+
if (typeof Element !== 'undefined' && v instanceof Element) {
|
|
406
|
+
let s = v.tagName.toLowerCase();
|
|
407
|
+
if (v.id)
|
|
408
|
+
s += `#${v.id}`;
|
|
409
|
+
if (v.classList.length)
|
|
410
|
+
s += `.${v.classList[0]}`;
|
|
411
|
+
return s;
|
|
412
|
+
}
|
|
413
|
+
if (typeof Node !== 'undefined' && v instanceof Node) {
|
|
414
|
+
return `[${v.constructor?.name ?? 'Node'}]`;
|
|
415
|
+
}
|
|
416
|
+
// Arrays and plain objects: compact JSON, depth-limited
|
|
417
|
+
try {
|
|
418
|
+
return JSON.stringify(v, jsonReplacer, 0) ?? String(v);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return String(v);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** JSON replacer that limits nesting depth to avoid huge output. */
|
|
425
|
+
function jsonReplacer(key, value) {
|
|
426
|
+
// 'this' is the parent object; key === '' at the root level
|
|
427
|
+
if (key !== '' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
428
|
+
// Count depth by checking how many ancestors are objects/arrays
|
|
429
|
+
// Simple approximation: truncate any nested object at depth > 2
|
|
430
|
+
const str = JSON.stringify(value);
|
|
431
|
+
if (str && str.length > 120)
|
|
432
|
+
return '{…}';
|
|
433
|
+
}
|
|
434
|
+
return value;
|
|
435
|
+
}
|
|
387
436
|
function ggLog(options, ...args) {
|
|
388
437
|
const { ns: nsLabel, file, line, col, src, level, stack, tableData } = options;
|
|
389
|
-
if (!ggConfig.enabled
|
|
438
|
+
if (!ggConfig.enabled) {
|
|
390
439
|
return args.length ? args[0] : { fileName: '', functionName: '', url: '' };
|
|
391
440
|
}
|
|
392
|
-
const namespace = `gg:${nsLabel}`;
|
|
393
|
-
if (
|
|
394
|
-
maxCallpointLength =
|
|
441
|
+
const namespace = nsLabel.startsWith('gg:') ? nsLabel : `gg:${nsLabel}`;
|
|
442
|
+
if (namespace.length < 80 && namespace.length > maxCallpointLength) {
|
|
443
|
+
maxCallpointLength = namespace.length;
|
|
395
444
|
}
|
|
396
445
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
397
446
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
@@ -428,7 +477,7 @@ function ggLog(options, ...args) {
|
|
|
428
477
|
namespace,
|
|
429
478
|
color: ggLogFunction.color,
|
|
430
479
|
diff,
|
|
431
|
-
message: logArgs.length === 1 ?
|
|
480
|
+
message: logArgs.length === 1 ? formatValue(logArgs[0]) : logArgs.map(formatValue).join(' '),
|
|
432
481
|
args: logArgs,
|
|
433
482
|
timestamp: Date.now(),
|
|
434
483
|
file,
|
|
@@ -439,11 +488,14 @@ function ggLog(options, ...args) {
|
|
|
439
488
|
stack,
|
|
440
489
|
tableData
|
|
441
490
|
};
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
491
|
+
// Always buffer — earlyLogBuffer is a persistent replay log so late-registering
|
|
492
|
+
// listeners (e.g. Eruda mounting after the file-sink listener) still receive entries
|
|
493
|
+
// that fired before they registered. Dispatched to all current listeners too.
|
|
494
|
+
earlyLogBuffer.push(entry);
|
|
495
|
+
if (earlyLogBuffer.length > EARLY_BUFFER_MAX)
|
|
496
|
+
earlyLogBuffer.shift();
|
|
497
|
+
if (_logListeners.size > 0) {
|
|
498
|
+
_dispatchToListeners(entry);
|
|
447
499
|
}
|
|
448
500
|
}
|
|
449
501
|
/**
|
|
@@ -457,7 +509,7 @@ function ggLog(options, ...args) {
|
|
|
457
509
|
* Returns a GgChain for chaining modifiers (.ns(), .warn(), etc.)
|
|
458
510
|
*/
|
|
459
511
|
gg._ns = function (options, ...args) {
|
|
460
|
-
const disabled = !ggConfig.enabled
|
|
512
|
+
const disabled = !ggConfig.enabled;
|
|
461
513
|
return new GgChain(args[0], args, options, disabled);
|
|
462
514
|
};
|
|
463
515
|
/**
|
|
@@ -466,14 +518,14 @@ gg._ns = function (options, ...args) {
|
|
|
466
518
|
* Called by the ggCallSitesPlugin when it rewrites gg.here() calls.
|
|
467
519
|
*/
|
|
468
520
|
gg._here = function (options) {
|
|
469
|
-
if (!ggConfig.enabled
|
|
521
|
+
if (!ggConfig.enabled) {
|
|
470
522
|
return { fileName: '', functionName: '', url: '' };
|
|
471
523
|
}
|
|
472
524
|
const { ns: nsLabel, file, line, col } = options;
|
|
473
|
-
const namespace = `gg:${nsLabel}`;
|
|
525
|
+
const namespace = nsLabel.startsWith('gg:') ? nsLabel : `gg:${nsLabel}`;
|
|
474
526
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
475
527
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
476
|
-
ggLogFunction(` 📝 ${
|
|
528
|
+
ggLogFunction(` 📝 ${namespace}`);
|
|
477
529
|
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
478
530
|
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
479
531
|
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
@@ -575,14 +627,14 @@ export class GgTimerChain {
|
|
|
575
627
|
*/
|
|
576
628
|
gg.time = function (label = 'default') {
|
|
577
629
|
const options = { ns: resolveCallpoint(3) };
|
|
578
|
-
if (ggConfig.enabled
|
|
630
|
+
if (ggConfig.enabled) {
|
|
579
631
|
timers.set(label, { start: performance.now(), options });
|
|
580
632
|
}
|
|
581
633
|
return new GgTimerChain(label, options);
|
|
582
634
|
};
|
|
583
635
|
/** gg._time() - Internal: time with call-site metadata from Vite plugin. */
|
|
584
636
|
gg._time = function (options, label = 'default') {
|
|
585
|
-
if (ggConfig.enabled
|
|
637
|
+
if (ggConfig.enabled) {
|
|
586
638
|
timers.set(label, { start: performance.now(), options });
|
|
587
639
|
}
|
|
588
640
|
return new GgTimerChain(label, options);
|
|
@@ -600,7 +652,7 @@ gg._time = function (options, label = 'default') {
|
|
|
600
652
|
* gg.timeEnd('process');
|
|
601
653
|
*/
|
|
602
654
|
gg.timeLog = function (label = 'default', ...args) {
|
|
603
|
-
if (!ggConfig.enabled
|
|
655
|
+
if (!ggConfig.enabled)
|
|
604
656
|
return;
|
|
605
657
|
const timer = timers.get(label);
|
|
606
658
|
if (timer === undefined) {
|
|
@@ -614,7 +666,7 @@ gg.timeLog = function (label = 'default', ...args) {
|
|
|
614
666
|
};
|
|
615
667
|
/** gg._timeLog() - Internal: timeLog with call-site metadata from Vite plugin. */
|
|
616
668
|
gg._timeLog = function (options, label = 'default', ...args) {
|
|
617
|
-
if (!ggConfig.enabled
|
|
669
|
+
if (!ggConfig.enabled)
|
|
618
670
|
return;
|
|
619
671
|
const timer = timers.get(label);
|
|
620
672
|
if (timer === undefined) {
|
|
@@ -636,7 +688,7 @@ gg._timeLog = function (options, label = 'default', ...args) {
|
|
|
636
688
|
* gg.timeEnd('fetch'); // logs under 'api-pipeline' namespace
|
|
637
689
|
*/
|
|
638
690
|
gg.timeEnd = function (label = 'default') {
|
|
639
|
-
if (!ggConfig.enabled
|
|
691
|
+
if (!ggConfig.enabled)
|
|
640
692
|
return;
|
|
641
693
|
const timer = timers.get(label);
|
|
642
694
|
if (timer === undefined) {
|
|
@@ -651,7 +703,7 @@ gg.timeEnd = function (label = 'default') {
|
|
|
651
703
|
};
|
|
652
704
|
/** gg._timeEnd() - Internal: timeEnd with call-site metadata from Vite plugin. */
|
|
653
705
|
gg._timeEnd = function (options, label = 'default') {
|
|
654
|
-
if (!ggConfig.enabled
|
|
706
|
+
if (!ggConfig.enabled)
|
|
655
707
|
return;
|
|
656
708
|
const timer = timers.get(label);
|
|
657
709
|
if (timer === undefined) {
|
|
@@ -922,23 +974,65 @@ export function dim() {
|
|
|
922
974
|
return createColorFunction('', '', STYLE_CODES.dim);
|
|
923
975
|
}
|
|
924
976
|
/**
|
|
925
|
-
*
|
|
926
|
-
*
|
|
977
|
+
* Multi-listener hook for capturing gg() output.
|
|
978
|
+
*
|
|
979
|
+
* Listeners can be added/removed via gg.addLogListener / gg.removeLogListener.
|
|
980
|
+
* The legacy gg._onLog setter is preserved as a backward-compatible alias
|
|
981
|
+
* (it replaces the single "legacy" slot without affecting other listeners).
|
|
927
982
|
*/
|
|
928
|
-
//
|
|
983
|
+
// Persistent replay buffer — every new listener gets a full replay of recent entries,
|
|
984
|
+
// so late-registering listeners (e.g. Eruda mounting after the file-sink listener) still
|
|
985
|
+
// receive entries that fired before they registered. In practice this only holds a handful
|
|
986
|
+
// of page-load entries. Capped at 2000 to bound memory in pathological cases.
|
|
987
|
+
// Note: this does NOT cap the JSONL file — the file grows unbounded and agents clear it
|
|
988
|
+
// explicitly via DELETE /__gg/logs.
|
|
989
|
+
const EARLY_BUFFER_MAX = 2000;
|
|
929
990
|
const earlyLogBuffer = [];
|
|
930
|
-
|
|
931
|
-
//
|
|
991
|
+
const _logListeners = new Set();
|
|
992
|
+
// Legacy single-slot: tracks the callback assigned via `gg._onLog = fn`
|
|
993
|
+
let _legacyOnLogCallback = null;
|
|
994
|
+
function _dispatchToListeners(entry) {
|
|
995
|
+
_logListeners.forEach((fn) => fn(entry));
|
|
996
|
+
}
|
|
997
|
+
// gg.addLogListener / gg.removeLogListener — primary multi-listener API
|
|
998
|
+
Object.defineProperty(gg, 'addLogListener', {
|
|
999
|
+
value(callback) {
|
|
1000
|
+
_logListeners.add(callback);
|
|
1001
|
+
// Replay early buffer to every new listener — buffer accumulates entries that
|
|
1002
|
+
// fired before any listener was registered. Each listener gets the full replay;
|
|
1003
|
+
// the buffer is never cleared so late-registering listeners (e.g. Eruda mounting
|
|
1004
|
+
// after the file-sink listener) still receive the early entries.
|
|
1005
|
+
if (earlyLogBuffer.length > 0) {
|
|
1006
|
+
earlyLogBuffer.forEach((entry) => callback(entry));
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
writable: false,
|
|
1010
|
+
configurable: true
|
|
1011
|
+
});
|
|
1012
|
+
Object.defineProperty(gg, 'removeLogListener', {
|
|
1013
|
+
value(callback) {
|
|
1014
|
+
_logListeners.delete(callback);
|
|
1015
|
+
},
|
|
1016
|
+
writable: false,
|
|
1017
|
+
configurable: true
|
|
1018
|
+
});
|
|
1019
|
+
// Legacy gg._onLog — backward-compatible single-slot alias
|
|
932
1020
|
Object.defineProperty(gg, '_onLog', {
|
|
933
1021
|
get() {
|
|
934
|
-
return
|
|
1022
|
+
return _legacyOnLogCallback;
|
|
935
1023
|
},
|
|
936
1024
|
set(callback) {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1025
|
+
// Remove previous legacy callback if any
|
|
1026
|
+
if (_legacyOnLogCallback) {
|
|
1027
|
+
_logListeners.delete(_legacyOnLogCallback);
|
|
1028
|
+
}
|
|
1029
|
+
_legacyOnLogCallback = callback;
|
|
1030
|
+
if (callback) {
|
|
1031
|
+
_logListeners.add(callback);
|
|
1032
|
+
// Replay early buffer — same policy as addLogListener
|
|
1033
|
+
if (earlyLogBuffer.length > 0) {
|
|
1034
|
+
earlyLogBuffer.forEach((entry) => callback(entry));
|
|
1035
|
+
}
|
|
942
1036
|
}
|
|
943
1037
|
}
|
|
944
1038
|
});
|
|
@@ -960,7 +1054,7 @@ export async function runGgDiagnostics() {
|
|
|
960
1054
|
await serverModulesReady;
|
|
961
1055
|
await debugReady;
|
|
962
1056
|
// Create test debugger for server-side enabled check
|
|
963
|
-
const ggLogTest = debugFactory('
|
|
1057
|
+
const ggLogTest = debugFactory('TEST');
|
|
964
1058
|
let ggMessage = '\n';
|
|
965
1059
|
const message = (s) => (ggMessage += `${s}\n`);
|
|
966
1060
|
const checkbox = (test) => (test ? '✅' : '❌');
|
|
@@ -987,15 +1081,19 @@ export async function runGgDiagnostics() {
|
|
|
987
1081
|
}
|
|
988
1082
|
message(`${checkbox(ggConfig.enabled)} gg enabled: ${ggConfig.enabled}${enableHint}`);
|
|
989
1083
|
if (!BROWSER) {
|
|
990
|
-
// Server-side:
|
|
991
|
-
|
|
992
|
-
|
|
1084
|
+
// Server-side: GG_KEEP controls which namespaces are output to the server console.
|
|
1085
|
+
// Falls back to '*' (all) if not set, so gg works zero-config.
|
|
1086
|
+
const hint = makeHint(!ggLogTest.enabled, ' (Try setting GG_KEEP=* in your .env file or shell)');
|
|
1087
|
+
message(`${checkbox(ggLogTest.enabled)} GG_KEEP env variable: ${process?.env?.GG_KEEP ?? '* (default)'}${hint}`);
|
|
993
1088
|
}
|
|
994
|
-
// Optional plugin diagnostics
|
|
995
|
-
message(makeHint(_ggCallSitesPlugin, `✅ gg-call-sites vite plugin detected! Call-site namespaces and open-in-editor links baked in at build time.`, `⚠️ gg-call-sites vite plugin not detected. Add ggCallSitesPlugin() to vite.config.ts for file:line call-site namespaces and open-in-editor links. Without plugin, using word-tuple names (e.g. calm-fox) as call-site identifiers.`));
|
|
996
1089
|
if (BROWSER && DEV) {
|
|
1090
|
+
// Optional plugin diagnostics — only meaningful in browser where Vite's define
|
|
1091
|
+
// replacements apply. Server-side Node.js load never sees __GG_TAG_PLUGIN__ = true.
|
|
1092
|
+
message(makeHint(_ggCallSitesPlugin, `✅ gg-call-sites vite plugin detected! Call-site namespaces and open-in-editor links baked in at build time.`, `⚠️ gg-call-sites vite plugin not detected. Add ggCallSitesPlugin() to vite.config.ts for file:line call-site namespaces and open-in-editor links. Without plugin, using word-tuple names (e.g. calm-fox) as call-site identifiers.`));
|
|
997
1093
|
const { status } = await fetch('/__open-in-editor?file=+');
|
|
998
1094
|
message(makeHint(status === 222, `✅ (optional) open-in-editor vite plugin detected! (status code: ${status}) Clickable links open source files in editor.`, `⚠️ (optional) open-in-editor vite plugin not detected. (status code: ${status}) Add openInEditorPlugin() to vite.config.ts for clickable links that open source files in editor`));
|
|
1095
|
+
const fileSinkStatus = await fetch('/__gg/logs', { method: 'HEAD' }).then((r) => r.status, () => 0);
|
|
1096
|
+
message(makeHint(fileSinkStatus === 200, `✅ gg-file-sink vite plugin detected! Logs written to .gg/logs-{port}.jsonl. Agent API at /__gg/logs.`, `ℹ️ (optional) gg-file-sink vite plugin not detected. Add fileSink: true to ggPlugins() options to write logs to .gg/logs-{port}.jsonl for coding agent access.`));
|
|
999
1097
|
}
|
|
1000
1098
|
console.log(ggMessage);
|
|
1001
1099
|
resetNamespaceWidth();
|
package/dist/open-in-editor.js
CHANGED
|
@@ -36,7 +36,7 @@ export default function openInEditorPlugin(specifiedEditor, srcRoot, onErrorCall
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
// Expose project root for client-side $ROOT variable (forward slashes for URI compat)
|
|
39
|
-
server.middlewares.use('/__gg
|
|
39
|
+
server.middlewares.use('/__gg/project-root', (_req, res) => {
|
|
40
40
|
res.setHeader('Content-Type', 'text/plain');
|
|
41
41
|
res.end(srcRoot.replace(/\\/g, '/'));
|
|
42
42
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared namespace pattern matching used by both the Eruda plugin and the
|
|
3
|
+
* gg-file-sink plugin.
|
|
4
|
+
*
|
|
5
|
+
* Patterns are comma-separated globs. A `-` prefix marks an exclusion:
|
|
6
|
+
* "gg:*" — all gg namespaces
|
|
7
|
+
* "gg:api:*,-gg:api:verbose:*" — gg:api:* except verbose
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Test whether `str` matches a single glob `pattern`.
|
|
11
|
+
* Supports `*` as a wildcard. Both sides are trimmed before comparison
|
|
12
|
+
* (namespaces may have trailing spaces from padEnd in the Eruda display).
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchesGlob(str: string, pattern: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Test whether `ns` matches a comma-separated pattern string.
|
|
17
|
+
*
|
|
18
|
+
* - Empty pattern, `*`, or `gg:*` → always true (fast path).
|
|
19
|
+
* - Inclusions (no `-` prefix) are OR-ed; at least one must match.
|
|
20
|
+
* - Exclusions (`-` prefix) take priority: any match → false.
|
|
21
|
+
* - If the pattern contains only exclusions, `ns` is included by default.
|
|
22
|
+
*/
|
|
23
|
+
export declare function matchesPattern(ns: string, pattern: string): boolean;
|
package/dist/pattern.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared namespace pattern matching used by both the Eruda plugin and the
|
|
3
|
+
* gg-file-sink plugin.
|
|
4
|
+
*
|
|
5
|
+
* Patterns are comma-separated globs. A `-` prefix marks an exclusion:
|
|
6
|
+
* "gg:*" — all gg namespaces
|
|
7
|
+
* "gg:api:*,-gg:api:verbose:*" — gg:api:* except verbose
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Test whether `str` matches a single glob `pattern`.
|
|
11
|
+
* Supports `*` as a wildcard. Both sides are trimmed before comparison
|
|
12
|
+
* (namespaces may have trailing spaces from padEnd in the Eruda display).
|
|
13
|
+
*/
|
|
14
|
+
export function matchesGlob(str, pattern) {
|
|
15
|
+
const s = str.trim();
|
|
16
|
+
const p = pattern.trim();
|
|
17
|
+
const regexPattern = p.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
18
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
19
|
+
return regex.test(s);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Test whether `ns` matches a comma-separated pattern string.
|
|
23
|
+
*
|
|
24
|
+
* - Empty pattern, `*`, or `gg:*` → always true (fast path).
|
|
25
|
+
* - Inclusions (no `-` prefix) are OR-ed; at least one must match.
|
|
26
|
+
* - Exclusions (`-` prefix) take priority: any match → false.
|
|
27
|
+
* - If the pattern contains only exclusions, `ns` is included by default.
|
|
28
|
+
*/
|
|
29
|
+
export function matchesPattern(ns, pattern) {
|
|
30
|
+
if (!pattern || pattern === '*' || pattern === 'gg:*')
|
|
31
|
+
return true;
|
|
32
|
+
const parts = pattern
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((p) => p.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
const inclusions = parts.filter((p) => !p.startsWith('-'));
|
|
37
|
+
const exclusions = parts.filter((p) => p.startsWith('-')).map((p) => p.slice(1));
|
|
38
|
+
const included = inclusions.length === 0 || inclusions.some((p) => matchesGlob(ns, p));
|
|
39
|
+
const excluded = exclusions.some((p) => matchesGlob(ns, p));
|
|
40
|
+
return included && !excluded;
|
|
41
|
+
}
|
package/dist/vite.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Plugin } from 'vite';
|
|
|
2
2
|
import ggCallSitesPlugin from './gg-call-sites-plugin.js';
|
|
3
3
|
import type { GgCallSitesPluginOptions } from './gg-call-sites-plugin.js';
|
|
4
4
|
import openInEditorPlugin from './open-in-editor.js';
|
|
5
|
+
import ggFileSinkPlugin from './gg-file-sink-plugin.js';
|
|
6
|
+
import type { GgFileSinkOptions } from './gg-file-sink-plugin.js';
|
|
5
7
|
export interface GgPluginsOptions {
|
|
6
8
|
/**
|
|
7
9
|
* Options for the call-sites Vite plugin (source metadata rewriting).
|
|
@@ -14,6 +16,13 @@ export interface GgPluginsOptions {
|
|
|
14
16
|
* @default true
|
|
15
17
|
*/
|
|
16
18
|
openInEditor?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Enable the file sink plugin — writes all gg() entries to `.gg/logs-{port}.jsonl`
|
|
21
|
+
* for coding agent access. Exposes `GET`/`DELETE /__gg/logs` for agent workflows.
|
|
22
|
+
* Set to `false` to disable.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
fileSink?: boolean | GgFileSinkOptions;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
28
|
* All gg Vite plugins bundled together.
|
|
@@ -21,6 +30,7 @@ export interface GgPluginsOptions {
|
|
|
21
30
|
* Includes:
|
|
22
31
|
* - `ggCallSitesPlugin` — rewrites `gg()` calls with source file/line/col metadata
|
|
23
32
|
* - `openInEditorPlugin` — adds `/__open-in-editor` dev server middleware
|
|
33
|
+
* - `ggFileSinkPlugin` — writes gg() entries to `.gg/logs-{port}.jsonl` for agent access
|
|
24
34
|
* @example
|
|
25
35
|
* ```ts
|
|
26
36
|
* import ggPlugins from '@leftium/gg/vite';
|
|
@@ -31,5 +41,5 @@ export interface GgPluginsOptions {
|
|
|
31
41
|
* ```
|
|
32
42
|
*/
|
|
33
43
|
export default function ggPlugins(options?: GgPluginsOptions): Plugin[];
|
|
34
|
-
export { ggCallSitesPlugin, openInEditorPlugin };
|
|
35
|
-
export type { GgCallSitesPluginOptions };
|
|
44
|
+
export { ggCallSitesPlugin, openInEditorPlugin, ggFileSinkPlugin };
|
|
45
|
+
export type { GgCallSitesPluginOptions, GgFileSinkOptions };
|
package/dist/vite.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import ggCallSitesPlugin from './gg-call-sites-plugin.js';
|
|
2
2
|
import openInEditorPlugin from './open-in-editor.js';
|
|
3
|
+
import ggFileSinkPlugin from './gg-file-sink-plugin.js';
|
|
3
4
|
/**
|
|
4
5
|
* All gg Vite plugins bundled together.
|
|
5
6
|
*
|
|
6
7
|
* Includes:
|
|
7
8
|
* - `ggCallSitesPlugin` — rewrites `gg()` calls with source file/line/col metadata
|
|
8
9
|
* - `openInEditorPlugin` — adds `/__open-in-editor` dev server middleware
|
|
10
|
+
* - `ggFileSinkPlugin` — writes gg() entries to `.gg/logs-{port}.jsonl` for agent access
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
11
13
|
* import ggPlugins from '@leftium/gg/vite';
|
|
@@ -21,7 +23,11 @@ export default function ggPlugins(options = {}) {
|
|
|
21
23
|
if (options.openInEditor !== false) {
|
|
22
24
|
plugins.push(openInEditorPlugin());
|
|
23
25
|
}
|
|
26
|
+
if (options.fileSink !== false) {
|
|
27
|
+
const fileSinkOptions = typeof options.fileSink === 'object' ? options.fileSink : {};
|
|
28
|
+
plugins.push(ggFileSinkPlugin(fileSinkOptions));
|
|
29
|
+
}
|
|
24
30
|
return plugins;
|
|
25
31
|
}
|
|
26
32
|
// Allow granular imports for advanced users
|
|
27
|
-
export { ggCallSitesPlugin, openInEditorPlugin };
|
|
33
|
+
export { ggCallSitesPlugin, openInEditorPlugin, ggFileSinkPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leftium/gg",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.51",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Leftium/gg.git"
|
|
@@ -40,27 +40,28 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@eslint/compat": "^2.0.
|
|
43
|
+
"@eslint/compat": "^2.0.3",
|
|
44
44
|
"@eslint/js": "^10.0.1",
|
|
45
45
|
"@picocss/pico": "^2.1.1",
|
|
46
|
-
"@sveltejs/adapter-vercel": "^6.3.
|
|
47
|
-
"@sveltejs/kit": "^2.
|
|
46
|
+
"@sveltejs/adapter-vercel": "^6.3.3",
|
|
47
|
+
"@sveltejs/kit": "^2.53.4",
|
|
48
48
|
"@sveltejs/package": "^2.5.7",
|
|
49
49
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
50
|
-
"@types/
|
|
50
|
+
"@types/estree": "^1.0.8",
|
|
51
|
+
"@types/node": "^25.3.5",
|
|
51
52
|
"add": "^2.0.6",
|
|
52
|
-
"eslint": "^10.0.
|
|
53
|
+
"eslint": "^10.0.3",
|
|
53
54
|
"eslint-config-prettier": "^10.1.8",
|
|
54
|
-
"eslint-plugin-svelte": "^3.
|
|
55
|
-
"globals": "^17.
|
|
55
|
+
"eslint-plugin-svelte": "^3.15.0",
|
|
56
|
+
"globals": "^17.4.0",
|
|
56
57
|
"prettier": "^3.8.1",
|
|
57
|
-
"prettier-plugin-svelte": "^3.
|
|
58
|
-
"publint": "^0.3.
|
|
58
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
59
|
+
"publint": "^0.3.18",
|
|
59
60
|
"supports-color": "^10.2.2",
|
|
60
|
-
"svelte": "^5.
|
|
61
|
-
"svelte-check": "^4.
|
|
61
|
+
"svelte": "^5.53.7",
|
|
62
|
+
"svelte-check": "^4.4.4",
|
|
62
63
|
"typescript": "^5.9.3",
|
|
63
|
-
"typescript-eslint": "^8.
|
|
64
|
+
"typescript-eslint": "^8.56.1",
|
|
64
65
|
"vite": "^7.3.1",
|
|
65
66
|
"vite-plugin-devtools-json": "^1.0.0",
|
|
66
67
|
"vitest": "^4.0.18"
|
|
@@ -70,12 +71,11 @@
|
|
|
70
71
|
],
|
|
71
72
|
"dependencies": {
|
|
72
73
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
73
|
-
"@tanstack/virtual-core": "^3.13.
|
|
74
|
-
"
|
|
75
|
-
"acorn": "^8.15.0",
|
|
74
|
+
"@tanstack/virtual-core": "^3.13.21",
|
|
75
|
+
"acorn": "^8.16.0",
|
|
76
76
|
"eruda": "^3.4.3",
|
|
77
77
|
"esm-env": "^1.2.2",
|
|
78
|
-
"launch-editor": "^2.
|
|
78
|
+
"launch-editor": "^2.13.1"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|
|
81
81
|
"dev": "vite dev",
|