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 CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { Options, Result } from './types';
2
- declare const readdir: (rootPath: string, options?: Options | undefined) => Promise<Result>;
2
+ declare const readdir: (rootPath: string, options?: Options) => Promise<Result>;
3
3
  export default readdir;
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
- return limiter.add(() => populateResultFromPath(subPath, depth + 1));
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
- return limiter.add(() => populateResultFromSymlink(subPath, depth + 1));
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 subPath = `${rootPath}${path.sep}${dirent.name}`;
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
- declare type Promisable<T> = Promise<T> | T;
2
- declare type Options = {
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
- declare type ResultDirectory = {
11
+ type ResultDirectory = {
11
12
  directories: string[];
12
13
  files: string[];
13
14
  symlinks: string[];
14
15
  };
15
- declare type ResultDirectories = {
16
+ type ResultDirectories = {
16
17
  [path: string]: ResultDirectory;
17
18
  };
18
- declare type Result = ResultDirectory & {
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.0.0",
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": "tsex test",
15
- "test:watch": "tsex test --watch",
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": "^17.0.23",
30
- "fava": "^0.0.6",
31
- "tsex": "^1.1.1",
32
- "typescript": "^4.6.3"
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
- return limiter.add ( () => populateResultFromPath ( subPath, depth + 1 ) );
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
- return limiter.add ( () => populateResultFromSymlink ( subPath, depth + 1 ) );
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 subPath = `${rootPath}${path.sep}${dirent.name}`;
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
@@ -7,6 +7,7 @@ type Promisable<T> = Promise<T> | T;
7
7
 
8
8
  type Options = {
9
9
  depth?: number,
10
+ limit?: number,
10
11
  followSymlinks?: boolean,
11
12
  ignore?: ( targetPath: string ) => boolean,
12
13
  signal?: { aborted: boolean }
@@ -10,7 +10,7 @@ import readdir from '../dist/index.js';
10
10
 
11
11
  describe ( 'Tiny Readdir', it => {
12
12
 
13
- it ( 'works', async t => {
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
  });
@@ -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 ();