path-treeify 1.0.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/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/index.cjs +1 -0
- package/dist/index.mjs +1 -0
- package/dist/types/index.d.ts +29 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 isaaxite
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# path-treeify
|
|
2
|
+
|
|
3
|
+
> π [δΈζζζ‘£ (Chinese README)](https://github.com/isaaxite/path-treeify/blob/main/docs/README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
<div align="left">
|
|
6
|
+
<!-- <h1>Path-Treeify</h1> -->
|
|
7
|
+
<p>Convert a path or an array of paths into a tree-structured JavaScript object, where each node holds a circular reference to its parent. </p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="left">
|
|
11
|
+
<a href="https://github.com/isaaxite/path-treeify">
|
|
12
|
+
<img alt="GitHub package.json dynamic" src="https://img.shields.io/github/package-json/version/isaaxite/path-treeify?logo=github">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://nodejs.org">
|
|
15
|
+
<img alt="node" src="https://img.shields.io/node/v/path-treeify">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://github.com/isaaxite/path-treeify/blob/main/LICENSE">
|
|
18
|
+
<img alt="license" src="https://img.shields.io/npm/l/path-treeify">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/isaaxite/path-treeify">
|
|
21
|
+
<img alt="GitHub Created At" src="https://img.shields.io/github/created-at/isaaxite/path-treeify">
|
|
22
|
+
</a>
|
|
23
|
+
<a href="https://github.com/isaaxite/path-treeify">
|
|
24
|
+
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/isaaxite/path-treeify">
|
|
25
|
+
</a>
|
|
26
|
+
<a href="https://github.com/isaaxite/path-treeify/commits/main/">
|
|
27
|
+
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/isaaxite/path-treeify">
|
|
28
|
+
</a>
|
|
29
|
+
<a href="https://github.com/isaaxite/path-treeify/commits/main/">
|
|
30
|
+
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/isaaxite/path-treeify">
|
|
31
|
+
</a>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- π² Builds a recursive tree from one or more directory paths
|
|
39
|
+
- π Each node carries a `parent` circular reference for upward traversal
|
|
40
|
+
- π Optional `filter` callback to include/exclude directories during scanning
|
|
41
|
+
- π¦ Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
42
|
+
- π« Zero runtime dependencies
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- Node.js `>= 18.0.0`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install path-treeify
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn add path-treeify
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { PathTreeify } from 'path-treeify';
|
|
68
|
+
|
|
69
|
+
const treeify = new PathTreeify({ base: '/your/project/root' });
|
|
70
|
+
|
|
71
|
+
// Scan the whole base directory
|
|
72
|
+
const tree = treeify.buildByDirPaths(['src', 'tests']);
|
|
73
|
+
|
|
74
|
+
console.log(tree);
|
|
75
|
+
// {
|
|
76
|
+
// parent: null,
|
|
77
|
+
// value: '',
|
|
78
|
+
// children: [
|
|
79
|
+
// { parent: [Circular], value: 'src', children: [...] },
|
|
80
|
+
// { parent: [Circular], value: 'tests', children: [...] }
|
|
81
|
+
// ]
|
|
82
|
+
// }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
### `new PathTreeify(options)`
|
|
90
|
+
|
|
91
|
+
Creates a new instance.
|
|
92
|
+
|
|
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 |
|
|
97
|
+
|
|
98
|
+
`base` must exist and be a directory, otherwise the constructor throws.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### `FilterFunction`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
type FilterFunction = (params: {
|
|
106
|
+
name: string; // directory name (leaf segment)
|
|
107
|
+
dirPath: string; // absolute path of the parent directory
|
|
108
|
+
}) => boolean;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Return `true` to **include** the directory and recurse into it; `false` to **skip** it.
|
|
112
|
+
|
|
113
|
+
**Example β skip hidden directories and `node_modules`:**
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const treeify = new PathTreeify({
|
|
117
|
+
base: '/your/project',
|
|
118
|
+
filter: ({ name }) => !name.startsWith('.') && name !== 'node_modules',
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `buildByDirPaths(paths: string[]): PathTreeNode`
|
|
125
|
+
|
|
126
|
+
Scans the given relative directory paths (resolved against `base`) and returns a synthetic root `PathTreeNode` whose `children` are the top-level nodes you requested.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const root = treeify.buildByDirPaths(['src', 'docs']);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- Each element of `paths` must be a valid, accessible directory relative to `base`.
|
|
133
|
+
- Leading and trailing slashes are stripped automatically.
|
|
134
|
+
- Throws if any path does not exist or is not a directory.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `getPathBy(node: PathTreeNode): { relative: string; absolute: string }`
|
|
139
|
+
|
|
140
|
+
Walks a node's `parent` chain to reconstruct its full path.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const srcNode = root.children[0];
|
|
144
|
+
const { relative, absolute } = treeify.getPathBy(srcNode);
|
|
145
|
+
// relative β 'src'
|
|
146
|
+
// absolute β '/your/project/src'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `PathTreeNode`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
interface PathTreeNode {
|
|
155
|
+
parent: PathTreeNode | null; // null only on the synthetic root
|
|
156
|
+
value: string; // directory name for this node
|
|
157
|
+
children: PathTreeNode[];
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> β οΈ **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
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Examples
|
|
166
|
+
|
|
167
|
+
### Scan an entire directory
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { PathTreeify } from 'path-treeify';
|
|
171
|
+
import { readdirSync } from 'fs';
|
|
172
|
+
|
|
173
|
+
const base = '/your/project';
|
|
174
|
+
const treeify = new PathTreeify({ base });
|
|
175
|
+
|
|
176
|
+
// Collect all top-level directories
|
|
177
|
+
const topLevel = readdirSync(base, { withFileTypes: true })
|
|
178
|
+
.filter(d => d.isDirectory())
|
|
179
|
+
.map(d => d.name);
|
|
180
|
+
|
|
181
|
+
const tree = treeify.buildByDirPaths(topLevel);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Retrieve absolute paths while walking
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
function printPaths(node, treeify) {
|
|
188
|
+
for (const child of node.children) {
|
|
189
|
+
const { absolute } = treeify.getPathBy(child);
|
|
190
|
+
console.log(absolute);
|
|
191
|
+
printPaths(child, treeify);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
printPaths(tree, treeify);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### CommonJS usage
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
const { PathTreeify } = require('path-treeify');
|
|
202
|
+
|
|
203
|
+
const treeify = new PathTreeify({ base: __dirname });
|
|
204
|
+
const tree = treeify.buildByDirPaths(['src']);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
[MIT](https://github.com/isaaxite/path-treeify/blob/main//LICENSE) Β© [isaaxite](https://github.com/isaaxite)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +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{parent:t,value:"",children:[]}}buildChildren(e,i){const o=t.readdirSync(e),s=[];for(const n of o){const o=r.join(e,n);if(!t.statSync(o).isDirectory())continue;if(this.filter&&!this.filter({dirPath:e,name:n}))continue;const a=this.initNode();a.value=n,a.parent=i,a.children=this.buildChildren(o,a),s.push(a)}return s}checkRelatePaths(t){if(!Array.isArray(t))throw new Error("Expected array, got "+typeof t);for(let i=0;i<t.length;i++){const o=t[i];if("string"!=typeof o)throw new Error(`Item at index ${i} is not a string, got ${typeof o}`);const s=r.resolve(this.base,o);if(!e.isValid(s))throw new Error(`Path does not exist or is not accessible: ${s} (from relative path: ${o})`);if(!e.isDirectory(s))throw new Error(`Path is not a directory: ${s} (from relative path: ${o})`)}}formatDirnames(t){return t.map(t=>t.replace(/^\/+|\/+$/g,"")).filter(t=>""!==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)}}buildByDirPaths(t){const e=this.initNode();this.checkRelatePaths(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}};
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdirSync as t,statSync as r,accessSync as e,constants as i}from"fs";import{join as o,resolve as n,sep as a}from"path";class s{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 h{constructor({filter:t,base:r}){if(void 0!==t&&(this.validateFilter(t),this.filter=t),!r||!s.isValid(r))throw new Error(`${r} is not a valid path!`);if(!s.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{parent:t,value:"",children:[]}}buildChildren(e,i){const n=t(e),a=[];for(const t of n){const n=o(e,t);if(!r(n).isDirectory())continue;if(this.filter&&!this.filter({dirPath:e,name:t}))continue;const s=this.initNode();s.value=t,s.parent=i,s.children=this.buildChildren(n,s),a.push(s)}return a}checkRelatePaths(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=n(this.base,e);if(!s.isValid(i))throw new Error(`Path does not exist or is not accessible: ${i} (from relative path: ${e})`);if(!s.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)}getPathBy(t){let r="",e=t;for(;e.parent;)r=r?`${e.value}${a}${r}`:e.value,e=e.parent;return{relative:r,absolute:n(this.base,r)}}buildByDirPaths(t){const r=this.initNode();this.checkRelatePaths(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(n(this.base,t),e),r.children.push(e)}return r}}export{h as PathTreeify};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type FilterFunction = (params: {
|
|
2
|
+
name: string;
|
|
3
|
+
dirPath: string;
|
|
4
|
+
}) => boolean;
|
|
5
|
+
interface PathTreeifyProps {
|
|
6
|
+
base: string;
|
|
7
|
+
filter?: FilterFunction;
|
|
8
|
+
}
|
|
9
|
+
interface PathTreeNode {
|
|
10
|
+
parent: PathTreeNode | null;
|
|
11
|
+
value: string;
|
|
12
|
+
children: PathTreeNode[];
|
|
13
|
+
}
|
|
14
|
+
export declare class PathTreeify {
|
|
15
|
+
private base;
|
|
16
|
+
private filter?;
|
|
17
|
+
constructor({ filter, base }: Partial<PathTreeifyProps>);
|
|
18
|
+
private validateFilter;
|
|
19
|
+
private initNode;
|
|
20
|
+
private buildChildren;
|
|
21
|
+
private checkRelatePaths;
|
|
22
|
+
private formatDirnames;
|
|
23
|
+
getPathBy(node: PathTreeNode): {
|
|
24
|
+
relative: string;
|
|
25
|
+
absolute: string;
|
|
26
|
+
};
|
|
27
|
+
buildByDirPaths(nameOfDirs: string[]): PathTreeNode;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "path-treeify",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"default": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"default": "./dist/index.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/index.cjs",
|
|
23
|
+
"dist/index.mjs",
|
|
24
|
+
"dist/**/*.d.ts",
|
|
25
|
+
"README.md",
|
|
26
|
+
"!README.zh-CN.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
31
|
+
"clean": "rimraf ./dist",
|
|
32
|
+
"build": "npm run clean && rollup -c",
|
|
33
|
+
"build:dev": "NODE_ENV=development npm run build",
|
|
34
|
+
"build:prod": "NODE_ENV=production npm run build",
|
|
35
|
+
"build:watch": "nodemon --watch src -e ts --exec \"npm run build:dev\"",
|
|
36
|
+
"check-publish": "npm pack --dry-run",
|
|
37
|
+
"publish": "npm publish"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/isaaxite/path-treeify.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/isaaxite/path-treeify/issues"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"path",
|
|
48
|
+
"tree",
|
|
49
|
+
"treeify",
|
|
50
|
+
"hierarchy",
|
|
51
|
+
"filesystem",
|
|
52
|
+
"object",
|
|
53
|
+
"circular",
|
|
54
|
+
"reference"
|
|
55
|
+
],
|
|
56
|
+
"author": {
|
|
57
|
+
"name": "isaaxite",
|
|
58
|
+
"url": "https://github.com/isaaxite"
|
|
59
|
+
},
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"homepage": "https://github.com/isaaxite/path-treeify#readme",
|
|
62
|
+
"sideEffects": false,
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
65
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
66
|
+
"@types/node": "^25.5.0",
|
|
67
|
+
"nodemon": "^3.1.14",
|
|
68
|
+
"rimraf": "^6.1.3",
|
|
69
|
+
"rollup": "^4.59.0",
|
|
70
|
+
"tslib": "^2.8.1",
|
|
71
|
+
"typescript": "^5.9.3"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=18.0.0"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public",
|
|
78
|
+
"provenance": false
|
|
79
|
+
}
|
|
80
|
+
}
|