@j0hanz/filesystem-context-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 +369 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +88 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/file-operations.test.d.ts +2 -0
- package/dist/__tests__/file-operations.test.d.ts.map +1 -0
- package/dist/__tests__/file-operations.test.js +230 -0
- package/dist/__tests__/file-operations.test.js.map +1 -0
- package/dist/__tests__/lib/errors.test.d.ts +2 -0
- package/dist/__tests__/lib/errors.test.d.ts.map +1 -0
- package/dist/__tests__/lib/errors.test.js +156 -0
- package/dist/__tests__/lib/errors.test.js.map +1 -0
- package/dist/__tests__/lib/file-operations.test.d.ts +2 -0
- package/dist/__tests__/lib/file-operations.test.d.ts.map +1 -0
- package/dist/__tests__/lib/file-operations.test.js +417 -0
- package/dist/__tests__/lib/file-operations.test.js.map +1 -0
- package/dist/__tests__/lib/fs-helpers.test.d.ts +2 -0
- package/dist/__tests__/lib/fs-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/lib/fs-helpers.test.js +183 -0
- package/dist/__tests__/lib/fs-helpers.test.js.map +1 -0
- package/dist/__tests__/lib/path-validation.test.d.ts +2 -0
- package/dist/__tests__/lib/path-validation.test.d.ts.map +1 -0
- package/dist/__tests__/lib/path-validation.test.js +103 -0
- package/dist/__tests__/lib/path-validation.test.js.map +1 -0
- package/dist/__tests__/path-validation.test.d.ts +2 -0
- package/dist/__tests__/path-validation.test.d.ts.map +1 -0
- package/dist/__tests__/path-validation.test.js +92 -0
- package/dist/__tests__/path-validation.test.js.map +1 -0
- package/dist/config/types.d.ts +222 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +23 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/constants.d.ts +16 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +335 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +205 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/file-operations.d.ts +69 -0
- package/dist/lib/file-operations.d.ts.map +1 -0
- package/dist/lib/file-operations.js +1003 -0
- package/dist/lib/file-operations.js.map +1 -0
- package/dist/lib/formatters.d.ts +30 -0
- package/dist/lib/formatters.d.ts.map +1 -0
- package/dist/lib/formatters.js +204 -0
- package/dist/lib/formatters.js.map +1 -0
- package/dist/lib/fs-helpers.d.ts +29 -0
- package/dist/lib/fs-helpers.d.ts.map +1 -0
- package/dist/lib/fs-helpers.js +295 -0
- package/dist/lib/fs-helpers.js.map +1 -0
- package/dist/lib/path-utils.d.ts +12 -0
- package/dist/lib/path-utils.d.ts.map +1 -0
- package/dist/lib/path-utils.js +34 -0
- package/dist/lib/path-utils.js.map +1 -0
- package/dist/lib/path-validation.d.ts +31 -0
- package/dist/lib/path-validation.d.ts.map +1 -0
- package/dist/lib/path-validation.js +181 -0
- package/dist/lib/path-validation.js.map +1 -0
- package/dist/lib/roots-utils.d.ts +7 -0
- package/dist/lib/roots-utils.d.ts.map +1 -0
- package/dist/lib/roots-utils.js +39 -0
- package/dist/lib/roots-utils.js.map +1 -0
- package/dist/lib/types.d.ts +6 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/schemas/common.d.ts +41 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +21 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/inputs.d.ts +72 -0
- package/dist/schemas/inputs.d.ts.map +1 -0
- package/dist/schemas/inputs.js +326 -0
- package/dist/schemas/inputs.js.map +1 -0
- package/dist/schemas/outputs.d.ts +476 -0
- package/dist/schemas/outputs.d.ts.map +1 -0
- package/dist/schemas/outputs.js +181 -0
- package/dist/schemas/outputs.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +83 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/analyze-directory.d.ts +3 -0
- package/dist/tools/analyze-directory.d.ts.map +1 -0
- package/dist/tools/analyze-directory.js +57 -0
- package/dist/tools/analyze-directory.js.map +1 -0
- package/dist/tools/directory-tree.d.ts +3 -0
- package/dist/tools/directory-tree.d.ts.map +1 -0
- package/dist/tools/directory-tree.js +47 -0
- package/dist/tools/directory-tree.js.map +1 -0
- package/dist/tools/get-file-info.d.ts +3 -0
- package/dist/tools/get-file-info.d.ts.map +1 -0
- package/dist/tools/get-file-info.js +44 -0
- package/dist/tools/get-file-info.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-allowed-dirs.d.ts +3 -0
- package/dist/tools/list-allowed-dirs.d.ts.map +1 -0
- package/dist/tools/list-allowed-dirs.js +23 -0
- package/dist/tools/list-allowed-dirs.js.map +1 -0
- package/dist/tools/list-directory.d.ts +3 -0
- package/dist/tools/list-directory.d.ts.map +1 -0
- package/dist/tools/list-directory.js +60 -0
- package/dist/tools/list-directory.js.map +1 -0
- package/dist/tools/read-file.d.ts +3 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +55 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/read-media-file.d.ts +3 -0
- package/dist/tools/read-media-file.d.ts.map +1 -0
- package/dist/tools/read-media-file.js +48 -0
- package/dist/tools/read-media-file.js.map +1 -0
- package/dist/tools/read-multiple-files.d.ts +3 -0
- package/dist/tools/read-multiple-files.d.ts.map +1 -0
- package/dist/tools/read-multiple-files.js +53 -0
- package/dist/tools/read-multiple-files.js.map +1 -0
- package/dist/tools/search-content.d.ts +3 -0
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +67 -0
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +3 -0
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +51 -0
- package/dist/tools/search-files.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/response-helpers.d.ts +22 -0
- package/dist/utils/response-helpers.d.ts.map +1 -0
- package/dist/utils/response-helpers.js +24 -0
- package/dist/utils/response-helpers.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
5
|
+
import { analyzeDirectory, getDirectoryTree, getFileInfo, listDirectory, readMediaFile, readMultipleFiles, searchContent, searchFiles, } from '../../lib/file-operations.js';
|
|
6
|
+
import { normalizePath } from '../../lib/path-utils.js';
|
|
7
|
+
import { setAllowedDirectories } from '../../lib/path-validation.js';
|
|
8
|
+
describe('File Operations', () => {
|
|
9
|
+
let testDir;
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
// Create a temporary test directory with various files
|
|
12
|
+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-fileops-test-'));
|
|
13
|
+
// Create test structure
|
|
14
|
+
await fs.mkdir(path.join(testDir, 'src'));
|
|
15
|
+
await fs.mkdir(path.join(testDir, 'docs'));
|
|
16
|
+
await fs.mkdir(path.join(testDir, '.hidden'));
|
|
17
|
+
// Create test files
|
|
18
|
+
await fs.writeFile(path.join(testDir, 'README.md'), '# Test Project\nThis is a test.\n');
|
|
19
|
+
await fs.writeFile(path.join(testDir, 'src', 'index.ts'), 'export const hello = "world";\n');
|
|
20
|
+
await fs.writeFile(path.join(testDir, 'src', 'utils.ts'), 'export function add(a: number, b: number) { return a + b; }\n');
|
|
21
|
+
await fs.writeFile(path.join(testDir, 'docs', 'guide.md'), '# Guide\nSome documentation.\n');
|
|
22
|
+
await fs.writeFile(path.join(testDir, '.hidden', 'secret.txt'), 'hidden content');
|
|
23
|
+
// Create a multi-line file for head/tail tests
|
|
24
|
+
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`).join('\n');
|
|
25
|
+
await fs.writeFile(path.join(testDir, 'multiline.txt'), lines);
|
|
26
|
+
// Create a simple PNG-like binary file (just bytes, not a real image)
|
|
27
|
+
const binaryData = Buffer.from([
|
|
28
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
|
|
29
|
+
]);
|
|
30
|
+
await fs.writeFile(path.join(testDir, 'image.png'), binaryData);
|
|
31
|
+
// Set allowed directories
|
|
32
|
+
setAllowedDirectories([normalizePath(testDir)]);
|
|
33
|
+
});
|
|
34
|
+
afterAll(async () => {
|
|
35
|
+
try {
|
|
36
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore cleanup errors
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
describe('listDirectory', () => {
|
|
43
|
+
it('should list directory contents', async () => {
|
|
44
|
+
const result = await listDirectory(testDir);
|
|
45
|
+
expect(result.entries.length).toBeGreaterThan(0);
|
|
46
|
+
expect(result.summary.totalEntries).toBeGreaterThan(0);
|
|
47
|
+
});
|
|
48
|
+
it('should list recursively when specified', async () => {
|
|
49
|
+
const result = await listDirectory(testDir, { recursive: true });
|
|
50
|
+
expect(result.entries.some((e) => e.name === 'index.ts')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('should include hidden files when specified', async () => {
|
|
53
|
+
const result = await listDirectory(testDir, { includeHidden: true });
|
|
54
|
+
expect(result.entries.some((e) => e.name === '.hidden')).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('should exclude hidden files by default', async () => {
|
|
57
|
+
const result = await listDirectory(testDir, { includeHidden: false });
|
|
58
|
+
expect(result.entries.some((e) => e.name === '.hidden')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it('should respect maxEntries limit', async () => {
|
|
61
|
+
const result = await listDirectory(testDir, { maxEntries: 2 });
|
|
62
|
+
expect(result.entries.length).toBeLessThanOrEqual(2);
|
|
63
|
+
expect(result.summary.truncated).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('searchFiles', () => {
|
|
67
|
+
it('should find files by glob pattern', async () => {
|
|
68
|
+
const result = await searchFiles(testDir, '**/*.ts');
|
|
69
|
+
expect(result.results.length).toBe(2);
|
|
70
|
+
expect(result.results.some((r) => r.path.includes('index.ts'))).toBe(true);
|
|
71
|
+
// Optional: validate modified is present on file results
|
|
72
|
+
const first = result.results.find((r) => r.type === 'file');
|
|
73
|
+
if (first) {
|
|
74
|
+
expect(first.modified).toBeInstanceOf(Date);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it('should find markdown files', async () => {
|
|
78
|
+
const result = await searchFiles(testDir, '**/*.md');
|
|
79
|
+
expect(result.results.length).toBe(2);
|
|
80
|
+
});
|
|
81
|
+
it('should return empty results for non-matching patterns', async () => {
|
|
82
|
+
const result = await searchFiles(testDir, '**/*.xyz');
|
|
83
|
+
expect(result.results.length).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
it('should respect maxResults', async () => {
|
|
86
|
+
const result = await searchFiles(testDir, '**/*', [], { maxResults: 1 });
|
|
87
|
+
expect(result.results.length).toBeLessThanOrEqual(1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('readMultipleFiles', () => {
|
|
91
|
+
it('should read multiple files in parallel', async () => {
|
|
92
|
+
const paths = [
|
|
93
|
+
path.join(testDir, 'README.md'),
|
|
94
|
+
path.join(testDir, 'src', 'index.ts'),
|
|
95
|
+
];
|
|
96
|
+
const results = await readMultipleFiles(paths);
|
|
97
|
+
expect(results.length).toBe(2);
|
|
98
|
+
expect(results.every((r) => r.content !== undefined)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('should handle individual file errors gracefully', async () => {
|
|
101
|
+
const paths = [
|
|
102
|
+
path.join(testDir, 'README.md'),
|
|
103
|
+
path.join(testDir, 'non-existent.txt'),
|
|
104
|
+
];
|
|
105
|
+
const results = await readMultipleFiles(paths);
|
|
106
|
+
expect(results.length).toBe(2);
|
|
107
|
+
expect(results[0]?.content).toBeDefined();
|
|
108
|
+
expect(results[1]?.error).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('getFileInfo', () => {
|
|
112
|
+
it('should return file metadata', async () => {
|
|
113
|
+
const info = await getFileInfo(path.join(testDir, 'README.md'));
|
|
114
|
+
expect(info.name).toBe('README.md');
|
|
115
|
+
expect(info.type).toBe('file');
|
|
116
|
+
expect(info.size).toBeGreaterThan(0);
|
|
117
|
+
expect(info.created).toBeInstanceOf(Date);
|
|
118
|
+
});
|
|
119
|
+
it('should return directory metadata', async () => {
|
|
120
|
+
const info = await getFileInfo(testDir);
|
|
121
|
+
expect(info.type).toBe('directory');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('searchContent', () => {
|
|
125
|
+
it('should find content in files', async () => {
|
|
126
|
+
const result = await searchContent(testDir, 'hello');
|
|
127
|
+
expect(result.matches.length).toBeGreaterThan(0);
|
|
128
|
+
expect(result.matches[0]?.file).toContain('index.ts');
|
|
129
|
+
});
|
|
130
|
+
it('should search case-insensitively by default', async () => {
|
|
131
|
+
const result = await searchContent(testDir, 'HELLO');
|
|
132
|
+
expect(result.matches.length).toBeGreaterThan(0);
|
|
133
|
+
});
|
|
134
|
+
it('should respect case sensitivity when specified', async () => {
|
|
135
|
+
const result = await searchContent(testDir, 'HELLO', {
|
|
136
|
+
caseSensitive: true,
|
|
137
|
+
});
|
|
138
|
+
expect(result.matches.length).toBe(0);
|
|
139
|
+
});
|
|
140
|
+
it('should skip binary files by default', async () => {
|
|
141
|
+
const result = await searchContent(testDir, 'PNG');
|
|
142
|
+
// Should not find matches in binary file
|
|
143
|
+
expect(result.summary.skippedBinary).toBeGreaterThanOrEqual(0);
|
|
144
|
+
});
|
|
145
|
+
it('should respect file pattern filter', async () => {
|
|
146
|
+
const result = await searchContent(testDir, 'export', {
|
|
147
|
+
filePattern: '**/*.ts',
|
|
148
|
+
});
|
|
149
|
+
expect(result.matches.length).toBeGreaterThan(0);
|
|
150
|
+
expect(result.matches.every((m) => m.file.endsWith('.ts'))).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
it('should reject unsafe regex patterns (ReDoS protection)', async () => {
|
|
153
|
+
// Classic ReDoS patterns detected by safe-regex2:
|
|
154
|
+
// - Nested quantifiers like (a+)+
|
|
155
|
+
// - Character class with nested quantifiers like ([a-zA-Z]+)*
|
|
156
|
+
// - High repetition counts like (.*a){25}
|
|
157
|
+
const unsafePatterns = [
|
|
158
|
+
'(a+)+', // Nested quantifiers
|
|
159
|
+
'([a-zA-Z]+)*', // Nested quantifiers with character class
|
|
160
|
+
'(.*a){25}', // High repetition count
|
|
161
|
+
];
|
|
162
|
+
for (const pattern of unsafePatterns) {
|
|
163
|
+
await expect(searchContent(testDir, pattern)).rejects.toThrow(/ReDoS|unsafe/i);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
it('should accept safe regex patterns', async () => {
|
|
167
|
+
// Safe patterns should work normally
|
|
168
|
+
const safePatterns = ['hello', 'world\\d+', '[a-z]+', 'function\\s+\\w+'];
|
|
169
|
+
for (const pattern of safePatterns) {
|
|
170
|
+
// Should not throw
|
|
171
|
+
const result = await searchContent(testDir, pattern, {
|
|
172
|
+
filePattern: '**/*.ts',
|
|
173
|
+
});
|
|
174
|
+
expect(result).toBeDefined();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe('analyzeDirectory', () => {
|
|
179
|
+
it('should analyze directory structure', async () => {
|
|
180
|
+
const result = await analyzeDirectory(testDir);
|
|
181
|
+
expect(result.analysis.totalFiles).toBeGreaterThan(0);
|
|
182
|
+
expect(result.analysis.totalDirectories).toBeGreaterThan(0);
|
|
183
|
+
expect(result.analysis.totalSize).toBeGreaterThan(0);
|
|
184
|
+
});
|
|
185
|
+
it('should list file types', async () => {
|
|
186
|
+
const result = await analyzeDirectory(testDir);
|
|
187
|
+
expect(Object.keys(result.analysis.fileTypes).length).toBeGreaterThan(0);
|
|
188
|
+
});
|
|
189
|
+
it('should track largest files', async () => {
|
|
190
|
+
const result = await analyzeDirectory(testDir, { topN: 5 });
|
|
191
|
+
expect(result.analysis.largestFiles.length).toBeLessThanOrEqual(5);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('getDirectoryTree', () => {
|
|
195
|
+
it('should return tree structure', async () => {
|
|
196
|
+
const result = await getDirectoryTree(testDir);
|
|
197
|
+
expect(result.tree.type).toBe('directory');
|
|
198
|
+
expect(result.tree.children).toBeDefined();
|
|
199
|
+
expect(result.tree.children?.length).toBeGreaterThan(0);
|
|
200
|
+
});
|
|
201
|
+
it('should respect maxDepth', async () => {
|
|
202
|
+
const result = await getDirectoryTree(testDir, { maxDepth: 1 });
|
|
203
|
+
expect(result.summary.maxDepthReached).toBeLessThanOrEqual(1);
|
|
204
|
+
});
|
|
205
|
+
it('should exclude patterns', async () => {
|
|
206
|
+
const result = await getDirectoryTree(testDir, {
|
|
207
|
+
excludePatterns: ['docs'],
|
|
208
|
+
});
|
|
209
|
+
const hasDocsDir = result.tree.children?.some((c) => c.name === 'docs');
|
|
210
|
+
expect(hasDocsDir).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
it('should include sizes when specified', async () => {
|
|
213
|
+
const result = await getDirectoryTree(testDir, { includeSize: true });
|
|
214
|
+
const fileEntry = result.tree.children?.find((c) => c.type === 'file');
|
|
215
|
+
expect(fileEntry?.size).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
it('should not traverse symlinks that escape allowed directories', async () => {
|
|
218
|
+
const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-outside-'));
|
|
219
|
+
const outsideFile = path.join(outsideDir, 'outside.txt');
|
|
220
|
+
await fs.writeFile(outsideFile, 'outside');
|
|
221
|
+
const linkPath = path.join(testDir, 'escape');
|
|
222
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
223
|
+
let createdLink = false;
|
|
224
|
+
try {
|
|
225
|
+
// Symlink creation can fail on some Windows setups without admin/dev mode.
|
|
226
|
+
// If creation fails, we treat this as a no-op environment limitation.
|
|
227
|
+
await fs.symlink(outsideDir, linkPath, linkType);
|
|
228
|
+
createdLink = true;
|
|
229
|
+
const result = await getDirectoryTree(testDir, { maxDepth: 3 });
|
|
230
|
+
// The symlink/junction itself should not appear in the tree (we skip symlinks).
|
|
231
|
+
const childNames = (result.tree.children ?? []).map((c) => c.name);
|
|
232
|
+
expect(childNames.includes('escape')).toBe(false);
|
|
233
|
+
// And we must not traverse into the outside directory.
|
|
234
|
+
const containsName = (entry, name) => {
|
|
235
|
+
if (entry.name === name)
|
|
236
|
+
return true;
|
|
237
|
+
const { children } = entry;
|
|
238
|
+
if (!children || !Array.isArray(children))
|
|
239
|
+
return false;
|
|
240
|
+
for (const child of children) {
|
|
241
|
+
if (child &&
|
|
242
|
+
typeof child === 'object' &&
|
|
243
|
+
containsName(child, name)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
};
|
|
249
|
+
expect(containsName(result.tree, 'outside.txt')).toBe(false);
|
|
250
|
+
expect(result.summary.skippedSymlinks).toBeGreaterThanOrEqual(1);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Skip assertion when symlink creation isn't permitted in the environment.
|
|
254
|
+
expect(createdLink).toBe(false);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
try {
|
|
258
|
+
await fs.rm(linkPath, { recursive: true, force: true });
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// ignore cleanup errors
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await fs.rm(outsideDir, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// ignore cleanup errors
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe('readMediaFile', () => {
|
|
273
|
+
it('should read binary file as base64', async () => {
|
|
274
|
+
const result = await readMediaFile(path.join(testDir, 'image.png'));
|
|
275
|
+
expect(result.mimeType).toBe('image/png');
|
|
276
|
+
expect(result.data).toBeTruthy();
|
|
277
|
+
expect(result.size).toBeGreaterThan(0);
|
|
278
|
+
});
|
|
279
|
+
it('should return correct MIME type for markdown files', async () => {
|
|
280
|
+
const result = await readMediaFile(path.join(testDir, 'README.md'));
|
|
281
|
+
expect(result.mimeType).toBe('text/markdown');
|
|
282
|
+
expect(result.data).toBeTruthy();
|
|
283
|
+
expect(result.size).toBeGreaterThan(0);
|
|
284
|
+
});
|
|
285
|
+
it('should reject files too large', async () => {
|
|
286
|
+
await expect(readMediaFile(path.join(testDir, 'image.png'), { maxSize: 1 })).rejects.toThrow('too large');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
describe('Edge Cases and Input Validation', () => {
|
|
290
|
+
describe('searchContent edge cases', () => {
|
|
291
|
+
it('should handle empty search results gracefully', async () => {
|
|
292
|
+
const result = await searchContent(testDir, 'xyznonexistent123', {
|
|
293
|
+
filePattern: '**/*.ts',
|
|
294
|
+
});
|
|
295
|
+
expect(result.matches.length).toBe(0);
|
|
296
|
+
expect(result.summary.filesMatched).toBe(0);
|
|
297
|
+
});
|
|
298
|
+
it('should handle special regex characters safely', async () => {
|
|
299
|
+
// These are valid regex patterns with special chars
|
|
300
|
+
const result = await searchContent(testDir, 'hello\\.world', {
|
|
301
|
+
filePattern: '**/*.ts',
|
|
302
|
+
});
|
|
303
|
+
expect(result).toBeDefined();
|
|
304
|
+
});
|
|
305
|
+
it('should throw on invalid regex syntax', async () => {
|
|
306
|
+
// Pattern that passes safe-regex2 but fails RegExp compilation
|
|
307
|
+
// Using an invalid backreference that's syntactically incorrect
|
|
308
|
+
await expect(searchContent(testDir, '(?')).rejects.toThrow(/Invalid regular expression|ReDoS/i);
|
|
309
|
+
});
|
|
310
|
+
it('should respect maxResults limit', async () => {
|
|
311
|
+
const result = await searchContent(testDir, '\\w+', {
|
|
312
|
+
filePattern: '**/*.ts',
|
|
313
|
+
maxResults: 1,
|
|
314
|
+
});
|
|
315
|
+
expect(result.matches.length).toBeLessThanOrEqual(1);
|
|
316
|
+
if (result.matches.length === 1) {
|
|
317
|
+
expect(result.summary.truncated).toBe(true);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
it('should respect maxFilesScanned limit', async () => {
|
|
321
|
+
const result = await searchContent(testDir, 'export', {
|
|
322
|
+
filePattern: '**/*',
|
|
323
|
+
maxFilesScanned: 1,
|
|
324
|
+
});
|
|
325
|
+
expect(result.summary.filesScanned).toBeLessThanOrEqual(1);
|
|
326
|
+
});
|
|
327
|
+
it('should handle timeout correctly', async () => {
|
|
328
|
+
// Very short timeout should trigger timeout behavior
|
|
329
|
+
const result = await searchContent(testDir, 'export', {
|
|
330
|
+
filePattern: '**/*',
|
|
331
|
+
timeoutMs: 1, // 1ms timeout
|
|
332
|
+
});
|
|
333
|
+
// Either completes quickly or times out
|
|
334
|
+
expect(result).toBeDefined();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('listDirectory edge cases', () => {
|
|
338
|
+
it('should handle maxDepth=0', async () => {
|
|
339
|
+
const result = await listDirectory(testDir, {
|
|
340
|
+
recursive: true,
|
|
341
|
+
maxDepth: 0,
|
|
342
|
+
});
|
|
343
|
+
// Should only list immediate children, not recurse
|
|
344
|
+
expect(result.summary.maxDepthReached).toBe(0);
|
|
345
|
+
});
|
|
346
|
+
it('should handle empty directory', async () => {
|
|
347
|
+
const emptyDir = path.join(testDir, 'empty-dir');
|
|
348
|
+
await fs.mkdir(emptyDir, { recursive: true });
|
|
349
|
+
const result = await listDirectory(emptyDir);
|
|
350
|
+
expect(result.entries.length).toBe(0);
|
|
351
|
+
await fs.rm(emptyDir, { recursive: true });
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
describe('searchFiles edge cases', () => {
|
|
355
|
+
it('should handle complex glob patterns', async () => {
|
|
356
|
+
const result = await searchFiles(testDir, '**/*.{ts,md}');
|
|
357
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
358
|
+
});
|
|
359
|
+
it('should handle negation in exclude patterns', async () => {
|
|
360
|
+
const result = await searchFiles(testDir, '**/*', ['**/docs/**']);
|
|
361
|
+
// Should not include files from docs directory
|
|
362
|
+
expect(result.results.every((r) => !r.path.includes('docs'))).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
describe('getDirectoryTree edge cases', () => {
|
|
366
|
+
it('should handle very deep nesting with truncation', async () => {
|
|
367
|
+
const result = await getDirectoryTree(testDir, { maxDepth: 0 });
|
|
368
|
+
expect(result.summary.truncated).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
it('should sort entries correctly (directories first)', async () => {
|
|
371
|
+
const result = await getDirectoryTree(testDir);
|
|
372
|
+
const children = result.tree.children ?? [];
|
|
373
|
+
const dirs = children.filter((c) => c.type === 'directory');
|
|
374
|
+
const files = children.filter((c) => c.type === 'file');
|
|
375
|
+
if (dirs.length > 0 && files.length > 0) {
|
|
376
|
+
// Find first file index and last dir index
|
|
377
|
+
const firstFileIdx = children.findIndex((c) => c.type === 'file');
|
|
378
|
+
const lastDirIdx = children
|
|
379
|
+
.map((c, i) => ({ c, i }))
|
|
380
|
+
.filter(({ c }) => c.type === 'directory')
|
|
381
|
+
.pop()?.i;
|
|
382
|
+
if (lastDirIdx !== undefined) {
|
|
383
|
+
expect(lastDirIdx).toBeLessThan(firstFileIdx);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
describe('analyzeDirectory edge cases', () => {
|
|
389
|
+
it('should handle topN=1', async () => {
|
|
390
|
+
const result = await analyzeDirectory(testDir, { topN: 1 });
|
|
391
|
+
expect(result.analysis.largestFiles.length).toBeLessThanOrEqual(1);
|
|
392
|
+
expect(result.analysis.recentlyModified.length).toBeLessThanOrEqual(1);
|
|
393
|
+
});
|
|
394
|
+
it('should correctly count file types', async () => {
|
|
395
|
+
const result = await analyzeDirectory(testDir);
|
|
396
|
+
expect(result.analysis.fileTypes['.ts']).toBe(2);
|
|
397
|
+
expect(result.analysis.fileTypes['.md']).toBe(2);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
describe('readMultipleFiles edge cases', () => {
|
|
401
|
+
it('should handle empty array', async () => {
|
|
402
|
+
const results = await readMultipleFiles([]);
|
|
403
|
+
expect(results.length).toBe(0);
|
|
404
|
+
});
|
|
405
|
+
it('should handle all files failing', async () => {
|
|
406
|
+
const paths = [
|
|
407
|
+
path.join(testDir, 'nonexistent1.txt'),
|
|
408
|
+
path.join(testDir, 'nonexistent2.txt'),
|
|
409
|
+
];
|
|
410
|
+
const results = await readMultipleFiles(paths);
|
|
411
|
+
expect(results.length).toBe(2);
|
|
412
|
+
expect(results.every((r) => r.error !== undefined)).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
//# sourceMappingURL=file-operations.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-operations.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/file-operations.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-helpers.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/fs-helpers.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
5
|
+
import { getFileType, headFile, isHidden, isProbablyBinary, readFile, tailFile, } from '../../lib/fs-helpers.js';
|
|
6
|
+
import { normalizePath } from '../../lib/path-utils.js';
|
|
7
|
+
import { setAllowedDirectories } from '../../lib/path-validation.js';
|
|
8
|
+
describe('FS Helpers', () => {
|
|
9
|
+
let testDir;
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-fshelpers-test-'));
|
|
12
|
+
// Create test files
|
|
13
|
+
await fs.writeFile(path.join(testDir, 'README.md'), '# Test Project\nThis is a test.\n');
|
|
14
|
+
// Create a multi-line file for head/tail tests
|
|
15
|
+
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`).join('\n');
|
|
16
|
+
await fs.writeFile(path.join(testDir, 'multiline.txt'), lines);
|
|
17
|
+
// Create a simple binary file
|
|
18
|
+
const binaryData = Buffer.from([0, 1, 2, 3, 4]);
|
|
19
|
+
await fs.writeFile(path.join(testDir, 'binary.bin'), binaryData);
|
|
20
|
+
setAllowedDirectories([normalizePath(testDir)]);
|
|
21
|
+
});
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
try {
|
|
24
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Ignore cleanup errors
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
describe('readFile', () => {
|
|
31
|
+
it('should read file contents', async () => {
|
|
32
|
+
const result = await readFile(path.join(testDir, 'README.md'));
|
|
33
|
+
expect(result.content).toContain('# Test Project');
|
|
34
|
+
});
|
|
35
|
+
it('should read specific line ranges', async () => {
|
|
36
|
+
const result = await readFile(path.join(testDir, 'multiline.txt'), {
|
|
37
|
+
lineRange: { start: 1, end: 5 },
|
|
38
|
+
});
|
|
39
|
+
expect(result.content).toContain('Line 1');
|
|
40
|
+
expect(result.content).toContain('Line 5');
|
|
41
|
+
expect(result.truncated).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('should reject non-files', async () => {
|
|
44
|
+
await expect(readFile(testDir)).rejects.toThrow('Not a file');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('headFile', () => {
|
|
48
|
+
it('should return first N lines', async () => {
|
|
49
|
+
const content = await headFile(path.join(testDir, 'multiline.txt'), 5);
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
expect(lines[0]).toBe('Line 1');
|
|
52
|
+
expect(lines.length).toBeLessThanOrEqual(5);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('tailFile', () => {
|
|
56
|
+
it('should return last N lines', async () => {
|
|
57
|
+
const content = await tailFile(path.join(testDir, 'multiline.txt'), 5);
|
|
58
|
+
const lines = content.split('\n').filter((l) => l);
|
|
59
|
+
expect(lines[lines.length - 1]).toBe('Line 100');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('getFileType', () => {
|
|
63
|
+
it('should identify files', async () => {
|
|
64
|
+
const stats = await fs.stat(path.join(testDir, 'README.md'));
|
|
65
|
+
expect(getFileType(stats)).toBe('file');
|
|
66
|
+
});
|
|
67
|
+
it('should identify directories', async () => {
|
|
68
|
+
const stats = await fs.stat(testDir);
|
|
69
|
+
expect(getFileType(stats)).toBe('directory');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('isHidden', () => {
|
|
73
|
+
it('should identify hidden files', () => {
|
|
74
|
+
expect(isHidden('.git')).toBe(true);
|
|
75
|
+
expect(isHidden('file.txt')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('isProbablyBinary', () => {
|
|
79
|
+
it('should identify binary files', async () => {
|
|
80
|
+
const isBinary = await isProbablyBinary(path.join(testDir, 'binary.bin'));
|
|
81
|
+
expect(isBinary).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('should identify text files', async () => {
|
|
84
|
+
const isBinary = await isProbablyBinary(path.join(testDir, 'README.md'));
|
|
85
|
+
expect(isBinary).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('should identify empty files as text', async () => {
|
|
88
|
+
const emptyFile = path.join(testDir, 'empty.txt');
|
|
89
|
+
await fs.writeFile(emptyFile, '');
|
|
90
|
+
const isBinary = await isProbablyBinary(emptyFile);
|
|
91
|
+
expect(isBinary).toBe(false);
|
|
92
|
+
await fs.rm(emptyFile);
|
|
93
|
+
});
|
|
94
|
+
it('should identify UTF-8 BOM files as text', async () => {
|
|
95
|
+
const bomFile = path.join(testDir, 'bom.txt');
|
|
96
|
+
// UTF-8 BOM: EF BB BF followed by text
|
|
97
|
+
const content = Buffer.concat([
|
|
98
|
+
Buffer.from([0xef, 0xbb, 0xbf]),
|
|
99
|
+
Buffer.from('Hello World'),
|
|
100
|
+
]);
|
|
101
|
+
await fs.writeFile(bomFile, content);
|
|
102
|
+
const isBinary = await isProbablyBinary(bomFile);
|
|
103
|
+
expect(isBinary).toBe(false);
|
|
104
|
+
await fs.rm(bomFile);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('readFile edge cases', () => {
|
|
108
|
+
it('should reject reading with both head and tail', async () => {
|
|
109
|
+
await expect(readFile(path.join(testDir, 'multiline.txt'), {
|
|
110
|
+
head: 5,
|
|
111
|
+
tail: 5,
|
|
112
|
+
})).rejects.toThrow(/Cannot specify multiple/);
|
|
113
|
+
});
|
|
114
|
+
it('should reject reading with both lineRange and head', async () => {
|
|
115
|
+
await expect(readFile(path.join(testDir, 'multiline.txt'), {
|
|
116
|
+
lineRange: { start: 1, end: 5 },
|
|
117
|
+
head: 5,
|
|
118
|
+
})).rejects.toThrow(/Cannot specify multiple/);
|
|
119
|
+
});
|
|
120
|
+
it('should reject invalid lineRange start', async () => {
|
|
121
|
+
await expect(readFile(path.join(testDir, 'multiline.txt'), {
|
|
122
|
+
lineRange: { start: 0, end: 5 },
|
|
123
|
+
})).rejects.toThrow(/start must be at least 1/);
|
|
124
|
+
});
|
|
125
|
+
it('should reject lineRange where end < start', async () => {
|
|
126
|
+
await expect(readFile(path.join(testDir, 'multiline.txt'), {
|
|
127
|
+
lineRange: { start: 10, end: 5 },
|
|
128
|
+
})).rejects.toThrow(/end.*must be >= start/);
|
|
129
|
+
});
|
|
130
|
+
it('should handle reading beyond file length gracefully', async () => {
|
|
131
|
+
const result = await readFile(path.join(testDir, 'multiline.txt'), {
|
|
132
|
+
lineRange: { start: 95, end: 200 },
|
|
133
|
+
});
|
|
134
|
+
// Should return lines 95-100 (the file has 100 lines)
|
|
135
|
+
expect(result.content).toContain('Line 100');
|
|
136
|
+
expect(result.truncated).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it('should handle empty file', async () => {
|
|
139
|
+
const emptyFile = path.join(testDir, 'empty-read.txt');
|
|
140
|
+
await fs.writeFile(emptyFile, '');
|
|
141
|
+
const result = await readFile(emptyFile);
|
|
142
|
+
expect(result.content).toBe('');
|
|
143
|
+
expect(result.truncated).toBe(false);
|
|
144
|
+
await fs.rm(emptyFile);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('headFile edge cases', () => {
|
|
148
|
+
it('should handle requesting more lines than file has', async () => {
|
|
149
|
+
const content = await headFile(path.join(testDir, 'multiline.txt'), 200);
|
|
150
|
+
const lines = content.split('\n');
|
|
151
|
+
expect(lines.length).toBe(100);
|
|
152
|
+
});
|
|
153
|
+
it('should handle empty file', async () => {
|
|
154
|
+
const emptyFile = path.join(testDir, 'empty-head.txt');
|
|
155
|
+
await fs.writeFile(emptyFile, '');
|
|
156
|
+
const content = await headFile(emptyFile, 5);
|
|
157
|
+
expect(content).toBe('');
|
|
158
|
+
await fs.rm(emptyFile);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('tailFile edge cases', () => {
|
|
162
|
+
it('should handle requesting more lines than file has', async () => {
|
|
163
|
+
const content = await tailFile(path.join(testDir, 'multiline.txt'), 200);
|
|
164
|
+
const lines = content.split('\n').filter((l) => l);
|
|
165
|
+
expect(lines.length).toBe(100);
|
|
166
|
+
});
|
|
167
|
+
it('should handle empty file', async () => {
|
|
168
|
+
const emptyFile = path.join(testDir, 'empty-tail.txt');
|
|
169
|
+
await fs.writeFile(emptyFile, '');
|
|
170
|
+
const content = await tailFile(emptyFile, 5);
|
|
171
|
+
expect(content).toBe('');
|
|
172
|
+
await fs.rm(emptyFile);
|
|
173
|
+
});
|
|
174
|
+
it('should handle single line file', async () => {
|
|
175
|
+
const singleLineFile = path.join(testDir, 'single-line.txt');
|
|
176
|
+
await fs.writeFile(singleLineFile, 'Only one line');
|
|
177
|
+
const content = await tailFile(singleLineFile, 5);
|
|
178
|
+
expect(content).toBe('Only one line');
|
|
179
|
+
await fs.rm(singleLineFile);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=fs-helpers.test.js.map
|