@dexto/tools-filesystem 1.5.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.
Files changed (62) hide show
  1. package/LICENSE +44 -0
  2. package/dist/directory-approval.integration.test.cjs +467 -0
  3. package/dist/directory-approval.integration.test.d.cts +2 -0
  4. package/dist/directory-approval.integration.test.d.ts +2 -0
  5. package/dist/directory-approval.integration.test.js +444 -0
  6. package/dist/edit-file-tool.cjs +181 -0
  7. package/dist/edit-file-tool.d.cts +17 -0
  8. package/dist/edit-file-tool.d.ts +17 -0
  9. package/dist/edit-file-tool.js +147 -0
  10. package/dist/error-codes.cjs +53 -0
  11. package/dist/error-codes.d.cts +32 -0
  12. package/dist/error-codes.d.ts +32 -0
  13. package/dist/error-codes.js +29 -0
  14. package/dist/errors.cjs +302 -0
  15. package/dist/errors.d.cts +112 -0
  16. package/dist/errors.d.ts +112 -0
  17. package/dist/errors.js +278 -0
  18. package/dist/file-tool-types.cjs +16 -0
  19. package/dist/file-tool-types.d.cts +46 -0
  20. package/dist/file-tool-types.d.ts +46 -0
  21. package/dist/file-tool-types.js +0 -0
  22. package/dist/filesystem-service.cjs +526 -0
  23. package/dist/filesystem-service.d.cts +107 -0
  24. package/dist/filesystem-service.d.ts +107 -0
  25. package/dist/filesystem-service.js +492 -0
  26. package/dist/glob-files-tool.cjs +70 -0
  27. package/dist/glob-files-tool.d.cts +16 -0
  28. package/dist/glob-files-tool.d.ts +16 -0
  29. package/dist/glob-files-tool.js +46 -0
  30. package/dist/grep-content-tool.cjs +86 -0
  31. package/dist/grep-content-tool.d.cts +16 -0
  32. package/dist/grep-content-tool.d.ts +16 -0
  33. package/dist/grep-content-tool.js +62 -0
  34. package/dist/index.cjs +55 -0
  35. package/dist/index.d.cts +14 -0
  36. package/dist/index.d.ts +14 -0
  37. package/dist/index.js +22 -0
  38. package/dist/path-validator.cjs +232 -0
  39. package/dist/path-validator.d.cts +90 -0
  40. package/dist/path-validator.d.ts +90 -0
  41. package/dist/path-validator.js +198 -0
  42. package/dist/path-validator.test.cjs +444 -0
  43. package/dist/path-validator.test.d.cts +2 -0
  44. package/dist/path-validator.test.d.ts +2 -0
  45. package/dist/path-validator.test.js +443 -0
  46. package/dist/read-file-tool.cjs +117 -0
  47. package/dist/read-file-tool.d.cts +17 -0
  48. package/dist/read-file-tool.d.ts +17 -0
  49. package/dist/read-file-tool.js +83 -0
  50. package/dist/tool-provider.cjs +108 -0
  51. package/dist/tool-provider.d.cts +74 -0
  52. package/dist/tool-provider.d.ts +74 -0
  53. package/dist/tool-provider.js +84 -0
  54. package/dist/types.cjs +16 -0
  55. package/dist/types.d.cts +172 -0
  56. package/dist/types.d.ts +172 -0
  57. package/dist/types.js +0 -0
  58. package/dist/write-file-tool.cjs +177 -0
  59. package/dist/write-file-tool.d.cts +17 -0
  60. package/dist/write-file-tool.d.ts +17 -0
  61. package/dist/write-file-tool.js +143 -0
  62. package/package.json +42 -0
