path-treeify 1.3.0-beta.d47c4d9 → 1.4.0-beta.004782a
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 +40 -13
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/types/index.d.ts +35 -48
- package/dist/types/src/types.d.ts +60 -0
- package/dist/types/src/utils.d.ts +31 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<img alt="GitHub Created At" src="https://img.shields.io/github/created-at/isaaxite/path-treeify">
|
|
28
28
|
</a>
|
|
29
29
|
<a href="https://github.com/isaaxite/path-treeify">
|
|
30
|
-
<img alt="
|
|
30
|
+
<img alt="NPM Unpacked Size" src="https://img.shields.io/npm/unpacked-size/path-treeify">
|
|
31
31
|
</a>
|
|
32
32
|
<a href="https://github.com/isaaxite/path-treeify/commits/main/">
|
|
33
33
|
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/isaaxite/path-treeify">
|
|
@@ -51,10 +51,12 @@
|
|
|
51
51
|
- 🔗 Each node carries a `parent` circular reference for upward traversal
|
|
52
52
|
- 📍 Each node exposes a `getPath()` method to retrieve its own paths directly
|
|
53
53
|
- 🏷️ Each node has a `type` field — `PathTreeNodeKind.Dir` or `PathTreeNodeKind.File`
|
|
54
|
+
- 📏 Each node has a `depth` field indicating its distance from the root
|
|
54
55
|
- 👁️ `fileVisible` option includes files as leaf nodes alongside directories
|
|
55
56
|
- 🔍 Optional `filter` callback applied at **every depth**, including top-level entries
|
|
56
57
|
- ⚡ `build()` scans the entire `base` directory with zero configuration
|
|
57
58
|
- 🎛️ `buildBy()` accepts either a path segment array or a top-level filter function
|
|
59
|
+
- 🗃️ `usePathCache` option caches `getPath()` results per node for repeated access
|
|
58
60
|
- 📦 Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
59
61
|
- 🚫 Zero runtime dependencies
|
|
60
62
|
|
|
@@ -94,12 +96,12 @@ const treeifyWithFiles = new PathTreeify({
|
|
|
94
96
|
});
|
|
95
97
|
const fullTree = treeifyWithFiles.build();
|
|
96
98
|
|
|
97
|
-
// Check node type
|
|
99
|
+
// Check node type and depth
|
|
98
100
|
for (const child of tree.children) {
|
|
99
101
|
if (child.type === PathTreeNodeKind.Dir) {
|
|
100
|
-
console.log(
|
|
102
|
+
console.log(`dir (depth ${child.depth}):`, child.value);
|
|
101
103
|
} else if (child.type === PathTreeNodeKind.File) {
|
|
102
|
-
console.log(
|
|
104
|
+
console.log(`file (depth ${child.depth}):`, child.value);
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
```
|
|
@@ -112,11 +114,12 @@ for (const child of tree.children) {
|
|
|
112
114
|
|
|
113
115
|
Creates a new instance.
|
|
114
116
|
|
|
115
|
-
| Option
|
|
116
|
-
|
|
117
|
-
| `base`
|
|
118
|
-
| `filter`
|
|
119
|
-
| `fileVisible`
|
|
117
|
+
| Option | Type | Required | Description |
|
|
118
|
+
|----------------|------------------------------|----------|-------------------------------------------------------------------------------------|
|
|
119
|
+
| `base` | `string` | ✅ | Absolute path to the root directory to scan from |
|
|
120
|
+
| `filter` | `FilterFunction` (see below) | ❌ | Applied at **every depth** — top-level entries included |
|
|
121
|
+
| `fileVisible` | `boolean` | ❌ | When `true`, files are included as leaf nodes. Defaults to `false` |
|
|
122
|
+
| `usePathCache` | `boolean` | ❌ | When `true`, `getPath()` results are cached on each node after the first call |
|
|
120
123
|
|
|
121
124
|
`base` must exist and be a directory, otherwise the constructor throws.
|
|
122
125
|
|
|
@@ -190,6 +193,7 @@ const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith
|
|
|
190
193
|
|
|
191
194
|
```ts
|
|
192
195
|
interface PathTreeNode {
|
|
196
|
+
depth: number; // distance from root; root is 0, its children are 1, etc.
|
|
193
197
|
parent: PathTreeNode | null; // null only on the synthetic root
|
|
194
198
|
value: string; // entry name for this node
|
|
195
199
|
children: PathTreeNode[]; // empty for file nodes
|
|
@@ -199,6 +203,8 @@ interface PathTreeNode {
|
|
|
199
203
|
}
|
|
200
204
|
```
|
|
201
205
|
|
|
206
|
+
When `usePathCache: true` is set on the `PathTreeify` instance, the result of `getPath()` is cached on the node after the first call — subsequent calls return the same object reference without recomputing the parent chain.
|
|
207
|
+
|
|
202
208
|
> ⚠️ **Circular references** — `parent` points back up the tree. Use `JSON.stringify` replacers or a library like `flatted` if you need to serialize the result.
|
|
203
209
|
|
|
204
210
|
---
|
|
@@ -209,9 +215,14 @@ An enum classifying each node's filesystem type.
|
|
|
209
215
|
|
|
210
216
|
```ts
|
|
211
217
|
enum PathTreeNodeKind {
|
|
212
|
-
Dir
|
|
213
|
-
File
|
|
214
|
-
Unknown
|
|
218
|
+
Dir = 'dir',
|
|
219
|
+
File = 'file',
|
|
220
|
+
Unknown = 'unknown', // assigned before the type is resolved
|
|
221
|
+
BrokenSymlink = 'broken_symlink',
|
|
222
|
+
Other = 'other', // FIFO/socket etc.
|
|
223
|
+
NotFound = 'not_found',
|
|
224
|
+
PermissionDenied = 'permission_denied',
|
|
225
|
+
Error = 'error',
|
|
215
226
|
}
|
|
216
227
|
```
|
|
217
228
|
|
|
@@ -266,7 +277,7 @@ const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith
|
|
|
266
277
|
function printPaths(node) {
|
|
267
278
|
for (const child of node.children) {
|
|
268
279
|
const { absolute } = child.getPath();
|
|
269
|
-
console.log(`[${child.type}] ${absolute}`);
|
|
280
|
+
console.log(`[${child.type}] depth=${child.depth} ${absolute}`);
|
|
270
281
|
printPaths(child);
|
|
271
282
|
}
|
|
272
283
|
}
|
|
@@ -274,6 +285,22 @@ function printPaths(node) {
|
|
|
274
285
|
printPaths(tree);
|
|
275
286
|
```
|
|
276
287
|
|
|
288
|
+
### Cache `getPath()` results for repeated traversal
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const treeify = new PathTreeify({
|
|
292
|
+
base: '/your/project',
|
|
293
|
+
usePathCache: true,
|
|
294
|
+
});
|
|
295
|
+
const tree = treeify.build();
|
|
296
|
+
|
|
297
|
+
// First call walks the parent chain and caches the result
|
|
298
|
+
const pathA = tree.children[0].getPath();
|
|
299
|
+
// Subsequent calls return the cached object directly
|
|
300
|
+
const pathB = tree.children[0].getPath();
|
|
301
|
+
console.log(pathA === pathB); // true
|
|
302
|
+
```
|
|
303
|
+
|
|
277
304
|
### CommonJS usage
|
|
278
305
|
|
|
279
306
|
```js
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=require("fs"),
|
|
1
|
+
"use strict";var e,t=require("fs"),r=require("path");function i(e,t){for(const r in t)Object.defineProperty(e,r,{value:t[r],writable:!1,configurable:!1,enumerable:!1})}function n(e){try{return function(e){try{const r=t.statSync(e);if(r.isDirectory()){try{t.accessSync(e,t.constants.X_OK)}catch{return exports.PathTreeNodeKind.PermissionDenied}return exports.PathTreeNodeKind.Dir}return r.isFile()?exports.PathTreeNodeKind.File:exports.PathTreeNodeKind.Other}catch(e){const t=e.code;if("EACCES"===t)return exports.PathTreeNodeKind.PermissionDenied;if("ENOENT"!==t)throw e}try{return t.lstatSync(e).isSymbolicLink()?exports.PathTreeNodeKind.BrokenSymlink:exports.PathTreeNodeKind.Other}catch(e){const t=e.code;if("EACCES"===t)return exports.PathTreeNodeKind.PermissionDenied;if("ENOENT"!==t)throw e}return exports.PathTreeNodeKind.NotFound}(e)}catch(e){return exports.PathTreeNodeKind.Error}}exports.PathTreeNodeKind=void 0,(e=exports.PathTreeNodeKind||(exports.PathTreeNodeKind={})).Dir="dir",e.File="file",e.Unknown="unknown",e.BrokenSymlink="broken_symlink",e.Other="other",e.NotFound="not_found",e.PermissionDenied="permission_denied",e.Error="error";class s{constructor(e){this.depth=-1,this.parent=null,this.value="",this.children=[],this.type=exports.PathTreeNodeKind.Unknown,i(this,{_usePathCache:e.usePathCache})}getPath(){let e=this,t="";const n=()=>{let e="",i=this;for(;;){if(null===i.parent){t=i.value;break}e=e?`${i.value}${r.sep}${e}`:i.value,i=i.parent}return{relative:e,absolute:r.resolve(t,e)}};return e._usePathCache?(e._pathCache||i(this,{_pathCache:n()}),e._pathCache):n()}}exports.PathTreeify=class{constructor(e){this._base="",this._fileVisible=!1,this._usePathCache=!1;const{filter:t,base:r,fileVisible:s,usePathCache:o}=e,a=n(r||"");if(void 0!==t&&(this.validateFilter(t),i(this,{_userFilter:t})),a!==exports.PathTreeNodeKind.Dir)throw new Error(`${r} not a valid dirPath!`);i(this,{_usePathCache:Boolean(o),_base:r,_fileVisible:!("boolean"!=typeof s||!s)&&s})}applyFilter(e,t){return!(!this._fileVisible&&n(e)!==exports.PathTreeNodeKind.Dir)&&(!this._userFilter||this._userFilter({name:t,dirPath:r.dirname(e)}))}validateFilter(e){if("function"!=typeof e)throw new TypeError("filter must be a function")}initNode(){return new s({usePathCache:this._usePathCache})}buildChildren(e,i,s){const o=[],a=i.depth+1,h=s||t.readdirSync(e);for(const t of h){const s=r.join(e,t);if(!this.applyFilter(s,t))continue;const h=n(s),d=this.initNode();d.depth=a,d.value=t,d.parent=i,o.push(d),this._fileVisible&&h===exports.PathTreeNodeKind.File?d.type=h:h===exports.PathTreeNodeKind.Dir?(d.type=h,d.children=this.buildChildren(s,d)):d.type=h}return o}checkRelativePaths(e){for(let t=0;t<e.length;t++){const i=e[t];if("string"!=typeof i)throw new Error(`Item at index ${t} is not a string, got ${typeof i}`);const s=r.resolve(this._base,i),o=n(s);if([exports.PathTreeNodeKind.NotFound,exports.PathTreeNodeKind.PermissionDenied,exports.PathTreeNodeKind.Other,exports.PathTreeNodeKind.Error,exports.PathTreeNodeKind.BrokenSymlink].includes(o))throw new Error(`Path does not exist or is not accessible: ${s} (from relative path: ${i})`);if(o!==exports.PathTreeNodeKind.Dir&&(!this._fileVisible||o!==exports.PathTreeNodeKind.File))throw new Error(`Path is not a directory: ${s} (from relative path: ${i})`)}}formatSegments(e){return e.map(e=>e.split(/[/\\]/).filter(Boolean).join(r.sep)).filter(Boolean)}getAllEntriesUnderBase(){return t.readdirSync(this._base).filter(e=>{const t=r.resolve(this._base,e);return this.applyFilter(t,e)})}buildBySegments(e){const t=this.initNode();return t.depth=0,t.value=this._base,t.type=exports.PathTreeNodeKind.Dir,t.children=this.buildChildren(this._base,t,e),t}buildByFilter(e){const t=this.getAllEntriesUnderBase();return this.buildBySegments(t.filter(e))}buildBy(e){if(Array.isArray(e)){const t=this.formatSegments(e);return this.checkRelativePaths(t),this.buildBySegments(t)}if("function"==typeof e)return this.buildByFilter(e);throw new TypeError("buildBy: expected an array of strings or a filter function, but received "+typeof e)}build(){const e=this.getAllEntriesUnderBase();return this.buildBySegments(e)}};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{statSync as e,accessSync as t,constants as i,lstatSync as r,readdirSync as n}from"fs";import{sep as s,resolve as o,dirname as h,join as l}from"path";var a;function u(e,t){for(const i in t)Object.defineProperty(e,i,{value:t[i],writable:!1,configurable:!1,enumerable:!1})}function c(n){try{return function(n){try{const r=e(n);if(r.isDirectory()){try{t(n,i.X_OK)}catch{return a.PermissionDenied}return a.Dir}return r.isFile()?a.File:a.Other}catch(e){const t=e.code;if("EACCES"===t)return a.PermissionDenied;if("ENOENT"!==t)throw e}try{return r(n).isSymbolicLink()?a.BrokenSymlink:a.Other}catch(e){const t=e.code;if("EACCES"===t)return a.PermissionDenied;if("ENOENT"!==t)throw e}return a.NotFound}(n)}catch(e){return a.Error}}!function(e){e.Dir="dir",e.File="file",e.Unknown="unknown",e.BrokenSymlink="broken_symlink",e.Other="other",e.NotFound="not_found",e.PermissionDenied="permission_denied",e.Error="error"}(a||(a={}));class f{constructor(e){this.depth=-1,this.parent=null,this.value="",this.children=[],this.type=a.Unknown,u(this,{_usePathCache:e.usePathCache})}getPath(){let e=this,t="";const i=()=>{let e="",i=this;for(;;){if(null===i.parent){t=i.value;break}e=e?`${i.value}${s}${e}`:i.value,i=i.parent}return{relative:e,absolute:o(t,e)}};return e._usePathCache?(e._pathCache||u(this,{_pathCache:i()}),e._pathCache):i()}}class d{constructor(e){this._base="",this._fileVisible=!1,this._usePathCache=!1;const{filter:t,base:i,fileVisible:r,usePathCache:n}=e,s=c(i||"");if(void 0!==t&&(this.validateFilter(t),u(this,{_userFilter:t})),s!==a.Dir)throw new Error(`${i} not a valid dirPath!`);u(this,{_usePathCache:Boolean(n),_base:i,_fileVisible:!("boolean"!=typeof r||!r)&&r})}applyFilter(e,t){return!(!this._fileVisible&&c(e)!==a.Dir)&&(!this._userFilter||this._userFilter({name:t,dirPath:h(e)}))}validateFilter(e){if("function"!=typeof e)throw new TypeError("filter must be a function")}initNode(){return new f({usePathCache:this._usePathCache})}buildChildren(e,t,i){const r=[],s=t.depth+1,o=i||n(e);for(const i of o){const n=l(e,i);if(!this.applyFilter(n,i))continue;const o=c(n),h=this.initNode();h.depth=s,h.value=i,h.parent=t,r.push(h),this._fileVisible&&o===a.File?h.type=o:o===a.Dir?(h.type=o,h.children=this.buildChildren(n,h)):h.type=o}return r}checkRelativePaths(e){for(let t=0;t<e.length;t++){const i=e[t];if("string"!=typeof i)throw new Error(`Item at index ${t} is not a string, got ${typeof i}`);const r=o(this._base,i),n=c(r);if([a.NotFound,a.PermissionDenied,a.Other,a.Error,a.BrokenSymlink].includes(n))throw new Error(`Path does not exist or is not accessible: ${r} (from relative path: ${i})`);if(n!==a.Dir&&(!this._fileVisible||n!==a.File))throw new Error(`Path is not a directory: ${r} (from relative path: ${i})`)}}formatSegments(e){return e.map(e=>e.split(/[/\\]/).filter(Boolean).join(s)).filter(Boolean)}getAllEntriesUnderBase(){return n(this._base).filter(e=>{const t=o(this._base,e);return this.applyFilter(t,e)})}buildBySegments(e){const t=this.initNode();return t.depth=0,t.value=this._base,t.type=a.Dir,t.children=this.buildChildren(this._base,t,e),t}buildByFilter(e){const t=this.getAllEntriesUnderBase();return this.buildBySegments(t.filter(e))}buildBy(e){if(Array.isArray(e)){const t=this.formatSegments(e);return this.checkRelativePaths(t),this.buildBySegments(t)}if("function"==typeof e)return this.buildByFilter(e);throw new TypeError("buildBy: expected an array of strings or a filter function, but received "+typeof e)}build(){const e=this.getAllEntriesUnderBase();return this.buildBySegments(e)}}export{a as PathTreeNodeKind,d as PathTreeify};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,45 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
name: string;
|
|
4
|
-
dirPath: string;
|
|
5
|
-
}) => boolean;
|
|
6
|
-
/** Constructor options for PathTreeify */
|
|
7
|
-
interface PathTreeifyProps {
|
|
8
|
-
/** The root directory to scan */
|
|
9
|
-
base: string;
|
|
10
|
-
/** Optional filter applied to every entry during recursive traversal */
|
|
11
|
-
filter?: FilterFunction;
|
|
12
|
-
/** When true, files are included as leaf nodes alongside directories. Defaults to false */
|
|
13
|
-
fileVisible?: boolean;
|
|
14
|
-
}
|
|
15
|
-
/** Classification of a node in the path tree */
|
|
16
|
-
export declare enum PathTreeNodeKind {
|
|
17
|
-
Dir = "dir",
|
|
18
|
-
File = "file",
|
|
19
|
-
/** Assigned before the node's type has been resolved */
|
|
20
|
-
Unknown = "unknown"
|
|
21
|
-
}
|
|
22
|
-
export interface PathTreeNode {
|
|
23
|
-
parent: PathTreeNode | null;
|
|
24
|
-
value: string;
|
|
25
|
-
children: PathTreeNode[];
|
|
26
|
-
type: PathTreeNodeKind;
|
|
27
|
-
getPath(): {
|
|
28
|
-
relative: string;
|
|
29
|
-
absolute: string;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
1
|
+
import { PathTreeifyProps, PathTreeNode } from './src/types';
|
|
2
|
+
export { PathTreeifyProps, PathTreeNodeKind, PathTreeNode } from './src/types';
|
|
32
3
|
/** Builds a tree of {@link PathTreeNode} entries rooted at a given base path */
|
|
33
4
|
export declare class PathTreeify {
|
|
34
5
|
/** The root directory to scan */
|
|
35
|
-
private
|
|
6
|
+
private _base;
|
|
7
|
+
/** When true, files are included as leaf nodes during traversal. Defaults to false */
|
|
8
|
+
private _fileVisible;
|
|
9
|
+
/** When true, absolute paths are cached on each node after the first retrieval to speed up subsequent `getPath` calls at the cost of higher memory usage. Defaults to false */
|
|
10
|
+
private _usePathCache;
|
|
36
11
|
/**
|
|
37
12
|
* Optional user-supplied filter. When set, every entry must pass this predicate
|
|
38
13
|
* in addition to the built-in visibility check.
|
|
39
14
|
*/
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
private fileVisible;
|
|
15
|
+
private _userFilter?;
|
|
16
|
+
constructor(props: Partial<PathTreeifyProps>);
|
|
43
17
|
/**
|
|
44
18
|
* Determines whether a given entry should be included in the tree.
|
|
45
19
|
* - If {@link fileVisible} is false, non-directory entries are always excluded.
|
|
@@ -48,27 +22,39 @@ export declare class PathTreeify {
|
|
|
48
22
|
* @param name - Entry name (filename or directory name)
|
|
49
23
|
*/
|
|
50
24
|
private applyFilter;
|
|
51
|
-
constructor({ filter, base, fileVisible }: Partial<PathTreeifyProps>);
|
|
52
25
|
/**
|
|
53
26
|
* Asserts that the provided value is a callable {@link FilterFunction}.
|
|
54
27
|
* Throws a TypeError if the check fails.
|
|
55
28
|
*/
|
|
56
29
|
private validateFilter;
|
|
57
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new unattached {@link PathTreeNode}.
|
|
32
|
+
* The node is created with a two-layer prototype chain:
|
|
33
|
+
* `node → cache → pathTreeNodeShared`. The intermediate `cache` layer is a
|
|
34
|
+
* per-node object that holds `_pathCache` when `usePathCache` is enabled,
|
|
35
|
+
* keeping the cached value isolated to each node while still inheriting `base`
|
|
36
|
+
* and `getPath` from `pathTreeNodeShared`.
|
|
37
|
+
* `depth` is initialised to `-1` and must be set by the caller.
|
|
38
|
+
*/
|
|
58
39
|
private initNode;
|
|
59
40
|
/**
|
|
60
41
|
* Recursively reads {@link dirPath} and builds child nodes for each entry that
|
|
61
42
|
* passes {@link applyFilter}. Directories are traversed depth-first;
|
|
62
43
|
* files (when {@link fileVisible} is true) become leaf nodes.
|
|
63
|
-
*
|
|
64
|
-
* @param
|
|
44
|
+
*
|
|
45
|
+
* @param dirPath - Absolute path of the directory to read
|
|
46
|
+
* @param parent - The parent node to attach child nodes to
|
|
47
|
+
* @param segments - Optional explicit list of entry names to use instead of reading the
|
|
48
|
+
* directory from disk; used by {@link buildBySegments} to skip a
|
|
49
|
+
* redundant `readdirSync` when the segment list is already known
|
|
65
50
|
*/
|
|
66
51
|
private buildChildren;
|
|
67
52
|
/**
|
|
68
53
|
* Validates that every entry in {@link relativeSegments} refers to an accessible
|
|
69
54
|
* path under {@link base}. When {@link fileVisible} is false, each path must be
|
|
70
55
|
* a directory; when true, regular files are also accepted.
|
|
71
|
-
* @param relativeSegments - Relative path strings to validate
|
|
56
|
+
* @param relativeSegments - Relative path strings to validate; assumed to be a string
|
|
57
|
+
* array (callers are responsible for type safety at the boundary)
|
|
72
58
|
*/
|
|
73
59
|
private checkRelativePaths;
|
|
74
60
|
/**
|
|
@@ -84,23 +70,24 @@ export declare class PathTreeify {
|
|
|
84
70
|
*/
|
|
85
71
|
private getAllEntriesUnderBase;
|
|
86
72
|
/**
|
|
87
|
-
* Builds a subtree
|
|
88
|
-
*
|
|
89
|
-
* @
|
|
73
|
+
* Builds a subtree whose top-depth children correspond to {@link segments}.
|
|
74
|
+
* The root node is created at depth 0; children are built by delegating to
|
|
75
|
+
* {@link buildChildren}, passing {@link segments} directly to avoid a redundant
|
|
76
|
+
* `readdirSync` of the base directory.
|
|
77
|
+
* @param segments - Normalised and validated relative path segments
|
|
90
78
|
*/
|
|
91
79
|
private buildBySegments;
|
|
92
80
|
/**
|
|
93
|
-
* Builds a subtree from top-
|
|
94
|
-
* Note: this predicate only affects top-
|
|
81
|
+
* Builds a subtree from top-depth entries whose names satisfy {@link filter}.
|
|
82
|
+
* Note: this predicate only affects top-depth selection, not recursive traversal.
|
|
95
83
|
* For recursive filtering use the `filter` constructor option.
|
|
96
|
-
* @param filter - Predicate applied to each top-
|
|
84
|
+
* @param filter - Predicate applied to each top-depth entry name
|
|
97
85
|
*/
|
|
98
86
|
private buildByFilter;
|
|
99
87
|
/** Overload: build the tree from an explicit list of relative path segments */
|
|
100
88
|
buildBy(segments: string[]): PathTreeNode;
|
|
101
|
-
/** Overload: build the tree from a predicate applied to top-
|
|
89
|
+
/** Overload: build the tree from a predicate applied to top-depth entry names */
|
|
102
90
|
buildBy(filter: (segment: string) => boolean): PathTreeNode;
|
|
103
91
|
/** Builds a full tree from all immediate entries under the base path */
|
|
104
92
|
build(): PathTreeNode;
|
|
105
93
|
}
|
|
106
|
-
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** A filter function that determines whether an entry should be included in the tree */
|
|
2
|
+
export type FilterFunction = (params: {
|
|
3
|
+
name: string;
|
|
4
|
+
dirPath: string;
|
|
5
|
+
}) => boolean;
|
|
6
|
+
/** Constructor options for PathTreeify */
|
|
7
|
+
export interface PathTreeifyProps {
|
|
8
|
+
/** The root directory to scan */
|
|
9
|
+
base: string;
|
|
10
|
+
/** Optional filter applied to every entry during recursive traversal */
|
|
11
|
+
filter?: FilterFunction;
|
|
12
|
+
/** When true, files are included as leaf nodes alongside directories. Defaults to false */
|
|
13
|
+
fileVisible?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* When true, the result of {@link PathTreeNode.getPath} is memoised on each node
|
|
16
|
+
* after the first call. Subsequent calls return the same object reference without
|
|
17
|
+
* re-walking the parent chain. Useful when nodes are accessed repeatedly.
|
|
18
|
+
* Defaults to false.
|
|
19
|
+
*/
|
|
20
|
+
usePathCache?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Classification of a node in the path tree */
|
|
23
|
+
export declare enum PathTreeNodeKind {
|
|
24
|
+
Dir = "dir",
|
|
25
|
+
File = "file",
|
|
26
|
+
/** Assigned before the node's type has been resolved */
|
|
27
|
+
Unknown = "unknown",
|
|
28
|
+
BrokenSymlink = "broken_symlink",
|
|
29
|
+
Other = "other",
|
|
30
|
+
NotFound = "not_found",
|
|
31
|
+
PermissionDenied = "permission_denied",
|
|
32
|
+
Error = "error"
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Public interface for a node in the path tree.
|
|
36
|
+
* Consumers receive this type; the internal implementation class is not exported.
|
|
37
|
+
*/
|
|
38
|
+
export interface PathTreeNode {
|
|
39
|
+
/** Distance from the root node; root itself is 0, its direct children are 1, and so on */
|
|
40
|
+
depth: number;
|
|
41
|
+
/** Reference to the parent node; null for the root node */
|
|
42
|
+
parent: PathTreeNode | null;
|
|
43
|
+
/** The entry name of this node (not a full path) */
|
|
44
|
+
value: string;
|
|
45
|
+
/** Child nodes; non-empty only for directory nodes */
|
|
46
|
+
children: PathTreeNode[];
|
|
47
|
+
/** Whether this node is a directory, a file, or not yet resolved */
|
|
48
|
+
type: PathTreeNodeKind;
|
|
49
|
+
/**
|
|
50
|
+
* Walks up the parent chain to compute this node's relative and absolute paths.
|
|
51
|
+
* When the owning {@link PathTreeify} instance was created with `usePathCache: true`,
|
|
52
|
+
* the result is memoised after the first call and the same object is returned on
|
|
53
|
+
* every subsequent call.
|
|
54
|
+
* @returns `relative` — path from the tree root; `absolute` — fully resolved path on disk
|
|
55
|
+
*/
|
|
56
|
+
getPath(): {
|
|
57
|
+
relative: string;
|
|
58
|
+
absolute: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PathTreeNode, PathTreeNodeKind } from "./types";
|
|
2
|
+
/** Defines read-only properties on an object. Each property is set to the corresponding value in the props object, and is non-writable, non-configurable, and non-enumerable.
|
|
3
|
+
* @param obj - The target object on which to define the properties
|
|
4
|
+
* @param props - An object where keys are property names and values are the corresponding property values to set
|
|
5
|
+
*/
|
|
6
|
+
export declare function defineReadOnlyProps(obj: any, props: {
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}): void;
|
|
9
|
+
/** Determines the type of a given path, classifying it as a directory, file, symbolic link, or other. If the path does not exist, it is classified as NotFound. If any unexpected error occurs during the stat/lstat operations, it will be thrown to the caller.
|
|
10
|
+
* @param p - The path to classify
|
|
11
|
+
* @returns A PathTreeNodeKind value indicating the type of the path
|
|
12
|
+
*/
|
|
13
|
+
export declare function getPathType(p: string): PathTreeNodeKind;
|
|
14
|
+
/** A safer wrapper around getPathType that catches any unexpected errors and returns PathTreeNodeKind.Error instead. This is useful for scenarios where we want to classify inaccessible paths without crashing the entire process. */
|
|
15
|
+
export declare function getSafePathType(p: string): PathTreeNodeKind;
|
|
16
|
+
/** Implementation of the PathTreeNode interface. This class is not exported; consumers receive nodes through the PathTreeify API as the PathTreeNode interface type. */
|
|
17
|
+
export declare class PathTreeNodeImp implements PathTreeNode {
|
|
18
|
+
depth: number;
|
|
19
|
+
parent: PathTreeNode | null;
|
|
20
|
+
value: string;
|
|
21
|
+
children: PathTreeNode[];
|
|
22
|
+
type: PathTreeNodeKind;
|
|
23
|
+
constructor(props: {
|
|
24
|
+
usePathCache: boolean;
|
|
25
|
+
});
|
|
26
|
+
/** Retrieves the absolute and relative path represented by this node. If path caching is enabled, the result will be cached after the first retrieval to optimize subsequent calls. */
|
|
27
|
+
getPath(): {
|
|
28
|
+
relative: string;
|
|
29
|
+
absolute: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "path-treeify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-beta.004782a",
|
|
4
4
|
"description": "Convert a path or an array of paths into a tree-structured JavaScript object, where each node has a `parent` property that holds a circular reference to its parent node.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"test": "ava",
|
|
28
28
|
"test:coverage": "c8 ava",
|
|
29
|
-
"test:
|
|
30
|
-
"test:report": "rimraf reports && npm run test:coverage && npm run test:
|
|
29
|
+
"test:md": "mkdir -p reports && ava --tap | tap-json | node ./.github/scripts/generate-report.js --title \"${npm_config_title:-AVA Test Results}\" > reports/ava-test.md",
|
|
30
|
+
"test:report": "rimraf reports && npm run test:coverage && npm run test:md",
|
|
31
31
|
"clean": "rimraf dist",
|
|
32
32
|
"build": "npm run clean && rollup -c",
|
|
33
33
|
"build:dev": "NODE_ENV=development npm run build",
|
|
34
34
|
"build:prod": "NODE_ENV=production npm run build",
|
|
35
|
-
"
|
|
35
|
+
"dev": "nodemon --watch index.ts -e ts --exec \"npm run build:dev\"",
|
|
36
36
|
"check-publish": "npm pack --dry-run",
|
|
37
37
|
"publish:beta": "npm publish --tag beta --access public",
|
|
38
38
|
"publish:latest": "npm publish --tag latest --access public"
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"nodemon": "^3.1.14",
|
|
94
94
|
"rimraf": "^6.1.3",
|
|
95
95
|
"rollup": "^4.59.0",
|
|
96
|
-
"tap-
|
|
96
|
+
"tap-json": "^1.0.0",
|
|
97
97
|
"tslib": "^2.8.1",
|
|
98
98
|
"tsx": "^4.21.0",
|
|
99
99
|
"typescript": "^5.9.3"
|