@leftium/gg 0.0.41 → 0.0.43

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.
@@ -1,9 +1,25 @@
1
1
  /**
2
2
  * Debug entry point — selects browser or node implementation.
3
3
  *
4
+ * Uses a lazy initialization pattern to avoid top-level await,
5
+ * which has incomplete Safari support and breaks SSR-disabled pages.
6
+ * The debug factory is loaded on first use via a synchronous getter
7
+ * backed by a pre-started import promise.
8
+ *
4
9
  * Re-exports the DebugFactory and Debugger types for consumers.
5
10
  */
6
11
  import type { DebugFactory } from './common.js';
7
12
  export type { DebugFactory, Debugger } from './common.js';
8
- declare const _default: DebugFactory;
9
- export default _default;
13
+ /**
14
+ * Promise that resolves when the debug factory is ready.
15
+ * Consumers that need to guarantee the factory is loaded can await this.
16
+ */
17
+ export declare const debugReady: Promise<void>;
18
+ /**
19
+ * Proxy-based default export that delegates to the real debug factory
20
+ * once loaded. This allows `import debug from './debug/index.js'` to
21
+ * work as a synchronous import while the actual implementation loads
22
+ * in the background.
23
+ */
24
+ declare const debug: DebugFactory;
25
+ export default debug;
@@ -1,11 +1,76 @@
1
1
  /**
2
2
  * Debug entry point — selects browser or node implementation.
3
3
  *
4
+ * Uses a lazy initialization pattern to avoid top-level await,
5
+ * which has incomplete Safari support and breaks SSR-disabled pages.
6
+ * The debug factory is loaded on first use via a synchronous getter
7
+ * backed by a pre-started import promise.
8
+ *
4
9
  * Re-exports the DebugFactory and Debugger types for consumers.
5
10
  */
6
11
  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');
12
+ // Start loading the platform-specific implementation immediately (non-blocking).
13
+ // The promise begins resolving at module load time but doesn't block evaluation.
14
+ let _debug = null;
15
+ const _ready = (BROWSER ? import('./browser.js') : import('./node.js')).then((m) => {
16
+ _debug = m.default;
17
+ });
18
+ /**
19
+ * Ensure the debug factory is loaded. In practice, the dynamic import resolves
20
+ * almost instantly (it's a local module, not a network fetch), so by the time
21
+ * any consumer calls debugFactory() the promise is already settled.
22
+ *
23
+ * For the rare case where it's called before resolution, this returns a
24
+ * no-op debugger that buffers nothing — acceptable since gg diagnostics
25
+ * and early logging are already deferred in practice.
26
+ */
27
+ function getDebugFactory() {
28
+ if (_debug)
29
+ return _debug;
30
+ // Fallback: return a minimal no-op factory while the real one loads.
31
+ // This path is hit only if someone calls debugFactory() synchronously
32
+ // during module evaluation, before the microtask resolves.
33
+ const noop = Object.assign(function noopDebug(_namespace) {
34
+ // Return a disabled debugger stub
35
+ const stub = Object.assign(function (..._args) { }, {
36
+ namespace: _namespace,
37
+ enabled: false,
38
+ color: '0',
39
+ diff: 0,
40
+ useColors: false,
41
+ log: () => { },
42
+ extend: (sub) => noopDebug(`${_namespace}:${sub}`),
43
+ destroy: () => stub,
44
+ formatArgs: undefined
45
+ });
46
+ return stub;
47
+ }, {
48
+ enable: (_namespaces) => { },
49
+ disable: () => '',
50
+ enabled: (_namespace) => false,
51
+ names: [],
52
+ skips: []
53
+ });
54
+ return noop;
55
+ }
56
+ /**
57
+ * Promise that resolves when the debug factory is ready.
58
+ * Consumers that need to guarantee the factory is loaded can await this.
59
+ */
60
+ export const debugReady = _ready;
61
+ /**
62
+ * Proxy-based default export that delegates to the real debug factory
63
+ * once loaded. This allows `import debug from './debug/index.js'` to
64
+ * work as a synchronous import while the actual implementation loads
65
+ * in the background.
66
+ */
67
+ const debug = new Proxy((() => { }), {
68
+ apply(_target, _thisArg, args) {
69
+ return getDebugFactory()(args[0]);
70
+ },
71
+ get(_target, prop, _receiver) {
72
+ const factory = getDebugFactory();
73
+ return factory[prop];
74
+ }
75
+ });
11
76
  export default debug;
