make-folder-txt 2.1.3 → 2.1.4

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.
@@ -617,7 +617,12 @@ for (let i = 0; i < args.length; i += 1) {
617
617
  if (arg === "--ignore-folder" || arg === "-ifo") {
618
618
  let consumed = 0;
619
619
  while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
620
- ignoreDirs.add(args[i + 1]);
620
+ // Normalize the folder name: remove backslashes, trailing slashes, and leading ./
621
+ let folderName = args[i + 1];
622
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
623
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
624
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
625
+ ignoreDirs.add(folderName);
621
626
  i += 1;
622
627
  consumed += 1;
623
628
  }
@@ -636,7 +641,12 @@ for (let i = 0; i < args.length; i += 1) {
636
641
  console.error("Error: --ignore-folder requires a folder name.");
637
642
  process.exit(1);
638
643
  }
639
- ignoreDirs.add(value);
644
+ // Normalize the folder name
645
+ let folderName = value;
646
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
647
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
648
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
649
+ ignoreDirs.add(folderName);
640
650
  continue;
641
651
  }
642
652
 
@@ -669,7 +679,12 @@ for (let i = 0; i < args.length; i += 1) {
669
679
  if (arg === "--only-folder" || arg === "-ofo") {
670
680
  let consumed = 0;
671
681
  while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
672
- onlyFolders.add(args[i + 1]);
682
+ // Normalize the folder name
683
+ let folderName = args[i + 1];
684
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
685
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
686
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
687
+ onlyFolders.add(folderName);
673
688
  i += 1;
674
689
  consumed += 1;
675
690
  }
@@ -688,7 +703,12 @@ for (let i = 0; i < args.length; i += 1) {
688
703
  console.error("Error: --only-folder requires a folder name.");
689
704
  process.exit(1);
690
705
  }
691
- onlyFolders.add(value);
706
+ // Normalize the folder name
707
+ let folderName = value;
708
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
709
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
710
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
711
+ onlyFolders.add(folderName);
692
712
  continue;
693
713
  }
694
714
 
@@ -8,20 +8,940 @@ PROJECT STRUCTURE
8
8
  Root: C:\Programming\make-folder-txt
9
9
 
10
10
  make-folder-txt/
11
+ ├── .git/ [skipped]
12
+ ├── bin/
13
+ │ └── make-folder-txt.js
14
+ ├── completion/ [skipped]
15
+ ├── LICENSE
11
16
  ├── package.json
17
+ └── README.md
12
18
 
13
- Total files: 1
19
+ Total files: 4
14
20
 
15
21
  ================================================================================
16
22
  FILE CONTENTS
17
23
  ================================================================================
18
24
 
