@theholocron/fs-utils 1.12.0 → 1.13.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/README.md CHANGED
@@ -11,7 +11,14 @@ pnpm add @theholocron/fs-utils
11
11
  ## Usage
12
12
 
13
13
  ```typescript
14
- import { findExecutable, findExecutableSync, findRoot, findUpward, hasGitEntry } from "@theholocron/fs-utils";
14
+ import {
15
+ findExecutable,
16
+ findExecutableSync,
17
+ findRoot,
18
+ findUpward,
19
+ hasGitEntry,
20
+ walkFilesSync,
21
+ } from "@theholocron/fs-utils";
15
22
 
16
23
  // nearest ancestor holding a marker file or directory
17
24
  const repoRoot = await findRoot(process.cwd(), ".git");
@@ -84,6 +91,21 @@ than "not there" / "not permitted".
84
91
  const tsc = findExecutableSync("tsc", { path: join(cwd, "node_modules", ".bin") }) ?? findExecutableSync("tsc");
85
92
  ```
86
93
 
94
+ ### `walkFilesSync(dir, options?)`
95
+
96
+ Every regular file under `dir`, as absolute paths in a stable (name-sorted,
97
+ depth-first) order. `options.skip` names the directories not entered, at any
98
+ depth (default `.git` and `node_modules`; it replaces the default); a _file_
99
+ with that name is still listed. Symlinks found while walking are neither
100
+ followed nor listed, so a link back up the tree can't loop; `dir` itself is
101
+ followed if it is a symlink. A subdirectory that can't be read is skipped; `dir`
102
+ itself must be readable — its error propagates. The tree is assumed not to
103
+ change mid-walk.
104
+
105
+ ```typescript
106
+ const sources = walkFilesSync(repoRoot, { skip: [".git", "node_modules", "dist"] });
107
+ ```
108
+
87
109
  ### `hasGitEntry(dir)`
88
110
 
89
111
  Returns `true` when `dir` has a `.git` entry — a directory in a normal
package/dist/index.d.mts CHANGED
@@ -88,4 +88,22 @@ export declare function findUpward<T>(startDir: string, check: (dir: string) =>
88
88
  * nearest `.git` as the project root, searched but not walked past.
89
89
  */
90
90
  export declare function hasGitEntry(dir: string): Promise<boolean>;
91
+ //#endregion
92
+ //#region src/walk-files.d.ts
93
+ export interface WalkFilesOptions {
94
+ /** Directory names not descended into, at any depth (files with these names are still listed). Defaults to `.git` and `node_modules`. */
95
+ skip?: Iterable<string>;
96
+ }
97
+ /**
98
+ * Every regular file under `dir`, as absolute paths in a stable (name-sorted,
99
+ * depth-first) order. Directories named in `options.skip` are not entered.
100
+ *
101
+ * Symlinks found while walking are neither followed nor listed, so a link back
102
+ * up the tree can't loop; `dir` itself is the caller's choice and is followed
103
+ * if it is a symlink. A subdirectory that can't be read is skipped; `dir`
104
+ * itself must be readable — its error propagates, since a typo'd root is a bug
105
+ * to surface, not an empty result. The tree is assumed not to change while it
106
+ * is walked: an entry swapped for a symlink mid-walk is not detected.
107
+ */
108
+ export declare function walkFilesSync(dir: string, options?: WalkFilesOptions): string[];
91
109
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { accessSync, constants, realpathSync, statSync } from "node:fs";
1
+ import { accessSync, constants, lstatSync, readdirSync, realpathSync, statSync } from "node:fs";
2
2
  import { access, realpath, stat } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  //#region src/find-executable.ts
@@ -190,4 +190,53 @@ function findRootSync(startDir, marker, options = {}) {
190
190
  }
191
191
  }
192
192
  //#endregion
193
- export { findExecutable, findExecutableSync, findRoot, findRootSync, findUpward, hasGitEntry };
193
+ //#region src/walk-files.ts
194
+ const DEFAULT_SKIP = [".git", "node_modules"];
195
+ /** The entry's type, asking the filesystem when the platform's directory listing couldn't say. */
196
+ function entryKind(entry, full) {
197
+ if (entry.isDirectory()) return "directory";
198
+ if (entry.isFile()) return "file";
199
+ if (entry.isSymbolicLink()) return "other";
200
+ try {
201
+ const stats = lstatSync(full);
202
+ return stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other";
203
+ } catch {
204
+ return "other";
205
+ }
206
+ }
207
+ /**
208
+ * Every regular file under `dir`, as absolute paths in a stable (name-sorted,
209
+ * depth-first) order. Directories named in `options.skip` are not entered.
210
+ *
211
+ * Symlinks found while walking are neither followed nor listed, so a link back
212
+ * up the tree can't loop; `dir` itself is the caller's choice and is followed
213
+ * if it is a symlink. A subdirectory that can't be read is skipped; `dir`
214
+ * itself must be readable — its error propagates, since a typo'd root is a bug
215
+ * to surface, not an empty result. The tree is assumed not to change while it
216
+ * is walked: an entry swapped for a symlink mid-walk is not detected.
217
+ */
218
+ function walkFilesSync(dir, options = {}) {
219
+ const skip = new Set(options.skip ?? DEFAULT_SKIP);
220
+ const files = [];
221
+ function walk(current, isRoot) {
222
+ let entries;
223
+ try {
224
+ entries = readdirSync(current, { withFileTypes: true });
225
+ } catch (error) {
226
+ if (isRoot) throw error;
227
+ return;
228
+ }
229
+ entries.sort((a, b) => Number(a.name > b.name) - Number(a.name < b.name));
230
+ for (const entry of entries) {
231
+ const full = join(current, entry.name);
232
+ const kind = entryKind(entry, full);
233
+ if (kind === "directory") {
234
+ if (!skip.has(entry.name)) walk(full, false);
235
+ } else if (kind === "file") files.push(full);
236
+ }
237
+ }
238
+ walk(resolve(dir), true);
239
+ return files;
240
+ }
241
+ //#endregion
242
+ export { findExecutable, findExecutableSync, findRoot, findRootSync, findUpward, hasGitEntry, walkFilesSync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/fs-utils",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Walk a directory tree upward to find the nearest match or project root.",
5
5
  "keywords": [
6
6
  "filesystem",