filehound 2.0.0-beta → 2.0.0-beta.3

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.
Files changed (198) hide show
  1. package/README.md +37 -11
  2. package/dist/arrays.d.ts +1 -0
  3. package/dist/arrays.js +4 -0
  4. package/dist/file.d.ts +108 -0
  5. package/dist/file.js +184 -0
  6. package/dist/filehound.d.ts +322 -0
  7. package/dist/filehound.js +443 -0
  8. package/dist/files.d.ts +4 -0
  9. package/dist/files.js +37 -0
  10. package/dist/filetype.d.ts +10 -0
  11. package/dist/filetype.js +9 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.js +3 -0
  14. package/dist/matchers.d.ts +297 -0
  15. package/dist/matchers.js +389 -0
  16. package/dist/walker/async.d.ts +4 -0
  17. package/dist/walker/async.js +237 -0
  18. package/dist/walker/heap.d.ts +7 -0
  19. package/dist/walker/heap.js +49 -0
  20. package/dist/walker/sync.d.ts +4 -0
  21. package/dist/walker/sync.js +66 -0
  22. package/dist/walker/walk.d.ts +32 -0
  23. package/dist/walker/walk.js +70 -0
  24. package/package.json +44 -49
  25. package/.github/stale.yml +0 -42
  26. package/.travis.yml +0 -8
  27. package/CONTRIBUTING.md +0 -8
  28. package/_config.yml +0 -1
  29. package/dist/src/arrays.js +0 -15
  30. package/dist/src/arrays.js.map +0 -1
  31. package/dist/src/asyncWalker.js +0 -63
  32. package/dist/src/asyncWalker.js.map +0 -1
  33. package/dist/src/bind.js +0 -15
  34. package/dist/src/bind.js.map +0 -1
  35. package/dist/src/filehound.js +0 -597
  36. package/dist/src/filehound.js.map +0 -1
  37. package/dist/src/files.js +0 -60
  38. package/dist/src/files.js.map +0 -1
  39. package/dist/src/functions.js +0 -22
  40. package/dist/src/functions.js.map +0 -1
  41. package/dist/src/matcher.js +0 -51
  42. package/dist/src/matcher.js.map +0 -1
  43. package/dist/src/syncWalker.js +0 -51
  44. package/dist/src/syncWalker.js.map +0 -1
  45. package/dist/src/walker/walk.js +0 -68
  46. package/dist/src/walker/walk.js.map +0 -1
  47. package/dist/src/walker/walkAsync.js +0 -33
  48. package/dist/src/walker/walkAsync.js.map +0 -1
  49. package/dist/src/walker/walkSync.js +0 -21
  50. package/dist/src/walker/walkSync.js.map +0 -1
  51. package/dist/src/walker.js +0 -31
  52. package/dist/src/walker.js.map +0 -1
  53. package/dist/test/filehound.js +0 -872
  54. package/dist/test/filehound.js.map +0 -1
  55. package/dist/test/files.js +0 -29
  56. package/dist/test/files.js.map +0 -1
  57. package/docconfig/jsdoc.json +0 -26
  58. package/docconfig/template/LICENSE +0 -61
  59. package/docconfig/template/README.md +0 -86
  60. package/docconfig/template/package.json +0 -22
  61. package/docconfig/template/publish.js +0 -676
  62. package/docconfig/template/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  63. package/docconfig/template/static/fonts/OpenSans-Bold-webfont.svg +0 -1830
  64. package/docconfig/template/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  65. package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  66. package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  67. package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  68. package/docconfig/template/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  69. package/docconfig/template/static/fonts/OpenSans-Italic-webfont.svg +0 -1830
  70. package/docconfig/template/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  71. package/docconfig/template/static/fonts/OpenSans-Light-webfont.eot +0 -0
  72. package/docconfig/template/static/fonts/OpenSans-Light-webfont.svg +0 -1831
  73. package/docconfig/template/static/fonts/OpenSans-Light-webfont.woff +0 -0
  74. package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  75. package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  76. package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  77. package/docconfig/template/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  78. package/docconfig/template/static/fonts/OpenSans-Regular-webfont.svg +0 -1831
  79. package/docconfig/template/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  80. package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.eot +0 -0
  81. package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.svg +0 -1830
  82. package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.ttf +0 -0
  83. package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.woff +0 -0
  84. package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
  85. package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
  86. package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  87. package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
  88. package/docconfig/template/static/scripts/linenumber.js +0 -25
  89. package/docconfig/template/static/scripts/prettify/Apache-License-2.0.txt +0 -202
  90. package/docconfig/template/static/scripts/prettify/lang-css.js +0 -2
  91. package/docconfig/template/static/scripts/prettify/prettify.js +0 -28
  92. package/docconfig/template/static/styles/jsdoc-default.css +0 -605
  93. package/docconfig/template/static/styles/prettify-jsdoc.css +0 -111
  94. package/docconfig/template/static/styles/prettify-tomorrow.css +0 -132
  95. package/docconfig/template/tmpl/augments.tmpl +0 -10
  96. package/docconfig/template/tmpl/container.tmpl +0 -183
  97. package/docconfig/template/tmpl/details.tmpl +0 -143
  98. package/docconfig/template/tmpl/example.tmpl +0 -2
  99. package/docconfig/template/tmpl/examples.tmpl +0 -13
  100. package/docconfig/template/tmpl/exceptions.tmpl +0 -32
  101. package/docconfig/template/tmpl/layout.tmpl +0 -47
  102. package/docconfig/template/tmpl/mainpage.tmpl +0 -10
  103. package/docconfig/template/tmpl/members.tmpl +0 -38
  104. package/docconfig/template/tmpl/method.tmpl +0 -105
  105. package/docconfig/template/tmpl/params.tmpl +0 -124
  106. package/docconfig/template/tmpl/properties.tmpl +0 -108
  107. package/docconfig/template/tmpl/returns.tmpl +0 -19
  108. package/docconfig/template/tmpl/source.tmpl +0 -8
  109. package/docconfig/template/tmpl/tutorial.tmpl +0 -19
  110. package/docconfig/template/tmpl/type.tmpl +0 -7
  111. package/docs/FileHound.html +0 -3079
  112. package/docs/FileHound_modified.html +0 -245
  113. package/docs/_config.yml +0 -1
  114. package/docs/filehound.js.html +0 -697
  115. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  116. package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
  117. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  118. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  119. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  120. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  121. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  122. package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
  123. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  124. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  125. package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
  126. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  127. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  128. package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  129. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  130. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  131. package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
  132. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  133. package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
  134. package/docs/fonts/OpenSans-Semibold-webfont.svg +0 -1830
  135. package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
  136. package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
  137. package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
  138. package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
  139. package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  140. package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
  141. package/docs/global.html +0 -288
  142. package/docs/index.html +0 -154
  143. package/docs/scripts/linenumber.js +0 -25
  144. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  145. package/docs/scripts/prettify/lang-css.js +0 -2
  146. package/docs/scripts/prettify/prettify.js +0 -28
  147. package/docs/styles/jsdoc-default.css +0 -605
  148. package/docs/styles/prettify-jsdoc.css +0 -111
  149. package/docs/styles/prettify-tomorrow.css +0 -132
  150. package/docs.md +0 -164
  151. package/index.js +0 -1
  152. package/issue_template.md +0 -6
  153. package/test/filehound.ts +0 -1076
  154. package/test/files.ts +0 -29
  155. package/test/fixtures/custom/failed.txt +0 -0
  156. package/test/fixtures/custom/passed.txt +0 -0
  157. package/test/fixtures/deeplyNested/c.json +0 -0
  158. package/test/fixtures/deeplyNested/d.json +0 -0
  159. package/test/fixtures/deeplyNested/mydir/e.json +0 -0
  160. package/test/fixtures/deeplyNested/mydir/mydir2/f.json +0 -0
  161. package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
  162. package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/z.json +0 -0
  163. package/test/fixtures/deeplyNested/mydir/mydir2/y.json +0 -0
  164. package/test/fixtures/deeplyNestedWithHiddenDir/c.json +0 -0
  165. package/test/fixtures/deeplyNestedWithHiddenDir/d.json +0 -0
  166. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/e.json +0 -0
  167. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/f.json +0 -0
  168. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/.mydir5/.keep +0 -0
  169. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
  170. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/z.json +0 -0
  171. package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/y.json +0 -0
  172. package/test/fixtures/ext/Dummy.java +0 -0
  173. package/test/fixtures/ext/Dumpy.go +0 -0
  174. package/test/fixtures/ext/dummy.json +0 -0
  175. package/test/fixtures/ext/dummy.txt +0 -0
  176. package/test/fixtures/justFiles/a.json +0 -0
  177. package/test/fixtures/justFiles/b.json +0 -0
  178. package/test/fixtures/justFiles/dummy.txt +0 -0
  179. package/test/fixtures/mixed/a.json +0 -0
  180. package/test/fixtures/mixed/aabbcc.json +0 -0
  181. package/test/fixtures/mixed/ab.json +0 -0
  182. package/test/fixtures/mixed/z.json +0 -0
  183. package/test/fixtures/nested/.hidden1/bad.txt +0 -0
  184. package/test/fixtures/nested/c.json +0 -0
  185. package/test/fixtures/nested/d.json +0 -0
  186. package/test/fixtures/nested/mydir/e.json +0 -0
  187. package/test/fixtures/sizes/10b.txt +0 -0
  188. package/test/fixtures/sizes/1b.txt +0 -0
  189. package/test/fixtures/sizes/1k.txt +0 -0
  190. package/test/fixtures/sizes/2k.txt +0 -0
  191. package/test/fixtures/visibility/.hidden/.hidden.json +0 -0
  192. package/test/fixtures/visibility/.hidden/visible.json +0 -3
  193. package/test/fixtures/visibility/.hidden.json +0 -3
  194. package/test/fixtures/visibility/visible/.hidden.json +0 -3
  195. package/test/fixtures/visibility/visible.json +0 -3
  196. package/test/mocha.opts +0 -4
  197. package/tsconfig.json +0 -16
  198. package/tslint.json +0 -44
