repack-logs-mcp 1.0.0
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 +116 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -0
- package/dist/log-store.d.ts +43 -0
- package/dist/log-store.d.ts.map +1 -0
- package/dist/log-store.js +93 -0
- package/dist/log-store.js.map +1 -0
- package/dist/log-watcher.d.ts +45 -0
- package/dist/log-watcher.d.ts.map +1 -0
- package/dist/log-watcher.js +164 -0
- package/dist/log-watcher.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -0
- package/src/config.ts +37 -0
- package/src/index.ts +197 -0
- package/src/log-store.ts +113 -0
- package/src/log-watcher.ts +186 -0
- package/src/types.ts +53 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { watch, type FSWatcher } from 'chokidar';
|
|
2
|
+
import { readFile, stat } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import type { LogEntry } from './types.js';
|
|
5
|
+
import { LogStore } from './log-store.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Watches a Re.Pack log file and streams entries to a LogStore.
|
|
9
|
+
*/
|
|
10
|
+
export class LogWatcher {
|
|
11
|
+
private filePath: string;
|
|
12
|
+
private store: LogStore;
|
|
13
|
+
private watcher: FSWatcher | null = null;
|
|
14
|
+
private lastPosition: number = 0;
|
|
15
|
+
private watching: boolean = false;
|
|
16
|
+
|
|
17
|
+
constructor(filePath: string, store: LogStore) {
|
|
18
|
+
this.filePath = filePath;
|
|
19
|
+
this.store = store;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Start watching the log file.
|
|
24
|
+
*/
|
|
25
|
+
async start(): Promise<void> {
|
|
26
|
+
if (this.watching) return;
|
|
27
|
+
|
|
28
|
+
// Read existing content first
|
|
29
|
+
await this.readNewContent();
|
|
30
|
+
|
|
31
|
+
// Watch for changes
|
|
32
|
+
this.watcher = watch(this.filePath, {
|
|
33
|
+
persistent: true,
|
|
34
|
+
ignoreInitial: true,
|
|
35
|
+
awaitWriteFinish: {
|
|
36
|
+
stabilityThreshold: 100,
|
|
37
|
+
pollInterval: 50,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.watcher.on('change', async () => {
|
|
42
|
+
await this.readNewContent();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.watcher.on('add', async () => {
|
|
46
|
+
// File was created, reset position and read
|
|
47
|
+
this.lastPosition = 0;
|
|
48
|
+
await this.readNewContent();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.watcher.on('unlink', () => {
|
|
52
|
+
// File was deleted, reset position
|
|
53
|
+
this.lastPosition = 0;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.watching = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stop watching the log file.
|
|
61
|
+
*/
|
|
62
|
+
async stop(): Promise<void> {
|
|
63
|
+
if (this.watcher) {
|
|
64
|
+
await this.watcher.close();
|
|
65
|
+
this.watcher = null;
|
|
66
|
+
}
|
|
67
|
+
this.watching = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read new content from the log file since last position.
|
|
72
|
+
*/
|
|
73
|
+
private async readNewContent(): Promise<void> {
|
|
74
|
+
if (!existsSync(this.filePath)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const stats = await stat(this.filePath);
|
|
80
|
+
|
|
81
|
+
// If file was truncated, reset position
|
|
82
|
+
if (stats.size < this.lastPosition) {
|
|
83
|
+
this.lastPosition = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Read only new content
|
|
87
|
+
const content = await readFile(this.filePath, 'utf-8');
|
|
88
|
+
const newContent = content.slice(this.lastPosition);
|
|
89
|
+
|
|
90
|
+
if (newContent.length === 0) return;
|
|
91
|
+
|
|
92
|
+
// Update position
|
|
93
|
+
this.lastPosition = content.length;
|
|
94
|
+
|
|
95
|
+
// Parse JSON lines
|
|
96
|
+
const lines = newContent.split('\n').filter(line => line.trim());
|
|
97
|
+
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
try {
|
|
100
|
+
const entry = this.parseLine(line);
|
|
101
|
+
if (entry) {
|
|
102
|
+
this.store.add(entry);
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Skip malformed lines
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// File might not exist yet or be inaccessible
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parse a single log line into a LogEntry.
|
|
115
|
+
*/
|
|
116
|
+
private parseLine(line: string): LogEntry | null {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(line);
|
|
119
|
+
|
|
120
|
+
// Ensure required fields exist
|
|
121
|
+
if (!parsed.message && !parsed.msg) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
timestamp: parsed.timestamp ?? parsed.time ?? new Date().toISOString(),
|
|
127
|
+
type: this.normalizeType(parsed.type ?? parsed.level ?? 'info'),
|
|
128
|
+
message: parsed.message ?? parsed.msg ?? '',
|
|
129
|
+
issuer: parsed.issuer ?? parsed.source ?? parsed.name,
|
|
130
|
+
request: parsed.request,
|
|
131
|
+
file: parsed.file ?? parsed.filename,
|
|
132
|
+
loader: parsed.loader,
|
|
133
|
+
stack: parsed.stack,
|
|
134
|
+
duration: parsed.duration,
|
|
135
|
+
...parsed,
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Normalize various log level formats to our LogType.
|
|
144
|
+
*/
|
|
145
|
+
private normalizeType(type: string): LogEntry['type'] {
|
|
146
|
+
const normalized = type.toLowerCase();
|
|
147
|
+
|
|
148
|
+
if (normalized.includes('error') || normalized === 'err') {
|
|
149
|
+
return 'error';
|
|
150
|
+
}
|
|
151
|
+
if (normalized.includes('warn')) {
|
|
152
|
+
return 'warn';
|
|
153
|
+
}
|
|
154
|
+
if (normalized.includes('debug') || normalized === 'trace') {
|
|
155
|
+
return 'debug';
|
|
156
|
+
}
|
|
157
|
+
if (normalized.includes('success') || normalized === 'done') {
|
|
158
|
+
return 'success';
|
|
159
|
+
}
|
|
160
|
+
if (normalized.includes('progress')) {
|
|
161
|
+
return 'progress';
|
|
162
|
+
}
|
|
163
|
+
return 'info';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if the watcher is currently active.
|
|
168
|
+
*/
|
|
169
|
+
get isWatching(): boolean {
|
|
170
|
+
return this.watching;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if the log file exists.
|
|
175
|
+
*/
|
|
176
|
+
get fileExists(): boolean {
|
|
177
|
+
return existsSync(this.filePath);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get the path being watched.
|
|
182
|
+
*/
|
|
183
|
+
get path(): string {
|
|
184
|
+
return this.filePath;
|
|
185
|
+
}
|
|
186
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log entry structure from Re.Pack's JSON log output.
|
|
3
|
+
* Re.Pack outputs JSON lines with webpack compilation info.
|
|
4
|
+
*/
|
|
5
|
+
export interface LogEntry {
|
|
6
|
+
timestamp: string;
|
|
7
|
+
type: LogType;
|
|
8
|
+
issuer?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
/** Module request path */
|
|
11
|
+
request?: string;
|
|
12
|
+
/** File path being processed */
|
|
13
|
+
file?: string;
|
|
14
|
+
/** Webpack loader info */
|
|
15
|
+
loader?: string;
|
|
16
|
+
/** Error stack trace */
|
|
17
|
+
stack?: string;
|
|
18
|
+
/** Build duration in ms */
|
|
19
|
+
duration?: number;
|
|
20
|
+
/** Additional metadata */
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type LogType =
|
|
25
|
+
| 'info'
|
|
26
|
+
| 'warn'
|
|
27
|
+
| 'error'
|
|
28
|
+
| 'debug'
|
|
29
|
+
| 'success'
|
|
30
|
+
| 'progress';
|
|
31
|
+
|
|
32
|
+
export interface LogFilter {
|
|
33
|
+
/** Filter by log type(s) */
|
|
34
|
+
types?: LogType[];
|
|
35
|
+
/** Maximum number of logs to return */
|
|
36
|
+
limit?: number;
|
|
37
|
+
/** Only logs after this timestamp (ISO string) */
|
|
38
|
+
since?: string;
|
|
39
|
+
/** Filter by issuer/source */
|
|
40
|
+
issuer?: string;
|
|
41
|
+
/** Search in message content */
|
|
42
|
+
search?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WatcherStatus {
|
|
46
|
+
watching: boolean;
|
|
47
|
+
filePath: string;
|
|
48
|
+
fileExists: boolean;
|
|
49
|
+
logCount: number;
|
|
50
|
+
errorCount: number;
|
|
51
|
+
warningCount: number;
|
|
52
|
+
lastUpdate: string | null;
|
|
53
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|