tiny-readdir 2.0.0 → 2.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.d.ts +1 -1
- package/dist/index.js +21 -6
- package/dist/types.d.ts +6 -5
- package/package.json +8 -10
- package/readme.md +1 -0
- package/src/index.ts +22 -8
- package/src/types.ts +1 -0
- package/test/{index.js → native.js} +68 -1
- package/test/yielding.js +35 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/* IMPORT */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import Limiter from 'promise-concurrency-limiter';
|
|
5
|
-
/* HELPERS */
|
|
6
|
-
const limiter = new Limiter({ concurrency: 500 });
|
|
7
4
|
/* MAIN */
|
|
8
5
|
const readdir = (rootPath, options) => {
|
|
9
6
|
const followSymlinks = options?.followSymlinks ?? false;
|
|
10
7
|
const maxDepth = options?.depth ?? Infinity;
|
|
8
|
+
const maxPaths = options?.limit ?? Infinity;
|
|
11
9
|
const isIgnored = options?.ignore ?? (() => false);
|
|
12
10
|
const signal = options?.signal ?? { aborted: false };
|
|
13
11
|
const directories = [];
|
|
@@ -17,19 +15,28 @@ const readdir = (rootPath, options) => {
|
|
|
17
15
|
const visited = new Set();
|
|
18
16
|
const resultEmpty = { directories: [], files: [], symlinks: [], map: {} };
|
|
19
17
|
const result = { directories, files, symlinks, map };
|
|
18
|
+
let foundPaths = 0;
|
|
20
19
|
const handleDirectory = (dirmap, subPath, depth) => {
|
|
21
20
|
if (visited.has(subPath))
|
|
22
21
|
return;
|
|
22
|
+
if (foundPaths >= maxPaths)
|
|
23
|
+
return;
|
|
24
|
+
foundPaths += 1;
|
|
23
25
|
dirmap.directories.push(subPath);
|
|
24
26
|
directories.push(subPath);
|
|
25
27
|
visited.add(subPath);
|
|
26
28
|
if (depth >= maxDepth)
|
|
27
29
|
return;
|
|
28
|
-
|
|
30
|
+
if (foundPaths >= maxPaths)
|
|
31
|
+
return;
|
|
32
|
+
return populateResultFromPath(subPath, depth + 1);
|
|
29
33
|
};
|
|
30
34
|
const handleFile = (dirmap, subPath) => {
|
|
31
35
|
if (visited.has(subPath))
|
|
32
36
|
return;
|
|
37
|
+
if (foundPaths >= maxPaths)
|
|
38
|
+
return;
|
|
39
|
+
foundPaths += 1;
|
|
33
40
|
dirmap.files.push(subPath);
|
|
34
41
|
files.push(subPath);
|
|
35
42
|
visited.add(subPath);
|
|
@@ -37,6 +44,9 @@ const readdir = (rootPath, options) => {
|
|
|
37
44
|
const handleSymlink = (dirmap, subPath, depth) => {
|
|
38
45
|
if (visited.has(subPath))
|
|
39
46
|
return;
|
|
47
|
+
if (foundPaths >= maxPaths)
|
|
48
|
+
return;
|
|
49
|
+
foundPaths += 1;
|
|
40
50
|
dirmap.symlinks.push(subPath);
|
|
41
51
|
symlinks.push(subPath);
|
|
42
52
|
visited.add(subPath);
|
|
@@ -44,7 +54,9 @@ const readdir = (rootPath, options) => {
|
|
|
44
54
|
return;
|
|
45
55
|
if (depth >= maxDepth)
|
|
46
56
|
return;
|
|
47
|
-
|
|
57
|
+
if (foundPaths >= maxPaths)
|
|
58
|
+
return;
|
|
59
|
+
return populateResultFromSymlink(subPath, depth + 1);
|
|
48
60
|
};
|
|
49
61
|
const handleStat = (dirmap, rootPath, stat, depth) => {
|
|
50
62
|
if (signal.aborted)
|
|
@@ -64,7 +76,8 @@ const readdir = (rootPath, options) => {
|
|
|
64
76
|
const handleDirent = (dirmap, rootPath, dirent, depth) => {
|
|
65
77
|
if (signal.aborted)
|
|
66
78
|
return;
|
|
67
|
-
const
|
|
79
|
+
const separator = (rootPath === path.sep) ? '' : path.sep;
|
|
80
|
+
const subPath = `${rootPath}${separator}${dirent.name}`;
|
|
68
81
|
if (isIgnored(subPath))
|
|
69
82
|
return;
|
|
70
83
|
if (dirent.isDirectory()) {
|
|
@@ -87,6 +100,8 @@ const readdir = (rootPath, options) => {
|
|
|
87
100
|
return;
|
|
88
101
|
if (depth > maxDepth)
|
|
89
102
|
return;
|
|
103
|
+
if (foundPaths >= maxPaths)
|
|
104
|
+
return;
|
|
90
105
|
const dirents = await fs.promises.readdir(rootPath, { withFileTypes: true }).catch(() => []);
|
|
91
106
|
if (signal.aborted)
|
|
92
107
|
return;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type Promisable<T> = Promise<T> | T;
|
|
2
|
+
type Options = {
|
|
3
3
|
depth?: number;
|
|
4
|
+
limit?: number;
|
|
4
5
|
followSymlinks?: boolean;
|
|
5
6
|
ignore?: (targetPath: string) => boolean;
|
|
6
7
|
signal?: {
|
|
7
8
|
aborted: boolean;
|
|
8
9
|
};
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
+
type ResultDirectory = {
|
|
11
12
|
directories: string[];
|
|
12
13
|
files: string[];
|
|
13
14
|
symlinks: string[];
|
|
14
15
|
};
|
|
15
|
-
|
|
16
|
+
type ResultDirectories = {
|
|
16
17
|
[path: string]: ResultDirectory;
|
|
17
18
|
};
|
|
18
|
-
|
|
19
|
+
type Result = ResultDirectory & {
|
|
19
20
|
map: ResultDirectories;
|
|
20
21
|
};
|
|
21
22
|
export type { Promisable, Options, ResultDirectory, ResultDirectories, Result };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "tiny-readdir",
|
|
3
3
|
"repository": "github:fabiospampinato/tiny-readdir",
|
|
4
4
|
"description": "A simple promisified recursive readdir function.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.1.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"exports": "./dist/index.js",
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"clean": "tsex clean",
|
|
12
12
|
"compile": "tsex compile",
|
|
13
13
|
"compile:watch": "tsex compile --watch",
|
|
14
|
-
"test": "
|
|
15
|
-
"test:
|
|
14
|
+
"test": "npm run test:native && npm run test:yielding",
|
|
15
|
+
"test:native": "fava '**/native.js'",
|
|
16
|
+
"test:yielding": "node test/yielding.js",
|
|
16
17
|
"prepublishOnly": "npm run clean && npm run compile && npm run test"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
@@ -22,13 +23,10 @@
|
|
|
22
23
|
"simple",
|
|
23
24
|
"tiny"
|
|
24
25
|
],
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"promise-concurrency-limiter": "^2.0.0"
|
|
27
|
-
},
|
|
28
26
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"fava": "^0.0.
|
|
31
|
-
"tsex": "^1.1.
|
|
32
|
-
"typescript": "^4.
|
|
27
|
+
"@types/node": "^18.11.9",
|
|
28
|
+
"fava": "^0.0.7",
|
|
29
|
+
"tsex": "^1.1.3",
|
|
30
|
+
"typescript": "^4.9.3"
|
|
33
31
|
}
|
|
34
32
|
}
|
package/readme.md
CHANGED
|
@@ -17,6 +17,7 @@ const aborter = new AbortController ();
|
|
|
17
17
|
|
|
18
18
|
const result = await readdir ( '/foo/bar', {
|
|
19
19
|
depth: 20, // Maximum depth to look at
|
|
20
|
+
limit: 1_000_000, // Maximum number of files explored, useful as a stop gap in some edge cases
|
|
20
21
|
followSymlinks: true, // Whether to follow symlinks or not
|
|
21
22
|
ignore: targetPath => /node_modules/.test ( targetPath ), // Function that if returns true will ignore this particular file or a directory and its descendants
|
|
22
23
|
signal: aborter.signal // Optional abort signal, useful for aborting potentially expensive operations
|
package/src/index.ts
CHANGED
|
@@ -3,19 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import Limiter from 'promise-concurrency-limiter';
|
|
7
6
|
import type {Promisable, Options, ResultDirectory, ResultDirectories, Result} from './types';
|
|
8
7
|
|
|
9
|
-
/* HELPERS */
|
|
10
|
-
|
|
11
|
-
const limiter = new Limiter ({ concurrency: 500 });
|
|
12
|
-
|
|
13
8
|
/* MAIN */
|
|
14
9
|
|
|
15
10
|
const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
16
11
|
|
|
17
12
|
const followSymlinks = options?.followSymlinks ?? false;
|
|
18
13
|
const maxDepth = options?.depth ?? Infinity;
|
|
14
|
+
const maxPaths = options?.limit ?? Infinity;
|
|
19
15
|
const isIgnored = options?.ignore ?? (() => false);
|
|
20
16
|
const signal = options?.signal ?? { aborted: false };
|
|
21
17
|
const directories: string[] = [];
|
|
@@ -26,17 +22,24 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
26
22
|
const resultEmpty: Result = { directories: [], files: [], symlinks: [], map: {} };
|
|
27
23
|
const result: Result = { directories, files, symlinks, map };
|
|
28
24
|
|
|
25
|
+
let foundPaths = 0;
|
|
26
|
+
|
|
29
27
|
const handleDirectory = ( dirmap: ResultDirectory, subPath: string, depth: number ): Promisable<void> => {
|
|
30
28
|
|
|
31
29
|
if ( visited.has ( subPath ) ) return;
|
|
32
30
|
|
|
31
|
+
if ( foundPaths >= maxPaths ) return;
|
|
32
|
+
|
|
33
|
+
foundPaths += 1;
|
|
33
34
|
dirmap.directories.push ( subPath );
|
|
34
35
|
directories.push ( subPath );
|
|
35
36
|
visited.add ( subPath );
|
|
36
37
|
|
|
37
38
|
if ( depth >= maxDepth ) return;
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
if ( foundPaths >= maxPaths ) return;
|
|
41
|
+
|
|
42
|
+
return populateResultFromPath ( subPath, depth + 1 );
|
|
40
43
|
|
|
41
44
|
};
|
|
42
45
|
|
|
@@ -44,6 +47,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
44
47
|
|
|
45
48
|
if ( visited.has ( subPath ) ) return;
|
|
46
49
|
|
|
50
|
+
if ( foundPaths >= maxPaths ) return;
|
|
51
|
+
|
|
52
|
+
foundPaths += 1;
|
|
47
53
|
dirmap.files.push ( subPath );
|
|
48
54
|
files.push ( subPath );
|
|
49
55
|
visited.add ( subPath );
|
|
@@ -54,6 +60,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
54
60
|
|
|
55
61
|
if ( visited.has ( subPath ) ) return;
|
|
56
62
|
|
|
63
|
+
if ( foundPaths >= maxPaths ) return;
|
|
64
|
+
|
|
65
|
+
foundPaths += 1;
|
|
57
66
|
dirmap.symlinks.push ( subPath );
|
|
58
67
|
symlinks.push ( subPath );
|
|
59
68
|
visited.add ( subPath );
|
|
@@ -62,7 +71,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
62
71
|
|
|
63
72
|
if ( depth >= maxDepth ) return;
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
if ( foundPaths >= maxPaths ) return;
|
|
75
|
+
|
|
76
|
+
return populateResultFromSymlink ( subPath, depth + 1 );
|
|
66
77
|
|
|
67
78
|
};
|
|
68
79
|
|
|
@@ -92,7 +103,8 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
92
103
|
|
|
93
104
|
if ( signal.aborted ) return;
|
|
94
105
|
|
|
95
|
-
const
|
|
106
|
+
const separator = ( rootPath === path.sep ) ? '' : path.sep;
|
|
107
|
+
const subPath = `${rootPath}${separator}${dirent.name}`;
|
|
96
108
|
|
|
97
109
|
if ( isIgnored ( subPath ) ) return;
|
|
98
110
|
|
|
@@ -128,6 +140,8 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
128
140
|
|
|
129
141
|
if ( depth > maxDepth ) return;
|
|
130
142
|
|
|
143
|
+
if ( foundPaths >= maxPaths ) return;
|
|
144
|
+
|
|
131
145
|
const dirents = await fs.promises.readdir ( rootPath, { withFileTypes: true } ).catch ( () => [] );
|
|
132
146
|
|
|
133
147
|
if ( signal.aborted ) return;
|
package/src/types.ts
CHANGED
|
@@ -10,7 +10,7 @@ import readdir from '../dist/index.js';
|
|
|
10
10
|
|
|
11
11
|
describe ( 'Tiny Readdir', it => {
|
|
12
12
|
|
|
13
|
-
it ( '
|
|
13
|
+
it ( 'finds folders, files and symlinks', async t => {
|
|
14
14
|
|
|
15
15
|
const cwdPath = process.cwd ();
|
|
16
16
|
const root1Path = path.join ( cwdPath, 'test', 'root1' );
|
|
@@ -97,4 +97,71 @@ describe ( 'Tiny Readdir', it => {
|
|
|
97
97
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
it ( 'supports a limit option', async t => {
|
|
101
|
+
|
|
102
|
+
const cwdPath = process.cwd ();
|
|
103
|
+
const root1Path = path.join ( cwdPath, 'test', 'root1' );
|
|
104
|
+
const root2Path = path.join ( cwdPath, 'test', 'root2' );
|
|
105
|
+
const folder1Path = path.join ( root1Path, 'folder1' );
|
|
106
|
+
const folder2Path = path.join ( root1Path, 'folder2' );
|
|
107
|
+
const folder1DeepPath = path.join ( folder1Path, 'deep' );
|
|
108
|
+
const file1aPath = path.join ( folder1Path, 'file1a.txt' );
|
|
109
|
+
const file1bPath = path.join ( folder1Path, 'file1b.txt' );
|
|
110
|
+
const file2Path = path.join ( folder2Path, 'file2.txt' );
|
|
111
|
+
const fileDeep1Path = path.join ( folder1DeepPath, 'file1.txt' );
|
|
112
|
+
const symlink1FromPath = path.join ( root1Path, 'symlink' );
|
|
113
|
+
const symlink1ToPath = root2Path;
|
|
114
|
+
const symlink2FromPath = path.join ( root2Path, 'symlink' );
|
|
115
|
+
const symlink2ToPath = root1Path;
|
|
116
|
+
|
|
117
|
+
fs.mkdirSync ( root1Path );
|
|
118
|
+
fs.mkdirSync ( root2Path );
|
|
119
|
+
fs.mkdirSync ( folder1Path );
|
|
120
|
+
fs.mkdirSync ( folder2Path );
|
|
121
|
+
fs.mkdirSync ( folder1DeepPath );
|
|
122
|
+
fs.writeFileSync ( file1aPath, '' );
|
|
123
|
+
fs.writeFileSync ( file1bPath, '' );
|
|
124
|
+
fs.writeFileSync ( file2Path, '' );
|
|
125
|
+
fs.writeFileSync ( fileDeep1Path, '' );
|
|
126
|
+
fs.symlinkSync ( symlink1ToPath, symlink1FromPath );
|
|
127
|
+
fs.symlinkSync ( symlink2ToPath, symlink2FromPath );
|
|
128
|
+
|
|
129
|
+
const expected = {
|
|
130
|
+
directories: [folder1Path, folder2Path],
|
|
131
|
+
files: [],
|
|
132
|
+
symlinks: [symlink1FromPath],
|
|
133
|
+
map: {
|
|
134
|
+
[root1Path]: {
|
|
135
|
+
directories: [folder1Path, folder2Path],
|
|
136
|
+
files: [],
|
|
137
|
+
symlinks: [symlink1FromPath]
|
|
138
|
+
},
|
|
139
|
+
[folder1Path]: {
|
|
140
|
+
directories: [],
|
|
141
|
+
files: [],
|
|
142
|
+
symlinks: []
|
|
143
|
+
},
|
|
144
|
+
[folder2Path]: {
|
|
145
|
+
directories: [],
|
|
146
|
+
files: [],
|
|
147
|
+
symlinks: []
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
|
|
154
|
+
const result = await readdir ( root1Path, { limit: 3, followSymlinks: true } );
|
|
155
|
+
|
|
156
|
+
t.deepEqual ( result, expected );
|
|
157
|
+
|
|
158
|
+
} finally {
|
|
159
|
+
|
|
160
|
+
fs.rmSync ( root1Path, { recursive: true } );
|
|
161
|
+
fs.rmSync ( root2Path, { recursive: true } );
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
});
|
|
166
|
+
|
|
100
167
|
});
|
package/test/yielding.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
|
|
4
|
+
import readdir from '../dist/index.js';
|
|
5
|
+
|
|
6
|
+
/* MAIN */
|
|
7
|
+
|
|
8
|
+
const main = async () => {
|
|
9
|
+
|
|
10
|
+
let count = 0;
|
|
11
|
+
let start = Date.now ();
|
|
12
|
+
|
|
13
|
+
setInterval ( () => {
|
|
14
|
+
count += 1;
|
|
15
|
+
console.log ( 'tick', count );
|
|
16
|
+
if ( count < 100 ) return;
|
|
17
|
+
const end = Date.now ();
|
|
18
|
+
const elapsed = end - start;
|
|
19
|
+
console.log ( 'elapsed', elapsed );
|
|
20
|
+
if ( elapsed > 1500 ) {
|
|
21
|
+
process.exit ( 1 ); // Fail
|
|
22
|
+
} else {
|
|
23
|
+
process.exit ( 0 ); // Success
|
|
24
|
+
}
|
|
25
|
+
}, 10 );
|
|
26
|
+
|
|
27
|
+
await readdir ( '/' );
|
|
28
|
+
|
|
29
|
+
process.exit ( 1 ); // Fail
|
|
30
|
+
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/* RUNNING */
|
|
34
|
+
|
|
35
|
+
await main ();
|