cntx-ui 3.0.7 → 3.0.9
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/dist/bin/cntx-ui.js +70 -0
- package/dist/lib/agent-runtime.js +269 -0
- package/dist/lib/agent-tools.js +162 -0
- package/dist/lib/api-router.js +387 -0
- package/dist/lib/bundle-manager.js +236 -0
- package/dist/lib/configuration-manager.js +230 -0
- package/dist/lib/database-manager.js +277 -0
- package/dist/lib/file-system-manager.js +305 -0
- package/dist/lib/function-level-chunker.js +144 -0
- package/dist/lib/heuristics-manager.js +491 -0
- package/dist/lib/mcp-server.js +159 -0
- package/dist/lib/mcp-transport.js +10 -0
- package/dist/lib/semantic-splitter.js +335 -0
- package/dist/lib/simple-vector-store.js +98 -0
- package/dist/lib/treesitter-semantic-chunker.js +277 -0
- package/dist/lib/websocket-manager.js +268 -0
- package/dist/server.js +225 -0
- package/package.json +18 -8
- package/bin/cntx-ui-mcp.sh +0 -3
- package/bin/cntx-ui.js +0 -123
- package/lib/agent-runtime.js +0 -371
- package/lib/agent-tools.js +0 -370
- package/lib/api-router.js +0 -1026
- package/lib/bundle-manager.js +0 -326
- package/lib/configuration-manager.js +0 -760
- package/lib/database-manager.js +0 -397
- package/lib/file-system-manager.js +0 -489
- package/lib/function-level-chunker.js +0 -406
- package/lib/heuristics-manager.js +0 -529
- package/lib/mcp-server.js +0 -1380
- package/lib/mcp-transport.js +0 -97
- package/lib/semantic-splitter.js +0 -304
- package/lib/simple-vector-store.js +0 -108
- package/lib/treesitter-semantic-chunker.js +0 -1485
- package/lib/websocket-manager.js +0 -470
- package/server.js +0 -687
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File System Manager for cntx-ui
|
|
3
|
-
* Handles file operations, pattern matching, and directory traversal
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
|
|
7
|
-
import { join, relative, extname, basename, dirname } from 'path';
|
|
8
|
-
|
|
9
|
-
export default class FileSystemManager {
|
|
10
|
-
constructor(cwd = process.cwd(), options = {}) {
|
|
11
|
-
this.CWD = cwd;
|
|
12
|
-
this.verbose = options.verbose || false;
|
|
13
|
-
this.watchers = [];
|
|
14
|
-
this.ignorePatterns = [];
|
|
15
|
-
this.loadIgnorePatterns();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
loadIgnorePatterns() {
|
|
19
|
-
const ignorePath = join(this.CWD, '.cntxignore');
|
|
20
|
-
if (existsSync(ignorePath)) {
|
|
21
|
-
try {
|
|
22
|
-
const content = readFileSync(ignorePath, 'utf8');
|
|
23
|
-
const patterns = content.split('\n')
|
|
24
|
-
.map(line => line.trim())
|
|
25
|
-
.filter(line => line && !line.startsWith('#'));
|
|
26
|
-
this.ignorePatterns = patterns;
|
|
27
|
-
if (this.verbose) console.log(`📋 Loaded ${patterns.length} patterns from .cntxignore`);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.error('Failed to load .cntxignore:', e.message);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// === File Traversal ===
|
|
35
|
-
|
|
36
|
-
getAllFiles(dir = this.CWD, files = []) {
|
|
37
|
-
try {
|
|
38
|
-
const items = readdirSync(dir);
|
|
39
|
-
|
|
40
|
-
for (const item of items) {
|
|
41
|
-
const fullPath = join(dir, item);
|
|
42
|
-
|
|
43
|
-
// Skip if should be ignored
|
|
44
|
-
if (this.shouldIgnoreAnything(item, fullPath)) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const stat = statSync(fullPath);
|
|
50
|
-
|
|
51
|
-
if (stat.isDirectory()) {
|
|
52
|
-
this.getAllFiles(fullPath, files);
|
|
53
|
-
} else if (stat.isFile()) {
|
|
54
|
-
if (!this.shouldIgnoreFile(fullPath)) {
|
|
55
|
-
files.push(fullPath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
// Skip files we can't stat (permission issues, broken symlinks, etc.)
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (this.verbose) {
|
|
65
|
-
console.warn(`Cannot read directory ${dir}: ${error.message}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return files;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
getFileTree() {
|
|
73
|
-
const files = this.getAllFiles();
|
|
74
|
-
|
|
75
|
-
return files.map(file => {
|
|
76
|
-
const stats = this.getFileStats(file);
|
|
77
|
-
const relativePath = relative(this.CWD, file);
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
path: relativePath,
|
|
81
|
-
fullPath: file,
|
|
82
|
-
size: stats.size,
|
|
83
|
-
modified: stats.mtime.toISOString(),
|
|
84
|
-
type: this.getFileType(file)
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// === Pattern Matching ===
|
|
90
|
-
|
|
91
|
-
matchesPattern(path, pattern) {
|
|
92
|
-
// Convert glob pattern to regex
|
|
93
|
-
let regexPattern = pattern
|
|
94
|
-
.replace(/\./g, '\\.') // Escape dots
|
|
95
|
-
.replace(/\*\*/g, '___GLOBSTAR___') // Temporary replace **
|
|
96
|
-
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
97
|
-
.replace(/___GLOBSTAR___/g, '.*') // ** matches anything including /
|
|
98
|
-
.replace(/\?/g, '[^/]'); // ? matches single char except /
|
|
99
|
-
|
|
100
|
-
// Ensure pattern matches from start or after a directory separator
|
|
101
|
-
if (!regexPattern.startsWith('.*') && !regexPattern.startsWith('[^/]*')) {
|
|
102
|
-
regexPattern = '(^|/)' + regexPattern;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Ensure pattern matches to end or before a directory separator
|
|
106
|
-
if (!regexPattern.endsWith('.*') && !regexPattern.endsWith('[^/]*')) {
|
|
107
|
-
regexPattern = regexPattern + '($|/)';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const regex = new RegExp(regexPattern);
|
|
111
|
-
const relativePath = relative(this.CWD, path);
|
|
112
|
-
|
|
113
|
-
return regex.test(relativePath) || regex.test(path);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
shouldIgnoreFile(filePath) {
|
|
117
|
-
const relativePath = relative(this.CWD, filePath);
|
|
118
|
-
|
|
119
|
-
return this.ignorePatterns.some(pattern =>
|
|
120
|
-
this.matchesPattern(filePath, pattern) ||
|
|
121
|
-
this.matchesPattern(relativePath, pattern)
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
shouldIgnoreAnything(itemName, fullPath) {
|
|
126
|
-
// Hardcoded bad directories and files to always ignore
|
|
127
|
-
const badDirs = [
|
|
128
|
-
'node_modules', '.git', '.svn', '.hg', '.bzr', '_darcs',
|
|
129
|
-
'CVS', '.cvs', 'RCS', 'SCCS', '{arch}', '.arch-ids',
|
|
130
|
-
'.monotone', '_MTN', '.fslckout', '_FOSSIL_',
|
|
131
|
-
'.fos', 'BitKeeper', 'ChangeSet', '.teamcity',
|
|
132
|
-
'.idea', '.vscode', '.vs', '.gradle', '.settings',
|
|
133
|
-
'target', 'build', 'dist', 'out', 'bin', 'obj',
|
|
134
|
-
'.next', '.nuxt', '.vite', '.tmp', '.temp',
|
|
135
|
-
'__pycache__', '.pytest_cache', '.coverage',
|
|
136
|
-
'.nyc_output', 'coverage', 'lcov-report'
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
const badExtensions = [
|
|
140
|
-
'.log', '.tmp', '.temp', '.cache', '.pid', '.lock',
|
|
141
|
-
'.swp', '.swo', '.DS_Store', 'Thumbs.db', '.env',
|
|
142
|
-
'.min.js', '.min.css', '.map', '.pyc', '.pyo',
|
|
143
|
-
'.class', '.jar', '.exe', '.dll', '.so', '.dylib',
|
|
144
|
-
'.o', '.a', '.obj', '.lib', '.pdb'
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
const badFiles = [
|
|
148
|
-
'.gitignore', '.gitkeep', '.gitattributes',
|
|
149
|
-
'.eslintcache', '.prettierignore',
|
|
150
|
-
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
151
|
-
'npm-debug.log', 'yarn-debug.log', 'yarn-error.log'
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
// Check bad directories
|
|
155
|
-
if (badDirs.includes(itemName)) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Check bad extensions
|
|
160
|
-
const ext = extname(itemName);
|
|
161
|
-
if (badExtensions.includes(ext)) {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Check bad files
|
|
166
|
-
if (badFiles.includes(itemName)) {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Check if it's a hidden file/directory (starts with .)
|
|
171
|
-
if (itemName.startsWith('.') && itemName !== '.cntx') {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Check ignore patterns if loaded
|
|
176
|
-
if (this.ignorePatterns.length > 0) {
|
|
177
|
-
const relativePath = relative(this.CWD, fullPath);
|
|
178
|
-
if (this.ignorePatterns.some(pattern =>
|
|
179
|
-
this.matchesPattern(fullPath, pattern) ||
|
|
180
|
-
this.matchesPattern(relativePath, pattern)
|
|
181
|
-
)) {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// === File Metadata ===
|
|
190
|
-
|
|
191
|
-
getFileStats(filePath) {
|
|
192
|
-
try {
|
|
193
|
-
const stats = statSync(filePath);
|
|
194
|
-
return {
|
|
195
|
-
size: stats.size,
|
|
196
|
-
mtime: stats.mtime,
|
|
197
|
-
ctime: stats.ctime,
|
|
198
|
-
isDirectory: stats.isDirectory(),
|
|
199
|
-
isFile: stats.isFile()
|
|
200
|
-
};
|
|
201
|
-
} catch (error) {
|
|
202
|
-
return {
|
|
203
|
-
size: 0,
|
|
204
|
-
mtime: new Date(0),
|
|
205
|
-
ctime: new Date(0),
|
|
206
|
-
isDirectory: false,
|
|
207
|
-
isFile: false
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
getFileType(filePath) {
|
|
213
|
-
const ext = extname(filePath).toLowerCase();
|
|
214
|
-
const fileName = basename(filePath).toLowerCase();
|
|
215
|
-
|
|
216
|
-
// Programming languages
|
|
217
|
-
if (ext.match(/\.(js|jsx|mjs|cjs)$/)) return 'javascript';
|
|
218
|
-
if (ext.match(/\.(ts|tsx)$/)) return 'typescript';
|
|
219
|
-
if (ext.match(/\.(py|pyw)$/)) return 'python';
|
|
220
|
-
if (ext.match(/\.(java|class)$/)) return 'java';
|
|
221
|
-
if (ext.match(/\.(c|h)$/)) return 'c';
|
|
222
|
-
if (ext.match(/\.(cpp|cxx|cc|hpp|hxx)$/)) return 'cpp';
|
|
223
|
-
if (ext.match(/\.(cs)$/)) return 'csharp';
|
|
224
|
-
if (ext.match(/\.(go)$/)) return 'go';
|
|
225
|
-
if (ext.match(/\.(rs)$/)) return 'rust';
|
|
226
|
-
if (ext.match(/\.(php)$/)) return 'php';
|
|
227
|
-
if (ext.match(/\.(rb)$/)) return 'ruby';
|
|
228
|
-
|
|
229
|
-
// Web technologies
|
|
230
|
-
if (ext.match(/\.(html|htm)$/)) return 'html';
|
|
231
|
-
if (ext.match(/\.(css|scss|sass|less|styl)$/)) return 'stylesheet';
|
|
232
|
-
if (ext.match(/\.(vue)$/)) return 'vue';
|
|
233
|
-
|
|
234
|
-
// Data formats
|
|
235
|
-
if (ext.match(/\.(json)$/)) return 'json';
|
|
236
|
-
if (ext.match(/\.(xml)$/)) return 'xml';
|
|
237
|
-
if (ext.match(/\.(yaml|yml)$/)) return 'yaml';
|
|
238
|
-
if (ext.match(/\.(toml)$/)) return 'toml';
|
|
239
|
-
if (ext.match(/\.(ini)$/)) return 'ini';
|
|
240
|
-
if (ext.match(/\.(csv)$/)) return 'csv';
|
|
241
|
-
|
|
242
|
-
// Documentation
|
|
243
|
-
if (ext.match(/\.(md|markdown)$/)) return 'markdown';
|
|
244
|
-
if (ext.match(/\.(txt)$/)) return 'text';
|
|
245
|
-
if (ext.match(/\.(rst)$/)) return 'restructuredtext';
|
|
246
|
-
|
|
247
|
-
// Media
|
|
248
|
-
if (ext.match(/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/)) return 'image';
|
|
249
|
-
if (ext.match(/\.(mp4|avi|mov|wmv|flv|webm)$/)) return 'video';
|
|
250
|
-
if (ext.match(/\.(mp3|wav|flac|aac|ogg)$/)) return 'audio';
|
|
251
|
-
|
|
252
|
-
// Archives
|
|
253
|
-
if (ext.match(/\.(zip|tar|gz|bz2|xz|7z|rar)$/)) return 'archive';
|
|
254
|
-
|
|
255
|
-
// Configuration
|
|
256
|
-
if (fileName.includes('config') || fileName.includes('setup')) return 'configuration';
|
|
257
|
-
|
|
258
|
-
return 'unknown';
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
getFileRole(file) {
|
|
262
|
-
const fileName = basename(file).toLowerCase();
|
|
263
|
-
const filePath = file.toLowerCase();
|
|
264
|
-
const ext = extname(file).toLowerCase();
|
|
265
|
-
|
|
266
|
-
// Entry points
|
|
267
|
-
if (fileName.match(/^(main|index|app)\.(js|jsx|ts|tsx)$/)) {
|
|
268
|
-
return 'entry_point';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Configuration
|
|
272
|
-
if (fileName.includes('config') || fileName.includes('setup') ||
|
|
273
|
-
ext.match(/\.(json|yaml|yml|toml|ini)$/)) {
|
|
274
|
-
return 'configuration';
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Documentation
|
|
278
|
-
if (fileName.includes('readme') || ext === '.md' || ext === '.txt') {
|
|
279
|
-
return 'documentation';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Tests
|
|
283
|
-
if (fileName.includes('.test.') || fileName.includes('.spec.') ||
|
|
284
|
-
filePath.includes('/test/') || filePath.includes('/__tests__/')) {
|
|
285
|
-
return 'test';
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Components
|
|
289
|
-
if (ext.match(/\.(jsx|tsx|vue)$/) || filePath.includes('/components/')) {
|
|
290
|
-
return 'component';
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Utilities
|
|
294
|
-
if (filePath.includes('/utils/') || filePath.includes('/helpers/') ||
|
|
295
|
-
filePath.includes('/lib/')) {
|
|
296
|
-
return 'utility';
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Styles
|
|
300
|
-
if (ext.match(/\.(css|scss|sass|less|styl)$/)) {
|
|
301
|
-
return 'style';
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return 'implementation';
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// === File Categorization ===
|
|
308
|
-
|
|
309
|
-
categorizeFiles(files) {
|
|
310
|
-
const categories = {
|
|
311
|
-
'entry_points': [],
|
|
312
|
-
'components': [],
|
|
313
|
-
'hooks': [],
|
|
314
|
-
'utilities': [],
|
|
315
|
-
'types': [],
|
|
316
|
-
'styles': [],
|
|
317
|
-
'tests': [],
|
|
318
|
-
'configuration': [],
|
|
319
|
-
'documentation': [],
|
|
320
|
-
'other': []
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
files.forEach(file => {
|
|
324
|
-
const ext = extname(file).toLowerCase();
|
|
325
|
-
const fileName = basename(file).toLowerCase();
|
|
326
|
-
const filePath = file.toLowerCase();
|
|
327
|
-
|
|
328
|
-
// Entry points
|
|
329
|
-
if (fileName.match(/^(main|index|app)\.(js|jsx|ts|tsx)$/)) {
|
|
330
|
-
categories.entry_points.push(file);
|
|
331
|
-
}
|
|
332
|
-
// Components
|
|
333
|
-
else if (ext.match(/\.(jsx|tsx|vue)$/) || filePath.includes('/components/')) {
|
|
334
|
-
categories.components.push(file);
|
|
335
|
-
}
|
|
336
|
-
// Hooks
|
|
337
|
-
else if (filePath.includes('/hooks/') || fileName.startsWith('use') && ext.match(/\.(js|ts)$/)) {
|
|
338
|
-
categories.hooks.push(file);
|
|
339
|
-
}
|
|
340
|
-
// Utilities
|
|
341
|
-
else if (filePath.includes('/utils/') || filePath.includes('/helpers/') || filePath.includes('/lib/')) {
|
|
342
|
-
categories.utilities.push(file);
|
|
343
|
-
}
|
|
344
|
-
// Types
|
|
345
|
-
else if (fileName.includes('.d.ts') || filePath.includes('/types/') || fileName.includes('types')) {
|
|
346
|
-
categories.types.push(file);
|
|
347
|
-
}
|
|
348
|
-
// Styles
|
|
349
|
-
else if (ext.match(/\.(css|scss|sass|less|styl)$/)) {
|
|
350
|
-
categories.styles.push(file);
|
|
351
|
-
}
|
|
352
|
-
// Tests
|
|
353
|
-
else if (fileName.includes('.test.') || fileName.includes('.spec.') || filePath.includes('/test/') || filePath.includes('/__tests__/')) {
|
|
354
|
-
categories.tests.push(file);
|
|
355
|
-
}
|
|
356
|
-
// Configuration
|
|
357
|
-
else if (ext.match(/\.(json|yaml|yml|toml|ini)$/) || fileName.includes('config')) {
|
|
358
|
-
categories.configuration.push(file);
|
|
359
|
-
}
|
|
360
|
-
// Documentation
|
|
361
|
-
else if (ext.match(/\.(md|txt|rst)$/)) {
|
|
362
|
-
categories.documentation.push(file);
|
|
363
|
-
}
|
|
364
|
-
// Other
|
|
365
|
-
else {
|
|
366
|
-
categories.other.push(file);
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
return categories;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
identifyEntryPoints(files) {
|
|
374
|
-
const entryPoints = [];
|
|
375
|
-
const entryPatterns = [
|
|
376
|
-
/^(main|index|app)\.(js|jsx|ts|tsx)$/i,
|
|
377
|
-
/^server\.(js|ts)$/i,
|
|
378
|
-
/^app\.(js|jsx|ts|tsx)$/i
|
|
379
|
-
];
|
|
380
|
-
|
|
381
|
-
files.forEach(file => {
|
|
382
|
-
const fileName = basename(file);
|
|
383
|
-
if (entryPatterns.some(pattern => pattern.test(fileName))) {
|
|
384
|
-
entryPoints.push(file);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
return entryPoints;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// === File Watching ===
|
|
392
|
-
|
|
393
|
-
startWatching(onFileChange) {
|
|
394
|
-
try {
|
|
395
|
-
// Watch the current working directory recursively
|
|
396
|
-
const watcher = watch(this.CWD, { recursive: true }, (eventType, filename) => {
|
|
397
|
-
if (filename && !this.shouldIgnoreAnything(basename(filename), join(this.CWD, filename))) {
|
|
398
|
-
onFileChange?.(eventType, filename);
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
this.watchers.push(watcher);
|
|
403
|
-
if (this.verbose) {
|
|
404
|
-
console.log('📁 File watcher started');
|
|
405
|
-
}
|
|
406
|
-
} catch (error) {
|
|
407
|
-
if (this.verbose) {
|
|
408
|
-
console.error('Failed to start file watcher:', error.message);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
stopWatching() {
|
|
414
|
-
this.watchers.forEach(watcher => {
|
|
415
|
-
try {
|
|
416
|
-
watcher.close();
|
|
417
|
-
} catch (error) {
|
|
418
|
-
if (this.verbose) {
|
|
419
|
-
console.error('Failed to close watcher:', error.message);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
this.watchers = [];
|
|
424
|
-
if (this.verbose) {
|
|
425
|
-
console.log('📁 File watchers stopped');
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// === Content Type Detection ===
|
|
430
|
-
|
|
431
|
-
getContentType(filePath) {
|
|
432
|
-
const ext = extname(filePath).toLowerCase();
|
|
433
|
-
const contentTypes = {
|
|
434
|
-
'.html': 'text/html',
|
|
435
|
-
'.js': 'application/javascript',
|
|
436
|
-
'.css': 'text/css',
|
|
437
|
-
'.json': 'application/json',
|
|
438
|
-
'.png': 'image/png',
|
|
439
|
-
'.jpg': 'image/jpeg',
|
|
440
|
-
'.jpeg': 'image/jpeg',
|
|
441
|
-
'.gif': 'image/gif',
|
|
442
|
-
'.svg': 'image/svg+xml',
|
|
443
|
-
'.ico': 'image/x-icon',
|
|
444
|
-
'.txt': 'text/plain',
|
|
445
|
-
'.md': 'text/markdown',
|
|
446
|
-
'.xml': 'application/xml',
|
|
447
|
-
'.pdf': 'application/pdf',
|
|
448
|
-
'.zip': 'application/zip'
|
|
449
|
-
};
|
|
450
|
-
return contentTypes[ext] || 'text/plain';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// === Utilities ===
|
|
454
|
-
|
|
455
|
-
setIgnorePatterns(patterns) {
|
|
456
|
-
this.ignorePatterns = patterns;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
relativePath(filePath) {
|
|
460
|
-
return relative(this.CWD, filePath);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
absolutePath(relativePath) {
|
|
464
|
-
return join(this.CWD, relativePath);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
isValidPath(filePath) {
|
|
468
|
-
try {
|
|
469
|
-
return existsSync(filePath);
|
|
470
|
-
} catch (error) {
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
getDirectoryContents(dirPath) {
|
|
476
|
-
try {
|
|
477
|
-
const items = readdirSync(dirPath);
|
|
478
|
-
return items.filter(item => !this.shouldIgnoreAnything(item, join(dirPath, item)));
|
|
479
|
-
} catch (error) {
|
|
480
|
-
return [];
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// === Cleanup ===
|
|
485
|
-
|
|
486
|
-
destroy() {
|
|
487
|
-
this.stopWatching();
|
|
488
|
-
}
|
|
489
|
-
}
|