make-folder-txt 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
Perfect for sharing your codebase with **AI tools**, **teammates**, or **code reviewers** — without zipping files or giving repo access.
|
|
13
13
|
|
|
14
|
-
[Installation](#-installation) · [Usage](#-usage) · [Help](#-get-help) · [Copy to Clipboard](#-copy-to-clipboard) · [Force Include Everything](#-force-include-everything) · [Shell Autocompletion](#-shell-autocompletion) · [Output Format](#-output-format) · [What Gets Skipped](#-what-gets-skipped) · [Contributing](#-contributing)
|
|
14
|
+
[Installation](#-installation) · [Usage](#-usage) · [Help](#-get-help) · [Copy to Clipboard](#-copy-to-clipboard) · [Force Include Everything](#-force-include-everything) · [Shell Autocompletion](#-shell-autocompletion) · [File Size Control](#-file-size-control) · [Output Splitting](#-output-splitting) · [Output Format](#-output-format) · [What Gets Skipped](#-what-gets-skipped) · [Contributing](#-contributing)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -24,6 +24,8 @@ Ever needed to share your entire project with ChatGPT, Claude, or a teammate —
|
|
|
24
24
|
- ✅ Run it from any project directory — no arguments needed
|
|
25
25
|
- ✅ Built-in help system with `--help` flag
|
|
26
26
|
- ✅ **Built-in shell autocompletion** (installs automatically)
|
|
27
|
+
- ✅ **File size control** with `--skip-large` and `--no-skip`
|
|
28
|
+
- ✅ **Output splitting** by folders, files, or size
|
|
27
29
|
- ✅ Copy to clipboard with `--copy` flag
|
|
28
30
|
- ✅ Force include everything with `--force` flag
|
|
29
31
|
- ✅ Generates a clean folder tree + every file's content
|
|
@@ -133,6 +135,76 @@ notepad $PROFILE
|
|
|
133
135
|
|
|
134
136
|
The completion works out of the box - just run the tool once and restart your terminal!
|
|
135
137
|
|
|
138
|
+
### 📏 File Size Control
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
make-folder-txt --skip-large 400KB # Skip files larger than 400KB
|
|
142
|
+
make-folder-txt --skip-large 5GB # Skip files larger than 5GB
|
|
143
|
+
make-folder-txt --skip-large 1.5MB # Skip files larger than 1.5MB
|
|
144
|
+
make-folder-txt --no-skip # Include all files regardless of size
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Default behavior**: Files larger than 500KB are skipped by default.
|
|
148
|
+
|
|
149
|
+
**Supported size units**:
|
|
150
|
+
- **B** - Bytes
|
|
151
|
+
- **KB** - Kilobytes (1024 bytes)
|
|
152
|
+
- **MB** - Megabytes (1024 KB)
|
|
153
|
+
- **GB** - Gigabytes (1024 MB)
|
|
154
|
+
- **TB** - Terabytes (1024 GB)
|
|
155
|
+
|
|
156
|
+
**Examples:**
|
|
157
|
+
```bash
|
|
158
|
+
# More restrictive - skip anything over 100KB
|
|
159
|
+
make-folder-txt --skip-large 100KB
|
|
160
|
+
|
|
161
|
+
# More permissive - allow files up to 10MB
|
|
162
|
+
make-folder-txt --skip-large 10MB
|
|
163
|
+
|
|
164
|
+
# Include everything - no size limits
|
|
165
|
+
make-folder-txt --no-skip
|
|
166
|
+
|
|
167
|
+
# Combine with other options
|
|
168
|
+
make-folder-txt --skip-large 2MB --ignore-folder node_modules
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Size format**: Accepts decimal numbers (e.g., `1.5MB`, `0.5GB`) and various units.
|
|
172
|
+
|
|
173
|
+
### 📂 Output Splitting
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
make-folder-txt --split-method folder # Split by folders
|
|
177
|
+
make-folder-txt --split-method file # Split by files
|
|
178
|
+
make-folder-txt --split-method size --split-size 5MB # Split by file size
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Split Methods:**
|
|
182
|
+
- **`folder`** - Creates separate files for each folder
|
|
183
|
+
- **`file`** - Creates separate files for each individual file
|
|
184
|
+
- **`size`** - Splits output when content exceeds specified size
|
|
185
|
+
|
|
186
|
+
**Examples:**
|
|
187
|
+
```bash
|
|
188
|
+
# Split by folders - creates folder-name.txt for each folder
|
|
189
|
+
make-folder-txt --split-method folder
|
|
190
|
+
|
|
191
|
+
# Split by files - creates filename.txt for each file
|
|
192
|
+
make-folder-txt --split-method file
|
|
193
|
+
|
|
194
|
+
# Split by size - creates part-1.txt, part-2.txt, etc.
|
|
195
|
+
make-folder-txt --split-method size --split-size 5MB
|
|
196
|
+
|
|
197
|
+
# Combine with other options
|
|
198
|
+
make-folder-txt --split-method size --split-size 2MB --ignore-folder node_modules
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Output Files:**
|
|
202
|
+
- **Folder method**: `projectname-foldername.txt`
|
|
203
|
+
- **File method**: `projectname-filename.txt`
|
|
204
|
+
- **Size method**: `projectname-part-1.txt`, `projectname-part-2.txt`, etc.
|
|
205
|
+
|
|
206
|
+
**Note**: Splitting is not compatible with `--copy` flag.
|
|
207
|
+
|
|
136
208
|
Ignore specific folders/files by name:
|
|
137
209
|
|
|
138
210
|
```bash
|
package/bin/make-folder-txt.js
CHANGED
|
@@ -179,13 +179,37 @@ function collectFiles(
|
|
|
179
179
|
return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
function
|
|
182
|
+
function parseFileSize(sizeStr) {
|
|
183
|
+
const units = {
|
|
184
|
+
'B': 1,
|
|
185
|
+
'KB': 1024,
|
|
186
|
+
'MB': 1024 * 1024,
|
|
187
|
+
'GB': 1024 * 1024 * 1024,
|
|
188
|
+
'TB': 1024 * 1024 * 1024 * 1024
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
|
|
192
|
+
if (!match) {
|
|
193
|
+
console.error(`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const value = parseFloat(match[1]);
|
|
198
|
+
const unit = match[2].toUpperCase();
|
|
199
|
+
return Math.floor(value * units[unit]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
|
|
183
203
|
const ext = path.extname(absPath).toLowerCase();
|
|
184
204
|
if (!force && BINARY_EXTS.has(ext)) return "[binary / skipped]";
|
|
185
205
|
try {
|
|
186
206
|
const stat = fs.statSync(absPath);
|
|
187
|
-
if (!force && stat.size >
|
|
188
|
-
|
|
207
|
+
if (!force && stat.size > maxFileSize) {
|
|
208
|
+
const sizeStr = stat.size < 1024 ? `${stat.size} B` :
|
|
209
|
+
stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB` :
|
|
210
|
+
stat.size < 1024 * 1024 * 1024 ? `${(stat.size / (1024 * 1024)).toFixed(1)} MB` :
|
|
211
|
+
`${(stat.size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
212
|
+
return `[file too large: ${sizeStr} – skipped]`;
|
|
189
213
|
}
|
|
190
214
|
return fs.readFileSync(absPath, "utf8");
|
|
191
215
|
} catch (err) {
|
|
@@ -193,6 +217,238 @@ function readContent(absPath, force = false) {
|
|
|
193
217
|
}
|
|
194
218
|
}
|
|
195
219
|
|
|
220
|
+
function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag) {
|
|
221
|
+
const folders = new Map();
|
|
222
|
+
|
|
223
|
+
// Group files by folder
|
|
224
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
225
|
+
const folderPath = path.dirname(rel);
|
|
226
|
+
const folderKey = folderPath === '/' ? rootName : folderPath.slice(1);
|
|
227
|
+
|
|
228
|
+
if (!folders.has(folderKey)) {
|
|
229
|
+
folders.set(folderKey, []);
|
|
230
|
+
}
|
|
231
|
+
folders.get(folderKey).push({ abs, rel });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const results = [];
|
|
235
|
+
|
|
236
|
+
folders.forEach((files, folderName) => {
|
|
237
|
+
const out = [];
|
|
238
|
+
const divider = "=".repeat(80);
|
|
239
|
+
const subDivider = "-".repeat(80);
|
|
240
|
+
|
|
241
|
+
out.push(divider);
|
|
242
|
+
out.push(`START OF FOLDER: ${folderName}`);
|
|
243
|
+
out.push(divider);
|
|
244
|
+
out.push("");
|
|
245
|
+
|
|
246
|
+
// Add folder structure (only this folder's structure)
|
|
247
|
+
const folderTreeLines = treeLines.filter(line =>
|
|
248
|
+
line.includes(folderName + '/') || line === `${rootName}/`
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
out.push(divider);
|
|
252
|
+
out.push("PROJECT STRUCTURE");
|
|
253
|
+
out.push(divider);
|
|
254
|
+
out.push(`Root: ${folderPath}\n`);
|
|
255
|
+
out.push(`${rootName}/`);
|
|
256
|
+
folderTreeLines.forEach(l => out.push(l));
|
|
257
|
+
out.push("");
|
|
258
|
+
out.push(`Total files in this folder: ${files.length}`);
|
|
259
|
+
out.push("");
|
|
260
|
+
|
|
261
|
+
out.push(divider);
|
|
262
|
+
out.push("FILE CONTENTS");
|
|
263
|
+
out.push(divider);
|
|
264
|
+
|
|
265
|
+
files.forEach(({ abs, rel }) => {
|
|
266
|
+
out.push("");
|
|
267
|
+
out.push(subDivider);
|
|
268
|
+
out.push(`FILE: ${rel}`);
|
|
269
|
+
out.push(subDivider);
|
|
270
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
out.push("");
|
|
274
|
+
out.push(divider);
|
|
275
|
+
out.push(`END OF FOLDER: ${folderName}`);
|
|
276
|
+
out.push(divider);
|
|
277
|
+
|
|
278
|
+
const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, '-')}.txt`;
|
|
279
|
+
const filePath = path.join(process.cwd(), fileName);
|
|
280
|
+
|
|
281
|
+
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
282
|
+
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
283
|
+
|
|
284
|
+
results.push({
|
|
285
|
+
file: filePath,
|
|
286
|
+
size: sizeKB,
|
|
287
|
+
files: files.length,
|
|
288
|
+
folder: folderName
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
|
|
296
|
+
const results = [];
|
|
297
|
+
|
|
298
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
299
|
+
const out = [];
|
|
300
|
+
const divider = "=".repeat(80);
|
|
301
|
+
const subDivider = "-".repeat(80);
|
|
302
|
+
const fileName = path.basename(rel, path.extname(rel));
|
|
303
|
+
|
|
304
|
+
out.push(divider);
|
|
305
|
+
out.push(`FILE: ${rel}`);
|
|
306
|
+
out.push(divider);
|
|
307
|
+
out.push("");
|
|
308
|
+
|
|
309
|
+
out.push(divider);
|
|
310
|
+
out.push("FILE CONTENTS");
|
|
311
|
+
out.push(divider);
|
|
312
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
313
|
+
|
|
314
|
+
out.push("");
|
|
315
|
+
out.push(divider);
|
|
316
|
+
out.push(`END OF FILE: ${rel}`);
|
|
317
|
+
out.push(divider);
|
|
318
|
+
|
|
319
|
+
const outputFileName = `${rootName}-${fileName}.txt`;
|
|
320
|
+
const filePath = path.join(process.cwd(), outputFileName);
|
|
321
|
+
|
|
322
|
+
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
323
|
+
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
324
|
+
|
|
325
|
+
results.push({
|
|
326
|
+
file: filePath,
|
|
327
|
+
size: sizeKB,
|
|
328
|
+
files: 1,
|
|
329
|
+
fileName: fileName
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
return results;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag) {
|
|
337
|
+
const results = [];
|
|
338
|
+
let currentPart = 1;
|
|
339
|
+
let currentSize = 0;
|
|
340
|
+
let currentFiles = [];
|
|
341
|
+
|
|
342
|
+
const divider = "=".repeat(80);
|
|
343
|
+
const subDivider = "-".repeat(80);
|
|
344
|
+
|
|
345
|
+
// Start with header
|
|
346
|
+
let out = [];
|
|
347
|
+
out.push(divider);
|
|
348
|
+
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
349
|
+
out.push(divider);
|
|
350
|
+
out.push("");
|
|
351
|
+
|
|
352
|
+
out.push(divider);
|
|
353
|
+
out.push("PROJECT STRUCTURE");
|
|
354
|
+
out.push(divider);
|
|
355
|
+
out.push(`Root: ${folderPath}\n`);
|
|
356
|
+
out.push(`${rootName}/`);
|
|
357
|
+
treeLines.forEach(l => out.push(l));
|
|
358
|
+
out.push("");
|
|
359
|
+
out.push(`Total files: ${filePaths.length}`);
|
|
360
|
+
out.push("");
|
|
361
|
+
|
|
362
|
+
out.push(divider);
|
|
363
|
+
out.push("FILE CONTENTS");
|
|
364
|
+
out.push(divider);
|
|
365
|
+
|
|
366
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
367
|
+
const content = readContent(abs, forceFlag, effectiveMaxSize);
|
|
368
|
+
const fileContent = [
|
|
369
|
+
"",
|
|
370
|
+
subDivider,
|
|
371
|
+
`FILE: ${rel}`,
|
|
372
|
+
subDivider,
|
|
373
|
+
content
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
const contentSize = fileContent.join("\n").length;
|
|
377
|
+
|
|
378
|
+
// Check if adding this file would exceed the split size
|
|
379
|
+
if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
|
|
380
|
+
// Finish current part
|
|
381
|
+
out.push("");
|
|
382
|
+
out.push(divider);
|
|
383
|
+
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
384
|
+
out.push(divider);
|
|
385
|
+
|
|
386
|
+
// Write current part
|
|
387
|
+
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
388
|
+
const filePath = path.join(process.cwd(), fileName);
|
|
389
|
+
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
390
|
+
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
391
|
+
|
|
392
|
+
results.push({
|
|
393
|
+
file: filePath,
|
|
394
|
+
size: sizeKB,
|
|
395
|
+
files: currentFiles.length,
|
|
396
|
+
part: currentPart
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Start new part
|
|
400
|
+
currentPart++;
|
|
401
|
+
currentSize = 0;
|
|
402
|
+
currentFiles = [];
|
|
403
|
+
|
|
404
|
+
out = [];
|
|
405
|
+
out.push(divider);
|
|
406
|
+
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
407
|
+
out.push(divider);
|
|
408
|
+
out.push("");
|
|
409
|
+
|
|
410
|
+
out.push(divider);
|
|
411
|
+
out.push("PROJECT STRUCTURE");
|
|
412
|
+
out.push(divider);
|
|
413
|
+
out.push(`Root: ${folderPath}\n`);
|
|
414
|
+
out.push(`${rootName}/`);
|
|
415
|
+
treeLines.forEach(l => out.push(l));
|
|
416
|
+
out.push("");
|
|
417
|
+
out.push(`Total files: ${filePaths.length}`);
|
|
418
|
+
out.push("");
|
|
419
|
+
|
|
420
|
+
out.push(divider);
|
|
421
|
+
out.push("FILE CONTENTS");
|
|
422
|
+
out.push(divider);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Add file to current part
|
|
426
|
+
out.push(...fileContent);
|
|
427
|
+
currentSize += contentSize;
|
|
428
|
+
currentFiles.push(rel);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Write final part
|
|
432
|
+
out.push("");
|
|
433
|
+
out.push(divider);
|
|
434
|
+
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
435
|
+
out.push(divider);
|
|
436
|
+
|
|
437
|
+
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
438
|
+
const filePath = path.join(process.cwd(), fileName);
|
|
439
|
+
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
440
|
+
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
441
|
+
|
|
442
|
+
results.push({
|
|
443
|
+
file: filePath,
|
|
444
|
+
size: sizeKB,
|
|
445
|
+
files: currentFiles.length,
|
|
446
|
+
part: currentPart
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
return results;
|
|
450
|
+
}
|
|
451
|
+
|
|
196
452
|
// ── main ──────────────────────────────────────────────────────────────────────
|
|
197
453
|
|
|
198
454
|
const args = process.argv.slice(2);
|
|
@@ -415,6 +671,10 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
415
671
|
--ignore-file, -ifi <names...> Ignore specific files by name
|
|
416
672
|
--only-folder, -ofo <names...> Include only specific folders
|
|
417
673
|
--only-file, -ofi <names...> Include only specific files
|
|
674
|
+
--skip-large <size> Skip files larger than specified size (default: 500KB)
|
|
675
|
+
--no-skip Include all files regardless of size
|
|
676
|
+
--split-method <method> Split output: folder, file, or size
|
|
677
|
+
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
418
678
|
--copy Copy output to clipboard
|
|
419
679
|
--force Include everything (overrides all ignore patterns)
|
|
420
680
|
--install-completion Install shell autocompletion (bash/zsh/PowerShell) - usually automatic
|
|
@@ -425,6 +685,12 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
425
685
|
make-folder-txt
|
|
426
686
|
make-folder-txt --copy
|
|
427
687
|
make-folder-txt --force
|
|
688
|
+
make-folder-txt --skip-large 400KB
|
|
689
|
+
make-folder-txt --skip-large 5GB
|
|
690
|
+
make-folder-txt --no-skip
|
|
691
|
+
make-folder-txt --split-method folder
|
|
692
|
+
make-folder-txt --split-method file
|
|
693
|
+
make-folder-txt --split-method size --split-size 5MB
|
|
428
694
|
make-folder-txt --install-completion
|
|
429
695
|
make-folder-txt --ignore-folder node_modules dist
|
|
430
696
|
make-folder-txt -ifo node_modules dist
|
|
@@ -456,6 +722,10 @@ const onlyFiles = new Set();
|
|
|
456
722
|
let outputArg = null;
|
|
457
723
|
let copyToClipboardFlag = false;
|
|
458
724
|
let forceFlag = false;
|
|
725
|
+
let maxFileSize = 500 * 1024; // Default 500KB
|
|
726
|
+
let noSkipFlag = false;
|
|
727
|
+
let splitMethod = null; // 'folder', 'file', 'size'
|
|
728
|
+
let splitSize = null; // size in bytes
|
|
459
729
|
|
|
460
730
|
for (let i = 0; i < args.length; i += 1) {
|
|
461
731
|
const arg = args[i];
|
|
@@ -470,6 +740,81 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
470
740
|
continue;
|
|
471
741
|
}
|
|
472
742
|
|
|
743
|
+
if (arg === "--no-skip") {
|
|
744
|
+
noSkipFlag = true;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (arg === "--skip-large") {
|
|
749
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
750
|
+
console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
maxFileSize = parseFileSize(args[i + 1]);
|
|
754
|
+
i += 1;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (arg.startsWith("--skip-large=")) {
|
|
759
|
+
const value = arg.slice("--skip-large=".length);
|
|
760
|
+
if (!value) {
|
|
761
|
+
console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
maxFileSize = parseFileSize(value);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (arg === "--split-method") {
|
|
769
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
770
|
+
console.error("Error: --split-method requires a method (folder, file, or size).");
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
const method = args[i + 1].toLowerCase();
|
|
774
|
+
if (!['folder', 'file', 'size'].includes(method)) {
|
|
775
|
+
console.error("Error: --split-method must be one of: folder, file, size");
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
splitMethod = method;
|
|
779
|
+
i += 1;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (arg.startsWith("--split-method=")) {
|
|
784
|
+
const value = arg.slice("--split-method=".length);
|
|
785
|
+
if (!value) {
|
|
786
|
+
console.error("Error: --split-method requires a method (folder, file, or size).");
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
const method = value.toLowerCase();
|
|
790
|
+
if (!['folder', 'file', 'size'].includes(method)) {
|
|
791
|
+
console.error("Error: --split-method must be one of: folder, file, size");
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
splitMethod = method;
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (arg === "--split-size") {
|
|
799
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
800
|
+
console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
splitSize = parseFileSize(args[i + 1]);
|
|
804
|
+
i += 1;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (arg.startsWith("--split-size=")) {
|
|
809
|
+
const value = arg.slice("--split-size=".length);
|
|
810
|
+
if (!value) {
|
|
811
|
+
console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
|
|
812
|
+
process.exit(1);
|
|
813
|
+
}
|
|
814
|
+
splitSize = parseFileSize(value);
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
|
|
473
818
|
if (arg === "--ignore-folder" || arg === "-ifo") {
|
|
474
819
|
let consumed = 0;
|
|
475
820
|
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
@@ -588,6 +933,17 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
588
933
|
process.exit(1);
|
|
589
934
|
}
|
|
590
935
|
|
|
936
|
+
// Validate split options
|
|
937
|
+
if (splitMethod === 'size' && !splitSize) {
|
|
938
|
+
console.error("Error: --split-method size requires --split-size to be specified.");
|
|
939
|
+
process.exit(1);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (splitSize && splitMethod !== 'size') {
|
|
943
|
+
console.error("Error: --split-size can only be used with --split-method size.");
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
|
|
591
947
|
const folderPath = process.cwd();
|
|
592
948
|
const rootName = path.basename(folderPath);
|
|
593
949
|
|
|
@@ -611,7 +967,47 @@ const { lines: treeLines, filePaths } = collectFiles(
|
|
|
611
967
|
{ hasOnlyFilters, rootName, txtIgnore, force: forceFlag },
|
|
612
968
|
);
|
|
613
969
|
|
|
614
|
-
// ──
|
|
970
|
+
// ── handle splitting ──────────────────────────────────────────────────────────────
|
|
971
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
972
|
+
|
|
973
|
+
if (splitMethod) {
|
|
974
|
+
console.log(`🔧 Splitting output by: ${splitMethod}`);
|
|
975
|
+
|
|
976
|
+
let results;
|
|
977
|
+
|
|
978
|
+
if (splitMethod === 'folder') {
|
|
979
|
+
results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
980
|
+
} else if (splitMethod === 'file') {
|
|
981
|
+
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
982
|
+
} else if (splitMethod === 'size') {
|
|
983
|
+
results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
987
|
+
console.log('');
|
|
988
|
+
|
|
989
|
+
results.forEach((result, index) => {
|
|
990
|
+
if (splitMethod === 'folder') {
|
|
991
|
+
console.log(`📁 Folder: ${result.folder}`);
|
|
992
|
+
} else if (splitMethod === 'file') {
|
|
993
|
+
console.log(`📄 File: ${result.fileName}`);
|
|
994
|
+
} else if (splitMethod === 'size') {
|
|
995
|
+
console.log(`📦 Part ${result.part}`);
|
|
996
|
+
}
|
|
997
|
+
console.log(`📄 Output : ${result.file}`);
|
|
998
|
+
console.log(`📊 Size : ${result.size} KB`);
|
|
999
|
+
console.log(`🗂️ Files : ${result.files}`);
|
|
1000
|
+
console.log('');
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
if (copyToClipboardFlag) {
|
|
1004
|
+
console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
process.exit(0);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
615
1011
|
const out = [];
|
|
616
1012
|
const divider = "=".repeat(80);
|
|
617
1013
|
const subDivider = "-".repeat(80);
|
|
@@ -636,11 +1032,12 @@ out.push("FILE CONTENTS");
|
|
|
636
1032
|
out.push(divider);
|
|
637
1033
|
|
|
638
1034
|
filePaths.forEach(({ abs, rel }) => {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1035
|
+
out.push("");
|
|
1036
|
+
out.push(subDivider);
|
|
1037
|
+
out.push(`FILE: ${rel}`);
|
|
1038
|
+
out.push(subDivider);
|
|
1039
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
1040
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
644
1041
|
});
|
|
645
1042
|
|
|
646
1043
|
out.push("");
|
|
@@ -7,7 +7,7 @@ _make_folder_txt_completion() {
|
|
|
7
7
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
8
8
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
9
9
|
|
|
10
|
-
opts="--ignore-folder -ifo --ignore-file -ifi --only-folder -ofo --only-file -ofi --copy --force --help --version -h -v"
|
|
10
|
+
opts="--ignore-folder -ifo --ignore-file -ifi --only-folder -ofo --only-file -ofi --skip-large --no-skip --split-method --split-size --copy --force --help --version -h -v"
|
|
11
11
|
|
|
12
12
|
case "${prev}" in
|
|
13
13
|
--ignore-folder|-ifo)
|
|
@@ -28,6 +28,24 @@ _make_folder_txt_completion() {
|
|
|
28
28
|
COMPREPLY=( $(compgen -W "${folders}" -- ${cur}) )
|
|
29
29
|
return 0
|
|
30
30
|
;;
|
|
31
|
+
--skip-large)
|
|
32
|
+
# Complete with common size formats
|
|
33
|
+
local sizes="100KB 200KB 400KB 500KB 1MB 5MB 10MB 100MB 1GB 5GB"
|
|
34
|
+
COMPREPLY=( $(compgen -W "${sizes}" -- ${cur}) )
|
|
35
|
+
return 0
|
|
36
|
+
;;
|
|
37
|
+
--split-method)
|
|
38
|
+
# Complete with split methods
|
|
39
|
+
local methods="folder file size"
|
|
40
|
+
COMPREPLY=( $(compgen -W "${methods}" -- ${cur}) )
|
|
41
|
+
return 0
|
|
42
|
+
;;
|
|
43
|
+
--split-size)
|
|
44
|
+
# Complete with common size formats
|
|
45
|
+
local sizes="1MB 5MB 10MB 50MB 100MB 500MB 1GB"
|
|
46
|
+
COMPREPLY=( $(compgen -W "${sizes}" -- ${cur}) )
|
|
47
|
+
return 0
|
|
48
|
+
;;
|
|
31
49
|
*)
|
|
32
50
|
;;
|
|
33
51
|
esac
|
|
@@ -12,6 +12,10 @@ Register-ArgumentCompleter -Native -CommandName 'make-folder-txt' -ScriptBlock {
|
|
|
12
12
|
'--ignore-file', '-ifi',
|
|
13
13
|
'--only-folder', '-ofo',
|
|
14
14
|
'--only-file', '-ofi',
|
|
15
|
+
'--skip-large',
|
|
16
|
+
'--no-skip',
|
|
17
|
+
'--split-method',
|
|
18
|
+
'--split-size',
|
|
15
19
|
'--copy',
|
|
16
20
|
'--force',
|
|
17
21
|
'--install-completion',
|
|
@@ -49,6 +53,30 @@ Register-ArgumentCompleter -Native -CommandName 'make-folder-txt' -ScriptBlock {
|
|
|
49
53
|
return @()
|
|
50
54
|
}
|
|
51
55
|
}
|
|
56
|
+
'--skip-large' {
|
|
57
|
+
# Complete with common size formats
|
|
58
|
+
$sizes = @('100KB', '200KB', '400KB', '500KB', '1MB', '5MB', '10MB', '100MB', '1GB', '5GB')
|
|
59
|
+
$matchingSizes = $sizes | Where-Object { $_ -like "*$currentArgument*" }
|
|
60
|
+
return $matchingSizes | ForEach-Object {
|
|
61
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Size: $_")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
'--split-method' {
|
|
65
|
+
# Complete with split methods
|
|
66
|
+
$methods = @('folder', 'file', 'size')
|
|
67
|
+
$matchingMethods = $methods | Where-Object { $_ -like "*$currentArgument*" }
|
|
68
|
+
return $matchingMethods | ForEach-Object {
|
|
69
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Method: $_")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
'--split-size' {
|
|
73
|
+
# Complete with common size formats
|
|
74
|
+
$sizes = @('1MB', '5MB', '10MB', '50MB', '100MB', '500MB', '1GB')
|
|
75
|
+
$matchingSizes = $sizes | Where-Object { $_ -like "*$currentArgument*" }
|
|
76
|
+
return $matchingSizes | ForEach-Object {
|
|
77
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Size: $_")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
52
80
|
default {
|
|
53
81
|
# Complete with options
|
|
54
82
|
if ($currentArgument -like '-*') {
|
|
@@ -12,6 +12,10 @@ _make_folder_txt() {
|
|
|
12
12
|
'-ofo[Include only specific folders]:folder:_directories'
|
|
13
13
|
'--only-file[Include only specific files]:file:_files'
|
|
14
14
|
'-ofi[Include only specific files]:file:_files'
|
|
15
|
+
'--skip-large[Skip files larger than specified size]:size:(100KB 200KB 400KB 500KB 1MB 5MB 10MB 100MB 1GB 5GB)'
|
|
16
|
+
'--no-skip[Include all files regardless of size]'
|
|
17
|
+
'--split-method[Split output by method]:method:(folder file size)'
|
|
18
|
+
'--split-size[Split output when size exceeds limit]:size:(1MB 5MB 10MB 50MB 100MB 500MB 1GB)'
|
|
15
19
|
'--copy[Copy output to clipboard]'
|
|
16
20
|
'--force[Include everything (overrides all ignore patterns)]'
|
|
17
21
|
'--help[Show help message]'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
START OF FOLDER: make-folder-txt
|
|
3
|
+
================================================================================
|
|
4
|
+
|
|
5
|
+
================================================================================
|
|
6
|
+
PROJECT STRUCTURE
|
|
7
|
+
================================================================================
|
|
8
|
+
Root: C:\Programming\make-folder-txt
|
|
9
|
+
|
|
10
|
+
make-folder-txt/
|
|
11
|
+
├── package.json
|
|
12
|
+
|
|
13
|
+
Total files: 1
|
|
14
|
+
|
|
15
|
+
================================================================================
|
|
16
|
+
FILE CONTENTS
|
|
17
|
+
================================================================================
|
|
18
|
+
|
|
19
|
+
--------------------------------------------------------------------------------
|
|
20
|
+
FILE: /package.json
|
|
21
|
+
--------------------------------------------------------------------------------
|
|
22
|
+
{
|
|
23
|
+
"name": "make-folder-txt",
|
|
24
|
+
"version": "2.0.1",
|
|
25
|
+
"description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
|
|
26
|
+
"main": "bin/make-folder-txt.js",
|
|
27
|
+
"bin": {
|
|
28
|
+
"make-folder-txt": "bin/make-folder-txt.js"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"folder",
|
|
35
|
+
"dump",
|
|
36
|
+
"project",
|
|
37
|
+
"structure",
|
|
38
|
+
"txt",
|
|
39
|
+
"cli",
|
|
40
|
+
"export"
|
|
41
|
+
],
|
|
42
|
+
"author": "Muhammad Saad Amin",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=14.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
================================================================================
|
|
51
|
+
END OF FOLDER: make-folder-txt
|
|
52
|
+
================================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-folder-txt",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
|
|
5
5
|
"main": "bin/make-folder-txt.js",
|
|
6
6
|
"bin": {
|