@leftium/gg 0.0.31 → 0.0.34

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 CHANGED
@@ -18,24 +18,95 @@
18
18
  npm add @leftium/gg
19
19
  ```
20
20
 
21
- ## Usage
21
+ ## SvelteKit Quick Start
22
22
 
23
- ### Basic Logging
23
+ ### 1. Add Vite plugins
24
24
 
25
- ```javascript
26
- import { gg } from '@leftium/gg';
25
+ ```ts
26
+ // vite.config.ts
27
+ import ggPlugins from '@leftium/gg/vite';
28
+ import { sveltekit } from '@sveltejs/kit/vite';
29
+ import { defineConfig } from 'vite';
30
+
31
+ export default defineConfig({
32
+ plugins: [sveltekit(), ...ggPlugins()]
33
+ });
34
+ ```
35
+
36
+ `ggPlugins()` includes:
27
37
 
28
- // Simple logging
29
- gg('Hello world');
38
+ - **Call-sites plugin** -- rewrites `gg()` calls with source file/line/col metadata
39
+ - **Open-in-editor plugin** -- adds dev server middleware for click-to-open
40
+ - **Automatic `es2022` target** -- required for top-level await
30
41
 
31
- // Log expressions (returns first argument)
32
- const result = gg(someFunction());
42
+ ### 2. Add the debug console
33
43
 
34
- // Multiple arguments
35
- gg('User:', user, 'Status:', status);
44
+ ```svelte
45
+ <!-- src/routes/+layout.svelte -->
46
+ <script>
47
+ import { GgConsole } from '@leftium/gg';
48
+ </script>
49
+
50
+ <GgConsole />
51
+ {@render children()}
36
52
  ```
37
53
 
38
- ### Color Support (ANSI)
54
+ ### 3. Use `gg()` anywhere
55
+
56
+ ```svelte
57
+ <script>
58
+ import { gg } from '@leftium/gg';
59
+
60
+ gg('Hello world');
61
+
62
+ // Log expressions (returns first argument)
63
+ const result = gg(someFunction());
64
+
65
+ // Multiple arguments
66
+ gg('User:', user, 'Status:', status);
67
+ </script>
68
+ ```
69
+
70
+ That's it! In development, a debug console appears automatically.
71
+ In production, add `?gg` to the URL or use a 5-tap gesture to activate.
72
+
73
+ ## GgConsole Options
74
+
75
+ ```svelte
76
+ <GgConsole prod={['url-param', 'gesture']} maxEntries={5000} />
77
+ ```
78
+
79
+ | Prop | Type | Default | Description |
80
+ | -------------- | -------------------------- | -------------------------- | ------------------------------ |
81
+ | `prod` | `Array \| string \| false` | `['url-param', 'gesture']` | Production activation triggers |
82
+ | `maxEntries` | `number` | `2000` | Max log entries in ring buffer |
83
+ | `erudaOptions` | `object` | `{}` | Pass-through options to Eruda |
84
+
85
+ **Production triggers:**
86
+
87
+ - `'url-param'` -- activate with `?gg` in the URL (persists to localStorage)
88
+ - `'gesture'` -- activate with 5 rapid taps anywhere on the page
89
+ - `'localStorage'` -- activate if `localStorage['gg-enabled']` is `'true'`
90
+ - `false` -- disable debug console in production entirely
91
+
92
+ ## Vite Plugin Options
93
+
94
+ ```ts
95
+ import ggPlugins from '@leftium/gg/vite';
96
+
97
+ ggPlugins({
98
+ callSites: { srcRootPattern: '.*?(/src/)' },
99
+ openInEditor: false // disable open-in-editor middleware
100
+ });
101
+ ```
102
+
103
+ Individual plugins are also available for advanced setups:
104
+
105
+ ```ts
106
+ import { ggCallSitesPlugin, openInEditorPlugin } from '@leftium/gg/vite';
107
+ ```
108
+
109
+ ## Color Support (ANSI)
39
110
 
