node icon indicating copy to clipboard operation
node copied to clipboard

fs.glob add option to only include files (not directories) in the results entries

Open theoludwig opened this issue 10 months ago • 4 comments

What is the problem this feature will solve?

Since Node.js v22, with this PR: https://github.com/nodejs/node/pull/51912, it is possible to use fs.promises.glob for matching file paths based on specified patterns.

However, the results of entries also includes directories, but other famous userland library (e.g: globby) only returns files (not directories).

Example

With a file structure like the following:

$ mkdir -p foo/bar && touch foo/bar.md
$ tree foo
foo
├── bar
└── bar.md

2 directories, 1 file

And the following code:

import fs from "node:fs"
import { globby } from "globby"

console.log("fs.glob", await Array.fromAsync(fs.promises.glob("foo/**")))
console.log("globby", await globby("foo/**"))

It prints:

fs.glob [ 'foo', 'foo/bar.md', 'foo/bar' ]
globby [ 'foo/bar.md' ]

What is the feature you are proposing to solve the problem?

Add 2 options to fs.glob:

Both default to false, to keep same behavior, so no breaking changes is introduced.

Options based on the fast-glob library which globby uses under the hood.

What alternatives have you considered?

export const globby = async (patterns) => {
  const files = []
  for await (const entry of fs.promises.glob(patterns)) {
    const stats = await fs.promises.stat(entry)
    if (stats.isFile()) {
      files.push(entry)
    }
  }
  return files
}

theoludwig avatar Apr 29 '24 18:04 theoludwig

@MoLow

benjamingr avatar Apr 29 '24 18:04 benjamingr

Maybe just expose the dirent insstead of its name to exclude which would enable:

fs.promises.glob("foo/**", { exclude(entry) { return entry.isFile(); })

benjamingr avatar Apr 29 '24 18:04 benjamingr

Maybe just expose the dirent insstead of its name to exclude which would enable:

fs.promises.glob("foo/**", { exclude(entry) { return entry.isFile(); })

You mean exclude take as argument (entry) the await fs.promises.stat(entry)? That would work, fs.promises.glob("foo/**", { exclude(entry) { return entry.isDirectory(); }) to only get files. However, that would be a BREAKING CHANGE, probably fine, as it's still experimental, but still worth to point out.

But then we don't have information about filename/path. Maybe the argument in exclude should be an object with multiple information about the file/directory (name, relativePath, absolutePath, stats, etc.).

theoludwig avatar Apr 29 '24 19:04 theoludwig

we should probably just add a withFileTypes option like fs.readDir has

MoLow avatar Apr 30 '24 07:04 MoLow

If I want to return strings (because they're relative to the cwd) and ensure they are all files, then is the code

for (const dirent of fs.globSync('**', { cwd: path, withFileTypes: true })) {
    if (dirent.isFile()) {
        const name = path.relative(this.path, path.resolve(dirent.parentPath, dirent.name));
        dict[name] = {};
     }
}

a bit too complicated?

LiST-GIT avatar Aug 30 '24 11:08 LiST-GIT