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 CHANGED
@@ -8,14 +8,14 @@
8
8
  </div>
9
9
 
10
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">
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="license" src="https://img.shields.io/npm/l/path-treeify">
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 the whole base directory
72
- const tree = treeify.buildByDirPaths(['src', 'tests']);
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
- 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
- // }
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
- ### `buildByDirPaths(paths: string[]): PathTreeNode`
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
- Scans the given relative directory paths (resolved against `base`) and returns a synthetic root `PathTreeNode` whose `children` are the top-level nodes you requested.
137
+ Builds a tree from the given list of directory names (relative to `base`).
127
138
 
128
139
  ```ts
129
- const root = treeify.buildByDirPaths(['src', 'docs']);
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 path does not exist or is not a directory.
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 srcNode = root.children[0];
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
- interface PathTreeNode {
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 base = '/your/project';
174
- const treeify = new PathTreeify({ base });
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
- // Collect all top-level directories
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
- const tree = treeify.buildByDirPaths(topLevel);
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 absolute paths while walking
221
+ ### Retrieve paths via `node.getPath()`
185
222
 
186
223
  ```ts
187
- function printPaths(node, treeify) {
224
+ function printPaths(node) {
188
225
  for (const child of node.children) {
189
- const { absolute } = treeify.getPathBy(child);
226
+ const { absolute } = child.getPath();
190
227
  console.log(absolute);
191
- printPaths(child, treeify);
228
+ printPaths(child);
192
229
  }
193
230
  }
194
231
 
195
- printPaths(tree, treeify);
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.buildByDirPaths(['src']);
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//LICENSE) © [isaaxite](https://github.com/isaaxite)
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{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}};
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 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};
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};
@@ -6,10 +6,16 @@ interface PathTreeifyProps {
6
6
  base: string;
7
7
  filter?: FilterFunction;
8
8
  }
9
- interface PathTreeNode {
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 checkRelatePaths;
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
- buildByDirPaths(nameOfDirs: string[]): PathTreeNode;
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.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 src -e ts --exec \"npm run build:dev\"",
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",