make-folder-txt 2.2.3 → 2.2.5

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.
@@ -643,7 +643,8 @@ function createInteractiveConfig() {
643
643
  if (config.addToTxtIgnore && (config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)) {
644
644
  console.log(`šŸ“ Ignore patterns added to .txtignore`);
645
645
  }
646
- console.log('\nšŸ’” You can now run: make-folder-txt --use-config');
646
+ console.log('\nšŸ’” Your settings will now be used automatically!');
647
+ console.log('šŸ”„ Run --delete-config to reset to defaults');
647
648
 
648
649
  } catch (err) {
649
650
  console.error('āŒ Error saving configuration:', err.message);
@@ -692,7 +693,31 @@ async function main() {
692
693
  process.exit(0);
693
694
  }
694
695
 
695
- if (args.includes("--use-config")) {
696
+ if (args.includes("--delete-config")) {
697
+ try {
698
+ const fs = require('fs');
699
+ const path = require('path');
700
+ const configPath = path.join(process.cwd(), '.txtconfig');
701
+
702
+ if (fs.existsSync(configPath)) {
703
+ fs.unlinkSync(configPath);
704
+ console.log('āœ… Configuration file deleted successfully!');
705
+ console.log('šŸ”„ Tool will now use default settings');
706
+ } else {
707
+ console.log('ā„¹ļø No configuration file found - already using defaults');
708
+ }
709
+ } catch (err) {
710
+ console.error('āŒ Error deleting configuration:', err.message);
711
+ }
712
+ process.exit(0);
713
+ }
714
+
715
+ // Auto-use config if it exists and no other flags are provided
716
+ const configPath = path.join(process.cwd(), '.txtconfig');
717
+ const hasConfig = fs.existsSync(configPath);
718
+ const hasOtherFlags = args.length > 0 && !args.includes('--help') && !args.includes('-h') && !args.includes('--version') && !args.includes('-v');
719
+
720
+ if (hasConfig && !hasOtherFlags) {
696
721
  const config = loadConfig();
697
722
 
698
723
  // Apply config to command line arguments
@@ -718,6 +743,7 @@ async function main() {
718
743
 
719
744
  // Replace args with config-based args
720
745
  args.splice(0, args.length, ...newArgs);
746
+ console.log('šŸ”§ Using saved configuration (use --delete-config to reset to defaults)');
721
747
  }
722
748
 
723
749
  if (args.includes("--help") || args.includes("-h")) {
@@ -740,7 +766,7 @@ Dump an entire project folder into a single readable .txt file.
740
766
  --copy Copy output to clipboard
741
767
  --force Include everything (overrides all ignore patterns)
742
768
  --make-config Create interactive configuration
743
- --use-config Use configuration from .txtconfig file
769
+ --delete-config Delete configuration and reset to defaults
744
770
  --help, -h Show this help message
745
771
  --version, -v Show version information
746
772
 
@@ -763,7 +789,7 @@ Dump an entire project folder into a single readable .txt file.
763
789
  make-folder-txt --only-file package.json README.md
764
790
  make-folder-txt -ofi package.json README.md
765
791
  make-folder-txt --make-config
766
- make-folder-txt --use-config
792
+ make-folder-txt --delete-config
767
793
 
768
794
  \x1b[33m.TXTIGNORE FILE\x1b[0m
769
795
  Create a .txtignore file in your project root to specify files/folders to ignore.
@@ -9,26 +9,1195 @@ Root: C:\Programming\make-folder-txt
9
9
 
10
10
  make-folder-txt/
11
11
  ā”œā”€ā”€ .git/ [skipped]
12
- ā”œā”€ā”€ bin/ [skipped]
13
- ā”œā”€ā”€ .txtconfig
12
+ ā”œā”€ā”€ bin/
13
+ │ └── make-folder-txt.js
14
14
  ā”œā”€ā”€ LICENSE
15
+ ā”œā”€ā”€ package.json
15
16
  └── README.md
16
17
 
17
- Total files: 3
18
+ Total files: 4
18
19
 
19
20
  ================================================================================
20
21
  FILE CONTENTS
21
22
  ================================================================================
22
23
 
23
24
  --------------------------------------------------------------------------------
24
- FILE: /.txtconfig
25
+ FILE: /bin/make-folder-txt.js
25
26
  --------------------------------------------------------------------------------
26
- {
27
- "maxFileSize": "500KB",
28
- "splitMethod": "none",
29
- "splitSize": "5MB",
30
- "copyToClipboard": true
31
- }
27
+ #!/usr/bin/env node
28
+
29
+ const fs = require("fs");
30
+ const path = require("path");
31
+ const { version } = require("../package.json");
32
+ const { execSync } = require("child_process");
33
+
34
+ // ── config ────────────────────────────────────────────────────────────────────
35
+ const IGNORE_DIRS = new Set(["node_modules", ".git", ".next", "dist", "build", ".cache"]);
36
+ const IGNORE_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".txtignore"]);
37
+
38
+ const BINARY_EXTS = new Set([
39
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg", ".webp",
40
+ ".pdf", ".zip", ".tar", ".gz", ".rar", ".7z",
41
+ ".exe", ".dll", ".so", ".dylib", ".bin",
42
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
43
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
44
+ ".lock",
45
+ ]);
46
+
47
+ // ── helpers ───────────────────────────────────────────────────────────────────
48
+
49
+ function readTxtIgnore(rootDir) {
50
+ const txtIgnorePath = path.join(rootDir, '.txtignore');
51
+ const ignorePatterns = new Set();
52
+
53
+ try {
54
+ const content = fs.readFileSync(txtIgnorePath, 'utf8');
55
+ const lines = content.split('\n')
56
+ .map(line => line.trim())
57
+ .filter(line => line && !line.startsWith('#'));
58
+
59
+ lines.forEach(line => ignorePatterns.add(line));
60
+ } catch (err) {
61
+ // .txtignore doesn't exist or can't be read - that's fine
62
+ }
63
+
64
+ return ignorePatterns;
65
+ }
66
+
67
+ function copyToClipboard(text) {
68
+ try {
69
+ if (process.platform === 'win32') {
70
+ // Windows - use PowerShell for better handling of large content
71
+ const tempFile = require('os').tmpdir() + '\\make-folder-txt-clipboard-temp.txt';
72
+ require('fs').writeFileSync(tempFile, text, 'utf8');
73
+ execSync(`powershell -Command "Get-Content '${tempFile}' | Set-Clipboard"`, { stdio: 'ignore' });
74
+ require('fs').unlinkSync(tempFile);
75
+ } else if (process.platform === 'darwin') {
76
+ // macOS
77
+ execSync(`echo ${JSON.stringify(text)} | pbcopy`, { stdio: 'ignore' });
78
+ } else {
79
+ // Linux (requires xclip or xsel)
80
+ try {
81
+ execSync(`echo ${JSON.stringify(text)} | xclip -selection clipboard`, { stdio: 'ignore' });
82
+ } catch {
83
+ try {
84
+ execSync(`echo ${JSON.stringify(text)} | xsel --clipboard --input`, { stdio: 'ignore' });
85
+ } catch {
86
+ console.warn('āš ļø Could not copy to clipboard. Install xclip or xsel on Linux.');
87
+ return false;
88
+ }
89
+ }
90
+ }
91
+ return true;
92
+ } catch (err) {
93
+ console.warn('āš ļø Could not copy to clipboard: ' + err.message);
94
+ return false;
95
+ }
96
+ }
97
+
98
+ function collectFiles(
99
+ dir,
100
+ rootDir,
101
+ ignoreDirs,
102
+ ignoreFiles,
103
+ onlyFolders,
104
+ onlyFiles,
105
+ options = {},
106
+ ) {
107
+ const {
108
+ indent = "",
109
+ lines = [],
110
+ filePaths = [],
111
+ inSelectedFolder = false,
112
+ hasOnlyFilters = false,
113
+ rootName = "",
114
+ txtIgnore = new Set(),
115
+ force = false,
116
+ } = options;
117
+
118
+ let entries;
119
+ try {
120
+ entries = fs.readdirSync(dir, { withFileTypes: true });
121
+ } catch {
122
+ return { lines, filePaths, hasIncluded: false };
123
+ }
124
+
125
+ entries.sort((a, b) => {
126
+ if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
127
+ return a.isDirectory() ? -1 : 1;
128
+ });
129
+
130
+ entries.forEach((entry, idx) => {
131
+ const isLast = idx === entries.length - 1;
132
+ const connector = isLast ? "└── " : "ā”œā”€ā”€ ";
133
+ const childIndent = indent + (isLast ? " " : "│ ");
134
+
135
+ if (entry.isDirectory()) {
136
+ if (!force && ignoreDirs.has(entry.name)) {
137
+ if (!hasOnlyFilters) {
138
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
139
+ }
140
+ return;
141
+ }
142
+
143
+ // Get relative path for .txtignore pattern matching
144
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
145
+
146
+ // Check against .txtignore patterns (both dirname and relative path) unless force is enabled
147
+ if (!force && (txtIgnore.has(entry.name) || txtIgnore.has(`${entry.name}/`) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`${relPathForIgnore}/`) || txtIgnore.has(`/${relPathForIgnore}/`))) {
148
+ if (!hasOnlyFilters) {
149
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
150
+ }
151
+ return;
152
+ }
153
+
154
+ const childPath = path.join(dir, entry.name);
155
+ const childInSelectedFolder = inSelectedFolder || onlyFolders.has(entry.name);
156
+ const childLines = [];
157
+ const childFiles = [];
158
+ const child = collectFiles(
159
+ childPath,
160
+ rootDir,
161
+ ignoreDirs,
162
+ ignoreFiles,
163
+ onlyFolders,
164
+ onlyFiles,
165
+ {
166
+ indent: childIndent,
167
+ lines: childLines,
168
+ filePaths: childFiles,
169
+ inSelectedFolder: childInSelectedFolder,
170
+ hasOnlyFilters,
171
+ rootName,
172
+ txtIgnore,
173
+ force,
174
+ },
175
+ );
176
+
177
+ const explicitlySelectedFolder = hasOnlyFilters && onlyFolders.has(entry.name);
178
+ const shouldIncludeDir = !hasOnlyFilters || child.hasIncluded || explicitlySelectedFolder;
179
+
180
+ if (shouldIncludeDir) {
181
+ lines.push(`${indent}${connector}${entry.name}/`);
182
+ lines.push(...child.lines);
183
+ filePaths.push(...child.filePaths);
184
+ }
185
+ } else {
186
+ if (!force && ignoreFiles.has(entry.name)) return;
187
+
188
+ // Get relative path for .txtignore pattern matching
189
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
190
+
191
+ // Check against .txtignore patterns (both filename and relative path) unless force is enabled
192
+ if (!force && (txtIgnore.has(entry.name) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`/${relPathForIgnore}`))) {
193
+ return;
194
+ }
195
+
196
+ // Ignore .txt files that match the folder name (e.g., foldername.txt) unless force is enabled
197
+ if (!force && entry.name.endsWith('.txt') && entry.name === `${rootName}.txt`) return;
198
+
199
+ const shouldIncludeFile = !hasOnlyFilters || inSelectedFolder || onlyFiles.has(entry.name);
200
+ if (!shouldIncludeFile) return;
201
+
202
+ lines.push(`${indent}${connector}${entry.name}`);
203
+ const relPath = "/" + path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
204
+ filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
205
+ }
206
+ });
207
+
208
+ return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
209
+ }
210
+
211
+ function parseFileSize(sizeStr) {
212
+ const units = {
213
+ 'B': 1,
214
+ 'KB': 1024,
215
+ 'MB': 1024 * 1024,
216
+ 'GB': 1024 * 1024 * 1024,
217
+ 'TB': 1024 * 1024 * 1024 * 1024
218
+ };
219
+
220
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
221
+ if (!match) {
222
+ console.error(`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`);
223
+ process.exit(1);
224
+ }
225
+
226
+ const value = parseFloat(match[1]);
227
+ const unit = match[2].toUpperCase();
228
+ return Math.floor(value * units[unit]);
229
+ }
230
+
231
+ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
232
+ const ext = path.extname(absPath).toLowerCase();
233
+ if (!force && BINARY_EXTS.has(ext)) return "[binary / skipped]";
234
+ try {
235
+ const stat = fs.statSync(absPath);
236
+ if (!force && stat.size > maxFileSize) {
237
+ const sizeStr = stat.size < 1024 ? `${stat.size} B` :
238
+ stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB` :
239
+ stat.size < 1024 * 1024 * 1024 ? `${(stat.size / (1024 * 1024)).toFixed(1)} MB` :
240
+ `${(stat.size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
241
+ return `[file too large: ${sizeStr} – skipped]`;
242
+ }
243
+ return fs.readFileSync(absPath, "utf8");
244
+ } catch (err) {
245
+ return `[could not read file: ${err.message}]`;
246
+ }
247
+ }
248
+
249
+ function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag) {
250
+ const folders = new Map();
251
+
252
+ // Group files by folder
253
+ filePaths.forEach(({ abs, rel }) => {
254
+ const folderPath = path.dirname(rel);
255
+ const folderKey = folderPath === '/' ? rootName : folderPath.slice(1);
256
+
257
+ if (!folders.has(folderKey)) {
258
+ folders.set(folderKey, []);
259
+ }
260
+ folders.get(folderKey).push({ abs, rel });
261
+ });
262
+
263
+ const results = [];
264
+
265
+ folders.forEach((files, folderName) => {
266
+ const out = [];
267
+ const divider = "=".repeat(80);
268
+ const subDivider = "-".repeat(80);
269
+
270
+ out.push(divider);
271
+ out.push(`START OF FOLDER: ${folderName}`);
272
+ out.push(divider);
273
+ out.push("");
274
+
275
+ // Add folder structure (only this folder's structure)
276
+ const folderTreeLines = treeLines.filter(line =>
277
+ line.includes(folderName + '/') || line === `${rootName}/`
278
+ );
279
+
280
+ out.push(divider);
281
+ out.push("PROJECT STRUCTURE");
282
+ out.push(divider);
283
+ out.push(`Root: ${folderPath}\n`);
284
+ out.push(`${rootName}/`);
285
+ folderTreeLines.forEach(l => out.push(l));
286
+ out.push("");
287
+ out.push(`Total files in this folder: ${files.length}`);
288
+ out.push("");
289
+
290
+ out.push(divider);
291
+ out.push("FILE CONTENTS");
292
+ out.push(divider);
293
+
294
+ files.forEach(({ abs, rel }) => {
295
+ out.push("");
296
+ out.push(subDivider);
297
+ out.push(`FILE: ${rel}`);
298
+ out.push(subDivider);
299
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
300
+ });
301
+
302
+ out.push("");
303
+ out.push(divider);
304
+ out.push(`END OF FOLDER: ${folderName}`);
305
+ out.push(divider);
306
+
307
+ const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, '-')}.txt`;
308
+ const filePath = path.join(process.cwd(), fileName);
309
+
310
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
311
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
312
+
313
+ results.push({
314
+ file: filePath,
315
+ size: sizeKB,
316
+ files: files.length,
317
+ folder: folderName
318
+ });
319
+ });
320
+
321
+ return results;
322
+ }
323
+
324
+ function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
325
+ const results = [];
326
+
327
+ filePaths.forEach(({ abs, rel }) => {
328
+ const out = [];
329
+ const divider = "=".repeat(80);
330
+ const subDivider = "-".repeat(80);
331
+ const fileName = path.basename(rel, path.extname(rel));
332
+
333
+ out.push(divider);
334
+ out.push(`FILE: ${rel}`);
335
+ out.push(divider);
336
+ out.push("");
337
+
338
+ out.push(divider);
339
+ out.push("FILE CONTENTS");
340
+ out.push(divider);
341
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
342
+
343
+ out.push("");
344
+ out.push(divider);
345
+ out.push(`END OF FILE: ${rel}`);
346
+ out.push(divider);
347
+
348
+ const outputFileName = `${rootName}-${fileName}.txt`;
349
+ const filePath = path.join(process.cwd(), outputFileName);
350
+
351
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
352
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
353
+
354
+ results.push({
355
+ file: filePath,
356
+ size: sizeKB,
357
+ files: 1,
358
+ fileName: fileName
359
+ });
360
+ });
361
+
362
+ return results;
363
+ }
364
+
365
+ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag) {
366
+ const results = [];
367
+ let currentPart = 1;
368
+ let currentSize = 0;
369
+ let currentFiles = [];
370
+
371
+ const divider = "=".repeat(80);
372
+ const subDivider = "-".repeat(80);
373
+
374
+ // Start with header
375
+ let out = [];
376
+ out.push(divider);
377
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
378
+ out.push(divider);
379
+ out.push("");
380
+
381
+ out.push(divider);
382
+ out.push("PROJECT STRUCTURE");
383
+ out.push(divider);
384
+ out.push(`Root: ${folderPath}\n`);
385
+ out.push(`${rootName}/`);
386
+ treeLines.forEach(l => out.push(l));
387
+ out.push("");
388
+ out.push(`Total files: ${filePaths.length}`);
389
+ out.push("");
390
+
391
+ out.push(divider);
392
+ out.push("FILE CONTENTS");
393
+ out.push(divider);
394
+
395
+ filePaths.forEach(({ abs, rel }) => {
396
+ const content = readContent(abs, forceFlag, effectiveMaxSize);
397
+ const fileContent = [
398
+ "",
399
+ subDivider,
400
+ `FILE: ${rel}`,
401
+ subDivider,
402
+ content
403
+ ];
404
+
405
+ const contentSize = fileContent.join("\n").length;
406
+
407
+ // Check if adding this file would exceed the split size
408
+ if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
409
+ // Finish current part
410
+ out.push("");
411
+ out.push(divider);
412
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
413
+ out.push(divider);
414
+
415
+ // Write current part
416
+ const fileName = `${rootName}-part-${currentPart}.txt`;
417
+ const filePath = path.join(process.cwd(), fileName);
418
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
419
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
420
+
421
+ results.push({
422
+ file: filePath,
423
+ size: sizeKB,
424
+ files: currentFiles.length,
425
+ part: currentPart
426
+ });
427
+
428
+ // Start new part
429
+ currentPart++;
430
+ currentSize = 0;
431
+ currentFiles = [];
432
+
433
+ out = [];
434
+ out.push(divider);
435
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
436
+ out.push(divider);
437
+ out.push("");
438
+
439
+ out.push(divider);
440
+ out.push("PROJECT STRUCTURE");
441
+ out.push(divider);
442
+ out.push(`Root: ${folderPath}\n`);
443
+ out.push(`${rootName}/`);
444
+ treeLines.forEach(l => out.push(l));
445
+ out.push("");
446
+ out.push(`Total files: ${filePaths.length}`);
447
+ out.push("");
448
+
449
+ out.push(divider);
450
+ out.push("FILE CONTENTS");
451
+ out.push(divider);
452
+ }
453
+
454
+ // Add file to current part
455
+ out.push(...fileContent);
456
+ currentSize += contentSize;
457
+ currentFiles.push(rel);
458
+ });
459
+
460
+ // Write final part
461
+ out.push("");
462
+ out.push(divider);
463
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
464
+ out.push(divider);
465
+
466
+ const fileName = `${rootName}-part-${currentPart}.txt`;
467
+ const filePath = path.join(process.cwd(), fileName);
468
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
469
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
470
+
471
+ results.push({
472
+ file: filePath,
473
+ size: sizeKB,
474
+ files: currentFiles.length,
475
+ part: currentPart
476
+ });
477
+
478
+ return results;
479
+ }
480
+
481
+ // ── configuration ────────────────────────────────────────────────────────────────
482
+
483
+ function createInteractiveConfig() {
484
+ const readline = require('readline');
485
+ const fs = require('fs');
486
+ const path = require('path');
487
+ const os = require('os');
488
+
489
+ const rl = readline.createInterface({
490
+ input: process.stdin,
491
+ output: process.stdout
492
+ });
493
+
494
+ console.log('\nšŸ”§ make-folder-txt Configuration Setup');
495
+ console.log('=====================================\n');
496
+
497
+ return new Promise((resolve) => {
498
+ const config = {
499
+ maxFileSize: '500KB',
500
+ splitMethod: 'none',
501
+ splitSize: '5MB',
502
+ copyToClipboard: false
503
+ };
504
+
505
+ let currentStep = 0;
506
+ const questions = [
507
+ {
508
+ key: 'maxFileSize',
509
+ question: 'Maximum file size to include (e.g., 500KB, 2MB, 1GB): ',
510
+ default: '500KB',
511
+ validate: (value) => {
512
+ if (!value.trim()) return true;
513
+ const validUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
514
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
515
+ if (!match) return 'Please enter a valid size (e.g., 500KB, 2MB, 1GB)';
516
+ if (!validUnits.includes(match[2].toUpperCase())) return `Invalid unit. Use: ${validUnits.join(', ')}`;
517
+ return true;
518
+ }
519
+ },
520
+ {
521
+ key: 'splitMethod',
522
+ question: 'Split output method (none, folder, file, size): ',
523
+ default: 'none',
524
+ validate: (value) => {
525
+ const validMethods = ['none', 'folder', 'file', 'size'];
526
+ if (!validMethods.includes(value.toLowerCase())) return `Please choose: ${validMethods.join(', ')}`;
527
+ return true;
528
+ }
529
+ },
530
+ {
531
+ key: 'splitSize',
532
+ question: 'Split size when using size method (e.g., 5MB, 10MB): ',
533
+ default: '5MB',
534
+ ask: () => config.splitMethod === 'size',
535
+ validate: (value) => {
536
+ if (!value.trim()) return true;
537
+ const validUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
538
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
539
+ if (!match) return 'Please enter a valid size (e.g., 5MB, 10MB)';
540
+ if (!validUnits.includes(match[2].toUpperCase())) return `Invalid unit. Use: ${validUnits.join(', ')}`;
541
+ return true;
542
+ }
543
+ },
544
+ {
545
+ key: 'copyToClipboard',
546
+ question: 'Copy to clipboard automatically? (y/n): ',
547
+ default: 'n',
548
+ validate: (value) => {
549
+ const answer = value.toLowerCase();
550
+ if (!['y', 'n', 'yes', 'no'].includes(answer)) return 'Please enter y/n or yes/no';
551
+ return true;
552
+ },
553
+ transform: (value) => ['y', 'yes'].includes(value.toLowerCase())
554
+ },
555
+ {
556
+ key: 'addToTxtIgnore',
557
+ question: 'Add ignore patterns to .txtignore file? (y/n): ',
558
+ default: 'n',
559
+ validate: (value) => {
560
+ const answer = value.toLowerCase();
561
+ if (!['y', 'n', 'yes', 'no'].includes(answer)) return 'Please enter y/n or yes/no';
562
+ return true;
563
+ },
564
+ transform: (value) => ['y', 'yes'].includes(value.toLowerCase())
565
+ },
566
+ {
567
+ key: 'ignoreFolders',
568
+ question: 'Ignore folders (comma-separated, or press Enter to skip): ',
569
+ default: '',
570
+ ask: () => config.addToTxtIgnore,
571
+ transform: (value) => {
572
+ if (!value || value.trim() === '') return [];
573
+ return value.split(',').map(f => f.trim()).filter(f => f);
574
+ }
575
+ },
576
+ {
577
+ key: 'ignoreFiles',
578
+ question: 'Ignore files (comma-separated, or press Enter to skip): ',
579
+ default: '',
580
+ ask: () => config.addToTxtIgnore,
581
+ transform: (value) => {
582
+ if (!value || value.trim() === '') return [];
583
+ return value.split(',').map(f => f.trim()).filter(f => f);
584
+ }
585
+ }
586
+ ];
587
+
588
+ function askQuestion() {
589
+ if (currentStep >= questions.length) {
590
+ // Save configuration
591
+ saveConfig();
592
+ return;
593
+ }
594
+
595
+ const q = questions[currentStep];
596
+
597
+ // Skip if conditional ask returns false
598
+ if (q.ask && !q.ask()) {
599
+ currentStep++;
600
+ askQuestion();
601
+ return;
602
+ }
603
+
604
+ const defaultValue = typeof q.default === 'function' ? q.default() : q.default;
605
+ rl.question(q.question + (defaultValue ? `(${defaultValue}) ` : ''), (answer) => {
606
+ const value = answer.trim() || defaultValue;
607
+
608
+ // Validate input
609
+ if (q.validate) {
610
+ const validation = q.validate(value);
611
+ if (validation !== true) {
612
+ console.log(`āŒ ${validation}`);
613
+ askQuestion();
614
+ return;
615
+ }
616
+ }
617
+
618
+ // Transform value if needed
619
+ if (q.transform) {
620
+ config[q.key] = q.transform(value);
621
+ } else {
622
+ config[q.key] = value;
623
+ }
624
+
625
+ currentStep++;
626
+ askQuestion();
627
+ });
628
+ }
629
+
630
+ function saveConfig() {
631
+ try {
632
+ // Create .txtconfig file with proper formatting
633
+ const configPath = path.join(process.cwd(), '.txtconfig');
634
+ const configContent = `{
635
+ "maxFileSize": "${config.maxFileSize}",
636
+ "splitMethod": "${config.splitMethod}",
637
+ "splitSize": "${config.splitSize}",
638
+ "copyToClipboard": ${config.copyToClipboard}
639
+ }`;
640
+
641
+ fs.writeFileSync(configPath, configContent);
642
+
643
+ // Update .txtignore if user wants to add ignore patterns
644
+ if (config.addToTxtIgnore && (config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)) {
645
+ const ignorePath = path.join(process.cwd(), '.txtignore');
646
+ let ignoreContent = '';
647
+
648
+ // Read existing content if file exists
649
+ if (fs.existsSync(ignorePath)) {
650
+ ignoreContent = fs.readFileSync(ignorePath, 'utf8');
651
+ if (!ignoreContent.endsWith('\n')) ignoreContent += '\n';
652
+ }
653
+
654
+ // Add new ignore patterns
655
+ const newPatterns = [
656
+ ...config.ignoreFolders.map(f => f.endsWith('/') ? f : `${f}/`),
657
+ ...config.ignoreFiles
658
+ ];
659
+
660
+ if (newPatterns.length > 0) {
661
+ ignoreContent += '\n# Added by make-folder-txt config\n';
662
+ ignoreContent += newPatterns.join('\n') + '\n';
663
+ fs.writeFileSync(ignorePath, ignoreContent);
664
+ }
665
+ }
666
+
667
+ console.log('\nāœ… Configuration saved successfully!');
668
+ console.log(`šŸ“„ Config file: ${configPath}`);
669
+ if (config.addToTxtIgnore && (config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)) {
670
+ console.log(`šŸ“ Ignore patterns added to .txtignore`);
671
+ }
672
+ console.log('\nšŸ’” Your settings will now be used automatically!');
673
+ console.log('šŸ”„ Run --delete-config to reset to defaults');
674
+
675
+ } catch (err) {
676
+ console.error('āŒ Error saving configuration:', err.message);
677
+ } finally {
678
+ rl.close();
679
+ resolve();
680
+ }
681
+ }
682
+
683
+ askQuestion();
684
+ });
685
+ }
686
+
687
+ function loadConfig() {
688
+ const fs = require('fs');
689
+ const path = require('path');
690
+
691
+ try {
692
+ const configPath = path.join(process.cwd(), '.txtconfig');
693
+ if (!fs.existsSync(configPath)) {
694
+ console.error('āŒ .txtconfig file not found. Run --make-config first.');
695
+ process.exit(1);
696
+ }
697
+
698
+ const configContent = fs.readFileSync(configPath, 'utf8');
699
+ return JSON.parse(configContent);
700
+ } catch (err) {
701
+ console.error('āŒ Error loading configuration:', err.message);
702
+ process.exit(1);
703
+ }
704
+ }
705
+
706
+ // ── main ──────────────────────────────────────────────────────────────────────
707
+
708
+ async function main() {
709
+ const args = process.argv.slice(2);
710
+
711
+ if (args.includes("-v") || args.includes("--version")) {
712
+ console.log(`v${version}`);
713
+ console.log("Built by Muhammad Saad Amin");
714
+ process.exit(0);
715
+ }
716
+
717
+ if (args.includes("--make-config")) {
718
+ await createInteractiveConfig();
719
+ process.exit(0);
720
+ }
721
+
722
+ if (args.includes("--delete-config")) {
723
+ try {
724
+ const fs = require('fs');
725
+ const path = require('path');
726
+ const configPath = path.join(process.cwd(), '.txtconfig');
727
+
728
+ if (fs.existsSync(configPath)) {
729
+ fs.unlinkSync(configPath);
730
+ console.log('āœ… Configuration file deleted successfully!');
731
+ console.log('šŸ”„ Tool will now use default settings');
732
+ } else {
733
+ console.log('ā„¹ļø No configuration file found - already using defaults');
734
+ }
735
+ } catch (err) {
736
+ console.error('āŒ Error deleting configuration:', err.message);
737
+ }
738
+ process.exit(0);
739
+ }
740
+
741
+ // Auto-use config if it exists and no other flags are provided
742
+ const configPath = path.join(process.cwd(), '.txtconfig');
743
+ const hasConfig = fs.existsSync(configPath);
744
+ const hasOtherFlags = args.length > 0 && !args.includes('--help') && !args.includes('-h') && !args.includes('--version') && !args.includes('-v');
745
+
746
+ if (hasConfig && !hasOtherFlags) {
747
+ const config = loadConfig();
748
+
749
+ // Apply config to command line arguments
750
+ const newArgs = [];
751
+
752
+ // Add max file size if not default
753
+ if (config.maxFileSize !== '500KB') {
754
+ newArgs.push('--skip-large', config.maxFileSize);
755
+ }
756
+
757
+ // Add split method if not none
758
+ if (config.splitMethod !== 'none') {
759
+ newArgs.push('--split-method', config.splitMethod);
760
+ if (config.splitMethod === 'size') {
761
+ newArgs.push('--split-size', config.splitSize);
762
+ }
763
+ }
764
+
765
+ // Add copy to clipboard if true
766
+ if (config.copyToClipboard) {
767
+ newArgs.push('--copy');
768
+ }
769
+
770
+ // Replace args with config-based args
771
+ args.splice(0, args.length, ...newArgs);
772
+ console.log('šŸ”§ Using saved configuration (use --delete-config to reset to defaults)');
773
+ }
774
+
775
+ if (args.includes("--help") || args.includes("-h")) {
776
+ console.log(`
777
+ \x1b[33mmake-folder-txt\x1b[0m
778
+ Dump an entire project folder into a single readable .txt file.
779
+
780
+ \x1b[33mUSAGE\x1b[0m
781
+ make-folder-txt [options]
782
+
783
+ \x1b[33mOPTIONS\x1b[0m
784
+ --ignore-folder, -ifo <names...> Ignore specific folders by name
785
+ --ignore-file, -ifi <names...> Ignore specific files by name
786
+ --only-folder, -ofo <names...> Include only specific folders
787
+ --only-file, -ofi <names...> Include only specific files
788
+ --skip-large <size> Skip files larger than specified size (default: 500KB)
789
+ --no-skip Include all files regardless of size
790
+ --split-method <method> Split output: folder, file, or size
791
+ --split-size <size> Split output when size exceeds limit (requires --split-method size)
792
+ --copy Copy output to clipboard
793
+ --force Include everything (overrides all ignore patterns)
794
+ --make-config Create interactive configuration
795
+ --delete-config Delete configuration and reset to defaults
796
+ --help, -h Show this help message
797
+ --version, -v Show version information
798
+
799
+ \x1b[33mEXAMPLES\x1b[0m
800
+ make-folder-txt
801
+ make-folder-txt --copy
802
+ make-folder-txt --force
803
+ make-folder-txt --skip-large 400KB
804
+ make-folder-txt --skip-large 5GB
805
+ make-folder-txt --no-skip
806
+ make-folder-txt --split-method folder
807
+ make-folder-txt --split-method file
808
+ make-folder-txt --split-method size --split-size 5MB
809
+ make-folder-txt --ignore-folder node_modules dist
810
+ make-folder-txt -ifo node_modules dist
811
+ make-folder-txt --ignore-file .env .env.local
812
+ make-folder-txt -ifi .env .env.local
813
+ make-folder-txt --only-folder src docs
814
+ make-folder-txt -ofo src docs
815
+ make-folder-txt --only-file package.json README.md
816
+ make-folder-txt -ofi package.json README.md
817
+ make-folder-txt --make-config
818
+ make-folder-txt --delete-config
819
+
820
+ \x1b[33m.TXTIGNORE FILE\x1b[0m
821
+ Create a .txtignore file in your project root to specify files/folders to ignore.
822
+ Works like .gitignore - supports file names, path patterns, and comments.
823
+
824
+ Example .txtignore:
825
+ node_modules/
826
+ *.log
827
+ .env
828
+ coverage/
829
+ LICENSE
830
+ `);
831
+ process.exit(0);
832
+ }
833
+
834
+ const ignoreDirs = new Set(IGNORE_DIRS);
835
+ const ignoreFiles = new Set(IGNORE_FILES);
836
+ const onlyFolders = new Set();
837
+ const onlyFiles = new Set();
838
+ let outputArg = null;
839
+ let copyToClipboardFlag = false;
840
+ let forceFlag = false;
841
+ let maxFileSize = 500 * 1024; // Default 500KB
842
+ let noSkipFlag = false;
843
+ let splitMethod = null; // 'folder', 'file', 'size'
844
+ let splitSize = null; // size in bytes
845
+
846
+ for (let i = 0; i < args.length; i += 1) {
847
+ const arg = args[i];
848
+
849
+ if (arg === "--copy") {
850
+ copyToClipboardFlag = true;
851
+ continue;
852
+ }
853
+
854
+ if (arg === "--force") {
855
+ forceFlag = true;
856
+ continue;
857
+ }
858
+
859
+ if (arg === "--no-skip") {
860
+ noSkipFlag = true;
861
+ continue;
862
+ }
863
+
864
+ if (arg === "--skip-large") {
865
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
866
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
867
+ process.exit(1);
868
+ }
869
+ maxFileSize = parseFileSize(args[i + 1]);
870
+ i += 1;
871
+ continue;
872
+ }
873
+
874
+ if (arg.startsWith("--skip-large=")) {
875
+ const value = arg.slice("--skip-large=".length);
876
+ if (!value) {
877
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
878
+ process.exit(1);
879
+ }
880
+ maxFileSize = parseFileSize(value);
881
+ continue;
882
+ }
883
+
884
+ if (arg === "--split-method") {
885
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
886
+ console.error("Error: --split-method requires a method (folder, file, or size).");
887
+ process.exit(1);
888
+ }
889
+ const method = args[i + 1].toLowerCase();
890
+ if (!['folder', 'file', 'size'].includes(method)) {
891
+ console.error("Error: --split-method must be one of: folder, file, size");
892
+ process.exit(1);
893
+ }
894
+ splitMethod = method;
895
+ i += 1;
896
+ continue;
897
+ }
898
+
899
+ if (arg.startsWith("--split-method=")) {
900
+ const value = arg.slice("--split-method=".length);
901
+ if (!value) {
902
+ console.error("Error: --split-method requires a method (folder, file, or size).");
903
+ process.exit(1);
904
+ }
905
+ const method = value.toLowerCase();
906
+ if (!['folder', 'file', 'size'].includes(method)) {
907
+ console.error("Error: --split-method must be one of: folder, file, size");
908
+ process.exit(1);
909
+ }
910
+ splitMethod = method;
911
+ continue;
912
+ }
913
+
914
+ if (arg === "--split-size") {
915
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
916
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
917
+ process.exit(1);
918
+ }
919
+ splitSize = parseFileSize(args[i + 1]);
920
+ i += 1;
921
+ continue;
922
+ }
923
+
924
+ if (arg.startsWith("--split-size=")) {
925
+ const value = arg.slice("--split-size=".length);
926
+ if (!value) {
927
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
928
+ process.exit(1);
929
+ }
930
+ splitSize = parseFileSize(value);
931
+ continue;
932
+ }
933
+
934
+ if (arg === "--ignore-folder" || arg === "-ifo") {
935
+ let consumed = 0;
936
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
937
+ // Normalize the folder name: remove backslashes, trailing slashes, and leading ./
938
+ let folderName = args[i + 1];
939
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
940
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
941
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
942
+ ignoreDirs.add(folderName);
943
+ i += 1;
944
+ consumed += 1;
945
+ }
946
+ if (consumed === 0) {
947
+ console.error("Error: --ignore-folder requires at least one folder name.");
948
+ process.exit(1);
949
+ }
950
+ continue;
951
+ }
952
+
953
+ if (arg.startsWith("--ignore-folder=") || arg.startsWith("-ifo=")) {
954
+ const value = arg.startsWith("--ignore-folder=")
955
+ ? arg.slice("--ignore-folder=".length)
956
+ : arg.slice("-ifo=".length);
957
+ if (!value) {
958
+ console.error("Error: --ignore-folder requires a folder name.");
959
+ process.exit(1);
960
+ }
961
+ // Normalize the folder name
962
+ let folderName = value;
963
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
964
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
965
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
966
+ ignoreDirs.add(folderName);
967
+ continue;
968
+ }
969
+
970
+ if (arg === "--ignore-file" || arg === "-ifi") {
971
+ let consumed = 0;
972
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
973
+ ignoreFiles.add(args[i + 1]);
974
+ i += 1;
975
+ consumed += 1;
976
+ }
977
+ if (consumed === 0) {
978
+ console.error("Error: --ignore-file requires at least one file name.");
979
+ process.exit(1);
980
+ }
981
+ continue;
982
+ }
983
+
984
+ if (arg.startsWith("--ignore-file=") || arg.startsWith("-ifi=")) {
985
+ const value = arg.startsWith("--ignore-file=")
986
+ ? arg.slice("--ignore-file=".length)
987
+ : arg.slice("-ifi=".length);
988
+ if (!value) {
989
+ console.error("Error: --ignore-file requires a file name.");
990
+ process.exit(1);
991
+ }
992
+ ignoreFiles.add(value);
993
+ continue;
994
+ }
995
+
996
+ if (arg === "--only-folder" || arg === "-ofo") {
997
+ let consumed = 0;
998
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
999
+ // Normalize the folder name
1000
+ let folderName = args[i + 1];
1001
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
1002
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
1003
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
1004
+ onlyFolders.add(folderName);
1005
+ i += 1;
1006
+ consumed += 1;
1007
+ }
1008
+ if (consumed === 0) {
1009
+ console.error("Error: --only-folder requires at least one folder name.");
1010
+ process.exit(1);
1011
+ }
1012
+ continue;
1013
+ }
1014
+
1015
+ if (arg.startsWith("--only-folder=") || arg.startsWith("-ofo=")) {
1016
+ const value = arg.startsWith("--only-folder=")
1017
+ ? arg.slice("--only-folder=".length)
1018
+ : arg.slice("-ofo=".length);
1019
+ if (!value) {
1020
+ console.error("Error: --only-folder requires a folder name.");
1021
+ process.exit(1);
1022
+ }
1023
+ // Normalize the folder name
1024
+ let folderName = value;
1025
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
1026
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
1027
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
1028
+ onlyFolders.add(folderName);
1029
+ continue;
1030
+ }
1031
+
1032
+ if (arg === "--only-file" || arg === "-ofi") {
1033
+ let consumed = 0;
1034
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1035
+ onlyFiles.add(args[i + 1]);
1036
+ i += 1;
1037
+ consumed += 1;
1038
+ }
1039
+ if (consumed === 0) {
1040
+ console.error("Error: --only-file requires at least one file name.");
1041
+ process.exit(1);
1042
+ }
1043
+ continue;
1044
+ }
1045
+
1046
+ if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
1047
+ const value = arg.startsWith("--only-file=")
1048
+ ? arg.slice("--only-file=".length)
1049
+ : arg.slice("-ofi=".length);
1050
+ if (!value) {
1051
+ console.error("Error: --only-file requires a file name.");
1052
+ process.exit(1);
1053
+ }
1054
+ onlyFiles.add(value);
1055
+ continue;
1056
+ }
1057
+
1058
+ // Unknown argument
1059
+ console.error(`Error: Unknown option "${arg}"`);
1060
+ console.error("Use --help for available options.");
1061
+ process.exit(1);
1062
+ }
1063
+
1064
+ // Validate split options
1065
+ if (splitMethod === 'size' && !splitSize) {
1066
+ console.error("Error: --split-method size requires --split-size to be specified");
1067
+ process.exit(1);
1068
+ }
1069
+
1070
+ if (splitSize && splitMethod !== 'size') {
1071
+ console.error("Error: --split-size can only be used with --split-method size");
1072
+ process.exit(1);
1073
+ }
1074
+
1075
+ // ── config ────────────────────────────────────────────────────────────────────────
1076
+
1077
+ const folderPath = process.cwd();
1078
+ const rootName = path.basename(folderPath);
1079
+ const txtIgnore = readTxtIgnore(folderPath);
1080
+
1081
+ // ── build tree & collect file paths ───────────────────────────────────────────────
1082
+
1083
+ const { lines: treeLines, filePaths } = collectFiles(
1084
+ folderPath,
1085
+ folderPath,
1086
+ ignoreDirs,
1087
+ ignoreFiles,
1088
+ onlyFolders,
1089
+ onlyFiles,
1090
+ { rootName, txtIgnore, force: forceFlag }
1091
+ );
1092
+
1093
+ // ── build output filename ───────────────────────────────────────────────────────────
1094
+
1095
+ let outputFile = outputArg || path.join(folderPath, `${rootName}.txt`);
1096
+
1097
+ // ── handle output splitting ─────────────────────────────────────────────────────────
1098
+
1099
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
1100
+
1101
+ if (splitMethod) {
1102
+ console.log(`šŸ”§ Splitting output by: ${splitMethod}`);
1103
+
1104
+ let results;
1105
+
1106
+ if (splitMethod === 'folder') {
1107
+ results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
1108
+ } else if (splitMethod === 'file') {
1109
+ results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
1110
+ } else if (splitMethod === 'size') {
1111
+ results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
1112
+ }
1113
+
1114
+ console.log(`āœ… Done! Created ${results.length} split files:`);
1115
+ console.log('');
1116
+
1117
+ results.forEach((result, index) => {
1118
+ if (splitMethod === 'folder') {
1119
+ console.log(`šŸ“ Folder: ${result.folder}`);
1120
+ } else if (splitMethod === 'file') {
1121
+ console.log(`šŸ“„ File: ${result.fileName}`);
1122
+ } else if (splitMethod === 'size') {
1123
+ console.log(`šŸ“¦ Part ${result.part}`);
1124
+ }
1125
+ console.log(`šŸ“„ Output : ${result.file}`);
1126
+ console.log(`šŸ“Š Size : ${result.size} KB`);
1127
+ console.log(`šŸ—‚ļø Files : ${result.files}`);
1128
+ console.log('');
1129
+ });
1130
+
1131
+ if (copyToClipboardFlag) {
1132
+ console.log('āš ļø --copy flag is not compatible with splitting - clipboard copy skipped');
1133
+ }
1134
+
1135
+ process.exit(0);
1136
+ }
1137
+
1138
+ // ── build output (no splitting) ───────────────────────────────────────────────────
1139
+ const out = [];
1140
+ const divider = "=".repeat(80);
1141
+ const subDivider = "-".repeat(80);
1142
+
1143
+ out.push(divider);
1144
+ out.push(`START OF FOLDER: ${rootName}`);
1145
+ out.push(divider);
1146
+ out.push("");
1147
+
1148
+ out.push(divider);
1149
+ out.push("PROJECT STRUCTURE");
1150
+ out.push(divider);
1151
+ out.push(`Root: ${folderPath}\n`);
1152
+
1153
+ out.push(`${rootName}/`);
1154
+ treeLines.forEach(l => out.push(l));
1155
+ out.push("");
1156
+ out.push(`Total files: ${filePaths.length}`);
1157
+ out.push("");
1158
+
1159
+ out.push(divider);
1160
+ out.push("FILE CONTENTS");
1161
+ out.push(divider);
1162
+
1163
+ filePaths.forEach(({ abs, rel }) => {
1164
+ out.push("");
1165
+ out.push(subDivider);
1166
+ out.push(`FILE: ${rel}`);
1167
+ out.push(subDivider);
1168
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
1169
+ });
1170
+
1171
+ out.push("");
1172
+ out.push(divider);
1173
+ out.push(`END OF FOLDER: ${rootName}`);
1174
+ out.push(divider);
1175
+
1176
+ fs.writeFileSync(outputFile, out.join("\n"), "utf8");
1177
+
1178
+ const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
1179
+ console.log(`āœ… Done!`);
1180
+ console.log(`šŸ“„ Output : ${outputFile}`);
1181
+ console.log(`šŸ“Š Size : ${sizeKB} KB`);
1182
+ console.log(`šŸ—‚ļø Files : ${filePaths.length}`);
1183
+
1184
+ if (copyToClipboardFlag) {
1185
+ const content = fs.readFileSync(outputFile, 'utf8');
1186
+ const success = copyToClipboard(content);
1187
+ if (success) {
1188
+ console.log(`šŸ“‹ Copied to clipboard!`);
1189
+ }
1190
+ }
1191
+
1192
+ console.log('');
1193
+ }
1194
+
1195
+ // Run the main function
1196
+ main().catch(err => {
1197
+ console.error('āŒ Error:', err.message);
1198
+ process.exit(1);
1199
+ });
1200
+
32
1201
 
33
1202
  --------------------------------------------------------------------------------
34
1203
  FILE: /LICENSE
@@ -56,6 +1225,37 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
56
1225
  SOFTWARE.
57
1226
 
58
1227
 
1228
+ --------------------------------------------------------------------------------
1229
+ FILE: /package.json
1230
+ --------------------------------------------------------------------------------
1231
+ {
1232
+ "name": "make-folder-txt",
1233
+ "version": "2.2.4",
1234
+ "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
1235
+ "main": "bin/make-folder-txt.js",
1236
+ "bin": {
1237
+ "make-folder-txt": "bin/make-folder-txt.js"
1238
+ },
1239
+ "scripts": {
1240
+ "test": "echo \"No tests yet\" && exit 0"
1241
+ },
1242
+ "keywords": [
1243
+ "folder",
1244
+ "dump",
1245
+ "project",
1246
+ "structure",
1247
+ "txt",
1248
+ "cli",
1249
+ "export"
1250
+ ],
1251
+ "author": "Muhammad Saad Amin",
1252
+ "license": "MIT",
1253
+ "engines": {
1254
+ "node": ">=14.0.0"
1255
+ }
1256
+ }
1257
+
1258
+
59
1259
  --------------------------------------------------------------------------------
60
1260
  FILE: /README.md
61
1261
  --------------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-folder-txt",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
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": {
package/.txtconfig DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "maxFileSize": "500KB",
3
- "splitMethod": "none",
4
- "splitSize": "5MB",
5
- "copyToClipboard": true
6
- }
package/.txtignore DELETED
@@ -1,4 +0,0 @@
1
-
2
- # Added by make-folder-txt config
3
- bin/
4
- package.json