@tanstack/devtools-vite 0.4.1 → 0.5.1

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.
@@ -0,0 +1,184 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { parseOpenSourceParam, stripEnhancedLogPrefix } from './utils'
3
+
4
+ describe('parseOpenSourceParam', () => {
5
+ test('parses simple file:line:column format', () => {
6
+ const result = parseOpenSourceParam('src/file.tsx:26:13')
7
+ expect(result).toEqual({ file: 'src/file.tsx', line: '26', column: '13' })
8
+ })
9
+
10
+ test('parses file path with multiple slashes', () => {
11
+ const result = parseOpenSourceParam('src/components/Header.tsx:100:5')
12
+ expect(result).toEqual({
13
+ file: 'src/components/Header.tsx',
14
+ line: '100',
15
+ column: '5',
16
+ })
17
+ })
18
+
19
+ test('parses file path with colons in filename (Windows-style)', () => {
20
+ const result = parseOpenSourceParam('C:/Users/test/file.tsx:10:20')
21
+ expect(result).toEqual({
22
+ file: 'C:/Users/test/file.tsx',
23
+ line: '10',
24
+ column: '20',
25
+ })
26
+ })
27
+
28
+ test('returns null for invalid format without line/column', () => {
29
+ const result = parseOpenSourceParam('src/file.tsx')
30
+ expect(result).toBeNull()
31
+ })
32
+
33
+ test('returns null for invalid format with only one number', () => {
34
+ const result = parseOpenSourceParam('src/file.tsx:26')
35
+ expect(result).toBeNull()
36
+ })
37
+ })
38
+
39
+ describe('stripEnhancedLogPrefix', () => {
40
+ test('returns empty array for empty input', () => {
41
+ const result = stripEnhancedLogPrefix([])
42
+ expect(result).toEqual([])
43
+ })
44
+
45
+ test('returns args unchanged when no enhanced log URL is present', () => {
46
+ const args = ['hello', 'world', 123]
47
+ const result = stripEnhancedLogPrefix(args)
48
+ expect(result).toEqual(args)
49
+ })
50
+
51
+ test('extracts source location and user args from enhanced log format', () => {
52
+ // Simulated enhanced log format:
53
+ // ['%cLOG%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Ffile.tsx%3A26%3A13%c \n → ', 'style1', 'style2', 'style3', 'style4', 'user message']
54
+ const args = [
55
+ '%cLOG%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Ffile.tsx%3A26%3A13%c \n → ',
56
+ 'color: blue',
57
+ 'color: reset',
58
+ 'color: green',
59
+ 'color: reset',
60
+ 'user message',
61
+ ]
62
+ const result = stripEnhancedLogPrefix(args)
63
+ expect(result).toEqual(['src/file.tsx:26:13', 'user message'])
64
+ })
65
+
66
+ test('handles multiple user args after enhanced log prefix', () => {
67
+ const args = [
68
+ '%cLOG%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Fcomponents%2FHeader.tsx%3A100%3A5%c \n → ',
69
+ 'style1',
70
+ 'style2',
71
+ 'style3',
72
+ 'style4',
73
+ 'message 1',
74
+ { key: 'value' },
75
+ 42,
76
+ ]
77
+ const result = stripEnhancedLogPrefix(args)
78
+ expect(result).toEqual([
79
+ 'src/components/Header.tsx:100:5',
80
+ 'message 1',
81
+ { key: 'value' },
82
+ 42,
83
+ ])
84
+ })
85
+
86
+ test('handles source location with leading slash by removing it', () => {
87
+ const args = [
88
+ '%cINFO%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fapp%2Fpage.tsx%3A10%3A1%c \n → ',
89
+ 's1',
90
+ 's2',
91
+ 's3',
92
+ 's4',
93
+ 'info log',
94
+ ]
95
+ const result = stripEnhancedLogPrefix(args)
96
+ // Leading slash is removed
97
+ expect(result).toEqual(['app/page.tsx:10:1', 'info log'])
98
+ })
99
+
100
+ test('applies custom formatter to source location', () => {
101
+ const args = [
102
+ '%cWARN%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Findex.ts%3A5%3A2%c \n → ',
103
+ 's1',
104
+ 's2',
105
+ 's3',
106
+ 's4',
107
+ 'warning message',
108
+ ]
109
+ const customFormatter = (loc: string) => `[${loc}]`
110
+ const result = stripEnhancedLogPrefix(args, customFormatter)
111
+ expect(result).toEqual(['[src/index.ts:5:2]', 'warning message'])
112
+ })
113
+
114
+ test('handles ANSI prefix format (second variant)', () => {
115
+ // Format: ['\x1b[...]%s\x1b[...]', '%cLOG%c %cGo to Source: ...%c \n → ', 'style...', ..., 'message']
116
+ const args = [
117
+ '\x1b[90m%s\x1b[0m',
118
+ '%cDEBUG%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Fdebug.ts%3A1%3A1%c \n → ',
119
+ 's1',
120
+ 's2',
121
+ 's3',
122
+ 's4',
123
+ 'debug output',
124
+ ]
125
+ const result = stripEnhancedLogPrefix(args)
126
+ // The source URL is in index 1, so source location should be extracted from there
127
+ expect(result).toEqual(['src/debug.ts:1:1', 'debug output'])
128
+ })
129
+
130
+ test('returns original args when source URL decoding fails', () => {
131
+ const args = [
132
+ '%cLOG%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%INVALID%c \n → ',
133
+ 's1',
134
+ 's2',
135
+ 's3',
136
+ 's4',
137
+ 'message',
138
+ ]
139
+ // Should gracefully handle decoding error and return user args without source
140
+ const result = stripEnhancedLogPrefix(args)
141
+ // Since decoding fails, sourceLocation will be empty, so result should just have user args
142
+ expect(result).toEqual(['message'])
143
+ })
144
+
145
+ test('handles enhanced log with no user args after style markers', () => {
146
+ const args = [
147
+ 'Go to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Ftest.ts%3A1%3A1%c',
148
+ 'style-arg', // This is consumed as a style arg due to %c marker
149
+ ]
150
+ const result = stripEnhancedLogPrefix(args)
151
+ // One %c marker means one style arg follows the source arg, leaving no user args
152
+ expect(result).toEqual(['src/test.ts:1:1'])
153
+ })
154
+
155
+ test('handles enhanced log at non-zero index in args array', () => {
156
+ const args = [
157
+ 'some prefix',
158
+ '%cERROR%c %cGo to Source: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Ferror.ts%3A50%3A10%c \n → ',
159
+ 's1',
160
+ 's2',
161
+ 's3',
162
+ 's4',
163
+ 'error details',
164
+ ]
165
+ const result = stripEnhancedLogPrefix(args)
166
+ expect(result).toEqual(['src/error.ts:50:10', 'error details'])
167
+ })
168
+
169
+ test('handles deeply nested path in source location', () => {
170
+ const args = [
171
+ '%cLOG%c %cGo to Source: http://localhost:5173/__tsd/open-source?source=%2Fpackages%2Fdevtools-vite%2Fsrc%2Fplugin.ts%3A123%3A45%c \n → ',
172
+ 's1',
173
+ 's2',
174
+ 's3',
175
+ 's4',
176
+ 'deep path log',
177
+ ]
178
+ const result = stripEnhancedLogPrefix(args)
179
+ expect(result).toEqual([
180
+ 'packages/devtools-vite/src/plugin.ts:123:45',
181
+ 'deep path log',
182
+ ])
183
+ })
184
+ })
package/src/utils.ts CHANGED
@@ -4,12 +4,31 @@ import type { Connect } from 'vite'
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http'
5
5
  import type { PackageJson } from '@tanstack/devtools-client'
