dev3000 0.0.10 → 0.0.13

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, it, expect } from 'vitest';
2
+ import { parseLogEntries } from './LogsClient';
3
+ import { LogEntry } from '../../types';
4
+
5
+ describe('parseLogEntries', () => {
6
+ it('should parse single-line log entries correctly', () => {
7
+ const logContent = `[2025-09-03T03:57:39.357Z] [SERVER] myapp:dev: Cache hits: 5, misses: 2
8
+ [2025-09-03T03:57:40.123Z] [BROWSER] [CONSOLE LOG] Component rendered successfully`;
9
+
10
+ const entries = parseLogEntries(logContent);
11
+
12
+ expect(entries).toHaveLength(2);
13
+
14
+ expect(entries[0]).toEqual({
15
+ timestamp: '2025-09-03T03:57:39.357Z',
16
+ source: 'SERVER',
17
+ message: 'myapp:dev: Cache hits: 5, misses: 2',
18
+ original: '[2025-09-03T03:57:39.357Z] [SERVER] myapp:dev: Cache hits: 5, misses: 2'
19
+ });
20
+
21
+ expect(entries[1]).toEqual({
22
+ timestamp: '2025-09-03T03:57:40.123Z',
23
+ source: 'BROWSER',
24
+ message: '[CONSOLE LOG] Component rendered successfully',
25
+ original: '[2025-09-03T03:57:40.123Z] [BROWSER] [CONSOLE LOG] Component rendered successfully'
26
+ });
27
+ });
28
+
29
+ it('should group multi-line log entries by timestamp', () => {
30
+ const logContent = `[2025-09-03T03:57:39.614Z] [SERVER] myapp:dev: api_request[warn] Server error: {
31
+ myapp:dev: request: '/api/v1/users/search',
32
+ myapp:dev: reason: Error [AuthError]: Missing authentication token
33
+ myapp:dev: at validateAuth (../../packages/auth/src/middleware.ts:45:28)
34
+ myapp:dev: 43 | const token = req.headers.authorization;
35
+ myapp:dev: 44 | if (!token) {
36
+ myapp:dev: > 45 | throw new Error('Missing authentication token');
37
+ myapp:dev: | ^
38
+ myapp:dev: 46 | }
39
+ myapp:dev: 47 | return validateToken(token);
40
+ myapp:dev: 48 | }
41
+ myapp:dev: code: 'auth_required',
42
+ myapp:dev: status: 401,
43
+ myapp:dev: attributes: {
44
+ myapp:dev: 'http.method': 'GET',
45
+ myapp:dev: 'http.url': 'http://localhost:3000/api/v1/users/search',
46
+ myapp:dev: 'http.status_code': 401,
47
+ myapp:dev: 'request.id': 'req_abc123def456'
48
+ myapp:dev: },
49
+ myapp:dev: missingAuth: true
50
+ myapp:dev: }
51
+ myapp:dev: }
52
+ [2025-09-03T03:57:40.778Z] [SERVER] myapp:dev: Cache[info] Prefetch unused: [ '"/api/v2/users?page=1"' ]`;
53
+
54
+ const entries = parseLogEntries(logContent);
55
+
56
+ expect(entries).toHaveLength(2);
57
+
58
+ // First entry should contain the entire multi-line error
59
+ expect(entries[0].timestamp).toBe('2025-09-03T03:57:39.614Z');
60
+ expect(entries[0].source).toBe('SERVER');
61
+ expect(entries[0].message).toContain('api_request[warn] Server error: {');
62
+ expect(entries[0].message).toContain('Missing authentication token');
63
+ expect(entries[0].message).toContain('missingAuth: true');
64
+ expect(entries[0].message).toContain('}');
65
+
66
+ // Second entry should be the single-line cache message
67
+ expect(entries[1].timestamp).toBe('2025-09-03T03:57:40.778Z');
68
+ expect(entries[1].source).toBe('SERVER');
69
+ expect(entries[1].message).toBe('myapp:dev: Cache[info] Prefetch unused: [ \'"/api/v2/users?page=1"\' ]');
70
+ });
71
+
72
+ it('should handle screenshot entries correctly', () => {
73
+ const logContent = `[2025-09-03T02:14:27.444Z] [BROWSER] [SCREENSHOT] http://localhost:3684/screenshots/2025-09-03T02-14-27-329Z-initial-load.png
74
+ [2025-09-03T02:14:27.444Z] [BROWSER] 📄 New page: http://localhost:3000/`;
75
+
76
+ const entries = parseLogEntries(logContent);
77
+
78
+ expect(entries).toHaveLength(2);
79
+
80
+ expect(entries[0].screenshot).toBe('http://localhost:3684/screenshots/2025-09-03T02-14-27-329Z-initial-load.png');
81
+ expect(entries[0].message).toBe('[SCREENSHOT] http://localhost:3684/screenshots/2025-09-03T02-14-27-329Z-initial-load.png');
82
+
83
+ expect(entries[1].screenshot).toBeUndefined();
84
+ expect(entries[1].message).toBe('📄 New page: http://localhost:3000/');
85
+ });
86
+
87
+ it('should handle complex stack traces and nested objects', () => {
88
+ const logContent = `[2025-09-03T03:57:39.098Z] [SERVER] ❌ Database connection failed: DatabaseError: Connection timeout
89
+ myapp:dev: at ConnectionPool.connect (/app/src/database/pool.ts:142:15)
90
+ myapp:dev: at DatabaseService.initialize (/app/src/services/database.ts:87:31)
91
+ myapp:dev: at async Server.start (/app/src/server.ts:56:8)
92
+ myapp:dev: > 142 | throw new DatabaseError('Connection timeout');
93
+ myapp:dev: | ^
94
+ myapp:dev: 143 | }
95
+ myapp:dev: 144 | return connection;
96
+ myapp:dev: 145 | }
97
+ [2025-09-03T03:57:39.098Z] [SERVER] ❌ Error details: {
98
+ myapp:dev: message: 'connection.query is not a function',
99
+ myapp:dev: stack: 'TypeError: connection.query is not a function\\n' +
100
+ myapp:dev: ' at Database.execute (/app/src/database/client.ts:89:27)\\n' +
101
+ myapp:dev: ' at UserService.findById (/app/src/services/user.ts:45:19)\\n' +
102
+ myapp:dev: ' at async Handler.getUser (/app/src/handlers/user.ts:23:18)',
103
+ myapp:dev: config: {
104
+ myapp:dev: host: 'localhost',
105
+ myapp:dev: port: 5432,
106
+ myapp:dev: database: 'myapp_dev',
107
+ myapp:dev: retries: 3
108
+ myapp:dev: },
109
+ myapp:dev: connectionId: 'conn_xyz789abc'
110
+ myapp:dev: }
111
+ [2025-09-03T03:57:40.200Z] [BROWSER] [CONSOLE ERROR] Uncaught TypeError: Cannot read properties of null`;
112
+
113
+ const entries = parseLogEntries(logContent);
114
+
115
+ expect(entries).toHaveLength(3);
116
+
117
+ // First entry - connection error with stack trace
118
+ expect(entries[0].timestamp).toBe('2025-09-03T03:57:39.098Z');
119
+ expect(entries[0].source).toBe('SERVER');
120
+ expect(entries[0].message).toContain('❌ Database connection failed');
121
+ expect(entries[0].message).toContain('ConnectionPool.connect');
122
+ expect(entries[0].message).toContain('Connection timeout');
123
+
124
+ // Second entry - error details object
125
+ expect(entries[1].timestamp).toBe('2025-09-03T03:57:39.098Z');
126
+ expect(entries[1].source).toBe('SERVER');
127
+ expect(entries[1].message).toContain('❌ Error details: {');
128
+ expect(entries[1].message).toContain('connection.query is not a function');
129
+ expect(entries[1].message).toContain('connectionId: \'conn_xyz789abc\'');
130
+
131
+ // Third entry - browser error
132
+ expect(entries[2].timestamp).toBe('2025-09-03T03:57:40.200Z');
133
+ expect(entries[2].source).toBe('BROWSER');
134
+ expect(entries[2].message).toBe('[CONSOLE ERROR] Uncaught TypeError: Cannot read properties of null');
135
+ });
136
+
137
+ it('should handle empty and malformed log content', () => {
138
+ expect(parseLogEntries('')).toHaveLength(0);
139
+ expect(parseLogEntries(' \n \n ')).toHaveLength(0);
140
+ expect(parseLogEntries('Invalid log line without timestamp')).toHaveLength(0);
141
+ });
142
+
143
+ it('should preserve original content for debugging', () => {
144
+ const logContent = `[2025-09-03T03:57:39.357Z] [SERVER] API response: {
145
+ myapp:dev: userId: 'user_123',
146
+ myapp:dev: status: 'active'
147
+ myapp:dev: }`;
148
+
149
+ const entries = parseLogEntries(logContent);
150
+
151
+ expect(entries).toHaveLength(1);
152
+ expect(entries[0].original).toBe(logContent);
153
+ expect(entries[0].message).toContain('API response: {');
154
+ expect(entries[0].message).toContain('userId: \'user_123\'');
155
+ });
156
+
157
+ it('should handle mixed single-line and multi-line entries', () => {
158
+ const logContent = `[2025-09-03T03:57:38.500Z] [SERVER] ✓ Server started on port 3000
159
+ [2025-09-03T03:57:39.614Z] [SERVER] myapp:dev: validation[error] Request failed: {
160
+ myapp:dev: endpoint: '/api/v1/validate',
161
+ myapp:dev: errors: [
162
+ myapp:dev: { field: 'email', message: 'Invalid format' },
163
+ myapp:dev: { field: 'password', message: 'Too short' }
164
+ myapp:dev: ],
165
+ myapp:dev: requestId: 'req_validation_456'
166
+ myapp:dev: }
167
+ [2025-09-03T03:57:40.100Z] [BROWSER] [NAVIGATION] http://localhost:3000/dashboard
168
+ [2025-09-03T03:57:40.300Z] [SERVER] Dashboard page rendered in 89ms`;
169
+
170
+ const entries = parseLogEntries(logContent);
171
+
172
+ expect(entries).toHaveLength(4);
173
+
174
+ // Single-line entries
175
+ expect(entries[0].message).toBe('✓ Server started on port 3000');
176
+ expect(entries[2].message).toBe('[NAVIGATION] http://localhost:3000/dashboard');
177
+ expect(entries[3].message).toBe('Dashboard page rendered in 89ms');
178
+
179
+ // Multi-line entry
180
+ expect(entries[1].message).toContain('validation[error] Request failed: {');
181
+ expect(entries[1].message).toContain('Invalid format');
182
+ expect(entries[1].message).toContain('requestId: \'req_validation_456\'');
183
+ });
184
+ });
@@ -3,8 +3,53 @@
3
3
  import { useState, useEffect, useRef, useMemo } from 'react';
