path-treeify 1.0.0 → 1.1.0-beta.94a5d17
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 +83 -46
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/types/index.d.ts +14 -3
- package/package.json +5 -7
package/README.md
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
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">
|
|
16
16
|
</a>
|
|
17
17
|
<a href="https://github.com/isaaxite/path-treeify/blob/main/LICENSE">
|
|
18
|
-
<img alt="
|
|
18
|
+
<img alt="GitHub License" src="https://img.shields.io/github/license/isaaxite/path-treeify">
|
|
19
19
|
</a>
|
|
20
20
|
<a href="https://github.com/isaaxite/path-treeify">
|
|
21
21
|
<img alt="GitHub Created At" src="https://img.shields.io/github/created-at/isaaxite/path-treeify">
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
|
|
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
|
+
- 📍 Each node exposes a `getPath()` method to retrieve its own paths directly
|
|
40
41
|
- 🔍 Optional `filter` callback to include/exclude directories during scanning
|
|
42
|
+
- ⚡ `build()` scans the entire `base` directory with zero configuration
|
|
43
|
+
- 🎛️ `buildBy()` accepts either a directory name array or a filter function
|
|
41
44
|
- 📦 Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
42
45
|
- 🚫 Zero runtime dependencies
|
|
43
46
|
|
|
@@ -68,18 +71,14 @@ import { PathTreeify } from 'path-treeify';
|
|
|
68
71
|
|
|
69
72
|
const treeify = new PathTreeify({ base: '/your/project/root' });
|
|
70
73
|
|
|
71
|
-
// Scan
|
|
72
|
-
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');
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// parent: null,
|
|
77
|
-
// value: '',
|
|
78
|
-
// children: [
|
|
79
|
-
// { parent: [Circular], value: 'src', children: [...] },
|
|
80
|
-
// { parent: [Circular], value: 'tests', children: [...] }
|
|
81
|
-
// ]
|
|
82
|
-
// }
|
|
80
|
+
// Or scan everything under base at once
|
|
81
|
+
const fullTree = treeify.build();
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
---
|
|
@@ -90,10 +89,10 @@ console.log(tree);
|
|
|
90
89
|
|
|
91
90
|
Creates a new instance.
|
|
92
91
|
|
|
93
|
-
| Option | Type | Required | Description
|
|
94
|
-
|
|
95
|
-
| `base` | `string` | ✅ | Absolute path to the root directory to scan from
|
|
96
|
-
| `filter` | `FilterFunction` (see below) | ❌ | Called for every directory found during traversal |
|
|
92
|
+
| Option | Type | Required | Description |
|
|
93
|
+
|----------|-------------------------------|----------|----------------------------------------------------|
|
|
94
|
+
| `base` | `string` | ✅ | Absolute path to the root directory to scan from |
|
|
95
|
+
| `filter` | `FilterFunction` (see below) | ❌ | Called for every directory found during deep traversal |
|
|
97
96
|
|
|
98
97
|
`base` must exist and be a directory, otherwise the constructor throws.
|
|
99
98
|
|
|
@@ -101,6 +100,8 @@ Creates a new instance.
|
|
|
101
100
|
|
|
102
101
|
### `FilterFunction`
|
|
103
102
|
|
|
103
|
+
Used as the `filter` option in the constructor. Applied recursively during deep traversal of the tree.
|
|
104
|
+
|
|
104
105
|
```ts
|
|
105
106
|
type FilterFunction = (params: {
|
|
106
107
|
name: string; // directory name (leaf segment)
|
|
@@ -110,7 +111,7 @@ type FilterFunction = (params: {
|
|
|
110
111
|
|
|
111
112
|
Return `true` to **include** the directory and recurse into it; `false` to **skip** it.
|
|
112
113
|
|
|
113
|
-
**Example — skip hidden directories and `node_modules
|
|
114
|
+
**Example — skip hidden directories and `node_modules` at every level:**
|
|
114
115
|
|
|
115
116
|
```ts
|
|
116
117
|
const treeify = new PathTreeify({
|
|
@@ -121,78 +122,114 @@ const treeify = new PathTreeify({
|
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
|
124
|
-
### `
|
|
125
|
+
### `build(): PathTreeNode`
|
|
126
|
+
|
|
127
|
+
Scans **all** subdirectories directly under `base` and returns a synthetic root `PathTreeNode`. This is the zero-configuration shorthand.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const tree = treeify.build();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### `buildBy(dirNames: string[]): PathTreeNode`
|
|
125
136
|
|
|
126
|
-
|
|
137
|
+
Builds a tree from the given list of directory names (relative to `base`).
|
|
127
138
|
|
|
128
139
|
```ts
|
|
129
|
-
const
|
|
140
|
+
const tree = treeify.buildBy(['src', 'docs', 'tests']);
|
|
130
141
|
```
|
|
131
142
|
|
|
132
|
-
- Each element of `paths` must be a valid, accessible directory relative to `base`.
|
|
133
143
|
- Leading and trailing slashes are stripped automatically.
|
|
134
|
-
- 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.
|
|
135
155
|
|
|
136
156
|
---
|
|
137
157
|
|
|
138
158
|
### `getPathBy(node: PathTreeNode): { relative: string; absolute: string }`
|
|
139
159
|
|
|
140
|
-
Walks a node's `parent` chain to reconstruct its full path.
|
|
160
|
+
Walks a node's `parent` chain to reconstruct its full path. Equivalent to calling `node.getPath()` directly.
|
|
141
161
|
|
|
142
162
|
```ts
|
|
143
|
-
const
|
|
144
|
-
const { relative, absolute } = treeify.getPathBy(srcNode);
|
|
145
|
-
// relative → 'src'
|
|
146
|
-
// absolute → '/your/project/src'
|
|
163
|
+
const { relative, absolute } = treeify.getPathBy(node);
|
|
147
164
|
```
|
|
148
165
|
|
|
149
166
|
---
|
|
150
167
|
|
|
151
168
|
### `PathTreeNode`
|
|
152
169
|
|
|
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.
|
|
171
|
+
|
|
153
172
|
```ts
|
|
154
|
-
|
|
173
|
+
class PathTreeNode {
|
|
155
174
|
parent: PathTreeNode | null; // null only on the synthetic root
|
|
156
175
|
value: string; // directory name for this node
|
|
157
176
|
children: PathTreeNode[];
|
|
177
|
+
|
|
178
|
+
getPath(): { relative: string; absolute: string };
|
|
158
179
|
}
|
|
159
180
|
```
|
|
160
181
|
|
|
182
|
+
**`node.getPath()`** returns the same result as `treeify.getPathBy(node)` — both are available for convenience.
|
|
183
|
+
|
|
161
184
|
> ⚠️ **Circular references** — `parent` points back up the tree. Use `JSON.stringify` replacers or a library like `flatted` if you need to serialize the result.
|
|
162
185
|
|
|
163
186
|
---
|
|
164
187
|
|
|
165
188
|
## Examples
|
|
166
189
|
|
|
167
|
-
### Scan an entire directory
|
|
190
|
+
### Scan an entire base directory
|
|
168
191
|
|
|
169
192
|
```ts
|
|
170
193
|
import { PathTreeify } from 'path-treeify';
|
|
171
|
-
import { readdirSync } from 'fs';
|
|
172
194
|
|
|
173
|
-
const
|
|
174
|
-
const
|
|
195
|
+
const treeify = new PathTreeify({ base: '/your/project' });
|
|
196
|
+
const tree = treeify.build();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Scan specific directories
|
|
200
|
+
|
|
201
|
+
```ts
|
|
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
|
+
```
|
|
175
210
|
|
|
176
|
-
|
|
177
|
-
const topLevel = readdirSync(base, { withFileTypes: true })
|
|
178
|
-
.filter(d => d.isDirectory())
|
|
179
|
-
.map(d => d.name);
|
|
211
|
+
### Filter at every depth via constructor
|
|
180
212
|
|
|
181
|
-
|
|
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();
|
|
182
219
|
```
|
|
183
220
|
|
|
184
|
-
### Retrieve
|
|
221
|
+
### Retrieve paths via `node.getPath()`
|
|
185
222
|
|
|
186
223
|
```ts
|
|
187
|
-
function printPaths(node
|
|
224
|
+
function printPaths(node) {
|
|
188
225
|
for (const child of node.children) {
|
|
189
|
-
const { absolute } =
|
|
226
|
+
const { absolute } = child.getPath();
|
|
190
227
|
console.log(absolute);
|
|
191
|
-
printPaths(child
|
|
228
|
+
printPaths(child);
|
|
192
229
|
}
|
|
193
230
|
}
|
|
194
231
|
|
|
195
|
-
printPaths(tree
|
|
232
|
+
printPaths(tree);
|
|
196
233
|
```
|
|
197
234
|
|
|
198
235
|
### CommonJS usage
|
|
@@ -201,11 +238,11 @@ printPaths(tree, treeify);
|
|
|
201
238
|
const { PathTreeify } = require('path-treeify');
|
|
202
239
|
|
|
203
240
|
const treeify = new PathTreeify({ base: __dirname });
|
|
204
|
-
const tree = treeify.
|
|
241
|
+
const tree = treeify.build();
|
|
205
242
|
```
|
|
206
243
|
|
|
207
244
|
---
|
|
208
245
|
|
|
209
246
|
## License
|
|
210
247
|
|
|
211
|
-
[MIT](https://github.com/isaaxite/path-treeify/blob/main
|
|
248
|
+
[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"),r=require("path");class e{static isValid(r){try{return t.accessSync(r,t.constants.F_OK),!0}catch{return!1}}static isDirectory(r){try{return t.statSync(r).isDirectory()}catch{return!1}}}exports.PathTreeify=class{constructor({filter:t,base:r}){if(void 0!==t&&(this.validateFilter(t),this.filter=t),!r||!e.isValid(r))throw new Error(`${r} is not a valid path!`);if(!e.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){return
|
|
1
|
+
"use strict";var t=require("fs"),r=require("path");class e{static isValid(r){try{return t.accessSync(r,t.constants.F_OK),!0}catch{return!1}}static isDirectory(r){try{return t.statSync(r).isDirectory()}catch{return!1}}}class i{constructor(t){this.parent=null,this.value="",this.children=[],this.base=t}getPath(){let t="",e=this;for(;e.parent;)t=t?`${e.value}${r.sep}${t}`:e.value,e=e.parent;return{relative:t,absolute:r.resolve(this.base,t)}}}exports.PathTreeify=class{constructor({filter:t,base:r}){if(void 0!==t&&(this.validateFilter(t),this.filter=t),!r||!e.isValid(r))throw new Error(`${r} is not a valid path!`);if(!e.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 i(this.base);return t&&(r.parent=t),r}buildChildren(e,i){const s=t.readdirSync(e),a=[];for(const n of s){const s=r.join(e,n);if(!t.statSync(s).isDirectory())continue;if(this.filter&&!this.filter({dirPath:e,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=r.resolve(this.base,s);if(!e.isValid(a))throw new Error(`Path does not exist or is not accessible: ${a} (from relative path: ${s})`);if(!e.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=r.resolve(this.base,t);return e.isDirectory(i)})}buildByDirNames(t){const e=this.initNode();this.checkRelativePaths(t);const i=this.formatDirnames(t);for(const t of i){const i=this.initNode();i.value=t,i.parent=e,i.children=this.buildChildren(r.resolve(this.base,t),i),e.children.push(i)}return e}buildByFilter(t){const r=this.getAllDirNamesUnderBase();return this.buildByDirNames(r.filter(t))}getPathBy(t){let e="",i=t;for(;i.parent;)e=e?`${i.value}${r.sep}${e}`:i.value,i=i.parent;return{relative:e,absolute:r.resolve(this.base,e)}}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 +1 @@
|
|
|
1
|
-
import{readdirSync as t,statSync as r,accessSync as e,constants as i}from"fs";import{join as
|
|
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)})}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
|
@@ -6,10 +6,16 @@ interface PathTreeifyProps {
|
|
|
6
6
|
base: string;
|
|
7
7
|
filter?: FilterFunction;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
declare class PathTreeNode {
|
|
10
|
+
private base;
|
|
10
11
|
parent: PathTreeNode | null;
|
|
11
12
|
value: string;
|
|
12
13
|
children: PathTreeNode[];
|
|
14
|
+
constructor(base: string);
|
|
15
|
+
getPath(): {
|
|
16
|
+
relative: string;
|
|
17
|
+
absolute: string;
|
|
18
|
+
};
|
|
13
19
|
}
|
|
14
20
|
export declare class PathTreeify {
|
|
15
21
|
private base;
|
|
@@ -18,12 +24,17 @@ export declare class PathTreeify {
|
|
|
18
24
|
private validateFilter;
|
|
19
25
|
private initNode;
|
|
20
26
|
private buildChildren;
|
|
21
|
-
private
|
|
27
|
+
private checkRelativePaths;
|
|
22
28
|
private formatDirnames;
|
|
29
|
+
private getAllDirNamesUnderBase;
|
|
30
|
+
private buildByDirNames;
|
|
31
|
+
private buildByFilter;
|
|
23
32
|
getPathBy(node: PathTreeNode): {
|
|
24
33
|
relative: string;
|
|
25
34
|
absolute: string;
|
|
26
35
|
};
|
|
27
|
-
|
|
36
|
+
buildBy(dirNames: string[]): PathTreeNode;
|
|
37
|
+
buildBy(filter: (dirName: string) => boolean): PathTreeNode;
|
|
38
|
+
build(): PathTreeNode;
|
|
28
39
|
}
|
|
29
40
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "path-treeify",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-beta.94a5d17",
|
|
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",
|
|
@@ -21,10 +21,7 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist/index.cjs",
|
|
23
23
|
"dist/index.mjs",
|
|
24
|
-
"dist/**/*.d.ts"
|
|
25
|
-
"README.md",
|
|
26
|
-
"!README.zh-CN.md",
|
|
27
|
-
"LICENSE"
|
|
24
|
+
"dist/**/*.d.ts"
|
|
28
25
|
],
|
|
29
26
|
"scripts": {
|
|
30
27
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -32,9 +29,10 @@
|
|
|
32
29
|
"build": "npm run clean && rollup -c",
|
|
33
30
|
"build:dev": "NODE_ENV=development npm run build",
|
|
34
31
|
"build:prod": "NODE_ENV=production npm run build",
|
|
35
|
-
"build:watch": "nodemon --watch
|
|
32
|
+
"build:watch": "nodemon --watch index.ts -e ts --exec \"npm run build:dev\"",
|
|
36
33
|
"check-publish": "npm pack --dry-run",
|
|
37
|
-
"publish": "npm publish"
|
|
34
|
+
"publish:beta": "npm publish --tag beta --access public",
|
|
35
|
+
"publish:latest": "npm publish --tag latest --access public"
|
|
38
36
|
},
|
|
39
37
|
"repository": {
|
|
40
38
|
"type": "git",
|