poof 3.0.0 → 3.1.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/dist/index.mjs CHANGED
@@ -1583,10 +1583,13 @@ const concurrentMap = async (items, concurrency, callback) => {
1583
1583
  await Promise.all(pending);
1584
1584
  };
1585
1585
 
1586
- const glob = async (root, globPattern, options) => {
1586
+ const glob = async (root, globPatterns, options) => {
1587
1587
  const includeDot = options?.dot ?? false;
1588
- const isMatch = picomatch(globPattern, { dot: includeDot });
1589
- const isRecursive = globPattern.includes("**");
1588
+ const ignorePatterns = options?.ignore;
1589
+ const isMatch = picomatch(globPatterns, { dot: includeDot });
1590
+ const shouldPrune = ignorePatterns?.length ? picomatch(ignorePatterns, { dot: true }) : void 0;
1591
+ const patterns = Array.isArray(globPatterns) ? globPatterns : [globPatterns];
1592
+ const isRecursive = patterns.some((p) => p.includes("**"));
1590
1593
  const results = [];
1591
1594
  const rootPrefix = root.length + 1;
1592
1595
  const crawl = async (directory) => {
@@ -1595,6 +1598,9 @@ const glob = async (root, globPattern, options) => {
1595
1598
  for (const entry of entries) {
1596
1599
  const fullPath = `${directory}/${entry.name}`;
1597
1600
  const relativePath = fullPath.slice(rootPrefix);
1601
+ if (shouldPrune?.(relativePath)) {
1602
+ continue;
1603
+ }
1598
1604
  if (isMatch(relativePath)) {
1599
1605
  results.push(fullPath);
1600
1606
  continue;
@@ -1632,10 +1638,14 @@ const validatePath = (target, cwd, dangerous) => {
1632
1638
  }
1633
1639
  }
1634
1640
  };
1635
- const resolvePatterns = async (patterns, cwd, dangerous = false) => {
1641
+ const needsDotMode = (globPattern) => /^\.[^\\/.]|[{,]\.[^\\/.]/.test(globPattern);
1642
+ const resolvePatterns = async (patterns, options) => {
1643
+ const { cwd, dangerous = false, ignore } = options;
1636
1644
  const files = [];
1637
1645
  const notFound = [];
1638
- await concurrentMap(patterns, GLOB_CONCURRENCY, async (pattern) => {
1646
+ const groups = /* @__PURE__ */ new Map();
1647
+ const explicitPaths = [];
1648
+ for (const pattern of patterns) {
1639
1649
  const posixPattern = toPosix(pattern);
1640
1650
  const fullPattern = path.isAbsolute(pattern) ? posixPattern : path.posix.join(toPosix(cwd), posixPattern);
1641
1651
  const scanned = picomatch.scan(fullPattern);
@@ -1643,30 +1653,57 @@ const resolvePatterns = async (patterns, cwd, dangerous = false) => {
1643
1653
  const pathToValidate = scanned.isGlob ? scanned.base || cwd : fullPattern;
1644
1654
  validatePath(pathToValidate, cwd, dangerous);
1645
1655
  if (!scanned.isGlob) {
1646
- await fs.access(fullPattern).then(
1647
- () => {
1648
- debug$2(`explicit path exists: ${fullPattern}`);
1649
- files.push(fullPattern);
1650
- },
1651
- () => {
1652
- debug$2(`explicit path not found: ${pattern}`);
1653
- notFound.push(pattern);
1654
- }
1655
- );
1656
- return;
1656
+ explicitPaths.push({
1657
+ fullPath: fullPattern,
1658
+ originalPattern: pattern
1659
+ });
1660
+ continue;
1657
1661
  }
1658
1662
  let root = scanned.base || toPosix(cwd);
1659
1663
  if (root.endsWith("/")) {
1660
1664
  root = root.slice(0, -1);
1661
1665
  }
1662
- const descendIntoDotDirectories = /^\.[^\\/.]|[{,]\.[^\\/.]/.test(scanned.glob);
1663
- const globStart = performance.now();
1664
- const matches = await glob(root, scanned.glob, { dot: descendIntoDotDirectories });
1665
- debug$2(`glob pattern=${pattern} files=${matches.length} time=${(performance.now() - globStart).toFixed(2)}ms`);
1666
- for (const match of matches) {
1667
- files.push(match);
1666
+ const group = groups.get(root);
1667
+ if (group) {
1668
+ group.globs.push(scanned.glob);
1669
+ group.needsDot = group.needsDot || needsDotMode(scanned.glob);
1670
+ } else {
1671
+ groups.set(root, {
1672
+ globs: [scanned.glob],
1673
+ needsDot: needsDotMode(scanned.glob)
1674
+ });
1668
1675
  }
1669
- });
1676
+ }
1677
+ await Promise.all([
1678
+ // Check explicit paths concurrently
1679
+ concurrentMap(explicitPaths, GLOB_CONCURRENCY, async ({ fullPath, originalPattern }) => {
1680
+ await fs.access(fullPath).then(
1681
+ () => {
1682
+ debug$2(`explicit path exists: ${fullPath}`);
1683
+ files.push(fullPath);
1684
+ },
1685
+ () => {
1686
+ debug$2(`explicit path not found: ${originalPattern}`);
1687
+ notFound.push(originalPattern);
1688
+ }
1689
+ );
1690
+ }),
1691
+ // Walk globs concurrently (one walk per unique root)
1692
+ concurrentMap([...groups.keys()], GLOB_CONCURRENCY, async (root) => {
1693
+ const group = groups.get(root);
1694
+ const globStart = performance.now();
1695
+ const matches = await glob(root, group.globs, {
1696
+ dot: group.needsDot,
1697
+ ignore
1698
+ });
1699
+ debug$2(
1700
+ `glob root=${root} patterns=${group.globs.length} matches=${matches.length} time=${(performance.now() - globStart).toFixed(2)}ms`
1701
+ );
1702
+ for (const match of matches) {
1703
+ files.push(match);
1704
+ }
1705
+ })
1706
+ ]);
1670
1707
  return {
1671
1708
  files,
1672
1709
  notFound
@@ -1680,7 +1717,9 @@ const startRmWorker = () => {
1680
1717
  const child = spawn(process.execPath, [rmWorkerPath], {
1681
1718
  detached: true,
1682
1719
  stdio: ["pipe", "ignore", "ignore"],
1683
- windowsHide: true
1720
+ windowsHide: true,
1721
+ cwd: "/"
1722
+ // Don't hold reference to parent's cwd (allows directory deletion on Windows)
1684
1723
  });
1685
1724
  const stdin = child.stdin;
1686
1725
  child.unref();
@@ -1745,14 +1784,35 @@ const withRetry = async (operation, shouldRetry) => {
1745
1784
 
1746
1785
  const debug = createDebug("poof:rename");
1747
1786
  const RENAME_CONCURRENCY = 100;
1787
+ const filterNestedPaths = (paths) => {
1788
+ if (paths.length === 0) {
1789
+ return [];
1790
+ }
1791
+ const sorted = paths.slice().sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1792
+ const roots = [];
1793
+ let lastAccepted;
1794
+ for (const filePath of sorted) {
1795
+ if (lastAccepted && filePath.startsWith(`${lastAccepted}/`)) {
1796
+ continue;
1797
+ }
1798
+ roots.push(filePath);
1799
+ lastAccepted = filePath;
1800
+ }
1801
+ return roots;
1802
+ };
1748
1803
  const poof = async (patterns, options) => {
1749
1804
  const patternArray = Array.isArray(patterns) ? patterns : [patterns];
1750
1805
  const cwd = options?.cwd ?? process.cwd();
1751
1806
  debug(`patterns: ${JSON.stringify(patternArray)}, cwd: ${cwd}`);
1752
1807
  const resolveStart = performance.now();
1753
- const { files, notFound } = await resolvePatterns(patternArray, cwd, options?.dangerous ?? false);
1808
+ const { files, notFound } = await resolvePatterns(patternArray, {
1809
+ cwd,
1810
+ dangerous: options?.dangerous,
1811
+ ignore: options?.ignore
1812
+ });
1754
1813
  debug(`resolve files=${files.length} time=${(performance.now() - resolveStart).toFixed(2)}ms`);
1755
- const filesToDelete = files;
1814
+ const filesToDelete = filterNestedPaths(files);
1815
+ debug(`filtered ${files.length} -> ${filesToDelete.length} (removed ${files.length - filesToDelete.length} nested)`);
1756
1816
  const errors = notFound.map((pattern) => {
1757
1817
  const error = new Error(`Path not found: ${pattern}`);
1758
1818
  error.code = "ENOENT";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poof",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Fast, non-blocking rm -rf alternative. Deletes files instantly while cleanup runs in the background.",
5
5
  "keywords": [
6
6
  "rm",
@@ -11,6 +11,11 @@
11
11
  "cli"
12
12
  ],
13
13
  "license": "MIT",
14
+ "repository": "privatenumber/poof",
15
+ "author": {
16
+ "name": "Hiroki Osame",
17
+ "email": "hiroki.osame@gmail.com"
18
+ },
14
19
  "files": [
15
20
  "dist"
16
21
  ],