@@ -106,6 +106,30 @@ export function createGgPlugin(options, gg) {
106
106
  localStorage.setItem(COPY_FORMAT_KEY, copyFormat);
107
107
  }
108
108
  }
109
+ /**
110
+ * Format a single log entry for clipboard copy
111
+ * Produces: HH:MM:SS.mmm namespace args [optional expression]
112
+ */
113
+ function formatEntryForClipboard(entry, includeExpressions) {
114
+ // Extract HH:MM:SS.mmm from timestamp (with milliseconds)
115
+ const time = new Date(entry.timestamp).toISOString().slice(11, 23);
116
+ // Trim namespace and strip 'gg:' prefix to save tokens
117
+ const ns = entry.namespace.trim().replace(/^gg:/, '');
118
+ // Include expression suffix when toggle is enabled
119
+ const hasSrcExpr = !entry.level && entry.src?.trim() && !/^['"`]/.test(entry.src);
120
+ const exprSuffix = includeExpressions && hasSrcExpr ? ` \u2039${entry.src}\u203A` : '';
121
+ // Format args: compact JSON for objects, primitives as-is
122
+ const argsStr = entry.args
123
+ .map((arg) => {
124
+ if (typeof arg === 'object' && arg !== null) {
125
+ return JSON.stringify(arg);
126
+ }
127
+ // Strip ANSI escape codes from string args
128
+ return stripAnsi(String(arg));
129
+ })
130
+ .join(' ');
131
+ return `${time} ${ns} ${argsStr}${exprSuffix}`;
132
+ }
109
133
  const plugin = {
110
134
  name: 'GG',
111
135
  init($container) {
@@ -1355,26 +1379,7 @@ export function createGgPlugin(options, gg) {
1355
1379
  // Apply same filtering as renderLogs() - only copy visible entries
1356
1380
  const entries = allEntries.filter((entry) => enabledNamespaces.has(entry.namespace));
1357
1381
  const text = entries
1358
- .map((e) => {
1359
- // Extract just HH:MM:SS from timestamp (compact for LLMs)
1360
- const time = new Date(e.timestamp).toISOString().slice(11, 19);
1361
- // Trim namespace and strip 'gg:' prefix to save tokens
1362
- const ns = e.namespace.trim().replace(/^gg:/, '');
1363
- // Include expression suffix when toggle is enabled
1364
- const hasSrcExpr = !e.level && e.src?.trim() && !/^['"`]/.test(e.src);
1365
- const exprSuffix = showExpressions && hasSrcExpr ? ` \u2039${e.src}\u203A` : '';
1366
- // Format args: compact JSON for objects, primitives as-is
1367
- const argsStr = e.args
1368
- .map((arg) => {
1369
- if (typeof arg === 'object' && arg !== null) {
1370
- return JSON.stringify(arg);
1371
- }
1372
- // Strip ANSI escape codes from string args
1373
- return stripAnsi(String(arg));
1374
- })
1375
- .join(' ');
1376
- return `${time} ${ns} ${argsStr}${exprSuffix}`;
1377
- })
1382
+ .map((e) => formatEntryForClipboard(e, showExpressions))
1378
1383
  .join('\n');
1379
1384
  try {
1380
1385
  await navigator.clipboard.writeText(text);
@@ -1795,20 +1800,7 @@ export function createGgPlugin(options, gg) {
1795
1800
  if (!entry)
1796
1801
  return;
1797
1802
  e.preventDefault();
1798
- const time = new Date(entry.timestamp).toISOString().slice(11, 19);
1799
- const ns = entry.namespace.trim().replace(/^gg:/, '');
1800
- // Include expression suffix when toggle is enabled
1801
- const hasSrcExpr = !entry.level && entry.src?.trim() && !/^['"`]/.test(entry.src);
1802
- const exprSuffix = showExpressions && hasSrcExpr ? ` \u2039${entry.src}\u203A` : '';
1803
- const argsStr = entry.args
1804
- .map((arg) => {
1805
- if (typeof arg === 'object' && arg !== null) {
1806
- return JSON.stringify(arg);
1807
- }
1808
- return stripAnsi(String(arg));
1809
- })
1810
- .join(' ');
1811
- const text = `${time} ${ns} ${argsStr}${exprSuffix}`;
1803
+ const text = formatEntryForClipboard(entry, showExpressions);
1812
1804
  navigator.clipboard.writeText(text).then(() => {
1813
1805
  showConfirmationTooltip(containerEl, contentEl, 'Copied message');
1814
1806
  });
package/dist/gg.d.ts CHANGED
@@ -28,8 +28,8 @@ export declare function gg(): {
28
28
  };
29
29
  export declare function gg<T>(arg: T, ...args: unknown[]): T;
30
30
  export declare namespace gg {
31
- var disable: () => void;
32
- var enable: (namespaces: string) => void;
31
+ var disable: () => string;
32
+ var enable: (ns: string) => void;
33
33
  var clearPersist: () => void;
34
34
  }
35
35
  /**
package/dist/gg.js CHANGED
@@ -39,19 +39,30 @@ const isCloudflareWorker = () => {
39
39
  if (isCloudflareWorker()) {
40
40
  console.warn('gg: CloudFlare not supported.');
41
41
  }
42
- // Try to load Node.js modules if not in CloudFlare Workers
42
+ // Lazy-load Node.js modules to avoid top-level await (Safari compatibility).
43
+ // The imports start immediately but don't block module evaluation.
43
44
  let dotenvModule = null;
44
45
  let httpModule = null;
45
- if (!isCloudflareWorker() && !BROWSER) {
46
- try {
47
- dotenvModule = await import('dotenv');
48
- }
49
- catch {
50
- httpModule = await import('http');
51
- // Failed to import Node.js modules
52
- console.warn('gg: Node.js modules not available');
53
- }
46
+ function loadServerModules() {
47
+ if (isCloudflareWorker() || BROWSER)
48
+ return Promise.resolve();
49
+ return (async () => {
50
+ try {
51
+ dotenvModule = await import('dotenv');
52
+ }
53
+ catch {
54
+ // dotenv not available — optional dependency
55
+ }
56
+ try {
57
+ httpModule = await import('http');
58
+ }
59
+ catch {
60
+ console.warn('gg: Node.js http module not available');
61
+ }
62
+ })();
54
63
  }
64
+ // Start loading immediately (non-blocking)
65
+ const serverModulesReady = loadServerModules();
55
66
  function findAvailablePort(startingPort) {
56
67
  if (!httpModule)
57
68
  return Promise.resolve(startingPort);
@@ -80,15 +91,23 @@ function getServerPort() {
80
91
  resolve('5173');
81
92
  }
82
93
  else {
83
- // Node.js environment
84
- const startingPort = Number(process?.env?.PORT) || 5173; // Default to Vite's default port
85
- findAvailablePort(startingPort).then((actualPort) => {
86
- resolve(actualPort);
94
+ // Node.js environment — wait for http module to be available
95
+ serverModulesReady.then(() => {
96
+ const startingPort = Number(process?.env?.PORT) || 5173; // Default to Vite's default port
97
+ findAvailablePort(startingPort).then((actualPort) => {
98
+ resolve(actualPort);
99
+ });
87
100
  });
88
101
  }
89
102
  });
90
103
  }
91
- const port = await getServerPort();
104
+ // Port resolution starts immediately but doesn't block module evaluation.
105
+ // The template is updated asynchronously once the port is known.
106
+ let port = 5173; // Default fallback
107
+ void getServerPort().then((p) => {
108
+ port = p;
109
+ ggConfig.openInEditorUrlTemplate = `http://localhost:${port}/__open-in-editor?file=$FILENAME`;
110
+ });
92
111
  /**
93
112
  * Determines if gg should be enabled based on environment and runtime triggers.
94
113
  *
@@ -350,8 +369,8 @@ gg._ns = function (options, ...args) {
350
369
  gg._o = function (ns, file, line, col, src) {
351
370
  return { ns, file, line, col, src };
352
371
  };
353
- gg.disable = isCloudflareWorker() ? () => { } : debugFactory.disable;
354
- gg.enable = isCloudflareWorker() ? () => { } : debugFactory.enable;
372
+ gg.disable = isCloudflareWorker() ? () => '' : () => debugFactory.disable();
373
+ gg.enable = isCloudflareWorker() ? () => { } : (ns) => debugFactory.enable(ns);
355
374
  /**
356
375
  * Clear the persisted gg-enabled state from localStorage.
357
376
  * Useful to reset production trigger after testing with ?gg parameter.
@@ -956,6 +975,8 @@ export async function runGgDiagnostics() {
956
975
  if (!ggConfig.showHints || isCloudflareWorker() || diagnosticsRan)
957
976
  return;
958
977
  diagnosticsRan = true;
978
+ // Ensure server modules (dotenv) are loaded before diagnostics
979
+ await serverModulesReady;
959
980
  // Create test debugger for server-side enabled check
960
981
  const ggLogTest = debugFactory('gg:TEST');
961
982
  let ggMessage = '\n';
package/dist/vite.d.ts CHANGED
@@ -21,8 +21,6 @@ export interface GgPluginsOptions {
21
21
  * Includes:
22
22
  * - `ggCallSitesPlugin` — rewrites `gg()` calls with source file/line/col metadata
23
23
  * - `openInEditorPlugin` — adds `/__open-in-editor` dev server middleware
24
- * - Automatic `es2022` build target (required for top-level await)
25
- *
26
24
  * @example
27
25
  * ```ts
28
26
  * import ggPlugins from '@leftium/gg/vite';
package/dist/vite.js CHANGED
@@ -6,8 +6,6 @@ import openInEditorPlugin from './open-in-editor.js';
6
6
  * Includes:
7
7
  * - `ggCallSitesPlugin` — rewrites `gg()` calls with source file/line/col metadata
8
8
  * - `openInEditorPlugin` — adds `/__open-in-editor` dev server middleware
9
- * - Automatic `es2022` build target (required for top-level await)
10
- *
11
9
  * @example
12
10
  * ```ts
13
11
  * import ggPlugins from '@leftium/gg/vite';
@@ -19,23 +17,6 @@ import openInEditorPlugin from './open-in-editor.js';
19
17
  */
20
18
  export default function ggPlugins(options = {}) {
21
19
  const plugins = [];
22
- // Ensure es2022 target for top-level await support
23
- plugins.push({
24
- name: 'gg-config',
25
- config() {
26
- return {
27
- build: {
28
- target: 'es2022'
29
- },
30
- optimizeDeps: {
31
- esbuildOptions: {
32
- target: 'es2022',
33
- supported: { 'top-level-await': true }
34
- }
35
- }
36
- };
37
- }
38
- });
39
20
  plugins.push(ggCallSitesPlugin(options.callSites));
40
21
  if (options.openInEditor !== false) {
41
22
  plugins.push(openInEditorPlugin());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leftium/gg",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Leftium/gg.git"