6
6
 
7
+ type DevToolsRequestHandler = (data: any) => void
8
+
9
+ type DevToolsViteRequestOptions = {
10
+ onOpenSource?: DevToolsRequestHandler
11
+ onConsolePipe?: (entries: Array<any>) => void
12
+ onServerConsolePipe?: (entries: Array<any>) => void
13
+ onConsolePipeSSE?: (
14
+ res: ServerResponse<IncomingMessage>,
15
+ req: Connect.IncomingMessage,
16
+ ) => void
17
+ }
18
+
7
19
  export const handleDevToolsViteRequest = (
8
20
  req: Connect.IncomingMessage,
9
21
  res: ServerResponse<IncomingMessage>,
10
22
  next: Connect.NextFunction,
11
- cb: (data: any) => void,
23
+ cbOrOptions: DevToolsRequestHandler | DevToolsViteRequestOptions,
12
24
  ) => {
25
+ // Normalize to options object for backward compatibility
26
+ const options: DevToolsViteRequestOptions =
27
+ typeof cbOrOptions === 'function'
28
+ ? { onOpenSource: cbOrOptions }
29
+ : cbOrOptions
30
+
31
+ // Handle open-source requests
13
32
  if (req.url?.includes('__tsd/open-source')) {
14
33
  const searchParams = new URLSearchParams(req.url.split('?')[1])
15
34
 
@@ -24,7 +43,7 @@ export const handleDevToolsViteRequest = (
24
43
  }
25
44
  const { file, line, column } = parsed
26
45
 
27
- cb({
46
+ options.onOpenSource?.({
28
47
  type: 'open-source',
29
48
  routine: 'open-source',
30
49
  data: {
@@ -38,6 +57,62 @@ export const handleDevToolsViteRequest = (
38
57
  res.end()
39
58
  return
40
59
  }
60
+
61
+ // Handle console-pipe SSE endpoint (browser subscribes to server logs)
62
+ if (req.url?.includes('__tsd/console-pipe/sse') && req.method === 'GET') {
63
+ if (options.onConsolePipeSSE) {
64
+ options.onConsolePipeSSE(res, req)
65
+ return
66
+ }
67
+ return next()
68
+ }
69
+
70
+ // Handle server console-pipe POST endpoint (from app server runtime)
71
+ if (req.url?.includes('__tsd/console-pipe/server') && req.method === 'POST') {
72
+ if (options.onServerConsolePipe) {
73
+ let body = ''
74
+ req.on('data', (chunk: Buffer) => {
75
+ body += chunk.toString()
76
+ })
77
+ req.on('end', () => {
78
+ try {
79
+ const { entries } = JSON.parse(body)
80
+ options.onServerConsolePipe!(entries)
81
+ res.statusCode = 200
82
+ res.end('OK')
83
+ } catch {
84
+ res.statusCode = 400
85
+ res.end('Bad Request')
86
+ }
87
+ })
88
+ return
89
+ }
90
+ return next()
91
+ }
92
+
93
+ // Handle console-pipe POST endpoint (from client)
94
+ if (req.url?.includes('__tsd/console-pipe') && req.method === 'POST') {
95
+ if (options.onConsolePipe) {
96
+ let body = ''
97
+ req.on('data', (chunk: Buffer) => {
98
+ body += chunk.toString()
99
+ })
100
+ req.on('end', () => {
101
+ try {
102
+ const { entries } = JSON.parse(body)
103
+ options.onConsolePipe!(entries)
104
+ res.statusCode = 200
105
+ res.end('OK')
106
+ } catch {
107
+ res.statusCode = 400
108
+ res.end('Bad Request')
109
+ }
110
+ })
111
+ return
112
+ }
113
+ return next()
114
+ }
115
+
41
116
  if (!req.url?.includes('__tsd')) {
42
117
  return next()
43
118
  }
@@ -50,9 +125,10 @@ export const handleDevToolsViteRequest = (
50
125
  const dataToParse = Buffer.concat(chunks)
51
126
  try {
52
127
  const parsedData = JSON.parse(dataToParse.toString())
53
- cb(parsedData)
128
+ options.onOpenSource?.(parsedData)
54
129
  } catch (e) {}
55
130
  res.write('OK')
131
+ res.end()
56
132
  })
57
133
  }
58
134
 
@@ -92,3 +168,80 @@ export const tryParseJson = <T extends any>(
92
168
 
93
169
  export const readPackageJson = async () =>
94
170
  tryParseJson<PackageJson>(await tryReadFile(process.cwd() + '/package.json'))
171
+
172
+ /**
173
+ * Extracts and formats the source location from enhanced client console logs.
174
+ * Instead of stripping the prefix entirely, we extract the file:line:column
175
+ * from the "Go to Source" URL and use that as a prefix.
176
+ *
177
+ * Enhanced logs format (two variants):
178
+ * 1. ['%cLOG%c %cGo to Source: http://...?source=%2Fsrc%2F...%c \n → ', 'color:...', 'color:...', 'color:...', 'color:...', 'message']
179
+ * 2. ['\x1b[...]%s\x1b[...]', '%cLOG%c %cGo to Source: ...%c \n → ', 'color:...', 'color:...', 'color:...', 'color:...', 'message']
180
+ *
181
+ * Output: ['src/components/Header.tsx:26:13', 'message']
182
+ */
183
+ export const stripEnhancedLogPrefix = (
184
+ args: Array<unknown>,
185
+ formatSourceLocation?: (location: string) => unknown,
186
+ ): Array<unknown> => {
187
+ if (args.length === 0) return args
188
+
189
+ // Find the arg that contains the Go to Source URL
190
+ let sourceArgIndex = -1
191
+ for (let i = 0; i < args.length; i++) {
192
+ const arg = args[i]
193
+ if (typeof arg === 'string' && arg.includes('__tsd/open-source?source=')) {
194
+ sourceArgIndex = i
195
+ break
196
+ }
197
+ }
198
+
199
+ // If no source URL found, return args as-is (not an enhanced log)
200
+ if (sourceArgIndex === -1) {
201
+ return args
202
+ }
203
+
204
+ const sourceArg = args[sourceArgIndex] as string
205
+
206
+ // Extract the source from the "Go to Source" URL
207
+ // URL format: http://localhost:3000/__tsd/open-source?source=%2Fsrc%2Ffile.tsx%3A26%3A13%c
208
+ // Note: The URL ends with %c which is a console format specifier, not URL encoding
209
+ let sourceLocation = ''
210
+ const sourceMatch = sourceArg.match(/source=([^&\s]+?)%c/)
211
+ if (sourceMatch?.[1]) {
212
+ try {
213
+ sourceLocation = decodeURIComponent(sourceMatch[1])
214
+ // Remove leading slash if present
215
+ if (sourceLocation.startsWith('/')) {
216
+ sourceLocation = sourceLocation.slice(1)
217
+ }
218
+ } catch {
219
+ // If decoding fails, leave it empty
220
+ }
221
+ }
222
+
223
+ // Count %c markers in the source arg to know how many style args follow it
224
+ const styleCount = (sourceArg.match(/%c/g) || []).length
225
+
226
+ // The actual user args start after the source arg and all its style args
227
+ const userArgsStart = sourceArgIndex + 1 + styleCount
228
+
229
+ // Build the result: source location prefix + remaining args (the actual user data)
230
+ const result: Array<unknown> = []
231
+
232
+ // Add source location as prefix if we found one
233
+ if (sourceLocation) {
234
+ result.push(
235
+ formatSourceLocation
236
+ ? formatSourceLocation(sourceLocation)
237
+ : sourceLocation,
238
+ )
239
+ }
240
+
241
+ // Add remaining args (the actual user data)
242
+ for (let i = userArgsStart; i < args.length; i++) {
243
+ result.push(args[i])
244
+ }
245
+
246
+ return result.length > 0 ? result : args
247
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { generateConsolePipeCode } from './virtual-console'
3
+
4
+ const TEST_VITE_URL = 'http://localhost:5173'
5
+
6
+ describe('virtual-console', () => {
7
+ test('generates inline code with specified levels', () => {
8
+ const code = generateConsolePipeCode(['log', 'error'], TEST_VITE_URL)
9
+
10
+ expect(code).toContain('["log","error"]')
11
+ expect(code).toContain('originalConsole')
12
+ expect(code).toContain('__TSD_CONSOLE_PIPE_INITIALIZED__')
13
+ })
14
+
15
+ test('uses fetch to send client logs', () => {
16
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
17
+
18
+ expect(code).toContain('/__tsd/console-pipe')
19
+ expect(code).toContain("method: 'POST'")
20
+ })
21
+
22
+ test('uses SSE to receive server logs', () => {
23
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
24
+
25
+ expect(code).toContain("new EventSource('/__tsd/console-pipe/sse')")
26
+ })
27
+
28
+ test('includes environment detection', () => {
29
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
30
+
31
+ expect(code).toContain("typeof window === 'undefined'")
32
+ expect(code).toContain('isServer')
33
+ })
34
+
35
+ test('includes batcher configuration', () => {
36
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
37
+
38
+ expect(code).toContain('BATCH_WAIT')
39
+ expect(code).toContain('BATCH_MAX_SIZE')
40
+ })
41
+
42
+ test('includes flush functionality', () => {
43
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
44
+
45
+ expect(code).toContain('flushBatch')
46
+ })
47
+
48
+ test('includes beforeunload listener for browser', () => {
49
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
50
+
51
+ expect(code).toContain('beforeunload')
52
+ })
53
+
54
+ test('wraps code in IIFE', () => {
55
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
56
+
57
+ expect(code).toContain('(function __tsdConsolePipe()')
58
+ expect(code).toContain('})();')
59
+ })
60
+
61
+ test('has no external imports', () => {
62
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
63
+
64
+ expect(code).not.toContain('import ')
65
+ })
66
+
67
+ test('includes vite server URL for server piping', () => {
68
+ const code = generateConsolePipeCode(['log'], TEST_VITE_URL)
69
+
70
+ expect(code).toContain(TEST_VITE_URL)
71
+ expect(code).toContain('/__tsd/console-pipe/server')
72
+ })
73
+ })
@@ -0,0 +1,202 @@
1
+ import type { ConsoleLevel } from './plugin'
2
+
3
+ // export const VIRTUAL_MODULE_ID = 'virtual:tanstack-devtools-console'
4
+ // export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID
5
+
6
+ /**
7
+ * Generates inline code to inject into entry files (both client and server).
8
+ * This code detects the environment at runtime and:
9
+ *
10
+ * CLIENT:
11
+ * 1. Store original console methods
12
+ * 2. Create batched wrappers that POST to server via fetch
13
+ * 3. Override global console with the wrapped methods
14
+ * 4. Listen for server console logs via SSE
15
+ *
16
+ * SERVER (Nitro/Vinxi runtime):
17
+ * 1. Store original console methods
18
+ * 2. Create batched wrappers that POST to Vite dev server
19
+ * 3. Override global console - original logging still happens, just also pipes to Vite
20
+ *
21
+ * Returns the inline code as a string - no imports needed since we use fetch.
22
+ */
23
+ export function generateConsolePipeCode(
24
+ levels: Array<ConsoleLevel>,
25
+ viteServerUrl: string,
26
+ ): string {
27
+ const levelsArray = JSON.stringify(levels)
28
+
29
+ return `
30
+ ;(function __tsdConsolePipe() {
31
+ // Detect environment
32
+ var isServer = typeof window === 'undefined';
33
+ var envKey = isServer ? '__TSD_SERVER_CONSOLE_PIPE_INITIALIZED__' : '__TSD_CONSOLE_PIPE_INITIALIZED__';
34
+ var globalObj = isServer ? globalThis : window;
35
+
36
+ // Only run once per environment
37
+ if (globalObj[envKey]) return;
38
+ globalObj[envKey] = true;
39
+
40
+ var CONSOLE_LEVELS = ${levelsArray};
41
+ var VITE_SERVER_URL = ${JSON.stringify(viteServerUrl)};
42
+
43
+ // Store original console methods before we override them
44
+ var originalConsole = {};
45
+ for (var i = 0; i < CONSOLE_LEVELS.length; i++) {
46
+ var level = CONSOLE_LEVELS[i];
47
+ originalConsole[level] = console[level].bind(console);
48
+ }
49
+
50
+ // Simple inline batcher implementation
51
+ var batchedEntries = [];
52
+ var batchTimeout = null;
53
+ var BATCH_WAIT = isServer ? 50 : 100;
54
+ var BATCH_MAX_SIZE = isServer ? 20 : 50;
55
+
56
+ function flushBatch() {
57
+ if (batchedEntries.length === 0) return;
58
+
59
+ var entries = batchedEntries;
60
+ batchedEntries = [];
61
+ batchTimeout = null;
62
+
63
+ // Determine endpoint based on environment
64
+ var endpoint = isServer
65
+ ? VITE_SERVER_URL + '/__tsd/console-pipe/server'
66
+ : '/__tsd/console-pipe';
67
+
68
+ // Send to Vite server via fetch
69
+ fetch(endpoint, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify({ entries: entries }),
73
+ }).catch(function( ) {
74
+ // Swallow errors
75
+ });
76
+ }
77
+
78
+ function addToBatch(entry) {
79
+ batchedEntries.push(entry);
80
+
81
+ if (batchedEntries.length >= BATCH_MAX_SIZE) {
82
+ if (batchTimeout) {
83
+ clearTimeout(batchTimeout);
84
+ batchTimeout = null;
85
+ }
86
+ flushBatch();
87
+ } else if (!batchTimeout) {
88
+ batchTimeout = setTimeout(flushBatch, BATCH_WAIT);
89
+ }
90
+ }
91
+
92
+ // Override global console methods
93
+ for (var j = 0; j < CONSOLE_LEVELS.length; j++) {
94
+ (function(level) {
95
+ var original = originalConsole[level];
96
+ console[level] = function() {
97
+ var args = Array.prototype.slice.call(arguments);
98
+
99
+ // Always call original first so logs appear normally
100
+ original.apply(console, args);
101
+
102
+ // Skip our own TSD Console Pipe logs to avoid recursion/noise
103
+ if (args.length > 0 && typeof args[0] === 'string' &&
104
+ (args[0].indexOf('[TSD Console Pipe]') !== -1 ||
105
+ args[0].indexOf('[@tanstack/devtools') !== -1)) {
106
+ return;
107
+ }
108
+
109
+ // Serialize args safely
110
+ var safeArgs = args.map(function(arg) {
111
+ if (arg === undefined) return 'undefined';
112
+ if (arg === null) return null;
113
+ if (typeof arg === 'function') return '[Function]';
114
+ if (typeof arg === 'symbol') return arg.toString();
115
+ try {
116
+ JSON.stringify(arg);
117
+ return arg;
118
+ } catch (e) {
119
+ return String(arg);
120
+ }
121
+ });
122
+
123
+ var entry = {
124
+ level: level,
125
+ args: safeArgs,
126
+ source: isServer ? 'server' : 'client',
127
+ timestamp: Date.now(),
128
+ };
129
+
130
+ addToBatch(entry);
131
+ };
132
+ })(CONSOLE_LEVELS[j]);
133
+ }
134
+
135
+ // CLIENT ONLY: Listen for server console logs via SSE
136
+ if (!isServer) {
137
+ // Transform server log args - strip ANSI codes and convert source paths to clickable URLs
138
+ function transformServerLogArgs(args) {
139
+ var escChar = String.fromCharCode(27);
140
+ var transformed = [];
141
+
142
+ for (var k = 0; k < args.length; k++) {
143
+ var arg = args[k];
144
+ if (typeof arg === 'string') {
145
+ // Strip ANSI escape sequences (ESC[...m patterns)
146
+ var cleaned = arg;
147
+ // Remove ESC character followed by [...m] - need to build regex dynamically
148
+ while (cleaned.indexOf(escChar) !== -1) {
149
+ cleaned = cleaned.split(escChar).join('');
150
+ }
151
+ // Also remove any leftover bracket codes like [35m
152
+ cleaned = cleaned.replace(/\\[[0-9;]*m/g, '');
153
+
154
+ // Transform source paths to clickable URLs
155
+ // Match patterns like /src/components/Header.tsx:17:3
156
+ var sourceRegex = /(\\/[^\\s]+:\\d+:\\d+)/g;
157
+ cleaned = cleaned.replace(sourceRegex, function(match) {
158
+ return window.location.origin + '/__tsd/open-source?source=' + encodeURIComponent(match);
159
+ });
160
+
161
+ if (cleaned.trim()) {
162
+ transformed.push(cleaned);
163
+ }
164
+ } else {
165
+ transformed.push(arg);
166
+ }
167
+ }
168
+
169
+ return transformed;
170
+ }
171
+
172
+ var eventSource = new EventSource('/__tsd/console-pipe/sse');
173
+
174
+ eventSource.onmessage = function(event) {
175
+ try {
176
+ var data = JSON.parse(event.data);
177
+ if (data.entries) {
178
+ for (var m = 0; m < data.entries.length; m++) {
179
+ var entry = data.entries[m];
180
+ var transformedArgs = transformServerLogArgs(entry.args);
181
+ var prefix = '%c[Server]%c';
182
+ var prefixStyle = 'color: #9333ea; font-weight: bold;';
183
+ var resetStyle = 'color: inherit;';
184
+ var logMethod = originalConsole[entry.level] || originalConsole.log;
185
+ logMethod.apply(console, [prefix, prefixStyle, resetStyle].concat(transformedArgs));
186
+ }
187
+ }
188
+ } catch (err) {
189
+ // Swallow errors
190
+ }
191
+ };
192
+
193
+ eventSource.onerror = function() {
194
+ // Swallow errors
195
+ };
196
+
197
+ // Flush on page unload
198
+ window.addEventListener('beforeunload', flushBatch);
199
+ }
200
+ })();
201
+ `
202
+ }