(filePath, fileName)
| 314 | } else { |
| 315 | // Only queue model-like files (and zips when enabled). Queuing every file caused |
| 316 | // millions of async processFile() calls on large trees even when stat() was skipped. |
| 317 | reportTraversedFile(); |
| 318 | if (shouldQueueFile(entry.name)) { |
| 319 | queue.push({ type: 'file', path: fullPath, name: entry.name }); |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | } catch (err) { |
| 324 | console.error(`Skipping directory ${dirPath} due to error: ${err.message}`); |
| 325 | } |
| 326 | }; |
| 327 | |
| 328 | const processFile = async (filePath, fileName) => { |
| 329 | try { |
| 330 | // Check extension FIRST to avoid unnecessary stat() calls |
| 331 | const ext = path.extname(fileName).toLowerCase(); |
| 332 | |
| 333 | if (extSet.has(ext)) { |
| 334 | // Only call stat() for valid 3D model files |
| 335 | const stats = await fs.promises.stat(filePath); |
| 336 | if (stats.size <= maxFileSize) { |
| 337 | // Add file without hash calculation (calculate later if needed) |
| 338 | files.push({ |
| 339 | filePath, |
| 340 | fileName, |
| 341 | size: stats.size, |
| 342 | mtime: stats.mtime, |
| 343 | hash: null, // Calculate later if needed |
| 344 | isZipArchive: false |
| 345 | }); |
| 346 | } |
| 347 | } else if (enableZipArchives && ext === '.zip') { |
| 348 | // Scan inside ZIP using same scanExtensions |
| 349 | // ZIP files still need stat for size check |
| 350 | const stats = await fs.promises.stat(filePath); |
| 351 | if (stats.size <= maxFileSize) { |
no test coverage detected