@youbamj/tree 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 youbamj
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,222 @@
1
+ # @youbamj/tree
2
+
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/@youbamj/tree">
5
+ <img src="https://img.shields.io/npm/v/@youbamj/tree" alt="NPM Version" />
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/@youbamj/tree">
8
+ <img src="https://img.shields.io/npm/l/@youbamj/tree" alt="License" />
9
+ </a>
10
+ </p>
11
+
12
+ `@youbamj/tree` is a modern TypeScript + Bun package to render directory trees and custom object trees.
13
+
14
+ - CLI command: `ytree`
15
+ - Library functions: `getFileTree()` and `getStringTree()`
16
+
17
+ ## Install
18
+
19
+ ### Install CLI globally
20
+
21
+ Using npm:
22
+
23
+ ```bash
24
+ npm install -g @youbamj/tree
25
+ ```
26
+
27
+ Using bun:
28
+
29
+ ```bash
30
+ bun add -g @youbamj/tree
31
+ ```
32
+
33
+ ### Install in your project
34
+
35
+ Using npm:
36
+
37
+ ```bash
38
+ npm install @youbamj/tree
39
+ ```
40
+
41
+ Using bun:
42
+
43
+ ```bash
44
+ bun add @youbamj/tree
45
+ ```
46
+
47
+ ## Use in terminal
48
+
49
+ You can run `ytree` in the shell:
50
+
51
+ ```bash
52
+ ytree -d ./src --ignore node_modules,.git --depth 3
53
+ ```
54
+
55
+ Example output:
56
+
57
+ ```text
58
+ ·
59
+ └── src
60
+ ├── cli.ts
61
+ ├── fs-tree.ts
62
+ ├── index.ts
63
+ ├── renderer.ts
64
+ └── types.ts
65
+ ```
66
+
67
+ ## Options
68
+
69
+ ```text
70
+ Usage: ytree [options] [directory]
71
+
72
+ Arguments:
73
+ directory Directory to render (default: ".")
74
+
75
+ Options:
76
+ -v, --version Show version
77
+ -d, --dir <path> Directory to render (overrides positional arg)
78
+ -o, --out <file> Write output to a file
79
+ -i, --ignore <pattern> Ignore basename or relative path (repeatable and comma-separated)
80
+ -l, --depth <n> Max depth (root is level 1)
81
+ --no-hidden Exclude hidden files/folders
82
+ --follow-symlinks Traverse symbolic links
83
+ --no-gitignore Do not respect .gitignore rules
84
+ --sort <mode> Sorting mode: asc | desc | none
85
+ --max-nodes <n> Maximum number of nodes to include (default: 10000, use -1 for no cap)
86
+ --json Print JSON tree
87
+ -c, --color [name] Output color (default: "white")
88
+ --no-color Disable color output
89
+ -h, --help Display help
90
+ ```
91
+
92
+ ## Use in your project
93
+
94
+ `getFileTree()` uses a default safety cap of `10000` nodes.
95
+ Set `maxNodes: -1` to disable the cap.
96
+
97
+ ### 1) Render a directory tree to string
98
+
99
+ ```ts
100
+ import { getFileTree, getStringTree } from "@youbamj/tree";
101
+
102
+ const treeData = await getFileTree({
103
+ dir: "./",
104
+ ignore: ["node_modules", ".git"],
105
+ level: 3,
106
+ includeHidden: false,
107
+ gitignore: true,
108
+ sort: "asc"
109
+ });
110
+
111
+ console.log(getStringTree(treeData));
112
+ ```
113
+
114
+ Example output:
115
+
116
+ ```text
117
+ ·
118
+ └── my-project
119
+ ├── package.json
120
+ ├── src
121
+ │ ├── cli.ts
122
+ │ └── index.ts
123
+ └── tsconfig.json
124
+ ```
125
+
126
+ Bun ESM script example:
127
+
128
+ ```ts
129
+ // scripts/show-tree.ts
130
+ import { getFileTree, getStringTree } from "@youbamj/tree";
131
+
132
+ const data = await getFileTree({ dir: ".", level: 2 });
133
+ console.log(getStringTree(data));
134
+ ```
135
+
136
+ Run it with:
137
+
138
+ ```bash
139
+ bun run scripts/show-tree.ts
140
+ ```
141
+
142
+ ### 2) Get raw `getFileTree()` JSON result
143
+
144
+ ```ts
145
+ import { getFileTree } from "@youbamj/tree";
146
+
147
+ const treeData = await getFileTree({
148
+ dir: "./src",
149
+ level: 2
150
+ });
151
+
152
+ console.log(JSON.stringify(treeData, null, 2));
153
+ ```
154
+
155
+ Example result:
156
+
157
+ ```json
158
+ [
159
+ {
160
+ "name": "src",
161
+ "path": "/absolute/path/to/src",
162
+ "type": "directory",
163
+ "children": [
164
+ {
165
+ "name": "cli.ts",
166
+ "path": "/absolute/path/to/src/cli.ts",
167
+ "type": "file"
168
+ },
169
+ {
170
+ "name": "index.ts",
171
+ "path": "/absolute/path/to/src/index.ts",
172
+ "type": "file"
173
+ }
174
+ ]
175
+ }
176
+ ]
177
+ ```
178
+
179
+ ### 3) Render custom object trees to string
180
+
181
+ ```ts
182
+ import { getStringTree } from "@youbamj/tree";
183
+
184
+ const output = getStringTree(
185
+ [
186
+ {
187
+ title: "done",
188
+ items: [{ title: "hiking" }, { title: "camping" }]
189
+ }
190
+ ],
191
+ {
192
+ labelKey: "title",
193
+ childrenKey: "items",
194
+ sanitizeLabels: true
195
+ }
196
+ );
197
+
198
+ console.log(output);
199
+ ```
200
+
201
+ Example output:
202
+
203
+ ```text
204
+ ·
205
+ └── done
206
+ ├── hiking
207
+ └── camping
208
+ ```
209
+
210
+ ## Why @youbamj/tree?
211
+
212
+ - 🌲 Render directory content in a clean tree structure
213
+ - 📝 Optionally write output to a file (`--out`)
214
+ - 🎨 Colorized CLI output
215
+ - 🧩 Convert custom arrays/objects to tree strings
216
+ - 🙈 `.gitignore` support by default
217
+ - 🔒 Terminal-safe label sanitization by default
218
+ - ⚡ TypeScript + Bun friendly
219
+
220
+ ## License
221
+
222
+ [MIT](./LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Command, InvalidOptionArgumentError } from "commander";
5
+ import { getFileTree, getStringTree } from "./index.js";
6
+ const COLOR_CODES = {
7
+ white: "\u001b[37m",
8
+ red: "\u001b[31m",
9
+ green: "\u001b[32m",
10
+ yellow: "\u001b[33m",
11
+ blue: "\u001b[34m",
12
+ magenta: "\u001b[35m",
13
+ cyan: "\u001b[36m",
14
+ gray: "\u001b[90m",
15
+ };
16
+ const RESET = "\u001b[0m";
17
+ const VERSION = "0.1.0";
18
+ function applyColor(text, color, enabled) {
19
+ if (!enabled)
20
+ return text;
21
+ const normalized = (color || "white");
22
+ const code = COLOR_CODES[normalized] ?? COLOR_CODES.white;
23
+ return `${code}${text}${RESET}`;
24
+ }
25
+ function parseDepth(value) {
26
+ const parsed = Number(value);
27
+ if (!Number.isInteger(parsed) || parsed < 1) {
28
+ throw new InvalidOptionArgumentError("depth must be a positive integer");
29
+ }
30
+ return parsed;
31
+ }
32
+ function parseSort(value) {
33
+ if (value === "asc" || value === "desc" || value === "none") {
34
+ return value;
35
+ }
36
+ throw new InvalidOptionArgumentError("sort must be one of: asc, desc, none");
37
+ }
38
+ function parseMaxNodes(value) {
39
+ const parsed = Number(value);
40
+ if (!Number.isInteger(parsed) || (parsed < 1 && parsed !== -1)) {
41
+ throw new InvalidOptionArgumentError("max-nodes must be a positive integer or -1");
42
+ }
43
+ return parsed;
44
+ }
45
+ function collectIgnore(value, previous) {
46
+ const parsed = value
47
+ .split(",")
48
+ .map((item) => item.trim())
49
+ .filter(Boolean);
50
+ return [...previous, ...parsed];
51
+ }
52
+ function normalizeOutput(content, filename) {
53
+ const ext = path.extname(filename).toLowerCase();
54
+ if (ext === ".md") {
55
+ return `\
56
+ \`\`\`text
57
+ ${content}
58
+ \`\`\`
59
+ `;
60
+ }
61
+ return content;
62
+ }
63
+ const program = new Command();
64
+ program
65
+ .name("ytree")
66
+ .description("Render directory trees from the terminal")
67
+ .version(VERSION, "-v, --version", "Show version")
68
+ .argument("[directory]", "Directory to render", ".")
69
+ .option("-d, --dir <path>", "Directory to render (overrides positional arg)")
70
+ .option("-o, --out <file>", "Write output to a file")
71
+ .option("-i, --ignore <pattern>", "Ignore basename or relative path (repeatable and comma-separated)", collectIgnore, [])
72
+ .option("-l, --depth <n>", "Max depth (root is level 1)", parseDepth)
73
+ .option("--no-hidden", "Exclude hidden files/folders")
74
+ .option("--follow-symlinks", "Traverse symbolic links", false)
75
+ .option("--no-gitignore", "Do not respect .gitignore rules")
76
+ .option("--sort <mode>", "Sorting mode", parseSort, "asc")
77
+ .option("--max-nodes <n>", "Maximum number of nodes to include", parseMaxNodes)
78
+ .option("--json", "Print JSON tree", false)
79
+ .option("-c, --color [name]", "Output color", "white")
80
+ .option("--no-color", "Disable color output")
81
+ .action(async (directory, options) => {
82
+ const dir = options.dir ?? directory;
83
+ const treeData = await getFileTree({
84
+ dir,
85
+ ignore: options.ignore,
86
+ level: options.depth,
87
+ includeHidden: options.hidden,
88
+ followSymlinks: options.followSymlinks,
89
+ gitignore: options.gitignore,
90
+ sort: options.sort,
91
+ maxNodes: options.maxNodes,
92
+ });
93
+ const rendered = options.json ? JSON.stringify(treeData, null, 2) : getStringTree(treeData);
94
+ const colorEnabled = Boolean(options.color) && !options.json;
95
+ const colorName = typeof options.color === "string" ? options.color : "white";
96
+ const output = applyColor(rendered, colorName, colorEnabled);
97
+ console.log(output);
98
+ if (options.out) {
99
+ const fileContent = normalizeOutput(rendered, options.out);
100
+ await writeFile(options.out, fileContent, "utf8");
101
+ }
102
+ });
103
+ program.parseAsync(process.argv).catch((error) => {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ process.stderr.write(`[ERR] ${message}\n`);
106
+ process.exit(1);
107
+ });
@@ -0,0 +1,2 @@
1
+ import type { GetFileTreeOptions, TreeNode } from "./types.js";
2
+ export declare function getFileTree({ dir, ignore, level, includeHidden, followSymlinks, sort, gitignore, maxNodes, }: GetFileTreeOptions): Promise<TreeNode[]>;
@@ -0,0 +1,167 @@
1
+ import { lstat, readFile, readdir, realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import createIgnore from "ignore";
4
+ const DEFAULT_MAX_NODES = 10_000;
5
+ function normalizeSlashes(input) {
6
+ return input.replace(/\\/g, "/");
7
+ }
8
+ function parseIgnore(ignore) {
9
+ if (!ignore)
10
+ return [];
11
+ const list = Array.isArray(ignore) ? ignore : ignore.split(",");
12
+ return list
13
+ .map((item) => item.trim())
14
+ .filter(Boolean)
15
+ .map((item) => normalizeSlashes(item.replace(/\/$/, "")));
16
+ }
17
+ function normalizeMaxNodes(maxNodes) {
18
+ if (maxNodes === -1)
19
+ return undefined;
20
+ if (!Number.isInteger(maxNodes) || maxNodes < 1) {
21
+ throw new Error("maxNodes must be a positive integer or -1 to disable the cap");
22
+ }
23
+ return maxNodes;
24
+ }
25
+ function shouldIgnoreByPatterns(relativePath, baseName, ignorePatterns) {
26
+ if (ignorePatterns.length === 0)
27
+ return false;
28
+ const rel = normalizeSlashes(relativePath);
29
+ for (const pattern of ignorePatterns) {
30
+ if (pattern.includes("/")) {
31
+ if (rel === pattern || rel.startsWith(`${pattern}/`)) {
32
+ return true;
33
+ }
34
+ continue;
35
+ }
36
+ if (baseName === pattern) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ function shouldIgnoreByGitignore(relativePath, isDirectory, gitignoreMatcher) {
43
+ if (!gitignoreMatcher || relativePath === "") {
44
+ return false;
45
+ }
46
+ if (gitignoreMatcher.ignores(relativePath)) {
47
+ return true;
48
+ }
49
+ if (isDirectory && gitignoreMatcher.ignores(`${relativePath}/`)) {
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ async function loadGitignore(rootDir, enabled) {
55
+ if (!enabled) {
56
+ return undefined;
57
+ }
58
+ try {
59
+ const gitignorePath = path.join(rootDir, ".gitignore");
60
+ const content = await readFile(gitignorePath, "utf8");
61
+ const gitignoreMatcher = createIgnore();
62
+ gitignoreMatcher.add(content);
63
+ return gitignoreMatcher;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ function compareNames(a, b, sort) {
70
+ if (sort === "none")
71
+ return 0;
72
+ const result = a.localeCompare(b);
73
+ return sort === "desc" ? -result : result;
74
+ }
75
+ async function buildNode(absolutePath, depth, options) {
76
+ let stats;
77
+ try {
78
+ stats = await lstat(absolutePath);
79
+ }
80
+ catch (error) {
81
+ if (depth === 1) {
82
+ throw error;
83
+ }
84
+ return null;
85
+ }
86
+ const relativePath = normalizeSlashes(path.relative(options.rootDir, absolutePath));
87
+ const name = path.basename(absolutePath);
88
+ const isDirectory = stats.isDirectory();
89
+ if (!options.includeHidden && name.startsWith(".")) {
90
+ return null;
91
+ }
92
+ if (shouldIgnoreByPatterns(relativePath, name, options.ignorePatterns)) {
93
+ return null;
94
+ }
95
+ if (shouldIgnoreByGitignore(relativePath, isDirectory, options.gitignoreMatcher)) {
96
+ return null;
97
+ }
98
+ if (options.maxNodes !== undefined && options.nodeCount >= options.maxNodes) {
99
+ return null;
100
+ }
101
+ const node = {
102
+ name,
103
+ path: absolutePath,
104
+ type: isDirectory ? "directory" : stats.isSymbolicLink() ? "symlink" : "file",
105
+ };
106
+ options.nodeCount += 1;
107
+ if (options.maxDepth !== undefined && depth >= options.maxDepth) {
108
+ return node;
109
+ }
110
+ const shouldTraverseDirectory = isDirectory;
111
+ const shouldTraverseSymlink = stats.isSymbolicLink() && options.followSymlinks;
112
+ if (!shouldTraverseDirectory && !shouldTraverseSymlink) {
113
+ return node;
114
+ }
115
+ let targetPath = absolutePath;
116
+ if (shouldTraverseSymlink) {
117
+ try {
118
+ targetPath = await realpath(absolutePath);
119
+ }
120
+ catch {
121
+ return node;
122
+ }
123
+ if (options.visited.has(targetPath)) {
124
+ return node;
125
+ }
126
+ options.visited.add(targetPath);
127
+ }
128
+ let entries;
129
+ try {
130
+ entries = await readdir(targetPath);
131
+ }
132
+ catch {
133
+ return node;
134
+ }
135
+ entries = [...entries].sort((a, b) => compareNames(a, b, options.sort));
136
+ const children = [];
137
+ for (const entry of entries) {
138
+ const childPath = path.join(targetPath, entry);
139
+ const child = await buildNode(childPath, depth + 1, options);
140
+ if (child) {
141
+ children.push(child);
142
+ }
143
+ }
144
+ if (children.length > 0) {
145
+ node.children = children;
146
+ }
147
+ return node;
148
+ }
149
+ export async function getFileTree({ dir, ignore, level, includeHidden = true, followSymlinks = false, sort = "asc", gitignore = true, maxNodes = DEFAULT_MAX_NODES, }) {
150
+ const rootDir = path.resolve(process.cwd(), dir);
151
+ const ignorePatterns = parseIgnore(ignore);
152
+ const gitignoreMatcher = await loadGitignore(rootDir, gitignore);
153
+ const options = {
154
+ rootDir,
155
+ maxDepth: level,
156
+ includeHidden,
157
+ followSymlinks,
158
+ sort,
159
+ ignorePatterns,
160
+ gitignoreMatcher,
161
+ maxNodes: normalizeMaxNodes(maxNodes),
162
+ nodeCount: 0,
163
+ visited: new Set(),
164
+ };
165
+ const rootNode = await buildNode(rootDir, 1, options);
166
+ return rootNode ? [rootNode] : [];
167
+ }
@@ -0,0 +1,3 @@
1
+ export { getStringTree, getTreeLines } from "./renderer.js";
2
+ export { getFileTree } from "./fs-tree.js";
3
+ export type { GetFileTreeOptions, NodeType, RenderTreeOptions, SortOrder, TreeNode, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { getStringTree, getTreeLines } from "./renderer.js";
2
+ export { getFileTree } from "./fs-tree.js";
@@ -0,0 +1,4 @@
1
+ import type { RenderTreeOptions, TreeNode } from "./types.js";
2
+ export declare function getTreeLines<T extends object>(data?: T[], renderOptions?: RenderTreeOptions): string[];
3
+ export declare function getStringTree<T extends object>(data?: T[], renderOptions?: RenderTreeOptions): string;
4
+ export type { TreeNode };
@@ -0,0 +1,84 @@
1
+ const DEFAULT_RENDER_OPTIONS = {
2
+ labelKey: "name",
3
+ childrenKey: "children",
4
+ rootMarker: "·",
5
+ indent: " ",
6
+ branch: "├── ",
7
+ lastBranch: "└── ",
8
+ vertical: "│ ",
9
+ sanitizeLabels: true,
10
+ };
11
+ function asRecord(value) {
12
+ return value;
13
+ }
14
+ function stripAnsiSequences(value) {
15
+ let output = "";
16
+ for (let i = 0; i < value.length; i += 1) {
17
+ const code = value.charCodeAt(i);
18
+ // CSI escape sequence: ESC [ ... final-byte
19
+ if (code === 0x1b && value.charCodeAt(i + 1) === 0x5b) {
20
+ i += 2;
21
+ while (i < value.length) {
22
+ const next = value.charCodeAt(i);
23
+ if (next >= 0x40 && next <= 0x7e) {
24
+ break;
25
+ }
26
+ i += 1;
27
+ }
28
+ continue;
29
+ }
30
+ output += value[i] ?? "";
31
+ }
32
+ return output;
33
+ }
34
+ function sanitizeControlCharacters(value) {
35
+ let output = "";
36
+ for (const char of value) {
37
+ const code = char.charCodeAt(0);
38
+ if (code < 32 || code === 127) {
39
+ output += `\\x${code.toString(16).padStart(2, "0")}`;
40
+ continue;
41
+ }
42
+ output += char;
43
+ }
44
+ return output;
45
+ }
46
+ function sanitizeLabel(label) {
47
+ return sanitizeControlCharacters(stripAnsiSequences(label));
48
+ }
49
+ function readChildren(node, childrenKey) {
50
+ const value = asRecord(node)[childrenKey];
51
+ return Array.isArray(value) ? value : [];
52
+ }
53
+ function readLabel(node, labelKey, sanitize) {
54
+ const value = asRecord(node)[labelKey];
55
+ const label = value == null ? "" : String(value);
56
+ return sanitize ? sanitizeLabel(label) : label;
57
+ }
58
+ function walk(nodes, options, parentLastFlags, out) {
59
+ for (const [i, node] of nodes.entries()) {
60
+ const isLast = i === nodes.length - 1;
61
+ let row = "";
62
+ for (const parentIsLast of parentLastFlags) {
63
+ row += parentIsLast ? options.indent : options.vertical;
64
+ }
65
+ row += isLast ? options.lastBranch : options.branch;
66
+ row += readLabel(node, options.labelKey, options.sanitizeLabels);
67
+ out.push(row);
68
+ const children = readChildren(node, options.childrenKey);
69
+ if (children.length > 0) {
70
+ walk(children, options, [...parentLastFlags, isLast], out);
71
+ }
72
+ }
73
+ }
74
+ export function getTreeLines(data = [], renderOptions = {}) {
75
+ const options = { ...DEFAULT_RENDER_OPTIONS, ...renderOptions };
76
+ if (data.length === 0)
77
+ return [];
78
+ const lines = [options.rootMarker];
79
+ walk(data, options, [], lines);
80
+ return lines;
81
+ }
82
+ export function getStringTree(data = [], renderOptions = {}) {
83
+ return getTreeLines(data, renderOptions).join("\n");
84
+ }
@@ -0,0 +1,28 @@
1
+ export type NodeType = "file" | "directory" | "symlink";
2
+ export interface TreeNode {
3
+ name: string;
4
+ path?: string;
5
+ type?: NodeType;
6
+ children?: TreeNode[];
7
+ }
8
+ export interface RenderTreeOptions {
9
+ labelKey?: string;
10
+ childrenKey?: string;
11
+ rootMarker?: string;
12
+ indent?: string;
13
+ branch?: string;
14
+ lastBranch?: string;
15
+ vertical?: string;
16
+ sanitizeLabels?: boolean;
17
+ }
18
+ export type SortOrder = "asc" | "desc" | "none";
19
+ export interface GetFileTreeOptions {
20
+ dir: string;
21
+ ignore?: string | string[];
22
+ level?: number;
23
+ includeHidden?: boolean;
24
+ followSymlinks?: boolean;
25
+ sort?: SortOrder;
26
+ gitignore?: boolean;
27
+ maxNodes?: number;
28
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@youbamj/tree",
3
+ "version": "0.1.0",
4
+ "description": "Render directory and object trees in a clean, modern TypeScript package with CLI support.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "youbamj",
8
+ "homepage": "https://github.com/youbamj/tree#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/youbamj/tree.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/youbamj/tree/issues"
15
+ },
16
+ "keywords": ["tree", "cli", "directory", "typescript", "bun"],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "bin": {
21
+ "ytree": "./dist/cli.js"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "types": "./dist/index.d.ts",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "files": ["dist", "README.md", "LICENSE"],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "dev": "bun run src/cli.ts",
37
+ "test": "bun test",
38
+ "check:types": "tsc --noEmit",
39
+ "lint": "biome check .",
40
+ "format": "biome format --write .",
41
+ "check": "bun run check:types && bun run lint",
42
+ "precommit": "lint-staged && bun run check:types",
43
+ "prepare": "husky",
44
+ "changeset": "changeset",
45
+ "version-packages": "changeset version",
46
+ "release": "npm publish --provenance"
47
+ },
48
+ "lint-staged": {
49
+ "*.{ts,tsx,js,jsx,json,md}": ["biome check --write"]
50
+ },
51
+ "dependencies": {
52
+ "commander": "^14.0.1",
53
+ "ignore": "^7.0.5"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^1.9.4",
57
+ "@changesets/cli": "^2.29.7",
58
+ "@types/bun": "latest",
59
+ "husky": "^9.1.7",
60
+ "lint-staged": "^16.2.0",
61
+ "typescript": "^5.6.3"
62
+ }
63
+ }