40
111
  Color your logs for better visual distinction using `fg()` (foreground/text) and `bg()` (background):
41
112
 
@@ -77,46 +148,56 @@ gg(fg('rgb(255,99,71)')`Tomato text`);
77
148
 
78
149
  **Where colors work:**
79
150
 
80
- - Native browser console (Chrome DevTools, Firefox, etc.)
81
- - Eruda GG panel (mobile debugging)
82
- - Node.js terminal
83
- - All environments that support ANSI escape codes
151
+ - Native browser console (Chrome DevTools, Firefox, etc.)
152
+ - GgConsole debug panel (mobile debugging)
153
+ - Node.js terminal
154
+ - All environments that support ANSI escape codes
84
155
 
85
- ## Technical Details
156
+ ## Other Frameworks
86
157
 
87
- ### Bundled Dependencies
158
+ `gg()` works in any JavaScript project. The Vite plugins work with any Vite-based framework (React, Vue, Solid, etc.).
88
159
 
89
- This library includes a **patched version** of the [`debug`](https://www.npmjs.com/package/debug) package. The patch reformats the output to display time diffs **before** the namespace for better readability:
160
+ ### Vanilla / Non-Svelte Setup
90
161
 
91
- **Standard debug output:**
162
+ ```ts
163
+ // vite.config.ts
164
+ import ggPlugins from '@leftium/gg/vite';
92
165
 
93
- ```
94
- gg:routes/+page.svelte +123ms
166
+ export default defineConfig({
167
+ plugins: [...ggPlugins()]
168
+ });
95
169
  ```
96
170
 
97
- **Patched output (this library):**
171
+ ```js
172
+ // app.js
173
+ import { gg } from '@leftium/gg';
174
+ import { initGgEruda } from '@leftium/gg/eruda';
98
175
 
176
+ initGgEruda();
177
+ gg('works in any framework');
99
178
  ```
100
- +123ms gg:routes/+page.svelte
101
- ```
102
179
 
103
- The patched `debug` library is bundled directly into the distribution, so consumers automatically get the correct behavior without needing to install or patch `debug` themselves.
180
+ ## Technical Details
181
+
182
+ ### Internal Debug Implementation
104
183
 
105
- ### Updating the Bundled debug Library
184
+ This library includes an **internal TypeScript implementation** inspired by the [`debug`](https://www.npmjs.com/package/debug) package. The output format displays time diffs **before** the namespace for better readability:
106
185
 
107
- When a new version of `debug` is released:
186
+ **Output format:**
187
+
188
+ ```
189
+ +123ms gg:routes/+page.svelte
190
+ ```
108
191
 
109
- 1. Update debug: `pnpm add debug@x.x.x`
110
- 2. Update patch: `pnpm patch debug@x.x.x` (apply changes, then `pnpm patch-commit`)
111
- 3. Run the update script: `./scripts/update-debug.sh`
112
- 4. Verify patches are present: `git diff src/lib/debug/src/`
113
- 5. Test dev mode: `pnpm dev`
114
- 6. Test production build: `pnpm prepack`
115
- 7. Commit changes: `git commit -am "Update bundled debug to x.x.x"`
192
+ Features implemented internally (~290 lines of TypeScript):
116
193
 
117
- The patch is maintained in `patches/debug@4.4.3.patch` for reference.
194
+ - Color hashing algorithm for consistent namespace colors
195
+ - Millisecond diff formatting (e.g., `+123ms`, `+2s`, `+5m`)
196
+ - Namespace wildcard matching (`gg:*`, `gg:routes/*`, `-gg:test`)
197
+ - localStorage.debug / process.env.DEBUG persistence
198
+ - Browser and Node.js environments
118
199
 
119
- **Note:** `debug` is kept in dependencies (not devDependencies) to support both dev and production modes.
200
+ This approach eliminates the need for vendoring, patching, and bundling third-party code, resulting in better type safety and simpler maintenance.
120
201
 
121
202
  ## Inspirations
122
203
 
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import type { GgErudaOptions } from './eruda/types.js';
4
+
5
+ let { prod, maxEntries, erudaOptions }: GgErudaOptions = $props();
6
+
7
+ onMount(() => {
8
+ import('./eruda/index.js').then(({ initGgEruda }) => {
9
+ initGgEruda({ prod, maxEntries, erudaOptions });
10
+ });
11
+ });
12
+ </script>
@@ -0,0 +1,4 @@
1
+ import type { GgErudaOptions } from './eruda/types.js';
2
+ declare const GgConsole: import("svelte").Component<GgErudaOptions, {}, "">;
3
+ type GgConsole = ReturnType<typeof GgConsole>;
4
+ export default GgConsole;
@@ -1,26 +1,24 @@
1
1
  <script lang="ts">
2
2
  import { dev } from '$app/environment';
3
3
 
4
- let { ggResult } = $props();
4
+ let {
5
+ url,
6
+ fileName,
7
+ title = fileName
8
+ }: { url: string; fileName: string; title?: string } = $props();
5
9
 
6
10
  // svelte-ignore non_reactive_update
7
11
  let iframeElement: HTMLIFrameElement;
8
12
 
9
13
  function onclick(event: MouseEvent) {
10
- iframeElement.src = ggResult.url;
14
+ iframeElement.src = url;
11
15
  event.preventDefault();
12
16
  }
13
17
  </script>
14
18
 
15
19
  {#if dev}
16
- [📝<a
17
- {onclick}
18
- href={ggResult.url}
19
- title={`${ggResult.fileName}@${ggResult.functionName}`}
20
- target="_open-in-editor"
21
- class="open-in-editor-link"
22
- >
23
- {ggResult.fileName}
20
+ [📝<a {onclick} href={url} {title} target="_open-in-editor" class="open-in-editor-link">
21
+ {fileName}
24
22
  </a>
25
23
  👀]
26
24
 
@@ -1,5 +1,8 @@
1
- declare const OpenInEditorLink: import("svelte").Component<{
2
- ggResult: any;
3
- }, {}, "">;
1
+ type $$ComponentProps = {
2
+ url: string;
3
+ fileName: string;
4
+ title?: string;
5
+ };
6
+ declare const OpenInEditorLink: import("svelte").Component<$$ComponentProps, {}, "">;
4
7
  type OpenInEditorLink = ReturnType<typeof OpenInEditorLink>;
5
8
  export default OpenInEditorLink;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Browser-specific debug implementation.
3
+ *
4
+ * Output: console.debug with %c CSS color formatting.
5
+ * Persistence: localStorage.debug
6
+ * Format (patched): +123ms namespace message
7
+ */
8
+ import { type DebugFactory } from './common.js';
9
+ declare const debug: DebugFactory;
10
+ export default debug;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Browser-specific debug implementation.
3
+ *
4
+ * Output: console.debug with %c CSS color formatting.
5
+ * Persistence: localStorage.debug
6
+ * Format (patched): +123ms namespace message
7
+ */
8
+ import { setup, humanize } from './common.js';
9
+ /**
10
+ * 76 hex colors — identical to debug@4 browser.js for color-hash stability.
11
+ */
12
+ const colors = [
13
+ '#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF',
14
+ '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99',
15
+ '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF',
16
+ '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33',
17
+ '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF',
18
+ '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF',
19
+ '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033',
20
+ '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333',
21
+ '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633',
22
+ '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033',
23
+ '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333',
24
+ '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633',
25
+ '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'
26
+ ];
27
+ function useColors() {
28
+ // Modern browsers all support %c — simplified from debug's original checks
29
+ return typeof document !== 'undefined' || typeof navigator !== 'undefined';
30
+ }
31
+ /**
32
+ * Format args with color CSS and gg's patched prefix order:
33
+ * +123ms namespace message
34
+ */
35
+ function formatArgs(args) {
36
+ const h = humanize(this.diff);
37
+ const prefix = ('+' + h).padStart(6);
38
+ args[0] = (this.useColors ? '%c' : '') +
39
+ `${prefix} ${this.namespace}` +
40
+ (this.useColors ? ' %c' : ' ') +
41
+ args[0] +
42
+ (this.useColors ? '%c ' : ' ');
43
+ if (!this.useColors)
44
+ return;
45
+ const c = 'color: ' + this.color;
46
+ args.splice(1, 0, c, 'color: inherit');
47
+ // Insert CSS for the final %c
48
+ let index = 0;
49
+ let lastC = 0;
50
+ args[0].replace(/%[a-zA-Z%]/g, (match) => {
51
+ if (match === '%%')
52
+ return match;
53
+ index++;
54
+ if (match === '%c')
55
+ lastC = index;
56
+ return match;
57
+ });
58
+ args.splice(lastC, 0, c);
59
+ }
60
+ function save(namespaces) {
61
+ try {
62
+ if (namespaces) {
63
+ localStorage.setItem('debug', namespaces);
64
+ }
65
+ else {
66
+ localStorage.removeItem('debug');
67
+ }
68
+ }
69
+ catch {
70
+ // localStorage may not be available
71
+ }
72
+ }
73
+ function load() {
74
+ try {
75
+ return localStorage.getItem('debug') || localStorage.getItem('DEBUG') || '';
76
+ }
77
+ catch {
78
+ return '';
79
+ }
80
+ }
81
+ const log = console.debug || console.log || (() => { });
82
+ const env = {
83
+ formatArgs,
84
+ save,
85
+ load,
86
+ useColors,
87
+ colors,
88
+ log,
89
+ formatters: {
90
+ /** %j → JSON.stringify (preserved for compatibility) */
91
+ j(v) {
92
+ try {
93
+ return JSON.stringify(v);
94
+ }
95
+ catch (e) {
96
+ return '[UnexpectedJSONParseError]: ' + e.message;
97
+ }
98
+ }
99
+ }
100
+ };
101
+ const debug = setup(env);
102
+ export default debug;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Internal debug implementation — replaces the `debug` npm package.
3
+ *
4
+ * Core logic: createDebug factory, enable/disable, namespace matching,
5
+ * color selection, and humanize (ms formatting).
6
+ */
7
+ /** Format ms like debug's `ms` package: 0ms, 500ms, 5s, 2m, 1h, 3d */
8
+ export declare function humanize(ms: number): string;
9
+ export interface Debugger {
10
+ (...args: unknown[]): void;
11
+ namespace: string;
12
+ color: string;
13
+ diff: number;
14
+ enabled: boolean;
15
+ useColors: boolean;
16
+ formatArgs: (args: unknown[]) => void;
17
+ log: ((...args: unknown[]) => void) | null;
18
+ }
19
+ export interface DebugFactory {
20
+ (namespace: string): Debugger;
21
+ enable: (namespaces: string) => void;
22
+ disable: () => string;
23
+ enabled: (namespace: string) => boolean;
24
+ humanize: typeof humanize;
25
+ names: string[];
26
+ skips: string[];
27
+ namespaces: string;
28
+ formatters: Record<string, (this: Debugger, val: unknown) => string>;
29
+ }
30
+ /** Platform-specific hooks provided by browser.ts or node.ts */
31
+ export interface DebugEnv {
32
+ formatArgs: (this: Debugger, args: unknown[]) => void;
33
+ save: (namespaces: string) => void;
34
+ load: () => string;
35
+ useColors: () => boolean;
36
+ colors: string[] | number[];
37
+ log: (...args: unknown[]) => void;
38
+ formatters?: Record<string, (this: Debugger, val: unknown) => string>;
39
+ init?: (instance: Debugger) => void;
40
+ }
41
+ export declare function setup(env: DebugEnv): DebugFactory;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Internal debug implementation — replaces the `debug` npm package.
3
+ *
4
+ * Core logic: createDebug factory, enable/disable, namespace matching,
5
+ * color selection, and humanize (ms formatting).
6
+ */
7
+ /** Format ms like debug's `ms` package: 0ms, 500ms, 5s, 2m, 1h, 3d */
8
+ export function humanize(ms) {
9
+ const abs = Math.abs(ms);
10
+ if (abs >= 86_400_000)
11
+ return Math.round(ms / 86_400_000) + 'd';
12
+ if (abs >= 3_600_000)
13
+ return Math.round(ms / 3_600_000) + 'h';
14
+ if (abs >= 60_000)
15
+ return Math.round(ms / 60_000) + 'm';
16
+ if (abs >= 1_000)
17
+ return Math.round(ms / 1_000) + 's';
18
+ return ms + 'ms';
19
+ }
20
+ /**
21
+ * Wildcard pattern matching (same algorithm as debug's `matchesTemplate`).
22
+ * Supports `*` as a wildcard that matches any sequence of characters.
23
+ */
24
+ function matchesTemplate(search, template) {
25
+ let si = 0;
26
+ let ti = 0;
27
+ let starIdx = -1;
28
+ let matchIdx = 0;
29
+ while (si < search.length) {
30
+ if (ti < template.length && (template[ti] === search[si] || template[ti] === '*')) {
31
+ if (template[ti] === '*') {
32
+ starIdx = ti;
33
+ matchIdx = si;
34
+ ti++;
35
+ }
36
+ else {
37
+ si++;
38
+ ti++;
39
+ }
40
+ }
41
+ else if (starIdx !== -1) {
42
+ ti = starIdx + 1;
43
+ matchIdx++;
44
+ si = matchIdx;
45
+ }
46
+ else {
47
+ return false;
48
+ }
49
+ }
50
+ while (ti < template.length && template[ti] === '*') {
51
+ ti++;
52
+ }
53
+ return ti === template.length;
54
+ }
55
+ // ── Factory ────────────────────────────────────────────────────────────
56
+ export function setup(env) {
57
+ /** Deterministic color for a namespace (same hash as debug@4) */
58
+ function selectColor(namespace) {
59
+ let hash = 0;
60
+ for (let i = 0; i < namespace.length; i++) {
61
+ hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
62
+ hash |= 0;
63
+ }
64
+ return env.colors[Math.abs(hash) % env.colors.length];
65
+ }
66
+ // Active include/exclude lists
67
+ let names = [];
68
+ let skips = [];
69
+ let currentNamespaces = '';
70
+ function enable(namespaces) {
71
+ env.save(namespaces);
72
+ currentNamespaces = namespaces;
73
+ names = [];
74
+ skips = [];
75
+ const parts = (typeof namespaces === 'string' ? namespaces : '')
76
+ .trim()
77
+ .replace(/\s+/g, ',')
78
+ .split(',')
79
+ .filter(Boolean);
80
+ for (const part of parts) {
81
+ if (part[0] === '-') {
82
+ skips.push(part.slice(1));
83
+ }
84
+ else {
85
+ names.push(part);
86
+ }
87
+ }
88
+ // Update factory-level arrays for external inspection
89
+ factory.names = names;
90
+ factory.skips = skips;
91
+ factory.namespaces = currentNamespaces;
92
+ }
93
+ function disable() {
94
+ const prev = [
95
+ ...names,
96
+ ...skips.map((ns) => '-' + ns)
97
+ ].join(',');
98
+ enable('');
99
+ return prev;
100
+ }
101
+ function enabled(name) {
102
+ for (const skip of skips) {
103
+ if (matchesTemplate(name, skip))
104
+ return false;
105
+ }
106
+ for (const ns of names) {
107
+ if (matchesTemplate(name, ns))
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ // ── createDebug ────────────────────────────────────────────────────
113
+ function createDebug(namespace) {
114
+ let prevTime;
115
+ let enableOverride = null;
116
+ let namespacesCache;
117
+ let enabledCache;
118
+ const debug = function (...args) {
119
+ if (!debug.enabled)
120
+ return;
121
+ const curr = Date.now();
122
+ const ms = curr - (prevTime || curr);
123
+ debug.diff = ms;
124
+ prevTime = curr;
125
+ // Coerce first arg
126
+ if (typeof args[0] !== 'string') {
127
+ args.unshift('%O');
128
+ }
129
+ // Apply %format replacements
130
+ let idx = 0;
131
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, fmt) => {
132
+ if (match === '%%')
133
+ return '%';
134
+ idx++;
135
+ const formatter = factory.formatters[fmt];
136
+ if (typeof formatter === 'function') {
137
+ const val = args[idx];
138
+ match = formatter.call(debug, val);
139
+ args.splice(idx, 1);
140
+ idx--;
141
+ }
142
+ return match;
143
+ });
144
+ // Platform-specific formatting (colors, prefix)
145
+ debug.formatArgs(args);
146
+ const logFn = debug.log || env.log;
147
+ logFn.apply(debug, args);
148
+ };
149
+ debug.namespace = namespace;
150
+ debug.useColors = env.useColors();
151
+ debug.color = String(selectColor(namespace));
152
+ debug.diff = 0;
153
+ debug.log = null;
154
+ debug.formatArgs = function (args) {
155
+ env.formatArgs.call(debug, args);
156
+ };
157
+ Object.defineProperty(debug, 'enabled', {
158
+ enumerable: true,
159
+ configurable: false,
160
+ get: () => {
161
+ if (enableOverride !== null)
162
+ return enableOverride;
163
+ if (namespacesCache !== currentNamespaces) {
164
+ namespacesCache = currentNamespaces;
165
+ enabledCache = enabled(namespace);
166
+ }
167
+ return enabledCache;
168
+ },
169
+ set: (v) => {
170
+ enableOverride = v;
171
+ }
172
+ });
173
+ if (env.init) {
174
+ env.init(debug);
175
+ }
176
+ return debug;
177
+ }
178
+ // ── Assemble factory ───────────────────────────────────────────────
179
+ const factory = createDebug;
180
+ factory.enable = enable;
181
+ factory.disable = disable;
182
+ factory.enabled = enabled;
183
+ factory.humanize = humanize;
184
+ factory.names = names;
185
+ factory.skips = skips;
186
+ factory.namespaces = '';
187
+ factory.formatters = { ...env.formatters };
188
+ // Initialize from persisted namespaces
189
+ enable(env.load());
190
+ return factory;
191
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Debug entry point — selects browser or node implementation.
3
+ *
4
+ * Re-exports the DebugFactory and Debugger types for consumers.
5
+ */
6
+ import type { DebugFactory } from './common.js';
7
+ export type { DebugFactory, Debugger } from './common.js';
8
+ declare const _default: DebugFactory;
9
+ export default _default;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Debug entry point — selects browser or node implementation.
3
+ *
4
+ * Re-exports the DebugFactory and Debugger types for consumers.
5
+ */
6
+ import { BROWSER } from 'esm-env';
7
+ // Conditional import: browser.ts for browsers, node.ts for Node/Deno/Bun
8
+ const { default: debug } = BROWSER
9
+ ? await import('./browser.js')
10
+ : await import('./node.js');
11
+ export default debug;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Node.js-specific debug implementation.
3
+ *
4
+ * Output: process.stderr via util.formatWithOptions.
5
+ * Persistence: process.env.DEBUG
6
+ * Format (patched): +123ms namespace message (ANSI colored)
7
+ */
8
+ import { type DebugFactory } from './common.js';
9
+ declare const debug: DebugFactory;
10
+ export default debug;