package/README.md CHANGED
@@ -1,39 +1,61 @@
1
1
  # Filehound
2
2
 
3
- ![npm](https://img.shields.io/npm/v/filehound.svg)
4
- [![Build Status](https://travis-ci.org/nspragg/filehound.svg)](https://travis-ci.org/nspragg/filehound) [![Coverage Status](https://coveralls.io/repos/github/nspragg/filehound/badge.svg?branch=master)](https://coveralls.io/github/nspragg/filehound?branch=master)
3
+ [![NPM downloads](https://img.shields.io/npm/dm/filehound.svg?style=flat)](https://npmjs.org/package/filehound)
4
+ [![CI](https://github.com/nspragg/filehound/actions/workflows/ci.yml/badge.svg)](https://github.com/nspragg/filehound/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/nspragg/filehound/badge.svg?branch=master)](https://coveralls.io/github/nspragg/filehound?branch=master)
5
5
  ![license](https://img.shields.io/badge/license-MIT-blue.svg)
6
6
  ![github-issues](https://img.shields.io/github/issues/nspragg/filehound.svg)
7
7
  ![stars](https://img.shields.io/github/stars/nspragg/filehound.svg)
8
8
  ![forks](https://img.shields.io/github/forks/nspragg/filehound.svg)
9
9
  > Flexible and fluent interface for searching the file system
10
10
 
11
- ![nodei.co](https://nodei.co/npm/filehound.png?downloads=true&downloadRank=true&stars=true)
12
-
13
-
14
11
  ## Installation
15
12
 
16
13
  ```
17
14
  npm install --save filehound
18
15
  ```
19
16
 
20
- ## Demo
17
+ Requires Node.js 24 or later.
21
18
 
22
- <img src="https://cloud.githubusercontent.com/assets/917111/13683231/7e915c2c-e6fd-11e5-9d58-e7228cf76ccf.gif" width="600"/>
19
+ Upgrading from V1? See the [migration guide](./MIGRATING.md).
23
20
 
24
21
  ## Usage
25
22
 
26
23
  The example below prints all of the files in a directory that have the `.json` file extension:
27
24
 
28
25
  ```js
29
- const FileHound = require('filehound');
26
+ import { FileHound, ext } from 'filehound';
30
27
 
31
- const files = FileHound.create()
28
+ const files = await FileHound.create()
32
29
  .paths('/some/dir')
33
- .ext('json')
30
+ .match(ext('json'))
34
31
  .find();
35
32
 
36
- files.then(console.log);
33
+ console.log(files);
34
+ ```
35
+
36
+ ### Iterating over matches
37
+
38
+ `find()` waits for the whole search to finish. To handle each match as it's found, loop over `iterate()` instead. Matches come in the same order as `find()`, and the search only runs a little ahead of the loop, so a large search doesn't hold every match in memory:
39
+
40
+ ```js
41
+ import { FileHound, ext } from 'filehound';
42
+
43
+ const query = FileHound.create()
44
+ .paths('/some/dir')
45
+ .match(ext('json'));
46
+
47
+ for await (const file of query.iterate()) {
48
+ console.log(file);
49
+ }
50
+ ```
51
+
52
+ Leaving the loop early stops the search, for example to find the first match only:
53
+
54
+ ```js
55
+ for await (const file of query.iterate()) {
56
+ console.log(`First match: ${file}`);
57
+ break;
58
+ }
37
59
  ```
38
60
 
39
61
  ## Documentation
@@ -53,3 +75,7 @@ npm run coverage
53
75
  ## Contributing
54
76
 
55
77
  See [contributing guide](./CONTRIBUTING.md)
78
+
79
+ ## Related modules
80
+
81
+ - Finding files by content - [FileSniffer](https://github.com/nspragg/filesniffer)
@@ -0,0 +1 @@
1
+ export declare function from<T>(args: (T | T[])[]): T[];
package/dist/arrays.js ADDED
@@ -0,0 +1,4 @@
1
+ export function from(args) {
2
+ const [first] = args;
3
+ return Array.isArray(first) ? first : args;
4
+ }
package/dist/file.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ import { type Dirent, type Stats } from 'node:fs';
2
+ export type FileKind = Pick<Stats, 'isDirectory' | 'isFile'>;
3
+ /**
4
+ * A read-only view of a file found by a search, as passed to predicates such
5
+ * as {@link applyFilter}. It has no methods that change the file system.
6
+ *
7
+ * @remarks
8
+ * Predicates may be called ahead of the matches being returned, at the same
9
+ * time as each other, and on files that are never returned, for example when
10
+ * a loop over {@link FileHound.iterate} is left early. So a predicate should
11
+ * have no side effects.
12
+ *
13
+ * A file's stats are fetched once, then shared by every predicate and by
14
+ * {@link FileHound.includeFileStats}.
15
+ */
16
+ export interface FileView {
17
+ /** The path, as found from the search path */
18
+ getName(): string;
19
+ /** The absolute path */
20
+ getAbsolutePath(): string;
21
+ /** The absolute path, normalised */
22
+ getCanonicalPath(): string;
23
+ /** The path relative to the search path */
24
+ getRelativePath(): string;
25
+ /** The extension, without the leading dot, or an empty string */
26
+ getPathExtension(): string;
27
+ /** Whether the path, relative to the search path, matches a glob pattern */
28
+ isMatch(globPattern: string): boolean;
29
+ /** Whether this is a directory, or a link to one */
30
+ isDirectory(): Promise<boolean>;
31
+ /** Whether this is a directory, or a link to one */
32
+ isDirectorySync(): boolean;
33
+ /** Whether this is a regular file, or a link to one */
34
+ isFile(): Promise<boolean>;
35
+ /** Whether this is a regular file, or a link to one */
36
+ isFileSync(): boolean;
37
+ /** Whether this is a symbolic link */
38
+ isSymbolicLink(): Promise<boolean>;
39
+ /** Whether this is a symbolic link */
40
+ isSymbolicLinkSync(): boolean;
41
+ /** Whether this is hidden: its name, or for a directory any in its path, begins with a dot */
42
+ isHidden(): Promise<boolean>;
43
+ /** Whether this is hidden: its name, or for a directory any in its path, begins with a dot */
44
+ isHiddenSync(): boolean;
45
+ /** The stats, as returned by `fs.stat` */
46
+ getStats(): Promise<Stats>;
47
+ /** The stats, as returned by `fs.statSync` */
48
+ getStatsSync(): Stats;
49
+ /** The size in bytes */
50
+ size(): Promise<number>;
51
+ /** The size in bytes */
52
+ sizeSync(): number;
53
+ /** When the contents were last modified */
54
+ lastModified(): Promise<Date>;
55
+ /** When the contents were last modified */
56
+ lastModifiedSync(): Date;
57
+ /** When the file was last accessed */
58
+ lastAccessed(): Promise<Date>;
59
+ /** When the file was last accessed */
60
+ lastAccessedSync(): Date;
61
+ /** When the file's status, such as its permissions, last changed */
62
+ lastChanged(): Promise<Date>;
63
+ /** When the file's status, such as its permissions, last changed */
64
+ lastChangedSync(): Date;
65
+ /** Whether the file exists and can be read */
66
+ exists(): Promise<boolean>;
67
+ /** Whether the file can be read */
68
+ isReadable(): Promise<boolean>;
69
+ /** Whether the file can be written to */
70
+ isWritable(): Promise<boolean>;
71
+ /** Whether the file can be executed */
72
+ isExecutable(): Promise<boolean>;
73
+ }
74
+ export declare function getKnownKind(file: File): FileKind | undefined;
75
+ export declare function getChildren(dir: File): Promise<File[]>;
76
+ export declare function getChildrenSync(dir: File): File[];
77
+ export declare class File implements FileView {
78
+ #private;
79
+ constructor(pathname: string, dirent?: Dirent, root?: string);
80
+ isDirectorySync(): boolean;
81
+ isFileSync(): boolean;
82
+ isDirectory(): Promise<boolean>;
83
+ isFile(): Promise<boolean>;
84
+ isSymbolicLinkSync(): boolean;
85
+ isSymbolicLink(): Promise<boolean>;
86
+ isHiddenSync(): boolean;
87
+ isHidden(): Promise<boolean>;
88
+ getName(): string;
89
+ getAbsolutePath(): string;
90
+ getCanonicalPath(): string;
91
+ getRelativePath(): string;
92
+ getPathExtension(): string;
93
+ lastModifiedSync(): Date;
94
+ lastAccessedSync(): Date;
95
+ lastChangedSync(): Date;
96
+ sizeSync(): number;
97
+ lastModified(): Promise<Date>;
98
+ lastAccessed(): Promise<Date>;
99
+ lastChanged(): Promise<Date>;
100
+ size(): Promise<number>;
101
+ getStatsSync(): Stats;
102
+ getStats(): Promise<Stats>;
103
+ isWritable(): Promise<boolean>;
104
+ isReadable(): Promise<boolean>;
105
+ isExecutable(): Promise<boolean>;
106
+ exists(): Promise<boolean>;
107
+ isMatch(globPattern: string): boolean;
108
+ }
package/dist/file.js ADDED
@@ -0,0 +1,184 @@
1
+ var _a;
2
+ import { constants, lstatSync, readdirSync, statSync } from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as fsp from 'node:fs/promises';
5
+ // What only the walker needs. These are functions rather than methods, so a
6
+ // predicate can't reach them. Only code inside File can see its private
7
+ // fields, so they're set from its static block.
8
+ let knownKindOf;
9
+ let childrenOf;
10
+ let childrenOfSync;
11
+ // The kind readdir gave, so needs no I/O. Undefined for links, whose targets
12
+ // are reported, and for files made without a dirent, such as a walk's root.
13
+ export function getKnownKind(file) {
14
+ return knownKindOf(file);
15
+ }
16
+ // A directory's entries, with their kinds from readdir
17
+ export function getChildren(dir) {
18
+ return childrenOf(dir);
19
+ }
20
+ export function getChildrenSync(dir) {
21
+ return childrenOfSync(dir);
22
+ }
23
+ export class File {
24
+ #dir;
25
+ #pathname;
26
+ // Type info from readdir, saving a stat call
27
+ #dirent;
28
+ // The directory a search started from; glob patterns are matched relative to it
29
+ #root;
30
+ // Fetched once, so predicates and results share a single stat call
31
+ #stats;
32
+ static {
33
+ knownKindOf = file => file.#dirent?.isSymbolicLink() ? undefined : file.#dirent;
34
+ childrenOf = async (dir) => dir.#children();
35
+ childrenOfSync = dir => dir.#childrenSync();
36
+ }
37
+ constructor(pathname, dirent, root) {
38
+ this.#dir = process.cwd();
39
+ this.#pathname = pathname;
40
+ this.#dirent = dirent;
41
+ this.#root = root;
42
+ }
43
+ isDirectorySync() {
44
+ return this.#getKindSync().isDirectory();
45
+ }
46
+ isFileSync() {
47
+ return this.#getKindSync().isFile();
48
+ }
49
+ async isDirectory() {
50
+ return (await this.#getKind()).isDirectory();
51
+ }
52
+ async isFile() {
53
+ return (await this.#getKind()).isFile();
54
+ }
55
+ isSymbolicLinkSync() {
56
+ return this.#dirent?.isSymbolicLink() ?? lstatSync(this.#pathname).isSymbolicLink();
57
+ }
58
+ async isSymbolicLink() {
59
+ return this.#dirent?.isSymbolicLink() ?? (await fsp.lstat(this.#pathname)).isSymbolicLink();
60
+ }
61
+ isHiddenSync() {
62
+ if (!this.isDirectorySync()) {
63
+ return this.#isHiddenFile();
64
+ }
65
+ return this.#isHiddenDirectory();
66
+ }
67
+ async isHidden() {
68
+ if (!await this.isDirectory()) {
69
+ return this.#isHiddenFile();
70
+ }
71
+ return this.#isHiddenDirectory();
72
+ }
73
+ getName() {
74
+ return this.#pathname;
75
+ }
76
+ getAbsolutePath() {
77
+ return path.resolve(this.#dir, this.#pathname);
78
+ }
79
+ getCanonicalPath() {
80
+ return path.normalize(this.getAbsolutePath());
81
+ }
82
+ // Relative to the search root, or the working directory like fs.glob
83
+ getRelativePath() {
84
+ return path.relative(this.#root ?? this.#dir, this.#pathname);
85
+ }
86
+ getPathExtension() {
87
+ return path.extname(this.#pathname).substring(1);
88
+ }
89
+ lastModifiedSync() {
90
+ return this.getStatsSync().mtime;
91
+ }
92
+ lastAccessedSync() {
93
+ return this.getStatsSync().atime;
94
+ }
95
+ lastChangedSync() {
96
+ return this.getStatsSync().ctime;
97
+ }
98
+ sizeSync() {
99
+ return this.getStatsSync().size;
100
+ }
101
+ async lastModified() {
102
+ return (await this.getStats()).mtime;
103
+ }
104
+ async lastAccessed() {
105
+ return (await this.getStats()).atime;
106
+ }
107
+ async lastChanged() {
108
+ return (await this.getStats()).ctime;
109
+ }
110
+ async size() {
111
+ return (await this.getStats()).size;
112
+ }
113
+ getStatsSync() {
114
+ this.#stats ??= statSync(this.#pathname);
115
+ return this.#stats;
116
+ }
117
+ async getStats() {
118
+ this.#stats ??= await fsp.stat(this.#pathname);
119
+ return this.#stats;
120
+ }
121
+ async isWritable() {
122
+ return this.#access(constants.W_OK);
123
+ }
124
+ async isReadable() {
125
+ return this.#access(constants.R_OK);
126
+ }
127
+ async isExecutable() {
128
+ return this.#access(constants.X_OK);
129
+ }
130
+ async exists() {
131
+ return this.#access(constants.R_OK);
132
+ }
133
+ isMatch(globPattern) {
134
+ return path.matchesGlob(this.getRelativePath(), globPattern);
135
+ }
136
+ async #children() {
137
+ if (!await this.isDirectory()) {
138
+ return [];
139
+ }
140
+ const prefix = this.#childPrefix();
141
+ return (await fsp.readdir(this.#pathname, { withFileTypes: true })).map(entry => this.#child(prefix, entry));
142
+ }
143
+ #childrenSync() {
144
+ if (!this.isDirectorySync()) {
145
+ return [];
146
+ }
147
+ const prefix = this.#childPrefix();
148
+ return readdirSync(this.#pathname, { withFileTypes: true }).map(entry => this.#child(prefix, entry));
149
+ }
150
+ // path.join is the costliest step of a walk, so it runs once per directory
151
+ // rather than once per entry. Entry names never contain a separator, so
152
+ // prefix + name is exactly what path.join would give.
153
+ #childPrefix() {
154
+ return path.join(this.#pathname, '_').slice(0, -1);
155
+ }
156
+ #child(prefix, entry) {
157
+ return new _a(prefix + entry.name, entry, this.#root ?? this.#pathname);
158
+ }
159
+ #getKindSync() {
160
+ return knownKindOf(this) ?? this.getStatsSync();
161
+ }
162
+ async #getKind() {
163
+ return knownKindOf(this) ?? this.getStats();
164
+ }
165
+ #isHiddenFile() {
166
+ return (/^\./).test(path.basename(this.#pathname));
167
+ }
168
+ // Normalising first means either separator is handled on Windows
169
+ #isHiddenDirectory() {
170
+ return path.normalize(this.#pathname)
171
+ .split(path.sep)
172
+ .some(segment => segment.startsWith('.') && segment !== '.' && segment !== '..');
173
+ }
174
+ async #access(permission) {
175
+ try {
176
+ await fsp.access(this.#pathname, permission);
177
+ }
178
+ catch {
179
+ return false;
180
+ }
181
+ return true;
182
+ }
183
+ }
184
+ _a = File;
@@ -0,0 +1,322 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Stats } from 'node:fs';
3
+ import { type FilePredicate } from './matchers.js';
4
+ import { FileType } from './filetype.js';
5
+ /**
6
+ * Options for {@link FileHound.find} and {@link FileHound.iterate}.
7
+ */
8
+ export type SearchOptions = {
9
+ /**
10
+ * Stops the search when aborted. The search then rejects with the
11
+ * signal's reason, which is an `AbortError` unless another is given.
12
+ */
13
+ signal?: AbortSignal;
14
+ };
15
+ /**
16
+ * A match returned when {@link FileHound.includeFileStats} is set.
17
+ */
18
+ export type FileWithStats = {
19
+ /** the path of the matching file */
20
+ path: string;
21
+ /** the file's stats, as returned by `fs.stat` */
22
+ stats: Stats;
23
+ };
24
+ /**
25
+ * A fluent query for searching the file system.
26
+ *
27
+ * @typeParam T - the type of each match: a path, or a {@link FileWithStats}
28
+ * when {@link FileHound.includeFileStats} is set
29
+ *
30
+ * @remarks
31
+ * Configure a query by chaining methods, then run it with {@link FileHound.find}
32
+ * or {@link FileHound.findSync}. Once a search has started the query is locked
33
+ * and can no longer be modified.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { FileHound, ext } from 'filehound';
38
+ *
39
+ * const files = await FileHound.create()
40
+ * .paths('/tmp')
41
+ * .match(ext('json'))
42
+ * .depth(1)
43
+ * .find();
44
+ * ```
45
+ */
46
+ export declare class FileHound<T = string> extends EventEmitter {
47
+ #private;
48
+ constructor();
49
+ /**
50
+ * Static factory method to create an instance of FileHound
51
+ *
52
+ * @returns a new FileHound instance
53
+ * @example
54
+ * ```ts
55
+ * import { FileHound } from 'filehound';
56
+ *
57
+ * const filehound = FileHound.create();
58
+ * ```
59
+ */
60
+ static create(): FileHound;
61
+ /**
62
+ * Executes one or more FileHound queries and combines their results
63
+ *
64
+ * @param args - the FileHound instances to execute
65
+ * @returns a Promise of all matches. If the Promise fulfils,
66
+ * the fulfilment value is an array of all files matched by any of the queries
67
+ * @example
68
+ * ```ts
69
+ * import { FileHound } from 'filehound';
70
+ *
71
+ * const files = await FileHound.any(fh1, fh2);
72
+ * ```
73
+ */
74
+ static any<T>(...args: FileHound<T>[]): Promise<T[]>;
75
+ /**
76
+ * Defines the search paths: the directories to search. Duplicate paths are
77
+ * removed and each path is normalised. Defaults to the current working directory.
78
+ *
79
+ * @param paths - one or more directory paths
80
+ * @returns the FileHound instance
81
+ * @throws `Error`
82
+ * Thrown if the query is locked because a search has started. A path that
83
+ * doesn't exist, or isn't a directory, is rejected when the search runs.
84
+ * @example
85
+ * ```ts
86
+ * import { FileHound } from 'filehound';
87
+ *
88
+ * const files = await FileHound.create()
89
+ * .paths('/tmp', '/etc') // or ['/tmp', '/etc']
90
+ * .find();
91
+ * ```
92
+ */
93
+ paths(...paths: string[]): this;
94
+ /**
95
+ * Defines the search paths from an array.
96
+ *
97
+ * @param paths - an array of paths
98
+ * @returns the FileHound instance
99
+ */
100
+ paths(paths: string[]): this;
101
+ /**
102
+ * Specifies the criteria that files must satisfy to be included in the
103
+ * results. Defaults to matching all files.
104
+ *
105
+ * @param query - the predicate used to test each file
106
+ * @returns the FileHound instance
107
+ * @throws `Error`
108
+ * Thrown if the query is locked because a search has started.
109
+ * @throws `TypeError`
110
+ * Thrown if the query is not a valid matcher.
111
+ * @example
112
+ * ```ts
113
+ * import { FileHound, ext } from 'filehound';
114
+ *
115
+ * const files = await FileHound.create()
116
+ * .match(ext('json'))
117
+ * .find();
118
+ * ```
119
+ */
120
+ match(query: FilePredicate): this;
121
+ /**
122
+ * Specify the directory search depth. If set to zero, recursive searching
123
+ * will be disabled. Directories at the limit are not searched, but are
124
+ * still returned when {@link FileHound.type} includes directories.
125
+ *
126
+ * @param depth - the maximum search depth (a non-negative integer)
127
+ * @returns the FileHound instance
128
+ * @throws `Error`
129
+ * Thrown if the query is locked because a search has started.
130
+ * @throws `RangeError`
131
+ * Thrown if depth is negative or not an integer.
132
+ * @example
133
+ * ```ts
134
+ * import { FileHound } from 'filehound';
135
+ *
136
+ * const files = await FileHound.create()
137
+ * .depth(0)
138
+ * .find(); // array of file names only in the current directory
139
+ * ```
140
+ */
141
+ depth(depth: number): this;
142
+ /**
143
+ * Specifies the types of file system entry to return. Defaults to
144
+ * regular files only.
145
+ *
146
+ * @param types - one or more file types
147
+ * @returns the FileHound instance
148
+ * @throws `Error`
149
+ * Thrown if the query is locked because a search has started.
150
+ * @throws `RangeError`
151
+ * Thrown if no file types are given.
152
+ * @throws `TypeError`
153
+ * Thrown if an invalid file type is given.
154
+ * @example
155
+ * ```ts
156
+ * import { FileHound, FileType } from 'filehound';
157
+ *
158
+ * const directories = await FileHound.create()
159
+ * .type(FileType.DIRECTORY)
160
+ * .find();
161
+ * ```
162
+ */
163
+ type(...types: FileType[]): this;
164
+ /**
165
+ * Excludes hidden files (names beginning with a dot) from the results
166
+ *
167
+ * @returns the FileHound instance
168
+ * @throws `Error`
169
+ * Thrown if the query is locked because a search has started.
170
+ * @example
171
+ * ```ts
172
+ * import { FileHound } from 'filehound';
173
+ *
174
+ * const files = await FileHound.create()
175
+ * .ignoreHiddenFiles()
176
+ * .find();
177
+ * ```
178
+ */
179
+ ignoreHiddenFiles(): this;
180
+ /**
181
+ * Excludes hidden directories (names beginning with a dot), and their
182
+ * contents, from the search
183
+ *
184
+ * @returns the FileHound instance
185
+ * @throws `Error`
186
+ * Thrown if the query is locked because a search has started.
187
+ * @example
188
+ * ```ts
189
+ * import { FileHound } from 'filehound';
190
+ *
191
+ * const files = await FileHound.create()
192
+ * .ignoreHiddenDirectories()
193
+ * .find();
194
+ * ```
195
+ */
196
+ ignoreHiddenDirectories(): this;
197
+ /**
198
+ * Excludes symbolic links from the search. Linked files are not matched
199
+ * and linked directories are not searched.
200
+ *
201
+ * @remarks
202
+ * By default, symbolic links are followed: a linked file is matched as its
203
+ * target and a linked directory is searched. Broken links, and links back to
204
+ * a directory already being searched, are always skipped.
205
+ *
206
+ * @returns the FileHound instance
207
+ * @throws `Error`
208
+ * Thrown if the query is locked because a search has started.
209
+ * @example
210
+ * ```ts
211
+ * import { FileHound } from 'filehound';
212
+ *
213
+ * const files = await FileHound.create()
214
+ * .ignoreSymlinks()
215
+ * .find();
216
+ * ```
217
+ */
218
+ ignoreSymlinks(): this;
219
+ /**
220
+ * Returns each match with its stats, as `{ path, stats }`, in place of
221
+ * its path. Stats already fetched by a predicate are reused.
222
+ *
223
+ * @returns the FileHound instance, typed to return {@link FileWithStats} matches
224
+ * @throws `Error`
225
+ * Thrown if the query is locked because a search has started.
226
+ * @example
227
+ * ```ts
228
+ * import { FileHound } from 'filehound';
229
+ *
230
+ * const files = await FileHound.create()
231
+ * .includeFileStats()
232
+ * .find();
233
+ *
234
+ * files.forEach(({ path, stats }) => console.log(path, stats.size));
235
+ * ```
236
+ */
237
+ includeFileStats(): FileHound<FileWithStats>;
238
+ /**
239
+ * Asynchronously executes a file search. Once called, the query is
240
+ * locked and can no longer be modified.
241
+ *
242
+ * @param options - a signal to stop the search
243
+ * @returns a Promise of all matches. If the Promise fulfils,
244
+ * the fulfilment value is an array of all matching files
245
+ *
246
+ * @remarks
247
+ * Emits a `match` event for each matching file, an `error` event if the
248
+ * search fails or is aborted and an `end` event when the search completes.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * import { FileHound } from 'filehound';
253
+ *
254
+ * const filehound = FileHound.create();
255
+ * const files = await filehound.find();
256
+ *
257
+ * console.log(files);
258
+ * ```
259
+ * @example
260
+ * Giving up on a search after five seconds:
261
+ * ```ts
262
+ * const files = await FileHound.create()
263
+ * .paths('/')
264
+ * .find({ signal: AbortSignal.timeout(5000) });
265
+ * ```
266
+ */
267
+ find(options?: SearchOptions): Promise<T[]>;
268
+ /**
269
+ * Asynchronously executes a file search, returning matches one at a time
270
+ * as they are found, in the same order as {@link FileHound.find}. Once
271
+ * called, the query is locked and can no longer be modified.
272
+ *
273
+ * @remarks
274
+ * The search runs ahead of the loop by a bounded amount, so a large search
275
+ * can be read without holding every match in memory. Breaking out of the
276
+ * loop stops the search.
277
+ *
278
+ * Emits a `match` event as each match is returned, an `error` event if the
279
+ * search fails or is aborted and an `end` event when the search finishes:
280
+ * when every match has been returned, the search fails or is aborted, or
281
+ * the loop is left early.
282
+ *
283
+ * @param options - a signal to stop the search
284
+ * @returns an async iterator of matches
285
+ * @example
286
+ * ```ts
287
+ * import { FileHound, ext } from 'filehound';
288
+ *
289
+ * const query = FileHound.create()
290
+ * .paths('/tmp')
291
+ * .match(ext('json'));
292
+ *
293
+ * for await (const file of query.iterate()) {
294
+ * console.log(file);
295
+ * }
296
+ * ```
297
+ */
298
+ iterate(options?: SearchOptions): AsyncGenerator<T>;
299
+ /**
300
+ * Synchronously executes a file search. Once called, the query is
301
+ * locked and can no longer be modified. No events are emitted.
302
+ *
303
+ * @returns an array of all matching files
304
+ * @example
305
+ * ```ts
306
+ * import { FileHound } from 'filehound';
307
+ *
308
+ * const filehound = FileHound.create();
309
+ * const files = filehound.findSync();
310
+ * console.log(files);
311
+ * ```
312
+ */
313
+ findSync(): T[];
314
+ /**
315
+ * Returns the paths that will be searched. When no depth is set, any path
316
+ * that is a subdirectory of another search path is removed, as it would
317
+ * otherwise be searched twice.
318
+ *
319
+ * @returns a copy of the search paths
320
+ */
321
+ getPaths(): string[];
322
+ }