make-folder-txt 2.2.9 → 2.2.10
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 +788 -592
- package/package.json +1 -1
package/bin/make-folder-txt.js
CHANGED
|
@@ -6,65 +6,112 @@ const { version } = require("../package.json");
|
|
|
6
6
|
const { execSync } = require("child_process");
|
|
7
7
|
|
|
8
8
|
// ── config ────────────────────────────────────────────────────────────────────
|
|
9
|
-
const IGNORE_DIRS = new Set([
|
|
10
|
-
|
|
9
|
+
const IGNORE_DIRS = new Set([
|
|
10
|
+
"node_modules",
|
|
11
|
+
".git",
|
|
12
|
+
".next",
|
|
13
|
+
"dist",
|
|
14
|
+
"build",
|
|
15
|
+
".cache",
|
|
16
|
+
]);
|
|
17
|
+
const IGNORE_FILES = new Set([
|
|
18
|
+
".DS_Store",
|
|
19
|
+
"Thumbs.db",
|
|
20
|
+
"desktop.ini",
|
|
21
|
+
".txtignore",
|
|
22
|
+
]);
|
|
11
23
|
|
|
12
24
|
const BINARY_EXTS = new Set([
|
|
13
|
-
".png",
|
|
14
|
-
".
|
|
15
|
-
".
|
|
16
|
-
".
|
|
17
|
-
".
|
|
25
|
+
".png",
|
|
26
|
+
".jpg",
|
|
27
|
+
".jpeg",
|
|
28
|
+
".gif",
|
|
29
|
+
".bmp",
|
|
30
|
+
".ico",
|
|
31
|
+
".svg",
|
|
32
|
+
".webp",
|
|
33
|
+
".pdf",
|
|
34
|
+
".zip",
|
|
35
|
+
".tar",
|
|
36
|
+
".gz",
|
|
37
|
+
".rar",
|
|
38
|
+
".7z",
|
|
39
|
+
".exe",
|
|
40
|
+
".dll",
|
|
41
|
+
".so",
|
|
42
|
+
".dylib",
|
|
43
|
+
".bin",
|
|
44
|
+
".mp3",
|
|
45
|
+
".mp4",
|
|
46
|
+
".wav",
|
|
47
|
+
".avi",
|
|
48
|
+
".mov",
|
|
49
|
+
".woff",
|
|
50
|
+
".woff2",
|
|
51
|
+
".ttf",
|
|
52
|
+
".eot",
|
|
53
|
+
".otf",
|
|
18
54
|
".lock",
|
|
19
55
|
]);
|
|
20
56
|
|
|
21
57
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
22
58
|
|
|
23
59
|
function readTxtIgnore(rootDir) {
|
|
24
|
-
const txtIgnorePath = path.join(rootDir,
|
|
60
|
+
const txtIgnorePath = path.join(rootDir, ".txtignore");
|
|
25
61
|
const ignorePatterns = new Set();
|
|
26
|
-
|
|
62
|
+
|
|
27
63
|
try {
|
|
28
|
-
const content = fs.readFileSync(txtIgnorePath,
|
|
29
|
-
const lines = content
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
const content = fs.readFileSync(txtIgnorePath, "utf8");
|
|
65
|
+
const lines = content
|
|
66
|
+
.split("\n")
|
|
67
|
+
.map((line) => line.trim())
|
|
68
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
69
|
+
|
|
70
|
+
lines.forEach((line) => ignorePatterns.add(line));
|
|
34
71
|
} catch (err) {
|
|
35
72
|
// .txtignore doesn't exist or can't be read - that's fine
|
|
36
73
|
}
|
|
37
|
-
|
|
74
|
+
|
|
38
75
|
return ignorePatterns;
|
|
39
76
|
}
|
|
40
77
|
|
|
41
78
|
function copyToClipboard(text) {
|
|
42
79
|
try {
|
|
43
|
-
if (process.platform ===
|
|
80
|
+
if (process.platform === "win32") {
|
|
44
81
|
// Windows - use PowerShell for better handling of large content
|
|
45
|
-
const tempFile =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
82
|
+
const tempFile =
|
|
83
|
+
require("os").tmpdir() + "\\make-folder-txt-clipboard-temp.txt";
|
|
84
|
+
require("fs").writeFileSync(tempFile, text, "utf8");
|
|
85
|
+
execSync(
|
|
86
|
+
`powershell -Command "Get-Content '${tempFile}' | Set-Clipboard"`,
|
|
87
|
+
{ stdio: "ignore" },
|
|
88
|
+
);
|
|
89
|
+
require("fs").unlinkSync(tempFile);
|
|
90
|
+
} else if (process.platform === "darwin") {
|
|
50
91
|
// macOS
|
|
51
|
-
execSync(`echo ${JSON.stringify(text)} | pbcopy`, { stdio:
|
|
92
|
+
execSync(`echo ${JSON.stringify(text)} | pbcopy`, { stdio: "ignore" });
|
|
52
93
|
} else {
|
|
53
94
|
// Linux (requires xclip or xsel)
|
|
54
95
|
try {
|
|
55
|
-
execSync(`echo ${JSON.stringify(text)} | xclip -selection clipboard`, {
|
|
96
|
+
execSync(`echo ${JSON.stringify(text)} | xclip -selection clipboard`, {
|
|
97
|
+
stdio: "ignore",
|
|
98
|
+
});
|
|
56
99
|
} catch {
|
|
57
100
|
try {
|
|
58
|
-
execSync(`echo ${JSON.stringify(text)} | xsel --clipboard --input`, {
|
|
101
|
+
execSync(`echo ${JSON.stringify(text)} | xsel --clipboard --input`, {
|
|
102
|
+
stdio: "ignore",
|
|
103
|
+
});
|
|
59
104
|
} catch {
|
|
60
|
-
console.warn(
|
|
105
|
+
console.warn(
|
|
106
|
+
"⚠️ Could not copy to clipboard. Install xclip or xsel on Linux.",
|
|
107
|
+
);
|
|
61
108
|
return false;
|
|
62
109
|
}
|
|
63
110
|
}
|
|
64
111
|
}
|
|
65
112
|
return true;
|
|
66
113
|
} catch (err) {
|
|
67
|
-
console.warn(
|
|
114
|
+
console.warn("⚠️ Could not copy to clipboard: " + err.message);
|
|
68
115
|
return false;
|
|
69
116
|
}
|
|
70
117
|
}
|
|
@@ -98,7 +145,8 @@ function collectFiles(
|
|
|
98
145
|
}
|
|
99
146
|
|
|
100
147
|
entries.sort((a, b) => {
|
|
101
|
-
if (a.isDirectory() === b.isDirectory())
|
|
148
|
+
if (a.isDirectory() === b.isDirectory())
|
|
149
|
+
return a.name.localeCompare(b.name);
|
|
102
150
|
return a.isDirectory() ? -1 : 1;
|
|
103
151
|
});
|
|
104
152
|
|
|
@@ -116,10 +164,20 @@ function collectFiles(
|
|
|
116
164
|
}
|
|
117
165
|
|
|
118
166
|
// Get relative path for .txtignore pattern matching
|
|
119
|
-
const relPathForIgnore = path
|
|
120
|
-
|
|
167
|
+
const relPathForIgnore = path
|
|
168
|
+
.relative(rootDir, path.join(dir, entry.name))
|
|
169
|
+
.split(path.sep)
|
|
170
|
+
.join("/");
|
|
171
|
+
|
|
121
172
|
// Check against .txtignore patterns (both dirname and relative path) unless force is enabled
|
|
122
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
!force &&
|
|
175
|
+
(txtIgnore.has(entry.name) ||
|
|
176
|
+
txtIgnore.has(`${entry.name}/`) ||
|
|
177
|
+
txtIgnore.has(relPathForIgnore) ||
|
|
178
|
+
txtIgnore.has(`${relPathForIgnore}/`) ||
|
|
179
|
+
txtIgnore.has(`/${relPathForIgnore}/`))
|
|
180
|
+
) {
|
|
123
181
|
if (!hasOnlyFilters) {
|
|
124
182
|
lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
|
|
125
183
|
}
|
|
@@ -129,13 +187,13 @@ function collectFiles(
|
|
|
129
187
|
const childPath = path.join(dir, entry.name);
|
|
130
188
|
// When rootOnlyInclude is active, a folder only "counts" if it's directly under root
|
|
131
189
|
const folderIsSelected = rootOnlyInclude
|
|
132
|
-
?
|
|
190
|
+
? dir === rootDir && onlyFolders.has(entry.name)
|
|
133
191
|
: onlyFolders.has(entry.name);
|
|
134
192
|
|
|
135
193
|
const childInSelectedFolder = inSelectedFolder || folderIsSelected;
|
|
136
194
|
const childLines = [];
|
|
137
195
|
const childFiles = [];
|
|
138
|
-
|
|
196
|
+
|
|
139
197
|
// If rootOnlyInclude is true, skip recursing into non-selected subdirectories
|
|
140
198
|
let child;
|
|
141
199
|
if (rootOnlyInclude && dir !== rootDir && !inSelectedFolder) {
|
|
@@ -164,8 +222,9 @@ function collectFiles(
|
|
|
164
222
|
}
|
|
165
223
|
|
|
166
224
|
// Include the dir if: no filters active, or it's explicitly selected, or (non-root-only) a child matched
|
|
167
|
-
const shouldIncludeDir =
|
|
168
|
-
|
|
225
|
+
const shouldIncludeDir =
|
|
226
|
+
!hasOnlyFilters ||
|
|
227
|
+
folderIsSelected ||
|
|
169
228
|
(!rootOnlyInclude && child.hasIncluded);
|
|
170
229
|
|
|
171
230
|
if (shouldIncludeDir) {
|
|
@@ -177,48 +236,74 @@ function collectFiles(
|
|
|
177
236
|
if (!force && ignoreFiles.has(entry.name)) return;
|
|
178
237
|
|
|
179
238
|
// Get relative path for .txtignore pattern matching
|
|
180
|
-
const relPathForIgnore = path
|
|
181
|
-
|
|
239
|
+
const relPathForIgnore = path
|
|
240
|
+
.relative(rootDir, path.join(dir, entry.name))
|
|
241
|
+
.split(path.sep)
|
|
242
|
+
.join("/");
|
|
243
|
+
|
|
182
244
|
// Check against .txtignore patterns (both filename and relative path) unless force is enabled
|
|
183
|
-
if (
|
|
245
|
+
if (
|
|
246
|
+
!force &&
|
|
247
|
+
(txtIgnore.has(entry.name) ||
|
|
248
|
+
txtIgnore.has(relPathForIgnore) ||
|
|
249
|
+
txtIgnore.has(`/${relPathForIgnore}`))
|
|
250
|
+
) {
|
|
184
251
|
return;
|
|
185
252
|
}
|
|
186
253
|
|
|
187
254
|
// Ignore .txt files that match the folder name (e.g., foldername.txt) unless force is enabled
|
|
188
|
-
if (
|
|
255
|
+
if (
|
|
256
|
+
!force &&
|
|
257
|
+
entry.name.endsWith(".txt") &&
|
|
258
|
+
entry.name === `${rootName}.txt`
|
|
259
|
+
)
|
|
260
|
+
return;
|
|
189
261
|
|
|
190
262
|
// Check if this file matches any of the onlyFiles patterns
|
|
191
|
-
const shouldIncludeFile =
|
|
192
|
-
|
|
193
|
-
|
|
263
|
+
const shouldIncludeFile =
|
|
264
|
+
!hasOnlyFilters ||
|
|
265
|
+
inSelectedFolder ||
|
|
266
|
+
onlyFiles.has(entry.name) ||
|
|
267
|
+
onlyFiles.has(relPathForIgnore) ||
|
|
194
268
|
onlyFiles.has(`/${relPathForIgnore}`);
|
|
195
|
-
|
|
269
|
+
|
|
196
270
|
if (!shouldIncludeFile) return;
|
|
197
271
|
|
|
198
272
|
lines.push(`${indent}${connector}${entry.name}`);
|
|
199
|
-
const relPath =
|
|
273
|
+
const relPath =
|
|
274
|
+
"/" +
|
|
275
|
+
path
|
|
276
|
+
.relative(rootDir, path.join(dir, entry.name))
|
|
277
|
+
.split(path.sep)
|
|
278
|
+
.join("/");
|
|
200
279
|
filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
|
|
201
280
|
}
|
|
202
281
|
});
|
|
203
282
|
|
|
204
|
-
return {
|
|
283
|
+
return {
|
|
284
|
+
lines,
|
|
285
|
+
filePaths,
|
|
286
|
+
hasIncluded: filePaths.length > 0 || lines.length > 0,
|
|
287
|
+
};
|
|
205
288
|
}
|
|
206
289
|
|
|
207
290
|
function parseFileSize(sizeStr) {
|
|
208
291
|
const units = {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
292
|
+
B: 1,
|
|
293
|
+
KB: 1024,
|
|
294
|
+
MB: 1024 * 1024,
|
|
295
|
+
GB: 1024 * 1024 * 1024,
|
|
296
|
+
TB: 1024 * 1024 * 1024 * 1024,
|
|
214
297
|
};
|
|
215
|
-
|
|
298
|
+
|
|
216
299
|
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
|
|
217
300
|
if (!match) {
|
|
218
|
-
console.error(
|
|
301
|
+
console.error(
|
|
302
|
+
`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`,
|
|
303
|
+
);
|
|
219
304
|
process.exit(1);
|
|
220
305
|
}
|
|
221
|
-
|
|
306
|
+
|
|
222
307
|
const value = parseFloat(match[1]);
|
|
223
308
|
const unit = match[2].toUpperCase();
|
|
224
309
|
return Math.floor(value * units[unit]);
|
|
@@ -230,10 +315,14 @@ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
|
|
|
230
315
|
try {
|
|
231
316
|
const stat = fs.statSync(absPath);
|
|
232
317
|
if (!force && stat.size > maxFileSize) {
|
|
233
|
-
const sizeStr =
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
318
|
+
const sizeStr =
|
|
319
|
+
stat.size < 1024
|
|
320
|
+
? `${stat.size} B`
|
|
321
|
+
: stat.size < 1024 * 1024
|
|
322
|
+
? `${(stat.size / 1024).toFixed(1)} KB`
|
|
323
|
+
: stat.size < 1024 * 1024 * 1024
|
|
324
|
+
? `${(stat.size / (1024 * 1024)).toFixed(1)} MB`
|
|
325
|
+
: `${(stat.size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
237
326
|
return `[file too large: ${sizeStr} – skipped]`;
|
|
238
327
|
}
|
|
239
328
|
return fs.readFileSync(absPath, "utf8");
|
|
@@ -242,51 +331,57 @@ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
|
|
|
242
331
|
}
|
|
243
332
|
}
|
|
244
333
|
|
|
245
|
-
function splitByFolders(
|
|
334
|
+
function splitByFolders(
|
|
335
|
+
treeLines,
|
|
336
|
+
filePaths,
|
|
337
|
+
rootName,
|
|
338
|
+
effectiveMaxSize,
|
|
339
|
+
forceFlag,
|
|
340
|
+
) {
|
|
246
341
|
const folders = new Map();
|
|
247
|
-
|
|
342
|
+
|
|
248
343
|
// Group files by folder
|
|
249
344
|
filePaths.forEach(({ abs, rel }) => {
|
|
250
345
|
const folderPath = path.dirname(rel);
|
|
251
|
-
const folderKey = folderPath ===
|
|
252
|
-
|
|
346
|
+
const folderKey = folderPath === "/" ? rootName : folderPath.slice(1);
|
|
347
|
+
|
|
253
348
|
if (!folders.has(folderKey)) {
|
|
254
349
|
folders.set(folderKey, []);
|
|
255
350
|
}
|
|
256
351
|
folders.get(folderKey).push({ abs, rel });
|
|
257
352
|
});
|
|
258
|
-
|
|
353
|
+
|
|
259
354
|
const results = [];
|
|
260
|
-
|
|
355
|
+
|
|
261
356
|
folders.forEach((files, folderName) => {
|
|
262
357
|
const out = [];
|
|
263
358
|
const divider = "=".repeat(80);
|
|
264
359
|
const subDivider = "-".repeat(80);
|
|
265
|
-
|
|
360
|
+
|
|
266
361
|
out.push(divider);
|
|
267
362
|
out.push(`START OF FOLDER: ${folderName}`);
|
|
268
363
|
out.push(divider);
|
|
269
364
|
out.push("");
|
|
270
|
-
|
|
365
|
+
|
|
271
366
|
// Add folder structure (only this folder's structure)
|
|
272
|
-
const folderTreeLines = treeLines.filter(
|
|
273
|
-
line.includes(folderName +
|
|
367
|
+
const folderTreeLines = treeLines.filter(
|
|
368
|
+
(line) => line.includes(folderName + "/") || line === `${rootName}/`,
|
|
274
369
|
);
|
|
275
|
-
|
|
370
|
+
|
|
276
371
|
out.push(divider);
|
|
277
372
|
out.push("PROJECT STRUCTURE");
|
|
278
373
|
out.push(divider);
|
|
279
|
-
out.push(`Root: ${
|
|
374
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
280
375
|
out.push(`${rootName}/`);
|
|
281
|
-
folderTreeLines.forEach(l => out.push(l));
|
|
376
|
+
folderTreeLines.forEach((l) => out.push(l));
|
|
282
377
|
out.push("");
|
|
283
378
|
out.push(`Total files in this folder: ${files.length}`);
|
|
284
379
|
out.push("");
|
|
285
|
-
|
|
380
|
+
|
|
286
381
|
out.push(divider);
|
|
287
382
|
out.push("FILE CONTENTS");
|
|
288
383
|
out.push(divider);
|
|
289
|
-
|
|
384
|
+
|
|
290
385
|
files.forEach(({ abs, rel }) => {
|
|
291
386
|
out.push("");
|
|
292
387
|
out.push(subDivider);
|
|
@@ -294,112 +389,113 @@ function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceF
|
|
|
294
389
|
out.push(subDivider);
|
|
295
390
|
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
296
391
|
});
|
|
297
|
-
|
|
392
|
+
|
|
298
393
|
out.push("");
|
|
299
394
|
out.push(divider);
|
|
300
395
|
out.push(`END OF FOLDER: ${folderName}`);
|
|
301
396
|
out.push(divider);
|
|
302
|
-
|
|
303
|
-
const fileName = `${rootName}-${folderName.replace(/[\/\\]/g,
|
|
397
|
+
|
|
398
|
+
const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, "-")}.txt`;
|
|
304
399
|
const filePath = path.join(process.cwd(), fileName);
|
|
305
|
-
|
|
400
|
+
|
|
306
401
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
307
402
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
308
|
-
|
|
403
|
+
|
|
309
404
|
results.push({
|
|
310
405
|
file: filePath,
|
|
311
406
|
size: sizeKB,
|
|
312
407
|
files: files.length,
|
|
313
|
-
folder: folderName
|
|
408
|
+
folder: folderName,
|
|
314
409
|
});
|
|
315
410
|
});
|
|
316
|
-
|
|
411
|
+
|
|
317
412
|
return results;
|
|
318
413
|
}
|
|
319
414
|
|
|
320
415
|
function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
|
|
321
416
|
const results = [];
|
|
322
|
-
|
|
417
|
+
|
|
323
418
|
filePaths.forEach(({ abs, rel }) => {
|
|
324
419
|
const out = [];
|
|
325
420
|
const divider = "=".repeat(80);
|
|
326
421
|
const subDivider = "-".repeat(80);
|
|
327
422
|
const fileName = path.basename(rel, path.extname(rel));
|
|
328
|
-
|
|
423
|
+
|
|
329
424
|
out.push(divider);
|
|
330
425
|
out.push(`FILE: ${rel}`);
|
|
331
426
|
out.push(divider);
|
|
332
427
|
out.push("");
|
|
333
|
-
|
|
428
|
+
|
|
334
429
|
out.push(divider);
|
|
335
430
|
out.push("FILE CONTENTS");
|
|
336
431
|
out.push(divider);
|
|
337
432
|
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
338
|
-
|
|
433
|
+
|
|
339
434
|
out.push("");
|
|
340
435
|
out.push(divider);
|
|
341
436
|
out.push(`END OF FILE: ${rel}`);
|
|
342
437
|
out.push(divider);
|
|
343
|
-
|
|
438
|
+
|
|
344
439
|
const outputFileName = `${rootName}-${fileName}.txt`;
|
|
345
440
|
const filePath = path.join(process.cwd(), outputFileName);
|
|
346
|
-
|
|
441
|
+
|
|
347
442
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
348
443
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
349
|
-
|
|
444
|
+
|
|
350
445
|
results.push({
|
|
351
446
|
file: filePath,
|
|
352
447
|
size: sizeKB,
|
|
353
448
|
files: 1,
|
|
354
|
-
fileName: fileName
|
|
449
|
+
fileName: fileName,
|
|
355
450
|
});
|
|
356
451
|
});
|
|
357
|
-
|
|
452
|
+
|
|
358
453
|
return results;
|
|
359
454
|
}
|
|
360
455
|
|
|
361
|
-
function splitBySize(
|
|
456
|
+
function splitBySize(
|
|
457
|
+
treeLines,
|
|
458
|
+
filePaths,
|
|
459
|
+
rootName,
|
|
460
|
+
splitSize,
|
|
461
|
+
effectiveMaxSize,
|
|
462
|
+
forceFlag,
|
|
463
|
+
) {
|
|
362
464
|
const results = [];
|
|
363
465
|
let currentPart = 1;
|
|
364
466
|
let currentSize = 0;
|
|
365
467
|
let currentFiles = [];
|
|
366
|
-
|
|
468
|
+
|
|
367
469
|
const divider = "=".repeat(80);
|
|
368
470
|
const subDivider = "-".repeat(80);
|
|
369
|
-
|
|
471
|
+
|
|
370
472
|
// Start with header
|
|
371
473
|
let out = [];
|
|
372
474
|
out.push(divider);
|
|
373
475
|
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
374
476
|
out.push(divider);
|
|
375
477
|
out.push("");
|
|
376
|
-
|
|
478
|
+
|
|
377
479
|
out.push(divider);
|
|
378
480
|
out.push("PROJECT STRUCTURE");
|
|
379
481
|
out.push(divider);
|
|
380
|
-
out.push(`Root: ${
|
|
482
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
381
483
|
out.push(`${rootName}/`);
|
|
382
|
-
treeLines.forEach(l => out.push(l));
|
|
484
|
+
treeLines.forEach((l) => out.push(l));
|
|
383
485
|
out.push("");
|
|
384
486
|
out.push(`Total files: ${filePaths.length}`);
|
|
385
487
|
out.push("");
|
|
386
|
-
|
|
488
|
+
|
|
387
489
|
out.push(divider);
|
|
388
490
|
out.push("FILE CONTENTS");
|
|
389
491
|
out.push(divider);
|
|
390
|
-
|
|
492
|
+
|
|
391
493
|
filePaths.forEach(({ abs, rel }) => {
|
|
392
494
|
const content = readContent(abs, forceFlag, effectiveMaxSize);
|
|
393
|
-
const fileContent = [
|
|
394
|
-
|
|
395
|
-
subDivider,
|
|
396
|
-
`FILE: ${rel}`,
|
|
397
|
-
subDivider,
|
|
398
|
-
content
|
|
399
|
-
];
|
|
400
|
-
|
|
495
|
+
const fileContent = ["", subDivider, `FILE: ${rel}`, subDivider, content];
|
|
496
|
+
|
|
401
497
|
const contentSize = fileContent.join("\n").length;
|
|
402
|
-
|
|
498
|
+
|
|
403
499
|
// Check if adding this file would exceed the split size
|
|
404
500
|
if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
|
|
405
501
|
// Finish current part
|
|
@@ -407,178 +503,203 @@ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize
|
|
|
407
503
|
out.push(divider);
|
|
408
504
|
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
409
505
|
out.push(divider);
|
|
410
|
-
|
|
506
|
+
|
|
411
507
|
// Write current part
|
|
412
508
|
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
413
509
|
const filePath = path.join(process.cwd(), fileName);
|
|
414
510
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
415
511
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
416
|
-
|
|
512
|
+
|
|
417
513
|
results.push({
|
|
418
514
|
file: filePath,
|
|
419
515
|
size: sizeKB,
|
|
420
516
|
files: currentFiles.length,
|
|
421
|
-
part: currentPart
|
|
517
|
+
part: currentPart,
|
|
422
518
|
});
|
|
423
|
-
|
|
519
|
+
|
|
424
520
|
// Start new part
|
|
425
521
|
currentPart++;
|
|
426
522
|
currentSize = 0;
|
|
427
523
|
currentFiles = [];
|
|
428
|
-
|
|
524
|
+
|
|
429
525
|
out = [];
|
|
430
526
|
out.push(divider);
|
|
431
527
|
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
432
528
|
out.push(divider);
|
|
433
529
|
out.push("");
|
|
434
|
-
|
|
530
|
+
|
|
435
531
|
out.push(divider);
|
|
436
532
|
out.push("PROJECT STRUCTURE");
|
|
437
533
|
out.push(divider);
|
|
438
|
-
out.push(`Root: ${
|
|
534
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
439
535
|
out.push(`${rootName}/`);
|
|
440
|
-
treeLines.forEach(l => out.push(l));
|
|
536
|
+
treeLines.forEach((l) => out.push(l));
|
|
441
537
|
out.push("");
|
|
442
538
|
out.push(`Total files: ${filePaths.length}`);
|
|
443
539
|
out.push("");
|
|
444
|
-
|
|
540
|
+
|
|
445
541
|
out.push(divider);
|
|
446
542
|
out.push("FILE CONTENTS");
|
|
447
543
|
out.push(divider);
|
|
448
544
|
}
|
|
449
|
-
|
|
545
|
+
|
|
450
546
|
// Add file to current part
|
|
451
547
|
out.push(...fileContent);
|
|
452
548
|
currentSize += contentSize;
|
|
453
549
|
currentFiles.push(rel);
|
|
454
550
|
});
|
|
455
|
-
|
|
551
|
+
|
|
456
552
|
// Write final part
|
|
457
553
|
out.push("");
|
|
458
554
|
out.push(divider);
|
|
459
555
|
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
460
556
|
out.push(divider);
|
|
461
|
-
|
|
557
|
+
|
|
462
558
|
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
463
559
|
const filePath = path.join(process.cwd(), fileName);
|
|
464
560
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
465
561
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
466
|
-
|
|
562
|
+
|
|
467
563
|
results.push({
|
|
468
564
|
file: filePath,
|
|
469
565
|
size: sizeKB,
|
|
470
566
|
files: currentFiles.length,
|
|
471
|
-
part: currentPart
|
|
567
|
+
part: currentPart,
|
|
472
568
|
});
|
|
473
|
-
|
|
569
|
+
|
|
474
570
|
return results;
|
|
475
571
|
}
|
|
476
572
|
|
|
477
573
|
// ── configuration ────────────────────────────────────────────────────────────────
|
|
478
574
|
|
|
479
575
|
function createInteractiveConfig() {
|
|
480
|
-
const readline = require(
|
|
481
|
-
const fs = require(
|
|
482
|
-
const path = require(
|
|
483
|
-
const os = require(
|
|
484
|
-
|
|
576
|
+
const readline = require("readline");
|
|
577
|
+
const fs = require("fs");
|
|
578
|
+
const path = require("path");
|
|
579
|
+
const os = require("os");
|
|
580
|
+
|
|
485
581
|
const rl = readline.createInterface({
|
|
486
582
|
input: process.stdin,
|
|
487
|
-
output: process.stdout
|
|
583
|
+
output: process.stdout,
|
|
488
584
|
});
|
|
489
585
|
|
|
490
|
-
console.log(
|
|
491
|
-
console.log(
|
|
586
|
+
console.log("\n🔧 make-folder-txt Configuration Setup");
|
|
587
|
+
console.log("=====================================\n");
|
|
492
588
|
|
|
493
589
|
return new Promise((resolve) => {
|
|
494
590
|
const config = {
|
|
495
|
-
maxFileSize:
|
|
496
|
-
splitMethod:
|
|
497
|
-
splitSize:
|
|
498
|
-
copyToClipboard: false
|
|
591
|
+
maxFileSize: "500KB",
|
|
592
|
+
splitMethod: "none",
|
|
593
|
+
splitSize: "5MB",
|
|
594
|
+
copyToClipboard: false,
|
|
499
595
|
};
|
|
500
596
|
|
|
501
597
|
let currentStep = 0;
|
|
502
598
|
const questions = [
|
|
503
599
|
{
|
|
504
|
-
key:
|
|
505
|
-
question:
|
|
506
|
-
default:
|
|
600
|
+
key: "maxFileSize",
|
|
601
|
+
question: "Maximum file size to include (e.g., 500KB, 2MB, 1GB): ",
|
|
602
|
+
default: "500KB",
|
|
507
603
|
validate: (value) => {
|
|
508
604
|
if (!value.trim()) return true;
|
|
509
|
-
const validUnits = [
|
|
605
|
+
const validUnits = ["B", "KB", "MB", "GB", "TB"];
|
|
510
606
|
const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
|
|
511
|
-
if (!match)
|
|
512
|
-
|
|
607
|
+
if (!match)
|
|
608
|
+
return "Please enter a valid size (e.g., 500KB, 2MB, 1GB)";
|
|
609
|
+
if (!validUnits.includes(match[2].toUpperCase()))
|
|
610
|
+
return `Invalid unit. Use: ${validUnits.join(", ")}`;
|
|
513
611
|
return true;
|
|
514
|
-
}
|
|
612
|
+
},
|
|
515
613
|
},
|
|
516
614
|
{
|
|
517
|
-
key:
|
|
518
|
-
question:
|
|
519
|
-
default:
|
|
615
|
+
key: "splitMethod",
|
|
616
|
+
question: "Split output method (none, folder, file, size): ",
|
|
617
|
+
default: "none",
|
|
520
618
|
validate: (value) => {
|
|
521
|
-
const validMethods = [
|
|
522
|
-
if (!validMethods.includes(value.toLowerCase()))
|
|
619
|
+
const validMethods = ["none", "folder", "file", "size"];
|
|
620
|
+
if (!validMethods.includes(value.toLowerCase()))
|
|
621
|
+
return `Please choose: ${validMethods.join(", ")}`;
|
|
523
622
|
return true;
|
|
524
|
-
}
|
|
623
|
+
},
|
|
525
624
|
},
|
|
526
625
|
{
|
|
527
|
-
key:
|
|
528
|
-
question:
|
|
529
|
-
default:
|
|
530
|
-
ask: () => config.splitMethod ===
|
|
626
|
+
key: "splitSize",
|
|
627
|
+
question: "Split size when using size method (e.g., 5MB, 10MB): ",
|
|
628
|
+
default: "5MB",
|
|
629
|
+
ask: () => config.splitMethod === "size",
|
|
531
630
|
validate: (value) => {
|
|
532
631
|
if (!value.trim()) return true;
|
|
533
|
-
const validUnits = [
|
|
632
|
+
const validUnits = ["B", "KB", "MB", "GB", "TB"];
|
|
534
633
|
const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
|
|
535
|
-
if (!match) return
|
|
536
|
-
if (!validUnits.includes(match[2].toUpperCase()))
|
|
634
|
+
if (!match) return "Please enter a valid size (e.g., 5MB, 10MB)";
|
|
635
|
+
if (!validUnits.includes(match[2].toUpperCase()))
|
|
636
|
+
return `Invalid unit. Use: ${validUnits.join(", ")}`;
|
|
537
637
|
return true;
|
|
538
|
-
}
|
|
638
|
+
},
|
|
539
639
|
},
|
|
540
640
|
{
|
|
541
|
-
key:
|
|
542
|
-
question:
|
|
543
|
-
default:
|
|
641
|
+
key: "copyToClipboard",
|
|
642
|
+
question: "Copy to clipboard automatically? (y/n): ",
|
|
643
|
+
default: "n",
|
|
544
644
|
validate: (value) => {
|
|
545
645
|
const answer = value.toLowerCase();
|
|
546
|
-
if (![
|
|
646
|
+
if (!["y", "n", "yes", "no"].includes(answer))
|
|
647
|
+
return "Please enter y/n or yes/no";
|
|
547
648
|
return true;
|
|
548
649
|
},
|
|
549
|
-
transform: (value) => [
|
|
650
|
+
transform: (value) => ["y", "yes"].includes(value.toLowerCase()),
|
|
550
651
|
},
|
|
551
652
|
{
|
|
552
|
-
key:
|
|
553
|
-
question:
|
|
554
|
-
default:
|
|
653
|
+
key: "addToTxtIgnore",
|
|
654
|
+
question: "Add ignore patterns to .txtignore file? (y/n): ",
|
|
655
|
+
default: "n",
|
|
555
656
|
validate: (value) => {
|
|
556
657
|
const answer = value.toLowerCase();
|
|
557
|
-
if (![
|
|
658
|
+
if (!["y", "n", "yes", "no"].includes(answer))
|
|
659
|
+
return "Please enter y/n or yes/no";
|
|
558
660
|
return true;
|
|
559
661
|
},
|
|
560
|
-
transform: (value) => [
|
|
662
|
+
transform: (value) => ["y", "yes"].includes(value.toLowerCase()),
|
|
561
663
|
},
|
|
562
664
|
{
|
|
563
|
-
key:
|
|
564
|
-
question:
|
|
565
|
-
default:
|
|
665
|
+
key: "ignoreFolders",
|
|
666
|
+
question: "Ignore folders (comma-separated, or press Enter to skip): ",
|
|
667
|
+
default: "",
|
|
566
668
|
ask: () => config.addToTxtIgnore,
|
|
567
669
|
transform: (value) => {
|
|
568
|
-
if (!value || value.trim() ===
|
|
569
|
-
return value
|
|
570
|
-
|
|
670
|
+
if (!value || value.trim() === "") return [];
|
|
671
|
+
return value
|
|
672
|
+
.split(",")
|
|
673
|
+
.map((f) => f.trim())
|
|
674
|
+
.filter((f) => f);
|
|
675
|
+
},
|
|
571
676
|
},
|
|
572
677
|
{
|
|
573
|
-
key:
|
|
574
|
-
question:
|
|
575
|
-
default:
|
|
678
|
+
key: "ignoreFiles",
|
|
679
|
+
question: "Ignore files (comma-separated, or press Enter to skip): ",
|
|
680
|
+
default: "",
|
|
576
681
|
ask: () => config.addToTxtIgnore,
|
|
577
682
|
transform: (value) => {
|
|
578
|
-
if (!value || value.trim() ===
|
|
579
|
-
return value
|
|
580
|
-
|
|
581
|
-
|
|
683
|
+
if (!value || value.trim() === "") return [];
|
|
684
|
+
return value
|
|
685
|
+
.split(",")
|
|
686
|
+
.map((f) => f.trim())
|
|
687
|
+
.filter((f) => f);
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
key: "addToGitignore",
|
|
692
|
+
question:
|
|
693
|
+
"Add .txtconfig, .txtignore, and <folder>.txt to .gitignore? (y/n): ",
|
|
694
|
+
default: "n",
|
|
695
|
+
validate: (value) => {
|
|
696
|
+
const answer = value.toLowerCase();
|
|
697
|
+
if (!["y", "n", "yes", "no"].includes(answer))
|
|
698
|
+
return "Please enter y/n or yes/no";
|
|
699
|
+
return true;
|
|
700
|
+
},
|
|
701
|
+
transform: (value) => ["y", "yes"].includes(value.toLowerCase()),
|
|
702
|
+
},
|
|
582
703
|
];
|
|
583
704
|
|
|
584
705
|
function askQuestion() {
|
|
@@ -589,7 +710,7 @@ function createInteractiveConfig() {
|
|
|
589
710
|
}
|
|
590
711
|
|
|
591
712
|
const q = questions[currentStep];
|
|
592
|
-
|
|
713
|
+
|
|
593
714
|
// Skip if conditional ask returns false
|
|
594
715
|
if (q.ask && !q.ask()) {
|
|
595
716
|
currentStep++;
|
|
@@ -597,36 +718,40 @@ function createInteractiveConfig() {
|
|
|
597
718
|
return;
|
|
598
719
|
}
|
|
599
720
|
|
|
600
|
-
const defaultValue =
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
721
|
+
const defaultValue =
|
|
722
|
+
typeof q.default === "function" ? q.default() : q.default;
|
|
723
|
+
rl.question(
|
|
724
|
+
q.question + (defaultValue ? `(${defaultValue}) ` : ""),
|
|
725
|
+
(answer) => {
|
|
726
|
+
const value = answer.trim() || defaultValue;
|
|
727
|
+
|
|
728
|
+
// Validate input
|
|
729
|
+
if (q.validate) {
|
|
730
|
+
const validation = q.validate(value);
|
|
731
|
+
if (validation !== true) {
|
|
732
|
+
console.log(`❌ ${validation}`);
|
|
733
|
+
askQuestion();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
611
736
|
}
|
|
612
|
-
}
|
|
613
737
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
738
|
+
// Transform value if needed
|
|
739
|
+
if (q.transform) {
|
|
740
|
+
config[q.key] = q.transform(value);
|
|
741
|
+
} else {
|
|
742
|
+
config[q.key] = value;
|
|
743
|
+
}
|
|
620
744
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
745
|
+
currentStep++;
|
|
746
|
+
askQuestion();
|
|
747
|
+
},
|
|
748
|
+
);
|
|
624
749
|
}
|
|
625
750
|
|
|
626
751
|
function saveConfig() {
|
|
627
752
|
try {
|
|
628
753
|
// Create .txtconfig file with proper formatting
|
|
629
|
-
const configPath = path.join(process.cwd(),
|
|
754
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
630
755
|
const configContent = `{
|
|
631
756
|
"maxFileSize": "${config.maxFileSize}",
|
|
632
757
|
"splitMethod": "${config.splitMethod}",
|
|
@@ -637,39 +762,81 @@ function createInteractiveConfig() {
|
|
|
637
762
|
fs.writeFileSync(configPath, configContent);
|
|
638
763
|
|
|
639
764
|
// Update .txtignore if user wants to add ignore patterns
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
765
|
+
if (
|
|
766
|
+
config.addToTxtIgnore &&
|
|
767
|
+
(config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)
|
|
768
|
+
) {
|
|
769
|
+
const ignorePath = path.join(process.cwd(), ".txtignore");
|
|
770
|
+
let ignoreContent = "";
|
|
771
|
+
|
|
644
772
|
// Read existing content if file exists
|
|
645
773
|
if (fs.existsSync(ignorePath)) {
|
|
646
|
-
ignoreContent = fs.readFileSync(ignorePath,
|
|
647
|
-
if (!ignoreContent.endsWith(
|
|
774
|
+
ignoreContent = fs.readFileSync(ignorePath, "utf8");
|
|
775
|
+
if (!ignoreContent.endsWith("\n")) ignoreContent += "\n";
|
|
648
776
|
}
|
|
649
777
|
|
|
650
778
|
// Add new ignore patterns
|
|
651
779
|
const newPatterns = [
|
|
652
|
-
...config.ignoreFolders.map(f => f.endsWith(
|
|
653
|
-
...config.ignoreFiles
|
|
780
|
+
...config.ignoreFolders.map((f) => (f.endsWith("/") ? f : `${f}/`)),
|
|
781
|
+
...config.ignoreFiles,
|
|
654
782
|
];
|
|
655
783
|
|
|
656
784
|
if (newPatterns.length > 0) {
|
|
657
|
-
ignoreContent +=
|
|
658
|
-
ignoreContent += newPatterns.join(
|
|
785
|
+
ignoreContent += "\n# Added by make-folder-txt config\n";
|
|
786
|
+
ignoreContent += newPatterns.join("\n") + "\n";
|
|
659
787
|
fs.writeFileSync(ignorePath, ignoreContent);
|
|
660
788
|
}
|
|
661
789
|
}
|
|
662
790
|
|
|
663
|
-
|
|
791
|
+
// Update .gitignore if user requested
|
|
792
|
+
if (config.addToGitignore) {
|
|
793
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
794
|
+
const rootFolderName = path.basename(process.cwd());
|
|
795
|
+
const entriesToAdd = [
|
|
796
|
+
".txtconfig",
|
|
797
|
+
".txtignore",
|
|
798
|
+
`${rootFolderName}.txt`,
|
|
799
|
+
];
|
|
800
|
+
|
|
801
|
+
let gitignoreContent = "";
|
|
802
|
+
if (fs.existsSync(gitignorePath)) {
|
|
803
|
+
gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
804
|
+
if (!gitignoreContent.endsWith("\n")) gitignoreContent += "\n";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Only add entries that aren't already present
|
|
808
|
+
const newEntries = entriesToAdd.filter((entry) => {
|
|
809
|
+
const lines = gitignoreContent.split("\n").map((l) => l.trim());
|
|
810
|
+
return !lines.includes(entry);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
if (newEntries.length > 0) {
|
|
814
|
+
gitignoreContent += "\n# make-folder-txt\n";
|
|
815
|
+
gitignoreContent += newEntries.join("\n") + "\n";
|
|
816
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
817
|
+
console.log(`\n🙈 Added to .gitignore: ${newEntries.join(", ")}`);
|
|
818
|
+
} else {
|
|
819
|
+
console.log(
|
|
820
|
+
`\nℹ️ .gitignore entries already present — nothing added`,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
console.log("\n✅ Configuration saved successfully!");
|
|
664
826
|
console.log(`📄 Config file: ${configPath}`);
|
|
665
|
-
if (
|
|
827
|
+
if (
|
|
828
|
+
config.addToTxtIgnore &&
|
|
829
|
+
(config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)
|
|
830
|
+
) {
|
|
666
831
|
console.log(`📝 Ignore patterns added to .txtignore`);
|
|
667
832
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
833
|
+
if (config.addToGitignore) {
|
|
834
|
+
console.log(`🙈 .gitignore updated`);
|
|
835
|
+
}
|
|
836
|
+
console.log("\n💡 Your settings will now be used automatically!");
|
|
837
|
+
console.log("🔄 Run --delete-config to reset to defaults");
|
|
671
838
|
} catch (err) {
|
|
672
|
-
console.error(
|
|
839
|
+
console.error("❌ Error saving configuration:", err.message);
|
|
673
840
|
} finally {
|
|
674
841
|
rl.close();
|
|
675
842
|
resolve();
|
|
@@ -681,20 +848,20 @@ function createInteractiveConfig() {
|
|
|
681
848
|
}
|
|
682
849
|
|
|
683
850
|
function loadConfig() {
|
|
684
|
-
const fs = require(
|
|
685
|
-
const path = require(
|
|
686
|
-
|
|
851
|
+
const fs = require("fs");
|
|
852
|
+
const path = require("path");
|
|
853
|
+
|
|
687
854
|
try {
|
|
688
|
-
const configPath = path.join(process.cwd(),
|
|
855
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
689
856
|
if (!fs.existsSync(configPath)) {
|
|
690
|
-
console.error(
|
|
857
|
+
console.error("❌ .txtconfig file not found. Run --make-config first.");
|
|
691
858
|
process.exit(1);
|
|
692
859
|
}
|
|
693
|
-
|
|
694
|
-
const configContent = fs.readFileSync(configPath,
|
|
860
|
+
|
|
861
|
+
const configContent = fs.readFileSync(configPath, "utf8");
|
|
695
862
|
return JSON.parse(configContent);
|
|
696
863
|
} catch (err) {
|
|
697
|
-
console.error(
|
|
864
|
+
console.error("❌ Error loading configuration:", err.message);
|
|
698
865
|
process.exit(1);
|
|
699
866
|
}
|
|
700
867
|
}
|
|
@@ -717,55 +884,62 @@ async function main() {
|
|
|
717
884
|
|
|
718
885
|
if (args.includes("--delete-config")) {
|
|
719
886
|
try {
|
|
720
|
-
const fs = require(
|
|
721
|
-
const path = require(
|
|
722
|
-
const configPath = path.join(process.cwd(),
|
|
723
|
-
|
|
887
|
+
const fs = require("fs");
|
|
888
|
+
const path = require("path");
|
|
889
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
890
|
+
|
|
724
891
|
if (fs.existsSync(configPath)) {
|
|
725
892
|
fs.unlinkSync(configPath);
|
|
726
|
-
console.log(
|
|
727
|
-
console.log(
|
|
893
|
+
console.log("✅ Configuration file deleted successfully!");
|
|
894
|
+
console.log("🔄 Tool will now use default settings");
|
|
728
895
|
} else {
|
|
729
|
-
console.log(
|
|
896
|
+
console.log("ℹ️ No configuration file found - already using defaults");
|
|
730
897
|
}
|
|
731
898
|
} catch (err) {
|
|
732
|
-
console.error(
|
|
899
|
+
console.error("❌ Error deleting configuration:", err.message);
|
|
733
900
|
}
|
|
734
901
|
process.exit(0);
|
|
735
902
|
}
|
|
736
903
|
|
|
737
904
|
// Auto-use config if it exists and no other flags are provided
|
|
738
|
-
const configPath = path.join(process.cwd(),
|
|
905
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
739
906
|
const hasConfig = fs.existsSync(configPath);
|
|
740
|
-
const hasOtherFlags =
|
|
741
|
-
|
|
907
|
+
const hasOtherFlags =
|
|
908
|
+
args.length > 0 &&
|
|
909
|
+
!args.includes("--help") &&
|
|
910
|
+
!args.includes("-h") &&
|
|
911
|
+
!args.includes("--version") &&
|
|
912
|
+
!args.includes("-v");
|
|
913
|
+
|
|
742
914
|
if (hasConfig && !hasOtherFlags) {
|
|
743
915
|
const config = loadConfig();
|
|
744
|
-
|
|
916
|
+
|
|
745
917
|
// Apply config to command line arguments
|
|
746
918
|
const newArgs = [];
|
|
747
|
-
|
|
919
|
+
|
|
748
920
|
// Add max file size if not default
|
|
749
|
-
if (config.maxFileSize !==
|
|
750
|
-
newArgs.push(
|
|
921
|
+
if (config.maxFileSize !== "500KB") {
|
|
922
|
+
newArgs.push("--skip-large", config.maxFileSize);
|
|
751
923
|
}
|
|
752
|
-
|
|
924
|
+
|
|
753
925
|
// Add split method if not none
|
|
754
|
-
if (config.splitMethod !==
|
|
755
|
-
newArgs.push(
|
|
756
|
-
if (config.splitMethod ===
|
|
757
|
-
newArgs.push(
|
|
926
|
+
if (config.splitMethod !== "none") {
|
|
927
|
+
newArgs.push("--split-method", config.splitMethod);
|
|
928
|
+
if (config.splitMethod === "size") {
|
|
929
|
+
newArgs.push("--split-size", config.splitSize);
|
|
758
930
|
}
|
|
759
931
|
}
|
|
760
|
-
|
|
932
|
+
|
|
761
933
|
// Add copy to clipboard if true
|
|
762
934
|
if (config.copyToClipboard) {
|
|
763
|
-
newArgs.push(
|
|
935
|
+
newArgs.push("--copy");
|
|
764
936
|
}
|
|
765
|
-
|
|
937
|
+
|
|
766
938
|
// Replace args with config-based args
|
|
767
939
|
args.splice(0, args.length, ...newArgs);
|
|
768
|
-
console.log(
|
|
940
|
+
console.log(
|
|
941
|
+
"🔧 Using saved configuration (use --delete-config to reset to defaults)",
|
|
942
|
+
);
|
|
769
943
|
}
|
|
770
944
|
|
|
771
945
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -787,7 +961,7 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
787
961
|
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
788
962
|
--copy Copy output to clipboard
|
|
789
963
|
--force Include everything (overrides all ignore patterns)
|
|
790
|
-
--make-config Create interactive configuration
|
|
964
|
+
--make-config Create interactive configuration (with optional .gitignore setup)
|
|
791
965
|
--delete-config Delete configuration and reset to defaults
|
|
792
966
|
--help, -h Show this help message
|
|
793
967
|
--version, -v Show version information
|
|
@@ -824,417 +998,439 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
824
998
|
coverage/
|
|
825
999
|
LICENSE
|
|
826
1000
|
`);
|
|
827
|
-
|
|
1001
|
+
process.exit(0);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const ignoreDirs = new Set(IGNORE_DIRS);
|
|
1005
|
+
const ignoreFiles = new Set(IGNORE_FILES);
|
|
1006
|
+
const onlyFolders = new Set();
|
|
1007
|
+
const onlyFiles = new Set();
|
|
1008
|
+
let outputArg = null;
|
|
1009
|
+
let copyToClipboardFlag = false;
|
|
1010
|
+
let forceFlag = false;
|
|
1011
|
+
let maxFileSize = 500 * 1024; // Default 500KB
|
|
1012
|
+
let noSkipFlag = false;
|
|
1013
|
+
let splitMethod = null; // 'folder', 'file', 'size'
|
|
1014
|
+
let splitSize = null; // size in bytes
|
|
1015
|
+
let rootOnlyInclude = false; // For /include flag
|
|
1016
|
+
|
|
1017
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1018
|
+
const arg = args[i];
|
|
1019
|
+
|
|
1020
|
+
if (arg === "--copy") {
|
|
1021
|
+
copyToClipboardFlag = true;
|
|
1022
|
+
continue;
|
|
828
1023
|
}
|
|
829
1024
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
let outputArg = null;
|
|
835
|
-
let copyToClipboardFlag = false;
|
|
836
|
-
let forceFlag = false;
|
|
837
|
-
let maxFileSize = 500 * 1024; // Default 500KB
|
|
838
|
-
let noSkipFlag = false;
|
|
839
|
-
let splitMethod = null; // 'folder', 'file', 'size'
|
|
840
|
-
let splitSize = null; // size in bytes
|
|
841
|
-
let rootOnlyInclude = false; // For /include flag
|
|
842
|
-
|
|
843
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
844
|
-
const arg = args[i];
|
|
845
|
-
|
|
846
|
-
if (arg === "--copy") {
|
|
847
|
-
copyToClipboardFlag = true;
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
1025
|
+
if (arg === "--force") {
|
|
1026
|
+
forceFlag = true;
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
850
1029
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1030
|
+
if (arg === "--no-skip") {
|
|
1031
|
+
noSkipFlag = true;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
855
1034
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1035
|
+
if (arg === "--skip-large") {
|
|
1036
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1037
|
+
console.error(
|
|
1038
|
+
"Error: --skip-large requires a size value (e.g., 400KB, 5GB).",
|
|
1039
|
+
);
|
|
1040
|
+
process.exit(1);
|
|
859
1041
|
}
|
|
1042
|
+
maxFileSize = parseFileSize(args[i + 1]);
|
|
1043
|
+
i += 1;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
860
1046
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
continue;
|
|
1047
|
+
if (arg.startsWith("--skip-large=")) {
|
|
1048
|
+
const value = arg.slice("--skip-large=".length);
|
|
1049
|
+
if (!value) {
|
|
1050
|
+
console.error(
|
|
1051
|
+
"Error: --skip-large requires a size value (e.g., 400KB, 5GB).",
|
|
1052
|
+
);
|
|
1053
|
+
process.exit(1);
|
|
869
1054
|
}
|
|
1055
|
+
maxFileSize = parseFileSize(value);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
870
1058
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
maxFileSize = parseFileSize(value);
|
|
878
|
-
continue;
|
|
1059
|
+
if (arg === "--split-method") {
|
|
1060
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1061
|
+
console.error(
|
|
1062
|
+
"Error: --split-method requires a method (folder, file, or size).",
|
|
1063
|
+
);
|
|
1064
|
+
process.exit(1);
|
|
879
1065
|
}
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
const method = args[i + 1].toLowerCase();
|
|
887
|
-
if (!['folder', 'file', 'size'].includes(method)) {
|
|
888
|
-
console.error("Error: --split-method must be one of: folder, file, size");
|
|
889
|
-
process.exit(1);
|
|
890
|
-
}
|
|
891
|
-
splitMethod = method;
|
|
892
|
-
i += 1;
|
|
893
|
-
continue;
|
|
1066
|
+
const method = args[i + 1].toLowerCase();
|
|
1067
|
+
if (!["folder", "file", "size"].includes(method)) {
|
|
1068
|
+
console.error(
|
|
1069
|
+
"Error: --split-method must be one of: folder, file, size",
|
|
1070
|
+
);
|
|
1071
|
+
process.exit(1);
|
|
894
1072
|
}
|
|
1073
|
+
splitMethod = method;
|
|
1074
|
+
i += 1;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
895
1077
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
if (!['folder', 'file', 'size'].includes(method)) {
|
|
904
|
-
console.error("Error: --split-method must be one of: folder, file, size");
|
|
905
|
-
process.exit(1);
|
|
906
|
-
}
|
|
907
|
-
splitMethod = method;
|
|
908
|
-
continue;
|
|
1078
|
+
if (arg.startsWith("--split-method=")) {
|
|
1079
|
+
const value = arg.slice("--split-method=".length);
|
|
1080
|
+
if (!value) {
|
|
1081
|
+
console.error(
|
|
1082
|
+
"Error: --split-method requires a method (folder, file, or size).",
|
|
1083
|
+
);
|
|
1084
|
+
process.exit(1);
|
|
909
1085
|
}
|
|
910
|
-
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
splitSize = parseFileSize(args[i + 1]);
|
|
917
|
-
i += 1;
|
|
918
|
-
continue;
|
|
1086
|
+
const method = value.toLowerCase();
|
|
1087
|
+
if (!["folder", "file", "size"].includes(method)) {
|
|
1088
|
+
console.error(
|
|
1089
|
+
"Error: --split-method must be one of: folder, file, size",
|
|
1090
|
+
);
|
|
1091
|
+
process.exit(1);
|
|
919
1092
|
}
|
|
1093
|
+
splitMethod = method;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
920
1096
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
splitSize = parseFileSize(value);
|
|
928
|
-
continue;
|
|
1097
|
+
if (arg === "--split-size") {
|
|
1098
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1099
|
+
console.error(
|
|
1100
|
+
"Error: --split-size requires a size value (e.g., 5MB, 10MB).",
|
|
1101
|
+
);
|
|
1102
|
+
process.exit(1);
|
|
929
1103
|
}
|
|
1104
|
+
splitSize = parseFileSize(args[i + 1]);
|
|
1105
|
+
i += 1;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
930
1108
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const cleanFolderName = folderName.slice(1); // Remove leading /
|
|
939
|
-
onlyFolders.add(cleanFolderName);
|
|
940
|
-
rootOnlyInclude = true;
|
|
941
|
-
i += 1;
|
|
942
|
-
consumed += 1;
|
|
943
|
-
} else {
|
|
944
|
-
// Normal folder path (could be nested like folder/subfolder)
|
|
945
|
-
onlyFolders.add(folderName);
|
|
946
|
-
i += 1;
|
|
947
|
-
consumed += 1;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
if (consumed === 0) {
|
|
951
|
-
console.error("Error: --ignore-folder requires at least one folder name.");
|
|
952
|
-
process.exit(1);
|
|
953
|
-
}
|
|
954
|
-
continue;
|
|
1109
|
+
if (arg.startsWith("--split-size=")) {
|
|
1110
|
+
const value = arg.slice("--split-size=".length);
|
|
1111
|
+
if (!value) {
|
|
1112
|
+
console.error(
|
|
1113
|
+
"Error: --split-size requires a size value (e.g., 5MB, 10MB).",
|
|
1114
|
+
);
|
|
1115
|
+
process.exit(1);
|
|
955
1116
|
}
|
|
1117
|
+
splitSize = parseFileSize(value);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
956
1120
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
// Normal folder path (could be nested like folder/subfolder)
|
|
973
|
-
onlyFolders.add(value);
|
|
974
|
-
}
|
|
975
|
-
continue;
|
|
1121
|
+
if (arg === "--ignore-folder" || arg === "-ifo") {
|
|
1122
|
+
let consumed = 0;
|
|
1123
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1124
|
+
let folderName = args[i + 1];
|
|
1125
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1126
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1127
|
+
ignoreDirs.add(folderName);
|
|
1128
|
+
i += 1;
|
|
1129
|
+
consumed += 1;
|
|
1130
|
+
}
|
|
1131
|
+
if (consumed === 0) {
|
|
1132
|
+
console.error(
|
|
1133
|
+
"Error: --ignore-folder requires at least one folder name.",
|
|
1134
|
+
);
|
|
1135
|
+
process.exit(1);
|
|
976
1136
|
}
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
977
1139
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
if (consumed === 0) {
|
|
986
|
-
console.error("Error: --ignore-file requires at least one file name.");
|
|
987
|
-
process.exit(1);
|
|
988
|
-
}
|
|
989
|
-
continue;
|
|
1140
|
+
if (arg.startsWith("--ignore-folder=") || arg.startsWith("-ifo=")) {
|
|
1141
|
+
const value = arg.startsWith("--ignore-folder=")
|
|
1142
|
+
? arg.slice("--ignore-folder=".length)
|
|
1143
|
+
: arg.slice("-ifo=".length);
|
|
1144
|
+
if (!value) {
|
|
1145
|
+
console.error("Error: --ignore-folder requires a folder name.");
|
|
1146
|
+
process.exit(1);
|
|
990
1147
|
}
|
|
1148
|
+
let folderName = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1149
|
+
ignoreDirs.add(folderName);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
991
1152
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
process.exit(1);
|
|
999
|
-
}
|
|
1000
|
-
ignoreFiles.add(value);
|
|
1001
|
-
continue;
|
|
1153
|
+
if (arg === "--ignore-file" || arg === "-ifi") {
|
|
1154
|
+
let consumed = 0;
|
|
1155
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1156
|
+
ignoreFiles.add(args[i + 1]);
|
|
1157
|
+
i += 1;
|
|
1158
|
+
consumed += 1;
|
|
1002
1159
|
}
|
|
1160
|
+
if (consumed === 0) {
|
|
1161
|
+
console.error("Error: --ignore-file requires at least one file name.");
|
|
1162
|
+
process.exit(1);
|
|
1163
|
+
}
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1003
1166
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
rootOnlyInclude = true;
|
|
1012
|
-
}
|
|
1013
|
-
folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
|
|
1014
|
-
folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
|
|
1015
|
-
onlyFolders.add(folderName);
|
|
1016
|
-
i += 1;
|
|
1017
|
-
consumed += 1;
|
|
1018
|
-
}
|
|
1019
|
-
if (consumed === 0) {
|
|
1020
|
-
console.error("Error: --only-folder requires at least one folder name.");
|
|
1021
|
-
process.exit(1);
|
|
1022
|
-
}
|
|
1023
|
-
continue;
|
|
1167
|
+
if (arg.startsWith("--ignore-file=") || arg.startsWith("-ifi=")) {
|
|
1168
|
+
const value = arg.startsWith("--ignore-file=")
|
|
1169
|
+
? arg.slice("--ignore-file=".length)
|
|
1170
|
+
: arg.slice("-ifi=".length);
|
|
1171
|
+
if (!value) {
|
|
1172
|
+
console.error("Error: --ignore-file requires a file name.");
|
|
1173
|
+
process.exit(1);
|
|
1024
1174
|
}
|
|
1175
|
+
ignoreFiles.add(value);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1025
1178
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
process.exit(1);
|
|
1033
|
-
}
|
|
1034
|
-
let folderName = value;
|
|
1035
|
-
folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
|
|
1036
|
-
// Detect leading / as root-only signal
|
|
1179
|
+
if (arg === "--only-folder" || arg === "-ofo") {
|
|
1180
|
+
let consumed = 0;
|
|
1181
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1182
|
+
let folderName = args[i + 1];
|
|
1183
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1184
|
+
// Detect leading / as root-only signal (same as --only-file behavior)
|
|
1037
1185
|
if (folderName.startsWith("/")) {
|
|
1038
1186
|
rootOnlyInclude = true;
|
|
1039
1187
|
}
|
|
1040
|
-
folderName = folderName.replace(/^\.?\//,
|
|
1041
|
-
folderName = folderName.replace(/\/+$/,
|
|
1188
|
+
folderName = folderName.replace(/^\.?\//, ""); // Remove leading ./ or /
|
|
1189
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1042
1190
|
onlyFolders.add(folderName);
|
|
1043
|
-
|
|
1191
|
+
i += 1;
|
|
1192
|
+
consumed += 1;
|
|
1193
|
+
}
|
|
1194
|
+
if (consumed === 0) {
|
|
1195
|
+
console.error(
|
|
1196
|
+
"Error: --only-folder requires at least one folder name.",
|
|
1197
|
+
);
|
|
1198
|
+
process.exit(1);
|
|
1044
1199
|
}
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1045
1202
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const cleanFileName = fileName.slice(1); // Remove leading /
|
|
1054
|
-
onlyFiles.add(cleanFileName);
|
|
1055
|
-
rootOnlyInclude = true;
|
|
1056
|
-
i += 1;
|
|
1057
|
-
consumed += 1;
|
|
1058
|
-
} else {
|
|
1059
|
-
// Normal file path (could be nested like folder/file.ext)
|
|
1060
|
-
onlyFiles.add(fileName);
|
|
1061
|
-
i += 1;
|
|
1062
|
-
consumed += 1;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
if (consumed === 0) {
|
|
1066
|
-
console.error("Error: --only-file requires at least one file name.");
|
|
1067
|
-
process.exit(1);
|
|
1068
|
-
}
|
|
1069
|
-
continue;
|
|
1203
|
+
if (arg.startsWith("--only-folder=") || arg.startsWith("-ofo=")) {
|
|
1204
|
+
const value = arg.startsWith("--only-folder=")
|
|
1205
|
+
? arg.slice("--only-folder=".length)
|
|
1206
|
+
: arg.slice("-ofo=".length);
|
|
1207
|
+
if (!value) {
|
|
1208
|
+
console.error("Error: --only-folder requires a folder name.");
|
|
1209
|
+
process.exit(1);
|
|
1070
1210
|
}
|
|
1211
|
+
let folderName = value;
|
|
1212
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1213
|
+
// Detect leading / as root-only signal
|
|
1214
|
+
if (folderName.startsWith("/")) {
|
|
1215
|
+
rootOnlyInclude = true;
|
|
1216
|
+
}
|
|
1217
|
+
folderName = folderName.replace(/^\.?\//, ""); // Remove leading ./ or /
|
|
1218
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1219
|
+
onlyFolders.add(folderName);
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (arg === "--only-file" || arg === "-ofi") {
|
|
1224
|
+
let consumed = 0;
|
|
1225
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1226
|
+
let fileName = args[i + 1];
|
|
1071
1227
|
|
|
1072
|
-
if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
|
|
1073
|
-
const value = arg.startsWith("--only-file=")
|
|
1074
|
-
? arg.slice("--only-file=".length)
|
|
1075
|
-
: arg.slice("-ofi=".length);
|
|
1076
|
-
if (!value) {
|
|
1077
|
-
console.error("Error: --only-file requires a file name.");
|
|
1078
|
-
process.exit(1);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1228
|
// Check for /file syntax (root-only include)
|
|
1082
|
-
if (
|
|
1083
|
-
const cleanFileName =
|
|
1229
|
+
if (fileName.startsWith("/")) {
|
|
1230
|
+
const cleanFileName = fileName.slice(1); // Remove leading /
|
|
1084
1231
|
onlyFiles.add(cleanFileName);
|
|
1085
1232
|
rootOnlyInclude = true;
|
|
1233
|
+
i += 1;
|
|
1234
|
+
consumed += 1;
|
|
1086
1235
|
} else {
|
|
1087
1236
|
// Normal file path (could be nested like folder/file.ext)
|
|
1088
|
-
onlyFiles.add(
|
|
1237
|
+
onlyFiles.add(fileName);
|
|
1238
|
+
i += 1;
|
|
1239
|
+
consumed += 1;
|
|
1089
1240
|
}
|
|
1090
|
-
continue;
|
|
1091
1241
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1242
|
+
if (consumed === 0) {
|
|
1243
|
+
console.error("Error: --only-file requires at least one file name.");
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
}
|
|
1246
|
+
continue;
|
|
1097
1247
|
}
|
|
1098
1248
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1249
|
+
if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
|
|
1250
|
+
const value = arg.startsWith("--only-file=")
|
|
1251
|
+
? arg.slice("--only-file=".length)
|
|
1252
|
+
: arg.slice("-ofi=".length);
|
|
1253
|
+
if (!value) {
|
|
1254
|
+
console.error("Error: --only-file requires a file name.");
|
|
1255
|
+
process.exit(1);
|
|
1256
|
+
}
|
|
1104
1257
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1258
|
+
// Check for /file syntax (root-only include)
|
|
1259
|
+
if (value.startsWith("/")) {
|
|
1260
|
+
const cleanFileName = value.slice(1); // Remove leading /
|
|
1261
|
+
onlyFiles.add(cleanFileName);
|
|
1262
|
+
rootOnlyInclude = true;
|
|
1263
|
+
} else {
|
|
1264
|
+
// Normal file path (could be nested like folder/file.ext)
|
|
1265
|
+
onlyFiles.add(value);
|
|
1266
|
+
}
|
|
1267
|
+
continue;
|
|
1108
1268
|
}
|
|
1109
1269
|
|
|
1110
|
-
//
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
// ── build tree & collect file paths ───────────────────────────────────────────────
|
|
1117
|
-
|
|
1118
|
-
const { lines: treeLines, filePaths } = collectFiles(
|
|
1119
|
-
folderPath,
|
|
1120
|
-
folderPath,
|
|
1121
|
-
ignoreDirs,
|
|
1122
|
-
ignoreFiles,
|
|
1123
|
-
onlyFolders,
|
|
1124
|
-
onlyFiles,
|
|
1125
|
-
{
|
|
1126
|
-
rootName,
|
|
1127
|
-
txtIgnore,
|
|
1128
|
-
force: forceFlag,
|
|
1129
|
-
hasOnlyFilters: onlyFolders.size > 0 || onlyFiles.size > 0,
|
|
1130
|
-
rootOnlyInclude
|
|
1131
|
-
}
|
|
1132
|
-
);
|
|
1270
|
+
// Unknown argument
|
|
1271
|
+
console.error(`Error: Unknown option "${arg}"`);
|
|
1272
|
+
console.error("Use --help for available options.");
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1133
1275
|
|
|
1134
|
-
|
|
1276
|
+
// Validate split options
|
|
1277
|
+
if (splitMethod === "size" && !splitSize) {
|
|
1278
|
+
console.error(
|
|
1279
|
+
"Error: --split-method size requires --split-size to be specified",
|
|
1280
|
+
);
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}
|
|
1135
1283
|
|
|
1136
|
-
|
|
1284
|
+
if (splitSize && splitMethod !== "size") {
|
|
1285
|
+
console.error(
|
|
1286
|
+
"Error: --split-size can only be used with --split-method size",
|
|
1287
|
+
);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1137
1290
|
|
|
1138
|
-
|
|
1291
|
+
// ── config ────────────────────────────────────────────────────────────────────────
|
|
1292
|
+
|
|
1293
|
+
const folderPath = process.cwd();
|
|
1294
|
+
const rootName = path.basename(folderPath);
|
|
1295
|
+
const txtIgnore = readTxtIgnore(folderPath);
|
|
1296
|
+
|
|
1297
|
+
// ── build tree & collect file paths ───────────────────────────────────────────────
|
|
1298
|
+
|
|
1299
|
+
const { lines: treeLines, filePaths } = collectFiles(
|
|
1300
|
+
folderPath,
|
|
1301
|
+
folderPath,
|
|
1302
|
+
ignoreDirs,
|
|
1303
|
+
ignoreFiles,
|
|
1304
|
+
onlyFolders,
|
|
1305
|
+
onlyFiles,
|
|
1306
|
+
{
|
|
1307
|
+
rootName,
|
|
1308
|
+
txtIgnore,
|
|
1309
|
+
force: forceFlag,
|
|
1310
|
+
hasOnlyFilters: onlyFolders.size > 0 || onlyFiles.size > 0,
|
|
1311
|
+
rootOnlyInclude,
|
|
1312
|
+
},
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
// ── build output filename ───────────────────────────────────────────────────────────
|
|
1316
|
+
|
|
1317
|
+
let outputFile = outputArg || path.join(folderPath, `${rootName}.txt`);
|
|
1318
|
+
|
|
1319
|
+
// ── handle output splitting ─────────────────────────────────────────────────────────
|
|
1320
|
+
|
|
1321
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
1322
|
+
|
|
1323
|
+
if (splitMethod) {
|
|
1324
|
+
console.log(`🔧 Splitting output by: ${splitMethod}`);
|
|
1325
|
+
|
|
1326
|
+
let results;
|
|
1327
|
+
|
|
1328
|
+
if (splitMethod === "folder") {
|
|
1329
|
+
results = splitByFolders(
|
|
1330
|
+
treeLines,
|
|
1331
|
+
filePaths,
|
|
1332
|
+
rootName,
|
|
1333
|
+
effectiveMaxSize,
|
|
1334
|
+
forceFlag,
|
|
1335
|
+
);
|
|
1336
|
+
} else if (splitMethod === "file") {
|
|
1337
|
+
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
1338
|
+
} else if (splitMethod === "size") {
|
|
1339
|
+
results = splitBySize(
|
|
1340
|
+
treeLines,
|
|
1341
|
+
filePaths,
|
|
1342
|
+
rootName,
|
|
1343
|
+
splitSize,
|
|
1344
|
+
effectiveMaxSize,
|
|
1345
|
+
forceFlag,
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1139
1348
|
|
|
1140
|
-
|
|
1349
|
+
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
1350
|
+
console.log("");
|
|
1141
1351
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
if (splitMethod ===
|
|
1148
|
-
|
|
1149
|
-
} else if (splitMethod === 'file') {
|
|
1150
|
-
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
1151
|
-
} else if (splitMethod === 'size') {
|
|
1152
|
-
results = splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize, forceFlag);
|
|
1352
|
+
results.forEach((result, index) => {
|
|
1353
|
+
if (splitMethod === "folder") {
|
|
1354
|
+
console.log(`📁 Folder: ${result.folder}`);
|
|
1355
|
+
} else if (splitMethod === "file") {
|
|
1356
|
+
console.log(`📄 File: ${result.fileName}`);
|
|
1357
|
+
} else if (splitMethod === "size") {
|
|
1358
|
+
console.log(`📦 Part ${result.part}`);
|
|
1153
1359
|
}
|
|
1154
|
-
|
|
1155
|
-
console.log(
|
|
1156
|
-
console.log(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
console.log(`📦 Part ${result.part}`);
|
|
1165
|
-
}
|
|
1166
|
-
console.log(`📄 Output : ${result.file}`);
|
|
1167
|
-
console.log(`📊 Size : ${result.size} KB`);
|
|
1168
|
-
console.log(`🗂️ Files : ${result.files}`);
|
|
1169
|
-
console.log('');
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
if (copyToClipboardFlag) {
|
|
1173
|
-
console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
process.exit(0);
|
|
1360
|
+
console.log(`📄 Output : ${result.file}`);
|
|
1361
|
+
console.log(`📊 Size : ${result.size} KB`);
|
|
1362
|
+
console.log(`🗂️ Files : ${result.files}`);
|
|
1363
|
+
console.log("");
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
if (copyToClipboardFlag) {
|
|
1367
|
+
console.log(
|
|
1368
|
+
"⚠️ --copy flag is not compatible with splitting - clipboard copy skipped",
|
|
1369
|
+
);
|
|
1177
1370
|
}
|
|
1178
1371
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const divider = "=".repeat(80);
|
|
1182
|
-
const subDivider = "-".repeat(80);
|
|
1372
|
+
process.exit(0);
|
|
1373
|
+
}
|
|
1183
1374
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1375
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
1376
|
+
const out = [];
|
|
1377
|
+
const divider = "=".repeat(80);
|
|
1378
|
+
const subDivider = "-".repeat(80);
|
|
1188
1379
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1380
|
+
out.push(divider);
|
|
1381
|
+
out.push(`START OF FOLDER: ${rootName}`);
|
|
1382
|
+
out.push(divider);
|
|
1383
|
+
out.push("");
|
|
1193
1384
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
out.push("");
|
|
1385
|
+
out.push(divider);
|
|
1386
|
+
out.push("PROJECT STRUCTURE");
|
|
1387
|
+
out.push(divider);
|
|
1388
|
+
out.push(`Root: ${folderPath}\n`);
|
|
1199
1389
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1390
|
+
out.push(`${rootName}/`);
|
|
1391
|
+
treeLines.forEach((l) => out.push(l));
|
|
1392
|
+
out.push("");
|
|
1393
|
+
out.push(`Total files: ${filePaths.length}`);
|
|
1394
|
+
out.push("");
|
|
1203
1395
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
out.push(`FILE: ${rel}`);
|
|
1208
|
-
out.push(subDivider);
|
|
1209
|
-
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
1210
|
-
});
|
|
1396
|
+
out.push(divider);
|
|
1397
|
+
out.push("FILE CONTENTS");
|
|
1398
|
+
out.push(divider);
|
|
1211
1399
|
|
|
1400
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
1212
1401
|
out.push("");
|
|
1213
|
-
out.push(
|
|
1214
|
-
out.push(`
|
|
1215
|
-
out.push(
|
|
1402
|
+
out.push(subDivider);
|
|
1403
|
+
out.push(`FILE: ${rel}`);
|
|
1404
|
+
out.push(subDivider);
|
|
1405
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
out.push("");
|
|
1409
|
+
out.push(divider);
|
|
1410
|
+
out.push(`END OF FOLDER: ${rootName}`);
|
|
1411
|
+
out.push(divider);
|
|
1216
1412
|
|
|
1217
|
-
|
|
1413
|
+
fs.writeFileSync(outputFile, out.join("\n"), "utf8");
|
|
1218
1414
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1415
|
+
const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
|
|
1416
|
+
console.log(`✅ Done!`);
|
|
1417
|
+
console.log(`📄 Output : ${outputFile}`);
|
|
1418
|
+
console.log(`📊 Size : ${sizeKB} KB`);
|
|
1419
|
+
console.log(`🗂️ Files : ${filePaths.length}`);
|
|
1224
1420
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1421
|
+
if (copyToClipboardFlag) {
|
|
1422
|
+
const content = fs.readFileSync(outputFile, "utf8");
|
|
1423
|
+
const success = copyToClipboard(content);
|
|
1424
|
+
if (success) {
|
|
1425
|
+
console.log(`📋 Copied to clipboard!`);
|
|
1231
1426
|
}
|
|
1427
|
+
}
|
|
1232
1428
|
|
|
1233
|
-
|
|
1429
|
+
console.log("");
|
|
1234
1430
|
}
|
|
1235
1431
|
|
|
1236
1432
|
// Run the main function
|
|
1237
|
-
main().catch(err => {
|
|
1238
|
-
console.error(
|
|
1433
|
+
main().catch((err) => {
|
|
1434
|
+
console.error("❌ Error:", err.message);
|
|
1239
1435
|
process.exit(1);
|
|
1240
|
-
});
|
|
1436
|
+
});
|