path-treeify 1.3.0 β 1.4.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 +108 -52
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/types/dev/index.d.ts +1 -0
- package/dist/types/index.d.ts +104 -46
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img alt="LOGO" width="320" src="https://assets-amu.pages.dev/path-treeify/logo.png">
|
|
3
|
+
</div>
|
|
4
|
+
<br/>
|
|
2
5
|
|
|
3
6
|
> π [δΈζζζ‘£ (Chinese README)](https://github.com/isaaxite/path-treeify/blob/main/docs/README.zh-CN.md)
|
|
4
7
|
|
|
@@ -14,6 +17,9 @@
|
|
|
14
17
|
<a href="https://nodejs.org">
|
|
15
18
|
<img alt="node" src="https://img.shields.io/node/v/path-treeify">
|
|
16
19
|
</a>
|
|
20
|
+
<a href="https://github.com/isaaxite/path-treeify/blob/main/CHANGELOG.md">
|
|
21
|
+
<img alt="CHANGELOG" src="https://img.shields.io/badge/changelog-maintained-brightgreen">
|
|
22
|
+
</a>
|
|
17
23
|
<a href="https://github.com/isaaxite/path-treeify/blob/main/LICENSE">
|
|
18
24
|
<img alt="GitHub License" src="https://img.shields.io/github/license/isaaxite/path-treeify">
|
|
19
25
|
</a>
|
|
@@ -29,8 +35,8 @@
|
|
|
29
35
|
<a href="https://github.com/isaaxite/path-treeify/commits/main/">
|
|
30
36
|
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/isaaxite/path-treeify">
|
|
31
37
|
</a>
|
|
32
|
-
<a href='https://
|
|
33
|
-
<img src='https://github.com/isaaxite/path-treeify/actions/workflows/unittests.yml/badge.svg' alt='
|
|
38
|
+
<a href='https://github.com/isaaxite/path-treeify/actions/workflows/unittests.yml'>
|
|
39
|
+
<img src='https://github.com/isaaxite/path-treeify/actions/workflows/unittests.yml/badge.svg' alt='Test CI Status' />
|
|
34
40
|
</a>
|
|
35
41
|
<a href='https://coveralls.io/github/isaaxite/path-treeify'>
|
|
36
42
|
<img src='https://coveralls.io/repos/github/isaaxite/path-treeify/badge.svg' alt='Coverage Status' />
|
|
@@ -44,9 +50,13 @@
|
|
|
44
50
|
- π² Builds a recursive tree from one or more directory paths
|
|
45
51
|
- π Each node carries a `parent` circular reference for upward traversal
|
|
46
52
|
- π Each node exposes a `getPath()` method to retrieve its own paths directly
|
|
47
|
-
-
|
|
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
|
|
55
|
+
- ποΈ `fileVisible` option includes files as leaf nodes alongside directories
|
|
56
|
+
- π Optional `filter` callback applied at **every depth**, including top-level entries
|
|
48
57
|
- β‘ `build()` scans the entire `base` directory with zero configuration
|
|
49
|
-
- ποΈ `buildBy()` accepts either a
|
|
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
|
|
50
60
|
- π¦ Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
51
61
|
- π« Zero runtime dependencies
|
|
52
62
|
|
|
@@ -73,18 +83,27 @@ yarn add path-treeify
|
|
|
73
83
|
## Quick Start
|
|
74
84
|
|
|
75
85
|
```ts
|
|
76
|
-
import { PathTreeify } from 'path-treeify';
|
|
86
|
+
import { PathTreeify, PathTreeNodeKind } from 'path-treeify';
|
|
77
87
|
|
|
88
|
+
// Directories only (default)
|
|
78
89
|
const treeify = new PathTreeify({ base: '/your/project/root' });
|
|
90
|
+
const tree = treeify.build();
|
|
79
91
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
// Include files as leaf nodes
|
|
93
|
+
const treeifyWithFiles = new PathTreeify({
|
|
94
|
+
base: '/your/project/root',
|
|
95
|
+
fileVisible: true,
|
|
96
|
+
});
|
|
97
|
+
const fullTree = treeifyWithFiles.build();
|
|
98
|
+
|
|
99
|
+
// Check node type and depth
|
|
100
|
+
for (const child of tree.children) {
|
|
101
|
+
if (child.type === PathTreeNodeKind.Dir) {
|
|
102
|
+
console.log(`dir (depth ${child.depth}):`, child.value);
|
|
103
|
+
} else if (child.type === PathTreeNodeKind.File) {
|
|
104
|
+
console.log(`file (depth ${child.depth}):`, child.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
88
107
|
```
|
|
89
108
|
|
|
90
109
|
---
|
|
@@ -95,10 +114,12 @@ const fullTree = treeify.build();
|
|
|
95
114
|
|
|
96
115
|
Creates a new instance.
|
|
97
116
|
|
|
98
|
-
| Option
|
|
99
|
-
|
|
100
|
-
| `base`
|
|
101
|
-
| `filter`
|
|
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 |
|
|
102
123
|
|
|
103
124
|
`base` must exist and be a directory, otherwise the constructor throws.
|
|
104
125
|
|
|
@@ -110,14 +131,14 @@ Used as the `filter` option in the constructor. Applied at every level of the tr
|
|
|
110
131
|
|
|
111
132
|
```ts
|
|
112
133
|
type FilterFunction = (params: {
|
|
113
|
-
name: string; //
|
|
134
|
+
name: string; // entry name (file or directory name)
|
|
114
135
|
dirPath: string; // absolute path of the parent directory
|
|
115
136
|
}) => boolean;
|
|
116
137
|
```
|
|
117
138
|
|
|
118
|
-
Return `true` to **include** the
|
|
139
|
+
Return `true` to **include** the entry; `false` to **skip** it entirely.
|
|
119
140
|
|
|
120
|
-
**Example β exclude `node_modules` and hidden
|
|
141
|
+
**Example β exclude `node_modules` and hidden entries at every depth:**
|
|
121
142
|
|
|
122
143
|
```ts
|
|
123
144
|
const treeify = new PathTreeify({
|
|
@@ -130,7 +151,7 @@ const treeify = new PathTreeify({
|
|
|
130
151
|
|
|
131
152
|
### `build(): PathTreeNode`
|
|
132
153
|
|
|
133
|
-
Scans all
|
|
154
|
+
Scans all entries directly under `base` that pass the instance-level `filter` and returns a synthetic root `PathTreeNode`. When `fileVisible` is `true`, files are included as leaf nodes.
|
|
134
155
|
|
|
135
156
|
```ts
|
|
136
157
|
const tree = treeify.build();
|
|
@@ -138,64 +159,73 @@ const tree = treeify.build();
|
|
|
138
159
|
|
|
139
160
|
---
|
|
140
161
|
|
|
141
|
-
### `buildBy(
|
|
162
|
+
### `buildBy(segments: string[]): PathTreeNode`
|
|
142
163
|
|
|
143
|
-
Builds a tree from the given list of
|
|
164
|
+
Builds a tree from the given list of relative path segments. When `fileVisible` is `true`, file paths are also accepted.
|
|
144
165
|
|
|
145
166
|
```ts
|
|
146
167
|
const tree = treeify.buildBy(['src', 'docs', 'tests']);
|
|
168
|
+
|
|
169
|
+
// With fileVisible: true, files can also be specified
|
|
170
|
+
const treeWithFiles = new PathTreeify({ base: '/your/project', fileVisible: true });
|
|
171
|
+
treeWithFiles.buildBy(['src', 'README.md']);
|
|
147
172
|
```
|
|
148
173
|
|
|
149
|
-
-
|
|
150
|
-
-
|
|
174
|
+
- Both `/` and `\` separators are normalised automatically.
|
|
175
|
+
- Leading/trailing slashes and empty segments are stripped.
|
|
176
|
+
- Throws if any segment does not resolve to a valid entry under `base`.
|
|
151
177
|
|
|
152
|
-
### `buildBy(filter: (
|
|
178
|
+
### `buildBy(filter: (segment: string) => boolean): PathTreeNode`
|
|
153
179
|
|
|
154
|
-
Collects all top-level
|
|
180
|
+
Collects all top-level entries under `base`, applies the predicate to select which ones to include, then builds a tree. The instance-level `filter` still applies during deep traversal.
|
|
155
181
|
|
|
156
182
|
```ts
|
|
157
183
|
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
158
184
|
```
|
|
159
185
|
|
|
160
|
-
> **Note:** the predicate passed to `buildBy(fn)` only selects which **top-level**
|
|
186
|
+
> **Note:** the predicate passed to `buildBy(fn)` only selects which **top-level** entries to include. To filter entries at every depth, pass a `filter` to the constructor.
|
|
161
187
|
|
|
162
188
|
---
|
|
163
189
|
|
|
164
|
-
### `
|
|
190
|
+
### `PathTreeNode`
|
|
165
191
|
|
|
166
|
-
|
|
192
|
+
`PathTreeNode` is an **interface** β each node exposes a `getPath()` method to retrieve its paths without needing the `PathTreeify` instance.
|
|
167
193
|
|
|
168
194
|
```ts
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
//
|
|
195
|
+
interface PathTreeNode {
|
|
196
|
+
depth: number; // distance from root; root is 0, its children are 1, etc.
|
|
197
|
+
parent: PathTreeNode | null; // null only on the synthetic root
|
|
198
|
+
value: string; // entry name for this node
|
|
199
|
+
children: PathTreeNode[]; // empty for file nodes
|
|
200
|
+
type: PathTreeNodeKind; // Dir, File, or Unknown
|
|
201
|
+
|
|
202
|
+
getPath(): { relative: string; absolute: string };
|
|
203
|
+
}
|
|
172
204
|
```
|
|
173
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
|
+
|
|
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.
|
|
209
|
+
|
|
174
210
|
---
|
|
175
211
|
|
|
176
|
-
### `
|
|
212
|
+
### `PathTreeNodeKind`
|
|
177
213
|
|
|
178
|
-
|
|
214
|
+
An enum classifying each node's filesystem type.
|
|
179
215
|
|
|
180
216
|
```ts
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
getPath(): { relative: string; absolute: string };
|
|
217
|
+
enum PathTreeNodeKind {
|
|
218
|
+
Dir = 'dir',
|
|
219
|
+
File = 'file',
|
|
220
|
+
Unknown = 'unknown', // assigned before the type is resolved
|
|
187
221
|
}
|
|
188
222
|
```
|
|
189
223
|
|
|
190
|
-
`node.getPath()` returns the same result as `treeify.getPathBy(node)` β both are available for convenience.
|
|
191
|
-
|
|
192
|
-
> β οΈ **Circular references** β `parent` points back up the tree. Use `JSON.stringify` replacers or a library like `flatted` if you need to serialize the result.
|
|
193
|
-
|
|
194
224
|
---
|
|
195
225
|
|
|
196
226
|
## Examples
|
|
197
227
|
|
|
198
|
-
###
|
|
228
|
+
### Directories only (default)
|
|
199
229
|
|
|
200
230
|
```ts
|
|
201
231
|
import { PathTreeify } from 'path-treeify';
|
|
@@ -204,6 +234,16 @@ const treeify = new PathTreeify({ base: '/your/project' });
|
|
|
204
234
|
const tree = treeify.build();
|
|
205
235
|
```
|
|
206
236
|
|
|
237
|
+
### Include files as leaf nodes
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const treeify = new PathTreeify({
|
|
241
|
+
base: '/your/project',
|
|
242
|
+
fileVisible: true,
|
|
243
|
+
});
|
|
244
|
+
const tree = treeify.build();
|
|
245
|
+
```
|
|
246
|
+
|
|
207
247
|
### Exclude directories at every depth via constructor filter
|
|
208
248
|
|
|
209
249
|
```ts
|
|
@@ -214,13 +254,13 @@ const treeify = new PathTreeify({
|
|
|
214
254
|
const tree = treeify.build();
|
|
215
255
|
```
|
|
216
256
|
|
|
217
|
-
### Scan specific
|
|
257
|
+
### Scan specific paths
|
|
218
258
|
|
|
219
259
|
```ts
|
|
220
260
|
const tree = treeify.buildBy(['src', 'tests', 'docs']);
|
|
221
261
|
```
|
|
222
262
|
|
|
223
|
-
### Select top-level
|
|
263
|
+
### Select top-level entries with a predicate
|
|
224
264
|
|
|
225
265
|
```ts
|
|
226
266
|
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
@@ -232,7 +272,7 @@ const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith
|
|
|
232
272
|
function printPaths(node) {
|
|
233
273
|
for (const child of node.children) {
|
|
234
274
|
const { absolute } = child.getPath();
|
|
235
|
-
console.log(absolute);
|
|
275
|
+
console.log(`[${child.type}] depth=${child.depth} ${absolute}`);
|
|
236
276
|
printPaths(child);
|
|
237
277
|
}
|
|
238
278
|
}
|
|
@@ -240,10 +280,26 @@ function printPaths(node) {
|
|
|
240
280
|
printPaths(tree);
|
|
241
281
|
```
|
|
242
282
|
|
|
283
|
+
### Cache `getPath()` results for repeated traversal
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const treeify = new PathTreeify({
|
|
287
|
+
base: '/your/project',
|
|
288
|
+
usePathCache: true,
|
|
289
|
+
});
|
|
290
|
+
const tree = treeify.build();
|
|
291
|
+
|
|
292
|
+
// First call walks the parent chain and caches the result
|
|
293
|
+
const pathA = tree.children[0].getPath();
|
|
294
|
+
// Subsequent calls return the cached object directly
|
|
295
|
+
const pathB = tree.children[0].getPath();
|
|
296
|
+
console.log(pathA === pathB); // true
|
|
297
|
+
```
|
|
298
|
+
|
|
243
299
|
### CommonJS usage
|
|
244
300
|
|
|
245
301
|
```js
|
|
246
|
-
const { PathTreeify } = require('path-treeify');
|
|
302
|
+
const { PathTreeify, PathTreeNodeKind } = require('path-treeify');
|
|
247
303
|
|
|
248
304
|
const treeify = new PathTreeify({ base: __dirname });
|
|
249
305
|
const tree = treeify.build();
|
|
@@ -253,4 +309,4 @@ const tree = treeify.build();
|
|
|
253
309
|
|
|
254
310
|
## License
|
|
255
311
|
|
|
256
|
-
[MIT](https://github.com/isaaxite/path-treeify/blob/main/LICENSE) Β© [isaaxite](https://github.com/isaaxite)
|
|
312
|
+
[MIT](https://github.com/isaaxite/path-treeify/blob/main/LICENSE) Β© [isaaxite](https://github.com/isaaxite)
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("fs"),
|
|
1
|
+
"use strict";var e,t=require("fs"),i=require("path");class r{static isValid(e){try{return t.accessSync(e,t.constants.F_OK),!0}catch{return!1}}static isDirectory(e){try{return t.statSync(e).isDirectory()}catch{return!1}}static isFile(e){try{return t.statSync(e).isFile()}catch{return!1}}}exports.PathTreeNodeKind=void 0,(e=exports.PathTreeNodeKind||(exports.PathTreeNodeKind={})).Dir="dir",e.File="file",e.Unknown="unknown";class s{constructor(e){this.usePathCache=!1,this.base=e.base,"boolean"==typeof e.usePathCache&&(this.usePathCache=e.usePathCache)}getPath(){let e=this;const t=()=>{let t="",r=this;for(;r.parent;)t=t?`${r.value}${i.sep}${t}`:r.value,r=r.parent;return{relative:t,absolute:i.resolve(e.base,t)}};return e.usePathCache?(e._pathCache||(e._pathCache=t()),e._pathCache):t()}}exports.PathTreeify=class{constructor(e){this.fileVisible=!1;const{filter:t,base:i,fileVisible:n,usePathCache:a}=e;if("boolean"==typeof n&&n&&(this.fileVisible=n),void 0!==t&&(this.validateFilter(t),this.userFilter=t),!i||!r.isValid(i))throw new Error(`${i} is not a valid path!`);if(!r.isDirectory(i))throw new Error(`${i} is not a dirPath!`);this.base=i,this.pathTreeNodeShared=new s({base:i,usePathCache:a})}applyFilter(e,t){return!(!this.fileVisible&&!r.isDirectory(e))&&(!this.userFilter||this.userFilter({name:t,dirPath:i.dirname(e)}))}validateFilter(e){if("function"!=typeof e)throw new TypeError("filter must be a function")}initNode(){const e=Object.create(this.pathTreeNodeShared),t=Object.create(e);return t.parent=null,t.value="",t.children=[],t.type=exports.PathTreeNodeKind.Unknown,t.depth=-1,t}buildChildren(e,s,n){const a=[],o=n||t.readdirSync(e),h=s.depth+1;for(const t of o){const n=i.join(e,t);if(!this.applyFilter(n,t))continue;const o=this.initNode();o.depth=h,o.value=t,o.parent=s,a.push(o),this.fileVisible&&r.isFile(n)?o.type=exports.PathTreeNodeKind.File:(o.type=exports.PathTreeNodeKind.Dir,o.children=this.buildChildren(n,o))}return a}checkRelativePaths(e){for(let t=0;t<e.length;t++){const s=e[t];if("string"!=typeof s)throw new Error(`Item at index ${t} is not a string, got ${typeof s}`);const n=i.resolve(this.base,s);if(!r.isValid(n))throw new Error(`Path does not exist or is not accessible: ${n} (from relative path: ${s})`);if(!(r.isDirectory(n)||this.fileVisible&&r.isFile(n)))throw new Error(`Path is not a directory: ${n} (from relative path: ${s})`)}}formatSegments(e){return e.map(e=>e.split(/[/\\]/).filter(Boolean).join(i.sep)).filter(Boolean)}getAllEntriesUnderBase(){return t.readdirSync(this.base).filter(e=>{const t=i.resolve(this.base,e);return this.applyFilter(t,e)})}buildBySegments(e){const t=this.initNode();return t.depth=0,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{readdirSync as t,
|
|
1
|
+
import{readdirSync as t,accessSync as e,constants as i,statSync as r}from"fs";import{dirname as s,join as n,resolve as a,sep as h}from"path";class l{static isValid(t){try{return e(t,i.F_OK),!0}catch{return!1}}static isDirectory(t){try{return r(t).isDirectory()}catch{return!1}}static isFile(t){try{return r(t).isFile()}catch{return!1}}}var o;!function(t){t.Dir="dir",t.File="file",t.Unknown="unknown"}(o||(o={}));class c{constructor(t){this.usePathCache=!1,this.base=t.base,"boolean"==typeof t.usePathCache&&(this.usePathCache=t.usePathCache)}getPath(){let t=this;const e=()=>{let e="",i=this;for(;i.parent;)e=e?`${i.value}${h}${e}`:i.value,i=i.parent;return{relative:e,absolute:a(t.base,e)}};return t.usePathCache?(t._pathCache||(t._pathCache=e()),t._pathCache):e()}}class u{constructor(t){this.fileVisible=!1;const{filter:e,base:i,fileVisible:r,usePathCache:s}=t;if("boolean"==typeof r&&r&&(this.fileVisible=r),void 0!==e&&(this.validateFilter(e),this.userFilter=e),!i||!l.isValid(i))throw new Error(`${i} is not a valid path!`);if(!l.isDirectory(i))throw new Error(`${i} is not a dirPath!`);this.base=i,this.pathTreeNodeShared=new c({base:i,usePathCache:s})}applyFilter(t,e){return!(!this.fileVisible&&!l.isDirectory(t))&&(!this.userFilter||this.userFilter({name:e,dirPath:s(t)}))}validateFilter(t){if("function"!=typeof t)throw new TypeError("filter must be a function")}initNode(){const t=Object.create(this.pathTreeNodeShared),e=Object.create(t);return e.parent=null,e.value="",e.children=[],e.type=o.Unknown,e.depth=-1,e}buildChildren(e,i,r){const s=[],a=r||t(e),h=i.depth+1;for(const t of a){const r=n(e,t);if(!this.applyFilter(r,t))continue;const a=this.initNode();a.depth=h,a.value=t,a.parent=i,s.push(a),this.fileVisible&&l.isFile(r)?a.type=o.File:(a.type=o.Dir,a.children=this.buildChildren(r,a))}return s}checkRelativePaths(t){for(let e=0;e<t.length;e++){const i=t[e];if("string"!=typeof i)throw new Error(`Item at index ${e} is not a string, got ${typeof i}`);const r=a(this.base,i);if(!l.isValid(r))throw new Error(`Path does not exist or is not accessible: ${r} (from relative path: ${i})`);if(!(l.isDirectory(r)||this.fileVisible&&l.isFile(r)))throw new Error(`Path is not a directory: ${r} (from relative path: ${i})`)}}formatSegments(t){return t.map(t=>t.split(/[/\\]/).filter(Boolean).join(h)).filter(Boolean)}getAllEntriesUnderBase(){return t(this.base).filter(t=>{const e=a(this.base,t);return this.applyFilter(e,t)})}buildBySegments(t){const e=this.initNode();return e.depth=0,e.children=this.buildChildren(this.base,e,t),e}buildByFilter(t){const e=this.getAllEntriesUnderBase();return this.buildBySegments(e.filter(t))}buildBy(t){if(Array.isArray(t)){const e=this.formatSegments(t);return this.checkRelativePaths(e),this.buildBySegments(e)}if("function"==typeof t)return this.buildByFilter(t);throw new TypeError("buildBy: expected an array of strings or a filter function, but received "+typeof t)}build(){const t=this.getAllEntriesUnderBase();return this.buildBySegments(t)}}export{o as PathTreeNodeKind,u as PathTreeify};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,93 +1,151 @@
|
|
|
1
|
-
/** A filter function that determines whether
|
|
1
|
+
/** A filter function that determines whether an entry should be included in the tree */
|
|
2
2
|
type FilterFunction = (params: {
|
|
3
3
|
name: string;
|
|
4
4
|
dirPath: string;
|
|
5
5
|
}) => boolean;
|
|
6
6
|
/** Constructor options for PathTreeify */
|
|
7
7
|
interface PathTreeifyProps {
|
|
8
|
+
/** The root directory to scan */
|
|
8
9
|
base: string;
|
|
10
|
+
/** Optional filter applied to every entry during recursive traversal */
|
|
9
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;
|
|
10
21
|
}
|
|
11
|
-
/**
|
|
12
|
-
declare
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Public interface for a node in the path tree.
|
|
31
|
+
* Consumers receive this type; the internal implementation class is not exported.
|
|
32
|
+
*/
|
|
33
|
+
export interface PathTreeNode {
|
|
34
|
+
/** Distance from the root node; root itself is 0, its direct children are 1, and so on */
|
|
35
|
+
depth: number;
|
|
15
36
|
/** Reference to the parent node; null for the root node */
|
|
16
37
|
parent: PathTreeNode | null;
|
|
17
|
-
/** The
|
|
38
|
+
/** The entry name of this node (not a full path) */
|
|
18
39
|
value: string;
|
|
19
|
-
/** Child nodes
|
|
40
|
+
/** Child nodes; non-empty only for directory nodes */
|
|
20
41
|
children: PathTreeNode[];
|
|
21
|
-
|
|
42
|
+
/** Whether this node is a directory, a file, or not yet resolved */
|
|
43
|
+
type: PathTreeNodeKind;
|
|
22
44
|
/**
|
|
23
45
|
* Walks up the parent chain to compute this node's relative and absolute paths.
|
|
24
|
-
*
|
|
46
|
+
* When the owning {@link PathTreeify} instance was created with `usePathCache: true`,
|
|
47
|
+
* the result is memoised after the first call and the same object is returned on
|
|
48
|
+
* every subsequent call.
|
|
49
|
+
* @returns `relative` β path from the tree root; `absolute` β fully resolved path on disk
|
|
25
50
|
*/
|
|
26
51
|
getPath(): {
|
|
27
52
|
relative: string;
|
|
28
53
|
absolute: string;
|
|
29
54
|
};
|
|
30
55
|
}
|
|
31
|
-
/** Builds a tree of
|
|
56
|
+
/** Builds a tree of {@link PathTreeNode} entries rooted at a given base path */
|
|
32
57
|
export declare class PathTreeify {
|
|
33
58
|
/** The root directory to scan */
|
|
34
59
|
private base;
|
|
35
|
-
/** Optional filter applied to each directory during traversal */
|
|
36
|
-
private filter?;
|
|
37
|
-
constructor({ filter, base }: Partial<PathTreeifyProps>);
|
|
38
60
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
61
|
+
* Shared prototype instance for nodes produced by this builder.
|
|
62
|
+
* All nodes created via {@link initNode} inherit `base` and `getPath` from this object,
|
|
63
|
+
* avoiding per-node storage of the base path string.
|
|
64
|
+
*/
|
|
65
|
+
private pathTreeNodeShared;
|
|
66
|
+
/**
|
|
67
|
+
* Optional user-supplied filter. When set, every entry must pass this predicate
|
|
68
|
+
* in addition to the built-in visibility check.
|
|
69
|
+
*/
|
|
70
|
+
private userFilter?;
|
|
71
|
+
/** When true, files are included as leaf nodes during traversal. Defaults to false */
|
|
72
|
+
private fileVisible;
|
|
73
|
+
constructor(props: Partial<PathTreeifyProps>);
|
|
74
|
+
/**
|
|
75
|
+
* Determines whether a given entry should be included in the tree.
|
|
76
|
+
* - If {@link fileVisible} is false, non-directory entries are always excluded.
|
|
77
|
+
* - If a {@link userFilter} is set, the entry must also satisfy it.
|
|
78
|
+
* @param absPath - Absolute path of the entry to test
|
|
79
|
+
* @param name - Entry name (filename or directory name)
|
|
80
|
+
*/
|
|
81
|
+
private applyFilter;
|
|
82
|
+
/**
|
|
83
|
+
* Asserts that the provided value is a callable {@link FilterFunction}.
|
|
84
|
+
* Throws a TypeError if the check fails.
|
|
41
85
|
*/
|
|
42
86
|
private validateFilter;
|
|
43
87
|
/**
|
|
44
|
-
* Creates
|
|
45
|
-
*
|
|
88
|
+
* Creates a new unattached {@link PathTreeNode}.
|
|
89
|
+
* The node is created with a two-layer prototype chain:
|
|
90
|
+
* `node β cache β pathTreeNodeShared`. The intermediate `cache` layer is a
|
|
91
|
+
* per-node object that holds `_pathCache` when `usePathCache` is enabled,
|
|
92
|
+
* keeping the cached value isolated to each node while still inheriting `base`
|
|
93
|
+
* and `getPath` from `pathTreeNodeShared`.
|
|
94
|
+
* `depth` is initialised to `-1` and must be set by the caller.
|
|
46
95
|
*/
|
|
47
96
|
private initNode;
|
|
48
97
|
/**
|
|
49
|
-
* Recursively reads
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
98
|
+
* Recursively reads {@link dirPath} and builds child nodes for each entry that
|
|
99
|
+
* passes {@link applyFilter}. Directories are traversed depth-first;
|
|
100
|
+
* files (when {@link fileVisible} is true) become leaf nodes.
|
|
101
|
+
*
|
|
102
|
+
* @param dirPath - Absolute path of the directory to read
|
|
103
|
+
* @param parent - The parent node to attach child nodes to
|
|
104
|
+
* @param segments - Optional explicit list of entry names to use instead of reading the
|
|
105
|
+
* directory from disk; used by {@link buildBySegments} to skip a
|
|
106
|
+
* redundant `readdirSync` when the segment list is already known
|
|
53
107
|
*/
|
|
54
108
|
private buildChildren;
|
|
55
109
|
/**
|
|
56
|
-
* Validates that
|
|
57
|
-
*
|
|
58
|
-
*
|
|
110
|
+
* Validates that every entry in {@link relativeSegments} refers to an accessible
|
|
111
|
+
* path under {@link base}. When {@link fileVisible} is false, each path must be
|
|
112
|
+
* a directory; when true, regular files are also accepted.
|
|
113
|
+
* @param relativeSegments - Relative path strings to validate; assumed to be a string
|
|
114
|
+
* array (callers are responsible for type safety at the boundary)
|
|
59
115
|
*/
|
|
60
116
|
private checkRelativePaths;
|
|
61
117
|
/**
|
|
62
|
-
*
|
|
63
|
-
* and
|
|
118
|
+
* Normalises an array of path strings by splitting on both slash styles,
|
|
119
|
+
* dropping empty segments, and rejoining with the platform separator.
|
|
120
|
+
* Entries that reduce to an empty string (e.g. `"///"`) are removed.
|
|
121
|
+
* @param segments - Raw path strings to normalise
|
|
64
122
|
*/
|
|
65
|
-
private
|
|
66
|
-
/** Returns the names of all immediate subdirectories under the base path */
|
|
67
|
-
private getAllDirNamesUnderBase;
|
|
123
|
+
private formatSegments;
|
|
68
124
|
/**
|
|
69
|
-
*
|
|
70
|
-
* @
|
|
125
|
+
* Returns the names of all immediate entries under {@link base} that pass
|
|
126
|
+
* {@link applyFilter}.
|
|
71
127
|
*/
|
|
72
|
-
private
|
|
128
|
+
private getAllEntriesUnderBase;
|
|
73
129
|
/**
|
|
74
|
-
* Builds a
|
|
75
|
-
*
|
|
130
|
+
* Builds a subtree whose top-depth children correspond to {@link segments}.
|
|
131
|
+
* The root node is created at depth 0; children are built by delegating to
|
|
132
|
+
* {@link buildChildren}, passing {@link segments} directly to avoid a redundant
|
|
133
|
+
* `readdirSync` of the base directory.
|
|
134
|
+
* @param segments - Normalised and validated relative path segments
|
|
76
135
|
*/
|
|
77
|
-
private
|
|
136
|
+
private buildBySegments;
|
|
78
137
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
138
|
+
* Builds a subtree from top-depth entries whose names satisfy {@link filter}.
|
|
139
|
+
* Note: this predicate only affects top-depth selection, not recursive traversal.
|
|
140
|
+
* For recursive filtering use the `filter` constructor option.
|
|
141
|
+
* @param filter - Predicate applied to each top-depth entry name
|
|
81
142
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/** Overload: build the tree from a predicate applied to top-level directory names */
|
|
89
|
-
buildBy(filter: (dirName: string) => boolean): PathTreeNode;
|
|
90
|
-
/** Builds a full tree from all immediate subdirectories under the base path */
|
|
143
|
+
private buildByFilter;
|
|
144
|
+
/** Overload: build the tree from an explicit list of relative path segments */
|
|
145
|
+
buildBy(segments: string[]): PathTreeNode;
|
|
146
|
+
/** Overload: build the tree from a predicate applied to top-depth entry names */
|
|
147
|
+
buildBy(filter: (segment: string) => boolean): PathTreeNode;
|
|
148
|
+
/** Builds a full tree from all immediate entries under the base path */
|
|
91
149
|
build(): PathTreeNode;
|
|
92
150
|
}
|
|
93
151
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "path-treeify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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,7 +26,7 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"test": "ava",
|
|
28
28
|
"test:coverage": "c8 ava",
|
|
29
|
-
"test:junit": "mkdir -p reports && ava --tap | tap-xunit > reports/junit.xml",
|
|
29
|
+
"test:junit": "mkdir -p reports && ava --tap | tap-xunit --package=path-treeify > reports/junit.xml",
|
|
30
30
|
"test:report": "rimraf reports && npm run test:coverage && npm run test:junit",
|
|
31
31
|
"clean": "rimraf dist",
|
|
32
32
|
"build": "npm run clean && rollup -c",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"rollup": "^4.59.0",
|
|
96
96
|
"tap-xunit": "^2.4.1",
|
|
97
97
|
"tslib": "^2.8.1",
|
|
98
|
+
"tsx": "^4.21.0",
|
|
98
99
|
"typescript": "^5.9.3"
|
|
99
100
|
},
|
|
100
101
|
"engines": {
|