@leftium/gg 0.0.50 → 0.0.52
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 +159 -58
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import debugFactory, { debugReady } from './debug/index.js';
|
|
2
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
|
+
import debugFactory, { debugReady } from './debug/index.js';
|
|
3
3
|
import { toWordTuple } from './words.js';
|
|
4
4
|
const _ggCallSitesPlugin = typeof __GG_TAG_PLUGIN__ !== 'undefined' ? __GG_TAG_PLUGIN__ : false;
|
|
5
5
|
/**
|
|
@@ -11,18 +11,18 @@ function createGgDebugger(namespace) {
|
|
|
11
11
|
// Store the original formatArgs
|
|
12
12
|
const originalFormatArgs = dbg.formatArgs;
|
|
13
13
|
// Override formatArgs to add padding to the namespace display
|
|
14
|
-
dbg.formatArgs =
|
|
14
|
+
dbg.formatArgs = (args) => {
|
|
15
15
|
// Call original formatArgs first
|
|
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;
|
|
@@ -110,9 +110,11 @@ function isGgEnabled() {
|
|
|
110
110
|
// GG_ENABLED=false: completely removes gg (even in DEV)
|
|
111
111
|
// GG_ENABLED=true: force-enables gg (even in PROD, e.g. Vercel deployments)
|
|
112
112
|
if (BROWSER) {
|
|
113
|
-
if (typeof import.meta.env?.VITE_GG_ENABLED === 'string'
|
|
114
|
-
import.meta.env.VITE_GG_ENABLED === 'false')
|
|
115
|
-
|
|
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;
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
else {
|
|
@@ -230,12 +232,12 @@ export function gg(...args) {
|
|
|
230
232
|
* @example
|
|
231
233
|
* <OpenInEditorLink gg={gg.here()} />
|
|
232
234
|
*/
|
|
233
|
-
gg.here =
|
|
235
|
+
gg.here = () => {
|
|
234
236
|
if (!ggConfig.enabled) {
|
|
235
237
|
return { fileName: '', functionName: '', url: '' };
|
|
236
238
|
}
|
|
237
239
|
const callpoint = resolveCallpoint(3);
|
|
238
|
-
const namespace =
|
|
240
|
+
const namespace = callpoint;
|
|
239
241
|
// Log the call-site info
|
|
240
242
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
241
243
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
@@ -303,12 +305,10 @@ export class GgChain {
|
|
|
303
305
|
#args;
|
|
304
306
|
#options;
|
|
305
307
|
#flushed = false;
|
|
306
|
-
#disabled;
|
|
307
308
|
constructor(value, args, options, disabled = false) {
|
|
308
309
|
this.#value = value;
|
|
309
310
|
this.#args = args;
|
|
310
311
|
this.#options = options;
|
|
311
|
-
this.#disabled = disabled;
|
|
312
312
|
if (!disabled) {
|
|
313
313
|
// Auto-flush on microtask if not flushed synchronously by .v or another trigger
|
|
314
314
|
queueMicrotask(() => this.#flush());
|
|
@@ -380,14 +380,67 @@ export class GgChain {
|
|
|
380
380
|
*
|
|
381
381
|
* Handles namespace resolution, debug output, capture hook, and return value.
|
|
382
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
|
+
}
|
|
383
436
|
function ggLog(options, ...args) {
|
|
384
437
|
const { ns: nsLabel, file, line, col, src, level, stack, tableData } = options;
|
|
385
438
|
if (!ggConfig.enabled) {
|
|
386
439
|
return args.length ? args[0] : { fileName: '', functionName: '', url: '' };
|
|
387
440
|
}
|
|
388
|
-
const namespace = `gg:${nsLabel}`;
|
|
389
|
-
if (
|
|
390
|
-
maxCallpointLength =
|
|
441
|
+
const namespace = nsLabel.startsWith('gg:') ? nsLabel : `gg:${nsLabel}`;
|
|
442
|
+
if (namespace.length < 80 && namespace.length > maxCallpointLength) {
|
|
443
|
+
maxCallpointLength = namespace.length;
|
|
391
444
|
}
|
|
392
445
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
393
446
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
@@ -395,14 +448,15 @@ function ggLog(options, ...args) {
|
|
|
395
448
|
// on CapturedEntry for the Eruda UI to display on hover)
|
|
396
449
|
const logArgs = args.length === 0 ? ['(no args)'] : [...args];
|
|
397
450
|
// Add level prefix emoji for info/warn/error
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
451
|
+
const levelEmoji = level === 'info' ? 'ℹ️' : level === 'warn' ? '⚠️' : level === 'error' ? '⛔' : '';
|
|
452
|
+
if (levelEmoji) {
|
|
453
|
+
if (typeof logArgs[0] === 'string') {
|
|
454
|
+
logArgs[0] = `${levelEmoji} ${logArgs[0]}`;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// Don't coerce objects to string via template literal — prepend emoji as separate arg
|
|
458
|
+
logArgs.unshift(levelEmoji);
|
|
459
|
+
}
|
|
406
460
|
}
|
|
407
461
|
// Compute diff independently of the debug library's enabled state.
|
|
408
462
|
// ggLogFunction.diff only updates when the debugger is enabled (i.e. localStorage.debug
|
|
@@ -424,7 +478,7 @@ function ggLog(options, ...args) {
|
|
|
424
478
|
namespace,
|
|
425
479
|
color: ggLogFunction.color,
|
|
426
480
|
diff,
|
|
427
|
-
message: logArgs.length === 1 ?
|
|
481
|
+
message: logArgs.length === 1 ? formatValue(logArgs[0]) : logArgs.map(formatValue).join(' '),
|
|
428
482
|
args: logArgs,
|
|
429
483
|
timestamp: Date.now(),
|
|
430
484
|
file,
|
|
@@ -435,11 +489,14 @@ function ggLog(options, ...args) {
|
|
|
435
489
|
stack,
|
|
436
490
|
tableData
|
|
437
491
|
};
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
492
|
+
// Always buffer — earlyLogBuffer is a persistent replay log so late-registering
|
|
493
|
+
// listeners (e.g. Eruda mounting after the file-sink listener) still receive entries
|
|
494
|
+
// that fired before they registered. Dispatched to all current listeners too.
|
|
495
|
+
earlyLogBuffer.push(entry);
|
|
496
|
+
if (earlyLogBuffer.length > EARLY_BUFFER_MAX)
|
|
497
|
+
earlyLogBuffer.shift();
|
|
498
|
+
if (_logListeners.size > 0) {
|
|
499
|
+
_dispatchToListeners(entry);
|
|
443
500
|
}
|
|
444
501
|
}
|
|
445
502
|
/**
|
|
@@ -452,7 +509,7 @@ function ggLog(options, ...args) {
|
|
|
452
509
|
*
|
|
453
510
|
* Returns a GgChain for chaining modifiers (.ns(), .warn(), etc.)
|
|
454
511
|
*/
|
|
455
|
-
gg._ns =
|
|
512
|
+
gg._ns = (options, ...args) => {
|
|
456
513
|
const disabled = !ggConfig.enabled;
|
|
457
514
|
return new GgChain(args[0], args, options, disabled);
|
|
458
515
|
};
|
|
@@ -461,15 +518,15 @@ gg._ns = function (options, ...args) {
|
|
|
461
518
|
*
|
|
462
519
|
* Called by the ggCallSitesPlugin when it rewrites gg.here() calls.
|
|
463
520
|
*/
|
|
464
|
-
gg._here =
|
|
521
|
+
gg._here = (options) => {
|
|
465
522
|
if (!ggConfig.enabled) {
|
|
466
523
|
return { fileName: '', functionName: '', url: '' };
|
|
467
524
|
}
|
|
468
525
|
const { ns: nsLabel, file, line, col } = options;
|
|
469
|
-
const namespace = `gg:${nsLabel}`;
|
|
526
|
+
const namespace = nsLabel.startsWith('gg:') ? nsLabel : `gg:${nsLabel}`;
|
|
470
527
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
471
528
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
472
|
-
ggLogFunction(` 📝 ${
|
|
529
|
+
ggLogFunction(` 📝 ${namespace}`);
|
|
473
530
|
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
474
531
|
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
475
532
|
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
@@ -484,9 +541,7 @@ gg._here = function (options) {
|
|
|
484
541
|
* In <script> blocks: gg._ns({ns:'...', file:'...', line:1, col:1}, args)
|
|
485
542
|
* In template markup: gg._ns(gg._o('...','...',1,1), args)
|
|
486
543
|
*/
|
|
487
|
-
gg._o =
|
|
488
|
-
return { ns, file, line, col, src };
|
|
489
|
-
};
|
|
544
|
+
gg._o = (ns, file, line, col, src) => ({ ns, file, line, col, src });
|
|
490
545
|
gg.disable = isCloudflareWorker() ? () => '' : () => debugFactory.disable();
|
|
491
546
|
gg.enable = isCloudflareWorker() ? () => { } : (ns) => debugFactory.enable(ns);
|
|
492
547
|
/**
|
|
@@ -577,7 +632,7 @@ gg.time = function (label = 'default') {
|
|
|
577
632
|
return new GgTimerChain(label, options);
|
|
578
633
|
};
|
|
579
634
|
/** gg._time() - Internal: time with call-site metadata from Vite plugin. */
|
|
580
|
-
gg._time =
|
|
635
|
+
gg._time = (options, label = 'default') => {
|
|
581
636
|
if (ggConfig.enabled) {
|
|
582
637
|
timers.set(label, { start: performance.now(), options });
|
|
583
638
|
}
|
|
@@ -609,7 +664,7 @@ gg.timeLog = function (label = 'default', ...args) {
|
|
|
609
664
|
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
610
665
|
};
|
|
611
666
|
/** gg._timeLog() - Internal: timeLog with call-site metadata from Vite plugin. */
|
|
612
|
-
gg._timeLog =
|
|
667
|
+
gg._timeLog = (options, label = 'default', ...args) => {
|
|
613
668
|
if (!ggConfig.enabled)
|
|
614
669
|
return;
|
|
615
670
|
const timer = timers.get(label);
|
|
@@ -646,7 +701,7 @@ gg.timeEnd = function (label = 'default') {
|
|
|
646
701
|
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`);
|
|
647
702
|
};
|
|
648
703
|
/** gg._timeEnd() - Internal: timeEnd with call-site metadata from Vite plugin. */
|
|
649
|
-
gg._timeEnd =
|
|
704
|
+
gg._timeEnd = (options, label = 'default') => {
|
|
650
705
|
if (!ggConfig.enabled)
|
|
651
706
|
return;
|
|
652
707
|
const timer = timers.get(label);
|
|
@@ -815,7 +870,7 @@ const STYLE_CODES = {
|
|
|
815
870
|
* Internal helper to create chainable color function with method chaining
|
|
816
871
|
*/
|
|
817
872
|
function createColorFunction(fgCode = '', bgCode = '', styleCode = '') {
|
|
818
|
-
const tagFn =
|
|
873
|
+
const tagFn = (strings, ...values) => {
|
|
819
874
|
const text = strings.reduce((acc, str, i) => acc + str + (values[i] !== undefined ? String(values[i]) : ''), '');
|
|
820
875
|
return fgCode + bgCode + styleCode + text + '\x1b[0m';
|
|
821
876
|
};
|
|
@@ -918,23 +973,65 @@ export function dim() {
|
|
|
918
973
|
return createColorFunction('', '', STYLE_CODES.dim);
|
|
919
974
|
}
|
|
920
975
|
/**
|
|
921
|
-
*
|
|
922
|
-
*
|
|
976
|
+
* Multi-listener hook for capturing gg() output.
|
|
977
|
+
*
|
|
978
|
+
* Listeners can be added/removed via gg.addLogListener / gg.removeLogListener.
|
|
979
|
+
* The legacy gg._onLog setter is preserved as a backward-compatible alias
|
|
980
|
+
* (it replaces the single "legacy" slot without affecting other listeners).
|
|
923
981
|
*/
|
|
924
|
-
//
|
|
982
|
+
// Persistent replay buffer — every new listener gets a full replay of recent entries,
|
|
983
|
+
// so late-registering listeners (e.g. Eruda mounting after the file-sink listener) still
|
|
984
|
+
// receive entries that fired before they registered. In practice this only holds a handful
|
|
985
|
+
// of page-load entries. Capped at 2000 to bound memory in pathological cases.
|
|
986
|
+
// Note: this does NOT cap the JSONL file — the file grows unbounded and agents clear it
|
|
987
|
+
// explicitly via DELETE /__gg/logs.
|
|
988
|
+
const EARLY_BUFFER_MAX = 2000;
|
|
925
989
|
const earlyLogBuffer = [];
|
|
926
|
-
|
|
927
|
-
//
|
|
990
|
+
const _logListeners = new Set();
|
|
991
|
+
// Legacy single-slot: tracks the callback assigned via `gg._onLog = fn`
|
|
992
|
+
let _legacyOnLogCallback = null;
|
|
993
|
+
function _dispatchToListeners(entry) {
|
|
994
|
+
_logListeners.forEach((fn) => fn(entry));
|
|
995
|
+
}
|
|
996
|
+
// gg.addLogListener / gg.removeLogListener — primary multi-listener API
|
|
997
|
+
Object.defineProperty(gg, 'addLogListener', {
|
|
998
|
+
value(callback) {
|
|
999
|
+
_logListeners.add(callback);
|
|
1000
|
+
// Replay early buffer to every new listener — buffer accumulates entries that
|
|
1001
|
+
// fired before any listener was registered. Each listener gets the full replay;
|
|
1002
|
+
// the buffer is never cleared so late-registering listeners (e.g. Eruda mounting
|
|
1003
|
+
// after the file-sink listener) still receive the early entries.
|
|
1004
|
+
if (earlyLogBuffer.length > 0) {
|
|
1005
|
+
earlyLogBuffer.forEach((entry) => callback(entry));
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
writable: false,
|
|
1009
|
+
configurable: true
|
|
1010
|
+
});
|
|
1011
|
+
Object.defineProperty(gg, 'removeLogListener', {
|
|
1012
|
+
value(callback) {
|
|
1013
|
+
_logListeners.delete(callback);
|
|
1014
|
+
},
|
|
1015
|
+
writable: false,
|
|
1016
|
+
configurable: true
|
|
1017
|
+
});
|
|
1018
|
+
// Legacy gg._onLog — backward-compatible single-slot alias
|
|
928
1019
|
Object.defineProperty(gg, '_onLog', {
|
|
929
1020
|
get() {
|
|
930
|
-
return
|
|
1021
|
+
return _legacyOnLogCallback;
|
|
931
1022
|
},
|
|
932
1023
|
set(callback) {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1024
|
+
// Remove previous legacy callback if any
|
|
1025
|
+
if (_legacyOnLogCallback) {
|
|
1026
|
+
_logListeners.delete(_legacyOnLogCallback);
|
|
1027
|
+
}
|
|
1028
|
+
_legacyOnLogCallback = callback;
|
|
1029
|
+
if (callback) {
|
|
1030
|
+
_logListeners.add(callback);
|
|
1031
|
+
// Replay early buffer — same policy as addLogListener
|
|
1032
|
+
if (earlyLogBuffer.length > 0) {
|
|
1033
|
+
earlyLogBuffer.forEach((entry) => callback(entry));
|
|
1034
|
+
}
|
|
938
1035
|
}
|
|
939
1036
|
}
|
|
940
1037
|
});
|
|
@@ -956,7 +1053,7 @@ export async function runGgDiagnostics() {
|
|
|
956
1053
|
await serverModulesReady;
|
|
957
1054
|
await debugReady;
|
|
958
1055
|
// Create test debugger for server-side enabled check
|
|
959
|
-
const ggLogTest = debugFactory('
|
|
1056
|
+
const ggLogTest = debugFactory('TEST');
|
|
960
1057
|
let ggMessage = '\n';
|
|
961
1058
|
const message = (s) => (ggMessage += `${s}\n`);
|
|
962
1059
|
const checkbox = (test) => (test ? '✅' : '❌');
|
|
@@ -983,15 +1080,19 @@ export async function runGgDiagnostics() {
|
|
|
983
1080
|
}
|
|
984
1081
|
message(`${checkbox(ggConfig.enabled)} gg enabled: ${ggConfig.enabled}${enableHint}`);
|
|
985
1082
|
if (!BROWSER) {
|
|
986
|
-
// Server-side:
|
|
987
|
-
|
|
988
|
-
|
|
1083
|
+
// Server-side: GG_KEEP controls which namespaces are output to the server console.
|
|
1084
|
+
// Falls back to '*' (all) if not set, so gg works zero-config.
|
|
1085
|
+
const hint = makeHint(!ggLogTest.enabled, ' (Try setting GG_KEEP=* in your .env file or shell)');
|
|
1086
|
+
message(`${checkbox(ggLogTest.enabled)} GG_KEEP env variable: ${process?.env?.GG_KEEP ?? '* (default)'}${hint}`);
|
|
989
1087
|
}
|
|
990
|
-
// Optional plugin diagnostics
|
|
991
|
-
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.`));
|
|
992
1088
|
if (BROWSER && DEV) {
|
|
1089
|
+
// Optional plugin diagnostics — only meaningful in browser where Vite's define
|
|
1090
|
+
// replacements apply. Server-side Node.js load never sees __GG_TAG_PLUGIN__ = true.
|
|
1091
|
+
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.`));
|
|
993
1092
|
const { status } = await fetch('/__open-in-editor?file=+');
|
|
994
1093
|
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`));
|
|
1094
|
+
const fileSinkStatus = await fetch('/__gg/logs', { method: 'HEAD' }).then((r) => r.status, () => 0);
|
|
1095
|
+
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.`));
|
|
995
1096
|
}
|
|
996
1097
|
console.log(ggMessage);
|
|
997
1098
|
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.52",
|
|
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",
|