@leftium/gg 0.0.47 → 0.0.48
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 +224 -5
- package/dist/gg-call-sites-plugin.d.ts +8 -5
- package/dist/gg-call-sites-plugin.js +26 -90
- package/dist/gg.d.ts +104 -60
- package/dist/gg.js +244 -267
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
- Each namespace gets a unique color for easier visual parsing.
|
|
7
7
|
- Simple syntax with wildcards to filter/hide debug output at runtime.
|
|
8
8
|
- Millisecond diff (timestamps) for each namespace.
|
|
9
|
-
-
|
|
9
|
+
- Chainable API: `.ns()`, `.warn()`, `.error()`, `.info()`, `.trace()`, `.table()`.
|
|
10
|
+
- Can be inserted into the middle of expressions (use `.v` to get the passthrough value).
|
|
10
11
|
- Can output a link that opens the source file in your editor (like VS Code).
|
|
11
12
|
- Simple to disable (turn all loggs into NOP's for production).
|
|
12
13
|
- Diagnostics/hints in dev console & terminal to help install and configure correctly.
|
|
@@ -28,8 +29,12 @@ npm add @leftium/gg
|
|
|
28
29
|
|
|
29
30
|
gg('Hello world');
|
|
30
31
|
|
|
31
|
-
// Log
|
|
32
|
-
|
|
32
|
+
// Log with modifiers
|
|
33
|
+
gg('Connection timeout').warn();
|
|
34
|
+
gg('User authenticated', user).info();
|
|
35
|
+
|
|
36
|
+
// Passthrough with .v (returns the first argument)
|
|
37
|
+
const result = gg(someFunction()).v;
|
|
33
38
|
|
|
34
39
|
// Multiple arguments
|
|
35
40
|
gg('User:', user, 'Status:', status);
|
|
@@ -57,11 +62,10 @@ export default defineConfig({
|
|
|
57
62
|
|
|
58
63
|
- **Call-sites plugin** -- rewrites `gg()` calls with source file/line/col metadata
|
|
59
64
|
- **Open-in-editor plugin** -- adds dev server middleware for click-to-open
|
|
60
|
-
- **Automatic `es2022` target** -- required for top-level await
|
|
61
65
|
|
|
62
66
|
### 3. Add the debug console (optional, recommended)
|
|
63
67
|
|
|
64
|
-
An in-browser debug console (powered by Eruda) with a dedicated GG tab for filtering and inspecting logs
|
|
68
|
+
An in-browser debug console (powered by Eruda) with a dedicated GG tab for filtering and inspecting logs -- especially useful on mobile.
|
|
65
69
|
|
|
66
70
|
```svelte
|
|
67
71
|
<!-- src/routes/+layout.svelte -->
|
|
@@ -76,6 +80,173 @@ An in-browser debug console (powered by Eruda) with a dedicated GG tab for filte
|
|
|
76
80
|
In development, the debug console appears automatically.
|
|
77
81
|
In production, add `?gg` to the URL or use a 5-tap gesture to activate.
|
|
78
82
|
|
|
83
|
+
## Chaining API
|
|
84
|
+
|
|
85
|
+
`gg()` returns a `GgChain<T>` with composable modifiers. Chain any combination, in any order. The log auto-flushes on the next microtask, or use `.v` to flush immediately and get the passthrough value.
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
import { gg } from '@leftium/gg';
|
|
89
|
+
|
|
90
|
+
// Basic logging (auto-flushes on microtask)
|
|
91
|
+
gg('hello');
|
|
92
|
+
gg('multiple', 'args', { data: 42 });
|
|
93
|
+
|
|
94
|
+
// Passthrough with .v (flushes immediately, returns first arg)
|
|
95
|
+
const result = gg(computeValue()).v;
|
|
96
|
+
const user = gg(await fetchUser()).ns('api').v;
|
|
97
|
+
|
|
98
|
+
// Log levels
|
|
99
|
+
gg('System ready').info(); // blue indicator
|
|
100
|
+
gg('Deprecated API call').warn(); // yellow indicator
|
|
101
|
+
gg('Connection failed').error(); // red indicator + stack trace
|
|
102
|
+
|
|
103
|
+
// Custom namespace
|
|
104
|
+
gg('Processing request').ns('api:handler');
|
|
105
|
+
|
|
106
|
+
// Stack trace
|
|
107
|
+
gg('Debug checkpoint').trace();
|
|
108
|
+
|
|
109
|
+
// Table formatting (also emits native console.table)
|
|
110
|
+
gg(arrayOfObjects).table();
|
|
111
|
+
gg(data).table(['name', 'age']); // filter columns
|
|
112
|
+
|
|
113
|
+
// Combine modifiers freely
|
|
114
|
+
gg('Slow query', { ms: 3200 }).ns('db').warn();
|
|
115
|
+
const rows = gg(queryResult).ns('db:query').table().v;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `.v` -- Passthrough
|
|
119
|
+
|
|
120
|
+
Use `.v` at the end of any chain to flush the log immediately and return the first argument. This lets you insert `gg()` into the middle of expressions:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Without .v: logs on microtask, returns GgChain (not the value!)
|
|
124
|
+
gg(someValue);
|
|
125
|
+
|
|
126
|
+
// With .v: logs immediately, returns someValue
|
|
127
|
+
const x = gg(someValue).v;
|
|
128
|
+
const y = gg(compute()).ns('math').warn().v;
|
|
129
|
+
|
|
130
|
+
// Insert into expressions
|
|
131
|
+
processData(gg(inputData).v);
|
|
132
|
+
return gg(result).ns('output').v;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `.ns(label)` -- Custom Namespace
|
|
136
|
+
|
|
137
|
+
Override the auto-generated namespace (file@function) with a custom label. Useful for grouping related logs across files:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
gg('Request received').ns('api:incoming');
|
|
141
|
+
gg('Response sent').ns('api:outgoing');
|
|
142
|
+
gg('Cache hit').ns('api:cache');
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Namespace labels support **template variables** that resolve from plugin-provided metadata:
|
|
146
|
+
|
|
147
|
+
| Variable | Description | Example |
|
|
148
|
+
| -------- | ----------------------------- | --------------------------------- |
|
|
149
|
+
| `$NS` | Full auto-generated callpoint | `routes/+page.svelte@handleClick` |
|
|
150
|
+
| `$FN` | Enclosing function name | `handleClick` |
|
|
151
|
+
| `$FILE` | Source file path | `routes/+page.svelte` |
|
|
152
|
+
| `$LINE` | Line number | `42` |
|
|
153
|
+
| `$COL` | Column number | `3` |
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
gg('debug info').ns('ERROR:$NS'); // → ERROR:routes/+page.svelte@handleClick
|
|
157
|
+
gg('validation').ns('$FILE:validate'); // → routes/+page.svelte:validate
|
|
158
|
+
gg('step 1').ns('TRACE:$FN'); // → TRACE:handleClick
|
|
159
|
+
gg('context').ns('$NS:debug'); // → routes/+page.svelte@handleClick:debug
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Without the Vite plugin, `$NS` falls back to a runtime word-tuple (e.g. `calm-fox`). `$FN`, `$FILE`, `$LINE`, and `$COL` require the plugin.
|
|
163
|
+
|
|
164
|
+
### `.info()` / `.warn()` / `.error()` -- Log Levels
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
gg('Server started on port 3000').info(); // blue badge
|
|
168
|
+
gg('Rate limit approaching').warn(); // yellow badge
|
|
169
|
+
gg('Unhandled exception').error(); // red badge + captures stack
|
|
170
|
+
|
|
171
|
+
// .error() with an Error object uses its .stack
|
|
172
|
+
try {
|
|
173
|
+
riskyOperation();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
gg(err).error();
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### `.trace()` -- Stack Trace
|
|
180
|
+
|
|
181
|
+
Captures a full stack trace (cleaned of internal gg frames) alongside the log entry:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
gg('How did we get here?').trace();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `.table(columns?)` -- Table Formatting
|
|
188
|
+
|
|
189
|
+
Formats the first argument as a table. Also emits a native `console.table()` call. Optionally filter columns:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
gg([
|
|
193
|
+
{ name: 'Alice', age: 30, role: 'admin' },
|
|
194
|
+
{ name: 'Bob', age: 25, role: 'user' }
|
|
195
|
+
]).table();
|
|
196
|
+
|
|
197
|
+
// Filter columns
|
|
198
|
+
gg(users).table(['name', 'role']);
|
|
199
|
+
|
|
200
|
+
// Works with objects-of-objects and arrays of primitives too
|
|
201
|
+
gg({ us: { pop: '331M' }, uk: { pop: '67M' } }).table();
|
|
202
|
+
gg(['apple', 'banana', 'cherry']).table();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Timers
|
|
206
|
+
|
|
207
|
+
Measure elapsed time with `gg.time()`, `gg.timeLog()`, and `gg.timeEnd()`:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
import { gg } from '@leftium/gg';
|
|
211
|
+
|
|
212
|
+
gg.time('fetch');
|
|
213
|
+
|
|
214
|
+
// ... some work ...
|
|
215
|
+
gg.timeLog('fetch', 'headers received'); // logs elapsed without stopping
|
|
216
|
+
|
|
217
|
+
// ... more work ...
|
|
218
|
+
gg.timeEnd('fetch'); // logs elapsed and stops timer
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Timer Namespaces
|
|
222
|
+
|
|
223
|
+
`gg.time()` returns a `GgTimerChain` that supports `.ns()` for grouping. The namespace is inherited by subsequent `timeLog` and `timeEnd` calls for the same label:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
gg.time('fetch').ns('api-pipeline');
|
|
227
|
+
|
|
228
|
+
gg.timeLog('fetch', 'step 1 done'); // logged under 'api-pipeline'
|
|
229
|
+
gg.timeEnd('fetch'); // logged under 'api-pipeline'
|
|
230
|
+
|
|
231
|
+
// Template variables work too
|
|
232
|
+
gg.time('db-query').ns('$FN:timers');
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## `gg.here()` -- Open in Editor
|
|
236
|
+
|
|
237
|
+
Returns call-site metadata for rendering "open in editor" links. Replaces the old no-arg `gg()` introspection.
|
|
238
|
+
|
|
239
|
+
```svelte
|
|
240
|
+
<script>
|
|
241
|
+
import { gg } from '@leftium/gg';
|
|
242
|
+
</script>
|
|
243
|
+
|
|
244
|
+
<!-- Pass to a link component -->
|
|
245
|
+
<OpenInEditorLink gg={gg.here()} />
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Returns `{ fileName, functionName, url }` where `url` points to the dev server's open-in-editor endpoint.
|
|
249
|
+
|
|
79
250
|
## GgConsole Options
|
|
80
251
|
|
|
81
252
|
```svelte
|
|
@@ -226,6 +397,40 @@ initGgEruda();
|
|
|
226
397
|
gg('works in any framework');
|
|
227
398
|
```
|
|
228
399
|
|
|
400
|
+
## API Reference
|
|
401
|
+
|
|
402
|
+
### `gg(value, ...args)` -- Returns `GgChain<T>`
|
|
403
|
+
|
|
404
|
+
| Method / Property | Description |
|
|
405
|
+
| ----------------- | --------------------------------------------- |
|
|
406
|
+
| `.v` | Flush log immediately, return first argument |
|
|
407
|
+
| `.ns(label)` | Set custom namespace (supports template vars) |
|
|
408
|
+
| `.info()` | Set log level to info |
|
|
409
|
+
| `.warn()` | Set log level to warn |
|
|
410
|
+
| `.error()` | Set log level to error (captures stack) |
|
|
411
|
+
| `.trace()` | Attach full stack trace |
|
|
412
|
+
| `.table(cols?)` | Format as table, optional column filter |
|
|
413
|
+
|
|
414
|
+
### `gg.time(label?)` -- Returns `GgTimerChain`
|
|
415
|
+
|
|
416
|
+
| Method | Description |
|
|
417
|
+
| ------------ | ----------------------------------------- |
|
|
418
|
+
| `.ns(label)` | Set namespace for timer group (inherited) |
|
|
419
|
+
|
|
420
|
+
### `gg.timeLog(label?, ...args)` -- Log elapsed without stopping
|
|
421
|
+
|
|
422
|
+
### `gg.timeEnd(label?)` -- Log elapsed and stop timer
|
|
423
|
+
|
|
424
|
+
### `gg.here()` -- Returns `{ fileName, functionName, url }`
|
|
425
|
+
|
|
426
|
+
### Control
|
|
427
|
+
|
|
428
|
+
| Method | Description |
|
|
429
|
+
| ------------------- | ----------------------------------------- |
|
|
430
|
+
| `gg.enable(ns)` | Enable debug output for namespace pattern |
|
|
431
|
+
| `gg.disable()` | Disable all debug output |
|
|
432
|
+
| `gg.clearPersist()` | Clear `gg-enabled` from localStorage |
|
|
433
|
+
|
|
229
434
|
## Technical Details
|
|
230
435
|
|
|
231
436
|
### Internal Debug Implementation
|
|
@@ -248,6 +453,20 @@ Features implemented internally (~290 lines of TypeScript):
|
|
|
248
453
|
|
|
249
454
|
This approach eliminates the need for vendoring, patching, and bundling third-party code, resulting in better type safety and simpler maintenance.
|
|
250
455
|
|
|
456
|
+
### Microtask Auto-Flush
|
|
457
|
+
|
|
458
|
+
When you call `gg(value)`, the log is **deferred to the next microtask**. This means chain modifiers (`.ns()`, `.warn()`, etc.) can be added synchronously after the call. If you need the value immediately (passthrough), use `.v` which forces an immediate flush.
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
// These two are equivalent:
|
|
462
|
+
gg('hello').warn(); // .warn() runs sync, log flushes on microtask
|
|
463
|
+
gg('hello').warn().v; // .v forces immediate flush + returns 'hello'
|
|
464
|
+
|
|
465
|
+
// Auto-flush means order doesn't matter for modifiers:
|
|
466
|
+
gg('x').ns('foo').warn(); // same as:
|
|
467
|
+
gg('x').warn().ns('foo'); // (both set ns and level before flush)
|
|
468
|
+
```
|
|
469
|
+
|
|
251
470
|
## Inspirations
|
|
252
471
|
|
|
253
472
|
### debug
|
|
@@ -90,16 +90,19 @@ export declare function findEnclosingFunctionFromScopes(pos: number, scopes: Fun
|
|
|
90
90
|
*/
|
|
91
91
|
export declare function escapeForString(s: string): string;
|
|
92
92
|
/**
|
|
93
|
-
* Transform gg()
|
|
93
|
+
* Transform gg() calls in source code to inject call-site metadata.
|
|
94
94
|
*
|
|
95
95
|
* Handles:
|
|
96
96
|
* - bare gg(expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
|
|
97
|
-
* - gg.
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* - gg.enable, gg.disable, gg.clearPersist, gg._onLog, gg._ns → left untouched
|
|
97
|
+
* - gg.here() → gg._here({ns, file, line, col})
|
|
98
|
+
* - gg.time/timeLog/timeEnd → gg._time/_timeLog/_timeEnd with metadata
|
|
99
|
+
* - gg.enable, gg.disable, gg.clearPersist, gg._ns, gg._onLog → left untouched
|
|
101
100
|
* - gg inside strings and comments → left untouched
|
|
102
101
|
*
|
|
102
|
+
* Chain methods (.ns(), .warn(), .error(), etc.) are NOT rewritten —
|
|
103
|
+
* they run at runtime and resolve template variables from the metadata
|
|
104
|
+
* that the plugin baked into the gg._ns() options object.
|
|
105
|
+
*
|
|
103
106
|
* For .svelte files, `svelteInfo` (from `collectCodeRanges()`) determines which
|
|
104
107
|
* positions contain JS code and provides AST-based function scope detection.
|
|
105
108
|
* Script ranges use `{...}` object literal syntax; template ranges use `gg._o()`
|
|
@@ -47,13 +47,7 @@ export default function ggCallSitesPlugin(options = {}) {
|
|
|
47
47
|
return null;
|
|
48
48
|
// Quick bail: no gg calls in this file
|
|
49
49
|
if (!code.includes('gg(') &&
|
|
50
|
-
!code.includes('gg.
|
|
51
|
-
!code.includes('gg.info(') &&
|
|
52
|
-
!code.includes('gg.warn(') &&
|
|
53
|
-
!code.includes('gg.error(') &&
|
|
54
|
-
!code.includes('gg.table(') &&
|
|
55
|
-
!code.includes('gg.trace(') &&
|
|
56
|
-
!code.includes('gg.assert(') &&
|
|
50
|
+
!code.includes('gg.here(') &&
|
|
57
51
|
!code.includes('gg.time(') &&
|
|
58
52
|
!code.includes('gg.timeLog(') &&
|
|
59
53
|
!code.includes('gg.timeEnd('))
|
|
@@ -590,16 +584,19 @@ export function escapeForString(s) {
|
|
|
590
584
|
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
591
585
|
}
|
|
592
586
|
/**
|
|
593
|
-
* Transform gg()
|
|
587
|
+
* Transform gg() calls in source code to inject call-site metadata.
|
|
594
588
|
*
|
|
595
589
|
* Handles:
|
|
596
590
|
* - bare gg(expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
|
|
597
|
-
* - gg.
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
* - gg.enable, gg.disable, gg.clearPersist, gg._onLog, gg._ns → left untouched
|
|
591
|
+
* - gg.here() → gg._here({ns, file, line, col})
|
|
592
|
+
* - gg.time/timeLog/timeEnd → gg._time/_timeLog/_timeEnd with metadata
|
|
593
|
+
* - gg.enable, gg.disable, gg.clearPersist, gg._ns, gg._onLog → left untouched
|
|
601
594
|
* - gg inside strings and comments → left untouched
|
|
602
595
|
*
|
|
596
|
+
* Chain methods (.ns(), .warn(), .error(), etc.) are NOT rewritten —
|
|
597
|
+
* they run at runtime and resolve template variables from the metadata
|
|
598
|
+
* that the plugin baked into the gg._ns() options object.
|
|
599
|
+
*
|
|
603
600
|
* For .svelte files, `svelteInfo` (from `collectCodeRanges()`) determines which
|
|
604
601
|
* positions contain JS code and provides AST-based function scope detection.
|
|
605
602
|
* Script ranges use `{...}` object literal syntax; template ranges use `gg._o()`
|
|
@@ -720,7 +717,7 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
|
|
|
720
717
|
continue;
|
|
721
718
|
}
|
|
722
719
|
}
|
|
723
|
-
// Look for 'gg' pattern — could be gg( or gg.
|
|
720
|
+
// Look for 'gg' pattern — could be gg( or gg.here( or gg.time(
|
|
724
721
|
if (code[i] === 'g' && code[i + 1] === 'g') {
|
|
725
722
|
// In .svelte files, skip gg outside code ranges (prose text, etc.)
|
|
726
723
|
const range = rangeAt(i);
|
|
@@ -734,83 +731,22 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
|
|
|
734
731
|
i++;
|
|
735
732
|
continue;
|
|
736
733
|
}
|
|
737
|
-
// Case 1: gg.
|
|
738
|
-
if (code.slice(i + 2, i +
|
|
734
|
+
// Case 1: gg.here() → gg._here({ns, file, line, col})
|
|
735
|
+
if (code.slice(i + 2, i + 9) === '.here()') {
|
|
739
736
|
const { line, col } = getLineCol(code, i);
|
|
740
737
|
const fnName = getFunctionName(i, range);
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
// Extract the first argument (the namespace string)
|
|
749
|
-
// Look for the string literal after 'gg.ns('
|
|
750
|
-
let afterNsParen = i + 6; // position after 'gg.ns('
|
|
751
|
-
while (afterNsParen < code.length && /\s/.test(code[afterNsParen]))
|
|
752
|
-
afterNsParen++;
|
|
753
|
-
const quoteChar = code[afterNsParen];
|
|
754
|
-
if (quoteChar === "'" || quoteChar === '"') {
|
|
755
|
-
// Find the closing quote
|
|
756
|
-
let j = afterNsParen + 1;
|
|
757
|
-
while (j < code.length && code[j] !== quoteChar) {
|
|
758
|
-
if (code[j] === '\\')
|
|
759
|
-
j++; // skip escaped chars
|
|
760
|
-
j++;
|
|
761
|
-
}
|
|
762
|
-
// j now points to closing quote
|
|
763
|
-
const nsLabelRaw = code.slice(afterNsParen + 1, j);
|
|
764
|
-
// Build callpoint: substitute $NS/$FN/$FILE/$LINE/$COL template variables.
|
|
765
|
-
// The auto-generated callpoint (file@fn) is what bare gg() would produce.
|
|
766
|
-
const autoCallpoint = `${shortPath}${fnName ? `@${fnName}` : ''}`;
|
|
767
|
-
const callpoint = escapeForString(nsLabelRaw
|
|
768
|
-
.replace(/\$NS/g, autoCallpoint)
|
|
769
|
-
.replace(/\$FN/g, fnName)
|
|
770
|
-
.replace(/\$FILE/g, shortPath)
|
|
771
|
-
.replace(/\$LINE/g, String(line))
|
|
772
|
-
.replace(/\$COL/g, String(col)));
|
|
773
|
-
// Check if there are more args after the string
|
|
774
|
-
const afterClosingQuote = j + 1;
|
|
775
|
-
let k = afterClosingQuote;
|
|
776
|
-
while (k < code.length && /\s/.test(code[k]))
|
|
777
|
-
k++;
|
|
778
|
-
if (code[k] === ')') {
|
|
779
|
-
// gg.ns('label') → gg._ns(opts)
|
|
780
|
-
result.push(code.slice(lastIndex, i));
|
|
781
|
-
result.push(`gg._ns(${buildOptions(range, callpoint, line, col)})`);
|
|
782
|
-
lastIndex = k + 1;
|
|
783
|
-
i = k + 1;
|
|
784
|
-
}
|
|
785
|
-
else if (code[k] === ',') {
|
|
786
|
-
// gg.ns('label', args...) → gg._ns(opts, args...)
|
|
787
|
-
let argsStart = k + 1;
|
|
788
|
-
while (argsStart < closeParenPos && /\s/.test(code[argsStart]))
|
|
789
|
-
argsStart++;
|
|
790
|
-
const argsSrc = code.slice(argsStart, closeParenPos).trim();
|
|
791
|
-
const escapedSrc = escapeForString(argsSrc);
|
|
792
|
-
result.push(code.slice(lastIndex, i));
|
|
793
|
-
result.push(`gg._ns(${buildOptions(range, callpoint, line, col, escapedSrc)}, `);
|
|
794
|
-
lastIndex = k + 1; // skip past the comma, keep args as-is
|
|
795
|
-
i = k + 1;
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
// Unexpected — leave untouched
|
|
799
|
-
i += 6;
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
modified = true;
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
// Non-string first arg to gg.ns — skip (can't extract ns at build time)
|
|
806
|
-
i += 6;
|
|
738
|
+
const callpoint = `${shortPath}${fnName ? `@${fnName}` : ''}`;
|
|
739
|
+
const escapedNs = escapeForString(callpoint);
|
|
740
|
+
result.push(code.slice(lastIndex, i));
|
|
741
|
+
result.push(`gg._here(${buildOptions(range, escapedNs, line, col)})`);
|
|
742
|
+
lastIndex = i + 9; // skip past 'gg.here()'
|
|
743
|
+
i = lastIndex;
|
|
744
|
+
modified = true;
|
|
807
745
|
continue;
|
|
808
746
|
}
|
|
809
|
-
// Case
|
|
810
|
-
//
|
|
811
|
-
const dotMethodMatch = code
|
|
812
|
-
.slice(i + 2)
|
|
813
|
-
.match(/^\.(info|warn|error|table|trace|assert|time|timeLog|timeEnd)\(/);
|
|
747
|
+
// Case 2: gg.time/timeLog/timeEnd → gg._time/_timeLog/_timeEnd
|
|
748
|
+
// Timer methods are rewritten to inject call-site metadata.
|
|
749
|
+
const dotMethodMatch = code.slice(i + 2).match(/^\.(time|timeLog|timeEnd)\(/);
|
|
814
750
|
if (dotMethodMatch) {
|
|
815
751
|
const methodName = dotMethodMatch[1];
|
|
816
752
|
const internalName = `_${methodName}`;
|
|
@@ -828,13 +764,13 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
|
|
|
828
764
|
const argsText = code.slice(openParenPos + 1, closeParenPos).trim();
|
|
829
765
|
result.push(code.slice(lastIndex, i));
|
|
830
766
|
if (argsText === '') {
|
|
831
|
-
// gg.
|
|
767
|
+
// gg.time() → gg._time(opts)
|
|
832
768
|
result.push(`gg.${internalName}(${buildOptions(range, escapedNs, line, col)})`);
|
|
833
769
|
lastIndex = closeParenPos + 1;
|
|
834
770
|
i = closeParenPos + 1;
|
|
835
771
|
}
|
|
836
772
|
else {
|
|
837
|
-
// gg.
|
|
773
|
+
// gg.time('label') → gg._time(opts, 'label')
|
|
838
774
|
const escapedSrc = escapeForString(argsText);
|
|
839
775
|
result.push(`gg.${internalName}(${buildOptions(range, escapedNs, line, col, escapedSrc)}, `);
|
|
840
776
|
lastIndex = openParenPos + 1; // keep original args
|
|
@@ -843,12 +779,12 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
|
|
|
843
779
|
modified = true;
|
|
844
780
|
continue;
|
|
845
781
|
}
|
|
846
|
-
// Skip other gg.* calls (gg.enable, gg.disable, gg._ns, gg._onLog,
|
|
782
|
+
// Skip other gg.* calls (gg.enable, gg.disable, gg._ns, gg._onLog, etc.)
|
|
847
783
|
if (code[i + 2] === '.') {
|
|
848
784
|
i += 3;
|
|
849
785
|
continue;
|
|
850
786
|
}
|
|
851
|
-
// Case
|
|
787
|
+
// Case 3: bare gg(...) → gg._ns({ns, file, line, col, src}, ...)
|
|
852
788
|
if (code[i + 2] === '(') {
|
|
853
789
|
const { line, col } = getLineCol(code, i);
|
|
854
790
|
const fnName = getFunctionName(i, range);
|
package/dist/gg.d.ts
CHANGED
|
@@ -21,16 +21,91 @@ interface CapturedEntry {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
type OnLogCallback = (entry: CapturedEntry) => void;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Log a value and return a chainable wrapper.
|
|
26
|
+
*
|
|
27
|
+
* Chain modifiers to configure the log entry:
|
|
28
|
+
* - `.ns('label')` — set a custom namespace
|
|
29
|
+
* - `.warn()` / `.error()` / `.info()` — set log level
|
|
30
|
+
* - `.trace()` — include stack trace
|
|
31
|
+
* - `.table()` — format as ASCII table
|
|
32
|
+
* - `.v` — flush immediately and return the passthrough value
|
|
33
|
+
*
|
|
34
|
+
* Without `.v`, the log auto-flushes on the next microtask.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* gg(value) // log with auto namespace
|
|
38
|
+
* gg(value).ns('label').warn() // log with namespace + warn level
|
|
39
|
+
* const x = gg(value).v // passthrough
|
|
40
|
+
* const x = gg(value).ns('foo').v // passthrough with namespace
|
|
41
|
+
*/
|
|
42
|
+
export declare function gg<T>(arg: T, ...args: unknown[]): GgChain<T>;
|
|
43
|
+
/** Internal options for the core log function */
|
|
44
|
+
interface LogOptions {
|
|
45
|
+
ns: string;
|
|
46
|
+
file?: string;
|
|
47
|
+
line?: number;
|
|
48
|
+
col?: number;
|
|
49
|
+
src?: string;
|
|
50
|
+
level?: LogLevel;
|
|
51
|
+
stack?: string;
|
|
52
|
+
tableData?: {
|
|
53
|
+
keys: string[];
|
|
54
|
+
rows: Array<Record<string, unknown>>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Chainable wrapper returned by gg(). Collects modifiers (.ns(), .warn(), etc.)
|
|
59
|
+
* and auto-flushes the log on the next microtask. Use `.v` to flush immediately
|
|
60
|
+
* and get the passthrough value.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* gg(value) // logs on microtask
|
|
64
|
+
* gg(value).ns('label').warn() // logs with namespace + warn level
|
|
65
|
+
* const x = gg(value).v // logs immediately, returns value
|
|
66
|
+
* const x = gg(value).ns('foo').v // logs with namespace, returns value
|
|
67
|
+
*/
|
|
68
|
+
export declare class GgChain<T> {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(value: T, args: unknown[], options: LogOptions, disabled?: boolean);
|
|
71
|
+
/** Set a custom namespace for this log entry.
|
|
72
|
+
*
|
|
73
|
+
* Supports template variables (resolved from plugin-provided metadata):
|
|
74
|
+
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
75
|
+
* $FN - enclosing function name (extracted from $NS)
|
|
76
|
+
* $FILE - short file path (extracted from $NS)
|
|
77
|
+
* $LINE - line number
|
|
78
|
+
* $COL - column number
|
|
79
|
+
*/
|
|
80
|
+
ns(label: string): GgChain<T>;
|
|
81
|
+
/** Set log level to info (blue indicator). */
|
|
82
|
+
info(): GgChain<T>;
|
|
83
|
+
/** Set log level to warn (yellow indicator). */
|
|
84
|
+
warn(): GgChain<T>;
|
|
85
|
+
/** Set log level to error (red indicator, captures stack trace). */
|
|
86
|
+
error(): GgChain<T>;
|
|
87
|
+
/** Include a full stack trace with this log entry. */
|
|
88
|
+
trace(): GgChain<T>;
|
|
89
|
+
/** Format the log output as an ASCII table. */
|
|
90
|
+
table(columns?: string[]): GgChain<T>;
|
|
91
|
+
/** Flush the log immediately and return the passthrough value. */
|
|
92
|
+
get v(): T;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Chainable wrapper returned by gg.time(). Only supports .ns() for setting
|
|
96
|
+
* the namespace for the entire timer group (inherited by timeLog/timeEnd).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* gg.time('fetch').ns('api-pipeline')
|
|
100
|
+
* gg.time('fetch').ns('$FN:timers') // template vars work too
|
|
101
|
+
*/
|
|
102
|
+
export declare class GgTimerChain {
|
|
103
|
+
#private;
|
|
104
|
+
constructor(label: string, options: LogOptions);
|
|
105
|
+
/** Set a custom namespace for this timer group.
|
|
106
|
+
* Supports the same template variables as GgChain.ns().
|
|
107
|
+
*/
|
|
108
|
+
ns(label: string): GgTimerChain;
|
|
34
109
|
}
|
|
35
110
|
/**
|
|
36
111
|
* ANSI Color Helpers for gg()
|
|
@@ -134,7 +209,6 @@ export declare function underline(): ChainableColorFn;
|
|
|
134
209
|
export declare function dim(): ChainableColorFn;
|
|
135
210
|
export declare namespace gg {
|
|
136
211
|
let _onLog: OnLogCallback | null;
|
|
137
|
-
let ns: (nsLabel: string, ...args: unknown[]) => unknown;
|
|
138
212
|
let _ns: (options: {
|
|
139
213
|
ns: string;
|
|
140
214
|
file?: string;
|
|
@@ -143,7 +217,7 @@ export declare namespace gg {
|
|
|
143
217
|
src?: string;
|
|
144
218
|
level?: LogLevel;
|
|
145
219
|
stack?: string;
|
|
146
|
-
}, ...args: unknown[]) => unknown
|
|
220
|
+
}, ...args: unknown[]) => GgChain<unknown>;
|
|
147
221
|
let _o: (ns: string, file?: string, line?: number, col?: number, src?: string) => {
|
|
148
222
|
ns: string;
|
|
149
223
|
file?: string;
|
|
@@ -151,64 +225,34 @@ export declare namespace gg {
|
|
|
151
225
|
col?: number;
|
|
152
226
|
src?: string;
|
|
153
227
|
};
|
|
154
|
-
let
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let
|
|
160
|
-
let timeLog: (label?: string, ...args: unknown[]) => void;
|
|
161
|
-
let timeEnd: (label?: string) => void;
|
|
162
|
-
let trace: (...args: unknown[]) => unknown;
|
|
163
|
-
let _info: (options: {
|
|
164
|
-
ns: string;
|
|
165
|
-
file?: string;
|
|
166
|
-
line?: number;
|
|
167
|
-
col?: number;
|
|
168
|
-
src?: string;
|
|
169
|
-
}, ...args: unknown[]) => unknown;
|
|
170
|
-
let _warn: (options: {
|
|
171
|
-
ns: string;
|
|
172
|
-
file?: string;
|
|
173
|
-
line?: number;
|
|
174
|
-
col?: number;
|
|
175
|
-
src?: string;
|
|
176
|
-
}, ...args: unknown[]) => unknown;
|
|
177
|
-
let _error: (options: {
|
|
178
|
-
ns: string;
|
|
179
|
-
file?: string;
|
|
180
|
-
line?: number;
|
|
181
|
-
col?: number;
|
|
182
|
-
src?: string;
|
|
183
|
-
}, ...args: unknown[]) => unknown;
|
|
184
|
-
let _assert: (options: {
|
|
185
|
-
ns: string;
|
|
186
|
-
file?: string;
|
|
187
|
-
line?: number;
|
|
188
|
-
col?: number;
|
|
189
|
-
src?: string;
|
|
190
|
-
}, condition: unknown, ...args: unknown[]) => unknown;
|
|
191
|
-
let _table: (options: {
|
|
192
|
-
ns: string;
|
|
193
|
-
file?: string;
|
|
194
|
-
line?: number;
|
|
195
|
-
col?: number;
|
|
196
|
-
src?: string;
|
|
197
|
-
}, data: unknown, columns?: string[]) => unknown;
|
|
198
|
-
let _trace: (options: {
|
|
228
|
+
let here: () => {
|
|
229
|
+
fileName: string;
|
|
230
|
+
functionName: string;
|
|
231
|
+
url: string;
|
|
232
|
+
};
|
|
233
|
+
let _here: (options: {
|
|
199
234
|
ns: string;
|
|
200
235
|
file?: string;
|
|
201
236
|
line?: number;
|
|
202
237
|
col?: number;
|
|
203
|
-
|
|
204
|
-
|
|
238
|
+
}) => {
|
|
239
|
+
fileName: string;
|
|
240
|
+
functionName: string;
|
|
241
|
+
url: string;
|
|
242
|
+
};
|
|
243
|
+
let enable: (ns: string) => void;
|
|
244
|
+
let disable: () => string;
|
|
245
|
+
let clearPersist: () => void;
|
|
246
|
+
let time: (label?: string) => GgTimerChain;
|
|
247
|
+
let timeLog: (label?: string, ...args: unknown[]) => void;
|
|
248
|
+
let timeEnd: (label?: string) => void;
|
|
205
249
|
let _time: (options: {
|
|
206
250
|
ns: string;
|
|
207
251
|
file?: string;
|
|
208
252
|
line?: number;
|
|
209
253
|
col?: number;
|
|
210
254
|
src?: string;
|
|
211
|
-
}, label?: string) =>
|
|
255
|
+
}, label?: string) => GgTimerChain;
|
|
212
256
|
let _timeLog: (options: {
|
|
213
257
|
ns: string;
|
|
214
258
|
file?: string;
|
package/dist/gg.js
CHANGED
|
@@ -221,7 +221,8 @@ function openInEditorUrl(fileName, line, col) {
|
|
|
221
221
|
}
|
|
222
222
|
export function gg(...args) {
|
|
223
223
|
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
224
|
-
|
|
224
|
+
// Return a no-op chain that skips logging
|
|
225
|
+
return new GgChain(args[0], args, { ns: '' }, true);
|
|
225
226
|
}
|
|
226
227
|
// Without the call-sites plugin, use cheap stack hash → deterministic word tuple.
|
|
227
228
|
// When the plugin IS installed, all gg() calls are rewritten to gg._ns() at build time,
|
|
@@ -229,49 +230,166 @@ export function gg(...args) {
|
|
|
229
230
|
// Same call site always produces the same word pair (e.g. "calm-fox").
|
|
230
231
|
// depth=2: skip "Error" header [0] and gg() frame [1]
|
|
231
232
|
const callpoint = resolveCallpoint(2);
|
|
232
|
-
return
|
|
233
|
+
return new GgChain(args[0], args, { ns: callpoint });
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
235
|
-
* gg.
|
|
236
|
-
*
|
|
237
|
-
* Users call gg.ns() directly to set a meaningful label that survives
|
|
238
|
-
* across builds. For the internal plugin-generated version with file
|
|
239
|
-
* metadata, see gg._ns().
|
|
240
|
-
*
|
|
241
|
-
* The label supports template variables (substituted by the vite plugin
|
|
242
|
-
* at build time, or at runtime for $NS):
|
|
243
|
-
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
244
|
-
* $FN - enclosing function name (plugin only, empty without)
|
|
245
|
-
* $FILE - short file path (plugin only, empty without)
|
|
246
|
-
* $LINE - line number (plugin only, empty without)
|
|
247
|
-
* $COL - column number (plugin only, empty without)
|
|
236
|
+
* gg.here() - Return call-site info for open-in-editor.
|
|
248
237
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
238
|
+
* Replaces the old no-arg gg() overload. Returns an object with the
|
|
239
|
+
* file name, function name, and URL for opening the source in an editor.
|
|
252
240
|
*
|
|
253
241
|
* @example
|
|
254
|
-
* gg.
|
|
255
|
-
* gg.ns("ERROR:$NS", msg) // → gg:ERROR:routes/+page.svelte@handleClick (with plugin)
|
|
256
|
-
* // → gg:ERROR:calm-fox (without plugin)
|
|
257
|
-
* gg.ns("$NS:validation", fieldName) // → gg:routes/+page.svelte@handleClick:validation
|
|
242
|
+
* <OpenInEditorLink gg={gg.here()} />
|
|
258
243
|
*/
|
|
259
|
-
gg.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// depth=3: skip "Error" [0], resolveCallpoint [1], gg.ns [2] → caller [3]
|
|
263
|
-
if (nsLabel.includes('$NS')) {
|
|
264
|
-
const callpoint = resolveCallpoint(3);
|
|
265
|
-
nsLabel = nsLabel.replace(/\$NS/g, callpoint);
|
|
244
|
+
gg.here = function () {
|
|
245
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
246
|
+
return { fileName: '', functionName: '', url: '' };
|
|
266
247
|
}
|
|
267
|
-
|
|
248
|
+
const callpoint = resolveCallpoint(3);
|
|
249
|
+
const namespace = `gg:${callpoint}`;
|
|
250
|
+
// Log the call-site info
|
|
251
|
+
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
252
|
+
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
253
|
+
ggLogFunction(` 📝 ${callpoint}`);
|
|
254
|
+
return {
|
|
255
|
+
fileName: callpoint,
|
|
256
|
+
functionName: callpoint.includes('@') ? callpoint.split('@').pop() || '' : '',
|
|
257
|
+
url: ''
|
|
258
|
+
};
|
|
268
259
|
};
|
|
260
|
+
/**
|
|
261
|
+
* Resolve template variables in a namespace label using metadata from the plugin.
|
|
262
|
+
*
|
|
263
|
+
* The Vite plugin bakes the auto-generated callpoint into options.ns at build time
|
|
264
|
+
* (e.g. "routes/+page.svelte@handleClick"). This function extracts components from
|
|
265
|
+
* that callpoint and substitutes template variables:
|
|
266
|
+
*
|
|
267
|
+
* $NS - the full auto-generated callpoint (or runtime word-tuple fallback)
|
|
268
|
+
* $FN - the function name portion (after @)
|
|
269
|
+
* $FILE - the file path portion (before @)
|
|
270
|
+
* $LINE - the line number
|
|
271
|
+
* $COL - the column number
|
|
272
|
+
*/
|
|
273
|
+
function resolveNsTemplateVars(label, options) {
|
|
274
|
+
if (!label.includes('$'))
|
|
275
|
+
return label;
|
|
276
|
+
const ns = options.ns || '';
|
|
277
|
+
// $NS: use the full auto-generated callpoint. If no plugin, fall back to runtime stack hash.
|
|
278
|
+
if (label.includes('$NS')) {
|
|
279
|
+
const callpoint = ns || resolveCallpoint(4);
|
|
280
|
+
label = label.replace(/\$NS/g, callpoint);
|
|
281
|
+
}
|
|
282
|
+
// $FN: extract function name from "file@fn" format
|
|
283
|
+
if (label.includes('$FN')) {
|
|
284
|
+
const fn = ns.includes('@') ? ns.split('@').pop() || '' : '';
|
|
285
|
+
label = label.replace(/\$FN/g, fn);
|
|
286
|
+
}
|
|
287
|
+
// $FILE: extract file path from "file@fn" format
|
|
288
|
+
if (label.includes('$FILE')) {
|
|
289
|
+
const file = ns.includes('@') ? ns.split('@')[0] : ns;
|
|
290
|
+
label = label.replace(/\$FILE/g, file);
|
|
291
|
+
}
|
|
292
|
+
// $LINE / $COL: from plugin metadata
|
|
293
|
+
if (label.includes('$LINE')) {
|
|
294
|
+
label = label.replace(/\$LINE/g, String(options.line ?? ''));
|
|
295
|
+
}
|
|
296
|
+
if (label.includes('$COL')) {
|
|
297
|
+
label = label.replace(/\$COL/g, String(options.col ?? ''));
|
|
298
|
+
}
|
|
299
|
+
return label;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Chainable wrapper returned by gg(). Collects modifiers (.ns(), .warn(), etc.)
|
|
303
|
+
* and auto-flushes the log on the next microtask. Use `.v` to flush immediately
|
|
304
|
+
* and get the passthrough value.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* gg(value) // logs on microtask
|
|
308
|
+
* gg(value).ns('label').warn() // logs with namespace + warn level
|
|
309
|
+
* const x = gg(value).v // logs immediately, returns value
|
|
310
|
+
* const x = gg(value).ns('foo').v // logs with namespace, returns value
|
|
311
|
+
*/
|
|
312
|
+
export class GgChain {
|
|
313
|
+
#value;
|
|
314
|
+
#args;
|
|
315
|
+
#options;
|
|
316
|
+
#flushed = false;
|
|
317
|
+
#disabled;
|
|
318
|
+
constructor(value, args, options, disabled = false) {
|
|
319
|
+
this.#value = value;
|
|
320
|
+
this.#args = args;
|
|
321
|
+
this.#options = options;
|
|
322
|
+
this.#disabled = disabled;
|
|
323
|
+
if (!disabled) {
|
|
324
|
+
// Auto-flush on microtask if not flushed synchronously by .v or another trigger
|
|
325
|
+
queueMicrotask(() => this.#flush());
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** Set a custom namespace for this log entry.
|
|
329
|
+
*
|
|
330
|
+
* Supports template variables (resolved from plugin-provided metadata):
|
|
331
|
+
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
332
|
+
* $FN - enclosing function name (extracted from $NS)
|
|
333
|
+
* $FILE - short file path (extracted from $NS)
|
|
334
|
+
* $LINE - line number
|
|
335
|
+
* $COL - column number
|
|
336
|
+
*/
|
|
337
|
+
ns(label) {
|
|
338
|
+
this.#options.ns = resolveNsTemplateVars(label, this.#options);
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
/** Set log level to info (blue indicator). */
|
|
342
|
+
info() {
|
|
343
|
+
this.#options.level = 'info';
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
/** Set log level to warn (yellow indicator). */
|
|
347
|
+
warn() {
|
|
348
|
+
this.#options.level = 'warn';
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
/** Set log level to error (red indicator, captures stack trace). */
|
|
352
|
+
error() {
|
|
353
|
+
this.#options.level = 'error';
|
|
354
|
+
this.#options.stack = getErrorStack(this.#args[0], 3);
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
/** Include a full stack trace with this log entry. */
|
|
358
|
+
trace() {
|
|
359
|
+
this.#options.stack = captureStack(3);
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
/** Format the log output as an ASCII table. */
|
|
363
|
+
table(columns) {
|
|
364
|
+
const { keys, rows } = formatTable(this.#args[0], columns);
|
|
365
|
+
this.#options.tableData = { keys, rows };
|
|
366
|
+
// Override args to show '(table)' label, matching original gg.table() behavior
|
|
367
|
+
this.#args = ['(table)'];
|
|
368
|
+
// Also emit native console.table
|
|
369
|
+
if (columns) {
|
|
370
|
+
console.table(this.#value, columns);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
console.table(this.#value);
|
|
374
|
+
}
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
/** Flush the log immediately and return the passthrough value. */
|
|
378
|
+
get v() {
|
|
379
|
+
this.#flush();
|
|
380
|
+
return this.#value;
|
|
381
|
+
}
|
|
382
|
+
#flush() {
|
|
383
|
+
if (this.#flushed)
|
|
384
|
+
return;
|
|
385
|
+
this.#flushed = true;
|
|
386
|
+
ggLog(this.#options, ...this.#args);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
269
389
|
/**
|
|
270
390
|
* Core logging function shared by all gg methods.
|
|
271
391
|
*
|
|
272
|
-
*
|
|
273
|
-
* funnel through this function. It handles namespace resolution,
|
|
274
|
-
* debug output, capture hook, and passthrough return.
|
|
392
|
+
* Handles namespace resolution, debug output, capture hook, and return value.
|
|
275
393
|
*/
|
|
276
394
|
function ggLog(options, ...args) {
|
|
277
395
|
const { ns: nsLabel, file, line, col, src, level, stack, tableData } = options;
|
|
@@ -286,24 +404,7 @@ function ggLog(options, ...args) {
|
|
|
286
404
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
287
405
|
// Prepare args for logging (console output is value-only; src is carried
|
|
288
406
|
// on CapturedEntry for the Eruda UI to display on hover)
|
|
289
|
-
|
|
290
|
-
let returnValue;
|
|
291
|
-
if (!args.length) {
|
|
292
|
-
// No arguments: return call-site info for open-in-editor
|
|
293
|
-
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
294
|
-
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
295
|
-
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
296
|
-
logArgs = [` 📝 ${nsLabel}`];
|
|
297
|
-
returnValue = { fileName, functionName, url };
|
|
298
|
-
}
|
|
299
|
-
else if (args.length === 1) {
|
|
300
|
-
logArgs = [args[0]];
|
|
301
|
-
returnValue = args[0];
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
logArgs = [args[0], ...args.slice(1)];
|
|
305
|
-
returnValue = args[0];
|
|
306
|
-
}
|
|
407
|
+
const logArgs = args.length === 0 ? ['(no args)'] : [...args];
|
|
307
408
|
// Add level prefix emoji for info/warn/error
|
|
308
409
|
if (level === 'info') {
|
|
309
410
|
logArgs[0] = `ℹ️ ${logArgs[0]}`;
|
|
@@ -351,22 +452,39 @@ function ggLog(options, ...args) {
|
|
|
351
452
|
else {
|
|
352
453
|
earlyLogBuffer.push(entry);
|
|
353
454
|
}
|
|
354
|
-
return returnValue;
|
|
355
455
|
}
|
|
356
456
|
/**
|
|
357
457
|
* gg._ns() - Internal: log with namespace and source file metadata.
|
|
358
458
|
*
|
|
359
|
-
* Called by the ggCallSitesPlugin Vite plugin, which rewrites
|
|
360
|
-
* calls
|
|
361
|
-
*
|
|
459
|
+
* Called by the ggCallSitesPlugin Vite plugin, which rewrites bare gg()
|
|
460
|
+
* calls to gg._ns({ns, file, line, col, src}, ...) at build time.
|
|
461
|
+
* This gives each call site a unique namespace plus the source
|
|
362
462
|
* location for open-in-editor support.
|
|
363
463
|
*
|
|
364
|
-
*
|
|
365
|
-
* @param args - Same arguments as gg()
|
|
366
|
-
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
464
|
+
* Returns a GgChain for chaining modifiers (.ns(), .warn(), etc.)
|
|
367
465
|
*/
|
|
368
466
|
gg._ns = function (options, ...args) {
|
|
369
|
-
|
|
467
|
+
const disabled = !ggConfig.enabled || isCloudflareWorker();
|
|
468
|
+
return new GgChain(args[0], args, options, disabled);
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* gg._here() - Internal: call-site info with source metadata from Vite plugin.
|
|
472
|
+
*
|
|
473
|
+
* Called by the ggCallSitesPlugin when it rewrites gg.here() calls.
|
|
474
|
+
*/
|
|
475
|
+
gg._here = function (options) {
|
|
476
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
477
|
+
return { fileName: '', functionName: '', url: '' };
|
|
478
|
+
}
|
|
479
|
+
const { ns: nsLabel, file, line, col } = options;
|
|
480
|
+
const namespace = `gg:${nsLabel}`;
|
|
481
|
+
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
482
|
+
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
483
|
+
ggLogFunction(` 📝 ${nsLabel}`);
|
|
484
|
+
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
485
|
+
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
486
|
+
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
487
|
+
return { fileName, functionName, url };
|
|
370
488
|
};
|
|
371
489
|
/**
|
|
372
490
|
* gg._o() - Internal: build options object for gg._ns() without object literal syntax.
|
|
@@ -423,181 +541,66 @@ function getErrorStack(firstArg, skipFrames) {
|
|
|
423
541
|
}
|
|
424
542
|
return captureStack(skipFrames);
|
|
425
543
|
}
|
|
544
|
+
// Timer storage for gg.time / gg.timeEnd / gg.timeLog
|
|
545
|
+
// Maps timer label → { start: number, ns?: string, options?: LogOptions }
|
|
546
|
+
const timers = new Map();
|
|
426
547
|
/**
|
|
427
|
-
* gg.
|
|
428
|
-
*
|
|
429
|
-
* Passthrough: returns the first argument.
|
|
430
|
-
* In Eruda, entries are styled with a blue/info indicator.
|
|
431
|
-
*
|
|
432
|
-
* @example
|
|
433
|
-
* gg.info('System startup complete');
|
|
434
|
-
* const config = gg.info(loadedConfig, 'loaded config');
|
|
435
|
-
*/
|
|
436
|
-
gg.info = function (...args) {
|
|
437
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
438
|
-
return args.length ? args[0] : undefined;
|
|
439
|
-
}
|
|
440
|
-
const callpoint = resolveCallpoint(3);
|
|
441
|
-
return ggLog({ ns: callpoint, level: 'info' }, ...args);
|
|
442
|
-
};
|
|
443
|
-
/**
|
|
444
|
-
* gg._info() - Internal: info with call-site metadata from Vite plugin.
|
|
445
|
-
*/
|
|
446
|
-
gg._info = function (options, ...args) {
|
|
447
|
-
return ggLog({ ...options, level: 'info' }, ...args);
|
|
448
|
-
};
|
|
449
|
-
/**
|
|
450
|
-
* gg.warn() - Log at warning level.
|
|
451
|
-
*
|
|
452
|
-
* Passthrough: returns the first argument.
|
|
453
|
-
* In Eruda, entries are styled with a yellow/warning indicator.
|
|
454
|
-
*
|
|
455
|
-
* @example
|
|
456
|
-
* gg.warn('deprecated API used');
|
|
457
|
-
* const result = gg.warn(computeValue(), 'might be slow');
|
|
458
|
-
*/
|
|
459
|
-
gg.warn = function (...args) {
|
|
460
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
461
|
-
return args.length ? args[0] : undefined;
|
|
462
|
-
}
|
|
463
|
-
const callpoint = resolveCallpoint(3);
|
|
464
|
-
return ggLog({ ns: callpoint, level: 'warn' }, ...args);
|
|
465
|
-
};
|
|
466
|
-
/**
|
|
467
|
-
* gg._warn() - Internal: warn with call-site metadata from Vite plugin.
|
|
468
|
-
*/
|
|
469
|
-
gg._warn = function (options, ...args) {
|
|
470
|
-
return ggLog({ ...options, level: 'warn' }, ...args);
|
|
471
|
-
};
|
|
472
|
-
/**
|
|
473
|
-
* gg.error() - Log at error level.
|
|
474
|
-
*
|
|
475
|
-
* Passthrough: returns the first argument.
|
|
476
|
-
* Captures a stack trace silently — visible in Eruda via a collapsible toggle.
|
|
477
|
-
* If the first argument is an Error object, its .stack is used instead.
|
|
478
|
-
*
|
|
479
|
-
* @example
|
|
480
|
-
* gg.error('connection failed');
|
|
481
|
-
* gg.error(new Error('timeout'));
|
|
482
|
-
* const val = gg.error(response, 'unexpected status');
|
|
483
|
-
*/
|
|
484
|
-
gg.error = function (...args) {
|
|
485
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
486
|
-
return args.length ? args[0] : undefined;
|
|
487
|
-
}
|
|
488
|
-
const callpoint = resolveCallpoint(3);
|
|
489
|
-
const stack = getErrorStack(args[0], 4);
|
|
490
|
-
return ggLog({ ns: callpoint, level: 'error', stack }, ...args);
|
|
491
|
-
};
|
|
492
|
-
/**
|
|
493
|
-
* gg._error() - Internal: error with call-site metadata from Vite plugin.
|
|
494
|
-
*/
|
|
495
|
-
gg._error = function (options, ...args) {
|
|
496
|
-
const stack = getErrorStack(args[0], 3);
|
|
497
|
-
return ggLog({ ...options, level: 'error', stack }, ...args);
|
|
498
|
-
};
|
|
499
|
-
/**
|
|
500
|
-
* gg.assert() - Log only if condition is false.
|
|
501
|
-
*
|
|
502
|
-
* Like console.assert: if the first argument is falsy, logs the remaining
|
|
503
|
-
* arguments at error level. If the condition is truthy, does nothing.
|
|
504
|
-
* Passthrough: always returns the condition value.
|
|
548
|
+
* Chainable wrapper returned by gg.time(). Only supports .ns() for setting
|
|
549
|
+
* the namespace for the entire timer group (inherited by timeLog/timeEnd).
|
|
505
550
|
*
|
|
506
551
|
* @example
|
|
507
|
-
* gg.
|
|
508
|
-
* gg.
|
|
509
|
-
*/
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
527
|
-
return condition;
|
|
528
|
-
const stack = captureStack(3);
|
|
529
|
-
const assertArgs = args.length > 0 ? args : ['Assertion failed'];
|
|
530
|
-
ggLog({ ...options, level: 'error', stack }, ...assertArgs);
|
|
552
|
+
* gg.time('fetch').ns('api-pipeline')
|
|
553
|
+
* gg.time('fetch').ns('$FN:timers') // template vars work too
|
|
554
|
+
*/
|
|
555
|
+
export class GgTimerChain {
|
|
556
|
+
#label;
|
|
557
|
+
#options;
|
|
558
|
+
constructor(label, options) {
|
|
559
|
+
this.#label = label;
|
|
560
|
+
this.#options = options;
|
|
561
|
+
}
|
|
562
|
+
/** Set a custom namespace for this timer group.
|
|
563
|
+
* Supports the same template variables as GgChain.ns().
|
|
564
|
+
*/
|
|
565
|
+
ns(label) {
|
|
566
|
+
const resolved = resolveNsTemplateVars(label, this.#options);
|
|
567
|
+
const timer = timers.get(this.#label);
|
|
568
|
+
if (timer)
|
|
569
|
+
timer.ns = resolved;
|
|
570
|
+
return this;
|
|
531
571
|
}
|
|
532
|
-
|
|
533
|
-
};
|
|
572
|
+
}
|
|
534
573
|
/**
|
|
535
|
-
* gg.
|
|
574
|
+
* gg.time() - Start a named timer. Returns a GgTimerChain for optional .ns() chaining.
|
|
536
575
|
*
|
|
537
|
-
*
|
|
538
|
-
* Passthrough: returns the data argument.
|
|
539
|
-
*
|
|
540
|
-
* @example
|
|
541
|
-
* gg.table([{name: 'Alice', age: 30}, {name: 'Bob', age: 25}]);
|
|
542
|
-
* gg.table({a: {x: 1}, b: {x: 2}});
|
|
543
|
-
*/
|
|
544
|
-
gg.table = function (data, columns) {
|
|
545
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
546
|
-
return data;
|
|
547
|
-
const callpoint = resolveCallpoint(3);
|
|
548
|
-
const { keys, rows } = formatTable(data, columns);
|
|
549
|
-
ggLog({ ns: callpoint, tableData: { keys, rows } }, '(table)');
|
|
550
|
-
// Also emit a native console.table for proper rendering in browser/Node consoles
|
|
551
|
-
if (columns) {
|
|
552
|
-
console.table(data, columns);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
console.table(data);
|
|
556
|
-
}
|
|
557
|
-
return data;
|
|
558
|
-
};
|
|
559
|
-
/**
|
|
560
|
-
* gg._table() - Internal: table with call-site metadata from Vite plugin.
|
|
561
|
-
*/
|
|
562
|
-
gg._table = function (options, data, columns) {
|
|
563
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
564
|
-
return data;
|
|
565
|
-
const { keys, rows } = formatTable(data, columns);
|
|
566
|
-
ggLog({ ...options, tableData: { keys, rows } }, '(table)');
|
|
567
|
-
if (columns) {
|
|
568
|
-
console.table(data, columns);
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
console.table(data);
|
|
572
|
-
}
|
|
573
|
-
return data;
|
|
574
|
-
};
|
|
575
|
-
// Timer storage for gg.time / gg.timeEnd / gg.timeLog
|
|
576
|
-
const timers = new Map();
|
|
577
|
-
/**
|
|
578
|
-
* gg.time() - Start a named timer.
|
|
576
|
+
* @param label - Timer label (default: 'default')
|
|
579
577
|
*
|
|
580
578
|
* @example
|
|
581
|
-
* gg.time('fetch')
|
|
582
|
-
*
|
|
583
|
-
* gg.
|
|
579
|
+
* gg.time('fetch') // basic timer
|
|
580
|
+
* gg.time('fetch').ns('api-pipeline') // with namespace (inherited by timeLog/timeEnd)
|
|
581
|
+
* gg.time('fetch').ns('$FN:timers') // with template variable (plugin)
|
|
584
582
|
*/
|
|
585
583
|
gg.time = function (label = 'default') {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
584
|
+
const options = { ns: resolveCallpoint(3) };
|
|
585
|
+
if (ggConfig.enabled && !isCloudflareWorker()) {
|
|
586
|
+
timers.set(label, { start: performance.now(), options });
|
|
587
|
+
}
|
|
588
|
+
return new GgTimerChain(label, options);
|
|
589
589
|
};
|
|
590
590
|
/** gg._time() - Internal: time with call-site metadata from Vite plugin. */
|
|
591
|
-
gg._time = function (
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
591
|
+
gg._time = function (options, label = 'default') {
|
|
592
|
+
if (ggConfig.enabled && !isCloudflareWorker()) {
|
|
593
|
+
timers.set(label, { start: performance.now(), options });
|
|
594
|
+
}
|
|
595
|
+
return new GgTimerChain(label, options);
|
|
595
596
|
};
|
|
596
597
|
/**
|
|
597
598
|
* gg.timeLog() - Log the current elapsed time without stopping the timer.
|
|
598
599
|
*
|
|
600
|
+
* Inherits the namespace set by gg.time().ns() for this timer label.
|
|
601
|
+
*
|
|
599
602
|
* @example
|
|
600
|
-
* gg.time('process');
|
|
603
|
+
* gg.time('process').ns('my-namespace');
|
|
601
604
|
* // ... step 1 ...
|
|
602
605
|
* gg.timeLog('process', 'step 1 done');
|
|
603
606
|
* // ... step 2 ...
|
|
@@ -606,92 +609,66 @@ gg._time = function (_options, label = 'default') {
|
|
|
606
609
|
gg.timeLog = function (label = 'default', ...args) {
|
|
607
610
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
608
611
|
return;
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
612
|
+
const timer = timers.get(label);
|
|
613
|
+
if (timer === undefined) {
|
|
611
614
|
const callpoint = resolveCallpoint(3);
|
|
612
615
|
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
613
616
|
return;
|
|
614
617
|
}
|
|
615
|
-
const elapsed = performance.now() - start;
|
|
616
|
-
const
|
|
617
|
-
ggLog({ ns
|
|
618
|
+
const elapsed = performance.now() - timer.start;
|
|
619
|
+
const ns = timer.ns ?? timer.options?.ns ?? resolveCallpoint(3);
|
|
620
|
+
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
618
621
|
};
|
|
619
622
|
/** gg._timeLog() - Internal: timeLog with call-site metadata from Vite plugin. */
|
|
620
623
|
gg._timeLog = function (options, label = 'default', ...args) {
|
|
621
624
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
622
625
|
return;
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
626
|
+
const timer = timers.get(label);
|
|
627
|
+
if (timer === undefined) {
|
|
625
628
|
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
626
629
|
return;
|
|
627
630
|
}
|
|
628
|
-
const elapsed = performance.now() - start;
|
|
629
|
-
|
|
631
|
+
const elapsed = performance.now() - timer.start;
|
|
632
|
+
const ns = timer.ns ?? timer.options?.ns ?? options.ns;
|
|
633
|
+
ggLog({ ...options, ns }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
630
634
|
};
|
|
631
635
|
/**
|
|
632
636
|
* gg.timeEnd() - Stop a named timer and log the elapsed time.
|
|
633
637
|
*
|
|
638
|
+
* Inherits the namespace set by gg.time().ns() for this timer label.
|
|
639
|
+
*
|
|
634
640
|
* @example
|
|
635
|
-
* gg.time('fetch');
|
|
641
|
+
* gg.time('fetch').ns('api-pipeline');
|
|
636
642
|
* const data = await fetchData();
|
|
637
|
-
* gg.timeEnd('fetch'); // logs
|
|
643
|
+
* gg.timeEnd('fetch'); // logs under 'api-pipeline' namespace
|
|
638
644
|
*/
|
|
639
645
|
gg.timeEnd = function (label = 'default') {
|
|
640
646
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
641
647
|
return;
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
648
|
+
const timer = timers.get(label);
|
|
649
|
+
if (timer === undefined) {
|
|
644
650
|
const callpoint = resolveCallpoint(3);
|
|
645
651
|
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
646
652
|
return;
|
|
647
653
|
}
|
|
648
|
-
const elapsed = performance.now() - start;
|
|
654
|
+
const elapsed = performance.now() - timer.start;
|
|
649
655
|
timers.delete(label);
|
|
650
|
-
const
|
|
651
|
-
ggLog({ ns
|
|
656
|
+
const ns = timer.ns ?? timer.options?.ns ?? resolveCallpoint(3);
|
|
657
|
+
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`);
|
|
652
658
|
};
|
|
653
659
|
/** gg._timeEnd() - Internal: timeEnd with call-site metadata from Vite plugin. */
|
|
654
660
|
gg._timeEnd = function (options, label = 'default') {
|
|
655
661
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
656
662
|
return;
|
|
657
|
-
const
|
|
658
|
-
if (
|
|
663
|
+
const timer = timers.get(label);
|
|
664
|
+
if (timer === undefined) {
|
|
659
665
|
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
660
666
|
return;
|
|
661
667
|
}
|
|
662
|
-
const elapsed = performance.now() - start;
|
|
668
|
+
const elapsed = performance.now() - timer.start;
|
|
663
669
|
timers.delete(label);
|
|
664
|
-
|
|
665
|
-
};
|
|
666
|
-
/**
|
|
667
|
-
* gg.trace() - Log with a stack trace.
|
|
668
|
-
*
|
|
669
|
-
* Like console.trace: logs the arguments plus a full stack trace.
|
|
670
|
-
* Passthrough: returns the first argument.
|
|
671
|
-
*
|
|
672
|
-
* @example
|
|
673
|
-
* gg.trace('how did we get here?');
|
|
674
|
-
* const val = gg.trace(result, 'call path');
|
|
675
|
-
*/
|
|
676
|
-
gg.trace = function (...args) {
|
|
677
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
678
|
-
return args.length ? args[0] : undefined;
|
|
679
|
-
}
|
|
680
|
-
const callpoint = resolveCallpoint(3);
|
|
681
|
-
const stack = captureStack(4);
|
|
682
|
-
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
683
|
-
return ggLog({ ns: callpoint, stack }, ...traceArgs);
|
|
684
|
-
};
|
|
685
|
-
/**
|
|
686
|
-
* gg._trace() - Internal: trace with call-site metadata from Vite plugin.
|
|
687
|
-
*/
|
|
688
|
-
gg._trace = function (options, ...args) {
|
|
689
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
690
|
-
return args.length ? args[0] : undefined;
|
|
691
|
-
}
|
|
692
|
-
const stack = captureStack(3);
|
|
693
|
-
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
694
|
-
return ggLog({ ...options, stack }, ...traceArgs);
|
|
670
|
+
const ns = timer.ns ?? timer.options?.ns ?? options.ns;
|
|
671
|
+
ggLog({ ...options, ns }, `${label}: ${formatElapsed(elapsed)}`);
|
|
695
672
|
};
|
|
696
673
|
/**
|
|
697
674
|
* Format elapsed time with appropriate precision.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { gg, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
1
|
+
import { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
2
2
|
import openInEditorPlugin from './open-in-editor.js';
|
|
3
3
|
import ggCallSitesPlugin from './gg-call-sites-plugin.js';
|
|
4
4
|
export { default as GgConsole } from './GgConsole.svelte';
|
|
5
|
-
export { gg, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|
|
5
|
+
export { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Reexport your entry components here
|
|
2
|
-
import { gg, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
2
|
+
import { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
3
3
|
import openInEditorPlugin from './open-in-editor.js';
|
|
4
4
|
import ggCallSitesPlugin from './gg-call-sites-plugin.js';
|
|
5
5
|
export { default as GgConsole } from './GgConsole.svelte';
|
|
6
|
-
export { gg, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|
|
6
|
+
export { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|