make-folder-txt 2.0.1 → 2.1.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/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,11 +217,243 @@ 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);
|
|
199
455
|
|
|
200
|
-
// Check if completion is already installed, install if not
|
|
456
|
+
// Check if completion is already installed, install if not (silent on subsequent runs)
|
|
201
457
|
function checkAndInstallCompletion() {
|
|
202
458
|
const { execSync } = require('child_process');
|
|
203
459
|
const path = require('path');
|
|
@@ -211,19 +467,33 @@ function checkAndInstallCompletion() {
|
|
|
211
467
|
let completionInstalled = false;
|
|
212
468
|
|
|
213
469
|
if (platform === 'win32') {
|
|
214
|
-
// Check PowerShell completion -
|
|
215
|
-
|
|
216
|
-
path
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
for (const profilePath of profilePaths) {
|
|
470
|
+
// Check PowerShell completion - use the same path as the installation script
|
|
471
|
+
try {
|
|
472
|
+
// Get the PowerShell profile path like the installation script does
|
|
473
|
+
const profilePathResult = execSync('powershell -Command "echo $PROFILE.CurrentUserCurrentHost"', { encoding: 'utf8' }).trim();
|
|
474
|
+
const profilePath = profilePathResult.replace(/['"]/g, '').replace(/\.CurrentUserCurrentHost$/, ''); // Remove quotes and suffix
|
|
475
|
+
|
|
222
476
|
if (fs.existsSync(profilePath)) {
|
|
223
477
|
const profileContent = fs.readFileSync(profilePath, 'utf8');
|
|
224
|
-
if (profileContent.includes('make-folder-txt-
|
|
478
|
+
if (profileContent.includes('make-folder-txt') || profileContent.includes('Register-ArgumentCompleter')) {
|
|
225
479
|
completionInstalled = true;
|
|
226
|
-
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} catch (err) {
|
|
483
|
+
// If PowerShell detection fails, try fallback paths
|
|
484
|
+
const profilePaths = [
|
|
485
|
+
path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
486
|
+
path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
487
|
+
path.join(homeDir, '.config', 'powershell', 'Microsoft.PowerShell_profile.ps1')
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
for (const profilePath of profilePaths) {
|
|
491
|
+
if (fs.existsSync(profilePath)) {
|
|
492
|
+
const profileContent = fs.readFileSync(profilePath, 'utf8');
|
|
493
|
+
if (profileContent.includes('make-folder-txt') || profileContent.includes('Register-ArgumentCompleter')) {
|
|
494
|
+
completionInstalled = true;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
227
497
|
}
|
|
228
498
|
}
|
|
229
499
|
}
|
|
@@ -247,7 +517,7 @@ function checkAndInstallCompletion() {
|
|
|
247
517
|
}
|
|
248
518
|
}
|
|
249
519
|
|
|
250
|
-
// If completion is not installed, install it automatically
|
|
520
|
+
// If completion is not installed, install it automatically (show message only first time)
|
|
251
521
|
if (!completionInstalled) {
|
|
252
522
|
console.log('🔧 Installing shell autocompletion for make-folder-txt...');
|
|
253
523
|
|
|
@@ -257,7 +527,7 @@ function checkAndInstallCompletion() {
|
|
|
257
527
|
execSync('powershell -Command "Get-Host"', { stdio: 'ignore' });
|
|
258
528
|
const installScript = path.join(__dirname, '..', 'completion', 'install-powershell-completion.ps1');
|
|
259
529
|
execSync(`powershell -ExecutionPolicy Bypass -File "${installScript}"`, { stdio: 'ignore' });
|
|
260
|
-
console.log('✅ PowerShell completion installed!');
|
|
530
|
+
console.log('✅ PowerShell completion installed! Restart your terminal to enable autocompletion');
|
|
261
531
|
} catch (err) {
|
|
262
532
|
// Silent fail for PowerShell
|
|
263
533
|
}
|
|
@@ -278,7 +548,7 @@ function checkAndInstallCompletion() {
|
|
|
278
548
|
} catch (e) {
|
|
279
549
|
fs.writeFileSync(zshrc, '# make-folder-txt completion\nfpath+=~/.zsh/completions\nautoload -U compinit && compinit\n');
|
|
280
550
|
}
|
|
281
|
-
console.log('✅ Zsh completion installed!');
|
|
551
|
+
console.log('✅ Zsh completion installed! Restart your terminal or run: source ~/.zshrc');
|
|
282
552
|
} catch (err) {
|
|
283
553
|
// Silent fail for zsh
|
|
284
554
|
}
|
|
@@ -295,7 +565,7 @@ function checkAndInstallCompletion() {
|
|
|
295
565
|
} catch (e) {
|
|
296
566
|
fs.writeFileSync(bashrc, `# make-folder-txt completion\nsource "${completionPath}"\n`);
|
|
297
567
|
}
|
|
298
|
-
console.log('✅ Bash completion installed!');
|
|
568
|
+
console.log('✅ Bash completion installed! Restart your terminal or run: source ~/.bashrc');
|
|
299
569
|
} catch (err) {
|
|
300
570
|
// Silent fail for bash
|
|
301
571
|
}
|
|
@@ -309,7 +579,7 @@ function checkAndInstallCompletion() {
|
|
|
309
579
|
}
|
|
310
580
|
}
|
|
311
581
|
|
|
312
|
-
// Run completion check on first run (but not for help/version commands)
|
|
582
|
+
// Run completion check on first run (but not for help/version/install-completion commands)
|
|
313
583
|
if (!args.includes("--help") && !args.includes("-h") &&
|
|
314
584
|
!args.includes("--version") && !args.includes("-v") &&
|
|
315
585
|
!args.includes("--install-completion")) {
|
|
@@ -336,7 +606,8 @@ if (args.includes("--install-completion")) {
|
|
|
336
606
|
const installScript = path.join(__dirname, '..', 'completion', 'install-powershell-completion.ps1');
|
|
337
607
|
|
|
338
608
|
// Run the PowerShell installation script
|
|
339
|
-
execSync(`powershell -ExecutionPolicy Bypass -File "${installScript}"`, { stdio: '
|
|
609
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${installScript}"`, { stdio: 'ignore' });
|
|
610
|
+
console.log('✅ PowerShell completion installed! Restart your terminal to enable autocompletion');
|
|
340
611
|
|
|
341
612
|
} catch (err) {
|
|
342
613
|
console.error('❌ Failed to install PowerShell completion:', err.message);
|
|
@@ -415,6 +686,10 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
415
686
|
--ignore-file, -ifi <names...> Ignore specific files by name
|
|
416
687
|
--only-folder, -ofo <names...> Include only specific folders
|
|
417
688
|
--only-file, -ofi <names...> Include only specific files
|
|
689
|
+
--skip-large <size> Skip files larger than specified size (default: 500KB)
|
|
690
|
+
--no-skip Include all files regardless of size
|
|
691
|
+
--split-method <method> Split output: folder, file, or size
|
|
692
|
+
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
418
693
|
--copy Copy output to clipboard
|
|
419
694
|
--force Include everything (overrides all ignore patterns)
|
|
420
695
|
--install-completion Install shell autocompletion (bash/zsh/PowerShell) - usually automatic
|
|
@@ -425,6 +700,12 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
425
700
|
make-folder-txt
|
|
426
701
|
make-folder-txt --copy
|
|
427
702
|
make-folder-txt --force
|
|
703
|
+
make-folder-txt --skip-large 400KB
|
|
704
|
+
make-folder-txt --skip-large 5GB
|
|
705
|
+
make-folder-txt --no-skip
|
|
706
|
+
make-folder-txt --split-method folder
|
|
707
|
+
make-folder-txt --split-method file
|
|
708
|
+
make-folder-txt --split-method size --split-size 5MB
|
|
428
709
|
make-folder-txt --install-completion
|
|
429
710
|
make-folder-txt --ignore-folder node_modules dist
|
|
430
711
|
make-folder-txt -ifo node_modules dist
|
|
@@ -456,6 +737,10 @@ const onlyFiles = new Set();
|
|
|
456
737
|
let outputArg = null;
|
|
457
738
|
let copyToClipboardFlag = false;
|
|
458
739
|
let forceFlag = false;
|
|
740
|
+
let maxFileSize = 500 * 1024; // Default 500KB
|
|
741
|
+
let noSkipFlag = false;
|
|
742
|
+
let splitMethod = null; // 'folder', 'file', 'size'
|
|
743
|
+
let splitSize = null; // size in bytes
|
|
459
744
|
|
|
460
745
|
for (let i = 0; i < args.length; i += 1) {
|
|
461
746
|
const arg = args[i];
|
|
@@ -470,6 +755,81 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
470
755
|
continue;
|
|
471
756
|
}
|
|
472
757
|
|
|
758
|
+
if (arg === "--no-skip") {
|
|
759
|
+
noSkipFlag = true;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (arg === "--skip-large") {
|
|
764
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
765
|
+
console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
maxFileSize = parseFileSize(args[i + 1]);
|
|
769
|
+
i += 1;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (arg.startsWith("--skip-large=")) {
|
|
774
|
+
const value = arg.slice("--skip-large=".length);
|
|
775
|
+
if (!value) {
|
|
776
|
+
console.error("Error: --skip-large requires a size value (e.g., 400KB, 5GB).");
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
maxFileSize = parseFileSize(value);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (arg === "--split-method") {
|
|
784
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
785
|
+
console.error("Error: --split-method requires a method (folder, file, or size).");
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
const method = args[i + 1].toLowerCase();
|
|
789
|
+
if (!['folder', 'file', 'size'].includes(method)) {
|
|
790
|
+
console.error("Error: --split-method must be one of: folder, file, size");
|
|
791
|
+
process.exit(1);
|
|
792
|
+
}
|
|
793
|
+
splitMethod = method;
|
|
794
|
+
i += 1;
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (arg.startsWith("--split-method=")) {
|
|
799
|
+
const value = arg.slice("--split-method=".length);
|
|
800
|
+
if (!value) {
|
|
801
|
+
console.error("Error: --split-method requires a method (folder, file, or size).");
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
const method = value.toLowerCase();
|
|
805
|
+
if (!['folder', 'file', 'size'].includes(method)) {
|
|
806
|
+
console.error("Error: --split-method must be one of: folder, file, size");
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
splitMethod = method;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (arg === "--split-size") {
|
|
814
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
815
|
+
console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
splitSize = parseFileSize(args[i + 1]);
|
|
819
|
+
i += 1;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (arg.startsWith("--split-size=")) {
|
|
824
|
+
const value = arg.slice("--split-size=".length);
|
|
825
|
+
if (!value) {
|
|
826
|
+
console.error("Error: --split-size requires a size value (e.g., 5MB, 10MB).");
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
splitSize = parseFileSize(value);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
|
|
473
833
|
if (arg === "--ignore-folder" || arg === "-ifo") {
|
|
474
834
|
let consumed = 0;
|
|
475
835
|
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
@@ -588,6 +948,17 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
588
948
|
process.exit(1);
|
|
589
949
|
}
|
|
590
950
|
|
|
951
|
+
// Validate split options
|
|
952
|
+
if (splitMethod === 'size' && !splitSize) {
|
|
953
|
+
console.error("Error: --split-method size requires --split-size to be specified.");
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (splitSize && splitMethod !== 'size') {
|
|
958
|
+
console.error("Error: --split-size can only be used with --split-method size.");
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
|
|
591
962
|
const folderPath = process.cwd();
|
|
592
963
|
const rootName = path.basename(folderPath);
|
|
593
964
|
|
|
@@ -611,7 +982,47 @@ const { lines: treeLines, filePaths } = collectFiles(
|
|
|
611
982
|
{ hasOnlyFilters, rootName, txtIgnore, force: forceFlag },
|
|
612
983
|
);
|
|
613
984
|
|
|
614
|
-
// ──
|
|
985
|
+
// ── handle splitting ──────────────────────────────────────────────────────────────
|
|
986
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
987
|
+
|
|
988
|
+
if (splitMethod) {
|
|
989
|
+
console.log(`🔧 Splitting output by: ${splitMethod}`);
|
|
990
|
+
|
|
991
|
+
let results;
|
|
992
|
+
|
|
993
|
+
if (splitMethod === 'folder') {
|
|
994
|
+
results = splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
995
|
+
} else if (splitMethod === 'file') {
|
|
996
|
+
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
997
|
+
} else if (splitMethod === 'size') {
|
|
998
|
+
results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
1002
|
+
console.log('');
|
|
1003
|
+
|
|
1004
|
+
results.forEach((result, index) => {
|
|
1005
|
+
if (splitMethod === 'folder') {
|
|
1006
|
+
console.log(`📁 Folder: ${result.folder}`);
|
|
1007
|
+
} else if (splitMethod === 'file') {
|
|
1008
|
+
console.log(`📄 File: ${result.fileName}`);
|
|
1009
|
+
} else if (splitMethod === 'size') {
|
|
1010
|
+
console.log(`📦 Part ${result.part}`);
|
|
1011
|
+
}
|
|
1012
|
+
console.log(`📄 Output : ${result.file}`);
|
|
1013
|
+
console.log(`📊 Size : ${result.size} KB`);
|
|
1014
|
+
console.log(`🗂️ Files : ${result.files}`);
|
|
1015
|
+
console.log('');
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
if (copyToClipboardFlag) {
|
|
1019
|
+
console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
process.exit(0);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
615
1026
|
const out = [];
|
|
616
1027
|
const divider = "=".repeat(80);
|
|
617
1028
|
const subDivider = "-".repeat(80);
|
|
@@ -636,11 +1047,12 @@ out.push("FILE CONTENTS");
|
|
|
636
1047
|
out.push(divider);
|
|
637
1048
|
|
|
638
1049
|
filePaths.forEach(({ abs, rel }) => {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1050
|
+
out.push("");
|
|
1051
|
+
out.push(subDivider);
|
|
1052
|
+
out.push(`FILE: ${rel}`);
|
|
1053
|
+
out.push(subDivider);
|
|
1054
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
1055
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
644
1056
|
});
|
|
645
1057
|
|
|
646
1058
|
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.1.0",
|
|
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.
|
|
3
|
+
"version": "2.1.1",
|
|
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": {
|