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.
Files changed (3) hide show
  1. package/README.md +159 -0
  2. package/index.js +416 -0
  3. 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
+ }