@leftium/gg 0.0.47 → 0.0.49
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 +246 -279
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -2
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;
|