@tb.p/dd 1.1.4 → 1.2.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.
@@ -48,7 +48,11 @@ async function performFileOperations(options, dbManager) {
48
48
  }
49
49
 
50
50
  // Convert map values back to array, maintaining priority order
51
- allFiles.push(...Array.from(fileMap.values()).sort((a, b) => a.priority - b.priority));
51
+ // Use a loop instead of apply/spread to avoid stack overflow with very large arrays
52
+ const sortedFiles = Array.from(fileMap.values()).sort((a, b) => a.priority - b.priority);
53
+ for (const file of sortedFiles) {
54
+ allFiles.push(file);
55
+ }
52
56
 
53
57
  // Store files in database
54
58
  let successCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tb.p/dd",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "A comprehensive command-line tool for finding and removing duplicate files using content-based hashing",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,6 +18,15 @@ class CandidateDetection {
18
18
  onProgress: null,
19
19
  ...options
20
20
  };
21
+
22
+ // Enforce minimum batch size to prevent stack overflow
23
+ // Batch sizes below 10 can cause excessive nested promises
24
+ if (this.options.batchSize < 10) {
25
+ if (this.options.verbose) {
26
+ console.log(`⚠️ Batch size ${this.options.batchSize} is too small, using minimum of 10 to prevent stack overflow`);
27
+ }
28
+ this.options.batchSize = 10;
29
+ }
21
30
  this.stats = {
22
31
  totalFiles: 0,
23
32
  uniqueFiles: 0,
@@ -19,7 +19,13 @@ async function scanDirectory(dirPath, options = {}) {
19
19
 
20
20
  try {
21
21
  const filePaths = await new Promise((resolve, reject) => {
22
+ // Add timeout to prevent hanging on very large directories
23
+ const timeout = setTimeout(() => {
24
+ reject(new Error('Directory scan timeout - directory may be too large or contain circular references'));
25
+ }, 300000); // 5 minute timeout
26
+
22
27
  recursive(dirPath, (err, files) => {
28
+ clearTimeout(timeout);
23
29
  if (err) reject(err);
24
30
  else resolve(files);
25
31
  });
@@ -38,6 +44,13 @@ async function scanDirectory(dirPath, options = {}) {
38
44
  async function getBatchFileInfo(filePaths, options = {}) {
39
45
  // OPTIMIZATION: Batch stat() operations with controlled concurrency to prevent stack overflow
40
46
  const BATCH_SIZE = 100; // Process files in chunks to prevent excessive Promise.all
47
+
48
+ // Safety check: prevent processing extremely large file lists that could cause stack overflow
49
+ if (filePaths.length > 100000) {
50
+ console.warn(`Warning: Very large file list detected (${filePaths.length} files). This may cause performance issues.`);
51
+ console.warn('Consider processing smaller directories or using file filters to reduce the scope.');
52
+ }
53
+
41
54
  const statResults = [];
42
55
 
43
56
  for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
@@ -53,7 +66,10 @@ async function getBatchFileInfo(filePaths, options = {}) {
53
66
  });
54
67
 
55
68
  const batchResults = await Promise.all(statPromises);
56
- statResults.push(...batchResults);
69
+ // Use a loop instead of apply/spread to avoid stack overflow with very large arrays
70
+ for (const result of batchResults) {
71
+ statResults.push(result);
72
+ }
57
73
  }
58
74
  const files = [];
59
75
 
@@ -120,7 +136,10 @@ async function scanDirectories(options = {}) {
120
136
  for (const dirPath of targets) {
121
137
  try {
122
138
  const files = await scanDirectory(dirPath, options);
123
- allFiles.push(...files);
139
+ // Use a loop instead of apply/spread to avoid stack overflow with very large arrays
140
+ for (const file of files) {
141
+ allFiles.push(file);
142
+ }
124
143
  } catch (error) {
125
144
  console.warn(`Warning: Failed to scan directory ${dirPath}: ${error.message}`);
126
145
  }
@@ -94,6 +94,30 @@ function validateOptions(options) {
94
94
  process.exit(1);
95
95
  }
96
96
  }
97
+
98
+ // Validate batch size to prevent stack overflow
99
+ if (options.batchSize) {
100
+ const batchSize = parseInt(options.batchSize);
101
+ if (isNaN(batchSize) || batchSize < 1) {
102
+ console.error('Error: Batch size must be a positive number');
103
+ process.exit(1);
104
+ } else if (batchSize < 10) {
105
+ console.warn(`Warning: Batch size ${batchSize} is very small and may cause performance issues or stack overflow. Recommended minimum: 10`);
106
+ } else if (batchSize > 1000) {
107
+ console.warn(`Warning: Batch size ${batchSize} is very large and may cause memory issues. Recommended maximum: 1000`);
108
+ }
109
+ }
110
+
111
+ // Validate max concurrency
112
+ if (options.maxConcurrency) {
113
+ const maxConcurrency = parseInt(options.maxConcurrency);
114
+ if (isNaN(maxConcurrency) || maxConcurrency < 1) {
115
+ console.error('Error: Max concurrency must be a positive number');
116
+ process.exit(1);
117
+ } else if (maxConcurrency > 50) {
118
+ console.warn(`Warning: Max concurrency ${maxConcurrency} is very high and may cause system instability. Recommended maximum: 50`);
119
+ }
120
+ }
97
121
  }
98
122
 
99
123
  export {