folder-tree-print 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/README.md +159 -0
- package/index.js +416 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# folder-tree-print
|
|
2
|
+
|
|
3
|
+
A zero-dependency CLI tool that prints your folder structure as a beautiful, colorized tree. Great for LLM context, README docs, and sharing project layouts.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Running `folder-tree-print` in any directory prints a tree view of its contents:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
my-project/
|
|
11
|
+
├── src/
|
|
12
|
+
│ ├── components/
|
|
13
|
+
│ │ ├── Header.tsx
|
|
14
|
+
│ │ └── Footer.tsx
|
|
15
|
+
│ ├── utils/
|
|
16
|
+
│ │ └── helpers.ts
|
|
17
|
+
│ └── index.ts
|
|
18
|
+
├── package.json
|
|
19
|
+
├── tsconfig.json
|
|
20
|
+
└── README.md
|
|
21
|
+
|
|
22
|
+
3 directories, 6 files
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
It automatically colorizes output (directories in blue, symlinks in cyan, executables in green), sorts directories before files, and skips common noise like `node_modules` and `.git`.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g folder-tree-print
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or run directly with npx:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx folder-tree-print
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Current directory
|
|
43
|
+
folder-tree
|
|
44
|
+
|
|
45
|
+
# Specific folder
|
|
46
|
+
folder-tree-print ./src
|
|
47
|
+
|
|
48
|
+
# Limit depth
|
|
49
|
+
folder-tree-print -L 3
|
|
50
|
+
|
|
51
|
+
# Include hidden files, ignore specific folders
|
|
52
|
+
folder-tree-print -a -I logs -I tmp
|
|
53
|
+
|
|
54
|
+
# Directories only
|
|
55
|
+
folder-tree-print --dirs-only
|
|
56
|
+
|
|
57
|
+
# JSON output (great for piping)
|
|
58
|
+
folder-tree-print --json > structure.json
|
|
59
|
+
|
|
60
|
+
# Flat list
|
|
61
|
+
folder-tree-print --flat
|
|
62
|
+
|
|
63
|
+
# ASCII mode for legacy terminals
|
|
64
|
+
folder-tree-print --ascii
|
|
65
|
+
|
|
66
|
+
# Combine flags
|
|
67
|
+
folder-tree-print ./src -L 2 -d --no-color
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
The script includes a `CONFIG` object at the top of the file that you can edit before running. Here's the full reference:
|
|
73
|
+
|
|
74
|
+
### Display Format
|
|
75
|
+
|
|
76
|
+
| Option | Default | Description |
|
|
77
|
+
| --------------- | ---------- | -------------------------------------------------------------------------------- |
|
|
78
|
+
| `displayFormat` | `"tree"` | `"tree"` for box-drawing tree, `"flat"` for flat paths, `"json"` for JSON output |
|
|
79
|
+
| `treeStyle` | `"unicode"`| `"unicode"` for `├── └──`, `"ascii"` for `+-- \`--` (legacy terminal fallback) |
|
|
80
|
+
|
|
81
|
+
### Sorting
|
|
82
|
+
|
|
83
|
+
| Option | Default | Description |
|
|
84
|
+
| ----------- | -------------- | -------------------------------------------------------------------------------- |
|
|
85
|
+
| `sortOrder` | `"dirs-first"` | `"dirs-first"` groups dirs above files, `"alpha"` pure alphabetical, `"none"` filesystem order |
|
|
86
|
+
|
|
87
|
+
### Filtering
|
|
88
|
+
|
|
89
|
+
| Option | Default | Description |
|
|
90
|
+
| ------------------- | ------- | ---------------------------------------------------------------- |
|
|
91
|
+
| `showHidden` | `false` | Show hidden files and directories (dotfiles) |
|
|
92
|
+
| `dirsOnly` | `false` | Show only directories, hide all files |
|
|
93
|
+
| `maxDepth` | `0` | Maximum depth to traverse (`0` = no limit) |
|
|
94
|
+
| `extraSkipDirs` | `[]` | Additional directories to skip (merged with built-in list) |
|
|
95
|
+
| `extraSkipFiles` | `[]` | Additional files to skip (merged with built-in list) |
|
|
96
|
+
| `onlyExtensions` | `[]` | Only show files matching these extensions (empty = all) |
|
|
97
|
+
| `excludeExtensions` | `[]` | Hide files matching these extensions |
|
|
98
|
+
|
|
99
|
+
### Colors
|
|
100
|
+
|
|
101
|
+
| Option | Default | Description |
|
|
102
|
+
| ---------- | ------- | -------------------------------------------------------------------- |
|
|
103
|
+
| `colorize` | `true` | Enable colored output (blue dirs, cyan symlinks, green executables) |
|
|
104
|
+
|
|
105
|
+
### Output
|
|
106
|
+
|
|
107
|
+
| Option | Default | Description |
|
|
108
|
+
| ------------- | ------- | ---------------------------------------------------- |
|
|
109
|
+
| `showSummary` | `true` | Show summary line at the end ("X directories, Y files") |
|
|
110
|
+
|
|
111
|
+
### Example Configurations
|
|
112
|
+
|
|
113
|
+
**Only show TypeScript and JavaScript files:**
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
onlyExtensions: ["ts", "tsx", "js", "jsx"],
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Use ASCII tree characters for CI logs:**
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
treeStyle: "ascii",
|
|
123
|
+
colorize: false,
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Shallow overview with no files:**
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
dirsOnly: true,
|
|
130
|
+
maxDepth: 2,
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## CLI Options
|
|
134
|
+
|
|
135
|
+
| Flag | Short | Description |
|
|
136
|
+
| -------------- | ----- | -------------------------------------------- |
|
|
137
|
+
| `--help` | `-h` | Show help message |
|
|
138
|
+
| `--all` | `-a` | Include hidden files (dotfiles) |
|
|
139
|
+
| `--dirs-only` | `-d` | Show only directories |
|
|
140
|
+
| `--depth <n>` | `-L` | Limit tree depth |
|
|
141
|
+
| `--ignore <s>` | `-I` | Ignore matching names (repeatable) |
|
|
142
|
+
| `--no-color` | | Disable colored output |
|
|
143
|
+
| `--no-summary` | | Hide the summary line |
|
|
144
|
+
| `--flat` | | Flat list instead of tree |
|
|
145
|
+
| `--json` | | Output as JSON |
|
|
146
|
+
| `--ascii` | | Use ASCII characters instead of Unicode |
|
|
147
|
+
|
|
148
|
+
## Behavior
|
|
149
|
+
|
|
150
|
+
- **Color-coded** — directories (blue), symlinks (cyan with → target), executables (green).
|
|
151
|
+
- **Smart defaults** — auto-skips `node_modules`, `.git`, `.next`, `dist`, `build`, `vendor`, `__pycache__`, `.idea`, `.vscode`, and more.
|
|
152
|
+
- **Sorted output** — directories listed before files, alphabetical within each group.
|
|
153
|
+
- **Symlink-aware** — displays symlink targets with `→` arrows.
|
|
154
|
+
- **Plural-correct summary** — "1 directory, 1 file" vs "3 directories, 12 files".
|
|
155
|
+
- **Configurable** — edit the `CONFIG` object for persistent preferences, or use CLI flags for one-off runs.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// CONFIGURATION - Edit this object to alter script behavior before running
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
// --- Display Format ---
|
|
11
|
+
// "tree" → classic tree with box-drawing characters (├── └── │)
|
|
12
|
+
// "flat" → flat list of relative paths
|
|
13
|
+
// "json" → JSON object output
|
|
14
|
+
displayFormat: "tree",
|
|
15
|
+
|
|
16
|
+
// --- Tree Characters ---
|
|
17
|
+
// "unicode" → ├── └── │ (works in most modern terminals)
|
|
18
|
+
// "ascii" → +-- `-- | (safe fallback for legacy terminals)
|
|
19
|
+
treeStyle: "unicode",
|
|
20
|
+
|
|
21
|
+
// --- Sorting ---
|
|
22
|
+
// "dirs-first" → directories listed before files, alphabetical within each
|
|
23
|
+
// "alpha" → pure alphabetical, no grouping
|
|
24
|
+
// "none" → filesystem order (as returned by readdir)
|
|
25
|
+
sortOrder: "dirs-first",
|
|
26
|
+
|
|
27
|
+
// --- Filtering ---
|
|
28
|
+
// Show hidden files and directories (dotfiles)
|
|
29
|
+
showHidden: false,
|
|
30
|
+
|
|
31
|
+
// Show only directories, hide all files
|
|
32
|
+
dirsOnly: false,
|
|
33
|
+
|
|
34
|
+
// Maximum depth to traverse (0 = no limit)
|
|
35
|
+
maxDepth: 0,
|
|
36
|
+
|
|
37
|
+
// Extra directories to skip (merged with built-in SKIP_DIRS)
|
|
38
|
+
extraSkipDirs: [],
|
|
39
|
+
|
|
40
|
+
// Extra files to skip (merged with built-in SKIP_FILES)
|
|
41
|
+
extraSkipFiles: [],
|
|
42
|
+
|
|
43
|
+
// Only show files matching these extensions (empty = show all)
|
|
44
|
+
// e.g. ["js", "ts", "tsx"] — directories are always shown
|
|
45
|
+
onlyExtensions: [],
|
|
46
|
+
|
|
47
|
+
// Hide files matching these extensions
|
|
48
|
+
excludeExtensions: [],
|
|
49
|
+
|
|
50
|
+
// --- Colors ---
|
|
51
|
+
// Enable colored output (directory names in blue, symlinks in cyan, etc.)
|
|
52
|
+
colorize: true,
|
|
53
|
+
|
|
54
|
+
// --- Output ---
|
|
55
|
+
// Show summary line at the end ("X directories, Y files")
|
|
56
|
+
showSummary: true,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// TREE CHARACTER SETS
|
|
61
|
+
// ============================================================================
|
|
62
|
+
const TREE_CHARS = {
|
|
63
|
+
unicode: {
|
|
64
|
+
TEE: "├── ",
|
|
65
|
+
LAST: "└── ",
|
|
66
|
+
PIPE: "│ ",
|
|
67
|
+
SPACE: " ",
|
|
68
|
+
},
|
|
69
|
+
ascii: {
|
|
70
|
+
TEE: "+-- ",
|
|
71
|
+
LAST: "`-- ",
|
|
72
|
+
PIPE: "| ",
|
|
73
|
+
SPACE: " ",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// DEFAULT SKIP LISTS
|
|
79
|
+
// ============================================================================
|
|
80
|
+
const SKIP_DIRS = new Set([
|
|
81
|
+
"node_modules", ".git", ".next", ".nuxt", "dist", "build",
|
|
82
|
+
".cache", ".turbo", "vendor", "__pycache__", ".pytest_cache",
|
|
83
|
+
".venv", "venv", ".idea", ".vscode", "coverage", ".nyc_output",
|
|
84
|
+
".parcel-cache",
|
|
85
|
+
...CONFIG.extraSkipDirs,
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const SKIP_FILES = new Set([
|
|
89
|
+
".DS_Store", "Thumbs.db",
|
|
90
|
+
...CONFIG.extraSkipFiles,
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// COLOR DEFINITIONS
|
|
95
|
+
// ============================================================================
|
|
96
|
+
function getColors(enabled) {
|
|
97
|
+
if (!enabled) {
|
|
98
|
+
return { reset: "", bold: "", dim: "", dir: "", file: "", sym: "", exec: "" };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
reset: "\x1b[0m",
|
|
102
|
+
bold: "\x1b[1m",
|
|
103
|
+
dim: "\x1b[2m",
|
|
104
|
+
dir: "\x1b[1;34m", // bold blue
|
|
105
|
+
file: "\x1b[0m", // default
|
|
106
|
+
sym: "\x1b[1;36m", // bold cyan
|
|
107
|
+
exec: "\x1b[1;32m", // bold green
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// CLI ARGUMENT PARSING
|
|
113
|
+
// ============================================================================
|
|
114
|
+
function parseArgs(argv) {
|
|
115
|
+
const args = argv.slice(2);
|
|
116
|
+
const flags = {
|
|
117
|
+
help: false,
|
|
118
|
+
all: false,
|
|
119
|
+
dirsOnly: false,
|
|
120
|
+
noColor: false,
|
|
121
|
+
flat: false,
|
|
122
|
+
json: false,
|
|
123
|
+
ascii: false,
|
|
124
|
+
noSummary: false,
|
|
125
|
+
};
|
|
126
|
+
const ignore = [];
|
|
127
|
+
let depth = 0;
|
|
128
|
+
let targetDir = null;
|
|
129
|
+
|
|
130
|
+
const consumed = new Set();
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < args.length; i++) {
|
|
133
|
+
const arg = args[i];
|
|
134
|
+
|
|
135
|
+
if (arg === "--help" || arg === "-h") { flags.help = true; consumed.add(i); }
|
|
136
|
+
else if (arg === "--all" || arg === "-a") { flags.all = true; consumed.add(i); }
|
|
137
|
+
else if (arg === "--dirs-only" || arg === "-d") { flags.dirsOnly = true; consumed.add(i); }
|
|
138
|
+
else if (arg === "--no-color") { flags.noColor = true; consumed.add(i); }
|
|
139
|
+
else if (arg === "--flat") { flags.flat = true; consumed.add(i); }
|
|
140
|
+
else if (arg === "--json") { flags.json = true; consumed.add(i); }
|
|
141
|
+
else if (arg === "--ascii") { flags.ascii = true; consumed.add(i); }
|
|
142
|
+
else if (arg === "--no-summary") { flags.noSummary = true; consumed.add(i); }
|
|
143
|
+
else if (arg === "--depth" || arg === "-L") {
|
|
144
|
+
consumed.add(i);
|
|
145
|
+
if (i + 1 < args.length) {
|
|
146
|
+
depth = parseInt(args[i + 1], 10);
|
|
147
|
+
consumed.add(i + 1);
|
|
148
|
+
i++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (arg === "--ignore" || arg === "-I") {
|
|
152
|
+
consumed.add(i);
|
|
153
|
+
if (i + 1 < args.length) {
|
|
154
|
+
ignore.push(args[i + 1]);
|
|
155
|
+
consumed.add(i + 1);
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// First non-consumed, non-flag argument is the target directory
|
|
162
|
+
for (let i = 0; i < args.length; i++) {
|
|
163
|
+
if (!consumed.has(i) && !args[i].startsWith("-")) {
|
|
164
|
+
targetDir = args[i];
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { flags, ignore, depth, targetDir: targetDir || "." };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// HELPERS
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
function shouldIgnore(name, ignoreSet) {
|
|
177
|
+
if (!CONFIG.showHidden && name.startsWith(".")) return true;
|
|
178
|
+
return ignoreSet.has(name);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function shouldShowFile(name) {
|
|
182
|
+
if (CONFIG.dirsOnly) return false;
|
|
183
|
+
|
|
184
|
+
const ext = path.extname(name).slice(1).toLowerCase();
|
|
185
|
+
|
|
186
|
+
if (CONFIG.onlyExtensions.length > 0 && !CONFIG.onlyExtensions.includes(ext)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (CONFIG.excludeExtensions.includes(ext)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getEntries(dirPath, ignoreSet) {
|
|
197
|
+
try {
|
|
198
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
199
|
+
return entries
|
|
200
|
+
.filter((e) => !shouldIgnore(e.name, ignoreSet))
|
|
201
|
+
.filter((e) => e.isDirectory() || e.isSymbolicLink() || shouldShowFile(e.name))
|
|
202
|
+
.sort((a, b) => {
|
|
203
|
+
if (CONFIG.sortOrder === "none") return 0;
|
|
204
|
+
if (CONFIG.sortOrder === "dirs-first") {
|
|
205
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
206
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
207
|
+
}
|
|
208
|
+
return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatEntryName(entry, fullPath, colors) {
|
|
216
|
+
const name = entry.name;
|
|
217
|
+
|
|
218
|
+
if (entry.isSymbolicLink()) {
|
|
219
|
+
try {
|
|
220
|
+
const target = fs.readlinkSync(fullPath);
|
|
221
|
+
return `${colors.sym}${name}${colors.reset} ${colors.dim}→ ${target}${colors.reset}`;
|
|
222
|
+
} catch {
|
|
223
|
+
return `${colors.sym}${name}${colors.reset}`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (entry.isDirectory()) {
|
|
228
|
+
return `${colors.dir}${name}/${colors.reset}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check if executable
|
|
232
|
+
try {
|
|
233
|
+
fs.accessSync(fullPath, fs.constants.X_OK);
|
|
234
|
+
const suffix = process.platform !== "win32" ? "*" : "";
|
|
235
|
+
return `${colors.exec}${name}${suffix}${colors.reset}`;
|
|
236
|
+
} catch {
|
|
237
|
+
return `${colors.file}${name}${colors.reset}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// DISPLAY: TREE FORMAT
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
function printTree(dirPath, prefix, depth, ignoreSet, colors, chars, stats) {
|
|
246
|
+
if (CONFIG.maxDepth > 0 && depth >= CONFIG.maxDepth) return;
|
|
247
|
+
|
|
248
|
+
const entries = getEntries(dirPath, ignoreSet);
|
|
249
|
+
|
|
250
|
+
for (let i = 0; i < entries.length; i++) {
|
|
251
|
+
const entry = entries[i];
|
|
252
|
+
const isLast = i === entries.length - 1;
|
|
253
|
+
const connector = isLast ? chars.LAST : chars.TEE;
|
|
254
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
255
|
+
const display = formatEntryName(entry, fullPath, colors);
|
|
256
|
+
|
|
257
|
+
console.log(`${colors.dim}${prefix}${connector}${colors.reset}${display}`);
|
|
258
|
+
|
|
259
|
+
if (entry.isDirectory()) {
|
|
260
|
+
stats.dirs++;
|
|
261
|
+
const newPrefix = prefix + (isLast ? chars.SPACE : chars.PIPE);
|
|
262
|
+
printTree(fullPath, newPrefix, depth + 1, ignoreSet, colors, chars, stats);
|
|
263
|
+
} else {
|
|
264
|
+
stats.files++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// DISPLAY: FLAT FORMAT
|
|
271
|
+
// ============================================================================
|
|
272
|
+
|
|
273
|
+
function printFlat(dirPath, relativePath, depth, ignoreSet, colors, stats) {
|
|
274
|
+
if (CONFIG.maxDepth > 0 && depth >= CONFIG.maxDepth) return;
|
|
275
|
+
|
|
276
|
+
const entries = getEntries(dirPath, ignoreSet);
|
|
277
|
+
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
280
|
+
const relPath = path.join(relativePath, entry.name);
|
|
281
|
+
|
|
282
|
+
if (entry.isDirectory()) {
|
|
283
|
+
stats.dirs++;
|
|
284
|
+
console.log(`${colors.dir}${relPath}/${colors.reset}`);
|
|
285
|
+
printFlat(fullPath, relPath, depth + 1, ignoreSet, colors, stats);
|
|
286
|
+
} else {
|
|
287
|
+
stats.files++;
|
|
288
|
+
console.log(`${colors.file}${relPath}${colors.reset}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// DISPLAY: JSON FORMAT
|
|
295
|
+
// ============================================================================
|
|
296
|
+
|
|
297
|
+
function buildJson(dirPath, depth, ignoreSet, stats) {
|
|
298
|
+
if (CONFIG.maxDepth > 0 && depth >= CONFIG.maxDepth) return [];
|
|
299
|
+
|
|
300
|
+
const entries = getEntries(dirPath, ignoreSet);
|
|
301
|
+
|
|
302
|
+
return entries.map((entry) => {
|
|
303
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
304
|
+
|
|
305
|
+
if (entry.isDirectory()) {
|
|
306
|
+
stats.dirs++;
|
|
307
|
+
return {
|
|
308
|
+
name: entry.name,
|
|
309
|
+
type: "directory",
|
|
310
|
+
children: buildJson(fullPath, depth + 1, ignoreSet, stats),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
stats.files++;
|
|
315
|
+
try {
|
|
316
|
+
const fileStat = fs.statSync(fullPath);
|
|
317
|
+
return { name: entry.name, type: "file", size: fileStat.size };
|
|
318
|
+
} catch {
|
|
319
|
+
return { name: entry.name, type: "file" };
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// HELP TEXT
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
function printHelp() {
|
|
329
|
+
console.log(`
|
|
330
|
+
📂 folder-tree-print — Print folder structure in a beautiful tree format
|
|
331
|
+
|
|
332
|
+
Usage:
|
|
333
|
+
folder-tree-print [path] [options]
|
|
334
|
+
|
|
335
|
+
Options:
|
|
336
|
+
-h, --help Show this help message
|
|
337
|
+
-a, --all Include hidden files/folders (dotfiles)
|
|
338
|
+
-d, --dirs-only Show only directories
|
|
339
|
+
-L, --depth <n> Limit depth of tree (e.g. -L 2)
|
|
340
|
+
-I, --ignore <s> Ignore files/folders matching name (repeatable)
|
|
341
|
+
--no-color Disable colored output
|
|
342
|
+
--no-summary Hide the summary line
|
|
343
|
+
--flat Print flat list instead of tree
|
|
344
|
+
--json Output as JSON
|
|
345
|
+
--ascii Use ASCII characters instead of Unicode
|
|
346
|
+
|
|
347
|
+
Examples:
|
|
348
|
+
folder-tree-print
|
|
349
|
+
folder-tree-print ./src -L 3
|
|
350
|
+
folder-tree-print -a -I node_modules -I .git
|
|
351
|
+
folder-tree-print --dirs-only --depth 2
|
|
352
|
+
folder-tree-print --json > structure.json
|
|
353
|
+
`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// CLI
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
const { flags, ignore, depth, targetDir } = parseArgs(process.argv);
|
|
361
|
+
|
|
362
|
+
// --- Apply CLI flags to CONFIG ---
|
|
363
|
+
if (flags.help) { printHelp(); process.exit(0); }
|
|
364
|
+
if (flags.all) CONFIG.showHidden = true;
|
|
365
|
+
if (flags.dirsOnly) CONFIG.dirsOnly = true;
|
|
366
|
+
if (flags.noColor) CONFIG.colorize = false;
|
|
367
|
+
if (flags.noSummary) CONFIG.showSummary = false;
|
|
368
|
+
if (flags.ascii) CONFIG.treeStyle = "ascii";
|
|
369
|
+
if (flags.flat) CONFIG.displayFormat = "flat";
|
|
370
|
+
if (flags.json) CONFIG.displayFormat = "json";
|
|
371
|
+
if (depth > 0) CONFIG.maxDepth = depth;
|
|
372
|
+
|
|
373
|
+
// --- Build ignore set ---
|
|
374
|
+
const ignoreSet = new Set([...SKIP_DIRS, ...SKIP_FILES, ...ignore]);
|
|
375
|
+
|
|
376
|
+
// --- Resolve target directory ---
|
|
377
|
+
const resolvedPath = path.resolve(targetDir);
|
|
378
|
+
|
|
379
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
380
|
+
console.error(`\x1b[31mError: "${targetDir}" does not exist.\x1b[0m`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!fs.statSync(resolvedPath).isDirectory()) {
|
|
385
|
+
console.error(`\x1b[31mError: "${targetDir}" is not a directory.\x1b[0m`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// --- Setup ---
|
|
390
|
+
const colors = getColors(CONFIG.colorize);
|
|
391
|
+
const chars = TREE_CHARS[CONFIG.treeStyle];
|
|
392
|
+
const rootName = path.basename(resolvedPath) || resolvedPath;
|
|
393
|
+
const stats = { dirs: 0, files: 0 };
|
|
394
|
+
|
|
395
|
+
// --- Run ---
|
|
396
|
+
if (CONFIG.displayFormat === "json") {
|
|
397
|
+
const tree = {
|
|
398
|
+
name: rootName,
|
|
399
|
+
type: "directory",
|
|
400
|
+
children: buildJson(resolvedPath, 0, ignoreSet, stats),
|
|
401
|
+
};
|
|
402
|
+
console.log(JSON.stringify(tree, null, 2));
|
|
403
|
+
} else if (CONFIG.displayFormat === "flat") {
|
|
404
|
+
console.log(`\n${colors.bold}${colors.dir}${rootName}/${colors.reset}`);
|
|
405
|
+
printFlat(resolvedPath, "", 0, ignoreSet, colors, stats);
|
|
406
|
+
} else {
|
|
407
|
+
console.log(`\n${colors.bold}${colors.dir}${rootName}/${colors.reset}`);
|
|
408
|
+
printTree(resolvedPath, "", 0, ignoreSet, colors, chars, stats);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// --- Summary ---
|
|
412
|
+
if (CONFIG.showSummary && CONFIG.displayFormat !== "json") {
|
|
413
|
+
console.log(
|
|
414
|
+
`\n${colors.dim}${stats.dirs} director${stats.dirs === 1 ? "y" : "ies"}, ${stats.files} file${stats.files === 1 ? "" : "s"}${colors.reset}\n`
|
|
415
|
+
);
|
|
416
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "folder-tree-print",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Print a beautiful tree view of any folder structure. Great for LLM context, docs, and sharing project layouts.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"folder-tree-print": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"tree",
|
|
11
|
+
"folder",
|
|
12
|
+
"directory",
|
|
13
|
+
"structure",
|
|
14
|
+
"cli",
|
|
15
|
+
"llm",
|
|
16
|
+
"project-structure",
|
|
17
|
+
"code-context"
|
|
18
|
+
],
|
|
19
|
+
"author": "Rayan Mansoor",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Rayan-Mansoor/Folder-Tree-Print.git"
|
|
24
|
+
}
|
|
25
|
+
}
|