@leftium/gg 0.0.33 → 0.0.35
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 +123 -36
- package/dist/GgConsole.svelte +12 -0
- package/dist/GgConsole.svelte.d.ts +4 -0
- package/dist/OpenInEditorLink.svelte +17 -7
- package/dist/OpenInEditorLink.svelte.d.ts +8 -2
- 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 +310 -29
- package/dist/eruda/types.d.ts +11 -5
- package/dist/gg-call-sites-plugin.d.ts +84 -0
- package/dist/gg-call-sites-plugin.js +600 -165
- package/dist/gg.d.ts +80 -0
- package/dist/gg.js +459 -110
- 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
package/dist/gg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import debugFactory from './debug.js';
|
|
1
|
+
import debugFactory, {} from './debug/index.js';
|
|
2
2
|
import { BROWSER, DEV } from 'esm-env';
|
|
3
3
|
import { toWordTuple } from './words.js';
|
|
4
4
|
const _ggCallSitesPlugin = typeof __GG_TAG_PLUGIN__ !== 'undefined' ? __GG_TAG_PLUGIN__ : false;
|
|
@@ -8,23 +8,21 @@ const _ggCallSitesPlugin = typeof __GG_TAG_PLUGIN__ !== 'undefined' ? __GG_TAG_P
|
|
|
8
8
|
*/
|
|
9
9
|
function createGgDebugger(namespace) {
|
|
10
10
|
const dbg = debugFactory(namespace);
|
|
11
|
-
// Store the original formatArgs
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
// Store the original formatArgs
|
|
13
12
|
const originalFormatArgs = dbg.formatArgs;
|
|
14
13
|
// Override formatArgs to add padding to the namespace display
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
14
|
dbg.formatArgs = function (args) {
|
|
17
15
|
// Call original formatArgs first
|
|
18
16
|
if (originalFormatArgs) {
|
|
19
|
-
originalFormatArgs.call(
|
|
17
|
+
originalFormatArgs.call(dbg, args);
|
|
20
18
|
}
|
|
21
19
|
// Extract the callpoint from namespace (strip 'gg:' prefix and any URL suffix)
|
|
22
|
-
const nsMatch =
|
|
23
|
-
const callpoint = nsMatch ? nsMatch[1] :
|
|
20
|
+
const nsMatch = dbg.namespace.match(/^gg:([^h]+?)(?:http|$)/);
|
|
21
|
+
const callpoint = nsMatch ? nsMatch[1] : dbg.namespace.replace(/^gg:/, '');
|
|
24
22
|
const paddedCallpoint = callpoint.padEnd(maxCallpointLength, ' ');
|
|
25
23
|
// Replace the namespace in the formatted string with padded version
|
|
26
24
|
if (typeof args[0] === 'string') {
|
|
27
|
-
args[0] = args[0].replace(
|
|
25
|
+
args[0] = args[0].replace(dbg.namespace, `gg:${paddedCallpoint}`);
|
|
28
26
|
}
|
|
29
27
|
};
|
|
30
28
|
return dbg;
|
|
@@ -167,6 +165,23 @@ const namespaceToLogFunction = new Map();
|
|
|
167
165
|
let maxCallpointLength = 0;
|
|
168
166
|
// Cache: raw stack line → word tuple (avoids re-hashing the same call site)
|
|
169
167
|
const stackLineCache = new Map();
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the callpoint for the caller at the given stack depth.
|
|
170
|
+
* depth=2 → caller of gg(), depth=3 → caller of gg.ns() (extra frame).
|
|
171
|
+
*/
|
|
172
|
+
function resolveCallpoint(depth) {
|
|
173
|
+
const rawStack = new Error().stack || '';
|
|
174
|
+
const callerLine = rawStack.split('\n')[depth] || rawStack;
|
|
175
|
+
const callerKey = callerLine.replace(/:\d+:\d+\)?$/, '').trim();
|
|
176
|
+
const callpoint = stackLineCache.get(callerKey) ?? toWordTuple(callerKey);
|
|
177
|
+
if (!stackLineCache.has(callerKey)) {
|
|
178
|
+
stackLineCache.set(callerKey, callpoint);
|
|
179
|
+
}
|
|
180
|
+
if (callpoint.length < 80 && callpoint.length > maxCallpointLength) {
|
|
181
|
+
maxCallpointLength = callpoint.length;
|
|
182
|
+
}
|
|
183
|
+
return callpoint;
|
|
184
|
+
}
|
|
170
185
|
/**
|
|
171
186
|
* Reset the namespace width tracking.
|
|
172
187
|
* Useful after configuration checks that may have long callpoint paths.
|
|
@@ -190,69 +205,9 @@ export function gg(...args) {
|
|
|
190
205
|
// When the plugin IS installed, all gg() calls are rewritten to gg._ns() at build time,
|
|
191
206
|
// so this code path only runs for un-transformed calls (i.e. plugin not installed).
|
|
192
207
|
// Same call site always produces the same word pair (e.g. "calm-fox").
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Strip line:col numbers so all gg() calls within the same function
|
|
197
|
-
// hash to the same word tuple. In minified builds, multiple gg() calls
|
|
198
|
-
// in one function differ only by column offset — we want them grouped.
|
|
199
|
-
// Chrome: "at handleClick (chunk-abc.js:1:45892)" → "at handleClick (chunk-abc.js)"
|
|
200
|
-
// Firefox: "handleClick@https://...:1:45892" → "handleClick@https://..."
|
|
201
|
-
const callerKey = callerLine.replace(/:\d+:\d+\)?$/, '').trim();
|
|
202
|
-
const callpoint = stackLineCache.get(callerKey) ?? toWordTuple(callerKey);
|
|
203
|
-
if (!stackLineCache.has(callerKey)) {
|
|
204
|
-
stackLineCache.set(callerKey, callpoint);
|
|
205
|
-
}
|
|
206
|
-
if (callpoint.length < 80 && callpoint.length > maxCallpointLength) {
|
|
207
|
-
maxCallpointLength = callpoint.length;
|
|
208
|
-
}
|
|
209
|
-
const namespace = `gg:${callpoint}`;
|
|
210
|
-
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
211
|
-
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
212
|
-
// Prepare args for logging
|
|
213
|
-
let logArgs;
|
|
214
|
-
let returnValue;
|
|
215
|
-
if (!args.length) {
|
|
216
|
-
// No arguments: return stub call-site info (no open-in-editor without plugin)
|
|
217
|
-
logArgs = [` 📝 ${callpoint} (install gg-call-sites-plugin for editor links)`];
|
|
218
|
-
returnValue = {
|
|
219
|
-
fileName: callpoint,
|
|
220
|
-
functionName: '',
|
|
221
|
-
url: ''
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
else if (args.length === 1) {
|
|
225
|
-
logArgs = [args[0]];
|
|
226
|
-
returnValue = args[0];
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
logArgs = [args[0], ...args.slice(1)];
|
|
230
|
-
returnValue = args[0];
|
|
231
|
-
}
|
|
232
|
-
// Log to console via debug
|
|
233
|
-
if (logArgs.length === 1) {
|
|
234
|
-
ggLogFunction(logArgs[0]);
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
ggLogFunction(logArgs[0], ...logArgs.slice(1));
|
|
238
|
-
}
|
|
239
|
-
// Call capture hook if registered (for Eruda plugin)
|
|
240
|
-
const entry = {
|
|
241
|
-
namespace,
|
|
242
|
-
color: ggLogFunction.color,
|
|
243
|
-
diff: ggLogFunction.diff || 0, // Millisecond diff from debug library
|
|
244
|
-
message: logArgs.length === 1 ? String(logArgs[0]) : logArgs.map(String).join(' '),
|
|
245
|
-
args: logArgs, // Keep raw args for object inspection
|
|
246
|
-
timestamp: Date.now()
|
|
247
|
-
};
|
|
248
|
-
if (_onLogCallback) {
|
|
249
|
-
_onLogCallback(entry);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
// Buffer early logs before Eruda initializes
|
|
253
|
-
earlyLogBuffer.push(entry);
|
|
254
|
-
}
|
|
255
|
-
return returnValue;
|
|
208
|
+
// depth=2: skip "Error" header [0] and gg() frame [1]
|
|
209
|
+
const callpoint = resolveCallpoint(2);
|
|
210
|
+
return ggLog({ ns: callpoint }, ...args);
|
|
256
211
|
}
|
|
257
212
|
/**
|
|
258
213
|
* gg.ns() - Log with an explicit namespace (callpoint label).
|
|
@@ -261,31 +216,43 @@ export function gg(...args) {
|
|
|
261
216
|
* across builds. For the internal plugin-generated version with file
|
|
262
217
|
* metadata, see gg._ns().
|
|
263
218
|
*
|
|
219
|
+
* The label supports template variables (substituted by the vite plugin
|
|
220
|
+
* at build time, or at runtime for $NS):
|
|
221
|
+
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
222
|
+
* $FN - enclosing function name (plugin only, empty without)
|
|
223
|
+
* $FILE - short file path (plugin only, empty without)
|
|
224
|
+
* $LINE - line number (plugin only, empty without)
|
|
225
|
+
* $COL - column number (plugin only, empty without)
|
|
226
|
+
*
|
|
264
227
|
* @param nsLabel - The namespace label (appears as gg:<nsLabel> in output)
|
|
265
228
|
* @param args - Same arguments as gg()
|
|
266
229
|
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
267
230
|
*
|
|
268
231
|
* @example
|
|
269
|
-
* gg.ns("auth", "login failed")
|
|
270
|
-
* gg.ns("
|
|
232
|
+
* gg.ns("auth", "login failed") // → gg:auth
|
|
233
|
+
* gg.ns("ERROR:$NS", msg) // → gg:ERROR:routes/+page.svelte@handleClick (with plugin)
|
|
234
|
+
* // → gg:ERROR:calm-fox (without plugin)
|
|
235
|
+
* gg.ns("$NS:validation", fieldName) // → gg:routes/+page.svelte@handleClick:validation
|
|
271
236
|
*/
|
|
272
237
|
gg.ns = function (nsLabel, ...args) {
|
|
238
|
+
// Resolve $NS at runtime (word-tuple fallback when plugin isn't installed).
|
|
239
|
+
// With the plugin, $NS is already substituted at build time before this runs.
|
|
240
|
+
// depth=3: skip "Error" [0], resolveCallpoint [1], gg.ns [2] → caller [3]
|
|
241
|
+
if (nsLabel.includes('$NS')) {
|
|
242
|
+
const callpoint = resolveCallpoint(3);
|
|
243
|
+
nsLabel = nsLabel.replace(/\$NS/g, callpoint);
|
|
244
|
+
}
|
|
273
245
|
return gg._ns({ ns: nsLabel }, ...args);
|
|
274
246
|
};
|
|
275
247
|
/**
|
|
276
|
-
*
|
|
248
|
+
* Core logging function shared by all gg methods.
|
|
277
249
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* location for open-in-editor support.
|
|
282
|
-
*
|
|
283
|
-
* @param options - { ns: string; file?: string; line?: number; col?: number }
|
|
284
|
-
* @param args - Same arguments as gg()
|
|
285
|
-
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
250
|
+
* All public methods (gg, gg.ns, gg.warn, gg.error, gg.table, etc.)
|
|
251
|
+
* funnel through this function. It handles namespace resolution,
|
|
252
|
+
* debug output, capture hook, and passthrough return.
|
|
286
253
|
*/
|
|
287
|
-
|
|
288
|
-
const { ns: nsLabel, file, line, col, src } = options;
|
|
254
|
+
function ggLog(options, ...args) {
|
|
255
|
+
const { ns: nsLabel, file, line, col, src, level, stack, tableData } = options;
|
|
289
256
|
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
290
257
|
return args.length ? args[0] : { fileName: '', functionName: '', url: '' };
|
|
291
258
|
}
|
|
@@ -315,6 +282,13 @@ gg._ns = function (options, ...args) {
|
|
|
315
282
|
logArgs = [args[0], ...args.slice(1)];
|
|
316
283
|
returnValue = args[0];
|
|
317
284
|
}
|
|
285
|
+
// Add level prefix emoji for warn/error
|
|
286
|
+
if (level === 'warn') {
|
|
287
|
+
logArgs[0] = `⚠️ ${logArgs[0]}`;
|
|
288
|
+
}
|
|
289
|
+
else if (level === 'error') {
|
|
290
|
+
logArgs[0] = `⛔ ${logArgs[0]}`;
|
|
291
|
+
}
|
|
318
292
|
// Log to console via debug
|
|
319
293
|
if (logArgs.length === 1) {
|
|
320
294
|
ggLogFunction(logArgs[0]);
|
|
@@ -333,7 +307,10 @@ gg._ns = function (options, ...args) {
|
|
|
333
307
|
file,
|
|
334
308
|
line,
|
|
335
309
|
col,
|
|
336
|
-
src
|
|
310
|
+
src,
|
|
311
|
+
level,
|
|
312
|
+
stack,
|
|
313
|
+
tableData
|
|
337
314
|
};
|
|
338
315
|
if (_onLogCallback) {
|
|
339
316
|
_onLogCallback(entry);
|
|
@@ -342,6 +319,33 @@ gg._ns = function (options, ...args) {
|
|
|
342
319
|
earlyLogBuffer.push(entry);
|
|
343
320
|
}
|
|
344
321
|
return returnValue;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* gg._ns() - Internal: log with namespace and source file metadata.
|
|
325
|
+
*
|
|
326
|
+
* Called by the ggCallSitesPlugin Vite plugin, which rewrites both bare gg()
|
|
327
|
+
* calls and manual gg.ns() calls to gg._ns({ns, file, line, col}, ...) at
|
|
328
|
+
* build time. This gives each call site a unique namespace plus the source
|
|
329
|
+
* location for open-in-editor support.
|
|
330
|
+
*
|
|
331
|
+
* @param options - { ns: string; file?: string; line?: number; col?: number }
|
|
332
|
+
* @param args - Same arguments as gg()
|
|
333
|
+
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
334
|
+
*/
|
|
335
|
+
gg._ns = function (options, ...args) {
|
|
336
|
+
return ggLog(options, ...args);
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* gg._o() - Internal: build options object for gg._ns() without object literal syntax.
|
|
340
|
+
*
|
|
341
|
+
* Used by the vite plugin to transform gg() calls in Svelte template markup,
|
|
342
|
+
* where object literals ({...}) would break Svelte's template parser.
|
|
343
|
+
*
|
|
344
|
+
* In <script> blocks: gg._ns({ns:'...', file:'...', line:1, col:1}, args)
|
|
345
|
+
* In template markup: gg._ns(gg._o('...','...',1,1), args)
|
|
346
|
+
*/
|
|
347
|
+
gg._o = function (ns, file, line, col, src) {
|
|
348
|
+
return { ns, file, line, col, src };
|
|
345
349
|
};
|
|
346
350
|
gg.disable = isCloudflareWorker() ? () => { } : debugFactory.disable;
|
|
347
351
|
gg.enable = isCloudflareWorker() ? () => { } : debugFactory.enable;
|
|
@@ -360,6 +364,357 @@ gg.clearPersist = () => {
|
|
|
360
364
|
}
|
|
361
365
|
}
|
|
362
366
|
};
|
|
367
|
+
// ── Console-like methods ───────────────────────────────────────────────
|
|
368
|
+
// Each public method (gg.warn, gg.error, etc.) has a corresponding internal
|
|
369
|
+
// method (gg._warn, gg._error, etc.) that accepts call-site metadata from
|
|
370
|
+
// the Vite plugin. The public methods use runtime stack-based callpoints
|
|
371
|
+
// as a fallback when the plugin isn't installed.
|
|
372
|
+
/**
|
|
373
|
+
* Capture a cleaned-up stack trace, stripping internal gg frames.
|
|
374
|
+
* @param skipFrames - Number of internal frames to strip from the top
|
|
375
|
+
*/
|
|
376
|
+
function captureStack(skipFrames) {
|
|
377
|
+
let stack = new Error().stack || undefined;
|
|
378
|
+
if (stack) {
|
|
379
|
+
const lines = stack.split('\n');
|
|
380
|
+
stack = lines.slice(skipFrames).join('\n');
|
|
381
|
+
}
|
|
382
|
+
return stack;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get stack from an Error arg or capture a fresh one.
|
|
386
|
+
*/
|
|
387
|
+
function getErrorStack(firstArg, skipFrames) {
|
|
388
|
+
if (firstArg instanceof Error && firstArg.stack) {
|
|
389
|
+
return firstArg.stack;
|
|
390
|
+
}
|
|
391
|
+
return captureStack(skipFrames);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* gg.warn() - Log at warning level.
|
|
395
|
+
*
|
|
396
|
+
* Passthrough: returns the first argument.
|
|
397
|
+
* In Eruda, entries are styled with a yellow/warning indicator.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* gg.warn('deprecated API used');
|
|
401
|
+
* const result = gg.warn(computeValue(), 'might be slow');
|
|
402
|
+
*/
|
|
403
|
+
gg.warn = function (...args) {
|
|
404
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
405
|
+
return args.length ? args[0] : undefined;
|
|
406
|
+
}
|
|
407
|
+
const callpoint = resolveCallpoint(3);
|
|
408
|
+
return ggLog({ ns: callpoint, level: 'warn' }, ...args);
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* gg._warn() - Internal: warn with call-site metadata from Vite plugin.
|
|
412
|
+
*/
|
|
413
|
+
gg._warn = function (options, ...args) {
|
|
414
|
+
return ggLog({ ...options, level: 'warn' }, ...args);
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* gg.error() - Log at error level.
|
|
418
|
+
*
|
|
419
|
+
* Passthrough: returns the first argument.
|
|
420
|
+
* Captures a stack trace silently — visible in Eruda via a collapsible toggle.
|
|
421
|
+
* If the first argument is an Error object, its .stack is used instead.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* gg.error('connection failed');
|
|
425
|
+
* gg.error(new Error('timeout'));
|
|
426
|
+
* const val = gg.error(response, 'unexpected status');
|
|
427
|
+
*/
|
|
428
|
+
gg.error = function (...args) {
|
|
429
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
430
|
+
return args.length ? args[0] : undefined;
|
|
431
|
+
}
|
|
432
|
+
const callpoint = resolveCallpoint(3);
|
|
433
|
+
const stack = getErrorStack(args[0], 4);
|
|
434
|
+
return ggLog({ ns: callpoint, level: 'error', stack }, ...args);
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* gg._error() - Internal: error with call-site metadata from Vite plugin.
|
|
438
|
+
*/
|
|
439
|
+
gg._error = function (options, ...args) {
|
|
440
|
+
const stack = getErrorStack(args[0], 3);
|
|
441
|
+
return ggLog({ ...options, level: 'error', stack }, ...args);
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* gg.assert() - Log only if condition is false.
|
|
445
|
+
*
|
|
446
|
+
* Like console.assert: if the first argument is falsy, logs the remaining
|
|
447
|
+
* arguments at error level. If the condition is truthy, does nothing.
|
|
448
|
+
* Passthrough: always returns the condition value.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* gg.assert(user != null, 'user should exist');
|
|
452
|
+
* gg.assert(list.length > 0, 'list is empty', list);
|
|
453
|
+
*/
|
|
454
|
+
gg.assert = function (condition, ...args) {
|
|
455
|
+
if (!condition) {
|
|
456
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
457
|
+
return condition;
|
|
458
|
+
const callpoint = resolveCallpoint(3);
|
|
459
|
+
const stack = captureStack(4);
|
|
460
|
+
const assertArgs = args.length > 0 ? args : ['Assertion failed'];
|
|
461
|
+
ggLog({ ns: callpoint, level: 'error', stack }, ...assertArgs);
|
|
462
|
+
}
|
|
463
|
+
return condition;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* gg._assert() - Internal: assert with call-site metadata from Vite plugin.
|
|
467
|
+
*/
|
|
468
|
+
gg._assert = function (options, condition, ...args) {
|
|
469
|
+
if (!condition) {
|
|
470
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
471
|
+
return condition;
|
|
472
|
+
const stack = captureStack(3);
|
|
473
|
+
const assertArgs = args.length > 0 ? args : ['Assertion failed'];
|
|
474
|
+
ggLog({ ...options, level: 'error', stack }, ...assertArgs);
|
|
475
|
+
}
|
|
476
|
+
return condition;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* gg.table() - Log tabular data.
|
|
480
|
+
*
|
|
481
|
+
* Formats an array of objects (or an object of objects) as an ASCII table.
|
|
482
|
+
* Passthrough: returns the data argument.
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* gg.table([{name: 'Alice', age: 30}, {name: 'Bob', age: 25}]);
|
|
486
|
+
* gg.table({a: {x: 1}, b: {x: 2}});
|
|
487
|
+
*/
|
|
488
|
+
gg.table = function (data, columns) {
|
|
489
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
490
|
+
return data;
|
|
491
|
+
const callpoint = resolveCallpoint(3);
|
|
492
|
+
const { keys, rows } = formatTable(data, columns);
|
|
493
|
+
ggLog({ ns: callpoint, tableData: { keys, rows } }, '(table)');
|
|
494
|
+
// Also emit a native console.table for proper rendering in browser/Node consoles
|
|
495
|
+
if (columns) {
|
|
496
|
+
console.table(data, columns);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
console.table(data);
|
|
500
|
+
}
|
|
501
|
+
return data;
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* gg._table() - Internal: table with call-site metadata from Vite plugin.
|
|
505
|
+
*/
|
|
506
|
+
gg._table = function (options, data, columns) {
|
|
507
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
508
|
+
return data;
|
|
509
|
+
const { keys, rows } = formatTable(data, columns);
|
|
510
|
+
ggLog({ ...options, tableData: { keys, rows } }, '(table)');
|
|
511
|
+
if (columns) {
|
|
512
|
+
console.table(data, columns);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
console.table(data);
|
|
516
|
+
}
|
|
517
|
+
return data;
|
|
518
|
+
};
|
|
519
|
+
// Timer storage for gg.time / gg.timeEnd / gg.timeLog
|
|
520
|
+
const timers = new Map();
|
|
521
|
+
/**
|
|
522
|
+
* gg.time() - Start a named timer.
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* gg.time('fetch');
|
|
526
|
+
* const data = await fetchData();
|
|
527
|
+
* gg.timeEnd('fetch'); // logs "+123ms fetch: 456ms"
|
|
528
|
+
*/
|
|
529
|
+
gg.time = function (label = 'default') {
|
|
530
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
531
|
+
return;
|
|
532
|
+
timers.set(label, performance.now());
|
|
533
|
+
};
|
|
534
|
+
/** gg._time() - Internal: time with call-site metadata from Vite plugin. */
|
|
535
|
+
gg._time = function (_options, label = 'default') {
|
|
536
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
537
|
+
return;
|
|
538
|
+
timers.set(label, performance.now());
|
|
539
|
+
};
|
|
540
|
+
/**
|
|
541
|
+
* gg.timeLog() - Log the current elapsed time without stopping the timer.
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* gg.time('process');
|
|
545
|
+
* // ... step 1 ...
|
|
546
|
+
* gg.timeLog('process', 'step 1 done');
|
|
547
|
+
* // ... step 2 ...
|
|
548
|
+
* gg.timeEnd('process');
|
|
549
|
+
*/
|
|
550
|
+
gg.timeLog = function (label = 'default', ...args) {
|
|
551
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
552
|
+
return;
|
|
553
|
+
const start = timers.get(label);
|
|
554
|
+
if (start === undefined) {
|
|
555
|
+
const callpoint = resolveCallpoint(3);
|
|
556
|
+
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const elapsed = performance.now() - start;
|
|
560
|
+
const callpoint = resolveCallpoint(3);
|
|
561
|
+
ggLog({ ns: callpoint }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
562
|
+
};
|
|
563
|
+
/** gg._timeLog() - Internal: timeLog with call-site metadata from Vite plugin. */
|
|
564
|
+
gg._timeLog = function (options, label = 'default', ...args) {
|
|
565
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
566
|
+
return;
|
|
567
|
+
const start = timers.get(label);
|
|
568
|
+
if (start === undefined) {
|
|
569
|
+
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const elapsed = performance.now() - start;
|
|
573
|
+
ggLog(options, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
574
|
+
};
|
|
575
|
+
/**
|
|
576
|
+
* gg.timeEnd() - Stop a named timer and log the elapsed time.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* gg.time('fetch');
|
|
580
|
+
* const data = await fetchData();
|
|
581
|
+
* gg.timeEnd('fetch'); // logs "fetch: 456.12ms"
|
|
582
|
+
*/
|
|
583
|
+
gg.timeEnd = function (label = 'default') {
|
|
584
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
585
|
+
return;
|
|
586
|
+
const start = timers.get(label);
|
|
587
|
+
if (start === undefined) {
|
|
588
|
+
const callpoint = resolveCallpoint(3);
|
|
589
|
+
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const elapsed = performance.now() - start;
|
|
593
|
+
timers.delete(label);
|
|
594
|
+
const callpoint = resolveCallpoint(3);
|
|
595
|
+
ggLog({ ns: callpoint }, `${label}: ${formatElapsed(elapsed)}`);
|
|
596
|
+
};
|
|
597
|
+
/** gg._timeEnd() - Internal: timeEnd with call-site metadata from Vite plugin. */
|
|
598
|
+
gg._timeEnd = function (options, label = 'default') {
|
|
599
|
+
if (!ggConfig.enabled || isCloudflareWorker())
|
|
600
|
+
return;
|
|
601
|
+
const start = timers.get(label);
|
|
602
|
+
if (start === undefined) {
|
|
603
|
+
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const elapsed = performance.now() - start;
|
|
607
|
+
timers.delete(label);
|
|
608
|
+
ggLog(options, `${label}: ${formatElapsed(elapsed)}`);
|
|
609
|
+
};
|
|
610
|
+
/**
|
|
611
|
+
* gg.trace() - Log with a stack trace.
|
|
612
|
+
*
|
|
613
|
+
* Like console.trace: logs the arguments plus a full stack trace.
|
|
614
|
+
* Passthrough: returns the first argument.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* gg.trace('how did we get here?');
|
|
618
|
+
* const val = gg.trace(result, 'call path');
|
|
619
|
+
*/
|
|
620
|
+
gg.trace = function (...args) {
|
|
621
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
622
|
+
return args.length ? args[0] : undefined;
|
|
623
|
+
}
|
|
624
|
+
const callpoint = resolveCallpoint(3);
|
|
625
|
+
const stack = captureStack(4);
|
|
626
|
+
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
627
|
+
return ggLog({ ns: callpoint, stack }, ...traceArgs);
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* gg._trace() - Internal: trace with call-site metadata from Vite plugin.
|
|
631
|
+
*/
|
|
632
|
+
gg._trace = function (options, ...args) {
|
|
633
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
634
|
+
return args.length ? args[0] : undefined;
|
|
635
|
+
}
|
|
636
|
+
const stack = captureStack(3);
|
|
637
|
+
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
638
|
+
return ggLog({ ...options, stack }, ...traceArgs);
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* Format elapsed time with appropriate precision.
|
|
642
|
+
* < 1s → "123.45ms", >= 1s → "1.23s", >= 60s → "1m 2.3s"
|
|
643
|
+
*/
|
|
644
|
+
function formatElapsed(ms) {
|
|
645
|
+
if (ms < 1000)
|
|
646
|
+
return `${ms.toFixed(2)}ms`;
|
|
647
|
+
if (ms < 60000)
|
|
648
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
649
|
+
const minutes = Math.floor(ms / 60000);
|
|
650
|
+
const seconds = (ms % 60000) / 1000;
|
|
651
|
+
return `${minutes}m ${seconds.toFixed(1)}s`;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Normalize data into structured keys + rows for table rendering.
|
|
655
|
+
* Used by both Eruda (HTML table) and console.table() delegation.
|
|
656
|
+
* Supports arrays of objects, arrays of primitives, and objects of objects.
|
|
657
|
+
*/
|
|
658
|
+
function formatTable(data, columns) {
|
|
659
|
+
if (data === null || data === undefined || typeof data !== 'object') {
|
|
660
|
+
return { keys: [], rows: [] };
|
|
661
|
+
}
|
|
662
|
+
// Normalize to rows: [{key, ...values}]
|
|
663
|
+
let rows;
|
|
664
|
+
let allKeys;
|
|
665
|
+
if (Array.isArray(data)) {
|
|
666
|
+
if (data.length === 0)
|
|
667
|
+
return { keys: [], rows: [] };
|
|
668
|
+
// Array of primitives
|
|
669
|
+
if (typeof data[0] !== 'object' || data[0] === null) {
|
|
670
|
+
allKeys = ['(index)', 'Value'];
|
|
671
|
+
rows = data.map((v, i) => ({ '(index)': i, Value: v }));
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Array of objects
|
|
675
|
+
const keySet = new Set();
|
|
676
|
+
keySet.add('(index)');
|
|
677
|
+
for (const item of data) {
|
|
678
|
+
if (item && typeof item === 'object') {
|
|
679
|
+
Object.keys(item).forEach((k) => keySet.add(k));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
allKeys = Array.from(keySet);
|
|
683
|
+
rows = data.map((item, i) => ({
|
|
684
|
+
'(index)': i,
|
|
685
|
+
...(item && typeof item === 'object' ? item : { Value: item })
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
// Object of objects/values
|
|
691
|
+
const entries = Object.entries(data);
|
|
692
|
+
if (entries.length === 0)
|
|
693
|
+
return { keys: [], rows: [] };
|
|
694
|
+
const keySet = new Set();
|
|
695
|
+
keySet.add('(index)');
|
|
696
|
+
for (const [, val] of entries) {
|
|
697
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
698
|
+
Object.keys(val).forEach((k) => keySet.add(k));
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
keySet.add('Value');
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
allKeys = Array.from(keySet);
|
|
705
|
+
rows = entries.map(([key, val]) => ({
|
|
706
|
+
'(index)': key,
|
|
707
|
+
...(val && typeof val === 'object' && !Array.isArray(val)
|
|
708
|
+
? val
|
|
709
|
+
: { Value: val })
|
|
710
|
+
}));
|
|
711
|
+
}
|
|
712
|
+
// Apply column filter
|
|
713
|
+
if (columns && columns.length > 0) {
|
|
714
|
+
allKeys = ['(index)', ...columns.filter((c) => allKeys.includes(c))];
|
|
715
|
+
}
|
|
716
|
+
return { keys: allKeys, rows };
|
|
717
|
+
}
|
|
363
718
|
/**
|
|
364
719
|
* Parse color string to RGB values
|
|
365
720
|
* Accepts: named colors, hex (#rgb, #rrggbb), rgb(r,g,b), rgba(r,g,b,a)
|
|
@@ -499,26 +854,28 @@ Object.defineProperty(gg, '_onLog', {
|
|
|
499
854
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
500
855
|
(function (gg) {
|
|
501
856
|
})(gg || (gg = {}));
|
|
857
|
+
// Track if diagnostics have already run to prevent double execution
|
|
858
|
+
let diagnosticsRan = false;
|
|
502
859
|
/**
|
|
503
860
|
* Run gg diagnostics and log configuration status
|
|
504
861
|
* Can be called immediately or delayed (e.g., after Eruda loads)
|
|
505
862
|
*/
|
|
506
863
|
export async function runGgDiagnostics() {
|
|
507
|
-
if (!ggConfig.showHints || isCloudflareWorker())
|
|
864
|
+
if (!ggConfig.showHints || isCloudflareWorker() || diagnosticsRan)
|
|
508
865
|
return;
|
|
866
|
+
diagnosticsRan = true;
|
|
867
|
+
// Create test debugger for server-side enabled check
|
|
509
868
|
const ggLogTest = debugFactory('gg:TEST');
|
|
510
869
|
let ggMessage = '\n';
|
|
511
|
-
// Utilities for forming ggMessage:
|
|
512
870
|
const message = (s) => (ggMessage += `${s}\n`);
|
|
513
871
|
const checkbox = (test) => (test ? '✅' : '❌');
|
|
514
872
|
const makeHint = (test, ifTrue, ifFalse = '') => (test ? ifTrue : ifFalse);
|
|
515
|
-
// Use plain console.log for diagnostics - appears in Eruda's Console tab
|
|
516
873
|
console.log(`Loaded gg module. Checking configuration...`);
|
|
517
|
-
|
|
518
|
-
|
|
874
|
+
const configOk = BROWSER ? ggConfig.enabled : ggConfig.enabled && ggLogTest.enabled;
|
|
875
|
+
if (configOk) {
|
|
519
876
|
message(`No problems detected:`);
|
|
520
877
|
if (BROWSER) {
|
|
521
|
-
message(`ℹ️
|
|
878
|
+
message(`ℹ️ gg messages appear in the Eruda GG panel. Use Settings > Native Console to also show in browser console.`);
|
|
522
879
|
}
|
|
523
880
|
}
|
|
524
881
|
else {
|
|
@@ -534,35 +891,27 @@ export async function runGgDiagnostics() {
|
|
|
534
891
|
}
|
|
535
892
|
}
|
|
536
893
|
message(`${checkbox(ggConfig.enabled)} gg enabled: ${ggConfig.enabled}${enableHint}`);
|
|
537
|
-
if (BROWSER) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
else {
|
|
542
|
-
const hint = makeHint(!ggLogTest.enabled, ' (Try `DEBUG=gg:* npm dev`)');
|
|
894
|
+
if (!BROWSER) {
|
|
895
|
+
// Server-side: check DEBUG env var (the only output path on the server)
|
|
896
|
+
const hint = makeHint(!ggLogTest.enabled, ' (Try `DEBUG=gg:* npm run dev`)');
|
|
543
897
|
if (dotenvModule) {
|
|
544
|
-
dotenvModule.config();
|
|
898
|
+
dotenvModule.config();
|
|
545
899
|
}
|
|
546
900
|
message(`${checkbox(ggLogTest.enabled)} DEBUG env variable: ${process?.env?.DEBUG}${hint}`);
|
|
547
901
|
}
|
|
548
|
-
// Optional plugin diagnostics
|
|
902
|
+
// Optional plugin diagnostics
|
|
549
903
|
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.`));
|
|
550
904
|
if (BROWSER && DEV) {
|
|
551
905
|
const { status } = await fetch('/__open-in-editor?file=+');
|
|
552
906
|
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`));
|
|
553
907
|
}
|
|
554
|
-
// Use plain console.log for diagnostics - appears in Eruda's Console tab
|
|
555
908
|
console.log(ggMessage);
|
|
556
|
-
// Reset namespace width after configuration check
|
|
557
|
-
// This prevents the long callpoint from the config check from affecting subsequent logs
|
|
558
909
|
resetNamespaceWidth();
|
|
559
910
|
}
|
|
560
|
-
// Run diagnostics immediately on module load
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
runGgDiagnostics();
|
|
567
|
-
}
|
|
911
|
+
// Run diagnostics immediately on module load ONLY in Node.js environments
|
|
912
|
+
// In browser, the Eruda loader (if configured) will call runGgDiagnostics()
|
|
913
|
+
// after Eruda is ready. If Eruda is not configured, diagnostics won't run
|
|
914
|
+
// in browser (user must manually check console or call runGgDiagnostics()).
|
|
915
|
+
if (ggConfig.showHints && !isCloudflareWorker() && !BROWSER) {
|
|
916
|
+
runGgDiagnostics();
|
|
568
917
|
}
|