make-folder-txt 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  Perfect for sharing your codebase with **AI tools**, **teammates**, or **code reviewers** — without zipping files or giving repo access.
13
13
 
14
- [Installation](#-installation) · [Usage](#-usage) · [Help](#-get-help) · [Copy to Clipboard](#-copy-to-clipboard) · [Force Include Everything](#-force-include-everything) · [Shell Autocompletion](#-shell-autocompletion) · [Output Format](#-output-format) · [What Gets Skipped](#-what-gets-skipped) · [Contributing](#-contributing)
14
+ [Installation](#-installation) · [Usage](#-usage) · [Help](#-get-help) · [Copy to Clipboard](#-copy-to-clipboard) · [Force Include Everything](#-force-include-everything) · [Shell Autocompletion](#-shell-autocompletion) · [File Size Control](#-file-size-control) · [Output Splitting](#-output-splitting) · [Output Format](#-output-format) · [What Gets Skipped](#-what-gets-skipped) · [Contributing](#-contributing)
15
15
 
16
16
  </div>
17
17
 
@@ -23,7 +23,9 @@ Ever needed to share your entire project with ChatGPT, Claude, or a teammate —
23
23
 
24
24
  - ✅ Run it from any project directory — no arguments needed
25
25
  - ✅ Built-in help system with `--help` flag
26
- - ✅ Shell autocompletion with `--install-completion`
26
+ - ✅ **Built-in shell autocompletion** (installs automatically)
27
+ - ✅ **File size control** with `--skip-large` and `--no-skip`
28
+ - ✅ **Output splitting** by folders, files, or size
27
29
  - ✅ Copy to clipboard with `--copy` flag
28
30
  - ✅ Force include everything with `--force` flag
29
31
  - ✅ Generates a clean folder tree + every file's content
@@ -89,17 +91,24 @@ The `--force` flag overrides all ignore patterns and includes:
89
91
 
90
92
  Use this when you need a complete, unfiltered dump of your entire project.
91
93
 
92
- ### ⚡ Shell Autocompletion
94
+ ### ⚡ Shell Autocompletion (Built-in)
93
95
 
94
96
  ```bash
95
- make-folder-txt --install-completion # Install bash/zsh autocompletion
97
+ make-folder-txt # Autocompletion installs automatically on first run
96
98
  ```
97
99
 
98
- After installation, you'll get intelligent tab completion:
100
+ **🎉 No installation required!** The tool automatically installs shell autocompletion the first time you run it.
101
+
102
+ **What gets installed automatically:**
99
103
  - **Flag completion**: `make-folder-txt --<TAB>` shows all available flags
100
104
  - **Folder completion**: `make-folder-txt --ignore-folder <TAB>` shows folders
101
105
  - **File completion**: `make-folder-txt --ignore-file <TAB>` shows files
102
106
 
107
+ **Supported Shells:**
108
+ - **Bash** - Linux/macOS/Windows (WSL)
109
+ - **Zsh** - macOS/Linux
110
+ - **PowerShell** - Windows (7+)
111
+
103
112
  **Example usage:**
104
113
  ```bash
105
114
  $ make-folder-txt --ignore-folder b<TAB>
@@ -109,7 +118,92 @@ $ make-folder-txt --ignore-file p<TAB>
109
118
  # → completes to "package.json" if package.json exists
110
119
  ```
111
120
 
112
- The completion automatically detects your shell (bash/zsh) and installs the appropriate scripts. Restart your terminal after installation.
121
+ **Manual Installation (if needed):**
122
+ ```bash
123
+ make-folder-txt --install-completion # Force reinstall completion
124
+ ```
125
+
126
+ **PowerShell Manual Setup:**
127
+ ```powershell
128
+ # If auto-installation doesn't work, load manually:
129
+ . .\completion\make-folder-txt-completion.ps1
130
+
131
+ # Or add to your PowerShell profile for persistence:
132
+ notepad $PROFILE
133
+ # Add: . "C:\path\to\make-folder-txt\completion\make-folder-txt-completion.ps1"
134
+ ```
135
+
136
+ The completion works out of the box - just run the tool once and restart your terminal!
137
+
138
+ ### 📏 File Size Control
139
+
140
+ ```bash
141
+ make-folder-txt --skip-large 400KB # Skip files larger than 400KB
142
+ make-folder-txt --skip-large 5GB # Skip files larger than 5GB
143
+ make-folder-txt --skip-large 1.5MB # Skip files larger than 1.5MB
144
+ make-folder-txt --no-skip # Include all files regardless of size
145
+ ```
146
+
147
+ **Default behavior**: Files larger than 500KB are skipped by default.
148
+
149
+ **Supported size units**:
150
+ - **B** - Bytes
151
+ - **KB** - Kilobytes (1024 bytes)
152
+ - **MB** - Megabytes (1024 KB)
153
+ - **GB** - Gigabytes (1024 MB)
154
+ - **TB** - Terabytes (1024 GB)
155
+
156
+ **Examples:**
157
+ ```bash
158
+ # More restrictive - skip anything over 100KB
159
+ make-folder-txt --skip-large 100KB
160
+
161
+ # More permissive - allow files up to 10MB
162
+ make-folder-txt --skip-large 10MB
163
+
164
+ # Include everything - no size limits
165
+ make-folder-txt --no-skip
166
+
167
+ # Combine with other options
168
+ make-folder-txt --skip-large 2MB --ignore-folder node_modules
169
+ ```
170
+
171
+ **Size format**: Accepts decimal numbers (e.g., `1.5MB`, `0.5GB`) and various units.
172
+
173
+ ### 📂 Output Splitting
174
+
175
+ ```bash
176
+ make-folder-txt --split-method folder # Split by folders
177
+ make-folder-txt --split-method file # Split by files
178
+ make-folder-txt --split-method size --split-size 5MB # Split by file size
179
+ ```
180
+
181
+ **Split Methods:**
182
+ - **`folder`** - Creates separate files for each folder
183
+ - **`file`** - Creates separate files for each individual file
184
+ - **`size`** - Splits output when content exceeds specified size
185
+
186
+ **Examples:**
187
+ ```bash
188
+ # Split by folders - creates folder-name.txt for each folder
189
+ make-folder-txt --split-method folder
190
+
191
+ # Split by files - creates filename.txt for each file
192
+ make-folder-txt --split-method file
193
+
194
+ # Split by size - creates part-1.txt, part-2.txt, etc.
195
+ make-folder-txt --split-method size --split-size 5MB
196
+
197
+ # Combine with other options
198
+ make-folder-txt --split-method size --split-size 2MB --ignore-folder node_modules
199
+ ```
200
+
201
+ **Output Files:**
202
+ - **Folder method**: `projectname-foldername.txt`
203
+ - **File method**: `projectname-filename.txt`
204
+ - **Size method**: `projectname-part-1.txt`, `projectname-part-2.txt`, etc.
205
+
206
+ **Note**: Splitting is not compatible with `--copy` flag.
113
207
 
114
208
  Ignore specific folders/files by name:
115
209
 
@@ -179,13 +179,37 @@ function collectFiles(
179
179
  return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
180
180
  }
181
181
 
182
- function readContent(absPath, force = false) {
182
+ function parseFileSize(sizeStr) {
183
+ const units = {
184
+ 'B': 1,
185
+ 'KB': 1024,
186
+ 'MB': 1024 * 1024,
187
+ 'GB': 1024 * 1024 * 1024,
188
+ 'TB': 1024 * 1024 * 1024 * 1024
189
+ };
190
+
191
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
192
+ if (!match) {
193
+ console.error(`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`);
194
+ process.exit(1);
195
+ }
196
+
197
+ const value = parseFloat(match[1]);
198
+ const unit = match[2].toUpperCase();
199
+ return Math.floor(value * units[unit]);
200
+ }
201
+
202
+ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
183
203
  const ext = path.extname(absPath).toLowerCase();
184
204
  if (!force && BINARY_EXTS.has(ext)) return "[binary / skipped]";
185
205
  try {
186
206
  const stat = fs.statSync(absPath);
187
- if (!force && stat.size > 500 * 1024) {
188
- return `[file too large: ${(stat.size / 1024).toFixed(1)} KB skipped]`;
207
+ if (!force && stat.size > maxFileSize) {
208
+ const sizeStr = stat.size < 1024 ? `${stat.size} B` :
209
+ stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB` :
210
+ stat.size < 1024 * 1024 * 1024 ? `${(stat.size / (1024 * 1024)).toFixed(1)} MB` :
211
+ `${(stat.size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
212
+ return `[file too large: ${sizeStr} – skipped]`;
189
213
  }
190
214
  return fs.readFileSync(absPath, "utf8");
191
215
  } catch (err) {
@@ -193,10 +217,361 @@ function readContent(absPath, force = false) {
193
217
  }
194
218
  }
195
219
 
220
+ function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag) {
221
+ const folders = new Map();
222
+
223
+ // Group files by folder
224
+ filePaths.forEach(({ abs, rel }) => {
225
+ const folderPath = path.dirname(rel);
226
+ const folderKey = folderPath === '/' ? rootName : folderPath.slice(1);
227
+
228
+ if (!folders.has(folderKey)) {
229
+ folders.set(folderKey, []);
230
+ }
231
+ folders.get(folderKey).push({ abs, rel });
232
+ });
233
+
234
+ const results = [];
235
+
236
+ folders.forEach((files, folderName) => {
237
+ const out = [];
238
+ const divider = "=".repeat(80);
239
+ const subDivider = "-".repeat(80);
240
+
241
+ out.push(divider);
242
+ out.push(`START OF FOLDER: ${folderName}`);
243
+ out.push(divider);
244
+ out.push("");
245
+
246
+ // Add folder structure (only this folder's structure)
247
+ const folderTreeLines = treeLines.filter(line =>
248
+ line.includes(folderName + '/') || line === `${rootName}/`
249
+ );
250
+
251
+ out.push(divider);
252
+ out.push("PROJECT STRUCTURE");
253
+ out.push(divider);
254
+ out.push(`Root: ${folderPath}\n`);
255
+ out.push(`${rootName}/`);
256
+ folderTreeLines.forEach(l => out.push(l));
257
+ out.push("");
258
+ out.push(`Total files in this folder: ${files.length}`);
259
+ out.push("");
260
+
261
+ out.push(divider);
262
+ out.push("FILE CONTENTS");
263
+ out.push(divider);
264
+
265
+ files.forEach(({ abs, rel }) => {
266
+ out.push("");
267
+ out.push(subDivider);
268
+ out.push(`FILE: ${rel}`);
269
+ out.push(subDivider);
270
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
271
+ });
272
+
273
+ out.push("");
274
+ out.push(divider);
275
+ out.push(`END OF FOLDER: ${folderName}`);
276
+ out.push(divider);
277
+
278
+ const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, '-')}.txt`;
279
+ const filePath = path.join(process.cwd(), fileName);
280
+
281
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
282
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
283
+
284
+ results.push({
285
+ file: filePath,
286
+ size: sizeKB,
287
+ files: files.length,
288
+ folder: folderName
289
+ });
290
+ });
291
+
292
+ return results;
293
+ }
294
+
295
+ function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
296
+ const results = [];
297
+
298
+ filePaths.forEach(({ abs, rel }) => {
299
+ const out = [];
300
+ const divider = "=".repeat(80);
301
+ const subDivider = "-".repeat(80);
302
+ const fileName = path.basename(rel, path.extname(rel));
303
+
304
+ out.push(divider);
305
+ out.push(`FILE: ${rel}`);
306
+ out.push(divider);
307
+ out.push("");
308
+
309
+ out.push(divider);
310
+ out.push("FILE CONTENTS");
311
+ out.push(divider);
312
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
313
+
314
+ out.push("");
315
+ out.push(divider);
316
+ out.push(`END OF FILE: ${rel}`);
317
+ out.push(divider);
318
+
319
+ const outputFileName = `${rootName}-${fileName}.txt`;
320
+ const filePath = path.join(process.cwd(), outputFileName);
321
+
322
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
323
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
324
+
325
+ results.push({
326
+ file: filePath,
327
+ size: sizeKB,
328
+ files: 1,
329
+ fileName: fileName
330
+ });
331
+ });
332
+
333
+ return results;
334
+ }
335
+
336
+ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag) {
337
+ const results = [];
338
+ let currentPart = 1;
339
+ let currentSize = 0;
340
+ let currentFiles = [];
341
+
342
+ const divider = "=".repeat(80);
343
+ const subDivider = "-".repeat(80);
344
+
345
+ // Start with header
346
+ let out = [];
347
+ out.push(divider);
348
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
349
+ out.push(divider);
350
+ out.push("");
351
+
352
+ out.push(divider);
353
+ out.push("PROJECT STRUCTURE");
354
+ out.push(divider);
355
+ out.push(`Root: ${folderPath}\n`);
356
+ out.push(`${rootName}/`);
357
+ treeLines.forEach(l => out.push(l));
358
+ out.push("");
359
+ out.push(`Total files: ${filePaths.length}`);
360
+ out.push("");
361
+
362
+ out.push(divider);
363
+ out.push("FILE CONTENTS");
364
+ out.push(divider);
365
+
366
+ filePaths.forEach(({ abs, rel }) => {
367
+ const content = readContent(abs, forceFlag, effectiveMaxSize);
368
+ const fileContent = [
369
+ "",
370
+ subDivider,
371
+ `FILE: ${rel}`,
372
+ subDivider,
373
+ content
374
+ ];
375
+
376
+ const contentSize = fileContent.join("\n").length;
377
+
378
+ // Check if adding this file would exceed the split size
379
+ if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
380
+ // Finish current part
381
+ out.push("");
382
+ out.push(divider);
383
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
384
+ out.push(divider);
385
+
386
+ // Write current part
387
+ const fileName = `${rootName}-part-${currentPart}.txt`;
388
+ const filePath = path.join(process.cwd(), fileName);
389
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
390
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
391
+
392
+ results.push({
393
+ file: filePath,
394
+ size: sizeKB,
395
+ files: currentFiles.length,
396
+ part: currentPart
397
+ });
398
+
399
+ // Start new part
400
+ currentPart++;
401
+ currentSize = 0;
402
+ currentFiles = [];
403
+
404
+ out = [];
405
+ out.push(divider);
406
+ out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
407
+ out.push(divider);
408
+ out.push("");
409
+
410
+ out.push(divider);
411
+ out.push("PROJECT STRUCTURE");
412
+ out.push(divider);
413
+ out.push(`Root: ${folderPath}\n`);
414
+ out.push(`${rootName}/`);
415
+ treeLines.forEach(l => out.push(l));
416
+ out.push("");
417
+ out.push(`Total files: ${filePaths.length}`);
418
+ out.push("");
419
+
420
+ out.push(divider);
421
+ out.push("FILE CONTENTS");
422
+ out.push(divider);
423
+ }
424
+
425
+ // Add file to current part
426
+ out.push(...fileContent);
427
+ currentSize += contentSize;
428
+ currentFiles.push(rel);
429
+ });
430
+
431
+ // Write final part
432
+ out.push("");
433
+ out.push(divider);
434
+ out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
435
+ out.push(divider);
436
+
437
+ const fileName = `${rootName}-part-${currentPart}.txt`;
438
+ const filePath = path.join(process.cwd(), fileName);
439
+ fs.writeFileSync(filePath, out.join("\n"), "utf8");
440
+ const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
441
+
442
+ results.push({
443
+ file: filePath,
444
+ size: sizeKB,
445
+ files: currentFiles.length,
446
+ part: currentPart
447
+ });
448
+
449
+ return results;
450
+ }
451
+
196
452
  // ── main ──────────────────────────────────────────────────────────────────────
197
453
 
198
454
  const args = process.argv.slice(2);
199
455
 
456
+ // Check if completion is already installed, install if not
457
+ function checkAndInstallCompletion() {
458
+ const { execSync } = require('child_process');
459
+ const path = require('path');
460
+ const os = require('os');
461
+ const fs = require('fs');
462
+
463
+ try {
464
+ const homeDir = os.homedir();
465
+ const shell = process.env.SHELL || '';
466
+ const platform = process.platform;
467
+ let completionInstalled = false;
468
+
469
+ if (platform === 'win32') {
470
+ // Check PowerShell completion - try multiple profile locations
471
+ const profilePaths = [
472
+ path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'),
473
+ path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
474
+ path.join(homeDir, '.config', 'powershell', 'Microsoft.PowerShell_profile.ps1')
475
+ ];
476
+
477
+ for (const profilePath of profilePaths) {
478
+ if (fs.existsSync(profilePath)) {
479
+ const profileContent = fs.readFileSync(profilePath, 'utf8');
480
+ if (profileContent.includes('make-folder-txt-completion')) {
481
+ completionInstalled = true;
482
+ break;
483
+ }
484
+ }
485
+ }
486
+ } else if (shell.includes('zsh')) {
487
+ // Check zsh completion
488
+ const zshrc = path.join(homeDir, '.zshrc');
489
+ if (fs.existsSync(zshrc)) {
490
+ const zshrcContent = fs.readFileSync(zshrc, 'utf8');
491
+ if (zshrcContent.includes('make-folder-txt')) {
492
+ completionInstalled = true;
493
+ }
494
+ }
495
+ } else {
496
+ // Check bash completion
497
+ const bashrc = path.join(homeDir, '.bashrc');
498
+ if (fs.existsSync(bashrc)) {
499
+ const bashrcContent = fs.readFileSync(bashrc, 'utf8');
500
+ if (bashrcContent.includes('make-folder-txt-completion')) {
501
+ completionInstalled = true;
502
+ }
503
+ }
504
+ }
505
+
506
+ // If completion is not installed, install it automatically
507
+ if (!completionInstalled) {
508
+ console.log('🔧 Installing shell autocompletion for make-folder-txt...');
509
+
510
+ if (platform === 'win32') {
511
+ // Windows PowerShell
512
+ try {
513
+ execSync('powershell -Command "Get-Host"', { stdio: 'ignore' });
514
+ const installScript = path.join(__dirname, '..', 'completion', 'install-powershell-completion.ps1');
515
+ execSync(`powershell -ExecutionPolicy Bypass -File "${installScript}"`, { stdio: 'ignore' });
516
+ console.log('✅ PowerShell completion installed!');
517
+ } catch (err) {
518
+ // Silent fail for PowerShell
519
+ }
520
+ } else if (shell.includes('zsh')) {
521
+ // zsh
522
+ try {
523
+ const zshrc = path.join(homeDir, '.zshrc');
524
+ const completionDir = path.join(homeDir, '.zsh', 'completions');
525
+ execSync(`mkdir -p "${completionDir}"`, { stdio: 'ignore' });
526
+ const completionPath = path.join(__dirname, '..', 'completion', 'make-folder-txt-completion.zsh');
527
+ execSync(`cp "${completionPath}" "${completionDir}/_make-folder-txt"`, { stdio: 'ignore' });
528
+
529
+ try {
530
+ const zshrcContent = fs.readFileSync(zshrc, 'utf8');
531
+ if (!zshrcContent.includes('fpath+=~/.zsh/completions')) {
532
+ fs.appendFileSync(zshrc, '\n# make-folder-txt completion\nfpath+=~/.zsh/completions\nautoload -U compinit && compinit\n');
533
+ }
534
+ } catch (e) {
535
+ fs.writeFileSync(zshrc, '# make-folder-txt completion\nfpath+=~/.zsh/completions\nautoload -U compinit && compinit\n');
536
+ }
537
+ console.log('✅ Zsh completion installed!');
538
+ } catch (err) {
539
+ // Silent fail for zsh
540
+ }
541
+ } else {
542
+ // bash
543
+ try {
544
+ const bashrc = path.join(homeDir, '.bashrc');
545
+ const completionPath = path.join(__dirname, '..', 'completion', 'make-folder-txt-completion.bash');
546
+ try {
547
+ const bashrcContent = fs.readFileSync(bashrc, 'utf8');
548
+ if (!bashrcContent.includes('make-folder-txt-completion.bash')) {
549
+ fs.appendFileSync(bashrc, `\n# make-folder-txt completion\nsource "${completionPath}"\n`);
550
+ }
551
+ } catch (e) {
552
+ fs.writeFileSync(bashrc, `# make-folder-txt completion\nsource "${completionPath}"\n`);
553
+ }
554
+ console.log('✅ Bash completion installed!');
555
+ } catch (err) {
556
+ // Silent fail for bash
557
+ }
558
+ }
559
+
560
+ console.log('💡 Restart your terminal to enable autocompletion');
561
+ console.log('');
562
+ }
563
+ } catch (err) {
564
+ // Silent fail - don't interrupt the main functionality
565
+ }
566
+ }
567
+
568
+ // Run completion check on first run (but not for help/version commands)
569
+ if (!args.includes("--help") && !args.includes("-h") &&
570
+ !args.includes("--version") && !args.includes("-v") &&
571
+ !args.includes("--install-completion")) {
572
+ checkAndInstallCompletion();
573
+ }
574
+
200
575
  if (args.includes("--install-completion")) {
201
576
  const { execSync } = require('child_process');
202
577
  const path = require('path');
@@ -205,8 +580,26 @@ if (args.includes("--install-completion")) {
205
580
  try {
206
581
  const homeDir = os.homedir();
207
582
  const shell = process.env.SHELL || '';
583
+ const platform = process.platform;
208
584
 
209
- if (shell.includes('zsh')) {
585
+ if (platform === 'win32') {
586
+ // Windows - Check if PowerShell is available
587
+ try {
588
+ execSync('powershell -Command "Get-Host"', { stdio: 'ignore' });
589
+
590
+ // Install PowerShell completion
591
+ const completionScript = path.join(__dirname, '..', 'completion', 'make-folder-txt-completion.ps1');
592
+ const installScript = path.join(__dirname, '..', 'completion', 'install-powershell-completion.ps1');
593
+
594
+ // Run the PowerShell installation script
595
+ execSync(`powershell -ExecutionPolicy Bypass -File "${installScript}"`, { stdio: 'inherit' });
596
+
597
+ } catch (err) {
598
+ console.error('❌ Failed to install PowerShell completion:', err.message);
599
+ process.exit(1);
600
+ }
601
+
602
+ } else if (shell.includes('zsh')) {
210
603
  // Install for zsh
211
604
  const zshrc = path.join(homeDir, '.zshrc');
212
605
  const completionDir = path.join(homeDir, '.zsh', 'completions');
@@ -278,9 +671,13 @@ Dump an entire project folder into a single readable .txt file.
278
671
  --ignore-file, -ifi <names...> Ignore specific files by name
279
672
  --only-folder, -ofo <names...> Include only specific folders
280
673
  --only-file, -ofi <names...> Include only specific files
674
+ --skip-large <size> Skip files larger than specified size (default: 500KB)
675
+ --no-skip Include all files regardless of size
676
+ --split-method <method> Split output: folder, file, or size
677
+ --split-size <size> Split output when size exceeds limit (requires --split-method size)
281
678
  --copy Copy output to clipboard
282
679
  --force Include everything (overrides all ignore patterns)
283
- --install-completion Install shell autocompletion (bash/zsh)
680
+ --install-completion Install shell autocompletion (bash/zsh/PowerShell) - usually automatic
284
681
  --help, -h Show this help message
285
682
  --version, -v Show version information
286
683
 
@@ -288,6 +685,12 @@ Dump an entire project folder into a single readable .txt file.
288
685
  make-folder-txt
289
686
  make-folder-txt --copy
290
687
  make-folder-txt --force
688
+ make-folder-txt --skip-large 400KB
689
+ make-folder-txt --skip-large 5GB
690
+ make-folder-txt --no-skip
691
+ make-folder-txt --split-method folder
692
+ make-folder-txt --split-method file
693
+ make-folder-txt --split-method size --split-size 5MB
291
694
  make-folder-txt --install-completion
292
695
  make-folder-txt --ignore-folder node_modules dist
293
696
  make-folder-txt -ifo node_modules dist
@@ -319,6 +722,10 @@ const onlyFiles = new Set();
319
722
  let outputArg = null;
320
723
  let copyToClipboardFlag = false;
321
724
  let forceFlag = false;
725
+ let maxFileSize = 500 * 1024; // Default 500KB
726
+ let noSkipFlag = false;
727
+ let splitMethod = null; // 'folder', 'file', 'size'
728
+ let splitSize = null; // size in bytes
322
729
 
323
730
  for (let i = 0; i < args.length; i += 1) {
324
731
  const arg = args[i];
@@ -333,6 +740,81 @@ for (let i = 0; i < args.length; i += 1) {
333
740
  continue;
334
741
  }
335
742
 
743
+ if (arg === "--no-skip") {
744
+ noSkipFlag = true;
745
+ continue;
746
+ }
747
+
748
+ if (arg === "--skip-large") {
749
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
750
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
751
+ process.exit(1);
752
+ }
753
+ maxFileSize = parseFileSize(args[i + 1]);
754
+ i += 1;
755
+ continue;
756
+ }
757
+
758
+ if (arg.startsWith("--skip-large=")) {
759
+ const value = arg.slice("--skip-large=".length);
760
+ if (!value) {
761
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
762
+ process.exit(1);
763
+ }
764
+ maxFileSize = parseFileSize(value);
765
+ continue;
766
+ }
767
+
768
+ if (arg === "--split-method") {
769
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
770
+ console.error("Error: --split-method requires a method (folder, file, or size).");
771
+ process.exit(1);
772
+ }
773
+ const method = args[i + 1].toLowerCase();
774
+ if (!['folder', 'file', 'size'].includes(method)) {
775
+ console.error("Error: --split-method must be one of: folder, file, size");
776
+ process.exit(1);
777
+ }
778
+ splitMethod = method;
779
+ i += 1;
780
+ continue;
781
+ }
782
+
783
+ if (arg.startsWith("--split-method=")) {
784
+ const value = arg.slice("--split-method=".length);
785
+ if (!value) {
786
+ console.error("Error: --split-method requires a method (folder, file, or size).");
787
+ process.exit(1);
788
+ }
789
+ const method = value.toLowerCase();
790
+ if (!['folder', 'file', 'size'].includes(method)) {
791
+ console.error("Error: --split-method must be one of: folder, file, size");
792
+ process.exit(1);
793
+ }
794
+ splitMethod = method;
795
+ continue;
796
+ }
797
+
798
+ if (arg === "--split-size") {
799
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
800
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
801
+ process.exit(1);
802
+ }
803
+ splitSize = parseFileSize(args[i + 1]);
804
+ i += 1;
805
+ continue;
806
+ }
807
+
808
+ if (arg.startsWith("--split-size=")) {
809
+ const value = arg.slice("--split-size=".length);
810
+ if (!value) {
811
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
812
+ process.exit(1);
813
+ }
814
+ splitSize = parseFileSize(value);
815
+ continue;
816
+ }
817
+
336
818
  if (arg === "--ignore-folder" || arg === "-ifo") {
337
819
  let consumed = 0;
338
820
  while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
@@ -451,6 +933,17 @@ for (let i = 0; i < args.length; i += 1) {
451
933
  process.exit(1);
452
934
  }
453
935
 
936
+ // Validate split options
937
+ if (splitMethod === 'size' && !splitSize) {
938
+ console.error("Error: --split-method size requires --split-size to be specified.");
939
+ process.exit(1);
940
+ }
941
+
942
+ if (splitSize && splitMethod !== 'size') {
943
+ console.error("Error: --split-size can only be used with --split-method size.");
944
+ process.exit(1);
945
+ }
946
+
454
947
  const folderPath = process.cwd();
455
948
  const rootName = path.basename(folderPath);
456
949
 
@@ -474,7 +967,47 @@ const { lines: treeLines, filePaths } = collectFiles(
474
967
  { hasOnlyFilters, rootName, txtIgnore, force: forceFlag },
475
968
  );
476
969
 
477
- // ── build output ──────────────────────────────────────────────────────────────
970
+ // ── handle splitting ──────────────────────────────────────────────────────────────
971
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
972
+
973
+ if (splitMethod) {
974
+ console.log(`🔧 Splitting output by: ${splitMethod}`);
975
+
976
+ let results;
977
+
978
+ if (splitMethod === 'folder') {
979
+ results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
980
+ } else if (splitMethod === 'file') {
981
+ results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
982
+ } else if (splitMethod === 'size') {
983
+ results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
984
+ }
985
+
986
+ console.log(`✅ Done! Created ${results.length} split files:`);
987
+ console.log('');
988
+
989
+ results.forEach((result, index) => {
990
+ if (splitMethod === 'folder') {
991
+ console.log(`📁 Folder: ${result.folder}`);
992
+ } else if (splitMethod === 'file') {
993
+ console.log(`📄 File: ${result.fileName}`);
994
+ } else if (splitMethod === 'size') {
995
+ console.log(`📦 Part ${result.part}`);
996
+ }
997
+ console.log(`📄 Output : ${result.file}`);
998
+ console.log(`📊 Size : ${result.size} KB`);
999
+ console.log(`🗂️ Files : ${result.files}`);
1000
+ console.log('');
1001
+ });
1002
+
1003
+ if (copyToClipboardFlag) {
1004
+ console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
1005
+ }
1006
+
1007
+ process.exit(0);
1008
+ }
1009
+
1010
+ // ── build output (no splitting) ───────────────────────────────────────────────────
478
1011
  const out = [];
479
1012
  const divider = "=".repeat(80);
480
1013
  const subDivider = "-".repeat(80);
@@ -499,11 +1032,12 @@ out.push("FILE CONTENTS");
499
1032
  out.push(divider);
500
1033
 
501
1034
  filePaths.forEach(({ abs, rel }) => {
502
- out.push("");
503
- out.push(subDivider);
504
- out.push(`FILE: ${rel}`);
505
- out.push(subDivider);
506
- out.push(readContent(abs, forceFlag));
1035
+ out.push("");
1036
+ out.push(subDivider);
1037
+ out.push(`FILE: ${rel}`);
1038
+ out.push(subDivider);
1039
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
1040
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
507
1041
  });
508
1042
 
509
1043
  out.push("");
@@ -0,0 +1,75 @@
1
+ # PowerShell completion installation script for make-folder-txt
2
+
3
+ function Install-MakeFolderTxtCompletion {
4
+ param(
5
+ [switch]$Force,
6
+ [switch]$CurrentUser
7
+ )
8
+
9
+ $ErrorActionPreference = 'Stop'
10
+
11
+ # Determine PowerShell profile path
12
+ if ($CurrentUser) {
13
+ $profilePath = $PROFILE.CurrentUserCurrentHost
14
+ } else {
15
+ $profilePath = $PROFILE.AllUsersCurrentHost
16
+ }
17
+
18
+ # Create profile directory if it doesn't exist
19
+ $profileDir = Split-Path $profilePath -Parent
20
+ if (-not (Test-Path $profileDir)) {
21
+ try {
22
+ New-Item -ItemType Directory -Path $profileDir -Force | Out-Null
23
+ Write-Host "Created profile directory: $profileDir" -ForegroundColor Green
24
+ } catch {
25
+ Write-Error "Failed to create profile directory: $profileDir"
26
+ return
27
+ }
28
+ }
29
+
30
+ # Get the completion script content
31
+ $completionScriptPath = Join-Path $PSScriptRoot 'make-folder-txt-completion.ps1'
32
+ if (-not (Test-Path $completionScriptPath)) {
33
+ Write-Error "Completion script not found: $completionScriptPath"
34
+ return
35
+ }
36
+
37
+ $completionContent = Get-Content $completionScriptPath -Raw
38
+
39
+ # Check if completion is already installed
40
+ if (Test-Path $profilePath) {
41
+ $profileContent = Get-Content $profilePath -Raw
42
+ if ($profileContent -match 'make-folder-txt.*completion') {
43
+ if (-not $Force) {
44
+ Write-Host "make-folder-txt completion is already installed in $profilePath" -ForegroundColor Yellow
45
+ Write-Host "Use -Force to reinstall" -ForegroundColor Yellow
46
+ return
47
+ }
48
+ Write-Host "Removing existing completion..." -ForegroundColor Yellow
49
+ # Remove existing completion
50
+ $profileContent = $profileContent -replace '(?s)# make-folder-txt completion.*?Register-ArgumentComplester.*?Export-ModuleMember.*?\n', ''
51
+ Set-Content $profilePath $profileContent -Force
52
+ }
53
+ }
54
+
55
+ # Add completion to profile
56
+ $completionBlock = @"
57
+
58
+ # make-folder-txt completion
59
+ $completionContent
60
+ "@
61
+
62
+ try {
63
+ Add-Content $profilePath $completionBlock -Force
64
+ Write-Host "✅ PowerShell completion installed successfully!" -ForegroundColor Green
65
+ Write-Host "Added to: $profilePath" -ForegroundColor Cyan
66
+ Write-Host "Restart PowerShell or run: . `$profile" -ForegroundColor Cyan
67
+ } catch {
68
+ Write-Error "Failed to install completion: $_"
69
+ }
70
+ }
71
+
72
+ # Auto-install if script is run directly
73
+ if ($MyInvocation.InvocationName -eq $MyInvocation.MyCommand.Name) {
74
+ Install-MakeFolderTxtCompletion -CurrentUser
75
+ }
@@ -7,7 +7,7 @@ _make_folder_txt_completion() {
7
7
  cur="${COMP_WORDS[COMP_CWORD]}"
8
8
  prev="${COMP_WORDS[COMP_CWORD-1]}"
9
9
 
10
- opts="--ignore-folder -ifo --ignore-file -ifi --only-folder -ofo --only-file -ofi --copy --force --help --version -h -v"
10
+ opts="--ignore-folder -ifo --ignore-file -ifi --only-folder -ofo --only-file -ofi --skip-large --no-skip --split-method --split-size --copy --force --help --version -h -v"
11
11
 
12
12
  case "${prev}" in
13
13
  --ignore-folder|-ifo)
@@ -28,6 +28,24 @@ _make_folder_txt_completion() {
28
28
  COMPREPLY=( $(compgen -W "${folders}" -- ${cur}) )
29
29
  return 0
30
30
  ;;
31
+ --skip-large)
32
+ # Complete with common size formats
33
+ local sizes="100KB 200KB 400KB 500KB 1MB 5MB 10MB 100MB 1GB 5GB"
34
+ COMPREPLY=( $(compgen -W "${sizes}" -- ${cur}) )
35
+ return 0
36
+ ;;
37
+ --split-method)
38
+ # Complete with split methods
39
+ local methods="folder file size"
40
+ COMPREPLY=( $(compgen -W "${methods}" -- ${cur}) )
41
+ return 0
42
+ ;;
43
+ --split-size)
44
+ # Complete with common size formats
45
+ local sizes="1MB 5MB 10MB 50MB 100MB 500MB 1GB"
46
+ COMPREPLY=( $(compgen -W "${sizes}" -- ${cur}) )
47
+ return 0
48
+ ;;
31
49
  *)
32
50
  ;;
33
51
  esac
@@ -0,0 +1,95 @@
1
+ # make-folder-txt PowerShell completion script
2
+
3
+ Register-ArgumentCompleter -Native -CommandName 'make-folder-txt' -ScriptBlock {
4
+ param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
5
+
6
+ # Get the current argument being completed
7
+ $currentArgument = $wordToComplete
8
+
9
+ # Define available options
10
+ $options = @(
11
+ '--ignore-folder', '-ifo',
12
+ '--ignore-file', '-ifi',
13
+ '--only-folder', '-ofo',
14
+ '--only-file', '-ofi',
15
+ '--skip-large',
16
+ '--no-skip',
17
+ '--split-method',
18
+ '--split-size',
19
+ '--copy',
20
+ '--force',
21
+ '--install-completion',
22
+ '--help', '-h',
23
+ '--version', '-v'
24
+ )
25
+
26
+ # Get the previous parameter to determine context
27
+ $previousParameter = if ($commandAst.CommandElements.Count -gt 1) {
28
+ $commandAst.CommandElements[-2].Extent.Text
29
+ } else {
30
+ ''
31
+ }
32
+
33
+ switch ($previousParameter) {
34
+ { $_ -in '--ignore-folder', '-ifo', '--only-folder', '-ofo' } {
35
+ # Complete with folder names
36
+ try {
37
+ $folders = Get-ChildItem -Directory -Name | Where-Object { $_ -like "*$currentArgument*" }
38
+ return $folders | ForEach-Object {
39
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Folder: $_")
40
+ }
41
+ } catch {
42
+ return @()
43
+ }
44
+ }
45
+ { $_ -in '--ignore-file', '-ifi', '--only-file', '-ofi' } {
46
+ # Complete with file names
47
+ try {
48
+ $files = Get-ChildItem -File -Name | Where-Object { $_ -like "*$currentArgument*" }
49
+ return $files | ForEach-Object {
50
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "File: $_")
51
+ }
52
+ } catch {
53
+ return @()
54
+ }
55
+ }
56
+ '--skip-large' {
57
+ # Complete with common size formats
58
+ $sizes = @('100KB', '200KB', '400KB', '500KB', '1MB', '5MB', '10MB', '100MB', '1GB', '5GB')
59
+ $matchingSizes = $sizes | Where-Object { $_ -like "*$currentArgument*" }
60
+ return $matchingSizes | ForEach-Object {
61
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Size: $_")
62
+ }
63
+ }
64
+ '--split-method' {
65
+ # Complete with split methods
66
+ $methods = @('folder', 'file', 'size')
67
+ $matchingMethods = $methods | Where-Object { $_ -like "*$currentArgument*" }
68
+ return $matchingMethods | ForEach-Object {
69
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Method: $_")
70
+ }
71
+ }
72
+ '--split-size' {
73
+ # Complete with common size formats
74
+ $sizes = @('1MB', '5MB', '10MB', '50MB', '100MB', '500MB', '1GB')
75
+ $matchingSizes = $sizes | Where-Object { $_ -like "*$currentArgument*" }
76
+ return $matchingSizes | ForEach-Object {
77
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Size: $_")
78
+ }
79
+ }
80
+ default {
81
+ # Complete with options
82
+ if ($currentArgument -like '-*') {
83
+ $matchingOptions = $options | Where-Object { $_ -like "*$currentArgument*" }
84
+ return $matchingOptions | ForEach-Object {
85
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Option: $_")
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ return @()
92
+ }
93
+
94
+ # Export the completion function
95
+ Export-ModuleMember -Function *
@@ -12,6 +12,10 @@ _make_folder_txt() {
12
12
  '-ofo[Include only specific folders]:folder:_directories'
13
13
  '--only-file[Include only specific files]:file:_files'
14
14
  '-ofi[Include only specific files]:file:_files'
15
+ '--skip-large[Skip files larger than specified size]:size:(100KB 200KB 400KB 500KB 1MB 5MB 10MB 100MB 1GB 5GB)'
16
+ '--no-skip[Include all files regardless of size]'
17
+ '--split-method[Split output by method]:method:(folder file size)'
18
+ '--split-size[Split output when size exceeds limit]:size:(1MB 5MB 10MB 50MB 100MB 500MB 1GB)'
15
19
  '--copy[Copy output to clipboard]'
16
20
  '--force[Include everything (overrides all ignore patterns)]'
17
21
  '--help[Show help message]'
@@ -0,0 +1,52 @@
1
+ ================================================================================
2
+ START OF FOLDER: make-folder-txt
3
+ ================================================================================
4
+
5
+ ================================================================================
6
+ PROJECT STRUCTURE
7
+ ================================================================================
8
+ Root: C:\Programming\make-folder-txt
9
+
10
+ make-folder-txt/
11
+ ├── package.json
12
+
13
+ Total files: 1
14
+
15
+ ================================================================================
16
+ FILE CONTENTS
17
+ ================================================================================
18
+
19
+ --------------------------------------------------------------------------------
20
+ FILE: /package.json
21
+ --------------------------------------------------------------------------------
22
+ {
23
+ "name": "make-folder-txt",
24
+ "version": "2.0.1",
25
+ "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
26
+ "main": "bin/make-folder-txt.js",
27
+ "bin": {
28
+ "make-folder-txt": "bin/make-folder-txt.js"
29
+ },
30
+ "scripts": {
31
+ "test": "echo \"No tests yet\" && exit 0"
32
+ },
33
+ "keywords": [
34
+ "folder",
35
+ "dump",
36
+ "project",
37
+ "structure",
38
+ "txt",
39
+ "cli",
40
+ "export"
41
+ ],
42
+ "author": "Muhammad Saad Amin",
43
+ "license": "MIT",
44
+ "engines": {
45
+ "node": ">=14.0.0"
46
+ }
47
+ }
48
+
49
+
50
+ ================================================================================
51
+ END OF FOLDER: make-folder-txt
52
+ ================================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-folder-txt",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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": {