25
+ --------------------------------------------------------------------------------
26
+ FILE: /bin/make-folder-txt.js
27
+ --------------------------------------------------------------------------------
28
+ #!/usr/bin/env node
29
+
30
+ const fs = require("fs");
31
+ const path = require("path");
32
+ const { version } = require("../package.json");
33
+ const { execSync } = require("child_process");
34
+
35
+ // ── config ────────────────────────────────────────────────────────────────────
36
+ const IGNORE_DIRS = new Set(["node_modules", ".git", ".next", "dist", "build", ".cache"]);
37
+ const IGNORE_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".txtignore"]);
38
+
39
+ const BINARY_EXTS = new Set([
40
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg", ".webp",
41
+ ".pdf", ".zip", ".tar", ".gz", ".rar", ".7z",
42
+ ".exe", ".dll", ".so", ".dylib", ".bin",
43
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
44
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
45
+ ".lock",
46
+ ]);
47
+
48
+ // ── helpers ───────────────────────────────────────────────────────────────────
49
+
50
+ function readTxtIgnore(rootDir) {
51
+ const txtIgnorePath = path.join(rootDir, '.txtignore');
52
+ const ignorePatterns = new Set();
53
+
54
+ try {
55
+ const content = fs.readFileSync(txtIgnorePath, 'utf8');
56
+ const lines = content.split('\n')
57
+ .map(line => line.trim())
58
+ .filter(line => line && !line.startsWith('#'));
59
+
60
+ lines.forEach(line => ignorePatterns.add(line));
61
+ } catch (err) {
62
+ // .txtignore doesn't exist or can't be read - that's fine
63
+ }
64
+
65
+ return ignorePatterns;
66
+ }
67
+
68
+ function copyToClipboard(text) {
69
+ try {
70
+ if (process.platform === 'win32') {
71
+ // Windows
72
+ execSync(`echo ${JSON.stringify(text).replace(/"/g, '""')} | clip`, { stdio: 'ignore' });
73
+ } else if (process.platform === 'darwin') {
74
+ // macOS
75
+ execSync(`echo ${JSON.stringify(text)} | pbcopy`, { stdio: 'ignore' });
76
+ } else {
77
+ // Linux (requires xclip or xsel)
78
+ try {
79
+ execSync(`echo ${JSON.stringify(text)} | xclip -selection clipboard`, { stdio: 'ignore' });
80
+ } catch {
81
+ try {
82
+ execSync(`echo ${JSON.stringify(text)} | xsel --clipboard --input`, { stdio: 'ignore' });
83
+ } catch {
84
+ console.warn('⚠️ Could not copy to clipboard. Install xclip or xsel on Linux.');
85
+ return false;
86
+ }
87
+ }
88
+ }
89
+ return true;
90
+ } catch (err) {
91
+ console.warn('⚠️ Could not copy to clipboard:', err.message);
92
+ return false;
93
+ }
94
+ }
95
+
96
+ function collectFiles(
97
+ dir,
98
+ rootDir,
99
+ ignoreDirs,
100
+ ignoreFiles,
101
+ onlyFolders,
102
+ onlyFiles,
103
+ options = {},
104
+ ) {
105
+ const {
106
+ indent = "",
107
+ lines = [],
108
+ filePaths = [],
109
+ inSelectedFolder = false,
110
+ hasOnlyFilters = false,
111
+ rootName = "",
112
+ txtIgnore = new Set(),
113
+ force = false,
114
+ } = options;
115
+
116
+ let entries;
117
+ try {
118
+ entries = fs.readdirSync(dir, { withFileTypes: true });
119
+ } catch {
120
+ return { lines, filePaths, hasIncluded: false };
121
+ }
122
+
123
+ entries.sort((a, b) => {
124
+ if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
125
+ return a.isDirectory() ? -1 : 1;
126
+ });
127
+
128
+ entries.forEach((entry, idx) => {
129
+ const isLast = idx === entries.length - 1;
130
+ const connector = isLast ? "└── " : "├── ";
131
+ const childIndent = indent + (isLast ? " " : "│ ");
132
+
133
+ if (entry.isDirectory()) {
134
+ if (!force && ignoreDirs.has(entry.name)) {
135
+ if (!hasOnlyFilters) {
136
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
137
+ }
138
+ return;
139
+ }
140
+
141
+ // Get relative path for .txtignore pattern matching
142
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
143
+
144
+ // Check against .txtignore patterns (both dirname and relative path) unless force is enabled
145
+ if (!force && (txtIgnore.has(entry.name) || txtIgnore.has(`${entry.name}/`) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`${relPathForIgnore}/`) || txtIgnore.has(`/${relPathForIgnore}/`))) {
146
+ if (!hasOnlyFilters) {
147
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
148
+ }
149
+ return;
150
+ }
151
+
152
+ const childPath = path.join(dir, entry.name);
153
+ const childInSelectedFolder = inSelectedFolder || onlyFolders.has(entry.name);
154
+ const childLines = [];
155
+ const childFiles = [];
156
+ const child = collectFiles(
157
+ childPath,
158
+ rootDir,
159
+ ignoreDirs,
160
+ ignoreFiles,
161
+ onlyFolders,
162
+ onlyFiles,
163
+ {
164
+ indent: childIndent,
165
+ lines: childLines,
166
+ filePaths: childFiles,
167
+ inSelectedFolder: childInSelectedFolder,
168
+ hasOnlyFilters,
169
+ rootName,
170
+ txtIgnore,
171
+ force,
172
+ },
173
+ );
174
+
175
+ const explicitlySelectedFolder = hasOnlyFilters && onlyFolders.has(entry.name);
176
+ const shouldIncludeDir = !hasOnlyFilters || child.hasIncluded || explicitlySelectedFolder;
177
+
178
+ if (shouldIncludeDir) {
179
+ lines.push(`${indent}${connector}${entry.name}/`);
180
+ lines.push(...child.lines);
181
+ filePaths.push(...child.filePaths);
182
+ }
183
+ } else {
184
+ if (!force && ignoreFiles.has(entry.name)) return;
185
+
186
+ // Get relative path for .txtignore pattern matching
187
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
188
+
189
+ // Check against .txtignore patterns (both filename and relative path) unless force is enabled
190
+ if (!force && (txtIgnore.has(entry.name) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`/${relPathForIgnore}`))) {
191
+ return;
192
+ }
193
+
194
+ // Ignore .txt files that match the folder name (e.g., foldername.txt) unless force is enabled
195
+ if (!force && entry.name.endsWith('.txt') && entry.name === `${rootName}.txt`) return;
196
+
197
+ const shouldIncludeFile = !hasOnlyFilters || inSelectedFolder || onlyFiles.has(entry.name);
198
+ if (!shouldIncludeFile) return;
199
+
200
+ lines.push(`${indent}${connector}${entry.name}`);
201
+ const relPath = "/" + path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
202
+ filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
203
+ }
204
+ });
205
+
206
+ return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
207
+ }
208
+
209
+ function parseFileSize(sizeStr) {
210
+ const units = {
211
+ 'B': 1,
212
+ 'KB': 1024,
213
+ 'MB': 1024 * 1024,
214
+ 'GB': 1024 * 1024 * 1024,
215
+ 'TB': 1024 * 1024 * 1024 * 1024
216
+ };
217
+
218
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
219
+ if (!match) {
220
+ console.error(`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`);
221
+ process.exit(1);
222
+ }
223
+
224
+ const value = parseFloat(match[1]);
225
+ const unit = match[2].toUpperCase();
226
+ return Math.floor(value * units[unit]);
227
+ }
228
+
229
+ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
230
+ const ext = path.extname(absPath).toLowerCase();
231
+ if (!force && BINARY_EXTS.has(ext)) return "[binary / skipped]";
232
+ try {
233
+ const stat = fs.statSync(absPath);
234
+ if (!force && stat.size > maxFileSize) {
235
+ const sizeStr = stat.size < 1024 ? `${stat.size} B` :
236
+ stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB` :
237
+ stat.size < 1024 * 1024 * 1024 ? `${(stat.size / (1024 * 1024)).toFixed(1)} MB` :
238
+ `${(stat.size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
239
+ return `[file too large: ${sizeStr} – skipped]`;
240
+ }
241
+ return fs.readFileSync(absPath, "utf8");
242
+ } catch (err) {
243
+ return `[could not read file: ${err.message}]`;
244
+ }
245
+ }
246
+
247
+ function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag) {
248
+ const folders = new Map();
249
+
250
+ // Group files by folder
251
+ filePaths.forEach(({ abs, rel }) => {
252
+ const folderPath = path.dirname(rel);
253
+ const folderKey = folderPath === '/' ? rootName : folderPath.slice(1);
254
+
255
+ if (!folders.has(folderKey)) {
256
+ folders.set(folderKey, []);
257
+ }
258
+ folders.get(folderKey).push({ abs, rel });
259
+ });
260
+
261
+ const results = [];
262
+
263
+ folders.forEach((files, folderName) => {
264
+ const out = [];
265
+ const divider = "=".repeat(80);
266
+ const subDivider = "-".repeat(80);
267
+
268
+ out.push(divider);
269
+ out.push(`START OF FOLDER: ${folderName}`);
270
+ out.push(divider);
271
+ out.push("");
272
+
273
+ // Add folder structure (only this folder's structure)
274
+ const folderTreeLines = treeLines.filter(line =>
275
+ line.includes(folderName + '/') || line === `${rootName}/`
276
+ );
277
+
278
+ out.push(divider);
279
+ out.push("PROJECT STRUCTURE");
280
+ out.push(divider);
281
+ out.push(`Root: ${folderPath}\n`);
282
+ out.push(`${rootName}/`);
283
+ folderTreeLines.forEach(l => out.push(l));
284
+ out.push("");
285
+ out.push(`Total files in this folder: ${files.length}`);
286
+ out.push("");
287
+
288
+ out.push(divider);
289
+ out.push("FILE CONTENTS");
290
+ out.push(divider);
291
+
292
+ files.forEach(({ abs, rel }) => {
293
+ out.push("");
294
+ out.push(subDivider);
295
+ out.push(`FILE: ${rel}`);
296
+ out.push(subDivider);
297
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
298
+ });
299
+
300
+ out.push("");
301
+ out.push(divider);
302
+ out.push(`END OF FOLDER: ${folderName}`);
303
+ out.push(divider);
304
+
305
+ const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, '-')}.txt`;
306
+ const filePath = path.join(process.cwd(), fileName);
307
+
308
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
309
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
310
+
311
+ results.push({
312
+ file: filePath,
313
+ size: sizeKB,
314
+ files: files.length,
315
+ folder: folderName
316
+ });
317
+ });
318
+
319
+ return results;
320
+ }
321
+
322
+ function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
323
+ const results = [];
324
+
325
+ filePaths.forEach(({ abs, rel }) => {
326
+ const out = [];
327
+ const divider = "=".repeat(80);
328
+ const subDivider = "-".repeat(80);
329
+ const fileName = path.basename(rel, path.extname(rel));
330
+
331
+ out.push(divider);
332
+ out.push(`FILE: ${rel}`);
333
+ out.push(divider);
334
+ out.push("");
335
+
336
+ out.push(divider);
337
+ out.push("FILE CONTENTS");
338
+ out.push(divider);
339
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
340
+
341
+ out.push("");
342
+ out.push(divider);
343
+ out.push(`END OF FILE: ${rel}`);
344
+ out.push(divider);
345
+
346
+ const outputFileName = `${rootName}-${fileName}.txt`;
347
+ const filePath = path.join(process.cwd(), outputFileName);
348
+
349
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
350
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
351
+
352
+ results.push({
353
+ file: filePath,
354
+ size: sizeKB,
355
+ files: 1,
356
+ fileName: fileName
357
+ });
358
+ });
359
+
360
+ return results;
361
+ }
362
+
363
+ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag) {
364
+ const results = [];
365
+ let currentPart = 1;
366
+ let currentSize = 0;
367
+ let currentFiles = [];
368
+
369
+ const divider = "=".repeat(80);
370
+ const subDivider = "-".repeat(80);
371
+
372
+ // Start with header
373
+ let out = [];
374
+ out.push(divider);
375
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
376
+ out.push(divider);
377
+ out.push("");
378
+
379
+ out.push(divider);
380
+ out.push("PROJECT STRUCTURE");
381
+ out.push(divider);
382
+ out.push(`Root: ${folderPath}\n`);
383
+ out.push(`${rootName}/`);
384
+ treeLines.forEach(l => out.push(l));
385
+ out.push("");
386
+ out.push(`Total files: ${filePaths.length}`);
387
+ out.push("");
388
+
389
+ out.push(divider);
390
+ out.push("FILE CONTENTS");
391
+ out.push(divider);
392
+
393
+ filePaths.forEach(({ abs, rel }) => {
394
+ const content = readContent(abs, forceFlag, effectiveMaxSize);
395
+ const fileContent = [
396
+ "",
397
+ subDivider,
398
+ `FILE: ${rel}`,
399
+ subDivider,
400
+ content
401
+ ];
402
+
403
+ const contentSize = fileContent.join("\n").length;
404
+
405
+ // Check if adding this file would exceed the split size
406
+ if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
407
+ // Finish current part
408
+ out.push("");
409
+ out.push(divider);
410
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
411
+ out.push(divider);
412
+
413
+ // Write current part
414
+ const fileName = `${rootName}-part-${currentPart}.txt`;
415
+ const filePath = path.join(process.cwd(), fileName);
416
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
417
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
418
+
419
+ results.push({
420
+ file: filePath,
421
+ size: sizeKB,
422
+ files: currentFiles.length,
423
+ part: currentPart
424
+ });
425
+
426
+ // Start new part
427
+ currentPart++;
428
+ currentSize = 0;
429
+ currentFiles = [];
430
+
431
+ out = [];
432
+ out.push(divider);
433
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
434
+ out.push(divider);
435
+ out.push("");
436
+
437
+ out.push(divider);
438
+ out.push("PROJECT STRUCTURE");
439
+ out.push(divider);
440
+ out.push(`Root: ${folderPath}\n`);
441
+ out.push(`${rootName}/`);
442
+ treeLines.forEach(l => out.push(l));
443
+ out.push("");
444
+ out.push(`Total files: ${filePaths.length}`);
445
+ out.push("");
446
+
447
+ out.push(divider);
448
+ out.push("FILE CONTENTS");
449
+ out.push(divider);
450
+ }
451
+
452
+ // Add file to current part
453
+ out.push(...fileContent);
454
+ currentSize += contentSize;
455
+ currentFiles.push(rel);
456
+ });
457
+
458
+ // Write final part
459
+ out.push("");
460
+ out.push(divider);
461
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
462
+ out.push(divider);
463
+
464
+ const fileName = `${rootName}-part-${currentPart}.txt`;
465
+ const filePath = path.join(process.cwd(), fileName);
466
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
467
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
468
+
469
+ results.push({
470
+ file: filePath,
471
+ size: sizeKB,
472
+ files: currentFiles.length,
473
+ part: currentPart
474
+ });
475
+
476
+ return results;
477
+ }
478
+
479
+ // ── main ──────────────────────────────────────────────────────────────────────
480
+
481
+ const args = process.argv.slice(2);
482
+
483
+ if (args.includes("-v") || args.includes("--version")) {
484
+ console.log(`v${version}`);
485
+ console.log("Built by Muhammad Saad Amin");
486
+ process.exit(0);
487
+ }
488
+
489
+ if (args.includes("--help") || args.includes("-h")) {
490
+ console.log(`
491
+ \x1b[33mmake-folder-txt\x1b[0m
492
+ Dump an entire project folder into a single readable .txt file.
493
+
494
+ \x1b[33mUSAGE\x1b[0m
495
+ make-folder-txt [options]
496
+
497
+ \x1b[33mOPTIONS\x1b[0m
498
+ --ignore-folder, -ifo <names...> Ignore specific folders by name
499
+ --ignore-file, -ifi <names...> Ignore specific files by name
500
+ --only-folder, -ofo <names...> Include only specific folders
501
+ --only-file, -ofi <names...> Include only specific files
502
+ --skip-large <size> Skip files larger than specified size (default: 500KB)
503
+ --no-skip Include all files regardless of size
504
+ --split-method <method> Split output: folder, file, or size
505
+ --split-size <size> Split output when size exceeds limit (requires --split-method size)
506
+ --copy Copy output to clipboard
507
+ --force Include everything (overrides all ignore patterns)
508
+ --help, -h Show this help message
509
+ --version, -v Show version information
510
+
511
+ \x1b[33mEXAMPLES\x1b[0m
512
+ make-folder-txt
513
+ make-folder-txt --copy
514
+ make-folder-txt --force
515
+ make-folder-txt --skip-large 400KB
516
+ make-folder-txt --skip-large 5GB
517
+ make-folder-txt --no-skip
518
+ make-folder-txt --split-method folder
519
+ make-folder-txt --split-method file
520
+ make-folder-txt --split-method size --split-size 5MB
521
+ make-folder-txt --ignore-folder node_modules dist
522
+ make-folder-txt -ifo node_modules dist
523
+ make-folder-txt --ignore-file .env .env.local
524
+ make-folder-txt -ifi .env .env.local
525
+ make-folder-txt --only-folder src docs
526
+ make-folder-txt -ofo src docs
527
+ make-folder-txt --only-file package.json README.md
528
+ make-folder-txt -ofi package.json README.md
529
+
530
+ \x1b[33m.TXTIGNORE FILE\x1b[0m
531
+ Create a .txtignore file in your project root to specify files/folders to ignore.
532
+ Works like .gitignore - supports file names, path patterns, and comments.
533
+
534
+ Example .txtignore:
535
+ node_modules/
536
+ *.log
537
+ .env
538
+ coverage/
539
+ LICENSE
540
+ `);
541
+ process.exit(0);
542
+ }
543
+
544
+ const ignoreDirs = new Set(IGNORE_DIRS);
545
+ const ignoreFiles = new Set(IGNORE_FILES);
546
+ const onlyFolders = new Set();
547
+ const onlyFiles = new Set();
548
+ let outputArg = null;
549
+ let copyToClipboardFlag = false;
550
+ let forceFlag = false;
551
+ let maxFileSize = 500 * 1024; // Default 500KB
552
+ let noSkipFlag = false;
553
+ let splitMethod = null; // 'folder', 'file', 'size'
554
+ let splitSize = null; // size in bytes
555
+
556
+ for (let i = 0; i < args.length; i += 1) {
557
+ const arg = args[i];
558
+
559
+ if (arg === "--copy") {
560
+ copyToClipboardFlag = true;
561
+ continue;
562
+ }
563
+
564
+ if (arg === "--force") {
565
+ forceFlag = true;
566
+ continue;
567
+ }
568
+
569
+ if (arg === "--no-skip") {
570
+ noSkipFlag = true;
571
+ continue;
572
+ }
573
+
574
+ if (arg === "--skip-large") {
575
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
576
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
577
+ process.exit(1);
578
+ }
579
+ maxFileSize = parseFileSize(args[i + 1]);
580
+ i += 1;
581
+ continue;
582
+ }
583
+
584
+ if (arg.startsWith("--skip-large=")) {
585
+ const value = arg.slice("--skip-large=".length);
586
+ if (!value) {
587
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
588
+ process.exit(1);
589
+ }
590
+ maxFileSize = parseFileSize(value);
591
+ continue;
592
+ }
593
+
594
+ if (arg === "--split-method") {
595
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
596
+ console.error("Error: --split-method requires a method (folder, file, or size).");
597
+ process.exit(1);
598
+ }
599
+ const method = args[i + 1].toLowerCase();
600
+ if (!['folder', 'file', 'size'].includes(method)) {
601
+ console.error("Error: --split-method must be one of: folder, file, size");
602
+ process.exit(1);
603
+ }
604
+ splitMethod = method;
605
+ i += 1;
606
+ continue;
607
+ }
608
+
609
+ if (arg.startsWith("--split-method=")) {
610
+ const value = arg.slice("--split-method=".length);
611
+ if (!value) {
612
+ console.error("Error: --split-method requires a method (folder, file, or size).");
613
+ process.exit(1);
614
+ }
615
+ const method = value.toLowerCase();
616
+ if (!['folder', 'file', 'size'].includes(method)) {
617
+ console.error("Error: --split-method must be one of: folder, file, size");
618
+ process.exit(1);
619
+ }
620
+ splitMethod = method;
621
+ continue;
622
+ }
623
+
624
+ if (arg === "--split-size") {
625
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
626
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
627
+ process.exit(1);
628
+ }
629
+ splitSize = parseFileSize(args[i + 1]);
630
+ i += 1;
631
+ continue;
632
+ }
633
+
634
+ if (arg.startsWith("--split-size=")) {
635
+ const value = arg.slice("--split-size=".length);
636
+ if (!value) {
637
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
638
+ process.exit(1);
639
+ }
640
+ splitSize = parseFileSize(value);
641
+ continue;
642
+ }
643
+
644
+ if (arg === "--ignore-folder" || arg === "-ifo") {
645
+ let consumed = 0;
646
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
647
+ // Normalize the folder name: remove backslashes, trailing slashes, and leading ./
648
+ let folderName = args[i + 1];
649
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
650
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
651
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
652
+ ignoreDirs.add(folderName);
653
+ i += 1;
654
+ consumed += 1;
655
+ }
656
+ if (consumed === 0) {
657
+ console.error("Error: --ignore-folder requires at least one folder name.");
658
+ process.exit(1);
659
+ }
660
+ continue;
661
+ }
662
+
663
+ if (arg.startsWith("--ignore-folder=") || arg.startsWith("-ifo=")) {
664
+ const value = arg.startsWith("--ignore-folder=")
665
+ ? arg.slice("--ignore-folder=".length)
666
+ : arg.slice("-ifo=".length);
667
+ if (!value) {
668
+ console.error("Error: --ignore-folder requires a folder name.");
669
+ process.exit(1);
670
+ }
671
+ // Normalize the folder name
672
+ let folderName = value;
673
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
674
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
675
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
676
+ ignoreDirs.add(folderName);
677
+ continue;
678
+ }
679
+
680
+ if (arg === "--ignore-file" || arg === "-ifi") {
681
+ let consumed = 0;
682
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
683
+ ignoreFiles.add(args[i + 1]);
684
+ i += 1;
685
+ consumed += 1;
686
+ }
687
+ if (consumed === 0) {
688
+ console.error("Error: --ignore-file requires at least one file name.");
689
+ process.exit(1);
690
+ }
691
+ continue;
692
+ }
693
+
694
+ if (arg.startsWith("--ignore-file=") || arg.startsWith("-ifi=")) {
695
+ const value = arg.startsWith("--ignore-file=")
696
+ ? arg.slice("--ignore-file=".length)
697
+ : arg.slice("-ifi=".length);
698
+ if (!value) {
699
+ console.error("Error: --ignore-file requires a file name.");
700
+ process.exit(1);
701
+ }
702
+ ignoreFiles.add(value);
703
+ continue;
704
+ }
705
+
706
+ if (arg === "--only-folder" || arg === "-ofo") {
707
+ let consumed = 0;
708
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
709
+ // Normalize the folder name
710
+ let folderName = args[i + 1];
711
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
712
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
713
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
714
+ onlyFolders.add(folderName);
715
+ i += 1;
716
+ consumed += 1;
717
+ }
718
+ if (consumed === 0) {
719
+ console.error("Error: --only-folder requires at least one folder name.");
720
+ process.exit(1);
721
+ }
722
+ continue;
723
+ }
724
+
725
+ if (arg.startsWith("--only-folder=") || arg.startsWith("-ofo=")) {
726
+ const value = arg.startsWith("--only-folder=")
727
+ ? arg.slice("--only-folder=".length)
728
+ : arg.slice("-ofo=".length);
729
+ if (!value) {
730
+ console.error("Error: --only-folder requires a folder name.");
731
+ process.exit(1);
732
+ }
733
+ // Normalize the folder name
734
+ let folderName = value;
735
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
736
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
737
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
738
+ onlyFolders.add(folderName);
739
+ continue;
740
+ }
741
+
742
+ if (arg === "--only-file" || arg === "-ofi") {
743
+ let consumed = 0;
744
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
745
+ onlyFiles.add(args[i + 1]);
746
+ i += 1;
747
+ consumed += 1;
748
+ }
749
+ if (consumed === 0) {
750
+ console.error("Error: --only-file requires at least one file name.");
751
+ process.exit(1);
752
+ }
753
+ continue;
754
+ }
755
+
756
+ if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
757
+ const value = arg.startsWith("--only-file=")
758
+ ? arg.slice("--only-file=".length)
759
+ : arg.slice("-ofi=".length);
760
+ if (!value) {
761
+ console.error("Error: --only-file requires a file name.");
762
+ process.exit(1);
763
+ }
764
+ onlyFiles.add(value);
765
+ continue;
766
+ }
767
+
768
+ if (arg.startsWith("-")) {
769
+ console.error(`Error: Unknown option "${arg}".`);
770
+ process.exit(1);
771
+ }
772
+
773
+ if (!outputArg) {
774
+ outputArg = arg;
775
+ continue;
776
+ }
777
+
778
+ console.error(`Error: Unexpected argument "${arg}".`);
779
+ process.exit(1);
780
+ }
781
+
782
+ // Validate split options
783
+ if (splitMethod === 'size' && !splitSize) {
784
+ console.error("Error: --split-method size requires --split-size to be specified.");
785
+ process.exit(1);
786
+ }
787
+
788
+ if (splitSize && splitMethod !== 'size') {
789
+ console.error("Error: --split-size can only be used with --split-method size.");
790
+ process.exit(1);
791
+ }
792
+
793
+ const folderPath = process.cwd();
794
+ const rootName = path.basename(folderPath);
795
+
796
+ // Read .txtignore file if it exists
797
+ const txtIgnore = readTxtIgnore(folderPath);
798
+
799
+ const outputFile = outputArg
800
+ ? path.resolve(outputArg)
801
+ : path.join(process.cwd(), `${rootName}.txt`);
802
+
803
+ console.log(`\n📂 Scanning: ${folderPath}`);
804
+
805
+ const hasOnlyFilters = onlyFolders.size > 0 || onlyFiles.size > 0;
806
+ const { lines: treeLines, filePaths } = collectFiles(
807
+ folderPath,
808
+ folderPath,
809
+ ignoreDirs,
810
+ ignoreFiles,
811
+ onlyFolders,
812
+ onlyFiles,
813
+ { hasOnlyFilters, rootName, txtIgnore, force: forceFlag },
814
+ );
815
+
816
+ // ── handle splitting ──────────────────────────────────────────────────────────────
817
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
818
+
819
+ if (splitMethod) {
820
+ console.log(`🔧 Splitting output by: ${splitMethod}`);
821
+
822
+ let results;
823
+
824
+ if (splitMethod === 'folder') {
825
+ results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
826
+ } else if (splitMethod === 'file') {
827
+ results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
828
+ } else if (splitMethod === 'size') {
829
+ results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
830
+ }
831
+
832
+ console.log(`✅ Done! Created ${results.length} split files:`);
833
+ console.log('');
834
+
835
+ results.forEach((result, index) => {
836
+ if (splitMethod === 'folder') {
837
+ console.log(`📁 Folder: ${result.folder}`);
838
+ } else if (splitMethod === 'file') {
839
+ console.log(`📄 File: ${result.fileName}`);
840
+ } else if (splitMethod === 'size') {
841
+ console.log(`📦 Part ${result.part}`);
842
+ }
843
+ console.log(`📄 Output : ${result.file}`);
844
+ console.log(`📊 Size : ${result.size} KB`);
845
+ console.log(`🗂️ Files : ${result.files}`);
846
+ console.log('');
847
+ });
848
+
849
+ if (copyToClipboardFlag) {
850
+ console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
851
+ }
852
+
853
+ process.exit(0);
854
+ }
855
+
856
+ // ── build output (no splitting) ───────────────────────────────────────────────────
857
+ const out = [];
858
+ const divider = "=".repeat(80);
859
+ const subDivider = "-".repeat(80);
860
+
861
+ out.push(divider);
862
+ out.push(`START OF FOLDER: ${rootName}`);
863
+ out.push(divider);
864
+ out.push("");
865
+
866
+ out.push(divider);
867
+ out.push("PROJECT STRUCTURE");
868
+ out.push(divider);
869
+ out.push(`Root: ${folderPath}\n`);
870
+ out.push(`${rootName}/`);
871
+ treeLines.forEach(l => out.push(l));
872
+ out.push("");
873
+ out.push(`Total files: ${filePaths.length}`);
874
+ out.push("");
875
+
876
+ out.push(divider);
877
+ out.push("FILE CONTENTS");
878
+ out.push(divider);
879
+
880
+ filePaths.forEach(({ abs, rel }) => {
881
+ out.push("");
882
+ out.push(subDivider);
883
+ out.push(`FILE: ${rel}`);
884
+ out.push(subDivider);
885
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
886
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
887
+ });
888
+
889
+ out.push("");
890
+ out.push(divider);
891
+ out.push(`END OF FOLDER: ${rootName}`);
892
+ out.push(divider);
893
+
894
+ fs.writeFileSync(outputFile, out.join("\n"), "utf8");
895
+
896
+ const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
897
+ console.log(`✅ Done!`);
898
+ console.log(`📄 Output : ${outputFile}`);
899
+ console.log(`📊 Size : ${sizeKB} KB`);
900
+ console.log(`🗂️ Files : ${filePaths.length}`);
901
+
902
+ if (copyToClipboardFlag) {
903
+ const content = fs.readFileSync(outputFile, 'utf8');
904
+ const success = copyToClipboard(content);
905
+ if (success) {
906
+ console.log(`📋 Copied to clipboard!`);
907
+ }
908
+ }
909
+
910
+ console.log('');
911
+
912
+
913
+ --------------------------------------------------------------------------------
914
+ FILE: /LICENSE
915
+ --------------------------------------------------------------------------------
916
+ MIT License
917
+
918
+ Copyright (c) 2026 Muhammad Saad Amin @SENODROOM
919
+
920
+ Permission is hereby granted, free of charge, to any person obtaining a copy
921
+ of this software and associated documentation files (the "Software"), to deal
922
+ in the Software without restriction, including without limitation the rights
923
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
924
+ copies of the Software, and to permit persons to whom the Software is
925
+ furnished to do so, subject to the following conditions:
926
+
927
+ The above copyright notice and this permission notice shall be included in all
928
+ copies or substantial portions of the Software.
929
+
930
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
931
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
932
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
933
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
934
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
935
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
936
+ SOFTWARE.
937
+
938
+
19
939
  --------------------------------------------------------------------------------
20
940
  FILE: /package.json
21
941
  --------------------------------------------------------------------------------
22
942
  {
23
943
  "name": "make-folder-txt",
24
- "version": "2.1.2",
944
+ "version": "2.1.3",
25
945
  "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
26
946
  "main": "bin/make-folder-txt.js",
27
947
  "bin": {
@@ -47,6 +967,343 @@ FILE: /package.json
47
967
  }
48
968
 
49
969
 
970
+ --------------------------------------------------------------------------------
971
+ FILE: /README.md
972
+ --------------------------------------------------------------------------------
973
+ <div align="center">
974
+
975
+ # 📁 make-folder-txt
976
+
977
+ **Instantly dump your entire project into a single, readable `.txt` file.**
978
+
979
+ [![npm version](https://img.shields.io/npm/v/make-folder-txt?color=crimson&style=flat-square)](https://www.npmjs.com/package/make-folder-txt)
980
+ [![npm downloads](https://img.shields.io/npm/dm/make-folder-txt?color=orange&style=flat-square)](https://www.npmjs.com/package/make-folder-txt)
981
+ [![license](https://img.shields.io/npm/l/make-folder-txt?color=blue&style=flat-square)](./LICENSE)
982
+ [![node](https://img.shields.io/node/v/make-folder-txt?color=green&style=flat-square)](https://nodejs.org)
983
+
984
+ Perfect for sharing your codebase with **AI tools**, **teammates**, or **code reviewers** — without zipping files or giving repo access.
985
+
986
+ [Installation](#-installation) · [Usage](#-usage) · [Help](#-get-help) · [Copy to Clipboard](#-copy-to-clipboard) · [Force Include Everything](#-force-include-everything) · [File Size Control](#-file-size-control) · [Output Splitting](#-output-splitting) · [Output Format](#-output-format) · [What Gets Skipped](#-what-gets-skipped) · [Contributing](#-contributing)
987
+
988
+ </div>
989
+
990
+ ---
991
+
992
+ ## ✨ Why make-folder-txt?
993
+
994
+ Ever needed to share your entire project with ChatGPT, Claude, or a teammate — but copy-pasting every file one by one is painful? **make-folder-txt** solves that in one command.
995
+
996
+ - ✅ Run it from any project directory — no arguments needed
997
+ - ✅ Built-in help system with `--help` flag
998
+ - ✅ File size control with `--skip-large` and `--no-skip`
999
+ - ✅ Output splitting by folders, files, or size
1000
+ - ✅ Copy to clipboard with `--copy` flag
1001
+ - ✅ Force include everything with `--force` flag
1002
+ - ✅ Generates a clean folder tree + every file's content
1003
+ - ✅ `.txtignore` support (works like `.gitignore`)
1004
+ - ✅ Automatically skips `node_modules`, binaries, and junk files
1005
+ - ✅ Zero dependencies — pure Node.js
1006
+ - ✅ Works on Windows, macOS, and Linux
1007
+
1008
+ ---
1009
+
1010
+ ## 📦 Installation
1011
+
1012
+ Install globally once, use anywhere:
1013
+
1014
+ ```bash
1015
+ npm install -g make-folder-txt
1016
+ ```
1017
+
1018
+ ---
1019
+
1020
+ ## 🚀 Usage
1021
+
1022
+ Navigate into your project folder and run:
1023
+
1024
+ ```bash
1025
+ cd my-project
1026
+ make-folder-txt
1027
+ ```
1028
+
1029
+ That's it. A `my-project.txt` file will be created in the same directory.
1030
+
1031
+ ### 📖 Get Help
1032
+
1033
+ ```bash
1034
+ make-folder-txt --help # Show all options and examples
1035
+ make-folder-txt -h # Short version of help
1036
+ make-folder-txt --version # Show version info
1037
+ make-folder-txt -v # Short version of version
1038
+ ```
1039
+
1040
+ ### 📋 Copy to Clipboard
1041
+
1042
+ ```bash
1043
+ make-folder-txt --copy # Generate output and copy to clipboard
1044
+ make-folder-txt --copy --ignore-folder node_modules # Copy filtered output
1045
+ ```
1046
+
1047
+ The `--copy` flag automatically copies the generated output to your system clipboard, making it easy to paste directly into AI tools, emails, or documents. Works on Windows, macOS, and Linux (requires `xclip` or `xsel` on Linux).
1048
+
1049
+ ### 🔥 Force Include Everything
1050
+
1051
+ ```bash
1052
+ make-folder-txt --force # Include everything (overrides all ignore patterns)
1053
+ make-folder-txt --force --copy # Include everything and copy to clipboard
1054
+ ```
1055
+
1056
+ The `--force` flag overrides all ignore patterns and includes:
1057
+ - `node_modules` and other ignored folders
1058
+ - Binary files (images, executables, etc.)
1059
+ - Large files (no 500 KB limit)
1060
+ - Files in `.txtignore`
1061
+ - System files and other normally skipped content
1062
+
1063
+ Use this when you need a complete, unfiltered dump of your entire project.
1064
+
1065
+ ### 📏 File Size Control
1066
+
1067
+ ```bash
1068
+ make-folder-txt --skip-large 400KB # Skip files larger than 400KB
1069
+ make-folder-txt --skip-large 5GB # Skip files larger than 5GB
1070
+ make-folder-txt --skip-large 1.5MB # Skip files larger than 1.5MB
1071
+ make-folder-txt --no-skip # Include all files regardless of size
1072
+ ```
1073
+
1074
+ **Default behavior**: Files larger than 500KB are skipped by default.
1075
+
1076
+ **Supported size units**:
1077
+ - **B** - Bytes
1078
+ - **KB** - Kilobytes (1024 bytes)
1079
+ - **MB** - Megabytes (1024 KB)
1080
+ - **GB** - Gigabytes (1024 MB)
1081
+ - **TB** - Terabytes (1024 GB)
1082
+
1083
+ **Examples:**
1084
+ ```bash
1085
+ # More restrictive - skip anything over 100KB
1086
+ make-folder-txt --skip-large 100KB
1087
+
1088
+ # More permissive - allow files up to 10MB
1089
+ make-folder-txt --skip-large 10MB
1090
+
1091
+ # Include everything - no size limits
1092
+ make-folder-txt --no-skip
1093
+
1094
+ # Combine with other options
1095
+ make-folder-txt --skip-large 2MB --ignore-folder node_modules
1096
+ ```
1097
+
1098
+ **Size format**: Accepts decimal numbers (e.g., `1.5MB`, `0.5GB`) and various units.
1099
+
1100
+ ### 📂 Output Splitting
1101
+
1102
+ ```bash
1103
+ make-folder-txt --split-method folder # Split by folders
1104
+ make-folder-txt --split-method file # Split by files
1105
+ make-folder-txt --split-method size --split-size 5MB # Split by file size
1106
+ ```
1107
+
1108
+ **Split Methods:**
1109
+ - **`folder`** - Creates separate files for each folder
1110
+ - **`file`** - Creates separate files for each individual file
1111
+ - **`size`** - Splits output when content exceeds specified size
1112
+
1113
+ **Examples:**
1114
+ ```bash
1115
+ # Split by folders - creates folder-name.txt for each folder
1116
+ make-folder-txt --split-method folder
1117
+
1118
+ # Split by files - creates filename.txt for each file
1119
+ make-folder-txt --split-method file
1120
+
1121
+ # Split by size - creates part-1.txt, part-2.txt, etc.
1122
+ make-folder-txt --split-method size --split-size 5MB
1123
+
1124
+ # Combine with other options
1125
+ make-folder-txt --split-method size --split-size 2MB --ignore-folder node_modules
1126
+ ```
1127
+
1128
+ **Output Files:**
1129
+ - **Folder method**: `projectname-foldername.txt`
1130
+ - **File method**: `projectname-filename.txt`
1131
+ - **Size method**: `projectname-part-1.txt`, `projectname-part-2.txt`, etc.
1132
+
1133
+ **Note**: Splitting is not compatible with `--copy` flag.
1134
+
1135
+ Ignore specific folders/files by name:
1136
+
1137
+ ```bash
1138
+ make-folder-txt --ignore-folder examples extensions docs
1139
+ make-folder-txt -ifo examples extensions docs # shorthand
1140
+ make-folder-txt --ignore-folder examples extensions "docs and explaination"
1141
+ make-folder-txt --ignore-folder examples extensions docs --ignore-file LICENSE
1142
+ make-folder-txt --ignore-file .env .env.local secrets.txt
1143
+ make-folder-txt -ifi .env .env.local secrets.txt # shorthand
1144
+ ```
1145
+
1146
+ Use a `.txtignore` file (works like `.gitignore`):
1147
+
1148
+ ```bash
1149
+ # Create a .txtignore file in your project root
1150
+ echo "node_modules/" > .txtignore
1151
+ echo "*.log" >> .txtignore
1152
+ echo ".env" >> .txtignore
1153
+ echo "coverage/" >> .txtignore
1154
+
1155
+ # The tool will automatically read and respect .txtignore patterns
1156
+ make-folder-txt
1157
+ ```
1158
+
1159
+ The `.txtignore` file supports:
1160
+ - File and folder names (one per line)
1161
+ - Wildcard patterns (`*.log`, `temp-*`)
1162
+ - Comments (lines starting with `#`)
1163
+ - Folder patterns with trailing slash (`docs/`)
1164
+
1165
+ Include only specific folders/files by name (everything else is ignored):
1166
+
1167
+ ```bash
1168
+ make-folder-txt --only-folder src docs
1169
+ make-folder-txt -ofo src docs # shorthand
1170
+ make-folder-txt --only-file package.json README.md
1171
+ make-folder-txt -ofi package.json README.md # shorthand
1172
+ make-folder-txt --only-folder src --only-file package.json
1173
+ ```
1174
+
1175
+ ---
1176
+
1177
+ ## 🎯 Real World Examples
1178
+
1179
+ **Sharing with an AI tool (ChatGPT, Claude, etc.):**
1180
+
1181
+ ```bash
1182
+ cd "C:\Web Development\my-app\backend"
1183
+ make-folder-txt
1184
+ # → backend.txt created, ready to paste into any AI chat
1185
+ ```
1186
+
1187
+ **On macOS / Linux:**
1188
+
1189
+ ```bash
1190
+ cd /home/user/projects/my-app
1191
+ make-folder-txt
1192
+ # → my-app.txt created
1193
+ ```
1194
+
1195
+ ---
1196
+
1197
+ ## 📄 Output Format
1198
+
1199
+ The generated `.txt` file is structured in two clear sections:
1200
+
1201
+ ```
1202
+ ================================================================================
1203
+ START OF FOLDER: my-project
1204
+ ================================================================================
1205
+
1206
+ ================================================================================
1207
+ PROJECT STRUCTURE
1208
+ ================================================================================
1209
+ Root: C:\Web Development\my-project
1210
+
1211
+ my-project/
1212
+ ├── src/
1213
+ │ ├── controllers/
1214
+ │ │ └── userController.js
1215
+ │ ├── models/
1216
+ │ │ └── User.js
1217
+ │ └── index.js
1218
+ ├── node_modules/ [skipped]
1219
+ ├── package.json
1220
+ └── README.md
1221
+
1222
+ Total files: 5
1223
+
1224
+ ================================================================================
1225
+ FILE CONTENTS
1226
+ ================================================================================
1227
+
1228
+ --------------------------------------------------------------------------------
1229
+ FILE: /src/index.js
1230
+ --------------------------------------------------------------------------------
1231
+ const express = require('express');
1232
+ ...
1233
+
1234
+ --------------------------------------------------------------------------------
1235
+ FILE: /package.json
1236
+ --------------------------------------------------------------------------------
1237
+ {
1238
+ "name": "my-project",
1239
+ ...
1240
+ }
1241
+
1242
+ ================================================================================
1243
+ END OF FOLDER: my-project
1244
+ ================================================================================
1245
+ ```
1246
+
1247
+ ---
1248
+
1249
+ ## 🚫 What Gets Skipped
1250
+
1251
+ The tool is smart about what it ignores so your output stays clean and readable.
1252
+
1253
+ | Category | Details |
1254
+ | --------------- | -------------------------------------------------------------- |
1255
+ | 📁 Folders | `node_modules`, `.git`, `.next`, `dist`, `build`, `.cache` |
1256
+ | 🖼️ Binary files | Images (`.png`, `.jpg`, `.gif`...), fonts, videos, executables |
1257
+ | 📦 Archives | `.zip`, `.tar`, `.gz`, `.rar`, `.7z` |
1258
+ | 🔤 Font files | `.woff`, `.woff2`, `.ttf`, `.eot`, `.otf` |
1259
+ | 📋 Lock files | `package-lock.json`, `yarn.lock` |
1260
+ | 📏 Large files | Any file over **500 KB** |
1261
+ | 🗑️ System files | `.DS_Store`, `Thumbs.db`, `desktop.ini` |
1262
+ | 📄 Output file | The generated `foldername.txt` file (to avoid infinite loops) |
1263
+ | 📝 .txtignore | Any files/folders specified in `.txtignore` file |
1264
+
1265
+ Binary and skipped files are noted in the output as `[binary / skipped]` so you always know what was omitted.
1266
+
1267
+ ---
1268
+
1269
+ ## 🛠️ Requirements
1270
+
1271
+ - **Node.js** v14.0.0 or higher
1272
+ - No other dependencies
1273
+
1274
+ ---
1275
+
1276
+ ## 🤝 Contributing
1277
+
1278
+ Contributions, issues, and feature requests are welcome!
1279
+
1280
+ 1. Fork the repository
1281
+ 2. Create your feature branch: `git checkout -b feature/my-feature`
1282
+ 3. Commit your changes: `git commit -m 'Add my feature'`
1283
+ 4. Push to the branch: `git push origin feature/my-feature`
1284
+ 5. Open a Pull Request
1285
+
1286
+ ---
1287
+
1288
+ ## 👤 Author
1289
+
1290
+ **Muhammad Saad Amin**
1291
+
1292
+ ---
1293
+
1294
+ ## 📝 License
1295
+
1296
+ This project is licensed under the **MIT License** — feel free to use it in personal and commercial projects.
1297
+
1298
+ ---
1299
+
1300
+ <div align="center">
1301
+
1302
+ If this tool saved you time, consider giving it a ⭐ on npm!
1303
+
1304
+ </div>
1305
+
1306
+
50
1307
  ================================================================================
51
1308
  END OF FOLDER: make-folder-txt
52
1309
  ================================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-folder-txt",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
5
5
  "main": "bin/make-folder-txt.js",
6
6
  "bin": {