make-folder-txt 2.1.4 → 2.2.1

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.
@@ -11,12 +11,12 @@ make-folder-txt/
11
11
  ├── .git/ [skipped]
12
12
  ├── bin/
13
13
  │ └── make-folder-txt.js
14
- ├── completion/ [skipped]
14
+ ├── .txtconfig
15
15
  ├── LICENSE
16
16
  ├── package.json
17
17
  └── README.md
18
18
 
19
- Total files: 4
19
+ Total files: 5
20
20
 
21
21
  ================================================================================
22
22
  FILE CONTENTS
@@ -476,18 +476,271 @@ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize
476
476
  return results;
477
477
  }
478
478
 
479
- // ── main ──────────────────────────────────────────────────────────────────────
479
+ // ── configuration ────────────────────────────────────────────────────────────────
480
+
481
+ function createInteractiveConfig() {
482
+ const readline = require('readline');
483
+ const fs = require('fs');
484
+ const path = require('path');
485
+ const os = require('os');
486
+
487
+ const rl = readline.createInterface({
488
+ input: process.stdin,
489
+ output: process.stdout
490
+ });
491
+
492
+ console.log('\n🔧 make-folder-txt Configuration Setup');
493
+ console.log('=====================================\n');
494
+
495
+ return new Promise((resolve) => {
496
+ const config = {
497
+ maxFileSize: '500KB',
498
+ splitMethod: 'none',
499
+ splitSize: '5MB',
500
+ copyToClipboard: false,
501
+ ignoreFolders: [],
502
+ ignoreFiles: []
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: 'ignoreFolders',
557
+ question: 'Ignore folders (comma-separated, or press Enter to skip): ',
558
+ default: '',
559
+ transform: (value) => value ? value.split(',').map(f => f.trim()).filter(f => f) : []
560
+ },
561
+ {
562
+ key: 'ignoreFiles',
563
+ question: 'Ignore files (comma-separated, or press Enter to skip): ',
564
+ default: '',
565
+ transform: (value) => value ? value.split(',').map(f => f.trim()).filter(f => f) : []
566
+ }
567
+ ];
568
+
569
+ function askQuestion() {
570
+ if (currentStep >= questions.length) {
571
+ // Save configuration
572
+ saveConfig();
573
+ return;
574
+ }
575
+
576
+ const q = questions[currentStep];
577
+
578
+ // Skip if conditional ask returns false
579
+ if (q.ask && !q.ask()) {
580
+ currentStep++;
581
+ askQuestion();
582
+ return;
583
+ }
584
+
585
+ const defaultValue = typeof q.default === 'function' ? q.default() : q.default;
586
+ rl.question(q.question + (defaultValue ? `(${defaultValue}) ` : ''), (answer) => {
587
+ const value = answer.trim() || defaultValue;
588
+
589
+ // Validate input
590
+ if (q.validate) {
591
+ const validation = q.validate(value);
592
+ if (validation !== true) {
593
+ console.log(`❌ ${validation}`);
594
+ askQuestion();
595
+ return;
596
+ }
597
+ }
598
+
599
+ // Transform value if needed
600
+ if (q.transform) {
601
+ config[q.key] = q.transform(value);
602
+ } else {
603
+ config[q.key] = value;
604
+ }
605
+
606
+ currentStep++;
607
+ askQuestion();
608
+ });
609
+ }
610
+
611
+ function saveConfig() {
612
+ try {
613
+ // Create .txtconfig file
614
+ const configPath = path.join(process.cwd(), '.txtconfig');
615
+ const configContent = `{
616
+ "maxFileSize": "${config.maxFileSize}",
617
+ "splitMethod": "${config.splitMethod}",
618
+ "splitSize": "${config.splitSize}",
619
+ "copyToClipboard": ${config.copyToClipboard},
620
+ "ignoreFolders": ${JSON.stringify(config.ignoreFolders)},
621
+ "ignoreFiles": ${JSON.stringify(config.ignoreFiles)}
622
+ }`;
623
+
624
+ fs.writeFileSync(configPath, configContent);
625
+
626
+ // Update .txtignore if there are ignore patterns
627
+ if (config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0) {
628
+ const ignorePath = path.join(process.cwd(), '.txtignore');
629
+ let ignoreContent = '';
630
+
631
+ // Read existing content if file exists
632
+ if (fs.existsSync(ignorePath)) {
633
+ ignoreContent = fs.readFileSync(ignorePath, 'utf8');
634
+ if (!ignoreContent.endsWith('\n')) ignoreContent += '\n';
635
+ }
636
+
637
+ // Add new ignore patterns
638
+ const newPatterns = [
639
+ ...config.ignoreFolders.map(f => f.endsWith('/') ? f : `${f}/`),
640
+ ...config.ignoreFiles
641
+ ];
642
+
643
+ if (newPatterns.length > 0) {
644
+ ignoreContent += '\n# Added by make-folder-txt config\n';
645
+ ignoreContent += newPatterns.join('\n') + '\n';
646
+ fs.writeFileSync(ignorePath, ignoreContent);
647
+ }
648
+ }
480
649
 
481
- const args = process.argv.slice(2);
650
+ console.log('\n✅ Configuration saved successfully!');
651
+ console.log(`📄 Config file: ${configPath}`);
652
+ if (config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0) {
653
+ console.log(`📝 Ignore patterns added to .txtignore`);
654
+ }
655
+ console.log('\n💡 You can now run: make-folder-txt --use-config');
656
+
657
+ } catch (err) {
658
+ console.error('❌ Error saving configuration:', err.message);
659
+ } finally {
660
+ rl.close();
661
+ resolve();
662
+ }
663
+ }
482
664
 
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);
665
+ askQuestion();
666
+ });
667
+ }
668
+
669
+ function loadConfig() {
670
+ const fs = require('fs');
671
+ const path = require('path');
672
+
673
+ try {
674
+ const configPath = path.join(process.cwd(), '.txtconfig');
675
+ if (!fs.existsSync(configPath)) {
676
+ console.error('❌ .txtconfig file not found. Run --make-config first.');
677
+ process.exit(1);
678
+ }
679
+
680
+ const configContent = fs.readFileSync(configPath, 'utf8');
681
+ return JSON.parse(configContent);
682
+ } catch (err) {
683
+ console.error('❌ Error loading configuration:', err.message);
684
+ process.exit(1);
685
+ }
487
686
  }
