mcp-code-context 3.5.3 → 3.6.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/CHANGELOG.md +57 -3
- package/INSTRUCTIONS.md +274 -0
- package/README.md +59 -6
- package/SECURITY.md +14 -14
- package/dist/src/core/cacheManager.d.ts +44 -0
- package/dist/src/core/cacheManager.js +255 -0
- package/dist/src/core/cacheManager.js.map +1 -0
- package/dist/src/core/engine.d.ts +1 -1
- package/dist/src/core/engine.js +1 -1
- package/dist/src/core/validator.d.ts +1 -1
- package/dist/src/core/validator.js +19 -7
- package/dist/src/core/validator.js.map +1 -1
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +197 -20
- package/dist/src/index.js.map +1 -1
- package/dist/src/operations/compress.d.ts +3 -1
- package/dist/src/operations/compress.js +47 -48
- package/dist/src/operations/compress.js.map +1 -1
- package/dist/src/operations/confirmationStore.d.ts +1 -1
- package/dist/src/operations/confirmationStore.js +1 -1
- package/dist/src/operations/read.d.ts +5 -1
- package/dist/src/operations/read.js +125 -64
- package/dist/src/operations/read.js.map +1 -1
- package/dist/src/operations/write.d.ts +2 -2
- package/dist/src/operations/write.js +83 -59
- package/dist/src/operations/write.js.map +1 -1
- package/dist/src/parsers/base.d.ts +1 -1
- package/dist/src/parsers/base.js +1 -1
- package/dist/src/parsers/dartTreeSitter.d.ts +1 -1
- package/dist/src/parsers/dartTreeSitter.js +1 -1
- package/dist/src/parsers/php.d.ts +1 -1
- package/dist/src/parsers/php.js +1 -1
- package/dist/src/parsers/python.d.ts +1 -1
- package/dist/src/parsers/python.js +1 -1
- package/dist/src/parsers/registry.d.ts +1 -1
- package/dist/src/parsers/registry.js +1 -1
- package/dist/src/parsers/typescript.d.ts +1 -1
- package/dist/src/parsers/typescript.js +1 -1
- package/dist/src/utils/auditLogger.d.ts +1 -1
- package/dist/src/utils/auditLogger.js +3 -2
- package/dist/src/utils/auditLogger.js.map +1 -1
- package/dist/src/utils/backupManager.d.ts +30 -9
- package/dist/src/utils/backupManager.js +75 -33
- package/dist/src/utils/backupManager.js.map +1 -1
- package/dist/src/utils/constants.d.ts +13 -2
- package/dist/src/utils/constants.js +21 -2
- package/dist/src/utils/constants.js.map +1 -1
- package/dist/src/utils/diff.d.ts +37 -7
- package/dist/src/utils/diff.js +91 -101
- package/dist/src/utils/diff.js.map +1 -1
- package/dist/src/utils/fileLock.d.ts +25 -50
- package/dist/src/utils/fileLock.js +66 -100
- package/dist/src/utils/fileLock.js.map +1 -1
- package/dist/src/utils/fileWalker.d.ts +28 -0
- package/dist/src/utils/fileWalker.js +57 -0
- package/dist/src/utils/fileWalker.js.map +1 -0
- package/dist/src/utils/fileWatcher.d.ts +23 -0
- package/dist/src/utils/fileWatcher.js +80 -0
- package/dist/src/utils/fileWatcher.js.map +1 -0
- package/dist/src/utils/fuzzySearch.d.ts +22 -0
- package/dist/src/utils/fuzzySearch.js +36 -0
- package/dist/src/utils/fuzzySearch.js.map +1 -0
- package/dist/src/utils/logger.d.ts +19 -0
- package/dist/src/utils/logger.js +50 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/rateLimiter.d.ts +1 -1
- package/dist/src/utils/rateLimiter.js +1 -1
- package/dist/src/utils/safeRegex.d.ts +1 -1
- package/dist/src/utils/safeRegex.js +1 -1
- package/dist/src/utils/searchTools.d.ts +14 -0
- package/dist/src/utils/searchTools.js +94 -0
- package/dist/src/utils/searchTools.js.map +1 -0
- package/dist/src/utils/streaming.d.ts +1 -1
- package/dist/src/utils/streaming.js +1 -1
- package/dist/src/utils/telemetry.d.ts +1 -1
- package/dist/src/utils/telemetry.js +1 -1
- package/llms.txt +5 -5
- package/package.json +28 -10
- package/vitest.config.ts +37 -0
package/dist/src/utils/diff.js
CHANGED
|
@@ -1,18 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Diff
|
|
3
|
-
*
|
|
2
|
+
* Diff Utilities - v3.6.0
|
|
3
|
+
* Uses diff-match-patch for efficient diffing
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
import DiffMatchPatch from 'diff-match-patch';
|
|
6
|
+
import { DIFF_MAX_FILE_LINES } from './constants.js';
|
|
7
|
+
const dmp = new DiffMatchPatch();
|
|
8
|
+
const COMPACT_DIFF_THRESHOLD = 2048;
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
10
|
+
* Generates a unified diff using Myers algorithm.
|
|
11
|
+
* Falls back to simple diff for files exceeding line limit.
|
|
12
|
+
*
|
|
13
|
+
* @param oldText - Original file content
|
|
14
|
+
* @param newText - Modified file content
|
|
15
|
+
* @returns Unified diff in patch format
|
|
8
16
|
*/
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
17
|
+
export function generateUnifiedDiff(oldText, newText) {
|
|
18
|
+
const oldLines = oldText.split('\n');
|
|
19
|
+
const newLines = newText.split('\n');
|
|
20
|
+
if (oldLines.length > DIFF_MAX_FILE_LINES || newLines.length > DIFF_MAX_FILE_LINES) {
|
|
21
|
+
return generateSimpleDiff(oldText, newText);
|
|
22
|
+
}
|
|
23
|
+
const diffs = dmp.diff_main(oldText, newText);
|
|
24
|
+
dmp.diff_cleanupSemantic(diffs);
|
|
25
|
+
const patch = dmp.patch_make(oldText, diffs);
|
|
26
|
+
return dmp.patch_toText(patch);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generates a simple diff summary for large files.
|
|
30
|
+
*
|
|
31
|
+
* @param oldText - Original file content
|
|
32
|
+
* @param newText - Modified file content
|
|
33
|
+
* @returns Simple line count summary
|
|
34
|
+
*/
|
|
35
|
+
export function generateSimpleDiff(oldText, newText) {
|
|
36
|
+
const oldLines = oldText.split('\n');
|
|
37
|
+
const newLines = newText.split('\n');
|
|
38
|
+
return `File too large for detailed diff.
|
|
39
|
+
Old: ${oldLines.length} lines
|
|
40
|
+
New: ${newLines.length} lines
|
|
41
|
+
Changed: ${Math.abs(oldLines.length - newLines.length)} lines`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generates a compact diff showing only changes (no context).
|
|
45
|
+
*
|
|
46
|
+
* @param oldText - Original file content
|
|
47
|
+
* @param newText - Modified file content
|
|
48
|
+
* @returns Compact diff with + and - prefixes
|
|
49
|
+
*/
|
|
50
|
+
export function generateCompactDiff(oldText, newText) {
|
|
51
|
+
const diffs = dmp.diff_main(oldText, newText);
|
|
52
|
+
dmp.diff_cleanupSemantic(diffs);
|
|
53
|
+
const compactLines = [];
|
|
54
|
+
for (const [op, text] of diffs) {
|
|
55
|
+
if (op === 0)
|
|
56
|
+
continue;
|
|
57
|
+
const prefix = op === 1 ? '+ ' : '- ';
|
|
58
|
+
const lines = text.split('\n').filter(line => line.trim().length > 0);
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
compactLines.push(prefix + line);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return compactLines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Automatically selects compact or full diff based on size.
|
|
67
|
+
*
|
|
68
|
+
* @param oldText - Original file content
|
|
69
|
+
* @param newText - Modified file content
|
|
70
|
+
* @returns Compact diff if full diff exceeds threshold, otherwise full diff
|
|
71
|
+
*/
|
|
72
|
+
export function generateSmartDiff(oldText, newText) {
|
|
73
|
+
const fullDiff = generateUnifiedDiff(oldText, newText);
|
|
74
|
+
if (fullDiff.length > COMPACT_DIFF_THRESHOLD) {
|
|
75
|
+
return generateCompactDiff(oldText, newText);
|
|
76
|
+
}
|
|
77
|
+
return fullDiff;
|
|
78
|
+
}
|
|
79
|
+
export function lcs(lines1, lines2) {
|
|
80
|
+
const m = lines1.length;
|
|
81
|
+
const n = lines2.length;
|
|
82
|
+
if (m === 0 || n === 0)
|
|
83
|
+
return [];
|
|
84
|
+
if (m > DIFF_MAX_FILE_LINES || n > DIFF_MAX_FILE_LINES)
|
|
85
|
+
return [];
|
|
86
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
13
87
|
for (let i = 1; i <= m; i++) {
|
|
14
88
|
for (let j = 1; j <= n; j++) {
|
|
15
|
-
if (
|
|
89
|
+
if (lines1[i - 1] === lines2[j - 1]) {
|
|
16
90
|
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
17
91
|
}
|
|
18
92
|
else {
|
|
@@ -20,105 +94,21 @@ function lcs(a, b) {
|
|
|
20
94
|
}
|
|
21
95
|
}
|
|
22
96
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
export function generateUnifiedDiff(oldContent, newContent, contextLines = 3) {
|
|
30
|
-
const oldLines = oldContent.split('\n');
|
|
31
|
-
const newLines = newContent.split('\n');
|
|
32
|
-
// SAFETY: Fall back to simple diff for large files (LCS is O(n*m) memory)
|
|
33
|
-
if (oldLines.length > MAX_DIFF_LINES || newLines.length > MAX_DIFF_LINES) {
|
|
34
|
-
return generateSimpleDiff(oldContent, newContent);
|
|
35
|
-
}
|
|
36
|
-
const dp = lcs(oldLines, newLines);
|
|
37
|
-
const diff = [];
|
|
38
|
-
// Backtrack to find differences
|
|
39
|
-
let i = oldLines.length;
|
|
40
|
-
let j = newLines.length;
|
|
41
|
-
while (i > 0 || j > 0) {
|
|
42
|
-
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
43
|
-
diff.unshift({ type: 'context', content: oldLines[i - 1], lineNumber: i });
|
|
97
|
+
const result = [];
|
|
98
|
+
let i = m, j = n;
|
|
99
|
+
while (i > 0 && j > 0) {
|
|
100
|
+
if (lines1[i - 1] === lines2[j - 1]) {
|
|
101
|
+
result.unshift(lines1[i - 1]);
|
|
44
102
|
i--;
|
|
45
103
|
j--;
|
|
46
104
|
}
|
|
47
|
-
else if (
|
|
48
|
-
diff.unshift({ type: 'add', content: newLines[j - 1], lineNumber: j });
|
|
49
|
-
j--;
|
|
50
|
-
}
|
|
51
|
-
else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
|
|
52
|
-
diff.unshift({ type: 'remove', content: oldLines[i - 1], lineNumber: i });
|
|
105
|
+
else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
53
106
|
i--;
|
|
54
107
|
}
|
|
55
|
-
}
|
|
56
|
-
// Format output with context
|
|
57
|
-
const output = [];
|
|
58
|
-
let inHunk = false;
|
|
59
|
-
let contextCount = 0;
|
|
60
|
-
for (let idx = 0; idx < diff.length; idx++) {
|
|
61
|
-
const line = diff[idx];
|
|
62
|
-
if (line.type === 'context') {
|
|
63
|
-
if (inHunk) {
|
|
64
|
-
contextCount++;
|
|
65
|
-
if (contextCount <= contextLines) {
|
|
66
|
-
output.push(` ${line.content}`);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
inHunk = false;
|
|
70
|
-
contextCount = 0;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
// Check if change is coming
|
|
75
|
-
const hasChangeAhead = diff.slice(idx, idx + contextLines + 1)
|
|
76
|
-
.some(l => l.type !== 'context');
|
|
77
|
-
if (hasChangeAhead) {
|
|
78
|
-
output.push(` ${line.content}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
if (!inHunk) {
|
|
84
|
-
// Add context before change
|
|
85
|
-
const contextBefore = diff.slice(Math.max(0, idx - contextLines), idx)
|
|
86
|
-
.filter(l => l.type === 'context');
|
|
87
|
-
contextBefore.forEach(l => output.push(` ${l.content}`));
|
|
88
|
-
}
|
|
89
|
-
inHunk = true;
|
|
90
|
-
contextCount = 0;
|
|
91
|
-
if (line.type === 'add') {
|
|
92
|
-
output.push(`+ ${line.content}`);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
output.push(`- ${line.content}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return output.join('\n');
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Simple line-by-line diff (O(n) memory fallback for large files)
|
|
103
|
-
*/
|
|
104
|
-
export function generateSimpleDiff(oldContent, newContent) {
|
|
105
|
-
const oldLines = oldContent.split('\n');
|
|
106
|
-
const newLines = newContent.split('\n');
|
|
107
|
-
const diff = [];
|
|
108
|
-
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
109
|
-
for (let i = 0; i < maxLen; i++) {
|
|
110
|
-
const oldLine = oldLines[i];
|
|
111
|
-
const newLine = newLines[i];
|
|
112
|
-
if (oldLine === newLine) {
|
|
113
|
-
diff.push(` ${oldLine || ''}`);
|
|
114
|
-
}
|
|
115
108
|
else {
|
|
116
|
-
|
|
117
|
-
diff.push(`- ${oldLine}`);
|
|
118
|
-
if (newLine !== undefined)
|
|
119
|
-
diff.push(`+ ${newLine}`);
|
|
109
|
+
j--;
|
|
120
110
|
}
|
|
121
111
|
}
|
|
122
|
-
return
|
|
112
|
+
return result;
|
|
123
113
|
}
|
|
124
114
|
//# sourceMappingURL=diff.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/utils/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,63 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File Lock Manager - v3.
|
|
3
|
-
*
|
|
2
|
+
* File Lock Manager - v3.6.0
|
|
3
|
+
* Filesystem-based locks for multi-process safety
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Manages filesystem-based locks for multi-process safety.
|
|
7
|
+
* Locks are stored in OS temp directory to prevent conflicts.
|
|
4
8
|
*/
|
|
5
|
-
interface LockInfo {
|
|
6
|
-
clientId: string;
|
|
7
|
-
operation: string;
|
|
8
|
-
timestamp: number;
|
|
9
|
-
timeout: NodeJS.Timeout;
|
|
10
|
-
}
|
|
11
9
|
export declare class FileLockManager {
|
|
12
|
-
private
|
|
13
|
-
private
|
|
14
|
-
constructor(lockTimeoutMs?: number);
|
|
15
|
-
/**
|
|
16
|
-
* Acquire lock on file
|
|
17
|
-
*/
|
|
18
|
-
acquireLock(filePath: string, clientId: string, operation: string): Promise<{
|
|
19
|
-
acquired: boolean;
|
|
20
|
-
error?: string;
|
|
21
|
-
lockedBy?: string;
|
|
22
|
-
}>;
|
|
23
|
-
/**
|
|
24
|
-
* Release lock on file
|
|
25
|
-
*/
|
|
26
|
-
releaseLock(filePath: string, clientId: string): boolean;
|
|
10
|
+
private lockDir;
|
|
11
|
+
private activeLocks;
|
|
27
12
|
/**
|
|
28
|
-
*
|
|
13
|
+
* Creates a new FileLockManager instance.
|
|
14
|
+
* Initializes lock directory in OS temp with project-specific hash.
|
|
29
15
|
*/
|
|
30
|
-
|
|
16
|
+
constructor();
|
|
31
17
|
/**
|
|
32
|
-
*
|
|
18
|
+
* Acquires an exclusive lock on a file.
|
|
19
|
+
*
|
|
20
|
+
* @param filePath - Absolute path to file to lock
|
|
21
|
+
* @param timeoutMs - Stale lock timeout in milliseconds (default: 30000)
|
|
22
|
+
* @returns Release function to unlock the file
|
|
23
|
+
* @throws Error if lock cannot be acquired after retries
|
|
33
24
|
*/
|
|
34
|
-
|
|
25
|
+
acquireLock(filePath: string, timeoutMs?: number): Promise<() => Promise<void>>;
|
|
35
26
|
/**
|
|
36
|
-
*
|
|
27
|
+
* Checks if a file is currently locked.
|
|
28
|
+
*
|
|
29
|
+
* @param filePath - Absolute path to file
|
|
30
|
+
* @returns True if file is locked, false otherwise
|
|
37
31
|
*/
|
|
38
|
-
|
|
32
|
+
isLocked(filePath: string): Promise<boolean>;
|
|
39
33
|
/**
|
|
40
|
-
*
|
|
34
|
+
* Releases all active locks managed by this instance.
|
|
41
35
|
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get all active locks
|
|
45
|
-
*/
|
|
46
|
-
getActiveLocks(): Array<{
|
|
47
|
-
path: string;
|
|
48
|
-
info: LockInfo;
|
|
49
|
-
}>;
|
|
50
|
-
/**
|
|
51
|
-
* Clear all locks
|
|
52
|
-
*/
|
|
53
|
-
clearAll(): void;
|
|
54
|
-
/**
|
|
55
|
-
* Normalize path for consistent comparison
|
|
56
|
-
*/
|
|
57
|
-
private normalizePath;
|
|
36
|
+
releaseAll(): Promise<void>;
|
|
58
37
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Global lock manager instance
|
|
61
|
-
*/
|
|
62
38
|
export declare const globalLockManager: FileLockManager;
|
|
63
|
-
export {};
|
|
@@ -1,123 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File Lock Manager - v3.
|
|
3
|
-
*
|
|
2
|
+
* File Lock Manager - v3.6.0
|
|
3
|
+
* Filesystem-based locks for multi-process safety
|
|
4
|
+
*/
|
|
5
|
+
import lockfile from 'proper-lockfile';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
import { mkdirSync, existsSync } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
import { LOCK_TIMEOUT_MS, LOCK_RETRY_COUNT, LOCK_RETRY_MIN_MS, LOCK_RETRY_MAX_MS, LOCK_RETRY_FACTOR, HASH_LENGTH } from './constants.js';
|
|
11
|
+
/**
|
|
12
|
+
* Manages filesystem-based locks for multi-process safety.
|
|
13
|
+
* Locks are stored in OS temp directory to prevent conflicts.
|
|
4
14
|
*/
|
|
5
15
|
export class FileLockManager {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
constructor(lockTimeoutMs = 30000) {
|
|
9
|
-
this.lockTimeout = lockTimeoutMs;
|
|
10
|
-
}
|
|
16
|
+
lockDir;
|
|
17
|
+
activeLocks = new Map();
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
19
|
+
* Creates a new FileLockManager instance.
|
|
20
|
+
* Initializes lock directory in OS temp with project-specific hash.
|
|
13
21
|
*/
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
22
|
+
constructor() {
|
|
23
|
+
const projectHash = crypto.createHash('md5')
|
|
24
|
+
.update(process.cwd())
|
|
25
|
+
.digest('hex')
|
|
26
|
+
.substring(0, HASH_LENGTH);
|
|
27
|
+
this.lockDir = path.join(tmpdir(), `mcp-locks-${projectHash}`);
|
|
28
|
+
if (!existsSync(this.lockDir)) {
|
|
29
|
+
mkdirSync(this.lockDir, { recursive: true });
|
|
23
30
|
}
|
|
24
|
-
// Create lock with auto-release timeout
|
|
25
|
-
const timeout = setTimeout(() => {
|
|
26
|
-
this.releaseLock(filePath, clientId);
|
|
27
|
-
console.warn(`⚠️ Lock auto-released for ${normalizedPath} (timeout)`);
|
|
28
|
-
}, this.lockTimeout);
|
|
29
|
-
this.locks.set(normalizedPath, {
|
|
30
|
-
clientId,
|
|
31
|
-
operation,
|
|
32
|
-
timestamp: Date.now(),
|
|
33
|
-
timeout,
|
|
34
|
-
});
|
|
35
|
-
return { acquired: true };
|
|
36
31
|
}
|
|
37
32
|
/**
|
|
38
|
-
*
|
|
33
|
+
* Acquires an exclusive lock on a file.
|
|
34
|
+
*
|
|
35
|
+
* @param filePath - Absolute path to file to lock
|
|
36
|
+
* @param timeoutMs - Stale lock timeout in milliseconds (default: 30000)
|
|
37
|
+
* @returns Release function to unlock the file
|
|
38
|
+
* @throws Error if lock cannot be acquired after retries
|
|
39
39
|
*/
|
|
40
|
-
|
|
41
|
-
const normalizedPath =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
async acquireLock(filePath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
41
|
+
const normalizedPath = path.resolve(filePath);
|
|
42
|
+
try {
|
|
43
|
+
const release = await lockfile.lock(normalizedPath, {
|
|
44
|
+
stale: timeoutMs,
|
|
45
|
+
retries: {
|
|
46
|
+
retries: LOCK_RETRY_COUNT,
|
|
47
|
+
minTimeout: LOCK_RETRY_MIN_MS,
|
|
48
|
+
maxTimeout: LOCK_RETRY_MAX_MS,
|
|
49
|
+
factor: LOCK_RETRY_FACTOR
|
|
50
|
+
},
|
|
51
|
+
lockfilePath: path.join(this.lockDir, `${crypto.createHash('md5').update(normalizedPath).digest('hex')}.lock`)
|
|
52
|
+
});
|
|
53
|
+
this.activeLocks.set(normalizedPath, release);
|
|
54
|
+
return async () => {
|
|
55
|
+
await release();
|
|
56
|
+
this.activeLocks.delete(normalizedPath);
|
|
57
|
+
};
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new Error(`Failed to acquire lock for "${filePath}": ${error.message || String(error)}`);
|
|
48
61
|
}
|
|
49
|
-
clearTimeout(lock.timeout);
|
|
50
|
-
this.locks.delete(normalizedPath);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Check if file is locked
|
|
55
|
-
*/
|
|
56
|
-
isLocked(filePath) {
|
|
57
|
-
const normalizedPath = this.normalizePath(filePath);
|
|
58
|
-
return this.locks.has(normalizedPath);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get lock info
|
|
62
|
-
*/
|
|
63
|
-
getLockInfo(filePath) {
|
|
64
|
-
const normalizedPath = this.normalizePath(filePath);
|
|
65
|
-
return this.locks.get(normalizedPath) || null;
|
|
66
62
|
}
|
|
67
63
|
/**
|
|
68
|
-
*
|
|
64
|
+
* Checks if a file is currently locked.
|
|
65
|
+
*
|
|
66
|
+
* @param filePath - Absolute path to file
|
|
67
|
+
* @returns True if file is locked, false otherwise
|
|
69
68
|
*/
|
|
70
|
-
|
|
71
|
-
const normalizedPath =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.locks.delete(normalizedPath);
|
|
76
|
-
return true;
|
|
69
|
+
async isLocked(filePath) {
|
|
70
|
+
const normalizedPath = path.resolve(filePath);
|
|
71
|
+
try {
|
|
72
|
+
const lockfilePath = path.join(this.lockDir, `${crypto.createHash('md5').update(normalizedPath).digest('hex')}.lock`);
|
|
73
|
+
return await lockfile.check(normalizedPath, { lockfilePath });
|
|
77
74
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Release all locks for client
|
|
82
|
-
*/
|
|
83
|
-
releaseAllForClient(clientId) {
|
|
84
|
-
let count = 0;
|
|
85
|
-
for (const [path, lock] of this.locks.entries()) {
|
|
86
|
-
if (lock.clientId === clientId) {
|
|
87
|
-
clearTimeout(lock.timeout);
|
|
88
|
-
this.locks.delete(path);
|
|
89
|
-
count++;
|
|
90
|
-
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
91
77
|
}
|
|
92
|
-
return count;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Get all active locks
|
|
96
|
-
*/
|
|
97
|
-
getActiveLocks() {
|
|
98
|
-
return Array.from(this.locks.entries()).map(([path, info]) => ({
|
|
99
|
-
path,
|
|
100
|
-
info,
|
|
101
|
-
}));
|
|
102
78
|
}
|
|
103
79
|
/**
|
|
104
|
-
*
|
|
80
|
+
* Releases all active locks managed by this instance.
|
|
105
81
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.locks.clear();
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Normalize path for consistent comparison
|
|
114
|
-
*/
|
|
115
|
-
normalizePath(filePath) {
|
|
116
|
-
return filePath.replace(/\\/g, '/').toLowerCase();
|
|
82
|
+
async releaseAll() {
|
|
83
|
+
const releases = Array.from(this.activeLocks.values());
|
|
84
|
+
await Promise.all(releases.map(release => release()));
|
|
85
|
+
this.activeLocks.clear();
|
|
117
86
|
}
|
|
118
87
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Global lock manager instance
|
|
121
|
-
*/
|
|
122
88
|
export const globalLockManager = new FileLockManager();
|
|
123
89
|
//# sourceMappingURL=fileLock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileLock.js","sourceRoot":"","sources":["../../../src/utils/fileLock.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"fileLock.js","sourceRoot":"","sources":["../../../src/utils/fileLock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Walker Utility - v3.6.0
|
|
3
|
+
* Centralized directory traversal with filtering
|
|
4
|
+
*/
|
|
5
|
+
export interface WalkOptions {
|
|
6
|
+
/** Extensions to include (e.g., [".ts", ".js"]) */
|
|
7
|
+
extensions?: readonly string[];
|
|
8
|
+
/** Directories to exclude (e.g., ["node_modules", ".git"]) */
|
|
9
|
+
excludeDirs?: readonly string[];
|
|
10
|
+
/** Maximum files to collect (prevents OOM) */
|
|
11
|
+
maxFiles?: number;
|
|
12
|
+
/** Maximum total size in bytes (prevents OOM) */
|
|
13
|
+
maxSize?: number;
|
|
14
|
+
/** Callback for each file found */
|
|
15
|
+
onFile?: (filePath: string) => Promise<void> | void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively walks a directory tree with filtering options.
|
|
19
|
+
* Supports extension filtering, directory exclusion, and size/count limits.
|
|
20
|
+
*
|
|
21
|
+
* @param dir - Root directory to walk
|
|
22
|
+
* @param options - Filtering and callback options
|
|
23
|
+
* @param state - Internal state for tracking counts (do not pass manually)
|
|
24
|
+
*/
|
|
25
|
+
export declare function walkDir(dir: string, options?: WalkOptions, state?: {
|
|
26
|
+
fileCount: number;
|
|
27
|
+
totalSize: number;
|
|
28
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Walker Utility - v3.6.0
|
|
3
|
+
* Centralized directory traversal with filtering
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs/promises";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
/**
|
|
8
|
+
* Recursively walks a directory tree with filtering options.
|
|
9
|
+
* Supports extension filtering, directory exclusion, and size/count limits.
|
|
10
|
+
*
|
|
11
|
+
* @param dir - Root directory to walk
|
|
12
|
+
* @param options - Filtering and callback options
|
|
13
|
+
* @param state - Internal state for tracking counts (do not pass manually)
|
|
14
|
+
*/
|
|
15
|
+
export async function walkDir(dir, options = {}, state = { fileCount: 0, totalSize: 0 }) {
|
|
16
|
+
// Check limits
|
|
17
|
+
if (options.maxFiles && state.fileCount >= options.maxFiles)
|
|
18
|
+
return;
|
|
19
|
+
if (options.maxSize && state.totalSize >= options.maxSize)
|
|
20
|
+
return;
|
|
21
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
// Check limits again in loop
|
|
24
|
+
if (options.maxFiles && state.fileCount >= options.maxFiles)
|
|
25
|
+
break;
|
|
26
|
+
if (options.maxSize && state.totalSize >= options.maxSize)
|
|
27
|
+
break;
|
|
28
|
+
const fullPath = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
// Recurse if not excluded
|
|
31
|
+
if (!options.excludeDirs || !options.excludeDirs.includes(entry.name)) {
|
|
32
|
+
await walkDir(fullPath, options, state);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (entry.isFile()) {
|
|
36
|
+
// Check extension filter
|
|
37
|
+
if (options.extensions) {
|
|
38
|
+
const ext = path.extname(entry.name);
|
|
39
|
+
if (!options.extensions.includes(ext))
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Update size tracking
|
|
43
|
+
if (options.maxSize) {
|
|
44
|
+
const stat = await fs.stat(fullPath);
|
|
45
|
+
if (state.totalSize + stat.size > options.maxSize)
|
|
46
|
+
break;
|
|
47
|
+
state.totalSize += stat.size;
|
|
48
|
+
}
|
|
49
|
+
// Increment count and invoke callback
|
|
50
|
+
state.fileCount++;
|
|
51
|
+
if (options.onFile) {
|
|
52
|
+
await options.onFile(fullPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=fileWalker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileWalker.js","sourceRoot":"","sources":["../../../src/utils/fileWalker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAelC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAW,EACX,UAAuB,EAAE,EACzB,QAAkD,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;IAEhF,eAAe;IACf,IAAI,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ;QAAE,OAAO;IACpE,IAAI,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO;IAElE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,6BAA6B;QAC7B,IAAI,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ;YAAE,MAAM;QACnE,IAAI,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO;YAAE,MAAM;QAEjE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,0BAA0B;YAC1B,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtE,MAAM,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,yBAAyB;YACzB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACvB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,SAAS;YAClD,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACrC,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO;oBAAE,MAAM;gBACzD,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC;YAC/B,CAAC;YAED,sCAAsC;YACtC,KAAK,CAAC,SAAS,EAAE,CAAC;YAClB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACnB,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|