@@ -0,0 +1,444 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_path_validator = require("./path-validator.js");
4
+ const createMockLogger = () => ({
5
+ debug: import_vitest.vi.fn(),
6
+ info: import_vitest.vi.fn(),
7
+ warn: import_vitest.vi.fn(),
8
+ error: import_vitest.vi.fn(),
9
+ createChild: import_vitest.vi.fn().mockReturnThis()
10
+ });
11
+ (0, import_vitest.describe)("PathValidator", () => {
12
+ let mockLogger;
13
+ (0, import_vitest.beforeEach)(() => {
14
+ mockLogger = createMockLogger();
15
+ import_vitest.vi.clearAllMocks();
16
+ });
17
+ (0, import_vitest.describe)("validatePath", () => {
18
+ (0, import_vitest.describe)("Empty and Invalid Paths", () => {
19
+ (0, import_vitest.it)("should reject empty path", () => {
20
+ const validator = new import_path_validator.PathValidator(
21
+ {
22
+ allowedPaths: ["/home/user/project"],
23
+ blockedPaths: [],
24
+ blockedExtensions: [],
25
+ maxFileSize: 10 * 1024 * 1024,
26
+ enableBackups: false,
27
+ backupRetentionDays: 7,
28
+ workingDirectory: "/home/user/project"
29
+ },
30
+ mockLogger
31
+ );
32
+ const result = validator.validatePath("");
33
+ (0, import_vitest.expect)(result.isValid).toBe(false);
34
+ (0, import_vitest.expect)(result.error).toBe("Path cannot be empty");
35
+ });
36
+ (0, import_vitest.it)("should reject whitespace-only path", () => {
37
+ const validator = new import_path_validator.PathValidator(
38
+ {
39
+ allowedPaths: ["/home/user/project"],
40
+ blockedPaths: [],
41
+ blockedExtensions: [],
42
+ maxFileSize: 10 * 1024 * 1024,
43
+ enableBackups: false,
44
+ backupRetentionDays: 7,
45
+ workingDirectory: "/home/user/project"
46
+ },
47
+ mockLogger
48
+ );
49
+ const result = validator.validatePath(" ");
50
+ (0, import_vitest.expect)(result.isValid).toBe(false);
51
+ (0, import_vitest.expect)(result.error).toBe("Path cannot be empty");
52
+ });
53
+ });
54
+ (0, import_vitest.describe)("Allowed Paths", () => {
55
+ (0, import_vitest.it)("should allow paths within allowed directories", () => {
56
+ const validator = new import_path_validator.PathValidator(
57
+ {
58
+ allowedPaths: ["/home/user/project"],
59
+ blockedPaths: [],
60
+ blockedExtensions: [],
61
+ maxFileSize: 10 * 1024 * 1024,
62
+ enableBackups: false,
63
+ backupRetentionDays: 7,
64
+ workingDirectory: "/home/user/project"
65
+ },
66
+ mockLogger
67
+ );
68
+ const result = validator.validatePath("/home/user/project/src/file.ts");
69
+ (0, import_vitest.expect)(result.isValid).toBe(true);
70
+ (0, import_vitest.expect)(result.normalizedPath).toBeDefined();
71
+ });
72
+ (0, import_vitest.it)("should allow relative paths within working directory", () => {
73
+ const validator = new import_path_validator.PathValidator(
74
+ {
75
+ allowedPaths: ["/home/user/project"],
76
+ blockedPaths: [],
77
+ blockedExtensions: [],
78
+ maxFileSize: 10 * 1024 * 1024,
79
+ enableBackups: false,
80
+ backupRetentionDays: 7,
81
+ workingDirectory: "/home/user/project"
82
+ },
83
+ mockLogger
84
+ );
85
+ const result = validator.validatePath("src/file.ts");
86
+ (0, import_vitest.expect)(result.isValid).toBe(true);
87
+ });
88
+ (0, import_vitest.it)("should reject paths outside allowed directories", () => {
89
+ const validator = new import_path_validator.PathValidator(
90
+ {
91
+ allowedPaths: ["/home/user/project"],
92
+ blockedPaths: [],
93
+ blockedExtensions: [],
94
+ maxFileSize: 10 * 1024 * 1024,
95
+ enableBackups: false,
96
+ backupRetentionDays: 7,
97
+ workingDirectory: "/home/user/project"
98
+ },
99
+ mockLogger
100
+ );
101
+ const result = validator.validatePath("/external/project/file.ts");
102
+ (0, import_vitest.expect)(result.isValid).toBe(false);
103
+ (0, import_vitest.expect)(result.error).toContain("not within allowed paths");
104
+ });
105
+ (0, import_vitest.it)("should allow all paths when allowedPaths is empty", () => {
106
+ const validator = new import_path_validator.PathValidator(
107
+ {
108
+ allowedPaths: [],
109
+ blockedPaths: [],
110
+ blockedExtensions: [],
111
+ maxFileSize: 10 * 1024 * 1024,
112
+ enableBackups: false,
113
+ backupRetentionDays: 7,
114
+ workingDirectory: "/home/user/project"
115
+ },
116
+ mockLogger
117
+ );
118
+ const result = validator.validatePath("/anywhere/file.ts");
119
+ (0, import_vitest.expect)(result.isValid).toBe(true);
120
+ });
121
+ });
122
+ (0, import_vitest.describe)("Path Traversal Detection", () => {
123
+ (0, import_vitest.it)("should reject path traversal attempts", () => {
124
+ const validator = new import_path_validator.PathValidator(
125
+ {
126
+ allowedPaths: ["/home/user/project"],
127
+ blockedPaths: [],
128
+ blockedExtensions: [],
129
+ maxFileSize: 10 * 1024 * 1024,
130
+ enableBackups: false,
131
+ backupRetentionDays: 7,
132
+ workingDirectory: "/home/user/project"
133
+ },
134
+ mockLogger
135
+ );
136
+ const result = validator.validatePath("/home/user/project/../../../etc/passwd");
137
+ (0, import_vitest.expect)(result.isValid).toBe(false);
138
+ (0, import_vitest.expect)(result.error).toBe("Path traversal detected");
139
+ });
140
+ });
141
+ (0, import_vitest.describe)("Blocked Paths", () => {
142
+ (0, import_vitest.it)("should reject paths in blocked directories", () => {
143
+ const validator = new import_path_validator.PathValidator(
144
+ {
145
+ allowedPaths: ["/home/user/project"],
146
+ blockedPaths: [".git", "node_modules"],
147
+ blockedExtensions: [],
148
+ maxFileSize: 10 * 1024 * 1024,
149
+ enableBackups: false,
150
+ backupRetentionDays: 7,
151
+ workingDirectory: "/home/user/project"
152
+ },
153
+ mockLogger
154
+ );
155
+ const result = validator.validatePath("/home/user/project/.git/config");
156
+ (0, import_vitest.expect)(result.isValid).toBe(false);
157
+ (0, import_vitest.expect)(result.error).toContain("blocked");
158
+ });
159
+ (0, import_vitest.it)("should reject paths in node_modules", () => {
160
+ const validator = new import_path_validator.PathValidator(
161
+ {
162
+ allowedPaths: ["/home/user/project"],
163
+ blockedPaths: ["node_modules"],
164
+ blockedExtensions: [],
165
+ maxFileSize: 10 * 1024 * 1024,
166
+ enableBackups: false,
167
+ backupRetentionDays: 7,
168
+ workingDirectory: "/home/user/project"
169
+ },
170
+ mockLogger
171
+ );
172
+ const result = validator.validatePath(
173
+ "/home/user/project/node_modules/lodash/index.js"
174
+ );
175
+ (0, import_vitest.expect)(result.isValid).toBe(false);
176
+ (0, import_vitest.expect)(result.error).toContain("blocked");
177
+ });
178
+ });
179
+ (0, import_vitest.describe)("Blocked Extensions", () => {
180
+ (0, import_vitest.it)("should reject files with blocked extensions", () => {
181
+ const validator = new import_path_validator.PathValidator(
182
+ {
183
+ allowedPaths: ["/home/user/project"],
184
+ blockedPaths: [],
185
+ blockedExtensions: [".exe", ".dll"],
186
+ maxFileSize: 10 * 1024 * 1024,
187
+ enableBackups: false,
188
+ backupRetentionDays: 7,
189
+ workingDirectory: "/home/user/project"
190
+ },
191
+ mockLogger
192
+ );
193
+ const result = validator.validatePath("/home/user/project/malware.exe");
194
+ (0, import_vitest.expect)(result.isValid).toBe(false);
195
+ (0, import_vitest.expect)(result.error).toContain(".exe is not allowed");
196
+ });
197
+ (0, import_vitest.it)("should handle extensions without leading dot", () => {
198
+ const validator = new import_path_validator.PathValidator(
199
+ {
200
+ allowedPaths: ["/home/user/project"],
201
+ blockedPaths: [],
202
+ blockedExtensions: ["exe", "dll"],
203
+ // No leading dot
204
+ maxFileSize: 10 * 1024 * 1024,
205
+ enableBackups: false,
206
+ backupRetentionDays: 7,
207
+ workingDirectory: "/home/user/project"
208
+ },
209
+ mockLogger
210
+ );
211
+ const result = validator.validatePath("/home/user/project/file.exe");
212
+ (0, import_vitest.expect)(result.isValid).toBe(false);
213
+ });
214
+ (0, import_vitest.it)("should be case-insensitive for extensions", () => {
215
+ const validator = new import_path_validator.PathValidator(
216
+ {
217
+ allowedPaths: ["/home/user/project"],
218
+ blockedPaths: [],
219
+ blockedExtensions: [".exe"],
220
+ maxFileSize: 10 * 1024 * 1024,
221
+ enableBackups: false,
222
+ backupRetentionDays: 7,
223
+ workingDirectory: "/home/user/project"
224
+ },
225
+ mockLogger
226
+ );
227
+ const result = validator.validatePath("/home/user/project/file.EXE");
228
+ (0, import_vitest.expect)(result.isValid).toBe(false);
229
+ });
230
+ });
231
+ (0, import_vitest.describe)("Directory Approval Checker Integration", () => {
232
+ (0, import_vitest.it)("should consult approval checker for external paths", () => {
233
+ const validator = new import_path_validator.PathValidator(
234
+ {
235
+ allowedPaths: ["/home/user/project"],
236
+ blockedPaths: [],
237
+ blockedExtensions: [],
238
+ maxFileSize: 10 * 1024 * 1024,
239
+ enableBackups: false,
240
+ backupRetentionDays: 7,
241
+ workingDirectory: "/home/user/project"
242
+ },
243
+ mockLogger
244
+ );
245
+ let result = validator.validatePath("/external/project/file.ts");
246
+ (0, import_vitest.expect)(result.isValid).toBe(false);
247
+ const approvalChecker = (filePath) => {
248
+ return filePath.startsWith("/external/project");
249
+ };
250
+ validator.setDirectoryApprovalChecker(approvalChecker);
251
+ result = validator.validatePath("/external/project/file.ts");
252
+ (0, import_vitest.expect)(result.isValid).toBe(true);
253
+ });
254
+ (0, import_vitest.it)("should not use approval checker for config-allowed paths", () => {
255
+ const approvalChecker = import_vitest.vi.fn().mockReturnValue(false);
256
+ const validator = new import_path_validator.PathValidator(
257
+ {
258
+ allowedPaths: ["/home/user/project"],
259
+ blockedPaths: [],
260
+ blockedExtensions: [],
261
+ maxFileSize: 10 * 1024 * 1024,
262
+ enableBackups: false,
263
+ backupRetentionDays: 7,
264
+ workingDirectory: "/home/user/project"
265
+ },
266
+ mockLogger
267
+ );
268
+ validator.setDirectoryApprovalChecker(approvalChecker);
269
+ const result = validator.validatePath("/home/user/project/src/file.ts");
270
+ (0, import_vitest.expect)(result.isValid).toBe(true);
271
+ (0, import_vitest.expect)(approvalChecker).not.toHaveBeenCalled();
272
+ });
273
+ });
274
+ });
275
+ (0, import_vitest.describe)("isPathWithinAllowed", () => {
276
+ (0, import_vitest.it)("should return true for paths within config-allowed directories", () => {
277
+ const validator = new import_path_validator.PathValidator(
278
+ {
279
+ allowedPaths: ["/home/user/project"],
280
+ blockedPaths: [],
281
+ blockedExtensions: [],
282
+ maxFileSize: 10 * 1024 * 1024,
283
+ enableBackups: false,
284
+ backupRetentionDays: 7,
285
+ workingDirectory: "/home/user/project"
286
+ },
287
+ mockLogger
288
+ );
289
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/project/src/file.ts")).toBe(true);
290
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/project/deep/nested/file.ts")).toBe(
291
+ true
292
+ );
293
+ });
294
+ (0, import_vitest.it)("should return false for paths outside config-allowed directories", () => {
295
+ const validator = new import_path_validator.PathValidator(
296
+ {
297
+ allowedPaths: ["/home/user/project"],
298
+ blockedPaths: [],
299
+ blockedExtensions: [],
300
+ maxFileSize: 10 * 1024 * 1024,
301
+ enableBackups: false,
302
+ backupRetentionDays: 7,
303
+ workingDirectory: "/home/user/project"
304
+ },
305
+ mockLogger
306
+ );
307
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
308
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/other/file.ts")).toBe(false);
309
+ });
310
+ (0, import_vitest.it)("should NOT consult approval checker (used for prompting decisions)", () => {
311
+ const approvalChecker = import_vitest.vi.fn().mockReturnValue(true);
312
+ const validator = new import_path_validator.PathValidator(
313
+ {
314
+ allowedPaths: ["/home/user/project"],
315
+ blockedPaths: [],
316
+ blockedExtensions: [],
317
+ maxFileSize: 10 * 1024 * 1024,
318
+ enableBackups: false,
319
+ backupRetentionDays: 7,
320
+ workingDirectory: "/home/user/project"
321
+ },
322
+ mockLogger
323
+ );
324
+ validator.setDirectoryApprovalChecker(approvalChecker);
325
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
326
+ (0, import_vitest.expect)(approvalChecker).not.toHaveBeenCalled();
327
+ });
328
+ (0, import_vitest.it)("should return false for empty path", () => {
329
+ const validator = new import_path_validator.PathValidator(
330
+ {
331
+ allowedPaths: ["/home/user/project"],
332
+ blockedPaths: [],
333
+ blockedExtensions: [],
334
+ maxFileSize: 10 * 1024 * 1024,
335
+ enableBackups: false,
336
+ backupRetentionDays: 7,
337
+ workingDirectory: "/home/user/project"
338
+ },
339
+ mockLogger
340
+ );
341
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("")).toBe(false);
342
+ (0, import_vitest.expect)(validator.isPathWithinAllowed(" ")).toBe(false);
343
+ });
344
+ (0, import_vitest.it)("should return true when allowedPaths is empty (all paths allowed)", () => {
345
+ const validator = new import_path_validator.PathValidator(
346
+ {
347
+ allowedPaths: [],
348
+ blockedPaths: [],
349
+ blockedExtensions: [],
350
+ maxFileSize: 10 * 1024 * 1024,
351
+ enableBackups: false,
352
+ backupRetentionDays: 7,
353
+ workingDirectory: "/home/user/project"
354
+ },
355
+ mockLogger
356
+ );
357
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/anywhere/file.ts")).toBe(true);
358
+ });
359
+ });
360
+ (0, import_vitest.describe)("Path Containment (Parent Directory Coverage)", () => {
361
+ (0, import_vitest.it)("should recognize that approving parent covers child paths", () => {
362
+ const validator = new import_path_validator.PathValidator(
363
+ {
364
+ allowedPaths: ["/external/sub"],
365
+ blockedPaths: [],
366
+ blockedExtensions: [],
367
+ maxFileSize: 10 * 1024 * 1024,
368
+ enableBackups: false,
369
+ backupRetentionDays: 7,
370
+ workingDirectory: "/home/user/project"
371
+ },
372
+ mockLogger
373
+ );
374
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/external/sub/deep/nested/file.ts")).toBe(true);
375
+ });
376
+ (0, import_vitest.it)("should not allow sibling directories", () => {
377
+ const validator = new import_path_validator.PathValidator(
378
+ {
379
+ allowedPaths: ["/external/sub"],
380
+ blockedPaths: [],
381
+ blockedExtensions: [],
382
+ maxFileSize: 10 * 1024 * 1024,
383
+ enableBackups: false,
384
+ backupRetentionDays: 7,
385
+ workingDirectory: "/home/user/project"
386
+ },
387
+ mockLogger
388
+ );
389
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/external/other/file.ts")).toBe(false);
390
+ });
391
+ (0, import_vitest.it)("should not allow parent directories when child is approved", () => {
392
+ const validator = new import_path_validator.PathValidator(
393
+ {
394
+ allowedPaths: ["/external/sub/deep"],
395
+ blockedPaths: [],
396
+ blockedExtensions: [],
397
+ maxFileSize: 10 * 1024 * 1024,
398
+ enableBackups: false,
399
+ backupRetentionDays: 7,
400
+ workingDirectory: "/home/user/project"
401
+ },
402
+ mockLogger
403
+ );
404
+ (0, import_vitest.expect)(validator.isPathWithinAllowed("/external/sub/file.ts")).toBe(false);
405
+ });
406
+ });
407
+ (0, import_vitest.describe)("getAllowedPaths and getBlockedPaths", () => {
408
+ (0, import_vitest.it)("should return normalized allowed paths", () => {
409
+ const validator = new import_path_validator.PathValidator(
410
+ {
411
+ allowedPaths: [".", "./src"],
412
+ blockedPaths: [],
413
+ blockedExtensions: [],
414
+ maxFileSize: 10 * 1024 * 1024,
415
+ enableBackups: false,
416
+ backupRetentionDays: 7,
417
+ workingDirectory: "/home/user/project"
418
+ },
419
+ mockLogger
420
+ );
421
+ const allowedPaths = validator.getAllowedPaths();
422
+ (0, import_vitest.expect)(allowedPaths).toHaveLength(2);
423
+ (0, import_vitest.expect)(allowedPaths[0]).toBe("/home/user/project");
424
+ (0, import_vitest.expect)(allowedPaths[1]).toBe("/home/user/project/src");
425
+ });
426
+ (0, import_vitest.it)("should return blocked paths", () => {
427
+ const validator = new import_path_validator.PathValidator(
428
+ {
429
+ allowedPaths: ["/home/user/project"],
430
+ blockedPaths: [".git", "node_modules"],
431
+ blockedExtensions: [],
432
+ maxFileSize: 10 * 1024 * 1024,
433
+ enableBackups: false,
434
+ backupRetentionDays: 7,
435
+ workingDirectory: "/home/user/project"
436
+ },
437
+ mockLogger
438
+ );
439
+ const blockedPaths = validator.getBlockedPaths();
440
+ (0, import_vitest.expect)(blockedPaths).toContain(".git");
441
+ (0, import_vitest.expect)(blockedPaths).toContain("node_modules");
442
+ });
443
+ });
444
+ });
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }