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