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