tri-cli 0.0.53 → 0.0.55
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/dist/cli.js +46 -80
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -73,33 +73,19 @@ const getDirSizeAsync = async dirPath => {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
// Fast file counting
|
|
76
|
+
// Fast file counting using shell command
|
|
77
77
|
const countFilesAsync = async dirPath => {
|
|
78
78
|
try {
|
|
79
79
|
const stats = fs.statSync(dirPath);
|
|
80
80
|
if (!stats.isDirectory()) return 1;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (entry.name.startsWith('.')) continue;
|
|
90
|
-
const fullPath = path.join(dir, entry.name);
|
|
91
|
-
if (entry.isFile()) {
|
|
92
|
-
count++;
|
|
93
|
-
} else if (entry.isDirectory()) {
|
|
94
|
-
await countRecursive(fullPath);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// Skip directories we can't read
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
await countRecursive(dirPath);
|
|
102
|
-
return count;
|
|
81
|
+
|
|
82
|
+
// Use find command for fast counting, excluding hidden files
|
|
83
|
+
const escapedPath = dirPath.replace(/"/g, '\\"');
|
|
84
|
+
const {
|
|
85
|
+
stdout
|
|
86
|
+
} = await execAsync(`find "${escapedPath}" -type f ! -path '*/.*' 2>/dev/null | wc -l`);
|
|
87
|
+
const count = parseInt(stdout.trim(), 10);
|
|
88
|
+
return isNaN(count) ? 0 : count;
|
|
103
89
|
} catch {
|
|
104
90
|
return 0;
|
|
105
91
|
}
|
|
@@ -279,13 +265,18 @@ const TreeRow = /*#__PURE__*/React.memo(({
|
|
|
279
265
|
color: oneHunter.purple
|
|
280
266
|
}, " [", formatCount(fileCount), " files]"), isLoading && /*#__PURE__*/React.createElement(Text, {
|
|
281
267
|
color: oneHunter.yellow
|
|
282
|
-
}, " ", SPINNER_FRAMES[spinnerFrame])), isLoading ? /*#__PURE__*/React.createElement(Text, {
|
|
268
|
+
}, " ", SPINNER_FRAMES[spinnerFrame])), isLoading && fileCount === undefined ? /*#__PURE__*/React.createElement(Text, {
|
|
283
269
|
color: oneHunter.yellow
|
|
284
270
|
}, " ", SPINNER_FRAMES[spinnerFrame]) : displaySize > 0 ? /*#__PURE__*/React.createElement(Text, {
|
|
285
271
|
color: oneHunter.cyan
|
|
286
272
|
}, " ", formatBytes(displaySize)) : /*#__PURE__*/React.createElement(Text, null, " "), renderShortcut(), lastModified && /*#__PURE__*/React.createElement(Text, {
|
|
287
273
|
color: oneHunter.text2
|
|
288
274
|
}, " ", formatDate(lastModified))));
|
|
275
|
+
},
|
|
276
|
+
// Custom comparison function to prevent unnecessary re-renders
|
|
277
|
+
(prevProps, nextProps) => {
|
|
278
|
+
// Only re-render if these specific props change
|
|
279
|
+
return prevProps.node.data.path === nextProps.node.data.path && prevProps.isSelected === nextProps.isSelected && prevProps.collapsed.has(prevProps.node.data.path) === nextProps.collapsed.has(nextProps.node.data.path) && prevProps.shortcutInput === nextProps.shortcutInput && prevProps.calculatedSizes.get(prevProps.node.data.path) === nextProps.calculatedSizes.get(nextProps.node.data.path) && prevProps.calculatedCounts.get(prevProps.node.data.path) === nextProps.calculatedCounts.get(nextProps.node.data.path) && prevProps.loadingPaths.has(prevProps.node.data.path) === nextProps.loadingPaths.has(nextProps.node.data.path) && prevProps.lastModifiedDates.get(prevProps.node.data.path) === nextProps.lastModifiedDates.get(nextProps.node.data.path) && prevProps.spinnerFrame === nextProps.spinnerFrame;
|
|
289
280
|
});
|
|
290
281
|
const TreeVisualization = ({
|
|
291
282
|
dirPath = '.',
|
|
@@ -451,64 +442,39 @@ const TreeVisualization = ({
|
|
|
451
442
|
collectNodes(node);
|
|
452
443
|
if (nodesToCount.length === 0) return; // Nothing to count
|
|
453
444
|
|
|
445
|
+
// Sort nodes by depth (shallowest first) for top-down display
|
|
446
|
+
nodesToCount.sort((a, b) => a.depth - b.depth);
|
|
447
|
+
|
|
454
448
|
// Mark all nodes as loading
|
|
455
|
-
|
|
456
|
-
setLoadingPaths(prev => new Set([...prev, ...pathsToLoad]));
|
|
457
|
-
const newMap = new Map(calculatedCounts);
|
|
458
|
-
|
|
459
|
-
// Count and stream updates for each node individually
|
|
460
|
-
for (const targetNode of nodesToCount) {
|
|
461
|
-
// For directories, stream partial counts as we count
|
|
462
|
-
if (!targetNode.data.isFile) {
|
|
463
|
-
let runningCount = 0;
|
|
464
|
-
let updateCounter = 0;
|
|
465
|
-
const countRecursive = async dir => {
|
|
466
|
-
try {
|
|
467
|
-
const entries = fs.readdirSync(dir, {
|
|
468
|
-
withFileTypes: true
|
|
469
|
-
});
|
|
470
|
-
for (const entry of entries) {
|
|
471
|
-
// Skip hidden files
|
|
472
|
-
if (entry.name.startsWith('.')) continue;
|
|
473
|
-
const fullPath = path.join(dir, entry.name);
|
|
474
|
-
if (entry.isFile()) {
|
|
475
|
-
runningCount++;
|
|
476
|
-
updateCounter++;
|
|
477
|
-
// Stream update every 100 files for better performance
|
|
478
|
-
if (updateCounter >= 100) {
|
|
479
|
-
updateCounter = 0;
|
|
480
|
-
newMap.set(targetNode.data.path, runningCount);
|
|
481
|
-
React.startTransition(() => {
|
|
482
|
-
setCalculatedCounts(new Map(newMap));
|
|
483
|
-
});
|
|
484
|
-
await new Promise(r => setTimeout(r, 0));
|
|
485
|
-
}
|
|
486
|
-
} else if (entry.isDirectory()) {
|
|
487
|
-
await countRecursive(fullPath);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
} catch {
|
|
491
|
-
// Skip directories we can't read
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
await countRecursive(targetNode.data.path);
|
|
495
|
-
newMap.set(targetNode.data.path, runningCount);
|
|
496
|
-
} else {
|
|
497
|
-
// Files are always 1
|
|
498
|
-
newMap.set(targetNode.data.path, 1);
|
|
499
|
-
}
|
|
449
|
+
setLoadingPaths(new Set(nodesToCount.map(n => n.data.path)));
|
|
500
450
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
451
|
+
// Track completed counts
|
|
452
|
+
const completedCounts = new Map();
|
|
453
|
+
const completedPaths = new Set();
|
|
454
|
+
|
|
455
|
+
// Process all nodes in parallel
|
|
456
|
+
const promises = nodesToCount.map(async targetNode => {
|
|
457
|
+
const count = await countFilesAsync(targetNode.data.path);
|
|
458
|
+
|
|
459
|
+
// Store result
|
|
460
|
+
completedCounts.set(targetNode.data.path, count);
|
|
461
|
+
completedPaths.add(targetNode.data.path);
|
|
462
|
+
|
|
463
|
+
// Batch update state (React will batch these automatically)
|
|
464
|
+
setCalculatedCounts(prev => {
|
|
465
|
+
const next = new Map(prev);
|
|
466
|
+
next.set(targetNode.data.path, count);
|
|
467
|
+
return next;
|
|
509
468
|
});
|
|
510
|
-
|
|
511
|
-
|
|
469
|
+
setLoadingPaths(prev => {
|
|
470
|
+
const next = new Set(prev);
|
|
471
|
+
next.delete(targetNode.data.path);
|
|
472
|
+
return next;
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Wait for all counts to complete
|
|
477
|
+
await Promise.all(promises);
|
|
512
478
|
};
|
|
513
479
|
const getLastModifiedAsync = async node => {
|
|
514
480
|
// Collect node and all its descendants
|
|
@@ -834,7 +800,7 @@ const TreeVisualization = ({
|
|
|
834
800
|
}, "\u2191/\u2193: Navigate | Enter: Expand/Traverse | c: Count | d: Size | l: Date | m: Map | Shift+C: CountMap | q: Quit"), loadingPaths.size > 0 && /*#__PURE__*/React.createElement(Text, {
|
|
835
801
|
color: oneHunter.yellow
|
|
836
802
|
}, ' ', SPINNER_FRAMES[spinnerFrame], " Loading ", loadingPaths.size, "...")), viewportNodes.map((node, i) => /*#__PURE__*/React.createElement(TreeRow, {
|
|
837
|
-
key:
|
|
803
|
+
key: node.data.path,
|
|
838
804
|
node: node,
|
|
839
805
|
isSelected: start + i === selectedIndex,
|
|
840
806
|
collapsed: collapsed,
|