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.
- package/bin/make-folder-txt.js +592 -336
- package/make-folder-txt.txt +1577 -0
- package/package.json +1 -1
package/bin/make-folder-txt.js
CHANGED
|
@@ -449,18 +449,271 @@ 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
|
+
});
|
|
453
464
|
|
|
454
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
794
|
+
if (arg === "--force") {
|
|
795
|
+
forceFlag = true;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
541
798
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
799
|
+
if (arg === "--no-skip") {
|
|
800
|
+
noSkipFlag = true;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
546
803
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
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.");
|
|
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
|
-
|
|
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.");
|
|
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
|
-
|
|
742
|
-
console.error(`Error: Unknown option "${arg}".`);
|
|
743
|
-
process.exit(1);
|
|
744
|
-
}
|
|
1015
|
+
// ── config ────────────────────────────────────────────────────────────────────────
|
|
745
1016
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
1017
|
+
const folderPath = process.cwd();
|
|
1018
|
+
const rootName = path.basename(folderPath);
|
|
1019
|
+
const txtIgnore = readTxtIgnore(folderPath);
|
|
750
1020
|
|
|
751
|
-
|
|
752
|
-
process.exit(1);
|
|
753
|
-
}
|
|
1021
|
+
// ── build tree & collect file paths ───────────────────────────────────────────────
|
|
754
1022
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1033
|
+
// ── build output filename ───────────────────────────────────────────────────────────
|
|
1034
|
+
|
|
1035
|
+
let outputFile = outputArg || path.join(folderPath, `${rootName}.txt`);
|
|
1036
|
+
|
|
1037
|
+
// ── handle output splitting ─────────────────────────────────────────────────────────
|
|
765
1038
|
|
|
766
|
-
const
|
|
767
|
-
|
|
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
|
-
//
|
|
770
|
-
const
|
|
1078
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
1079
|
+
const out = [];
|
|
1080
|
+
const divider = "=".repeat(80);
|
|
1081
|
+
const subDivider = "-".repeat(80);
|
|
771
1082
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1083
|
+
out.push(divider);
|
|
1084
|
+
out.push(`START OF FOLDER: ${rootName}`);
|
|
1085
|
+
out.push(divider);
|
|
1086
|
+
out.push("");
|
|
775
1087
|
|
|
776
|
-
|
|
1088
|
+
out.push(divider);
|
|
1089
|
+
out.push("PROJECT STRUCTURE");
|
|
1090
|
+
out.push(divider);
|
|
1091
|
+
out.push(`Root: ${folderPath}\n`);
|
|
777
1092
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
|
|
790
|
-
|
|
1099
|
+
out.push(divider);
|
|
1100
|
+
out.push("FILE CONTENTS");
|
|
1101
|
+
out.push(divider);
|
|
791
1102
|
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
});
|
|
1111
|
+
out.push("");
|
|
1112
|
+
out.push(divider);
|
|
1113
|
+
out.push(`END OF FOLDER: ${rootName}`);
|
|
1114
|
+
out.push(divider);
|
|
861
1115
|
|
|
862
|
-
out.
|
|
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.
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
1135
|
+
// Run the main function
|
|
1136
|
+
main().catch(err => {
|
|
1137
|
+
console.error('❌ Error:', err.message);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
});
|