path-treeify 1.3.0-beta.56a60fd β†’ 1.3.0-beta.9bb4032

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 CHANGED
@@ -1,7 +1,4 @@
1
- <div align="center">
2
- <img alt="LOGO" width="320" src="https://assets-amu.pages.dev/path-treeify/logo.png">
3
- </div>
4
- <br/>
1
+ # path-treeify
5
2
 
6
3
  > πŸ“– [δΈ­ζ–‡ζ–‡ζ‘£ (Chinese README)](https://github.com/isaaxite/path-treeify/blob/main/docs/README.zh-CN.md)
7
4
 
@@ -17,9 +14,6 @@
17
14
  <a href="https://nodejs.org">
18
15
  <img alt="node" src="https://img.shields.io/node/v/path-treeify">
19
16
  </a>
20
- <a href="https://github.com/isaaxite/path-treeify/blob/main/CHANGELOG.md">
21
- <img alt="CHANGELOG" src="https://img.shields.io/badge/changelog-maintained-brightgreen">
22
- </a>
23
17
  <a href="https://github.com/isaaxite/path-treeify/blob/main/LICENSE">
24
18
  <img alt="GitHub License" src="https://img.shields.io/github/license/isaaxite/path-treeify">
25
19
  </a>
@@ -259,4 +253,4 @@ const tree = treeify.build();
259
253
 
260
254
  ## License
261
255
 
262
- [MIT](https://github.com/isaaxite/path-treeify/blob/main/LICENSE) Β© [isaaxite](https://github.com/isaaxite)
256
+ [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 e,t=require("fs"),r=require("path");class i{static isValid(e){try{return t.accessSync(e,t.constants.F_OK),!0}catch{return!1}}static isDirectory(e){try{return t.statSync(e).isDirectory()}catch{return!1}}static isFile(e){try{return t.statSync(e).isFile()}catch{return!1}}}exports.PathTreeNodeType=void 0,(e=exports.PathTreeNodeType||(exports.PathTreeNodeType={})).Dir="dir",e.File="file",e.Unknown="unknown";class s{constructor(){this.parent=null,this.value="",this.children=[],this.type=exports.PathTreeNodeType.Unknown}getPath(){let e="",t=this;for(;t.parent;)e=e?`${t.value}${r.sep}${e}`:t.value,t=t.parent;return e}}exports.PathTreeNode=s,exports.PathTreeify=class{applyFilter(e,t){return!(!this.fileVisible&&!i.isDirectory(e))&&(!this.userFilter||this.userFilter({name:t,dirPath:r.dirname(e)}))}constructor({filter:e,base:t,fileVisible:r}){if(this.fileVisible=!1,"boolean"==typeof r&&r&&(this.fileVisible=r),void 0!==e&&(this.validateFilter(e),this.userFilter=e),!t||!i.isValid(t))throw new Error(`${t} is not a valid path!`);if(!i.isDirectory(t))throw new Error(`${t} is not a dirPath!`);this.base=t}validateFilter(e){if("function"!=typeof e)throw new TypeError("filter must be a function")}initNode(){return new s}buildChildren(e,s){const n=[],o=t.readdirSync(e);for(const t of o){const o=r.join(e,t);if(!this.applyFilter(o,t))continue;const l=this.initNode();l.value=t,l.parent=s,n.push(l),this.fileVisible&&i.isFile(o)?l.type=exports.PathTreeNodeType.File:(l.type=exports.PathTreeNodeType.Dir,l.children=this.buildChildren(o,l))}return n}checkRelativePaths(e){if(!Array.isArray(e))throw new Error("Expected array, got "+typeof e);for(let t=0;t<e.length;t++){const s=e[t];if("string"!=typeof s)throw new Error(`Item at index ${t} is not a string, got ${typeof s}`);const n=r.resolve(this.base,s);if(!i.isValid(n))throw new Error(`Path does not exist or is not accessible: ${n} (from relative path: ${s})`);if(!(i.isDirectory(n)||this.fileVisible&&i.isFile(n)))throw new Error(`Path is not a directory: ${n} (from relative path: ${s})`)}}formatSegments(e){return e.map(e=>e.split(/[/\\]/).filter(Boolean).join(r.sep)).filter(Boolean)}getAllEntriesUnderBase(){return t.readdirSync(this.base).filter(e=>{const t=r.resolve(this.base,e);return this.applyFilter(t,e)})}buildBySegments(e){const t=this.initNode(),s=this.formatSegments(e);this.checkRelativePaths(s);for(const e of s){const s=r.resolve(this.base,e),n=this.initNode();n.value=e,n.parent=t,t.children.push(n),this.fileVisible&&i.isFile(s)?n.type=exports.PathTreeNodeType.File:(n.type=exports.PathTreeNodeType.Dir,n.children=this.buildChildren(s,n))}return t}buildByFilter(e){const t=this.getAllEntriesUnderBase();return this.buildBySegments(t.filter(e))}getPathBy(e){const t=e.getPath();return{relative:t,absolute:r.resolve(this.base,t)}}buildBy(e){if(Array.isArray(e))return this.buildBySegments(e);if("function"==typeof e)return this.buildByFilter(e);throw new TypeError("buildBy: expected an array of strings or a filter function, but received "+typeof e)}build(){const e=this.getAllEntriesUnderBase();return this.buildBySegments(e)}};
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 +1 @@
1
- import{readdirSync as t,accessSync as i,constants as e,statSync as r}from"fs";import{sep as s,dirname as n,join as l,resolve as o}from"path";class a{static isValid(t){try{return i(t,e.F_OK),!0}catch{return!1}}static isDirectory(t){try{return r(t).isDirectory()}catch{return!1}}static isFile(t){try{return r(t).isFile()}catch{return!1}}}var h;!function(t){t.Dir="dir",t.File="file",t.Unknown="unknown"}(h||(h={}));class c{constructor(){this.parent=null,this.value="",this.children=[],this.type=h.Unknown}getPath(){let t="",i=this;for(;i.parent;)t=t?`${i.value}${s}${t}`:i.value,i=i.parent;return t}}class u{applyFilter(t,i){return!(!this.fileVisible&&!a.isDirectory(t))&&(!this.userFilter||this.userFilter({name:i,dirPath:n(t)}))}constructor({filter:t,base:i,fileVisible:e}){if(this.fileVisible=!1,"boolean"==typeof e&&e&&(this.fileVisible=e),void 0!==t&&(this.validateFilter(t),this.userFilter=t),!i||!a.isValid(i))throw new Error(`${i} is not a valid path!`);if(!a.isDirectory(i))throw new Error(`${i} is not a dirPath!`);this.base=i}validateFilter(t){if("function"!=typeof t)throw new TypeError("filter must be a function")}initNode(){return new c}buildChildren(i,e){const r=[],s=t(i);for(const t of s){const s=l(i,t);if(!this.applyFilter(s,t))continue;const n=this.initNode();n.value=t,n.parent=e,r.push(n),this.fileVisible&&a.isFile(s)?n.type=h.File:(n.type=h.Dir,n.children=this.buildChildren(s,n))}return r}checkRelativePaths(t){if(!Array.isArray(t))throw new Error("Expected array, got "+typeof t);for(let i=0;i<t.length;i++){const e=t[i];if("string"!=typeof e)throw new Error(`Item at index ${i} is not a string, got ${typeof e}`);const r=o(this.base,e);if(!a.isValid(r))throw new Error(`Path does not exist or is not accessible: ${r} (from relative path: ${e})`);if(!(a.isDirectory(r)||this.fileVisible&&a.isFile(r)))throw new Error(`Path is not a directory: ${r} (from relative path: ${e})`)}}formatSegments(t){return t.map(t=>t.split(/[/\\]/).filter(Boolean).join(s)).filter(Boolean)}getAllEntriesUnderBase(){return t(this.base).filter(t=>{const i=o(this.base,t);return this.applyFilter(i,t)})}buildBySegments(t){const i=this.initNode(),e=this.formatSegments(t);this.checkRelativePaths(e);for(const t of e){const e=o(this.base,t),r=this.initNode();r.value=t,r.parent=i,i.children.push(r),this.fileVisible&&a.isFile(e)?r.type=h.File:(r.type=h.Dir,r.children=this.buildChildren(e,r))}return i}buildByFilter(t){const i=this.getAllEntriesUnderBase();return this.buildBySegments(i.filter(t))}getPathBy(t){const i=t.getPath();return{relative:i,absolute:o(this.base,i)}}buildBy(t){if(Array.isArray(t))return this.buildBySegments(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.getAllEntriesUnderBase();return this.buildBySegments(t)}}export{c as PathTreeNode,h as PathTreeNodeType,u as PathTreeify};
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};
@@ -1,120 +1,93 @@
1
- /** A filter function that determines whether an entry should be included in the tree */
1
+ /** A filter function that determines whether a directory should be included in the tree */
2
2
  type FilterFunction = (params: {
3
3
  name: string;
4
4
  dirPath: string;
5
5
  }) => boolean;
6
6
  /** Constructor options for PathTreeify */
7
7
  interface PathTreeifyProps {
8
- /** The root directory to scan */
9
8
  base: string;
10
- /** Optional filter applied to every entry during recursive traversal */
11
9
  filter?: FilterFunction;
12
- /** When true, files are included as leaf nodes alongside directories. Defaults to false */
13
- fileVisible?: boolean;
14
- }
15
- /** Classification of a node in the path tree */
16
- export declare enum PathTreeNodeType {
17
- Dir = "dir",
18
- File = "file",
19
- /** Assigned before the node's type has been resolved */
20
- Unknown = "unknown"
21
10
  }
22
- /** Represents a single entry (directory or file) in the path tree */
23
- export declare class PathTreeNode {
11
+ /** Represents a single node (directory) in the path tree */
12
+ declare class PathTreeNode {
13
+ /** The root base path used to resolve absolute paths */
14
+ private base;
24
15
  /** Reference to the parent node; null for the root node */
25
16
  parent: PathTreeNode | null;
26
- /** The entry name of this node (not a full path) */
17
+ /** The directory name of this node (not a full path) */
27
18
  value: string;
28
- /** Child nodes; non-empty only for directory nodes */
19
+ /** Child nodes representing subdirectories */
29
20
  children: PathTreeNode[];
30
- /** Whether this node is a directory, a file, or not yet resolved */
31
- type: PathTreeNodeType;
21
+ constructor(base: string);
32
22
  /**
33
- * Walks up the parent chain to compute this node's relative path from the tree root.
34
- * @returns The relative path string using the platform separator
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
35
25
  */
36
- getPath(): string;
26
+ getPath(): {
27
+ relative: string;
28
+ absolute: string;
29
+ };
37
30
  }
38
- /** Builds a tree of {@link PathTreeNode} entries rooted at a given base path */
31
+ /** Builds a tree of directory nodes rooted at a given base path */
39
32
  export declare class PathTreeify {
40
33
  /** The root directory to scan */
41
34
  private base;
35
+ /** Optional filter applied to each directory during traversal */
36
+ private filter?;
37
+ constructor({ filter, base }: Partial<PathTreeifyProps>);
42
38
  /**
43
- * Optional user-supplied filter. When set, every entry must pass this predicate
44
- * in addition to the built-in visibility check.
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.
45
41
  */
46
- private userFilter?;
47
- /** When true, files are included as leaf nodes during traversal. Defaults to false */
48
- private fileVisible;
49
- /**
50
- * Determines whether a given entry should be included in the tree.
51
- * - If {@link fileVisible} is false, non-directory entries are always excluded.
52
- * - If a {@link userFilter} is set, the entry must also satisfy it.
53
- * @param absPath - Absolute path of the entry to test
54
- * @param name - Entry name (filename or directory name)
55
- */
56
- private applyFilter;
57
- constructor({ filter, base, fileVisible }: Partial<PathTreeifyProps>);
42
+ private validateFilter;
58
43
  /**
59
- * Asserts that the provided value is a callable {@link FilterFunction}.
60
- * Throws a TypeError if the check fails.
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
61
46
  */
62
- private validateFilter;
63
- /** Creates a new unattached {@link PathTreeNode} */
64
47
  private initNode;
65
48
  /**
66
- * Recursively reads {@link dirPath} and builds child nodes for each entry that
67
- * passes {@link applyFilter}. Directories are traversed depth-first;
68
- * files (when {@link fileVisible} is true) become leaf nodes.
49
+ * Recursively reads a directory and builds child nodes for each subdirectory.
50
+ * Applies the instance-level filter if one is set.
69
51
  * @param dirPath - Absolute path of the directory to read
70
- * @param parent - The parent node to attach child nodes to
52
+ * @param parent - The parent node to attach children to
71
53
  */
72
54
  private buildChildren;
73
55
  /**
74
- * Validates that every entry in {@link relativeSegments} refers to an accessible
75
- * path under {@link base}. When {@link fileVisible} is false, each path must be
76
- * a directory; when true, regular files are also accepted.
77
- * @param relativeSegments - Relative path strings to validate
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
78
59
  */
79
60
  private checkRelativePaths;
80
61
  /**
81
- * Normalises an array of path strings by splitting on both slash styles,
82
- * dropping empty segments, and rejoining with the platform separator.
83
- * Entries that reduce to an empty string (e.g. `"///"`) are removed.
84
- * @param segments - Raw path strings to normalise
85
- */
86
- private formatSegments;
87
- /**
88
- * Returns the names of all immediate entries under {@link base} that pass
89
- * {@link applyFilter}.
62
+ * Strips leading and trailing slashes from each directory name
63
+ * and removes any resulting empty strings.
90
64
  */
91
- private getAllEntriesUnderBase;
65
+ private formatDirnames;
66
+ /** Returns the names of all immediate subdirectories under the base path */
67
+ private getAllDirNamesUnderBase;
92
68
  /**
93
- * Builds a subtree containing only the entries identified by {@link segments}.
94
- * Paths are normalised via {@link formatSegments} and validated before use.
95
- * @param segments - Relative paths to include as top-level nodes
69
+ * Builds a tree rooted at base, containing only the specified subdirectories.
70
+ * @param dirNames - Relative directory names to include as top-level nodes
96
71
  */
97
- private buildBySegments;
72
+ private buildByDirNames;
98
73
  /**
99
- * Builds a subtree from top-level entries whose names satisfy {@link filter}.
100
- * Note: this predicate only affects top-level selection, not recursive traversal.
101
- * For recursive filtering use the `filter` constructor option.
102
- * @param filter - Predicate applied to each top-level entry name
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
103
76
  */
104
77
  private buildByFilter;
105
78
  /**
106
- * Returns the relative and absolute paths for a given node by delegating to
107
- * {@link PathTreeNode.getPath} and resolving against {@link base}.
79
+ * Computes the relative and absolute paths for a given node
80
+ * by walking up the parent chain.
108
81
  */
109
82
  getPathBy(node: PathTreeNode): {
110
83
  relative: string;
111
84
  absolute: string;
112
85
  };
113
- /** Overload: build the tree from an explicit list of relative path segments */
114
- buildBy(segments: string[]): PathTreeNode;
115
- /** Overload: build the tree from a predicate applied to top-level entry names */
116
- buildBy(filter: (segment: string) => boolean): PathTreeNode;
117
- /** Builds a full tree from all immediate entries under the base path */
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 */
118
91
  build(): PathTreeNode;
119
92
  }
120
93
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "path-treeify",
3
- "version": "1.3.0-beta.56a60fd",
3
+ "version": "1.3.0-beta.9bb4032",
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",
@@ -95,7 +95,6 @@
95
95
  "rollup": "^4.59.0",
96
96
  "tap-xunit": "^2.4.1",
97
97
  "tslib": "^2.8.1",
98
- "tsx": "^4.21.0",
99
98
  "typescript": "^5.9.3"
100
99
  },
101
100
  "engines": {