tiny-readdir 2.2.0 → 2.3.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.js +62 -35
- package/dist/types.d.ts +5 -2
- package/dist/utils.d.ts +7 -1
- package/dist/utils.js +17 -1
- package/package.json +11 -9
- package/readme.md +4 -0
- package/src/index.ts +71 -35
- package/src/types.ts +6 -3
- package/src/utils.ts +32 -1
- package/test/{native.js → index.js} +84 -10
- package/test/yielding.js +0 -35
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* IMPORT */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { isFunction } from './utils.js';
|
|
4
|
+
import { isFunction, makeCounterPromise } from './utils.js';
|
|
5
5
|
/* MAIN */
|
|
6
6
|
const readdir = (rootPath, options) => {
|
|
7
7
|
const followSymlinks = options?.followSymlinks ?? false;
|
|
@@ -11,46 +11,56 @@ const readdir = (rootPath, options) => {
|
|
|
11
11
|
const isIgnored = isFunction(ignore) ? ignore : (targetPath) => ignore.test(targetPath);
|
|
12
12
|
const signal = options?.signal ?? { aborted: false };
|
|
13
13
|
const directories = [];
|
|
14
|
+
const directoriesNames = new Set();
|
|
14
15
|
const files = [];
|
|
16
|
+
const filesNames = new Set();
|
|
15
17
|
const symlinks = [];
|
|
18
|
+
const symlinksNames = new Set();
|
|
16
19
|
const map = {};
|
|
17
20
|
const visited = new Set();
|
|
18
|
-
const resultEmpty = { directories: [], files: [], symlinks: [], map: {} };
|
|
19
|
-
const result = { directories, files, symlinks, map };
|
|
21
|
+
const resultEmpty = { directories: [], directoriesNames: new Set(), files: [], filesNames: new Set(), symlinks: [], symlinksNames: new Set(), map: {} };
|
|
22
|
+
const result = { directories, directoriesNames, files, filesNames, symlinks, symlinksNames, map };
|
|
23
|
+
const { promise, increment, decrement } = makeCounterPromise();
|
|
20
24
|
let foundPaths = 0;
|
|
21
|
-
const handleDirectory = (dirmap, subPath, depth) => {
|
|
25
|
+
const handleDirectory = (dirmap, subPath, name, depth) => {
|
|
22
26
|
if (visited.has(subPath))
|
|
23
27
|
return;
|
|
24
28
|
if (foundPaths >= maxPaths)
|
|
25
29
|
return;
|
|
26
30
|
foundPaths += 1;
|
|
27
31
|
dirmap.directories.push(subPath);
|
|
32
|
+
dirmap.directoriesNames.add(name);
|
|
28
33
|
directories.push(subPath);
|
|
34
|
+
directoriesNames.add(name);
|
|
29
35
|
visited.add(subPath);
|
|
30
36
|
if (depth >= maxDepth)
|
|
31
37
|
return;
|
|
32
38
|
if (foundPaths >= maxPaths)
|
|
33
39
|
return;
|
|
34
|
-
|
|
40
|
+
populateResultFromPath(subPath, depth + 1);
|
|
35
41
|
};
|
|
36
|
-
const handleFile = (dirmap, subPath) => {
|
|
42
|
+
const handleFile = (dirmap, subPath, name) => {
|
|
37
43
|
if (visited.has(subPath))
|
|
38
44
|
return;
|
|
39
45
|
if (foundPaths >= maxPaths)
|
|
40
46
|
return;
|
|
41
47
|
foundPaths += 1;
|
|
42
48
|
dirmap.files.push(subPath);
|
|
49
|
+
dirmap.filesNames.add(name);
|
|
43
50
|
files.push(subPath);
|
|
51
|
+
filesNames.add(name);
|
|
44
52
|
visited.add(subPath);
|
|
45
53
|
};
|
|
46
|
-
const handleSymlink = (dirmap, subPath, depth) => {
|
|
54
|
+
const handleSymlink = (dirmap, subPath, name, depth) => {
|
|
47
55
|
if (visited.has(subPath))
|
|
48
56
|
return;
|
|
49
57
|
if (foundPaths >= maxPaths)
|
|
50
58
|
return;
|
|
51
59
|
foundPaths += 1;
|
|
52
60
|
dirmap.symlinks.push(subPath);
|
|
61
|
+
dirmap.symlinksNames.add(name);
|
|
53
62
|
symlinks.push(subPath);
|
|
63
|
+
symlinksNames.add(name);
|
|
54
64
|
visited.add(subPath);
|
|
55
65
|
if (!followSymlinks)
|
|
56
66
|
return;
|
|
@@ -58,73 +68,90 @@ const readdir = (rootPath, options) => {
|
|
|
58
68
|
return;
|
|
59
69
|
if (foundPaths >= maxPaths)
|
|
60
70
|
return;
|
|
61
|
-
|
|
71
|
+
populateResultFromSymlink(subPath, depth + 1);
|
|
62
72
|
};
|
|
63
|
-
const handleStat = (dirmap, rootPath, stat, depth) => {
|
|
73
|
+
const handleStat = (dirmap, rootPath, name, stat, depth) => {
|
|
64
74
|
if (signal.aborted)
|
|
65
75
|
return;
|
|
66
76
|
if (isIgnored(rootPath))
|
|
67
77
|
return;
|
|
68
78
|
if (stat.isDirectory()) {
|
|
69
|
-
|
|
79
|
+
handleDirectory(dirmap, rootPath, name, depth);
|
|
70
80
|
}
|
|
71
81
|
else if (stat.isFile()) {
|
|
72
|
-
|
|
82
|
+
handleFile(dirmap, rootPath, name);
|
|
73
83
|
}
|
|
74
84
|
else if (stat.isSymbolicLink()) {
|
|
75
|
-
|
|
85
|
+
handleSymlink(dirmap, rootPath, name, depth);
|
|
76
86
|
}
|
|
77
87
|
};
|
|
78
88
|
const handleDirent = (dirmap, rootPath, dirent, depth) => {
|
|
79
89
|
if (signal.aborted)
|
|
80
90
|
return;
|
|
81
91
|
const separator = (rootPath === path.sep) ? '' : path.sep;
|
|
82
|
-
const
|
|
92
|
+
const name = dirent.name;
|
|
93
|
+
const subPath = `${rootPath}${separator}${name}`;
|
|
83
94
|
if (isIgnored(subPath))
|
|
84
95
|
return;
|
|
85
96
|
if (dirent.isDirectory()) {
|
|
86
|
-
|
|
97
|
+
handleDirectory(dirmap, subPath, name, depth);
|
|
87
98
|
}
|
|
88
99
|
else if (dirent.isFile()) {
|
|
89
|
-
|
|
100
|
+
handleFile(dirmap, subPath, name);
|
|
90
101
|
}
|
|
91
102
|
else if (dirent.isSymbolicLink()) {
|
|
92
|
-
|
|
103
|
+
handleSymlink(dirmap, subPath, name, depth);
|
|
93
104
|
}
|
|
94
105
|
};
|
|
95
106
|
const handleDirents = (dirmap, rootPath, dirents, depth) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
107
|
+
for (let i = 0, l = dirents.length; i < l; i++) {
|
|
108
|
+
handleDirent(dirmap, rootPath, dirents[i], depth);
|
|
109
|
+
}
|
|
99
110
|
};
|
|
100
|
-
const populateResultFromPath =
|
|
111
|
+
const populateResultFromPath = (rootPath, depth) => {
|
|
101
112
|
if (signal.aborted)
|
|
102
113
|
return;
|
|
103
114
|
if (depth > maxDepth)
|
|
104
115
|
return;
|
|
105
116
|
if (foundPaths >= maxPaths)
|
|
106
117
|
return;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
increment();
|
|
119
|
+
fs.readdir(rootPath, { withFileTypes: true }, (error, dirents) => {
|
|
120
|
+
if (error)
|
|
121
|
+
return decrement();
|
|
122
|
+
if (signal.aborted)
|
|
123
|
+
return decrement();
|
|
124
|
+
if (!dirents.length)
|
|
125
|
+
return decrement();
|
|
126
|
+
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set(), files: [], filesNames: new Set(), symlinks: [], symlinksNames: new Set() };
|
|
127
|
+
handleDirents(dirmap, rootPath, dirents, depth);
|
|
128
|
+
decrement();
|
|
129
|
+
});
|
|
114
130
|
};
|
|
115
131
|
const populateResultFromSymlink = async (rootPath, depth) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
increment();
|
|
133
|
+
fs.realpath(rootPath, (error, realPath) => {
|
|
134
|
+
if (error)
|
|
135
|
+
return decrement();
|
|
136
|
+
if (signal.aborted)
|
|
137
|
+
return decrement();
|
|
138
|
+
fs.stat(realPath, async (error, stat) => {
|
|
139
|
+
if (error)
|
|
140
|
+
return decrement();
|
|
141
|
+
if (signal.aborted)
|
|
142
|
+
return decrement();
|
|
143
|
+
const name = path.basename(realPath);
|
|
144
|
+
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set(), files: [], filesNames: new Set(), symlinks: [], symlinksNames: new Set() };
|
|
145
|
+
handleStat(dirmap, realPath, name, stat, depth);
|
|
146
|
+
decrement();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
123
149
|
};
|
|
124
150
|
const getResult = async (rootPath, depth = 1) => {
|
|
125
151
|
rootPath = path.normalize(rootPath);
|
|
126
152
|
visited.add(rootPath);
|
|
127
|
-
|
|
153
|
+
populateResultFromPath(rootPath, depth);
|
|
154
|
+
await promise;
|
|
128
155
|
if (signal.aborted)
|
|
129
156
|
return resultEmpty;
|
|
130
157
|
return result;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type
|
|
1
|
+
type Callback = () => void;
|
|
2
2
|
type Options = {
|
|
3
3
|
depth?: number;
|
|
4
4
|
limit?: number;
|
|
@@ -10,8 +10,11 @@ type Options = {
|
|
|
10
10
|
};
|
|
11
11
|
type ResultDirectory = {
|
|
12
12
|
directories: string[];
|
|
13
|
+
directoriesNames: Set<string>;
|
|
13
14
|
files: string[];
|
|
15
|
+
filesNames: Set<string>;
|
|
14
16
|
symlinks: string[];
|
|
17
|
+
symlinksNames: Set<string>;
|
|
15
18
|
};
|
|
16
19
|
type ResultDirectories = {
|
|
17
20
|
[path: string]: ResultDirectory;
|
|
@@ -19,4 +22,4 @@ type ResultDirectories = {
|
|
|
19
22
|
type Result = ResultDirectory & {
|
|
20
23
|
map: ResultDirectories;
|
|
21
24
|
};
|
|
22
|
-
export type {
|
|
25
|
+
export type { Callback, Options, ResultDirectory, ResultDirectories, Result };
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import type { Callback } from './types';
|
|
1
2
|
declare const isFunction: (value: unknown) => value is Function;
|
|
2
|
-
|
|
3
|
+
declare const makeCounterPromise: () => {
|
|
4
|
+
promise: Promise<void>;
|
|
5
|
+
increment: Callback;
|
|
6
|
+
decrement: Callback;
|
|
7
|
+
};
|
|
8
|
+
export { isFunction, makeCounterPromise };
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
/* IMPORT */
|
|
2
|
+
import makeNakedPromise from 'promise-make-naked';
|
|
1
3
|
/* MAIN */
|
|
2
4
|
const isFunction = (value) => {
|
|
3
5
|
return (typeof value === 'function');
|
|
4
6
|
};
|
|
7
|
+
const makeCounterPromise = () => {
|
|
8
|
+
const { promise, resolve } = makeNakedPromise();
|
|
9
|
+
let counter = 0;
|
|
10
|
+
const increment = () => {
|
|
11
|
+
counter += 1;
|
|
12
|
+
};
|
|
13
|
+
const decrement = () => {
|
|
14
|
+
counter -= 1;
|
|
15
|
+
if (counter)
|
|
16
|
+
return;
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
return { promise, increment, decrement };
|
|
20
|
+
};
|
|
5
21
|
/* EXPORT */
|
|
6
|
-
export { isFunction };
|
|
22
|
+
export { isFunction, makeCounterPromise };
|
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.3.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"exports": "./dist/index.js",
|
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
"clean": "tsex clean",
|
|
12
12
|
"compile": "tsex compile",
|
|
13
13
|
"compile:watch": "tsex compile --watch",
|
|
14
|
-
"test": "
|
|
15
|
-
"test:
|
|
16
|
-
"
|
|
17
|
-
"prepublishOnly": "npm run clean && npm run compile && npm run test"
|
|
14
|
+
"test": "tsex test",
|
|
15
|
+
"test:watch": "tsex test --watch",
|
|
16
|
+
"prepublishOnly": "tsex prepare"
|
|
18
17
|
},
|
|
19
18
|
"keywords": [
|
|
20
19
|
"readdir",
|
|
@@ -23,10 +22,13 @@
|
|
|
23
22
|
"simple",
|
|
24
23
|
"tiny"
|
|
25
24
|
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"promise-make-naked": "^2.1.1"
|
|
27
|
+
},
|
|
26
28
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^
|
|
28
|
-
"fava": "^0.
|
|
29
|
-
"tsex": "^
|
|
30
|
-
"typescript": "^
|
|
29
|
+
"@types/node": "^20.4.8",
|
|
30
|
+
"fava": "^0.2.1",
|
|
31
|
+
"tsex": "^3.0.1",
|
|
32
|
+
"typescript": "^5.1.6"
|
|
31
33
|
}
|
|
32
34
|
}
|
package/readme.md
CHANGED
|
@@ -27,6 +27,10 @@ console.log ( result.directories ); // => Array of absolute paths pointing to di
|
|
|
27
27
|
console.log ( result.files ); // => Array of absolute paths pointing to files
|
|
28
28
|
console.log ( result.symlinks ); // => Array of absolute paths pointing to symlinks
|
|
29
29
|
|
|
30
|
+
console.log ( result.directoriesNames ); // => Set of directories names found
|
|
31
|
+
console.log ( result.filesNames ); // => Set of files name found
|
|
32
|
+
console.log ( result.symlinksNames ); // => Set of symlinks names found
|
|
33
|
+
|
|
30
34
|
setTimeout ( () => aborter.abort (), 10000 ); // Aborting if it's going to take longer than 10s
|
|
31
35
|
```
|
|
32
36
|
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {isFunction} from './utils';
|
|
7
|
-
import type {
|
|
6
|
+
import {isFunction, makeCounterPromise} from './utils';
|
|
7
|
+
import type {Options, ResultDirectory, ResultDirectories, Result} from './types';
|
|
8
8
|
|
|
9
9
|
/* MAIN */
|
|
10
10
|
|
|
@@ -17,16 +17,20 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
17
17
|
const isIgnored = isFunction ( ignore ) ? ignore : ( targetPath: string ) => ignore.test ( targetPath );
|
|
18
18
|
const signal = options?.signal ?? { aborted: false };
|
|
19
19
|
const directories: string[] = [];
|
|
20
|
+
const directoriesNames: Set<string> = new Set ();
|
|
20
21
|
const files: string[] = [];
|
|
22
|
+
const filesNames: Set<string> = new Set ();
|
|
21
23
|
const symlinks: string[] = [];
|
|
24
|
+
const symlinksNames: Set<string> = new Set ();
|
|
22
25
|
const map: ResultDirectories = {};
|
|
23
26
|
const visited = new Set<string> ();
|
|
24
|
-
const resultEmpty: Result = { directories: [], files: [], symlinks: [], map: {} };
|
|
25
|
-
const result: Result = { directories, files, symlinks, map };
|
|
27
|
+
const resultEmpty: Result = { directories: [], directoriesNames: new Set (), files: [], filesNames: new Set (), symlinks: [], symlinksNames: new Set (), map: {} };
|
|
28
|
+
const result: Result = { directories, directoriesNames, files, filesNames, symlinks, symlinksNames, map };
|
|
29
|
+
const {promise, increment, decrement} = makeCounterPromise ();
|
|
26
30
|
|
|
27
31
|
let foundPaths = 0;
|
|
28
32
|
|
|
29
|
-
const handleDirectory = ( dirmap: ResultDirectory, subPath: string, depth: number ):
|
|
33
|
+
const handleDirectory = ( dirmap: ResultDirectory, subPath: string, name: string, depth: number ): void => {
|
|
30
34
|
|
|
31
35
|
if ( visited.has ( subPath ) ) return;
|
|
32
36
|
|
|
@@ -34,18 +38,20 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
34
38
|
|
|
35
39
|
foundPaths += 1;
|
|
36
40
|
dirmap.directories.push ( subPath );
|
|
41
|
+
dirmap.directoriesNames.add ( name );
|
|
37
42
|
directories.push ( subPath );
|
|
43
|
+
directoriesNames.add ( name );
|
|
38
44
|
visited.add ( subPath );
|
|
39
45
|
|
|
40
46
|
if ( depth >= maxDepth ) return;
|
|
41
47
|
|
|
42
48
|
if ( foundPaths >= maxPaths ) return;
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
populateResultFromPath ( subPath, depth + 1 );
|
|
45
51
|
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
const handleFile = ( dirmap: ResultDirectory, subPath: string ): void => {
|
|
54
|
+
const handleFile = ( dirmap: ResultDirectory, subPath: string, name: string ): void => {
|
|
49
55
|
|
|
50
56
|
if ( visited.has ( subPath ) ) return;
|
|
51
57
|
|
|
@@ -53,12 +59,14 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
53
59
|
|
|
54
60
|
foundPaths += 1;
|
|
55
61
|
dirmap.files.push ( subPath );
|
|
62
|
+
dirmap.filesNames.add ( name );
|
|
56
63
|
files.push ( subPath );
|
|
64
|
+
filesNames.add ( name );
|
|
57
65
|
visited.add ( subPath );
|
|
58
66
|
|
|
59
67
|
};
|
|
60
68
|
|
|
61
|
-
const handleSymlink = ( dirmap: ResultDirectory, subPath: string, depth: number ):
|
|
69
|
+
const handleSymlink = ( dirmap: ResultDirectory, subPath: string, name: string, depth: number ): void => {
|
|
62
70
|
|
|
63
71
|
if ( visited.has ( subPath ) ) return;
|
|
64
72
|
|
|
@@ -66,7 +74,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
66
74
|
|
|
67
75
|
foundPaths += 1;
|
|
68
76
|
dirmap.symlinks.push ( subPath );
|
|
77
|
+
dirmap.symlinksNames.add ( name );
|
|
69
78
|
symlinks.push ( subPath );
|
|
79
|
+
symlinksNames.add ( name );
|
|
70
80
|
visited.add ( subPath );
|
|
71
81
|
|
|
72
82
|
if ( !followSymlinks ) return;
|
|
@@ -75,11 +85,11 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
75
85
|
|
|
76
86
|
if ( foundPaths >= maxPaths ) return;
|
|
77
87
|
|
|
78
|
-
|
|
88
|
+
populateResultFromSymlink ( subPath, depth + 1 );
|
|
79
89
|
|
|
80
90
|
};
|
|
81
91
|
|
|
82
|
-
const handleStat = ( dirmap: ResultDirectory, rootPath: string, stat: fs.Stats, depth: number ):
|
|
92
|
+
const handleStat = ( dirmap: ResultDirectory, rootPath: string, name: string, stat: fs.Stats, depth: number ): void => {
|
|
83
93
|
|
|
84
94
|
if ( signal.aborted ) return;
|
|
85
95
|
|
|
@@ -87,56 +97,57 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
87
97
|
|
|
88
98
|
if ( stat.isDirectory () ) {
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
handleDirectory ( dirmap, rootPath, name, depth );
|
|
91
101
|
|
|
92
102
|
} else if ( stat.isFile () ) {
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
handleFile ( dirmap, rootPath, name );
|
|
95
105
|
|
|
96
106
|
} else if ( stat.isSymbolicLink () ) {
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
handleSymlink ( dirmap, rootPath, name, depth );
|
|
99
109
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
};
|
|
103
113
|
|
|
104
|
-
const handleDirent = ( dirmap: ResultDirectory, rootPath: string, dirent: fs.Dirent, depth: number ):
|
|
114
|
+
const handleDirent = ( dirmap: ResultDirectory, rootPath: string, dirent: fs.Dirent, depth: number ): void => {
|
|
105
115
|
|
|
106
116
|
if ( signal.aborted ) return;
|
|
107
117
|
|
|
108
118
|
const separator = ( rootPath === path.sep ) ? '' : path.sep;
|
|
109
|
-
const
|
|
119
|
+
const name = dirent.name;
|
|
120
|
+
const subPath = `${rootPath}${separator}${name}`;
|
|
110
121
|
|
|
111
122
|
if ( isIgnored ( subPath ) ) return;
|
|
112
123
|
|
|
113
124
|
if ( dirent.isDirectory () ) {
|
|
114
125
|
|
|
115
|
-
|
|
126
|
+
handleDirectory ( dirmap, subPath, name, depth );
|
|
116
127
|
|
|
117
128
|
} else if ( dirent.isFile () ) {
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
handleFile ( dirmap, subPath, name );
|
|
120
131
|
|
|
121
132
|
} else if ( dirent.isSymbolicLink () ) {
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
handleSymlink ( dirmap, subPath, name, depth );
|
|
124
135
|
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
};
|
|
128
139
|
|
|
129
|
-
const handleDirents = ( dirmap: ResultDirectory, rootPath: string, dirents: fs.Dirent[], depth: number ):
|
|
140
|
+
const handleDirents = ( dirmap: ResultDirectory, rootPath: string, dirents: fs.Dirent[], depth: number ): void => {
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
for ( let i = 0, l = dirents.length; i < l; i++ ) {
|
|
132
143
|
|
|
133
|
-
|
|
144
|
+
handleDirent ( dirmap, rootPath, dirents[i], depth );
|
|
134
145
|
|
|
135
|
-
}
|
|
146
|
+
}
|
|
136
147
|
|
|
137
148
|
};
|
|
138
149
|
|
|
139
|
-
const populateResultFromPath =
|
|
150
|
+
const populateResultFromPath = ( rootPath: string, depth: number ): void => {
|
|
140
151
|
|
|
141
152
|
if ( signal.aborted ) return;
|
|
142
153
|
|
|
@@ -144,29 +155,52 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
144
155
|
|
|
145
156
|
if ( foundPaths >= maxPaths ) return;
|
|
146
157
|
|
|
147
|
-
|
|
158
|
+
increment ();
|
|
148
159
|
|
|
149
|
-
|
|
160
|
+
fs.readdir ( rootPath, { withFileTypes: true }, ( error, dirents ) => {
|
|
161
|
+
|
|
162
|
+
if ( error ) return decrement ();
|
|
163
|
+
|
|
164
|
+
if ( signal.aborted ) return decrement ();
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
if ( !dirents.length ) return decrement ();
|
|
152
167
|
|
|
153
|
-
|
|
168
|
+
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set (), files: [], filesNames: new Set (), symlinks: [], symlinksNames: new Set () };
|
|
154
169
|
|
|
155
|
-
|
|
170
|
+
handleDirents ( dirmap, rootPath, dirents, depth );
|
|
171
|
+
|
|
172
|
+
decrement ();
|
|
173
|
+
|
|
174
|
+
});
|
|
156
175
|
|
|
157
176
|
};
|
|
158
177
|
|
|
159
178
|
const populateResultFromSymlink = async ( rootPath: string, depth: number ): Promise<void> => {
|
|
160
179
|
|
|
161
|
-
|
|
180
|
+
increment ();
|
|
181
|
+
|
|
182
|
+
fs.realpath ( rootPath, ( error, realPath ) => {
|
|
183
|
+
|
|
184
|
+
if ( error ) return decrement ();
|
|
162
185
|
|
|
163
|
-
|
|
164
|
-
const stat = await fs.promises.stat ( realPath );
|
|
165
|
-
const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
|
|
186
|
+
if ( signal.aborted ) return decrement ();
|
|
166
187
|
|
|
167
|
-
|
|
188
|
+
fs.stat ( realPath, async ( error, stat ) => {
|
|
168
189
|
|
|
169
|
-
|
|
190
|
+
if ( error ) return decrement ();
|
|
191
|
+
|
|
192
|
+
if ( signal.aborted ) return decrement ();
|
|
193
|
+
|
|
194
|
+
const name = path.basename ( realPath );
|
|
195
|
+
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set (), files: [], filesNames: new Set (), symlinks: [], symlinksNames: new Set () };
|
|
196
|
+
|
|
197
|
+
handleStat ( dirmap, realPath, name, stat, depth );
|
|
198
|
+
|
|
199
|
+
decrement ();
|
|
200
|
+
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
});
|
|
170
204
|
|
|
171
205
|
};
|
|
172
206
|
|
|
@@ -176,7 +210,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
|
|
176
210
|
|
|
177
211
|
visited.add ( rootPath );
|
|
178
212
|
|
|
179
|
-
|
|
213
|
+
populateResultFromPath ( rootPath, depth );
|
|
214
|
+
|
|
215
|
+
await promise;
|
|
180
216
|
|
|
181
217
|
if ( signal.aborted ) return resultEmpty;
|
|
182
218
|
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/* HELPERS */
|
|
3
3
|
|
|
4
|
-
type
|
|
4
|
+
type Callback = () => void;
|
|
5
5
|
|
|
6
6
|
/* MAIN */
|
|
7
7
|
|
|
@@ -15,8 +15,11 @@ type Options = {
|
|
|
15
15
|
|
|
16
16
|
type ResultDirectory = {
|
|
17
17
|
directories: string[],
|
|
18
|
+
directoriesNames: Set<string>,
|
|
18
19
|
files: string[],
|
|
19
|
-
|
|
20
|
+
filesNames: Set<string>,
|
|
21
|
+
symlinks: string[],
|
|
22
|
+
symlinksNames: Set<string>
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
type ResultDirectories = {
|
|
@@ -29,4 +32,4 @@ type Result = ResultDirectory & {
|
|
|
29
32
|
|
|
30
33
|
/* EXPORT */
|
|
31
34
|
|
|
32
|
-
export type {
|
|
35
|
+
export type {Callback, Options, ResultDirectory, ResultDirectories, Result};
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
|
|
4
|
+
import makeNakedPromise from 'promise-make-naked';
|
|
5
|
+
import type {Callback} from './types';
|
|
6
|
+
|
|
2
7
|
/* MAIN */
|
|
3
8
|
|
|
4
9
|
const isFunction = ( value: unknown ): value is Function => {
|
|
@@ -7,6 +12,32 @@ const isFunction = ( value: unknown ): value is Function => {
|
|
|
7
12
|
|
|
8
13
|
};
|
|
9
14
|
|
|
15
|
+
const makeCounterPromise = (): { promise: Promise<void>, increment: Callback, decrement: Callback } => {
|
|
16
|
+
|
|
17
|
+
const {promise, resolve} = makeNakedPromise<void> ();
|
|
18
|
+
|
|
19
|
+
let counter = 0;
|
|
20
|
+
|
|
21
|
+
const increment = (): void => {
|
|
22
|
+
|
|
23
|
+
counter += 1;
|
|
24
|
+
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const decrement = (): void => {
|
|
28
|
+
|
|
29
|
+
counter -= 1;
|
|
30
|
+
|
|
31
|
+
if ( counter ) return;
|
|
32
|
+
|
|
33
|
+
resolve ();
|
|
34
|
+
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return { promise, increment, decrement };
|
|
38
|
+
|
|
39
|
+
};
|
|
40
|
+
|
|
10
41
|
/* EXPORT */
|
|
11
42
|
|
|
12
|
-
export {isFunction};
|
|
43
|
+
export {isFunction, makeCounterPromise};
|
|
@@ -6,6 +6,10 @@ import fs from 'node:fs';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import readdir from '../dist/index.js';
|
|
8
8
|
|
|
9
|
+
/* HELPERS */
|
|
10
|
+
|
|
11
|
+
const toBasename = filePath => path.basename ( filePath );
|
|
12
|
+
|
|
9
13
|
/* MAIN */
|
|
10
14
|
|
|
11
15
|
describe ( 'Tiny Readdir', it => {
|
|
@@ -41,43 +45,67 @@ describe ( 'Tiny Readdir', it => {
|
|
|
41
45
|
|
|
42
46
|
const expected = {
|
|
43
47
|
directories: [folder1Path, folder2Path, folder1DeepPath, root2Path],
|
|
48
|
+
directoriesNames: new Set ( [folder1Path, folder2Path, folder1DeepPath, root2Path].map ( toBasename ) ),
|
|
44
49
|
files: [file1aPath, file1bPath, file2Path, fileDeep1Path],
|
|
50
|
+
filesNames: new Set ( [file1aPath, file1bPath, file2Path, fileDeep1Path].map ( toBasename ) ),
|
|
45
51
|
symlinks: [symlink1FromPath, symlink2FromPath],
|
|
52
|
+
symlinksNames: new Set ( [symlink1FromPath, symlink2FromPath].map ( toBasename ) ),
|
|
46
53
|
map: {
|
|
47
54
|
[root1Path]: {
|
|
48
55
|
directories: [folder1Path, folder2Path],
|
|
56
|
+
directoriesNames: new Set ( [folder1Path, folder2Path].map ( toBasename ) ),
|
|
49
57
|
files: [],
|
|
50
|
-
|
|
58
|
+
filesNames: new Set (),
|
|
59
|
+
symlinks: [symlink1FromPath],
|
|
60
|
+
symlinksNames: new Set ( [symlink1FromPath].map ( toBasename ) )
|
|
51
61
|
},
|
|
52
62
|
[root2Path]: {
|
|
53
63
|
directories: [],
|
|
64
|
+
directoriesNames: new Set (),
|
|
54
65
|
files: [],
|
|
55
|
-
|
|
66
|
+
filesNames: new Set (),
|
|
67
|
+
symlinks: [symlink2FromPath],
|
|
68
|
+
symlinksNames: new Set ( [symlink2FromPath].map ( toBasename ) )
|
|
56
69
|
},
|
|
57
70
|
[folder1Path]: {
|
|
58
71
|
directories: [folder1DeepPath],
|
|
72
|
+
directoriesNames: new Set ( [folder1DeepPath].map ( toBasename ) ),
|
|
59
73
|
files: [file1aPath, file1bPath],
|
|
60
|
-
|
|
74
|
+
filesNames: new Set ( [file1aPath, file1bPath].map ( toBasename ) ),
|
|
75
|
+
symlinks: [],
|
|
76
|
+
symlinksNames: new Set ()
|
|
61
77
|
},
|
|
62
78
|
[folder2Path]: {
|
|
63
79
|
directories: [],
|
|
80
|
+
directoriesNames: new Set (),
|
|
64
81
|
files: [file2Path],
|
|
65
|
-
|
|
82
|
+
filesNames: new Set ( [file2Path].map ( toBasename ) ),
|
|
83
|
+
symlinks: [],
|
|
84
|
+
symlinksNames: new Set ()
|
|
66
85
|
},
|
|
67
86
|
[folder1DeepPath]: {
|
|
68
87
|
directories: [],
|
|
88
|
+
directoriesNames: new Set (),
|
|
69
89
|
files: [fileDeep1Path],
|
|
70
|
-
|
|
90
|
+
filesNames: new Set ( [fileDeep1Path].map ( toBasename ) ),
|
|
91
|
+
symlinks: [],
|
|
92
|
+
symlinksNames: new Set ()
|
|
71
93
|
},
|
|
72
94
|
[symlink1FromPath]: {
|
|
73
95
|
directories: [root2Path],
|
|
96
|
+
directoriesNames: new Set ( [root2Path].map ( toBasename ) ),
|
|
74
97
|
files: [],
|
|
75
|
-
|
|
98
|
+
filesNames: new Set (),
|
|
99
|
+
symlinks: [],
|
|
100
|
+
symlinksNames: new Set ()
|
|
76
101
|
},
|
|
77
102
|
[symlink2FromPath]: {
|
|
78
103
|
directories: [],
|
|
104
|
+
directoriesNames: new Set (),
|
|
79
105
|
files: [],
|
|
80
|
-
|
|
106
|
+
filesNames: new Set (),
|
|
107
|
+
symlinks: [],
|
|
108
|
+
symlinksNames: new Set ()
|
|
81
109
|
}
|
|
82
110
|
}
|
|
83
111
|
};
|
|
@@ -128,23 +156,35 @@ describe ( 'Tiny Readdir', it => {
|
|
|
128
156
|
|
|
129
157
|
const expected = {
|
|
130
158
|
directories: [folder1Path, folder2Path],
|
|
159
|
+
directoriesNames: new Set ( [folder1Path, folder2Path].map ( toBasename ) ),
|
|
131
160
|
files: [],
|
|
161
|
+
filesNames: new Set (),
|
|
132
162
|
symlinks: [symlink1FromPath],
|
|
163
|
+
symlinksNames: new Set ( [symlink1FromPath].map ( toBasename ) ),
|
|
133
164
|
map: {
|
|
134
165
|
[root1Path]: {
|
|
135
166
|
directories: [folder1Path, folder2Path],
|
|
167
|
+
directoriesNames: new Set ( [folder1Path, folder2Path].map ( toBasename ) ),
|
|
136
168
|
files: [],
|
|
137
|
-
|
|
169
|
+
filesNames: new Set (),
|
|
170
|
+
symlinks: [symlink1FromPath],
|
|
171
|
+
symlinksNames: new Set ( [symlink1FromPath].map ( toBasename ) )
|
|
138
172
|
},
|
|
139
173
|
[folder1Path]: {
|
|
140
174
|
directories: [],
|
|
175
|
+
directoriesNames: new Set (),
|
|
141
176
|
files: [],
|
|
142
|
-
|
|
177
|
+
filesNames: new Set (),
|
|
178
|
+
symlinks: [],
|
|
179
|
+
symlinksNames: new Set ()
|
|
143
180
|
},
|
|
144
181
|
[folder2Path]: {
|
|
145
182
|
directories: [],
|
|
183
|
+
directoriesNames: new Set (),
|
|
146
184
|
files: [],
|
|
147
|
-
|
|
185
|
+
filesNames: new Set (),
|
|
186
|
+
symlinks: [],
|
|
187
|
+
symlinksNames: new Set ()
|
|
148
188
|
}
|
|
149
189
|
}
|
|
150
190
|
};
|
|
@@ -164,4 +204,38 @@ describe ( 'Tiny Readdir', it => {
|
|
|
164
204
|
|
|
165
205
|
});
|
|
166
206
|
|
|
207
|
+
it ( 'does not freeze the main thread', async t => {
|
|
208
|
+
|
|
209
|
+
return new Promise ( resolve => {
|
|
210
|
+
|
|
211
|
+
let count = 0;
|
|
212
|
+
let start = Date.now ();
|
|
213
|
+
|
|
214
|
+
const aborter = new AbortController ();
|
|
215
|
+
const signal = aborter.signal;
|
|
216
|
+
|
|
217
|
+
const intervalId = setInterval ( () => {
|
|
218
|
+
count += 1;
|
|
219
|
+
console.log ( 'tick', count );
|
|
220
|
+
if ( count !== 100 ) return;
|
|
221
|
+
clearInterval ( intervalId );
|
|
222
|
+
const end = Date.now ();
|
|
223
|
+
const elapsed = end - start;
|
|
224
|
+
console.log ( 'elapsed', elapsed );
|
|
225
|
+
console.log ( elapsed );
|
|
226
|
+
if ( elapsed > 1500 ) {
|
|
227
|
+
t.fail ();
|
|
228
|
+
} else {
|
|
229
|
+
t.pass ();
|
|
230
|
+
}
|
|
231
|
+
aborter.abort ();
|
|
232
|
+
resolve ();
|
|
233
|
+
}, 10 );
|
|
234
|
+
|
|
235
|
+
readdir ( '/', { signal } );
|
|
236
|
+
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
});
|
|
240
|
+
|
|
167
241
|
});
|
package/test/yielding.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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 ();
|