filter-scan-dir 2.1.3 → 2.1.5

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/README.md CHANGED
@@ -7,7 +7,10 @@
7
7
 
8
8
  [![npm badge][npm-badge-png]][package-url]
9
9
 
10
- Recursively scan and filter directory for a flat array of files.
10
+ Extremely fast recursive directory crawling and filtering for Node.js.
11
+ Returns a flat array of file paths.
12
+ It can scan 1 million files in about half a second with a warm OS cache and `fullStat: false`.
13
+ See [Performance](#performance) for the measured setup.
11
14
 
12
15
  - Supports super fast concurrent mode in async version.
13
16
 
@@ -34,8 +37,109 @@ console.log(await filterScanDir({ cwd: "test" }));
34
37
 
35
38
  - **[API Docs]**
36
39
 
40
+ # Filtering
41
+
42
+ | Option | Purpose |
43
+ | --- | --- |
44
+ | `ignoreDirs` | Skip exact directory basenames at every depth. Accepts a string or array. |
45
+ | `prefilter` | Reject entries before `lstat`. Requires `fullStat: true`. |
46
+ | `filterExt` | Include only matching extensions. |
47
+ | `ignoreExt` | Exclude matching extensions. |
48
+ | `filter` | Decide whether to include each file. |
49
+ | `filterDir` | Skip directories before scanning their children. |
50
+
51
+ `filter` and `filterDir` receive `(name, relativeDir, extras)`.
52
+ Return `true` to accept an entry or `false` to skip it.
53
+ Directories enter the output only with `includeDir: true`.
54
+ Return `{ stop: true }` to stop the scan.
55
+ With `grouping: true`, return a string to choose a result group.
56
+
57
+ Use `prefilter` when only some entries need full metadata:
58
+
59
+ ```ts
60
+ const files = await filterScanDir({
61
+ cwd: ".",
62
+ fullStat: true,
63
+ ignoreDirs: ["node_modules", ".git"],
64
+ prefilter: (name, _dir, entry) => entry.isDirectory() || name.endsWith(".ts"),
65
+ filter: (_name, _dir, { stat }) => stat.size < 100_000,
66
+ });
67
+ ```
68
+
69
+ `prefilter` receives `(name, relativeDir, Dirent)` and runs synchronously.
70
+ Return `false` to reject an entry. Rejecting a directory skips its entire subtree.
71
+ `prefilter` with `fullStat: false` throws before scanning, regardless of `rethrowError`.
72
+
73
+ The order is `ignoreDirs`, `prefilter`, extension filters, `lstat`, then `filter` or `filterDir`.
74
+ Extension filters apply only to non-directory entries.
75
+ Early rejections avoid `lstat`, so rejected entries cannot report `lstat` errors.
76
+ `ignoreDirs` matches names, not paths or glob patterns. Symlinks are never followed.
77
+
78
+ # Performance
79
+
80
+ `fullStat: false` gets entry types from `readdir` as `Dirent` objects.
81
+ This avoids a separate `lstat` call for every file and directory entry.
82
+ In our warm-cache tests, it was **2.3× faster on the [fynmesh repo](https://www.fynmesh.win)** than `fullStat: true`.
83
+ It was **8.1× faster on the 1,000 × 1,000 synthetic tree**.
84
+
85
+ Use it when names and entry types are enough:
86
+
87
+ ```ts
88
+ const files = await filterScanDir({ cwd: "src", fullStat: false });
89
+ ```
90
+
91
+ Filter callbacks receive `Dirent` objects instead of `Stats` objects.
92
+ Sizes, timestamps, and permissions are unavailable.
93
+
94
+ The default concurrency is `50`.
95
+ Higher concurrency is not always faster. `concurrency: Infinity` removes the limit.
96
+ More concurrent reads can increase memory use. Benchmark your directory tree before changing it.
97
+ `filterScanDirSync` blocks the event loop.
98
+
99
+ Keep Dirent mode for filters that only need names or entry types:
100
+
101
+ ```ts
102
+ const files = await filterScanDir({
103
+ cwd: ".",
104
+ fullStat: false,
105
+ filterExt: [".js", ".ts"],
106
+ ignoreDirs: ["node_modules", ".git"],
107
+ });
108
+ ```
109
+
110
+ - `filterDir` can skip entire subtrees.
111
+ - Extension filters still read every visited directory. Rejected entries skip `lstat` in full-stat mode.
112
+ - Custom callbacks add work per entry.
113
+ - `sortFiles: true` adds sorting work per directory.
114
+
115
+ Use the default `fullStat: true` for full metadata.
116
+ The extra `lstat` calls can make scans much slower.
117
+ The cost is higher on cold caches or slow storage.
118
+
119
+ Scan times depend on storage, directory layout, and OS cache state.
120
+ Warm-cache measurements do not predict cold-cache performance.
121
+ Scanning lists entries without reading file contents.
122
+
123
+ ## Example measurements
124
+
125
+ These are medians from seven warm-cache runs.
126
+ Both modes used concurrency `50`.
127
+ No filters or sorting were enabled. Symlinks were excluded.
128
+
129
+ Test system: Node.js 22.22.2 on macOS, Apple M4 Pro, 24 GB RAM.
130
+
131
+ | Tree | Files | Async `fullStat: false` | Async `fullStat: true` |
132
+ | --- | ---: | ---: | ---: |
133
+ | Synthetic: 1,000 directories with 1,000 empty files each | 1,000,000 | 551 ms | 4,483 ms |
134
+ | [fynmesh repo](https://www.fynmesh.win) with installed dependencies: 48,347 directories | 292,242 | 949 ms | 2,157 ms |
135
+
136
+ The fynmesh repo scan included `node_modules` and `.git`.
137
+ Its directory count includes the root.
138
+
37
139
  # License
38
140
 
141
+ Copyright (c) 2022-2026 Joel Chen
142
+
39
143
  Licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
40
144
 
41
145
  [npm-version-svg]: https://badge.fury.io/js/filter-scan-dir.svg
package/dist/index.d.ts CHANGED
@@ -2,13 +2,13 @@ import { Dirent, Stats } from "fs";
2
2
  /**
3
3
  * type of the 3rd argument for the filter callback
4
4
  */
5
- export type ExtrasData = {
5
+ export type ExtrasData<Stat extends Dirent | Stats = Dirent | Stats> = {
6
6
  /** name of the file being considered */
7
7
  file: string;
8
8
  /** path to the directory being processed that contains the file */
9
9
  path: string;
10
10
  /** result from fs.lstat or readdir */
11
- stat: Dirent | Stats;
11
+ stat: Stat;
12
12
  /** full path with cwd + path + file */
13
13
  fullFile: string;
14
14
  /** path to file without cwd: path + file */
@@ -28,7 +28,7 @@ export type FilterInfo = {
28
28
  group?: string;
29
29
  /** if `true` then skip the file or directory, else add it */
30
30
  skip?: boolean;
31
- /** stop the scanning and return the result immediately. */
31
+ /** stop all filtering; async scans drain reads already in flight before returning. */
32
32
  stop?: boolean;
33
33
  /** if not `undefined`, then use this as the value to add to the output */
34
34
  formatName?: string;
@@ -49,11 +49,11 @@ export type FilterResult = boolean | string | FilterInfo;
49
49
  * first level directory.
50
50
  * @param extras - extras data
51
51
  */
52
- export type FilterCallback = (file: string, path: string, extras: ExtrasData) => FilterResult;
52
+ export type FilterCallback<Stat extends Dirent | Stats = Dirent | Stats> = (file: string, path: string, extras: ExtrasData<Stat>) => FilterResult;
53
53
  /**
54
54
  * Options for filterScanDir
55
55
  */
56
- export type Options = {
56
+ export type Options<FullStat extends boolean = boolean> = {
57
57
  /** current working directory to start scanning */
58
58
  cwd?: string;
59
59
  /**
@@ -79,9 +79,10 @@ export type Options = {
79
79
  * - *Default*: `true` - for significant performance improvement, set this to `false`
80
80
  *
81
81
  */
82
- fullStat?: boolean;
82
+ fullStat?: FullStat;
83
83
  /**
84
- * for async version only - numer of directories to process concurrently. *Default*: `50`
84
+ * for async version only - maximum directories reading entries or metadata concurrently.
85
+ * *Default*: `50`. Ancestors waiting for child directories do not consume a slot.
85
86
  *
86
87
  * - Set this to `0` or `1` to disable concurrent mode
87
88
  *
@@ -93,9 +94,16 @@ export type Options = {
93
94
  /** set to `true` to throw errors instead of ignoring them */
94
95
  rethrowError?: boolean;
95
96
  /** callback to filter files. */
96
- filter?: FilterCallback;
97
+ filter?: FilterCallback<FullStat extends false ? Dirent : Stats>;
97
98
  /** callback to filter directories. */
98
- filterDir?: FilterCallback;
99
+ filterDir?: FilterCallback<FullStat extends false ? Dirent : Stats>;
100
+ /** directory basenames to skip at every depth, before reading metadata or children */
101
+ ignoreDirs?: string | string[];
102
+ /**
103
+ * synchronous entry filter before lstat; returning false also prunes directories.
104
+ * Requires fullStat to be true (the default). Throws when fullStat is false.
105
+ */
106
+ prefilter?: (file: string, path: string, entry: Dirent) => boolean;
99
107
  /** array or string of extensions to ignore. ext must include `.`, ie: `".js"` */
100
108
  ignoreExt?: string | string[];
101
109
  /** array or string of extensions to include only, apply after `ignoreExt` */
@@ -107,17 +115,19 @@ export type Options = {
107
115
  * - If you didn't specify this, then `cwd` is automatically converted to use `/`.
108
116
  */
109
117
  pathSep?: string;
110
- };
118
+ } & ([FullStat] extends [false] ? {
119
+ fullStat: false;
120
+ } : {});
111
121
  /**
112
122
  * options specifically to set grouping flag `true` to enable grouping of files
113
123
  */
114
- export type GroupingOptions = {
124
+ export type GroupingOptions<FullStat extends boolean = boolean> = {
115
125
  /**
116
126
  * enable grouping of files
117
127
  * This is default to disabled, so it's only expecting `true` to enable it.
118
128
  */
119
129
  grouping: true;
120
- } & Options;
130
+ } & Options<FullStat>;
121
131
  /**
122
132
  * The scanned result if grouping is enabled.
123
133
  *
@@ -133,9 +143,17 @@ export type GroupingResult = {
133
143
  * @returns
134
144
  */
135
145
  export declare function filterScanDir(options?: string): Promise<string[]>;
146
+ export declare function filterScanDir(options: GroupingOptions<true>): Promise<GroupingResult>;
147
+ export declare function filterScanDir(options: GroupingOptions<false>): Promise<GroupingResult>;
148
+ export declare function filterScanDir(options: GroupingOptions): Promise<GroupingResult>;
149
+ export declare function filterScanDir(options: Options<true>): Promise<string[]>;
150
+ export declare function filterScanDir(options: Options<false>): Promise<string[]>;
136
151
  export declare function filterScanDir(options?: Options): Promise<string[]>;
137
- export declare function filterScanDir(options?: GroupingOptions): Promise<GroupingResult>;
138
152
  /** sync version of filter scan dir */
139
153
  export declare function filterScanDirSync(options?: string): string[];
154
+ export declare function filterScanDirSync(options: GroupingOptions<true>): GroupingResult;
155
+ export declare function filterScanDirSync(options: GroupingOptions<false>): GroupingResult;
156
+ export declare function filterScanDirSync(options: GroupingOptions): GroupingResult;
157
+ export declare function filterScanDirSync(options: Options<true>): string[];
158
+ export declare function filterScanDirSync(options: Options<false>): string[];
140
159
  export declare function filterScanDirSync(options?: Options): string[];
141
- export declare function filterScanDirSync(options?: GroupingOptions): GroupingResult;
package/dist/index.js CHANGED
@@ -95,12 +95,6 @@ function processFile(options, extras) {
95
95
  extras.stat.isSymbolicLink()) {
96
96
  return false;
97
97
  }
98
- if (options.ignoreExt.length > 0 && options.ignoreExt.indexOf(extras.ext) >= 0) {
99
- return false;
100
- }
101
- if (options.filterExt.length > 0 && options.filterExt.indexOf(extras.ext) < 0) {
102
- return false;
103
- }
104
98
  const filterResult = options.filter ? options.filter(extras.file, extras.path, extras) : true; // default to include
105
99
  if (filterResult) {
106
100
  if (filterResult.skip !== true) {
@@ -110,6 +104,25 @@ function processFile(options, extras) {
110
104
  }
111
105
  return false;
112
106
  }
107
+ // Reject entries before allocating callback extras or requesting full metadata.
108
+ function acceptEntry(options, entry, path) {
109
+ const isDirectory = entry.isDirectory();
110
+ if (isDirectory && options._ignoreDirs.has(entry.name))
111
+ return false;
112
+ if (options.prefilter && !options.prefilter(entry.name, path, entry))
113
+ return false;
114
+ if (isDirectory)
115
+ return true;
116
+ if (options.ignoreExt.length || options.filterExt.length) {
117
+ const ix = entry.name.lastIndexOf(".");
118
+ const ext = ix > 0 ? entry.name.substring(ix) : "";
119
+ if (options.ignoreExt.indexOf(ext) >= 0)
120
+ return false;
121
+ if (options.filterExt.length && options.filterExt.indexOf(ext) < 0)
122
+ return false;
123
+ }
124
+ return true;
125
+ }
113
126
  /**
114
127
  * get the result base on grouping enable flag
115
128
  *
@@ -118,7 +131,7 @@ function processFile(options, extras) {
118
131
  */
119
132
  function getResult(options) {
120
133
  return options.grouping
121
- ? Object.assign({ files: [] }, options.result)
134
+ ? { files: [], ...options.result }
122
135
  : options.result.files || [];
123
136
  }
124
137
  /**
@@ -135,7 +148,7 @@ function walkSync(path, options, level = 0) {
135
148
  const dir = Path.join(options.dir, path);
136
149
  let files = Fs.readdirSync(dir, options.readdirOpts);
137
150
  if (options.sortFiles) {
138
- if (options.fullStat) {
151
+ if (!options.readdirOpts) {
139
152
  files = files.sort();
140
153
  }
141
154
  else {
@@ -143,15 +156,20 @@ function walkSync(path, options, level = 0) {
143
156
  }
144
157
  }
145
158
  const dirs = [];
146
- let stop = false;
159
+ const extrasFiles = options.fullStat && options._earlyFilter && (options.filter || options.filterDir)
160
+ ? files.map((entry) => entry.name)
161
+ : files;
147
162
  // process files first
148
- for (let ix = 0; !stop && ix < files.length; ix++) {
163
+ for (let ix = 0; !options._stopped && ix < files.length; ix++) {
149
164
  const file = files[ix];
165
+ if (options._earlyFilter && !acceptEntry(options, file, path))
166
+ continue;
150
167
  let extras;
151
168
  if (options.fullStat) {
152
- const fullFile = join2(options._sep, dir, file);
169
+ const name = options._earlyFilter ? file.name : file;
170
+ const fullFile = join2(options._sep, dir, name);
153
171
  const stat = Fs.lstatSync(fullFile);
154
- extras = makeExtrasData(file, fullFile, path, stat, files, options);
172
+ extras = makeExtrasData(name, fullFile, path, stat, extrasFiles, options);
155
173
  }
156
174
  else {
157
175
  const fullFile = join2(options._sep, dir, file.name);
@@ -161,15 +179,16 @@ function walkSync(path, options, level = 0) {
161
179
  dirs.push(extras);
162
180
  }
163
181
  else {
164
- stop = processFile(options, extras);
182
+ options._stopped = !!processFile(options, extras);
165
183
  }
166
184
  }
167
185
  // now process dirs
168
- if (!stop && dirs.length > 0) {
169
- for (let ix = 0; ix < dirs.length; ix++) {
186
+ if (!options._stopped && dirs.length > 0) {
187
+ for (let ix = 0; !options._stopped && ix < dirs.length; ix++) {
170
188
  const extras = dirs[ix];
171
189
  const flags = processDir(options, extras);
172
190
  if (flags.stop) {
191
+ options._stopped = true;
173
192
  break;
174
193
  }
175
194
  if (!flags.skip && level < options.maxLevel) {
@@ -183,10 +202,24 @@ function walkSync(path, options, level = 0) {
183
202
  throw err;
184
203
  }
185
204
  }
186
- return getResult(options);
205
+ return level === 0 ? getResult(options) : undefined;
187
206
  }
188
207
  const asyncReaddir = Util.promisify(Fs.readdir);
189
208
  const asyncLStat = Util.promisify(Fs.lstat);
209
+ // Transfer a directory slot to the next waiter without shifting the queue.
210
+ function releaseDirectory(options) {
211
+ if (options._waitIndex < options._waiting.length) {
212
+ const resume = options._waiting[options._waitIndex++];
213
+ if (options._waitIndex === options._waiting.length) {
214
+ options._waiting = [];
215
+ options._waitIndex = 0;
216
+ }
217
+ resume();
218
+ }
219
+ else {
220
+ options._concurrentCount--;
221
+ }
222
+ }
190
223
  /**
191
224
  * async version of dir walk
192
225
  *
@@ -196,12 +229,25 @@ const asyncLStat = Util.promisify(Fs.lstat);
196
229
  * @returns
197
230
  */
198
231
  async function walk(path, options, level = 0) {
232
+ let promises = [];
233
+ let hasSlot = false;
199
234
  try {
235
+ if (options.concurrency > 1) {
236
+ if (options._concurrentCount >= options.concurrency) {
237
+ await new Promise((resolve) => options._waiting.push(resolve));
238
+ }
239
+ else {
240
+ options._concurrentCount++;
241
+ }
242
+ hasSlot = true;
243
+ }
244
+ if (options._stopped)
245
+ return undefined;
200
246
  // Use Path.join to normalize the directory path once at entry
201
247
  const dir = Path.join(options.dir, path);
202
248
  let files = await asyncReaddir(dir, options.readdirOpts);
203
249
  if (options.sortFiles) {
204
- if (options.fullStat) {
250
+ if (!options.readdirOpts) {
205
251
  files = files.sort();
206
252
  }
207
253
  else {
@@ -209,15 +255,22 @@ async function walk(path, options, level = 0) {
209
255
  }
210
256
  }
211
257
  const dirs = [];
212
- let stop = false;
258
+ const extrasFiles = options.fullStat && options._earlyFilter && (options.filter || options.filterDir)
259
+ ? files.map((entry) => entry.name)
260
+ : files;
213
261
  // process files first
214
- for (let ix = 0; !stop && ix < files.length; ix++) {
262
+ for (let ix = 0; !options._stopped && ix < files.length; ix++) {
215
263
  const file = files[ix];
264
+ if (options._earlyFilter && !acceptEntry(options, file, path))
265
+ continue;
216
266
  let extras;
217
267
  if (options.fullStat) {
218
- const fullFile = join2(options._sep, dir, file);
268
+ const name = options._earlyFilter ? file.name : file;
269
+ const fullFile = join2(options._sep, dir, name);
219
270
  const stat = await asyncLStat(fullFile);
220
- extras = makeExtrasData(file, fullFile, path, stat, files, options);
271
+ if (options._stopped)
272
+ break;
273
+ extras = makeExtrasData(name, fullFile, path, stat, extrasFiles, options);
221
274
  }
222
275
  else {
223
276
  const fullFile = join2(options._sep, dir, file.name);
@@ -227,33 +280,31 @@ async function walk(path, options, level = 0) {
227
280
  dirs.push(extras);
228
281
  }
229
282
  else {
230
- stop = processFile(options, extras);
283
+ options._stopped = !!processFile(options, extras);
231
284
  }
232
285
  }
286
+ // The slot covers readdir and all lstats, but not waiting for descendants.
287
+ if (hasSlot) {
288
+ releaseDirectory(options);
289
+ hasSlot = false;
290
+ }
233
291
  // now process dirs
234
- if (!stop && dirs.length > 0) {
235
- let promises = [];
236
- for (let ix = 0; ix < dirs.length; ix++) {
292
+ if (!options._stopped && dirs.length > 0) {
293
+ for (let ix = 0; !options._stopped && ix < dirs.length; ix++) {
237
294
  const extras = dirs[ix];
238
295
  const flags = processDir(options, extras);
239
296
  if (flags.stop) {
297
+ options._stopped = true;
240
298
  break;
241
299
  }
242
300
  if (!flags.skip && level < options.maxLevel) {
243
301
  const walkP = walk(extras.dirFile, options, level + 1);
244
302
  if (options.concurrency > 1) {
245
- if (options._concurrentCount < options.concurrency) {
246
- options._concurrentCount++;
247
- promises.push(walkP);
248
- }
249
- else if (promises.length) {
303
+ promises.push(walkP);
304
+ // Bound eager child walks as well as active directory operations.
305
+ if (promises.length >= options.concurrency) {
250
306
  await Promise.all(promises);
251
- options._concurrentCount -= promises.length;
252
- promises = [walkP];
253
- options._concurrentCount++;
254
- }
255
- else {
256
- await walkP;
307
+ promises = [];
257
308
  }
258
309
  }
259
310
  else {
@@ -261,19 +312,27 @@ async function walk(path, options, level = 0) {
261
312
  }
262
313
  }
263
314
  }
264
- if (promises.length) {
265
- await Promise.all(promises);
266
- options._concurrentCount -= promises.length;
267
- promises = [];
268
- }
269
315
  }
270
316
  }
271
317
  catch (err) {
272
318
  if (options.rethrowError) {
273
- throw err;
319
+ // Child walks resolve after recording failure, so none can reject unobserved.
320
+ if (!options._error)
321
+ options._error = { cause: err };
322
+ options._stopped = true;
274
323
  }
275
324
  }
276
- return getResult(options);
325
+ finally {
326
+ if (hasSlot)
327
+ releaseDirectory(options);
328
+ // A callback can throw after siblings have started. Drain them before returning.
329
+ if (promises.length) {
330
+ await Promise.all(promises);
331
+ }
332
+ }
333
+ if (level === 0 && options._error)
334
+ throw options._error.cause;
335
+ return level === 0 ? getResult(options) : undefined;
277
336
  }
278
337
  /**
279
338
  * make a copy of the user's options with proper defaults
@@ -282,7 +341,11 @@ async function walk(path, options, level = 0) {
282
341
  * @returns
283
342
  */
284
343
  function makeOptions(opts) {
285
- const options = typeof opts === "string" ? { cwd: opts } : opts;
344
+ // Walkers select the matching callback metadata from fullStat at runtime.
345
+ const options = (typeof opts === "string" ? { cwd: opts } : opts);
346
+ if (options.prefilter && options.fullStat === false) {
347
+ throw new TypeError("prefilter requires fullStat: true");
348
+ }
286
349
  const sep = options.pathSep || Path.posix.sep;
287
350
  let cwd = options.cwd || options.dir || process.cwd();
288
351
  if (!options.hasOwnProperty("pathSep") && cwd.includes("\\")) {
@@ -314,7 +377,8 @@ function makeOptions(opts) {
314
377
  grouping: undefined,
315
378
  }, options, {
316
379
  dir: cwd,
317
- result: {},
380
+ fullStat: options.fullStat === undefined ? true : options.fullStat,
381
+ result: Object.create(null),
318
382
  ignoreExt: []
319
383
  .concat(options.ignoreExt)
320
384
  .map(cleanExt)
@@ -324,8 +388,19 @@ function makeOptions(opts) {
324
388
  .map(cleanExt)
325
389
  .filter((x) => x),
326
390
  _concurrentCount: 0,
391
+ _waiting: [],
392
+ _waitIndex: 0,
393
+ _stopped: false,
394
+ _error: undefined,
395
+ _earlyFilter: false,
396
+ _ignoreDirs: new Set([].concat(options.ignoreDirs || [])),
327
397
  });
328
- if (!opts2.fullStat) {
398
+ opts2._earlyFilter =
399
+ !!opts2.prefilter ||
400
+ opts2._ignoreDirs.size > 0 ||
401
+ opts2.ignoreExt.length > 0 ||
402
+ opts2.filterExt.length > 0;
403
+ if (!opts2.fullStat || opts2._earlyFilter) {
329
404
  opts2.readdirOpts = { withFileTypes: true };
330
405
  }
331
406
  return opts2;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "filter-scan-dir",
3
- "version": "2.1.3",
4
- "description": "Recursively scan and filter directory for a flat array of files",
3
+ "version": "2.1.5",
4
+ "description": "Extremely fast recursive directory crawling and filtering for Node.js.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "main": "./index.cjs",
@@ -62,13 +62,13 @@
62
62
  "homepage": "https://github.com/jchip/fynjs/tree/main/packages/filter-scan-dir",
63
63
  "dependencies": {},
64
64
  "devDependencies": {
65
- "@fynjs/run": "^1.1.3",
65
+ "@fynjs/run": "^1.1.5",
66
66
  "@types/node": "^26.4.1",
67
67
  "@vitest/coverage-v8": "^5.0.0",
68
68
  "@vitest/ui": "^5.0.0",
69
69
  "prettier": "^3.5.3",
70
- "publish-util": "^3.1.2",
71
- "run-verify": "^2.1.3",
70
+ "publish-util": "^3.1.4",
71
+ "run-verify": "^2.1.5",
72
72
  "typedoc": "^0.28.14",
73
73
  "typescript": "^7.0.2",
74
74
  "vite": "^8.2.2",