path-treeify 1.1.0 → 1.2.0-beta.67126e2
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 +54 -28
- package/dist/index.cjs +1 -159
- package/dist/index.mjs +1 -157
- package/dist/types/index.d.ts +58 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div align="left">
|
|
11
|
-
<a href="https://
|
|
12
|
-
<img alt="
|
|
11
|
+
<a href="https://www.npmjs.com/package/path-treeify">
|
|
12
|
+
<img alt="NPM Version" src="https://img.shields.io/npm/v/path-treeify">
|
|
13
13
|
</a>
|
|
14
14
|
<a href="https://nodejs.org">
|
|
15
15
|
<img alt="node" src="https://img.shields.io/node/v/path-treeify">
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
- 🌲 Builds a recursive tree from one or more directory paths
|
|
39
39
|
- 🔗 Each node carries a `parent` circular reference for upward traversal
|
|
40
40
|
- 📍 Each node exposes a `getPath()` method to retrieve its own paths directly
|
|
41
|
-
- 🔍 Optional `filter` callback
|
|
42
|
-
- ⚡ `build()`
|
|
41
|
+
- 🔍 Optional `filter` callback applied at **every depth**, including top-level directories
|
|
42
|
+
- ⚡ `build()` scans the entire `base` directory with zero configuration
|
|
43
|
+
- 🎛️ `buildBy()` accepts either a directory name array or a top-level filter function
|
|
43
44
|
- 📦 Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
44
45
|
- 🚫 Zero runtime dependencies
|
|
45
46
|
|
|
@@ -70,8 +71,11 @@ import { PathTreeify } from 'path-treeify';
|
|
|
70
71
|
|
|
71
72
|
const treeify = new PathTreeify({ base: '/your/project/root' });
|
|
72
73
|
|
|
73
|
-
// Scan specific directories
|
|
74
|
-
const tree = treeify.
|
|
74
|
+
// Scan specific directories by name
|
|
75
|
+
const tree = treeify.buildBy(['src', 'tests']);
|
|
76
|
+
|
|
77
|
+
// Scan with a top-level filter function
|
|
78
|
+
const filtered = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
75
79
|
|
|
76
80
|
// Or scan everything under base at once
|
|
77
81
|
const fullTree = treeify.build();
|
|
@@ -85,10 +89,10 @@ const fullTree = treeify.build();
|
|
|
85
89
|
|
|
86
90
|
Creates a new instance.
|
|
87
91
|
|
|
88
|
-
| Option | Type | Required | Description
|
|
89
|
-
|
|
90
|
-
| `base` | `string` | ✅ | Absolute path to the root directory to scan from
|
|
91
|
-
| `filter` | `FilterFunction` (see below) | ❌ |
|
|
92
|
+
| Option | Type | Required | Description |
|
|
93
|
+
|----------|-------------------------------|----------|-----------------------------------------------------------------------------|
|
|
94
|
+
| `base` | `string` | ✅ | Absolute path to the root directory to scan from |
|
|
95
|
+
| `filter` | `FilterFunction` (see below) | ❌ | Applied at **every depth** — top-level directories included |
|
|
92
96
|
|
|
93
97
|
`base` must exist and be a directory, otherwise the constructor throws.
|
|
94
98
|
|
|
@@ -96,6 +100,8 @@ Creates a new instance.
|
|
|
96
100
|
|
|
97
101
|
### `FilterFunction`
|
|
98
102
|
|
|
103
|
+
Used as the `filter` option in the constructor. Applied at every level of the tree, including the immediate children of `base`.
|
|
104
|
+
|
|
99
105
|
```ts
|
|
100
106
|
type FilterFunction = (params: {
|
|
101
107
|
name: string; // directory name (leaf segment)
|
|
@@ -103,14 +109,14 @@ type FilterFunction = (params: {
|
|
|
103
109
|
}) => boolean;
|
|
104
110
|
```
|
|
105
111
|
|
|
106
|
-
Return `true` to **include** the directory and recurse into it; `false` to **skip** it.
|
|
112
|
+
Return `true` to **include** the directory and recurse into it; `false` to **skip** it entirely.
|
|
107
113
|
|
|
108
|
-
**Example —
|
|
114
|
+
**Example — exclude `node_modules` and hidden directories at every depth:**
|
|
109
115
|
|
|
110
116
|
```ts
|
|
111
117
|
const treeify = new PathTreeify({
|
|
112
118
|
base: '/your/project',
|
|
113
|
-
filter: ({ name }) => !name.startsWith('.')
|
|
119
|
+
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
114
120
|
});
|
|
115
121
|
```
|
|
116
122
|
|
|
@@ -118,7 +124,7 @@ const treeify = new PathTreeify({
|
|
|
118
124
|
|
|
119
125
|
### `build(): PathTreeNode`
|
|
120
126
|
|
|
121
|
-
Scans
|
|
127
|
+
Scans all subdirectories directly under `base` (applying the instance-level `filter` if set) and returns a synthetic root `PathTreeNode`.
|
|
122
128
|
|
|
123
129
|
```ts
|
|
124
130
|
const tree = treeify.build();
|
|
@@ -126,17 +132,26 @@ const tree = treeify.build();
|
|
|
126
132
|
|
|
127
133
|
---
|
|
128
134
|
|
|
129
|
-
### `
|
|
135
|
+
### `buildBy(dirNames: string[]): PathTreeNode`
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
Builds a tree from the given list of directory names (relative to `base`).
|
|
132
138
|
|
|
133
139
|
```ts
|
|
134
|
-
const
|
|
140
|
+
const tree = treeify.buildBy(['src', 'docs', 'tests']);
|
|
135
141
|
```
|
|
136
142
|
|
|
137
|
-
- Each element must be a valid, accessible directory relative to `base`.
|
|
138
143
|
- Leading and trailing slashes are stripped automatically.
|
|
139
|
-
- Throws if any
|
|
144
|
+
- Throws if any name does not resolve to a valid directory under `base`.
|
|
145
|
+
|
|
146
|
+
### `buildBy(filter: (dirName: string) => boolean): PathTreeNode`
|
|
147
|
+
|
|
148
|
+
Collects all top-level subdirectories under `base`, applies the given predicate to select which ones to include, then builds a tree from the matching names. The instance-level `filter` still applies during deep traversal.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> **Note:** the predicate passed to `buildBy(fn)` only selects which **top-level** directories to include. To filter directories at every depth, pass a `filter` to the constructor.
|
|
140
155
|
|
|
141
156
|
---
|
|
142
157
|
|
|
@@ -145,17 +160,16 @@ const root = treeify.buildByDirNames(['src', 'docs']);
|
|
|
145
160
|
Walks a node's `parent` chain to reconstruct its full path. Equivalent to calling `node.getPath()` directly.
|
|
146
161
|
|
|
147
162
|
```ts
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
// absolute → '/your/project/src'
|
|
163
|
+
const { relative, absolute } = treeify.getPathBy(node);
|
|
164
|
+
// relative → e.g. 'src/components'
|
|
165
|
+
// absolute → e.g. '/your/project/src/components'
|
|
152
166
|
```
|
|
153
167
|
|
|
154
168
|
---
|
|
155
169
|
|
|
156
170
|
### `PathTreeNode`
|
|
157
171
|
|
|
158
|
-
`PathTreeNode` is
|
|
172
|
+
`PathTreeNode` is a **class** with its own `getPath()` method, so you can retrieve a node's path without passing it back to the `PathTreeify` instance.
|
|
159
173
|
|
|
160
174
|
```ts
|
|
161
175
|
class PathTreeNode {
|
|
@@ -167,7 +181,7 @@ class PathTreeNode {
|
|
|
167
181
|
}
|
|
168
182
|
```
|
|
169
183
|
|
|
170
|
-
|
|
184
|
+
`node.getPath()` returns the same result as `treeify.getPathBy(node)` — both are available for convenience.
|
|
171
185
|
|
|
172
186
|
> ⚠️ **Circular references** — `parent` points back up the tree. Use `JSON.stringify` replacers or a library like `flatted` if you need to serialize the result.
|
|
173
187
|
|
|
@@ -180,18 +194,30 @@ class PathTreeNode {
|
|
|
180
194
|
```ts
|
|
181
195
|
import { PathTreeify } from 'path-treeify';
|
|
182
196
|
|
|
197
|
+
const treeify = new PathTreeify({ base: '/your/project' });
|
|
198
|
+
const tree = treeify.build();
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Exclude directories at every depth via constructor filter
|
|
202
|
+
|
|
203
|
+
```ts
|
|
183
204
|
const treeify = new PathTreeify({
|
|
184
205
|
base: '/your/project',
|
|
185
206
|
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
186
207
|
});
|
|
187
|
-
|
|
188
208
|
const tree = treeify.build();
|
|
189
209
|
```
|
|
190
210
|
|
|
191
211
|
### Scan specific directories
|
|
192
212
|
|
|
193
213
|
```ts
|
|
194
|
-
const tree = treeify.
|
|
214
|
+
const tree = treeify.buildBy(['src', 'tests', 'docs']);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Select top-level directories with a predicate
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
195
221
|
```
|
|
196
222
|
|
|
197
223
|
### Retrieve paths via `node.getPath()`
|
|
@@ -221,4 +247,4 @@ const tree = treeify.build();
|
|
|
221
247
|
|
|
222
248
|
## License
|
|
223
249
|
|
|
224
|
-
[MIT](https://github.com/isaaxite/path-treeify/blob/main/LICENSE) © [isaaxite](https://github.com/isaaxite)
|
|
250
|
+
[MIT](https://github.com/isaaxite/path-treeify/blob/main/LICENSE) © [isaaxite](https://github.com/isaaxite)
|
package/dist/index.cjs
CHANGED
|
@@ -1,159 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var fs = require('fs');
|
|
4
|
-
var path = require('path');
|
|
5
|
-
|
|
6
|
-
class PathValidator {
|
|
7
|
-
static isValid(path) {
|
|
8
|
-
try {
|
|
9
|
-
fs.accessSync(path, fs.constants.F_OK);
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
static isDirectory(path) {
|
|
17
|
-
try {
|
|
18
|
-
return fs.statSync(path).isDirectory();
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
class PathTreeNode {
|
|
26
|
-
constructor(base) {
|
|
27
|
-
this.parent = null;
|
|
28
|
-
this.value = '';
|
|
29
|
-
this.children = [];
|
|
30
|
-
this.base = base;
|
|
31
|
-
}
|
|
32
|
-
getPath() {
|
|
33
|
-
let relative = '';
|
|
34
|
-
let current = this;
|
|
35
|
-
while (current.parent) {
|
|
36
|
-
relative = relative
|
|
37
|
-
? `${current.value}${path.sep}${relative}`
|
|
38
|
-
: current.value;
|
|
39
|
-
current = current.parent;
|
|
40
|
-
}
|
|
41
|
-
return { relative, absolute: path.resolve(this.base, relative) };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
class PathTreeify {
|
|
45
|
-
constructor({ filter, base }) {
|
|
46
|
-
if (typeof filter !== 'undefined') {
|
|
47
|
-
this.validateFilter(filter);
|
|
48
|
-
this.filter = filter;
|
|
49
|
-
}
|
|
50
|
-
if (!base || !PathValidator.isValid(base)) {
|
|
51
|
-
throw new Error(`${base} is not a valid path!`);
|
|
52
|
-
}
|
|
53
|
-
if (!PathValidator.isDirectory(base)) {
|
|
54
|
-
throw new Error(`${base} is not a dirPath!`);
|
|
55
|
-
}
|
|
56
|
-
this.base = base;
|
|
57
|
-
}
|
|
58
|
-
validateFilter(filter) {
|
|
59
|
-
if (typeof filter !== 'function') {
|
|
60
|
-
throw new TypeError('filter must be a function');
|
|
61
|
-
}
|
|
62
|
-
if (filter.length !== 1) {
|
|
63
|
-
throw new TypeError('filter must accept exactly one parameter');
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const testResult = filter({ name: 'test', postPath: '/test' });
|
|
67
|
-
if (typeof testResult !== 'boolean') {
|
|
68
|
-
throw new TypeError('filter must return a boolean');
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
throw new TypeError('filter function threw an error during test: ' + error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
initNode(parent = null) {
|
|
76
|
-
const node = new PathTreeNode(this.base);
|
|
77
|
-
if (parent) {
|
|
78
|
-
node.parent = parent;
|
|
79
|
-
}
|
|
80
|
-
return node;
|
|
81
|
-
}
|
|
82
|
-
buildChildren(dirPath, parent) {
|
|
83
|
-
const names = fs.readdirSync(dirPath);
|
|
84
|
-
const children = [];
|
|
85
|
-
for (const name of names) {
|
|
86
|
-
const subPath = path.join(dirPath, name);
|
|
87
|
-
if (!fs.statSync(subPath).isDirectory()) {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (this.filter && !this.filter({ dirPath, name })) {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const node = this.initNode();
|
|
94
|
-
node.value = name;
|
|
95
|
-
node.parent = parent;
|
|
96
|
-
node.children = this.buildChildren(subPath, node);
|
|
97
|
-
children.push(node);
|
|
98
|
-
}
|
|
99
|
-
return children;
|
|
100
|
-
}
|
|
101
|
-
checkRelativePaths(relativeDirNames) {
|
|
102
|
-
if (!Array.isArray(relativeDirNames)) {
|
|
103
|
-
throw new Error(`Expected array, got ${typeof relativeDirNames}`);
|
|
104
|
-
}
|
|
105
|
-
for (let i = 0; i < relativeDirNames.length; i++) {
|
|
106
|
-
const it = relativeDirNames[i];
|
|
107
|
-
if (typeof it !== 'string') {
|
|
108
|
-
throw new Error(`Item at index ${i} is not a string, got ${typeof it}`);
|
|
109
|
-
}
|
|
110
|
-
const absPath = path.resolve(this.base, it);
|
|
111
|
-
if (!PathValidator.isValid(absPath)) {
|
|
112
|
-
throw new Error(`Path does not exist or is not accessible: ${absPath} (from relative path: ${it})`);
|
|
113
|
-
}
|
|
114
|
-
if (!PathValidator.isDirectory(absPath)) {
|
|
115
|
-
throw new Error(`Path is not a directory: ${absPath} (from relative path: ${it})`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
formatDirnames(dirNames) {
|
|
120
|
-
return dirNames.map(dir => {
|
|
121
|
-
// Remove leading and trailing slashes
|
|
122
|
-
return dir.replace(/^\/+|\/+$/g, '');
|
|
123
|
-
}).filter(dir => dir !== ''); // Optional: filter empty strings
|
|
124
|
-
}
|
|
125
|
-
getPathBy(node) {
|
|
126
|
-
let relative = '';
|
|
127
|
-
let current = node;
|
|
128
|
-
while (current.parent) {
|
|
129
|
-
relative = relative
|
|
130
|
-
? `${current.value}${path.sep}${relative}`
|
|
131
|
-
: current.value;
|
|
132
|
-
current = current.parent;
|
|
133
|
-
}
|
|
134
|
-
return { relative, absolute: path.resolve(this.base, relative) };
|
|
135
|
-
}
|
|
136
|
-
buildByDirNames(dirNames) {
|
|
137
|
-
const root = this.initNode();
|
|
138
|
-
this.checkRelativePaths(dirNames);
|
|
139
|
-
const dirNameArr = this.formatDirnames(dirNames);
|
|
140
|
-
for (const dirName of dirNameArr) {
|
|
141
|
-
const node = this.initNode();
|
|
142
|
-
node.value = dirName;
|
|
143
|
-
node.parent = root;
|
|
144
|
-
node.children = this.buildChildren(path.resolve(this.base, dirName), node);
|
|
145
|
-
root.children.push(node);
|
|
146
|
-
}
|
|
147
|
-
return root;
|
|
148
|
-
}
|
|
149
|
-
build() {
|
|
150
|
-
const dirNameArr = fs.readdirSync(this.base).filter(name => {
|
|
151
|
-
const abs = path.resolve(this.base, name);
|
|
152
|
-
return PathValidator.isDirectory(abs);
|
|
153
|
-
});
|
|
154
|
-
return this.buildByDirNames(dirNameArr);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
exports.PathTreeify = PathTreeify;
|
|
159
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
"use strict";var t=require("fs"),e=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}}}class i{constructor(t){this.parent=null,this.value="",this.children=[],this.base=t}getPath(){let t="",r=this;for(;r.parent;)t=t?`${r.value}${e.sep}${t}`:r.value,r=r.parent;return{relative:t,absolute:e.resolve(this.base,t)}}}exports.PathTreeify=class{constructor({filter:t,base:e}){if(void 0!==t&&(this.validateFilter(t),this.filter=t),!e||!r.isValid(e))throw new Error(`${e} is not a valid path!`);if(!r.isDirectory(e))throw new Error(`${e} is not a dirPath!`);this.base=e}validateFilter(t){if("function"!=typeof t)throw new TypeError("filter must be a function");if(1!==t.length)throw new TypeError("filter must accept exactly one parameter");try{if("boolean"!=typeof t({name:"test",postPath:"/test"}))throw new TypeError("filter must return a boolean")}catch(t){throw new TypeError("filter function threw an error during test: "+t)}}initNode(t=null){const e=new i(this.base);return t&&(e.parent=t),e}buildChildren(r,i){const s=t.readdirSync(r),a=[];for(const n of s){const s=e.join(r,n);if(!t.statSync(s).isDirectory())continue;if(this.filter&&!this.filter({dirPath:r,name:n}))continue;const o=this.initNode();o.value=n,o.parent=i,o.children=this.buildChildren(s,o),a.push(o)}return a}checkRelativePaths(t){if(!Array.isArray(t))throw new Error("Expected array, got "+typeof t);for(let i=0;i<t.length;i++){const s=t[i];if("string"!=typeof s)throw new Error(`Item at index ${i} is not a string, got ${typeof s}`);const a=e.resolve(this.base,s);if(!r.isValid(a))throw new Error(`Path does not exist or is not accessible: ${a} (from relative path: ${s})`);if(!r.isDirectory(a))throw new Error(`Path is not a directory: ${a} (from relative path: ${s})`)}}formatDirnames(t){return t.map(t=>t.replace(/^\/+|\/+$/g,"")).filter(t=>""!==t)}getAllDirNamesUnderBase(){return t.readdirSync(this.base).filter(t=>{const i=e.resolve(this.base,t);return!!r.isDirectory(i)&&!(this.filter&&!this.filter({name:t,dirPath:this.base}))})}buildByDirNames(t){const r=this.initNode(),i=this.formatDirnames(t);this.checkRelativePaths(i);for(const t of i){const i=this.initNode();i.value=t,i.parent=r,i.children=this.buildChildren(e.resolve(this.base,t),i),r.children.push(i)}return r}buildByFilter(t){const e=this.getAllDirNamesUnderBase();return this.buildByDirNames(e.filter(t))}getPathBy(t){let r="",i=t;for(;i.parent;)r=r?`${i.value}${e.sep}${r}`:i.value,i=i.parent;return{relative:r,absolute:e.resolve(this.base,r)}}buildBy(t){if(Array.isArray(t))return this.buildByDirNames(t);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.getAllDirNamesUnderBase();return this.buildByDirNames(t)}};
|
package/dist/index.mjs
CHANGED
|
@@ -1,157 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, resolve, sep } from 'path';
|
|
3
|
-
|
|
4
|
-
class PathValidator {
|
|
5
|
-
static isValid(path) {
|
|
6
|
-
try {
|
|
7
|
-
accessSync(path, constants.F_OK);
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
static isDirectory(path) {
|
|
15
|
-
try {
|
|
16
|
-
return statSync(path).isDirectory();
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
class PathTreeNode {
|
|
24
|
-
constructor(base) {
|
|
25
|
-
this.parent = null;
|
|
26
|
-
this.value = '';
|
|
27
|
-
this.children = [];
|
|
28
|
-
this.base = base;
|
|
29
|
-
}
|
|
30
|
-
getPath() {
|
|
31
|
-
let relative = '';
|
|
32
|
-
let current = this;
|
|
33
|
-
while (current.parent) {
|
|
34
|
-
relative = relative
|
|
35
|
-
? `${current.value}${sep}${relative}`
|
|
36
|
-
: current.value;
|
|
37
|
-
current = current.parent;
|
|
38
|
-
}
|
|
39
|
-
return { relative, absolute: resolve(this.base, relative) };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
class PathTreeify {
|
|
43
|
-
constructor({ filter, base }) {
|
|
44
|
-
if (typeof filter !== 'undefined') {
|
|
45
|
-
this.validateFilter(filter);
|
|
46
|
-
this.filter = filter;
|
|
47
|
-
}
|
|
48
|
-
if (!base || !PathValidator.isValid(base)) {
|
|
49
|
-
throw new Error(`${base} is not a valid path!`);
|
|
50
|
-
}
|
|
51
|
-
if (!PathValidator.isDirectory(base)) {
|
|
52
|
-
throw new Error(`${base} is not a dirPath!`);
|
|
53
|
-
}
|
|
54
|
-
this.base = base;
|
|
55
|
-
}
|
|
56
|
-
validateFilter(filter) {
|
|
57
|
-
if (typeof filter !== 'function') {
|
|
58
|
-
throw new TypeError('filter must be a function');
|
|
59
|
-
}
|
|
60
|
-
if (filter.length !== 1) {
|
|
61
|
-
throw new TypeError('filter must accept exactly one parameter');
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
const testResult = filter({ name: 'test', postPath: '/test' });
|
|
65
|
-
if (typeof testResult !== 'boolean') {
|
|
66
|
-
throw new TypeError('filter must return a boolean');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
throw new TypeError('filter function threw an error during test: ' + error);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
initNode(parent = null) {
|
|
74
|
-
const node = new PathTreeNode(this.base);
|
|
75
|
-
if (parent) {
|
|
76
|
-
node.parent = parent;
|
|
77
|
-
}
|
|
78
|
-
return node;
|
|
79
|
-
}
|
|
80
|
-
buildChildren(dirPath, parent) {
|
|
81
|
-
const names = readdirSync(dirPath);
|
|
82
|
-
const children = [];
|
|
83
|
-
for (const name of names) {
|
|
84
|
-
const subPath = join(dirPath, name);
|
|
85
|
-
if (!statSync(subPath).isDirectory()) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (this.filter && !this.filter({ dirPath, name })) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const node = this.initNode();
|
|
92
|
-
node.value = name;
|
|
93
|
-
node.parent = parent;
|
|
94
|
-
node.children = this.buildChildren(subPath, node);
|
|
95
|
-
children.push(node);
|
|
96
|
-
}
|
|
97
|
-
return children;
|
|
98
|
-
}
|
|
99
|
-
checkRelativePaths(relativeDirNames) {
|
|
100
|
-
if (!Array.isArray(relativeDirNames)) {
|
|
101
|
-
throw new Error(`Expected array, got ${typeof relativeDirNames}`);
|
|
102
|
-
}
|
|
103
|
-
for (let i = 0; i < relativeDirNames.length; i++) {
|
|
104
|
-
const it = relativeDirNames[i];
|
|
105
|
-
if (typeof it !== 'string') {
|
|
106
|
-
throw new Error(`Item at index ${i} is not a string, got ${typeof it}`);
|
|
107
|
-
}
|
|
108
|
-
const absPath = resolve(this.base, it);
|
|
109
|
-
if (!PathValidator.isValid(absPath)) {
|
|
110
|
-
throw new Error(`Path does not exist or is not accessible: ${absPath} (from relative path: ${it})`);
|
|
111
|
-
}
|
|
112
|
-
if (!PathValidator.isDirectory(absPath)) {
|
|
113
|
-
throw new Error(`Path is not a directory: ${absPath} (from relative path: ${it})`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
formatDirnames(dirNames) {
|
|
118
|
-
return dirNames.map(dir => {
|
|
119
|
-
// Remove leading and trailing slashes
|
|
120
|
-
return dir.replace(/^\/+|\/+$/g, '');
|
|
121
|
-
}).filter(dir => dir !== ''); // Optional: filter empty strings
|
|
122
|
-
}
|
|
123
|
-
getPathBy(node) {
|
|
124
|
-
let relative = '';
|
|
125
|
-
let current = node;
|
|
126
|
-
while (current.parent) {
|
|
127
|
-
relative = relative
|
|
128
|
-
? `${current.value}${sep}${relative}`
|
|
129
|
-
: current.value;
|
|
130
|
-
current = current.parent;
|
|
131
|
-
}
|
|
132
|
-
return { relative, absolute: resolve(this.base, relative) };
|
|
133
|
-
}
|
|
134
|
-
buildByDirNames(dirNames) {
|
|
135
|
-
const root = this.initNode();
|
|
136
|
-
this.checkRelativePaths(dirNames);
|
|
137
|
-
const dirNameArr = this.formatDirnames(dirNames);
|
|
138
|
-
for (const dirName of dirNameArr) {
|
|
139
|
-
const node = this.initNode();
|
|
140
|
-
node.value = dirName;
|
|
141
|
-
node.parent = root;
|
|
142
|
-
node.children = this.buildChildren(resolve(this.base, dirName), node);
|
|
143
|
-
root.children.push(node);
|
|
144
|
-
}
|
|
145
|
-
return root;
|
|
146
|
-
}
|
|
147
|
-
build() {
|
|
148
|
-
const dirNameArr = readdirSync(this.base).filter(name => {
|
|
149
|
-
const abs = resolve(this.base, name);
|
|
150
|
-
return PathValidator.isDirectory(abs);
|
|
151
|
-
});
|
|
152
|
-
return this.buildByDirNames(dirNameArr);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export { PathTreeify };
|
|
157
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import{readdirSync as t,statSync as r,accessSync as e,constants as i}from"fs";import{join as s,resolve as a,sep as n}from"path";class o{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}}}class l{constructor(t){this.parent=null,this.value="",this.children=[],this.base=t}getPath(){let t="",r=this;for(;r.parent;)t=t?`${r.value}${n}${t}`:r.value,r=r.parent;return{relative:t,absolute:a(this.base,t)}}}class h{constructor({filter:t,base:r}){if(void 0!==t&&(this.validateFilter(t),this.filter=t),!r||!o.isValid(r))throw new Error(`${r} is not a valid path!`);if(!o.isDirectory(r))throw new Error(`${r} is not a dirPath!`);this.base=r}validateFilter(t){if("function"!=typeof t)throw new TypeError("filter must be a function");if(1!==t.length)throw new TypeError("filter must accept exactly one parameter");try{if("boolean"!=typeof t({name:"test",postPath:"/test"}))throw new TypeError("filter must return a boolean")}catch(t){throw new TypeError("filter function threw an error during test: "+t)}}initNode(t=null){const r=new l(this.base);return t&&(r.parent=t),r}buildChildren(e,i){const a=t(e),n=[];for(const t of a){const a=s(e,t);if(!r(a).isDirectory())continue;if(this.filter&&!this.filter({dirPath:e,name:t}))continue;const o=this.initNode();o.value=t,o.parent=i,o.children=this.buildChildren(a,o),n.push(o)}return n}checkRelativePaths(t){if(!Array.isArray(t))throw new Error("Expected array, got "+typeof t);for(let r=0;r<t.length;r++){const e=t[r];if("string"!=typeof e)throw new Error(`Item at index ${r} is not a string, got ${typeof e}`);const i=a(this.base,e);if(!o.isValid(i))throw new Error(`Path does not exist or is not accessible: ${i} (from relative path: ${e})`);if(!o.isDirectory(i))throw new Error(`Path is not a directory: ${i} (from relative path: ${e})`)}}formatDirnames(t){return t.map(t=>t.replace(/^\/+|\/+$/g,"")).filter(t=>""!==t)}getAllDirNamesUnderBase(){return t(this.base).filter(t=>{const r=a(this.base,t);return!!o.isDirectory(r)&&!(this.filter&&!this.filter({name:t,dirPath:this.base}))})}buildByDirNames(t){const r=this.initNode(),e=this.formatDirnames(t);this.checkRelativePaths(e);for(const t of e){const e=this.initNode();e.value=t,e.parent=r,e.children=this.buildChildren(a(this.base,t),e),r.children.push(e)}return r}buildByFilter(t){const r=this.getAllDirNamesUnderBase();return this.buildByDirNames(r.filter(t))}getPathBy(t){let r="",e=t;for(;e.parent;)r=r?`${e.value}${n}${r}`:e.value,e=e.parent;return{relative:r,absolute:a(this.base,r)}}buildBy(t){if(Array.isArray(t))return this.buildByDirNames(t);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.getAllDirNamesUnderBase();return this.buildByDirNames(t)}}export{h as PathTreeify};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,36 +1,93 @@
|
|
|
1
|
+
/** A filter function that determines whether a directory should be included in the tree */
|
|
1
2
|
type FilterFunction = (params: {
|
|
2
3
|
name: string;
|
|
3
4
|
dirPath: string;
|
|
4
5
|
}) => boolean;
|
|
6
|
+
/** Constructor options for PathTreeify */
|
|
5
7
|
interface PathTreeifyProps {
|
|
6
8
|
base: string;
|
|
7
9
|
filter?: FilterFunction;
|
|
8
10
|
}
|
|
11
|
+
/** Represents a single node (directory) in the path tree */
|
|
9
12
|
declare class PathTreeNode {
|
|
13
|
+
/** The root base path used to resolve absolute paths */
|
|
10
14
|
private base;
|
|
15
|
+
/** Reference to the parent node; null for the root node */
|
|
11
16
|
parent: PathTreeNode | null;
|
|
17
|
+
/** The directory name of this node (not a full path) */
|
|
12
18
|
value: string;
|
|
19
|
+
/** Child nodes representing subdirectories */
|
|
13
20
|
children: PathTreeNode[];
|
|
14
21
|
constructor(base: string);
|
|
22
|
+
/**
|
|
23
|
+
* Walks up the parent chain to compute this node's relative and absolute paths.
|
|
24
|
+
* @returns An object containing the relative path from base and the absolute path
|
|
25
|
+
*/
|
|
15
26
|
getPath(): {
|
|
16
27
|
relative: string;
|
|
17
28
|
absolute: string;
|
|
18
29
|
};
|
|
19
30
|
}
|
|
31
|
+
/** Builds a tree of directory nodes rooted at a given base path */
|
|
20
32
|
export declare class PathTreeify {
|
|
33
|
+
/** The root directory to scan */
|
|
21
34
|
private base;
|
|
35
|
+
/** Optional filter applied to each directory during traversal */
|
|
22
36
|
private filter?;
|
|
23
37
|
constructor({ filter, base }: Partial<PathTreeifyProps>);
|
|
38
|
+
/**
|
|
39
|
+
* Validates that the provided filter is a function, accepts one parameter,
|
|
40
|
+
* and returns a boolean. Throws a TypeError if any condition is violated.
|
|
41
|
+
*/
|
|
24
42
|
private validateFilter;
|
|
43
|
+
/**
|
|
44
|
+
* Creates and optionally attaches a new PathTreeNode to a parent.
|
|
45
|
+
* @param parent - The parent node to attach to, or null for the root
|
|
46
|
+
*/
|
|
25
47
|
private initNode;
|
|
48
|
+
/**
|
|
49
|
+
* Recursively reads a directory and builds child nodes for each subdirectory.
|
|
50
|
+
* Applies the instance-level filter if one is set.
|
|
51
|
+
* @param dirPath - Absolute path of the directory to read
|
|
52
|
+
* @param parent - The parent node to attach children to
|
|
53
|
+
*/
|
|
26
54
|
private buildChildren;
|
|
55
|
+
/**
|
|
56
|
+
* Validates that each entry in the array is a string pointing to
|
|
57
|
+
* an accessible directory relative to the base path.
|
|
58
|
+
* @param relativeDirNames - Array of relative directory path strings to validate
|
|
59
|
+
*/
|
|
27
60
|
private checkRelativePaths;
|
|
61
|
+
/**
|
|
62
|
+
* Strips leading and trailing slashes from each directory name
|
|
63
|
+
* and removes any resulting empty strings.
|
|
64
|
+
*/
|
|
28
65
|
private formatDirnames;
|
|
66
|
+
/** Returns the names of all immediate subdirectories under the base path */
|
|
67
|
+
private getAllDirNamesUnderBase;
|
|
68
|
+
/**
|
|
69
|
+
* Builds a tree rooted at base, containing only the specified subdirectories.
|
|
70
|
+
* @param dirNames - Relative directory names to include as top-level nodes
|
|
71
|
+
*/
|
|
72
|
+
private buildByDirNames;
|
|
73
|
+
/**
|
|
74
|
+
* Builds a tree using only the subdirectories under base that pass the given filter.
|
|
75
|
+
* @param filter - A predicate applied to each top-level directory name
|
|
76
|
+
*/
|
|
77
|
+
private buildByFilter;
|
|
78
|
+
/**
|
|
79
|
+
* Computes the relative and absolute paths for a given node
|
|
80
|
+
* by walking up the parent chain.
|
|
81
|
+
*/
|
|
29
82
|
getPathBy(node: PathTreeNode): {
|
|
30
83
|
relative: string;
|
|
31
84
|
absolute: string;
|
|
32
85
|
};
|
|
33
|
-
|
|
86
|
+
/** Overload: build the tree from an explicit list of relative directory names */
|
|
87
|
+
buildBy(dirNames: string[]): PathTreeNode;
|
|
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 */
|
|
34
91
|
build(): PathTreeNode;
|
|
35
92
|
}
|
|
36
93
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "path-treeify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.67126e2",
|
|
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",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"dist/**/*.d.ts"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "
|
|
27
|
+
"test": "ava",
|
|
28
|
+
"test:watch": "ava --watch",
|
|
28
29
|
"clean": "rimraf ./dist",
|
|
29
30
|
"build": "npm run clean && rollup -c",
|
|
30
31
|
"build:dev": "NODE_ENV=development npm run build",
|
|
@@ -34,6 +35,14 @@
|
|
|
34
35
|
"publish:beta": "npm publish --tag beta --access public",
|
|
35
36
|
"publish:latest": "npm publish --tag latest --access public"
|
|
36
37
|
},
|
|
38
|
+
"ava": {
|
|
39
|
+
"files": [
|
|
40
|
+
"tests/**/*.mjs"
|
|
41
|
+
],
|
|
42
|
+
"nodeArguments": [
|
|
43
|
+
"--experimental-vm-modules"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
37
46
|
"repository": {
|
|
38
47
|
"type": "git",
|
|
39
48
|
"url": "git+https://github.com/isaaxite/path-treeify.git"
|
|
@@ -62,6 +71,7 @@
|
|
|
62
71
|
"@rollup/plugin-terser": "^1.0.0",
|
|
63
72
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
64
73
|
"@types/node": "^25.5.0",
|
|
74
|
+
"ava": "^7.0.0",
|
|
65
75
|
"nodemon": "^3.1.14",
|
|
66
76
|
"rimraf": "^6.1.3",
|
|
67
77
|
"rollup": "^4.59.0",
|