path-treeify 1.2.0-beta.db6fe0b → 1.3.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/README.md +35 -27
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +22 -3
package/README.md
CHANGED
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
<a href="https://github.com/isaaxite/path-treeify/commits/main/">
|
|
30
30
|
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/isaaxite/path-treeify">
|
|
31
31
|
</a>
|
|
32
|
+
<a href='https://codecov.io/github/isaaxite/path-treeify/tests'>
|
|
33
|
+
<img src='https://github.com/isaaxite/path-treeify/actions/workflows/unittests.yml/badge.svg' alt='Coverage Status' />
|
|
34
|
+
</a>
|
|
35
|
+
<a href='https://coveralls.io/github/isaaxite/path-treeify'>
|
|
36
|
+
<img src='https://coveralls.io/repos/github/isaaxite/path-treeify/badge.svg' alt='Coverage Status' />
|
|
37
|
+
</a>
|
|
32
38
|
</div>
|
|
33
39
|
|
|
34
40
|
---
|
|
@@ -38,9 +44,9 @@
|
|
|
38
44
|
- 🌲 Builds a recursive tree from one or more directory paths
|
|
39
45
|
- 🔗 Each node carries a `parent` circular reference for upward traversal
|
|
40
46
|
- 📍 Each node exposes a `getPath()` method to retrieve its own paths directly
|
|
41
|
-
- 🔍 Optional `filter` callback
|
|
47
|
+
- 🔍 Optional `filter` callback applied at **every depth**, including top-level directories
|
|
42
48
|
- ⚡ `build()` scans the entire `base` directory with zero configuration
|
|
43
|
-
- 🎛️ `buildBy()` accepts either a directory name array or a filter function
|
|
49
|
+
- 🎛️ `buildBy()` accepts either a directory name array or a top-level filter function
|
|
44
50
|
- 📦 Ships as both ESM (`index.mjs`) and CJS (`index.cjs`) with full TypeScript types
|
|
45
51
|
- 🚫 Zero runtime dependencies
|
|
46
52
|
|
|
@@ -74,8 +80,8 @@ const treeify = new PathTreeify({ base: '/your/project/root' });
|
|
|
74
80
|
// Scan specific directories by name
|
|
75
81
|
const tree = treeify.buildBy(['src', 'tests']);
|
|
76
82
|
|
|
77
|
-
// Scan with a
|
|
78
|
-
const filtered = treeify.buildBy(name =>
|
|
83
|
+
// Scan with a top-level filter function
|
|
84
|
+
const filtered = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
79
85
|
|
|
80
86
|
// Or scan everything under base at once
|
|
81
87
|
const fullTree = treeify.build();
|
|
@@ -89,10 +95,10 @@ const fullTree = treeify.build();
|
|
|
89
95
|
|
|
90
96
|
Creates a new instance.
|
|
91
97
|
|
|
92
|
-
| Option | Type | Required | Description
|
|
93
|
-
|
|
94
|
-
| `base` | `string` | ✅ | Absolute path to the root directory to scan from
|
|
95
|
-
| `filter` | `FilterFunction` (see below) | ❌ |
|
|
98
|
+
| Option | Type | Required | Description |
|
|
99
|
+
|----------|-------------------------------|----------|-----------------------------------------------------------------------------|
|
|
100
|
+
| `base` | `string` | ✅ | Absolute path to the root directory to scan from |
|
|
101
|
+
| `filter` | `FilterFunction` (see below) | ❌ | Applied at **every depth** — top-level directories included |
|
|
96
102
|
|
|
97
103
|
`base` must exist and be a directory, otherwise the constructor throws.
|
|
98
104
|
|
|
@@ -100,7 +106,7 @@ Creates a new instance.
|
|
|
100
106
|
|
|
101
107
|
### `FilterFunction`
|
|
102
108
|
|
|
103
|
-
Used as the `filter` option in the constructor. Applied
|
|
109
|
+
Used as the `filter` option in the constructor. Applied at every level of the tree, including the immediate children of `base`.
|
|
104
110
|
|
|
105
111
|
```ts
|
|
106
112
|
type FilterFunction = (params: {
|
|
@@ -109,14 +115,14 @@ type FilterFunction = (params: {
|
|
|
109
115
|
}) => boolean;
|
|
110
116
|
```
|
|
111
117
|
|
|
112
|
-
Return `true` to **include** the directory and recurse into it; `false` to **skip** it.
|
|
118
|
+
Return `true` to **include** the directory and recurse into it; `false` to **skip** it entirely.
|
|
113
119
|
|
|
114
|
-
**Example —
|
|
120
|
+
**Example — exclude `node_modules` and hidden directories at every depth:**
|
|
115
121
|
|
|
116
122
|
```ts
|
|
117
123
|
const treeify = new PathTreeify({
|
|
118
124
|
base: '/your/project',
|
|
119
|
-
filter: ({ name }) => !name.startsWith('.')
|
|
125
|
+
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
120
126
|
});
|
|
121
127
|
```
|
|
122
128
|
|
|
@@ -124,7 +130,7 @@ const treeify = new PathTreeify({
|
|
|
124
130
|
|
|
125
131
|
### `build(): PathTreeNode`
|
|
126
132
|
|
|
127
|
-
Scans
|
|
133
|
+
Scans all subdirectories directly under `base` (applying the instance-level `filter` if set) and returns a synthetic root `PathTreeNode`.
|
|
128
134
|
|
|
129
135
|
```ts
|
|
130
136
|
const tree = treeify.build();
|
|
@@ -145,13 +151,13 @@ const tree = treeify.buildBy(['src', 'docs', 'tests']);
|
|
|
145
151
|
|
|
146
152
|
### `buildBy(filter: (dirName: string) => boolean): PathTreeNode`
|
|
147
153
|
|
|
148
|
-
Collects all top-level subdirectories under `base`, applies the given
|
|
154
|
+
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
155
|
|
|
150
156
|
```ts
|
|
151
157
|
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
152
158
|
```
|
|
153
159
|
|
|
154
|
-
> Note
|
|
160
|
+
> **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.
|
|
155
161
|
|
|
156
162
|
---
|
|
157
163
|
|
|
@@ -161,6 +167,8 @@ Walks a node's `parent` chain to reconstruct its full path. Equivalent to callin
|
|
|
161
167
|
|
|
162
168
|
```ts
|
|
163
169
|
const { relative, absolute } = treeify.getPathBy(node);
|
|
170
|
+
// relative → e.g. 'src/components'
|
|
171
|
+
// absolute → e.g. '/your/project/src/components'
|
|
164
172
|
```
|
|
165
173
|
|
|
166
174
|
---
|
|
@@ -179,7 +187,7 @@ class PathTreeNode {
|
|
|
179
187
|
}
|
|
180
188
|
```
|
|
181
189
|
|
|
182
|
-
|
|
190
|
+
`node.getPath()` returns the same result as `treeify.getPathBy(node)` — both are available for convenience.
|
|
183
191
|
|
|
184
192
|
> ⚠️ **Circular references** — `parent` points back up the tree. Use `JSON.stringify` replacers or a library like `flatted` if you need to serialize the result.
|
|
185
193
|
|
|
@@ -196,26 +204,26 @@ const treeify = new PathTreeify({ base: '/your/project' });
|
|
|
196
204
|
const tree = treeify.build();
|
|
197
205
|
```
|
|
198
206
|
|
|
199
|
-
###
|
|
207
|
+
### Exclude directories at every depth via constructor filter
|
|
200
208
|
|
|
201
209
|
```ts
|
|
202
|
-
const
|
|
210
|
+
const treeify = new PathTreeify({
|
|
211
|
+
base: '/your/project',
|
|
212
|
+
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
213
|
+
});
|
|
214
|
+
const tree = treeify.build();
|
|
203
215
|
```
|
|
204
216
|
|
|
205
|
-
###
|
|
217
|
+
### Scan specific directories
|
|
206
218
|
|
|
207
219
|
```ts
|
|
208
|
-
const tree = treeify.buildBy(
|
|
220
|
+
const tree = treeify.buildBy(['src', 'tests', 'docs']);
|
|
209
221
|
```
|
|
210
222
|
|
|
211
|
-
###
|
|
223
|
+
### Select top-level directories with a predicate
|
|
212
224
|
|
|
213
225
|
```ts
|
|
214
|
-
const
|
|
215
|
-
base: '/your/project',
|
|
216
|
-
filter: ({ name }) => name !== 'node_modules' && !name.startsWith('.'),
|
|
217
|
-
});
|
|
218
|
-
const tree = treeify.build();
|
|
226
|
+
const tree = treeify.buildBy(name => name !== 'node_modules' && !name.startsWith('.'));
|
|
219
227
|
```
|
|
220
228
|
|
|
221
229
|
### Retrieve paths via `node.getPath()`
|
|
@@ -245,4 +253,4 @@ const tree = treeify.build();
|
|
|
245
253
|
|
|
246
254
|
## License
|
|
247
255
|
|
|
248
|
-
[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 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()
|
|
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,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()
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "path-treeify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"test": "ava",
|
|
28
|
-
"test:
|
|
29
|
-
"
|
|
28
|
+
"test:coverage": "c8 ava",
|
|
29
|
+
"test:junit": "mkdir -p reports && ava --tap | tap-xunit > reports/junit.xml",
|
|
30
|
+
"test:report": "rimraf reports && npm run test:coverage && npm run test:junit",
|
|
31
|
+
"clean": "rimraf dist",
|
|
30
32
|
"build": "npm run clean && rollup -c",
|
|
31
33
|
"build:dev": "NODE_ENV=development npm run build",
|
|
32
34
|
"build:prod": "NODE_ENV=production npm run build",
|
|
@@ -43,6 +45,21 @@
|
|
|
43
45
|
"--experimental-vm-modules"
|
|
44
46
|
]
|
|
45
47
|
},
|
|
48
|
+
"c8": {
|
|
49
|
+
"reports-dir": "reports/coverage",
|
|
50
|
+
"reporter": [
|
|
51
|
+
"html",
|
|
52
|
+
"lcov"
|
|
53
|
+
],
|
|
54
|
+
"exclude": [
|
|
55
|
+
"tests/**",
|
|
56
|
+
"node_modules/**"
|
|
57
|
+
],
|
|
58
|
+
"lines": 80,
|
|
59
|
+
"statements": 80,
|
|
60
|
+
"functions": 80,
|
|
61
|
+
"branches": 75
|
|
62
|
+
},
|
|
46
63
|
"repository": {
|
|
47
64
|
"type": "git",
|
|
48
65
|
"url": "git+https://github.com/isaaxite/path-treeify.git"
|
|
@@ -72,9 +89,11 @@
|
|
|
72
89
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
73
90
|
"@types/node": "^25.5.0",
|
|
74
91
|
"ava": "^7.0.0",
|
|
92
|
+
"c8": "^11.0.0",
|
|
75
93
|
"nodemon": "^3.1.14",
|
|
76
94
|
"rimraf": "^6.1.3",
|
|
77
95
|
"rollup": "^4.59.0",
|
|
96
|
+
"tap-xunit": "^2.4.1",
|
|
78
97
|
"tslib": "^2.8.1",
|
|
79
98
|
"typescript": "^5.9.3"
|
|
80
99
|
},
|