From 89949e5dc2d09859b161c9beba6874cc7fc2ec54 Mon Sep 17 00:00:00 2001 From: Yann Armelin Date: Sun, 7 Apr 2024 21:36:31 +0200 Subject: [PATCH 1/2] Migrate to typescript + Bump minimatch + Add ESM support --- .github/workflows/test.yml | 2 + .gitignore | 1 + README.md | 4 +- package-lock.json | 49 +++++++-- package.json | 28 ++++- src/index.cjs.ts | 3 + index.js => src/index.ts | 206 ++++++++++++++++++++++++++++--------- tsconfig.cjs.json | 11 ++ tsconfig.esm.json | 11 ++ tsconfig.json | 12 +++ 10 files changed, 267 insertions(+), 60 deletions(-) create mode 100644 src/index.cjs.ts rename index.js => src/index.ts (50%) create mode 100644 tsconfig.cjs.json create mode 100644 tsconfig.esm.json create mode 100644 tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 650163d..752ff31 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm ci + - run: npm run build - run: npm test coverage: runs-on: ubuntu-latest @@ -26,6 +27,7 @@ jobs: with: node-version: 20.x - run: npm ci + - run: npm run build - run: npm test - uses: coverallsapp/github-action@master with: diff --git a/.gitignore b/.gitignore index 941a46f..84cd256 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .*.swp +/dist/ /node_modules/* /v8.log /profile.txt diff --git a/README.md b/README.md index 103ebfd..150ab59 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Recursive version of fs.readdir wih stream API and glob filtering. Uses the `minimatch` library to do its matching. -Requires Node.js 10.0 or later. +Requirements: +- 1.x.x requires Node.js 10.0 or later. +- 2.x.x requires Node.js 14.0 or later. ## Performances diff --git a/package-lock.json b/package-lock.json index 27213dd..5389759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,24 @@ { "name": "readdir-glob", - "version": "1.1.3", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "readdir-glob", - "version": "1.1.3", + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.1.0" + "minimatch": "^9.0.0" }, "devDependencies": { + "@types/node": "^20.12.5", "jasmine": "^4.6.0", "mkdirp": "^2.1.6", "nyc": "^15.1.0", "rimraf": "^3.0.2", - "tick": "0.0.6" + "tick": "0.0.6", + "typescript": "^5.4.4" } }, "node_modules/@ampproject/remapping": { @@ -400,6 +402,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@types/node": { + "version": "20.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", + "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -1258,14 +1269,17 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mkdirp": { @@ -1727,6 +1741,25 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", diff --git a/package.json b/package.json index 5f899a3..db66ecd 100644 --- a/package.json +++ b/package.json @@ -2,25 +2,42 @@ "author": "Yann Armelin", "name": "readdir-glob", "description": "Recursive fs.readdir with streaming API and glob filtering.", - "version": "1.1.3", + "version": "2.0.0", "homepage": "https://github.com/Yqnn/node-readdir-glob", "repository": { "type": "git", "url": "git://github.com/Yqnn/node-readdir-glob.git" }, - "main": "index.js", + "main": "./dist/cjs/index.cjs.js", "files": [ - "index.js" + "dist/**", + "src" ], + "funding": { + "url": "https://github.com/sponsors/yqnn" + }, + "exports": { + "import": { + "default": "./dist/esm/index.js", + "types": "./dist/esm/index.d.ts" + }, + "require": { + "default": "./dist/cjs/index.cjs.js", + "types": "./dist/cjs/index.cjs.d.ts" + } + }, + "types": "./dist/cjs/index.cjs.d.ts", "dependencies": { - "minimatch": "^5.1.0" + "minimatch": "^9.0.0" }, "devDependencies": { + "@types/node": "^20.12.5", "jasmine": "^4.6.0", "mkdirp": "^2.1.6", "nyc": "^15.1.0", "rimraf": "^3.0.2", - "tick": "0.0.6" + "tick": "0.0.6", + "typescript": "^5.4.4" }, "keywords": [ "recursive", @@ -36,6 +53,7 @@ "scripts": { "test": "npx nyc --reporter=lcov --reporter=text-summary --reporter=html jasmine --config=jasmine.json", "test-regen": "TEST_REGEN=1 jasmine --config=jasmine.json", + "build": "rimraf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json", "bench": "bash scripts/benchmark.sh", "profile": "bash scripts/profile.sh && cat profile.txt" }, diff --git a/src/index.cjs.ts b/src/index.cjs.ts new file mode 100644 index 0000000..d15981a --- /dev/null +++ b/src/index.cjs.ts @@ -0,0 +1,3 @@ +import {readdirGlob} from './index'; + +export = readdirGlob; diff --git a/index.js b/src/index.ts similarity index 50% rename from index.js rename to src/index.ts index 9808147..6b9495d 100644 --- a/index.js +++ b/src/index.ts @@ -1,11 +1,9 @@ -module.exports = readdirGlob; +import * as fs from 'fs'; +import { EventEmitter } from 'events'; +import { Minimatch } from 'minimatch'; +import { resolve } from 'path'; -const fs = require('fs'); -const { EventEmitter } = require('events'); -const { Minimatch } = require('minimatch'); -const { resolve } = require('path'); - -function readdir(dir, strict) { +function readdir(dir: fs.PathLike, strict: boolean): Promise { return new Promise((resolve, reject) => { fs.readdir(dir, {withFileTypes: true} ,(err, files) => { if(err) { @@ -34,7 +32,8 @@ function readdir(dir, strict) { }); }); } -function stat(file, followSymlinks) { + +function getStat(file: fs.PathLike, followSymlinks: boolean): Promise { return new Promise((resolve, reject) => { const statFunc = followSymlinks ? fs.stat : fs.lstat; statFunc(file, (err, stats) => { @@ -43,7 +42,7 @@ function stat(file, followSymlinks) { case 'ENOENT': if(followSymlinks) { // Fallback to lstat to handle broken links as files - resolve(stat(file, false)); + resolve(getStat(file, false)); } else { resolve(null); } @@ -59,10 +58,24 @@ function stat(file, followSymlinks) { }); } -async function* exploreWalkAsync(dir, path, followSymlinks, useStat, shouldSkip, strict) { +export type Stat = fs.Dirent | fs.Stats; +export type Match = { + relative: string, + absolute: string, + stat?: Stat +}; + +async function* exploreWalkAsync( + dir: string, + path: string, + followSymlinks: boolean, + useStat:boolean, + shouldSkip:(path: string) => boolean, + strict:boolean +) : AsyncGenerator { let files = await readdir(path + dir, strict); for(const file of files) { - let name = file.name; + let name: fs.Dirent | string = file.name; if(name === undefined) { // undefined file.name means the `withFileTypes` options is not supported by node // we have to call the stat function to know if file is directory or not. @@ -72,33 +85,112 @@ async function* exploreWalkAsync(dir, path, followSymlinks, useStat, shouldSkip, const filename = dir + '/' + name; const relative = filename.slice(1); // Remove the leading / const absolute = path + '/' + relative; - let stats = null; + let stat: Stat | null = null; if(useStat || followSymlinks) { - stats = await stat(absolute, followSymlinks); + stat = await getStat(absolute, followSymlinks); } - if(!stats && file.name !== undefined) { - stats = file; - } - if(stats === null) { - stats = { isDirectory: () => false }; + if(!stat && file.name !== undefined) { + stat = file; } - if(stats.isDirectory()) { + if(stat?.isDirectory()) { if(!shouldSkip(relative)) { - yield {relative, absolute, stats}; + yield {relative, absolute, stat}; yield* exploreWalkAsync(filename, path, followSymlinks, useStat, shouldSkip, false); } } else { - yield {relative, absolute, stats}; + yield {relative, absolute, stat : stat ?? undefined}; } } } -async function* explore(path, followSymlinks, useStat, shouldSkip) { + + +async function* explore( + path: string, + followSymlinks: boolean, + useStat: boolean, + shouldSkip: (path: string) => boolean +): AsyncGenerator { yield* exploreWalkAsync('', path, followSymlinks, useStat, shouldSkip, true); } -function readOptions(options) { +export type Options = { + /** + * Glob pattern or Array of Glob patterns to match the found files with. + * A file has to match at least one of the provided patterns to be returned. + */ + pattern?: string | string[]; + /** + * Allow pattern to match filenames starting with a period, even if the pattern + * does not explicitly have a period in that spot. + */ + dot?: boolean; + /** + * Disable `**` matching against multiple folder names. + */ + noglobstar?: boolean; + /** + * Perform a basename-only match if the pattern does not contain any slash + * characters. That is, `*.js` would be treated as equivalent to `**\/*.js`, + * matching all js files in all directories. + */ + matchBase?: boolean; + /** + * Perform a case-insensitive match. Note: on case-insensitive file systems, + * non-magic patterns will match by default, since `stat` and `readdir` will + * not raise errors. + */ + nocase?: boolean; + /** + * Glob pattern or Array of Glob patterns to exclude matches. If a file or a + * folder matches at least one of the provided patterns, it's not returned. + * It doesn't prevent files from folder content to be returned. Note: ignore + * patterns are always in dot:true mode. + */ + ignore?: string | string[]; + /** + * Glob pattern or Array of Glob patterns to exclude folders. + * If a folder matches one of the provided patterns, it's not returned, and + * it's not explored: this prevents any of its children to be returned. + * Note: skip patterns are always in dot:true mode. + */ + skip?: string | string[]; + /** + * Follow symlinked directories. Note that requires to stat _all_ results, + * and so reduces performance. + */ + follow?: boolean; + /** + * Set to true to stat _all_ results. This reduces performance. + */ + stat?: boolean; + /** + * Do not match directories, only files. + */ + nodir?: boolean; + /** + * Add a `/` character to directory matches. + */ + mark?: boolean; + /** + * When an unusual error is encountered when attempting to read a directory, + * a warning will be printed to stderr. Set the `silent` option to true to + * suppress these warnings. + */ + silent?: boolean; + /** + * Absolute paths will be returned instead of relative paths. + */ + absolute?: boolean; +}; + +type StrictOptions = Options & Required< + Omit +>; + + +function readOptions(options: Options) : StrictOptions { return { pattern: options.pattern, dot: !!options.dot, @@ -107,7 +199,6 @@ function readOptions(options) { nocase: !!options.nocase, ignore: options.ignore, skip: options.skip, - follow: !!options.follow, stat: !!options.stat, nodir: !!options.nodir, @@ -117,15 +208,34 @@ function readOptions(options) { }; } -class ReaddirGlob extends EventEmitter { - constructor(cwd, options, cb) { +export type Callback = (err: Error | null, matches?: readonly string[]) => void; + +export class ReaddirGlob extends EventEmitter<{ + match: [Match], + end: [], + error: [NodeJS.ErrnoException] +}> { + public options: StrictOptions; + + private matchers: Minimatch[]; + private ignoreMatchers: Minimatch[]; + private skipMatchers: Minimatch[]; + + public paused: boolean; + public aborted: boolean; + private inactive: boolean; + + private iterator: ReturnType; + private _matches: string[] | undefined; + + constructor(cwd: string, options?: Options | Callback, cb?: Callback) { super(); if(typeof options === 'function') { cb = options; - options = null; + options = undefined; } - this.options = readOptions(options || {}); + this.options = readOptions(options || {}); this.matchers = []; if(this.options.pattern) { @@ -163,32 +273,31 @@ class ReaddirGlob extends EventEmitter { if(cb) { this._matches = []; - this.on('match', match => this._matches.push(this.options.absolute ? match.absolute : match.relative)); - this.on('error', err => cb(err)); - this.on('end', () => cb(null, this._matches)); + this.on('match', match => this._matches?.push(this.options.absolute ? match.absolute : match.relative)); + this.on('error', err => cb?.(err)); + this.on('end', () => cb?.(null, this._matches)); } setTimeout( () => this._next(), 0); } - _shouldSkipDirectory(relative) { - //console.log(relative, this.skipMatchers.some(m => m.match(relative))); + private _shouldSkipDirectory(relative: string) { return this.skipMatchers.some(m => m.match(relative)); } - _fileMatches(relative, isDirectory) { + private _fileMatches(relative: string, isDirectory: boolean) { const file = relative + (isDirectory ? '/' : ''); return (this.matchers.length === 0 || this.matchers.some(m => m.match(file))) && !this.ignoreMatchers.some(m => m.match(file)) && (!this.options.nodir || !isDirectory); } - _next() { + private _next() { if(!this.paused && !this.aborted) { this.iterator.next() .then((obj)=> { if(!obj.done) { - const isDirectory = obj.value.stats.isDirectory(); + const isDirectory = obj.value.stat?.isDirectory() ?? false; if(this._fileMatches(obj.value.relative, isDirectory )) { let relative = obj.value.relative; let absolute = obj.value.absolute; @@ -197,17 +306,17 @@ class ReaddirGlob extends EventEmitter { absolute += '/'; } if(this.options.stat) { - this.emit('match', {relative, absolute, stat:obj.value.stats}); + this.emit('match', {relative, absolute, stat:obj.value.stat}); } else { this.emit('match', {relative, absolute}); } } - this._next(this.iterator); + this._next(); } else { this.emit('end'); } }) - .catch((err) => { + .catch((err: NodeJS.ErrnoException) => { this.abort(); this.emit('error', err); if(!err.code && !this.options.silent) { @@ -219,15 +328,15 @@ class ReaddirGlob extends EventEmitter { } } - abort() { + abort(): void { this.aborted = true; } - pause() { + pause(): void { this.paused = true; } - resume() { + resume(): void { this.paused = false; if(this.inactive) { this.inactive = false; @@ -236,8 +345,13 @@ class ReaddirGlob extends EventEmitter { } } - -function readdirGlob(pattern, options, cb) { - return new ReaddirGlob(pattern, options, cb); +interface readdirGlobInterface { + (pattern: string, options?: Options | Callback, cb?: Callback): ReaddirGlob; + ReaddirGlob: typeof ReaddirGlob; } -readdirGlob.ReaddirGlob = ReaddirGlob; \ No newline at end of file + +export const readdirGlob: readdirGlobInterface = (pattern: string, options?: Options | Callback, cb?: Callback) => + new ReaddirGlob(pattern, options, cb); + +readdirGlob.ReaddirGlob = ReaddirGlob; + diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..423990e --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "declarationDir": "dist/cjs", + "outDir": "dist/cjs" + }, + "files": ["src/index.cjs.ts"], + "exclude": ["node_modules", "test/**"] +} \ No newline at end of file diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..9b45809 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ES2020", + "declarationDir": "dist/esm", + "outDir": "dist/esm" + }, + "files": ["src/index.ts"], + "exclude": ["node_modules", "test/**"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c61f336 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "lib": ["es2018"], + "target": "es2018", + "strict": true, + "moduleResolution": "node10", + "declaration": true, + "noImplicitAny": true, + "sourceMap": true, + } +} \ No newline at end of file From 2eaa84713dc8511402f613526b7b45e803b453b0 Mon Sep 17 00:00:00 2001 From: Yann Armelin Date: Fri, 12 Apr 2024 23:05:13 +0200 Subject: [PATCH 2/2] Code cleanup --- src/index.ts | 46 +++++++++++---------------- test/withfiletype-unsupported.spec.js | 46 --------------------------- 2 files changed, 18 insertions(+), 74 deletions(-) delete mode 100644 test/withfiletype-unsupported.spec.js diff --git a/src/index.ts b/src/index.ts index 6b9495d..6dc946c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,7 @@ function readdir(dir: fs.PathLike, strict: boolean): Promise { } function getStat(file: fs.PathLike, followSymlinks: boolean): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const statFunc = followSymlinks ? fs.stat : fs.lstat; statFunc(file, (err, stats) => { if(err) { @@ -72,34 +72,24 @@ async function* exploreWalkAsync( useStat:boolean, shouldSkip:(path: string) => boolean, strict:boolean -) : AsyncGenerator { +) : AsyncGenerator> { let files = await readdir(path + dir, strict); for(const file of files) { - let name: fs.Dirent | string = file.name; - if(name === undefined) { - // undefined file.name means the `withFileTypes` options is not supported by node - // we have to call the stat function to know if file is directory or not. - name = file; - useStat = true; - } + let name: string = file.name; const filename = dir + '/' + name; const relative = filename.slice(1); // Remove the leading / const absolute = path + '/' + relative; - let stat: Stat | null = null; + let stat: Stat = file; if(useStat || followSymlinks) { - stat = await getStat(absolute, followSymlinks); + stat = await getStat(absolute, followSymlinks) ?? stat; } - if(!stat && file.name !== undefined) { - stat = file; - } - - if(stat?.isDirectory()) { + if(stat.isDirectory()) { if(!shouldSkip(relative)) { yield {relative, absolute, stat}; yield* exploreWalkAsync(filename, path, followSymlinks, useStat, shouldSkip, false); } } else { - yield {relative, absolute, stat : stat ?? undefined}; + yield {relative, absolute, stat}; } } } @@ -110,7 +100,7 @@ async function* explore( followSymlinks: boolean, useStat: boolean, shouldSkip: (path: string) => boolean -): AsyncGenerator { +): AsyncGenerator> { yield* exploreWalkAsync('', path, followSymlinks, useStat, shouldSkip, true); } @@ -226,9 +216,8 @@ export class ReaddirGlob extends EventEmitter<{ private inactive: boolean; private iterator: ReturnType; - private _matches: string[] | undefined; - constructor(cwd: string, options?: Options | Callback, cb?: Callback) { + constructor(cwd?: string, options?: Options | Callback, cb?: Callback) { super(); if(typeof options === 'function') { cb = options; @@ -272,13 +261,14 @@ export class ReaddirGlob extends EventEmitter<{ this.aborted = false; if(cb) { - this._matches = []; - this.on('match', match => this._matches?.push(this.options.absolute ? match.absolute : match.relative)); - this.on('error', err => cb?.(err)); - this.on('end', () => cb?.(null, this._matches)); + const nonNullCb = cb; + const matches: string[] = []; + this.on('match', match => matches.push(this.options.absolute ? match.absolute : match.relative)); + this.on('error', err => nonNullCb(err)); + this.on('end', () => nonNullCb(null, matches)); } - setTimeout( () => this._next(), 0); + setTimeout( () => this._next() ); } private _shouldSkipDirectory(relative: string) { @@ -297,7 +287,7 @@ export class ReaddirGlob extends EventEmitter<{ this.iterator.next() .then((obj)=> { if(!obj.done) { - const isDirectory = obj.value.stat?.isDirectory() ?? false; + const isDirectory = obj.value.stat.isDirectory(); if(this._fileMatches(obj.value.relative, isDirectory )) { let relative = obj.value.relative; let absolute = obj.value.absolute; @@ -346,11 +336,11 @@ export class ReaddirGlob extends EventEmitter<{ } interface readdirGlobInterface { - (pattern: string, options?: Options | Callback, cb?: Callback): ReaddirGlob; + (pattern?: string, options?: Options | Callback, cb?: Callback): ReaddirGlob; ReaddirGlob: typeof ReaddirGlob; } -export const readdirGlob: readdirGlobInterface = (pattern: string, options?: Options | Callback, cb?: Callback) => +export const readdirGlob: readdirGlobInterface = (pattern?: string, options?: Options | Callback, cb?: Callback) => new ReaddirGlob(pattern, options, cb); readdirGlob.ReaddirGlob = ReaddirGlob; diff --git a/test/withfiletype-unsupported.spec.js b/test/withfiletype-unsupported.spec.js deleted file mode 100644 index a93a770..0000000 --- a/test/withfiletype-unsupported.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -const fs = require('fs'); -const glob = require('../'); - - -describe('withfiletype-unsupported', () => { - - beforeEach(() => { - process.chdir(__dirname + '/fixtures'); - - const readdir = fs.readdir; - const lstat = fs.lstat; - - // Replace readdir function so that it behaves as if the withFileTypes - // option was not supported - spyOn(fs, 'readdir').and.callFake(function (p, opts, cb) { - readdir(p, opts, function(err, files) { - cb(err, files.map(f => f.name || f)); - }); - }); - - const badStatPaths = /\/c$/; - spyOn(fs, 'lstat').and.callFake(function (path, cb) { - // synthetically generate a non-ENOENT error - if (badStatPaths.test(path)) { - const er = new Error('synthetic'); - er.code = 'EPERM'; - return process.nextTick(cb.bind(null, er)); - } - return lstat.call(fs, path, cb); - }); - }); - - - it('withFileTypes option unsupported', done => { - glob('a/c', (er, res) => { - expect(er).toBeFalsy(); - res.sort(); - expect(res).toEqual([ - 'd', - 'd/c' // This folder cannot be explored because lstat returned an error on it - ]); - done(); - }); - }); - -}); \ No newline at end of file