path-treeify 1.1.0 → 1.2.0-beta.db6fe0b
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 +48 -24
- 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">
|
|
@@ -39,7 +39,8 @@
|
|
|
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
41
|
- 🔍 Optional `filter` callback to include/exclude directories during scanning
|
|
42
|
-
- ⚡ `build()`
|
|
42
|
+
- ⚡ `build()` scans the entire `base` directory with zero configuration
|
|
43
|
+
- 🎛️ `buildBy()` accepts either a directory name array or a 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 filter function over all top-level directories
|
|
78
|
+
const filtered = treeify.buildBy(name => !name.startsWith('.') && name !== 'node_modules');
|
|
75
79
|
|
|
76
80
|
// Or scan everything under base at once
|
|
77
81
|
const fullTree = treeify.build();
|
|
@@ -88,7 +92,7 @@ Creates a new instance.
|
|
|
88
92
|
| Option | Type | Required | Description |
|
|
89
93
|
|----------|-------------------------------|----------|----------------------------------------------------|
|
|
90
94
|
| `base` | `string` | ✅ | Absolute path to the root directory to scan from |
|
|
91
|
-
| `filter` | `FilterFunction` (see below) | ❌ | Called for every directory found during traversal
|
|
95
|
+
| `filter` | `FilterFunction` (see below) | ❌ | Called for every directory found during deep traversal |
|
|
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 recursively during deep traversal of the tree.
|
|
104
|
+
|
|
99
105
|
```ts
|
|
100
106
|
type FilterFunction = (params: {
|
|
101
107
|
name: string; // directory name (leaf segment)
|
|
@@ -105,7 +111,7 @@ type FilterFunction = (params: {
|
|
|
105
111
|
|
|
106
112
|
Return `true` to **include** the directory and recurse into it; `false` to **skip** it.
|
|
107
113
|
|
|
108
|
-
**Example — skip hidden directories and `node_modules
|
|
114
|
+
**Example — skip hidden directories and `node_modules` at every level:**
|
|
109
115
|
|
|
110
116
|
```ts
|
|
111
117
|
const treeify = new PathTreeify({
|
|
@@ -118,7 +124,7 @@ const treeify = new PathTreeify({
|
|
|
118
124
|
|
|
119
125
|
### `build(): PathTreeNode`
|
|
120
126
|
|
|
121
|
-
Scans **all** subdirectories directly under `base` and returns a synthetic root `PathTreeNode`. This is the zero-configuration
|
|
127
|
+
Scans **all** subdirectories directly under `base` and returns a synthetic root `PathTreeNode`. This is the zero-configuration shorthand.
|
|
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 filter function, then builds a tree from the matching names.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> Note: this `filter` operates only on the **top-level** directory names under `base`. For filtering at every depth, pass a `filter` to the constructor instead.
|
|
140
155
|
|
|
141
156
|
---
|
|
142
157
|
|
|
@@ -145,17 +160,14 @@ 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
|
-
const { relative, absolute } = treeify.getPathBy(srcNode);
|
|
150
|
-
// relative → 'src'
|
|
151
|
-
// absolute → '/your/project/src'
|
|
163
|
+
const { relative, absolute } = treeify.getPathBy(node);
|
|
152
164
|
```
|
|
153
165
|
|
|
154
166
|
---
|
|
155
167
|
|
|
156
168
|
### `PathTreeNode`
|
|
157
169
|
|
|
158
|
-
`PathTreeNode` is
|
|
170
|
+
`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
171
|
|
|
160
172
|
```ts
|
|
161
173
|
class PathTreeNode {
|
|
@@ -180,18 +192,30 @@ class PathTreeNode {
|
|
|
180
192
|
```ts
|
|
181
193
|
import { PathTreeify } from 'path-treeify';
|
|
182
194
|
|
|
183
|
-
const treeify = new PathTreeify({
|
|
184
|
-
base: '/your/project',
|
|
185
|
-
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
186
|
-
});
|
|
187
|
-
|
|
195
|
+
const treeify = new PathTreeify({ base: '/your/project' });
|
|
188
196
|
const tree = treeify.build();
|
|
189
197
|
```
|
|
190
198
|
|
|
191
199
|
### Scan specific directories
|
|
192
200
|
|
|
193
201
|
```ts
|
|
194
|
-
const tree = treeify.
|
|
202
|
+
const tree = treeify.buildBy(['src', 'tests', 'docs']);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Filter top-level directories
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Filter at every depth via constructor
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const treeify = new PathTreeify({
|
|
215
|
+
base: '/your/project',
|
|
216
|
+
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
217
|
+
});
|
|
218
|
+
const tree = treeify.build();
|
|
195
219
|
```
|
|
196
220
|
|
|
197
221
|
### Retrieve paths via `node.getPath()`
|
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();this.checkRelativePaths(t);const i=this.formatDirnames(t);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();this.checkRelativePaths(t);const e=this.formatDirnames(t);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.db6fe0b",
|
|
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",
|