488
687
 
489
- if (args.includes("--help") || args.includes("-h")) {
490
- console.log(`
688
+ // ── main ──────────────────────────────────────────────────────────────────────
689
+
690
+ async function main() {
691
+ const args = process.argv.slice(2);
692
+
693
+ if (args.includes("-v") || args.includes("--version")) {
694
+ console.log(`v${version}`);
695
+ console.log("Built by Muhammad Saad Amin");
696
+ process.exit(0);
697
+ }
698
+
699
+ if (args.includes("--make-config")) {
700
+ await createInteractiveConfig();
701
+ process.exit(0);
702
+ }
703
+
704
+ if (args.includes("--use-config")) {
705
+ const config = loadConfig();
706
+
707
+ // Apply config to command line arguments
708
+ const newArgs = [];
709
+
710
+ // Add max file size if not default
711
+ if (config.maxFileSize !== '500KB') {
712
+ newArgs.push('--skip-large', config.maxFileSize);
713
+ }
714
+
715
+ // Add split method if not none
716
+ if (config.splitMethod !== 'none') {
717
+ newArgs.push('--split-method', config.splitMethod);
718
+ if (config.splitMethod === 'size') {
719
+ newArgs.push('--split-size', config.splitSize);
720
+ }
721
+ }
722
+
723
+ // Add copy to clipboard if true
724
+ if (config.copyToClipboard) {
725
+ newArgs.push('--copy');
726
+ }
727
+
728
+ // Add ignore folders
729
+ if (config.ignoreFolders.length > 0) {
730
+ newArgs.push('--ignore-folder', ...config.ignoreFolders);
731
+ }
732
+
733
+ // Add ignore files
734
+ if (config.ignoreFiles.length > 0) {
735
+ newArgs.push('--ignore-file', ...config.ignoreFiles);
736
+ }
737
+
738
+ // Replace args with config-based args
739
+ args.splice(0, args.length, ...newArgs);
740
+ }
741
+
742
+ if (args.includes("--help") || args.includes("-h")) {
743
+ console.log(`
491
744
  \x1b[33mmake-folder-txt\x1b[0m
492
745
  Dump an entire project folder into a single readable .txt file.
493
746
 
@@ -505,6 +758,8 @@ Dump an entire project folder into a single readable .txt file.
505
758
  --split-size <size> Split output when size exceeds limit (requires --split-method size)
506
759
  --copy Copy output to clipboard
507
760
  --force Include everything (overrides all ignore patterns)
761
+ --make-config Create interactive configuration
762
+ --use-config Use configuration from .txtconfig file
508
763
  --help, -h Show this help message
509
764
  --version, -v Show version information
510
765
 
@@ -526,6 +781,8 @@ Dump an entire project folder into a single readable .txt file.
526
781
  make-folder-txt -ofo src docs
527
782
  make-folder-txt --only-file package.json README.md
528
783
  make-folder-txt -ofi package.json README.md
784
+ make-folder-txt --make-config
785
+ make-folder-txt --use-config
529
786
 
530
787
  \x1b[33m.TXTIGNORE FILE\x1b[0m
531
788
  Create a .txtignore file in your project root to specify files/folders to ignore.
@@ -538,378 +795,389 @@ Dump an entire project folder into a single readable .txt file.
538
795
  coverage/
539
796
  LICENSE
540
797
  `);
541
- process.exit(0);
542
- }
798
+ process.exit(0);
799
+ }
543
800
 
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
- }
801
+ const ignoreDirs = new Set(IGNORE_DIRS);
802
+ const ignoreFiles = new Set(IGNORE_FILES);
803
+ const onlyFolders = new Set();
804
+ const onlyFiles = new Set();
805
+ let outputArg = null;
806
+ let copyToClipboardFlag = false;
807
+ let forceFlag = false;
808
+ let maxFileSize = 500 * 1024; // Default 500KB
809
+ let noSkipFlag = false;
810
+ let splitMethod = null; // 'folder', 'file', 'size'
811
+ let splitSize = null; // size in bytes
812
+
813
+ for (let i = 0; i < args.length; i += 1) {
814
+ const arg = args[i];
815
+
816
+ if (arg === "--copy") {
817
+ copyToClipboardFlag = true;
818
+ continue;
819
+ }
563
820
 
564
- if (arg === "--force") {
565
- forceFlag = true;
566
- continue;
567
- }
821
+ if (arg === "--force") {
822
+ forceFlag = true;
823
+ continue;
824
+ }
568
825
 
569
- if (arg === "--no-skip") {
570
- noSkipFlag = true;
571
- continue;
572
- }
826
+ if (arg === "--no-skip") {
827
+ noSkipFlag = true;
828
+ continue;
829
+ }
573
830
 
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
- }
831
+ if (arg === "--skip-large") {
832
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
833
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
834
+ process.exit(1);
835
+ }
836
+ maxFileSize = parseFileSize(args[i + 1]);
837
+ i += 1;
838
+ continue;
839
+ }
583
840
 
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
- }
841
+ if (arg.startsWith("--skip-large=")) {
842
+ const value = arg.slice("--skip-large=".length);
843
+ if (!value) {
844
+ console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
845
+ process.exit(1);
846
+ }
847
+ maxFileSize = parseFileSize(value);
848
+ continue;
849
+ }
593
850
 
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
- }
851
+ if (arg === "--split-method") {
852
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
853
+ console.error("Error: --split-method requires a method (folder, file, or size).");
854
+ process.exit(1);
855
+ }
856
+ const method = args[i + 1].toLowerCase();
857
+ if (!['folder', 'file', 'size'].includes(method)) {
858
+ console.error("Error: --split-method must be one of: folder, file, size");
859
+ process.exit(1);
860
+ }
861
+ splitMethod = method;
862
+ i += 1;
863
+ continue;
864
+ }
608
865
 
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
- }
866
+ if (arg.startsWith("--split-method=")) {
867
+ const value = arg.slice("--split-method=".length);
868
+ if (!value) {
869
+ console.error("Error: --split-method requires a method (folder, file, or size).");
870
+ process.exit(1);
871
+ }
872
+ const method = value.toLowerCase();
873
+ if (!['folder', 'file', 'size'].includes(method)) {
874
+ console.error("Error: --split-method must be one of: folder, file, size");
875
+ process.exit(1);
876
+ }
877
+ splitMethod = method;
878
+ continue;
879
+ }
623
880
 
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
- }
881
+ if (arg === "--split-size") {
882
+ if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
883
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
884
+ process.exit(1);
885
+ }
886
+ splitSize = parseFileSize(args[i + 1]);
887
+ i += 1;
888
+ continue;
889
+ }
633
890
 
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
- }
891
+ if (arg.startsWith("--split-size=")) {
892
+ const value = arg.slice("--split-size=".length);
893
+ if (!value) {
894
+ console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
895
+ process.exit(1);
896
+ }
897
+ splitSize = parseFileSize(value);
898
+ continue;
899
+ }
643
900
 
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
- }
901
+ if (arg === "--ignore-folder" || arg === "-ifo") {
902
+ let consumed = 0;
903
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
904
+ // Normalize the folder name: remove backslashes, trailing slashes, and leading ./
905
+ let folderName = args[i + 1];
906
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
907
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
908
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
909
+ ignoreDirs.add(folderName);
910
+ i += 1;
911
+ consumed += 1;
912
+ }
913
+ if (consumed === 0) {
914
+ console.error("Error: --ignore-folder requires at least one folder name.");
915
+ process.exit(1);
916
+ }
917
+ continue;
918
+ }
662
919
 
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
- }
920
+ if (arg.startsWith("--ignore-folder=") || arg.startsWith("-ifo=")) {
921
+ const value = arg.startsWith("--ignore-folder=")
922
+ ? arg.slice("--ignore-folder=".length)
923
+ : arg.slice("-ifo=".length);
924
+ if (!value) {
925
+ console.error("Error: --ignore-folder requires a folder name.");
926
+ process.exit(1);
927
+ }
928
+ // Normalize the folder name
929
+ let folderName = value;
930
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
931
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
932
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
933
+ ignoreDirs.add(folderName);
934
+ continue;
935
+ }
679
936
 
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
- }
937
+ if (arg === "--ignore-file" || arg === "-ifi") {
938
+ let consumed = 0;
939
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
940
+ ignoreFiles.add(args[i + 1]);
941
+ i += 1;
942
+ consumed += 1;
943
+ }
944
+ if (consumed === 0) {
945
+ console.error("Error: --ignore-file requires at least one file name.");
946
+ process.exit(1);
947
+ }
948
+ continue;
949
+ }
693
950
 
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
- }
951
+ if (arg.startsWith("--ignore-file=") || arg.startsWith("-ifi=")) {
952
+ const value = arg.startsWith("--ignore-file=")
953
+ ? arg.slice("--ignore-file=".length)
954
+ : arg.slice("-ifi=".length);
955
+ if (!value) {
956
+ console.error("Error: --ignore-file requires a file name.");
957
+ process.exit(1);
958
+ }
959
+ ignoreFiles.add(value);
960
+ continue;
961
+ }
705
962
 
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
- }
963
+ if (arg === "--only-folder" || arg === "-ofo") {
964
+ let consumed = 0;
965
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
966
+ // Normalize the folder name
967
+ let folderName = args[i + 1];
968
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
969
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
970
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
971
+ onlyFolders.add(folderName);
972
+ i += 1;
973
+ consumed += 1;
974
+ }
975
+ if (consumed === 0) {
976
+ console.error("Error: --only-folder requires at least one folder name.");
977
+ process.exit(1);
978
+ }
979
+ continue;
980
+ }
981
+
982
+ if (arg.startsWith("--only-folder=") || arg.startsWith("-ofo=")) {
983
+ const value = arg.startsWith("--only-folder=")
984
+ ? arg.slice("--only-folder=".length)
985
+ : arg.slice("-ofo=".length);
986
+ if (!value) {
987
+ console.error("Error: --only-folder requires a folder name.");
988
+ process.exit(1);
989
+ }
990
+ // Normalize the folder name
991
+ let folderName = value;
992
+ folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
993
+ folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
994
+ folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
995
+ onlyFolders.add(folderName);
996
+ continue;
997
+ }
998
+
999
+ if (arg === "--only-file" || arg === "-ofi") {
1000
+ let consumed = 0;
1001
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1002
+ onlyFiles.add(args[i + 1]);
1003
+ i += 1;
1004
+ consumed += 1;
1005
+ }
1006
+ if (consumed === 0) {
1007
+ console.error("Error: --only-file requires at least one file name.");
1008
+ process.exit(1);
1009
+ }
1010
+ continue;
1011
+ }
1012
+
1013
+ if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
1014
+ const value = arg.startsWith("--only-file=")
1015
+ ? arg.slice("--only-file=".length)
1016
+ : arg.slice("-ofi=".length);
1017
+ if (!value) {
1018
+ console.error("Error: --only-file requires a file name.");
1019
+ process.exit(1);
1020
+ }
1021
+ onlyFiles.add(value);
1022
+ continue;
1023
+ }
724
1024
 
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.");
1025
+ // Unknown argument
1026
+ console.error(`Error: Unknown option "${arg}"`);
1027
+ console.error("Use --help for available options.");
731
1028
  process.exit(1);
732
1029
  }
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
1030
 
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.");
1031
+ // Validate split options
1032
+ if (splitMethod === 'size' && !splitSize) {
1033
+ console.error("Error: --split-method size requires --split-size to be specified");
751
1034
  process.exit(1);
752
1035
  }
753
- continue;
754
- }
755
1036
 
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.");
1037
+ if (splitSize && splitMethod !== 'size') {
1038
+ console.error("Error: --split-size can only be used with --split-method size");
762
1039
  process.exit(1);
763
1040
  }
764
- onlyFiles.add(value);
765
- continue;
766
- }
767
1041
 
768
- if (arg.startsWith("-")) {
769
- console.error(`Error: Unknown option "${arg}".`);
770
- process.exit(1);
771
- }
1042
+ // ── config ────────────────────────────────────────────────────────────────────────
772
1043
 
773
- if (!outputArg) {
774
- outputArg = arg;
775
- continue;
776
- }
1044
+ const folderPath = process.cwd();
1045
+ const rootName = path.basename(folderPath);
1046
+ const txtIgnore = readTxtIgnore(folderPath);
777
1047
 
778
- console.error(`Error: Unexpected argument "${arg}".`);
779
- process.exit(1);
780
- }
1048
+ // ── build tree & collect file paths ───────────────────────────────────────────────
781
1049
 
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
- }
1050
+ const { lines: treeLines, filePaths } = collectFiles(
1051
+ folderPath,
1052
+ folderPath,
1053
+ ignoreDirs,
1054
+ ignoreFiles,
1055
+ onlyFolders,
1056
+ onlyFiles,
1057
+ { rootName, txtIgnore, force: forceFlag }
1058
+ );
787
1059
 
788
- if (splitSize && splitMethod !== 'size') {
789
- console.error("Error: --split-size can only be used with --split-method size.");
790
- process.exit(1);
791
- }
1060
+ // ── build output filename ───────────────────────────────────────────────────────────
792
1061
 
793
- const folderPath = process.cwd();
794
- const rootName = path.basename(folderPath);
1062
+ let outputFile = outputArg || path.join(folderPath, `${rootName}.txt`);
795
1063
 
796
- // Read .txtignore file if it exists
797
- const txtIgnore = readTxtIgnore(folderPath);
1064
+ // ── handle output splitting ─────────────────────────────────────────────────────────
798
1065
 
799
- const outputFile = outputArg
800
- ? path.resolve(outputArg)
801
- : path.join(process.cwd(), `${rootName}.txt`);
1066
+ const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
802
1067
 
803
- console.log(`\n📂 Scanning: ${folderPath}`);
1068
+ if (splitMethod) {
1069
+ console.log(`🔧 Splitting output by: ${splitMethod}`);
1070
+
1071
+ let results;
1072
+
1073
+ if (splitMethod === 'folder') {
1074
+ results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
1075
+ } else if (splitMethod === 'file') {
1076
+ results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
1077
+ } else if (splitMethod === 'size') {
1078
+ results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
1079
+ }
1080
+
1081
+ console.log(`✅ Done! Created ${results.length} split files:`);
1082
+ console.log('');
1083
+
1084
+ results.forEach((result, index) => {
1085
+ if (splitMethod === 'folder') {
1086
+ console.log(`📁 Folder: ${result.folder}`);
1087
+ } else if (splitMethod === 'file') {
1088
+ console.log(`📄 File: ${result.fileName}`);
1089
+ } else if (splitMethod === 'size') {
1090
+ console.log(`📦 Part ${result.part}`);
1091
+ }
1092
+ console.log(`📄 Output : ${result.file}`);
1093
+ console.log(`📊 Size : ${result.size} KB`);
1094
+ console.log(`🗂️ Files : ${result.files}`);
1095
+ console.log('');
1096
+ });
1097
+
1098
+ if (copyToClipboardFlag) {
1099
+ console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
1100
+ }
1101
+
1102
+ process.exit(0);
1103
+ }
804
1104
 
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
- );
1105
+ // ── build output (no splitting) ───────────────────────────────────────────────────
1106
+ const out = [];
1107
+ const divider = "=".repeat(80);
1108
+ const subDivider = "-".repeat(80);
815
1109
 
816
- // ── handle splitting ──────────────────────────────────────────────────────────────
817
- const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
1110
+ out.push(divider);
1111
+ out.push(`START OF FOLDER: ${rootName}`);
1112
+ out.push(divider);
1113
+ out.push("");
818
1114
 
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
- }
1115
+ out.push(divider);
1116
+ out.push("PROJECT STRUCTURE");
1117
+ out.push(divider);
1118
+ out.push(`Root: ${folderPath}\n`);
855
1119
 
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
- });
1120
+ out.push(`${rootName}/`);
1121
+ treeLines.forEach(l => out.push(l));
1122
+ out.push("");
1123
+ out.push(`Total files: ${filePaths.length}`);
1124
+ out.push("");
1125
+
1126
+ out.push(divider);
1127
+ out.push("FILE CONTENTS");
1128
+ out.push(divider);
1129
+
1130
+ filePaths.forEach(({ abs, rel }) => {
1131
+ out.push("");
1132
+ out.push(subDivider);
1133
+ out.push(`FILE: ${rel}`);
1134
+ out.push(subDivider);
1135
+ out.push(readContent(abs, forceFlag, effectiveMaxSize));
1136
+ });
1137
+
1138
+ out.push("");
1139
+ out.push(divider);
1140
+ out.push(`END OF FOLDER: ${rootName}`);
1141
+ out.push(divider);
888
1142
 
889
- out.push("");
890
- out.push(divider);
891
- out.push(`END OF FOLDER: ${rootName}`);
892
- out.push(divider);
1143
+ fs.writeFileSync(outputFile, out.join("\n"), "utf8");
893
1144
 
894
- fs.writeFileSync(outputFile, out.join("\n"), "utf8");
1145
+ const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
1146
+ console.log(`✅ Done!`);
1147
+ console.log(`📄 Output : ${outputFile}`);
1148
+ console.log(`📊 Size : ${sizeKB} KB`);
1149
+ console.log(`🗂️ Files : ${filePaths.length}`);
895
1150
 
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}`);
1151
+ if (copyToClipboardFlag) {
1152
+ const content = fs.readFileSync(outputFile, 'utf8');
1153
+ const success = copyToClipboard(content);
1154
+ if (success) {
1155
+ console.log(`📋 Copied to clipboard!`);
1156
+ }
1157
+ }
901
1158
 
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
- }
1159
+ console.log('');
908
1160
  }
909
1161
 
910
- console.log('');
1162
+ // Run the main function
1163
+ main().catch(err => {
1164
+ console.error('❌ Error:', err.message);
1165
+ process.exit(1);
1166
+ });
911
1167
 
912
1168
 
1169
+ --------------------------------------------------------------------------------
1170
+ FILE: /.txtconfig
1171
+ --------------------------------------------------------------------------------
1172
+ {
1173
+ "maxFileSize": "400KB",
1174
+ "splitMethod": "none",
1175
+ "splitSize": "5MB",
1176
+ "copyToClipboard": false,
1177
+ "ignoreFolders": ["skip"],
1178
+ "ignoreFiles": ["skip"]
1179
+ }
1180
+
913
1181
  --------------------------------------------------------------------------------
914
1182
  FILE: /LICENSE
915
1183
  --------------------------------------------------------------------------------
@@ -941,7 +1209,7 @@ FILE: /package.json
941
1209
  --------------------------------------------------------------------------------
942
1210
  {
943
1211
  "name": "make-folder-txt",
944
- "version": "2.1.3",
1212
+ "version": "2.2.0",
945
1213
  "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
946
1214
  "main": "bin/make-folder-txt.js",
947
1215
  "bin": {