4
4
  import { LogEntry, LogsApiResponse, ConfigApiResponse } from '../../types';
5
5
 
6
+ export function parseLogEntries(logContent: string): LogEntry[] {
7
+ // Split by timestamp pattern - each timestamp starts a new log entry
8
+ const timestampPattern = /\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\] \[([^\]]+)\] /;
9
+
10
+ const entries: LogEntry[] = [];
11
+ const lines = logContent.split('\n');
12
+ let currentEntry: LogEntry | null = null;
13
+
14
+ for (const line of lines) {
15
+ if (!line.trim()) continue;
16
+
17
+ const match = line.match(timestampPattern);
18
+ if (match) {
19
+ // Save previous entry if exists
20
+ if (currentEntry) {
21
+ entries.push(currentEntry);
22
+ }
23
+
24
+ // Start new entry
25
+ const [fullMatch, timestamp, source] = match;
26
+ const message = line.substring(fullMatch.length);
27
+ const screenshot = message.match(/\[SCREENSHOT\] (.+)/)?.[1];
28
+
29
+ currentEntry = {
30
+ timestamp,
31
+ source,
32
+ message,
33
+ screenshot,
34
+ original: line
35
+ };
36
+ } else if (currentEntry) {
37
+ // Append to current entry's message
38
+ currentEntry.message += '\n' + line;
39
+ currentEntry.original += '\n' + line;
40
+ }
41
+ }
42
+
43
+ // Don't forget the last entry
44
+ if (currentEntry) {
45
+ entries.push(currentEntry);
46
+ }
47
+
48
+ return entries;
49
+ }
50
+
51
+ // Keep this for backwards compatibility, but it's not used anymore
6
52
  function parseLogLine(line: string): LogEntry | null {
7
- // More robust parsing - match timestamp and source, then take everything else as message
8
53
  const match = line.match(/^\[([^\]]+)\] \[([^\]]+)\] (.*)$/s);
