tiny-readdir 2.2.0 → 2.2.1

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 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;
@@ -17,6 +17,7 @@ const readdir = (rootPath, options) => {
17
17
  const visited = new Set();
18
18
  const resultEmpty = { directories: [], files: [], symlinks: [], map: {} };
19
19
  const result = { directories, files, symlinks, map };
20
+ const { promise, increment, decrement } = makeCounterPromise();
20
21
  let foundPaths = 0;
21
22
  const handleDirectory = (dirmap, subPath, depth) => {
22
23
  if (visited.has(subPath))
@@ -31,7 +32,7 @@ const readdir = (rootPath, options) => {
31
32
  return;
32
33
  if (foundPaths >= maxPaths)
33
34
  return;
34
- return populateResultFromPath(subPath, depth + 1);
35
+ populateResultFromPath(subPath, depth + 1);
35
36
  };
36
37
  const handleFile = (dirmap, subPath) => {
37
38
  if (visited.has(subPath))
@@ -58,7 +59,7 @@ const readdir = (rootPath, options) => {
58
59
  return;
59
60
  if (foundPaths >= maxPaths)
60
61
  return;
61
- return populateResultFromSymlink(subPath, depth + 1);
62
+ populateResultFromSymlink(subPath, depth + 1);
62
63
  };
63
64
  const handleStat = (dirmap, rootPath, stat, depth) => {
64
65
  if (signal.aborted)
@@ -66,13 +67,13 @@ const readdir = (rootPath, options) => {
66
67
  if (isIgnored(rootPath))
67
68
  return;
68
69
  if (stat.isDirectory()) {
69
- return handleDirectory(dirmap, rootPath, depth);
70
+ handleDirectory(dirmap, rootPath, depth);
70
71
  }
71
72
  else if (stat.isFile()) {
72
- return handleFile(dirmap, rootPath);
73
+ handleFile(dirmap, rootPath);
73
74
  }
74
75
  else if (stat.isSymbolicLink()) {
75
- return handleSymlink(dirmap, rootPath, depth);
76
+ handleSymlink(dirmap, rootPath, depth);
76
77
  }
77
78
  };
78
79
  const handleDirent = (dirmap, rootPath, dirent, depth) => {
@@ -83,48 +84,63 @@ const readdir = (rootPath, options) => {
83
84
  if (isIgnored(subPath))
84
85
  return;
85
86
  if (dirent.isDirectory()) {
86
- return handleDirectory(dirmap, subPath, depth);
87
+ handleDirectory(dirmap, subPath, depth);
87
88
  }
88
89
  else if (dirent.isFile()) {
89
- return handleFile(dirmap, subPath);
90
+ handleFile(dirmap, subPath);
90
91
  }
91
92
  else if (dirent.isSymbolicLink()) {
92
- return handleSymlink(dirmap, subPath, depth);
93
+ handleSymlink(dirmap, subPath, depth);
93
94
  }
94
95
  };
95
96
  const handleDirents = (dirmap, rootPath, dirents, depth) => {
96
- return Promise.all(dirents.map((dirent) => {
97
- return handleDirent(dirmap, rootPath, dirent, depth);
98
- }));
97
+ for (let i = 0, l = dirents.length; i < l; i++) {
98
+ handleDirent(dirmap, rootPath, dirents[i], depth);
99
+ }
99
100
  };
100
- const populateResultFromPath = async (rootPath, depth) => {
101
+ const populateResultFromPath = (rootPath, depth) => {
101
102
  if (signal.aborted)
102
103
  return;
103
104
  if (depth > maxDepth)
104
105
  return;
105
106
  if (foundPaths >= maxPaths)
106
107
  return;
107
- const dirents = await fs.promises.readdir(rootPath, { withFileTypes: true }).catch(() => []);
108
- if (signal.aborted)
109
- return;
110
- const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
111
- if (!dirents.length)
112
- return;
113
- await handleDirents(dirmap, rootPath, dirents, depth);
108
+ increment();
109
+ fs.readdir(rootPath, { withFileTypes: true }, (error, dirents) => {
110
+ if (error)
111
+ return decrement();
112
+ if (signal.aborted)
113
+ return decrement();
114
+ if (!dirents.length)
115
+ return decrement();
116
+ const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
117
+ handleDirents(dirmap, rootPath, dirents, depth);
118
+ decrement();
119
+ });
114
120
  };
115
121
  const populateResultFromSymlink = async (rootPath, depth) => {
116
- try {
117
- const realPath = await fs.promises.realpath(rootPath);
118
- const stat = await fs.promises.stat(realPath);
119
- const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
120
- await handleStat(dirmap, realPath, stat, depth);
121
- }
122
- catch { }
122
+ increment();
123
+ fs.realpath(rootPath, (error, realPath) => {
124
+ if (error)
125
+ return decrement();
126
+ if (signal.aborted)
127
+ return decrement();
128
+ fs.stat(realPath, async (error, stat) => {
129
+ if (error)
130
+ return decrement();
131
+ if (signal.aborted)
132
+ return decrement();
133
+ const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
134
+ handleStat(dirmap, realPath, stat, depth);
135
+ decrement();
136
+ });
137
+ });
123
138
  };
124
139
  const getResult = async (rootPath, depth = 1) => {
125
140
  rootPath = path.normalize(rootPath);
126
141
  visited.add(rootPath);
127
- await populateResultFromPath(rootPath, depth);
142
+ populateResultFromPath(rootPath, depth);
143
+ await promise;
128
144
  if (signal.aborted)
129
145
  return resultEmpty;
130
146
  return result;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type Promisable<T> = Promise<T> | T;
1
+ type Callback = () => void;
2
2
  type Options = {
3
3
  depth?: number;
4
4
  limit?: number;
@@ -19,4 +19,4 @@ type ResultDirectories = {
19
19
  type Result = ResultDirectory & {
20
20
  map: ResultDirectories;
21
21
  };
22
- export type { Promisable, Options, ResultDirectory, ResultDirectories, Result };
22
+ 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
- export { isFunction };
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.2.0",
5
+ "version": "2.2.1",
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": "npm run test:native && npm run test:yielding",
15
- "test:native": "fava '**/native.js'",
16
- "test:yielding": "node test/yielding.js",
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": "^18.11.9",
28
- "fava": "^0.0.7",
29
- "tsex": "^1.1.3",
30
- "typescript": "^4.9.3"
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/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 {Promisable, Options, ResultDirectory, ResultDirectories, Result} from './types';
6
+ import {isFunction, makeCounterPromise} from './utils';
7
+ import type {Options, ResultDirectory, ResultDirectories, Result} from './types';
8
8
 
9
9
  /* MAIN */
10
10
 
@@ -23,10 +23,11 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
23
23
  const visited = new Set<string> ();
24
24
  const resultEmpty: Result = { directories: [], files: [], symlinks: [], map: {} };
25
25
  const result: Result = { directories, files, symlinks, map };
26
+ const {promise, increment, decrement} = makeCounterPromise ();
26
27
 
27
28
  let foundPaths = 0;
28
29
 
29
- const handleDirectory = ( dirmap: ResultDirectory, subPath: string, depth: number ): Promisable<void> => {
30
+ const handleDirectory = ( dirmap: ResultDirectory, subPath: string, depth: number ): void => {
30
31
 
31
32
  if ( visited.has ( subPath ) ) return;
32
33
 
@@ -41,7 +42,7 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
41
42
 
42
43
  if ( foundPaths >= maxPaths ) return;
43
44
 
44
- return populateResultFromPath ( subPath, depth + 1 );
45
+ populateResultFromPath ( subPath, depth + 1 );
45
46
 
46
47
  };
47
48
 
@@ -58,7 +59,7 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
58
59
 
59
60
  };
60
61
 
61
- const handleSymlink = ( dirmap: ResultDirectory, subPath: string, depth: number ): Promisable<void> => {
62
+ const handleSymlink = ( dirmap: ResultDirectory, subPath: string, depth: number ): void => {
62
63
 
63
64
  if ( visited.has ( subPath ) ) return;
64
65
 
@@ -75,11 +76,11 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
75
76
 
76
77
  if ( foundPaths >= maxPaths ) return;
77
78
 
78
- return populateResultFromSymlink ( subPath, depth + 1 );
79
+ populateResultFromSymlink ( subPath, depth + 1 );
79
80
 
80
81
  };
81
82
 
82
- const handleStat = ( dirmap: ResultDirectory, rootPath: string, stat: fs.Stats, depth: number ): Promisable<void> => {
83
+ const handleStat = ( dirmap: ResultDirectory, rootPath: string, stat: fs.Stats, depth: number ): void => {
83
84
 
84
85
  if ( signal.aborted ) return;
85
86
 
@@ -87,21 +88,21 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
87
88
 
88
89
  if ( stat.isDirectory () ) {
89
90
 
90
- return handleDirectory ( dirmap, rootPath, depth );
91
+ handleDirectory ( dirmap, rootPath, depth );
91
92
 
92
93
  } else if ( stat.isFile () ) {
93
94
 
94
- return handleFile ( dirmap, rootPath );
95
+ handleFile ( dirmap, rootPath );
95
96
 
96
97
  } else if ( stat.isSymbolicLink () ) {
97
98
 
98
- return handleSymlink ( dirmap, rootPath, depth );
99
+ handleSymlink ( dirmap, rootPath, depth );
99
100
 
100
101
  }
101
102
 
102
103
  };
103
104
 
104
- const handleDirent = ( dirmap: ResultDirectory, rootPath: string, dirent: fs.Dirent, depth: number ): Promisable<void> => {
105
+ const handleDirent = ( dirmap: ResultDirectory, rootPath: string, dirent: fs.Dirent, depth: number ): void => {
105
106
 
106
107
  if ( signal.aborted ) return;
107
108
 
@@ -112,31 +113,31 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
112
113
 
113
114
  if ( dirent.isDirectory () ) {
114
115
 
115
- return handleDirectory ( dirmap, subPath, depth );
116
+ handleDirectory ( dirmap, subPath, depth );
116
117
 
117
118
  } else if ( dirent.isFile () ) {
118
119
 
119
- return handleFile ( dirmap, subPath );
120
+ handleFile ( dirmap, subPath );
120
121
 
121
122
  } else if ( dirent.isSymbolicLink () ) {
122
123
 
123
- return handleSymlink ( dirmap, subPath, depth );
124
+ handleSymlink ( dirmap, subPath, depth );
124
125
 
125
126
  }
126
127
 
127
128
  };
128
129
 
129
- const handleDirents = ( dirmap: ResultDirectory, rootPath: string, dirents: fs.Dirent[], depth: number ): Promise<void[]> => {
130
+ const handleDirents = ( dirmap: ResultDirectory, rootPath: string, dirents: fs.Dirent[], depth: number ): void => {
130
131
 
131
- return Promise.all ( dirents.map ( ( dirent ): Promisable<void> => {
132
+ for ( let i = 0, l = dirents.length; i < l; i++ ) {
132
133
 
133
- return handleDirent ( dirmap, rootPath, dirent, depth );
134
+ handleDirent ( dirmap, rootPath, dirents[i], depth );
134
135
 
135
- }));
136
+ }
136
137
 
137
138
  };
138
139
 
139
- const populateResultFromPath = async ( rootPath: string, depth: number ): Promise<void> => {
140
+ const populateResultFromPath = ( rootPath: string, depth: number ): void => {
140
141
 
141
142
  if ( signal.aborted ) return;
142
143
 
@@ -144,29 +145,51 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
144
145
 
145
146
  if ( foundPaths >= maxPaths ) return;
146
147
 
147
- const dirents = await fs.promises.readdir ( rootPath, { withFileTypes: true } ).catch ( () => [] );
148
+ increment ();
148
149
 
149
- if ( signal.aborted ) return;
150
+ fs.readdir ( rootPath, { withFileTypes: true }, ( error, dirents ) => {
151
+
152
+ if ( error ) return decrement ();
150
153
 
151
- const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
154
+ if ( signal.aborted ) return decrement ();
152
155
 
153
- if ( !dirents.length ) return;
156
+ if ( !dirents.length ) return decrement ();
157
+
158
+ const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
154
159
 
155
- await handleDirents ( dirmap, rootPath, dirents, depth );
160
+ handleDirents ( dirmap, rootPath, dirents, depth );
161
+
162
+ decrement ();
163
+
164
+ });
156
165
 
157
166
  };
158
167
 
159
168
  const populateResultFromSymlink = async ( rootPath: string, depth: number ): Promise<void> => {
160
169
 
161
- try {
170
+ increment ();
162
171
 
163
- const realPath = await fs.promises.realpath ( rootPath );
164
- const stat = await fs.promises.stat ( realPath );
165
- const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
172
+ fs.realpath ( rootPath, ( error, realPath ) => {
173
+
174
+ if ( error ) return decrement ();
166
175
 
167
- await handleStat ( dirmap, realPath, stat, depth );
176
+ if ( signal.aborted ) return decrement ();
168
177
 
169
- } catch {}
178
+ fs.stat ( realPath, async ( error, stat ) => {
179
+
180
+ if ( error ) return decrement ();
181
+
182
+ if ( signal.aborted ) return decrement ();
183
+
184
+ const dirmap = map[rootPath] = { directories: [], files: [], symlinks: [] };
185
+
186
+ handleStat ( dirmap, realPath, stat, depth );
187
+
188
+ decrement ();
189
+
190
+ });
191
+
192
+ });
170
193
 
171
194
  };
172
195
 
@@ -176,7 +199,9 @@ const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
176
199
 
177
200
  visited.add ( rootPath );
178
201
 
179
- await populateResultFromPath ( rootPath, depth );
202
+ populateResultFromPath ( rootPath, depth );
203
+
204
+ await promise;
180
205
 
181
206
  if ( signal.aborted ) return resultEmpty;
182
207
 
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /* HELPERS */
3
3
 
4
- type Promisable<T> = Promise<T> | T;
4
+ type Callback = () => void;
5
5
 
6
6
  /* MAIN */
7
7
 
@@ -29,4 +29,4 @@ type Result = ResultDirectory & {
29
29
 
30
30
  /* EXPORT */
31
31
 
32
- export type {Promisable, Options, ResultDirectory, ResultDirectories, Result};
32
+ 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};
@@ -164,4 +164,38 @@ describe ( 'Tiny Readdir', it => {
164
164
 
165
165
  });
166
166
 
167
+ it ( 'does not freeze the main thread', async t => {
168
+
169
+ return new Promise ( resolve => {
170
+
171
+ let count = 0;
172
+ let start = Date.now ();
173
+
174
+ const aborter = new AbortController ();
175
+ const signal = aborter.signal;
176
+
177
+ const intervalId = setInterval ( () => {
178
+ count += 1;
179
+ console.log ( 'tick', count );
180
+ if ( count !== 100 ) return;
181
+ clearInterval ( intervalId );
182
+ const end = Date.now ();
183
+ const elapsed = end - start;
184
+ console.log ( 'elapsed', elapsed );
185
+ console.log ( elapsed );
186
+ if ( elapsed > 1500 ) {
187
+ t.fail ();
188
+ } else {
189
+ t.pass ();
190
+ }
191
+ aborter.abort ();
192
+ resolve ();
193
+ }, 10 );
194
+
195
+ readdir ( '/', { signal } );
196
+
197
+ });
198
+
199
+ });
200
+
167
201
  });
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 ();