9
54
  if (!match) return null;
10
55
 
@@ -81,11 +126,7 @@ export default function LogsClient({ version }: LogsClientProps) {
81
126
  return;
82
127
  }
83
128
 
84
- const entries = data.logs
85
- .split('\n')
86
- .filter((line: string) => line.trim())
87
- .map(parseLogLine)
88
- .filter((entry: LogEntry | null): entry is LogEntry => entry !== null);
129
+ const entries = parseLogEntries(data.logs);
89
130
 
90
131
  if (entries.length > lastLogCount) {
91
132
  setIsLoadingNew(true);
@@ -137,11 +178,7 @@ export default function LogsClient({ version }: LogsClientProps) {
137
178
  return;
138
179
  }
139
180
 
140
- const entries = data.logs
141
- .split('\n')
142
- .filter((line: string) => line.trim())
143
- .map(parseLogLine)
144
- .filter((entry: LogEntry | null): entry is LogEntry => entry !== null);
181
+ const entries = parseLogEntries(data.logs);
145
182
 
146
183
  setLogs(entries);
147
184
  setLastLogCount(entries.length);
@@ -3,9 +3,9 @@
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "pnpx next dev --turbopack",
7
- "build": "pnpx next build --turbopack",
8
- "start": "pnpx next start"
6
+ "dev": "next dev --turbopack",
7
+ "build": "next build --turbopack",
8
+ "start": "next start"
9
9
  },
10
10
  "dependencies": {
11
11
  "next": "^15.5.1-canary.13",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev3000",
3
- "version": "0.0.10",
3
+ "version": "0.0.13",
4
4
  "description": "AI-powered development tools with browser monitoring and MCP server integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,9 @@
38
38
  "tsx": "^4.20.3",
39
39
  "@types/node": "^22.8.7",
40
40
  "@types/cli-progress": "^3.11.6",
41
- "typescript": "^5.0.0"
41
+ "typescript": "^5.0.0",
42
+ "vitest": "^1.0.0",
43
+ "husky": "^9.0.0"
42
44
  },
43
45
  "engines": {
44
46
  "node": ">=18.0.0"
@@ -52,6 +54,7 @@
52
54
  "scripts": {
53
55
  "build": "tsc",
54
56
  "dev": "tsc --watch",
55
- "release": "pnpm run build && pnpm version patch && git push origin main --tags && pnpm publish --otp=$(op item get npm --otp)"
57
+ "test": "vitest run",
58
+ "release": "pnpm run build && pnpm test && pnpm version patch && git push origin main --tags && pnpm publish --otp=$(op item get npm --otp)"
56
59
  }
57
60
  }