make-folder-txt 2.2.7 → 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 +799 -590
- package/make-folder-txt.txt +6 -2
- 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
|
}
|
|
@@ -127,14 +185,19 @@ function collectFiles(
|
|
|
127
185
|
}
|
|
128
186
|
|
|
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
|
|
189
|
+
const folderIsSelected = rootOnlyInclude
|
|
190
|
+
? dir === rootDir && onlyFolders.has(entry.name)
|
|
191
|
+
: onlyFolders.has(entry.name);
|
|
192
|
+
|
|
193
|
+
const childInSelectedFolder = inSelectedFolder || folderIsSelected;
|
|
131
194
|
const childLines = [];
|
|
132
195
|
const childFiles = [];
|
|
133
|
-
|
|
134
|
-
// If rootOnlyInclude is true,
|
|
196
|
+
|
|
197
|
+
// If rootOnlyInclude is true, skip recursing into non-selected subdirectories
|
|
135
198
|
let child;
|
|
136
|
-
if (rootOnlyInclude && dir !== rootDir) {
|
|
137
|
-
// Don't recurse
|
|
199
|
+
if (rootOnlyInclude && dir !== rootDir && !inSelectedFolder) {
|
|
200
|
+
// Don't recurse unless already inside a selected folder
|
|
138
201
|
child = { lines: [], filePaths: [], hasIncluded: false };
|
|
139
202
|
} else {
|
|
140
203
|
child = collectFiles(
|
|
@@ -158,8 +221,11 @@ function collectFiles(
|
|
|
158
221
|
);
|
|
159
222
|
}
|
|
160
223
|
|
|
161
|
-
|
|
162
|
-
const shouldIncludeDir =
|
|
224
|
+
// Include the dir if: no filters active, or it's explicitly selected, or (non-root-only) a child matched
|
|
225
|
+
const shouldIncludeDir =
|
|
226
|
+
!hasOnlyFilters ||
|
|
227
|
+
folderIsSelected ||
|
|
228
|
+
(!rootOnlyInclude && child.hasIncluded);
|
|
163
229
|
|
|
164
230
|
if (shouldIncludeDir) {
|
|
165
231
|
lines.push(`${indent}${connector}${entry.name}/`);
|
|
@@ -170,48 +236,74 @@ function collectFiles(
|
|
|
170
236
|
if (!force && ignoreFiles.has(entry.name)) return;
|
|
171
237
|
|
|
172
238
|
// Get relative path for .txtignore pattern matching
|
|
173
|
-
const relPathForIgnore = path
|
|
174
|
-
|
|
239
|
+
const relPathForIgnore = path
|
|
240
|
+
.relative(rootDir, path.join(dir, entry.name))
|
|
241
|
+
.split(path.sep)
|
|
242
|
+
.join("/");
|
|
243
|
+
|
|
175
244
|
// Check against .txtignore patterns (both filename and relative path) unless force is enabled
|
|
176
|
-
if (
|
|
245
|
+
if (
|
|
246
|
+
!force &&
|
|
247
|
+
(txtIgnore.has(entry.name) ||
|
|
248
|
+
txtIgnore.has(relPathForIgnore) ||
|
|
249
|
+
txtIgnore.has(`/${relPathForIgnore}`))
|
|
250
|
+
) {
|
|
177
251
|
return;
|
|
178
252
|
}
|
|
179
253
|
|
|
180
254
|
// Ignore .txt files that match the folder name (e.g., foldername.txt) unless force is enabled
|
|
181
|
-
if (
|
|
255
|
+
if (
|
|
256
|
+
!force &&
|
|
257
|
+
entry.name.endsWith(".txt") &&
|
|
258
|
+
entry.name === `${rootName}.txt`
|
|
259
|
+
)
|
|
260
|
+
return;
|
|
182
261
|
|
|
183
262
|
// Check if this file matches any of the onlyFiles patterns
|
|
184
|
-
const shouldIncludeFile =
|
|
185
|
-
|
|
186
|
-
|
|
263
|
+
const shouldIncludeFile =
|
|
264
|
+
!hasOnlyFilters ||
|
|
265
|
+
inSelectedFolder ||
|
|
266
|
+
onlyFiles.has(entry.name) ||
|
|
267
|
+
onlyFiles.has(relPathForIgnore) ||
|
|
187
268
|
onlyFiles.has(`/${relPathForIgnore}`);
|
|
188
|
-
|
|
269
|
+
|
|
189
270
|
if (!shouldIncludeFile) return;
|
|
190
271
|
|
|
191
272
|
lines.push(`${indent}${connector}${entry.name}`);
|
|
192
|
-
const relPath =
|
|
273
|
+
const relPath =
|
|
274
|
+
"/" +
|
|
275
|
+
path
|
|
276
|
+
.relative(rootDir, path.join(dir, entry.name))
|
|
277
|
+
.split(path.sep)
|
|
278
|
+
.join("/");
|
|
193
279
|
filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
|
|
194
280
|
}
|
|
195
281
|
});
|
|
196
282
|
|
|
197
|
-
return {
|
|
283
|
+
return {
|
|
284
|
+
lines,
|
|
285
|
+
filePaths,
|
|
286
|
+
hasIncluded: filePaths.length > 0 || lines.length > 0,
|
|
287
|
+
};
|
|
198
288
|
}
|
|
199
289
|
|
|
200
290
|
function parseFileSize(sizeStr) {
|
|
201
291
|
const units = {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
292
|
+
B: 1,
|
|
293
|
+
KB: 1024,
|
|
294
|
+
MB: 1024 * 1024,
|
|
295
|
+
GB: 1024 * 1024 * 1024,
|
|
296
|
+
TB: 1024 * 1024 * 1024 * 1024,
|
|
207
297
|
};
|
|
208
|
-
|
|
298
|
+
|
|
209
299
|
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
|
|
210
300
|
if (!match) {
|
|
211
|
-
console.error(
|
|
301
|
+
console.error(
|
|
302
|
+
`Error: Invalid size format "${sizeStr}". Use format like "500KB", "2MB", "1GB".`,
|
|
303
|
+
);
|
|
212
304
|
process.exit(1);
|
|
213
305
|
}
|
|
214
|
-
|
|
306
|
+
|
|
215
307
|
const value = parseFloat(match[1]);
|
|
216
308
|
const unit = match[2].toUpperCase();
|
|
217
309
|
return Math.floor(value * units[unit]);
|
|
@@ -223,10 +315,14 @@ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
|
|
|
223
315
|
try {
|
|
224
316
|
const stat = fs.statSync(absPath);
|
|
225
317
|
if (!force && stat.size > maxFileSize) {
|
|
226
|
-
const sizeStr =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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`;
|
|
230
326
|
return `[file too large: ${sizeStr} – skipped]`;
|
|
231
327
|
}
|
|
232
328
|
return fs.readFileSync(absPath, "utf8");
|
|
@@ -235,51 +331,57 @@ function readContent(absPath, force = false, maxFileSize = 500 * 1024) {
|
|
|
235
331
|
}
|
|
236
332
|
}
|
|
237
333
|
|
|
238
|
-
function splitByFolders(
|
|
334
|
+
function splitByFolders(
|
|
335
|
+
treeLines,
|
|
336
|
+
filePaths,
|
|
337
|
+
rootName,
|
|
338
|
+
effectiveMaxSize,
|
|
339
|
+
forceFlag,
|
|
340
|
+
) {
|
|
239
341
|
const folders = new Map();
|
|
240
|
-
|
|
342
|
+
|
|
241
343
|
// Group files by folder
|
|
242
344
|
filePaths.forEach(({ abs, rel }) => {
|
|
243
345
|
const folderPath = path.dirname(rel);
|
|
244
|
-
const folderKey = folderPath ===
|
|
245
|
-
|
|
346
|
+
const folderKey = folderPath === "/" ? rootName : folderPath.slice(1);
|
|
347
|
+
|
|
246
348
|
if (!folders.has(folderKey)) {
|
|
247
349
|
folders.set(folderKey, []);
|
|
248
350
|
}
|
|
249
351
|
folders.get(folderKey).push({ abs, rel });
|
|
250
352
|
});
|
|
251
|
-
|
|
353
|
+
|
|
252
354
|
const results = [];
|
|
253
|
-
|
|
355
|
+
|
|
254
356
|
folders.forEach((files, folderName) => {
|
|
255
357
|
const out = [];
|
|
256
358
|
const divider = "=".repeat(80);
|
|
257
359
|
const subDivider = "-".repeat(80);
|
|
258
|
-
|
|
360
|
+
|
|
259
361
|
out.push(divider);
|
|
260
362
|
out.push(`START OF FOLDER: ${folderName}`);
|
|
261
363
|
out.push(divider);
|
|
262
364
|
out.push("");
|
|
263
|
-
|
|
365
|
+
|
|
264
366
|
// Add folder structure (only this folder's structure)
|
|
265
|
-
const folderTreeLines = treeLines.filter(
|
|
266
|
-
line.includes(folderName +
|
|
367
|
+
const folderTreeLines = treeLines.filter(
|
|
368
|
+
(line) => line.includes(folderName + "/") || line === `${rootName}/`,
|
|
267
369
|
);
|
|
268
|
-
|
|
370
|
+
|
|
269
371
|
out.push(divider);
|
|
270
372
|
out.push("PROJECT STRUCTURE");
|
|
271
373
|
out.push(divider);
|
|
272
|
-
out.push(`Root: ${
|
|
374
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
273
375
|
out.push(`${rootName}/`);
|
|
274
|
-
folderTreeLines.forEach(l => out.push(l));
|
|
376
|
+
folderTreeLines.forEach((l) => out.push(l));
|
|
275
377
|
out.push("");
|
|
276
378
|
out.push(`Total files in this folder: ${files.length}`);
|
|
277
379
|
out.push("");
|
|
278
|
-
|
|
380
|
+
|
|
279
381
|
out.push(divider);
|
|
280
382
|
out.push("FILE CONTENTS");
|
|
281
383
|
out.push(divider);
|
|
282
|
-
|
|
384
|
+
|
|
283
385
|
files.forEach(({ abs, rel }) => {
|
|
284
386
|
out.push("");
|
|
285
387
|
out.push(subDivider);
|
|
@@ -287,112 +389,113 @@ function splitByFolders(treeLines, filePaths, rootName, effectiveMaxSize, forceF
|
|
|
287
389
|
out.push(subDivider);
|
|
288
390
|
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
289
391
|
});
|
|
290
|
-
|
|
392
|
+
|
|
291
393
|
out.push("");
|
|
292
394
|
out.push(divider);
|
|
293
395
|
out.push(`END OF FOLDER: ${folderName}`);
|
|
294
396
|
out.push(divider);
|
|
295
|
-
|
|
296
|
-
const fileName = `${rootName}-${folderName.replace(/[\/\\]/g,
|
|
397
|
+
|
|
398
|
+
const fileName = `${rootName}-${folderName.replace(/[\/\\]/g, "-")}.txt`;
|
|
297
399
|
const filePath = path.join(process.cwd(), fileName);
|
|
298
|
-
|
|
400
|
+
|
|
299
401
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
300
402
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
301
|
-
|
|
403
|
+
|
|
302
404
|
results.push({
|
|
303
405
|
file: filePath,
|
|
304
406
|
size: sizeKB,
|
|
305
407
|
files: files.length,
|
|
306
|
-
folder: folderName
|
|
408
|
+
folder: folderName,
|
|
307
409
|
});
|
|
308
410
|
});
|
|
309
|
-
|
|
411
|
+
|
|
310
412
|
return results;
|
|
311
413
|
}
|
|
312
414
|
|
|
313
415
|
function splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag) {
|
|
314
416
|
const results = [];
|
|
315
|
-
|
|
417
|
+
|
|
316
418
|
filePaths.forEach(({ abs, rel }) => {
|
|
317
419
|
const out = [];
|
|
318
420
|
const divider = "=".repeat(80);
|
|
319
421
|
const subDivider = "-".repeat(80);
|
|
320
422
|
const fileName = path.basename(rel, path.extname(rel));
|
|
321
|
-
|
|
423
|
+
|
|
322
424
|
out.push(divider);
|
|
323
425
|
out.push(`FILE: ${rel}`);
|
|
324
426
|
out.push(divider);
|
|
325
427
|
out.push("");
|
|
326
|
-
|
|
428
|
+
|
|
327
429
|
out.push(divider);
|
|
328
430
|
out.push("FILE CONTENTS");
|
|
329
431
|
out.push(divider);
|
|
330
432
|
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
331
|
-
|
|
433
|
+
|
|
332
434
|
out.push("");
|
|
333
435
|
out.push(divider);
|
|
334
436
|
out.push(`END OF FILE: ${rel}`);
|
|
335
437
|
out.push(divider);
|
|
336
|
-
|
|
438
|
+
|
|
337
439
|
const outputFileName = `${rootName}-${fileName}.txt`;
|
|
338
440
|
const filePath = path.join(process.cwd(), outputFileName);
|
|
339
|
-
|
|
441
|
+
|
|
340
442
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
341
443
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
342
|
-
|
|
444
|
+
|
|
343
445
|
results.push({
|
|
344
446
|
file: filePath,
|
|
345
447
|
size: sizeKB,
|
|
346
448
|
files: 1,
|
|
347
|
-
fileName: fileName
|
|
449
|
+
fileName: fileName,
|
|
348
450
|
});
|
|
349
451
|
});
|
|
350
|
-
|
|
452
|
+
|
|
351
453
|
return results;
|
|
352
454
|
}
|
|
353
455
|
|
|
354
|
-
function splitBySize(
|
|
456
|
+
function splitBySize(
|
|
457
|
+
treeLines,
|
|
458
|
+
filePaths,
|
|
459
|
+
rootName,
|
|
460
|
+
splitSize,
|
|
461
|
+
effectiveMaxSize,
|
|
462
|
+
forceFlag,
|
|
463
|
+
) {
|
|
355
464
|
const results = [];
|
|
356
465
|
let currentPart = 1;
|
|
357
466
|
let currentSize = 0;
|
|
358
467
|
let currentFiles = [];
|
|
359
|
-
|
|
468
|
+
|
|
360
469
|
const divider = "=".repeat(80);
|
|
361
470
|
const subDivider = "-".repeat(80);
|
|
362
|
-
|
|
471
|
+
|
|
363
472
|
// Start with header
|
|
364
473
|
let out = [];
|
|
365
474
|
out.push(divider);
|
|
366
475
|
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
367
476
|
out.push(divider);
|
|
368
477
|
out.push("");
|
|
369
|
-
|
|
478
|
+
|
|
370
479
|
out.push(divider);
|
|
371
480
|
out.push("PROJECT STRUCTURE");
|
|
372
481
|
out.push(divider);
|
|
373
|
-
out.push(`Root: ${
|
|
482
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
374
483
|
out.push(`${rootName}/`);
|
|
375
|
-
treeLines.forEach(l => out.push(l));
|
|
484
|
+
treeLines.forEach((l) => out.push(l));
|
|
376
485
|
out.push("");
|
|
377
486
|
out.push(`Total files: ${filePaths.length}`);
|
|
378
487
|
out.push("");
|
|
379
|
-
|
|
488
|
+
|
|
380
489
|
out.push(divider);
|
|
381
490
|
out.push("FILE CONTENTS");
|
|
382
491
|
out.push(divider);
|
|
383
|
-
|
|
492
|
+
|
|
384
493
|
filePaths.forEach(({ abs, rel }) => {
|
|
385
494
|
const content = readContent(abs, forceFlag, effectiveMaxSize);
|
|
386
|
-
const fileContent = [
|
|
387
|
-
|
|
388
|
-
subDivider,
|
|
389
|
-
`FILE: ${rel}`,
|
|
390
|
-
subDivider,
|
|
391
|
-
content
|
|
392
|
-
];
|
|
393
|
-
|
|
495
|
+
const fileContent = ["", subDivider, `FILE: ${rel}`, subDivider, content];
|
|
496
|
+
|
|
394
497
|
const contentSize = fileContent.join("\n").length;
|
|
395
|
-
|
|
498
|
+
|
|
396
499
|
// Check if adding this file would exceed the split size
|
|
397
500
|
if (currentSize + contentSize > splitSize && currentFiles.length > 0) {
|
|
398
501
|
// Finish current part
|
|
@@ -400,178 +503,203 @@ function splitBySize(treeLines, filePaths, rootName, splitSize, effectiveMaxSize
|
|
|
400
503
|
out.push(divider);
|
|
401
504
|
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
402
505
|
out.push(divider);
|
|
403
|
-
|
|
506
|
+
|
|
404
507
|
// Write current part
|
|
405
508
|
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
406
509
|
const filePath = path.join(process.cwd(), fileName);
|
|
407
510
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
408
511
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
409
|
-
|
|
512
|
+
|
|
410
513
|
results.push({
|
|
411
514
|
file: filePath,
|
|
412
515
|
size: sizeKB,
|
|
413
516
|
files: currentFiles.length,
|
|
414
|
-
part: currentPart
|
|
517
|
+
part: currentPart,
|
|
415
518
|
});
|
|
416
|
-
|
|
519
|
+
|
|
417
520
|
// Start new part
|
|
418
521
|
currentPart++;
|
|
419
522
|
currentSize = 0;
|
|
420
523
|
currentFiles = [];
|
|
421
|
-
|
|
524
|
+
|
|
422
525
|
out = [];
|
|
423
526
|
out.push(divider);
|
|
424
527
|
out.push(`START OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
425
528
|
out.push(divider);
|
|
426
529
|
out.push("");
|
|
427
|
-
|
|
530
|
+
|
|
428
531
|
out.push(divider);
|
|
429
532
|
out.push("PROJECT STRUCTURE");
|
|
430
533
|
out.push(divider);
|
|
431
|
-
out.push(`Root: ${
|
|
534
|
+
out.push(`Root: ${process.cwd()}\n`);
|
|
432
535
|
out.push(`${rootName}/`);
|
|
433
|
-
treeLines.forEach(l => out.push(l));
|
|
536
|
+
treeLines.forEach((l) => out.push(l));
|
|
434
537
|
out.push("");
|
|
435
538
|
out.push(`Total files: ${filePaths.length}`);
|
|
436
539
|
out.push("");
|
|
437
|
-
|
|
540
|
+
|
|
438
541
|
out.push(divider);
|
|
439
542
|
out.push("FILE CONTENTS");
|
|
440
543
|
out.push(divider);
|
|
441
544
|
}
|
|
442
|
-
|
|
545
|
+
|
|
443
546
|
// Add file to current part
|
|
444
547
|
out.push(...fileContent);
|
|
445
548
|
currentSize += contentSize;
|
|
446
549
|
currentFiles.push(rel);
|
|
447
550
|
});
|
|
448
|
-
|
|
551
|
+
|
|
449
552
|
// Write final part
|
|
450
553
|
out.push("");
|
|
451
554
|
out.push(divider);
|
|
452
555
|
out.push(`END OF FOLDER: ${rootName} (Part ${currentPart})`);
|
|
453
556
|
out.push(divider);
|
|
454
|
-
|
|
557
|
+
|
|
455
558
|
const fileName = `${rootName}-part-${currentPart}.txt`;
|
|
456
559
|
const filePath = path.join(process.cwd(), fileName);
|
|
457
560
|
fs.writeFileSync(filePath, out.join("\n"), "utf8");
|
|
458
561
|
const sizeKB = (fs.statSync(filePath).size / 1024).toFixed(1);
|
|
459
|
-
|
|
562
|
+
|
|
460
563
|
results.push({
|
|
461
564
|
file: filePath,
|
|
462
565
|
size: sizeKB,
|
|
463
566
|
files: currentFiles.length,
|
|
464
|
-
part: currentPart
|
|
567
|
+
part: currentPart,
|
|
465
568
|
});
|
|
466
|
-
|
|
569
|
+
|
|
467
570
|
return results;
|
|
468
571
|
}
|
|
469
572
|
|
|
470
573
|
// ── configuration ────────────────────────────────────────────────────────────────
|
|
471
574
|
|
|
472
575
|
function createInteractiveConfig() {
|
|
473
|
-
const readline = require(
|
|
474
|
-
const fs = require(
|
|
475
|
-
const path = require(
|
|
476
|
-
const os = require(
|
|
477
|
-
|
|
576
|
+
const readline = require("readline");
|
|
577
|
+
const fs = require("fs");
|
|
578
|
+
const path = require("path");
|
|
579
|
+
const os = require("os");
|
|
580
|
+
|
|
478
581
|
const rl = readline.createInterface({
|
|
479
582
|
input: process.stdin,
|
|
480
|
-
output: process.stdout
|
|
583
|
+
output: process.stdout,
|
|
481
584
|
});
|
|
482
585
|
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
586
|
+
console.log("\n🔧 make-folder-txt Configuration Setup");
|
|
587
|
+
console.log("=====================================\n");
|
|
485
588
|
|
|
486
589
|
return new Promise((resolve) => {
|
|
487
590
|
const config = {
|
|
488
|
-
maxFileSize:
|
|
489
|
-
splitMethod:
|
|
490
|
-
splitSize:
|
|
491
|
-
copyToClipboard: false
|
|
591
|
+
maxFileSize: "500KB",
|
|
592
|
+
splitMethod: "none",
|
|
593
|
+
splitSize: "5MB",
|
|
594
|
+
copyToClipboard: false,
|
|
492
595
|
};
|
|
493
596
|
|
|
494
597
|
let currentStep = 0;
|
|
495
598
|
const questions = [
|
|
496
599
|
{
|
|
497
|
-
key:
|
|
498
|
-
question:
|
|
499
|
-
default:
|
|
600
|
+
key: "maxFileSize",
|
|
601
|
+
question: "Maximum file size to include (e.g., 500KB, 2MB, 1GB): ",
|
|
602
|
+
default: "500KB",
|
|
500
603
|
validate: (value) => {
|
|
501
604
|
if (!value.trim()) return true;
|
|
502
|
-
const validUnits = [
|
|
605
|
+
const validUnits = ["B", "KB", "MB", "GB", "TB"];
|
|
503
606
|
const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
|
|
504
|
-
if (!match)
|
|
505
|
-
|
|
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(", ")}`;
|
|
506
611
|
return true;
|
|
507
|
-
}
|
|
612
|
+
},
|
|
508
613
|
},
|
|
509
614
|
{
|
|
510
|
-
key:
|
|
511
|
-
question:
|
|
512
|
-
default:
|
|
615
|
+
key: "splitMethod",
|
|
616
|
+
question: "Split output method (none, folder, file, size): ",
|
|
617
|
+
default: "none",
|
|
513
618
|
validate: (value) => {
|
|
514
|
-
const validMethods = [
|
|
515
|
-
if (!validMethods.includes(value.toLowerCase()))
|
|
619
|
+
const validMethods = ["none", "folder", "file", "size"];
|
|
620
|
+
if (!validMethods.includes(value.toLowerCase()))
|
|
621
|
+
return `Please choose: ${validMethods.join(", ")}`;
|
|
516
622
|
return true;
|
|
517
|
-
}
|
|
623
|
+
},
|
|
518
624
|
},
|
|
519
625
|
{
|
|
520
|
-
key:
|
|
521
|
-
question:
|
|
522
|
-
default:
|
|
523
|
-
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",
|
|
524
630
|
validate: (value) => {
|
|
525
631
|
if (!value.trim()) return true;
|
|
526
|
-
const validUnits = [
|
|
632
|
+
const validUnits = ["B", "KB", "MB", "GB", "TB"];
|
|
527
633
|
const match = value.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
|
|
528
|
-
if (!match) return
|
|
529
|
-
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(", ")}`;
|
|
530
637
|
return true;
|
|
531
|
-
}
|
|
638
|
+
},
|
|
532
639
|
},
|
|
533
640
|
{
|
|
534
|
-
key:
|
|
535
|
-
question:
|
|
536
|
-
default:
|
|
641
|
+
key: "copyToClipboard",
|
|
642
|
+
question: "Copy to clipboard automatically? (y/n): ",
|
|
643
|
+
default: "n",
|
|
537
644
|
validate: (value) => {
|
|
538
645
|
const answer = value.toLowerCase();
|
|
539
|
-
if (![
|
|
646
|
+
if (!["y", "n", "yes", "no"].includes(answer))
|
|
647
|
+
return "Please enter y/n or yes/no";
|
|
540
648
|
return true;
|
|
541
649
|
},
|
|
542
|
-
transform: (value) => [
|
|
650
|
+
transform: (value) => ["y", "yes"].includes(value.toLowerCase()),
|
|
543
651
|
},
|
|
544
652
|
{
|
|
545
|
-
key:
|
|
546
|
-
question:
|
|
547
|
-
default:
|
|
653
|
+
key: "addToTxtIgnore",
|
|
654
|
+
question: "Add ignore patterns to .txtignore file? (y/n): ",
|
|
655
|
+
default: "n",
|
|
548
656
|
validate: (value) => {
|
|
549
657
|
const answer = value.toLowerCase();
|
|
550
|
-
if (![
|
|
658
|
+
if (!["y", "n", "yes", "no"].includes(answer))
|
|
659
|
+
return "Please enter y/n or yes/no";
|
|
551
660
|
return true;
|
|
552
661
|
},
|
|
553
|
-
transform: (value) => [
|
|
662
|
+
transform: (value) => ["y", "yes"].includes(value.toLowerCase()),
|
|
554
663
|
},
|
|
555
664
|
{
|
|
556
|
-
key:
|
|
557
|
-
question:
|
|
558
|
-
default:
|
|
665
|
+
key: "ignoreFolders",
|
|
666
|
+
question: "Ignore folders (comma-separated, or press Enter to skip): ",
|
|
667
|
+
default: "",
|
|
559
668
|
ask: () => config.addToTxtIgnore,
|
|
560
669
|
transform: (value) => {
|
|
561
|
-
if (!value || value.trim() ===
|
|
562
|
-
return value
|
|
563
|
-
|
|
670
|
+
if (!value || value.trim() === "") return [];
|
|
671
|
+
return value
|
|
672
|
+
.split(",")
|
|
673
|
+
.map((f) => f.trim())
|
|
674
|
+
.filter((f) => f);
|
|
675
|
+
},
|
|
564
676
|
},
|
|
565
677
|
{
|
|
566
|
-
key:
|
|
567
|
-
question:
|
|
568
|
-
default:
|
|
678
|
+
key: "ignoreFiles",
|
|
679
|
+
question: "Ignore files (comma-separated, or press Enter to skip): ",
|
|
680
|
+
default: "",
|
|
569
681
|
ask: () => config.addToTxtIgnore,
|
|
570
682
|
transform: (value) => {
|
|
571
|
-
if (!value || value.trim() ===
|
|
572
|
-
return value
|
|
573
|
-
|
|
574
|
-
|
|
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
|
+
},
|
|
575
703
|
];
|
|
576
704
|
|
|
577
705
|
function askQuestion() {
|
|
@@ -582,7 +710,7 @@ function createInteractiveConfig() {
|
|
|
582
710
|
}
|
|
583
711
|
|
|
584
712
|
const q = questions[currentStep];
|
|
585
|
-
|
|
713
|
+
|
|
586
714
|
// Skip if conditional ask returns false
|
|
587
715
|
if (q.ask && !q.ask()) {
|
|
588
716
|
currentStep++;
|
|
@@ -590,36 +718,40 @@ function createInteractiveConfig() {
|
|
|
590
718
|
return;
|
|
591
719
|
}
|
|
592
720
|
|
|
593
|
-
const defaultValue =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
+
}
|
|
604
736
|
}
|
|
605
|
-
}
|
|
606
737
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
738
|
+
// Transform value if needed
|
|
739
|
+
if (q.transform) {
|
|
740
|
+
config[q.key] = q.transform(value);
|
|
741
|
+
} else {
|
|
742
|
+
config[q.key] = value;
|
|
743
|
+
}
|
|
613
744
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
745
|
+
currentStep++;
|
|
746
|
+
askQuestion();
|
|
747
|
+
},
|
|
748
|
+
);
|
|
617
749
|
}
|
|
618
750
|
|
|
619
751
|
function saveConfig() {
|
|
620
752
|
try {
|
|
621
753
|
// Create .txtconfig file with proper formatting
|
|
622
|
-
const configPath = path.join(process.cwd(),
|
|
754
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
623
755
|
const configContent = `{
|
|
624
756
|
"maxFileSize": "${config.maxFileSize}",
|
|
625
757
|
"splitMethod": "${config.splitMethod}",
|
|
@@ -630,39 +762,81 @@ function createInteractiveConfig() {
|
|
|
630
762
|
fs.writeFileSync(configPath, configContent);
|
|
631
763
|
|
|
632
764
|
// Update .txtignore if user wants to add ignore patterns
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
+
|
|
637
772
|
// Read existing content if file exists
|
|
638
773
|
if (fs.existsSync(ignorePath)) {
|
|
639
|
-
ignoreContent = fs.readFileSync(ignorePath,
|
|
640
|
-
if (!ignoreContent.endsWith(
|
|
774
|
+
ignoreContent = fs.readFileSync(ignorePath, "utf8");
|
|
775
|
+
if (!ignoreContent.endsWith("\n")) ignoreContent += "\n";
|
|
641
776
|
}
|
|
642
777
|
|
|
643
778
|
// Add new ignore patterns
|
|
644
779
|
const newPatterns = [
|
|
645
|
-
...config.ignoreFolders.map(f => f.endsWith(
|
|
646
|
-
...config.ignoreFiles
|
|
780
|
+
...config.ignoreFolders.map((f) => (f.endsWith("/") ? f : `${f}/`)),
|
|
781
|
+
...config.ignoreFiles,
|
|
647
782
|
];
|
|
648
783
|
|
|
649
784
|
if (newPatterns.length > 0) {
|
|
650
|
-
ignoreContent +=
|
|
651
|
-
ignoreContent += newPatterns.join(
|
|
785
|
+
ignoreContent += "\n# Added by make-folder-txt config\n";
|
|
786
|
+
ignoreContent += newPatterns.join("\n") + "\n";
|
|
652
787
|
fs.writeFileSync(ignorePath, ignoreContent);
|
|
653
788
|
}
|
|
654
789
|
}
|
|
655
790
|
|
|
656
|
-
|
|
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!");
|
|
657
826
|
console.log(`📄 Config file: ${configPath}`);
|
|
658
|
-
if (
|
|
827
|
+
if (
|
|
828
|
+
config.addToTxtIgnore &&
|
|
829
|
+
(config.ignoreFolders.length > 0 || config.ignoreFiles.length > 0)
|
|
830
|
+
) {
|
|
659
831
|
console.log(`📝 Ignore patterns added to .txtignore`);
|
|
660
832
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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");
|
|
664
838
|
} catch (err) {
|
|
665
|
-
console.error(
|
|
839
|
+
console.error("❌ Error saving configuration:", err.message);
|
|
666
840
|
} finally {
|
|
667
841
|
rl.close();
|
|
668
842
|
resolve();
|
|
@@ -674,20 +848,20 @@ function createInteractiveConfig() {
|
|
|
674
848
|
}
|
|
675
849
|
|
|
676
850
|
function loadConfig() {
|
|
677
|
-
const fs = require(
|
|
678
|
-
const path = require(
|
|
679
|
-
|
|
851
|
+
const fs = require("fs");
|
|
852
|
+
const path = require("path");
|
|
853
|
+
|
|
680
854
|
try {
|
|
681
|
-
const configPath = path.join(process.cwd(),
|
|
855
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
682
856
|
if (!fs.existsSync(configPath)) {
|
|
683
|
-
console.error(
|
|
857
|
+
console.error("❌ .txtconfig file not found. Run --make-config first.");
|
|
684
858
|
process.exit(1);
|
|
685
859
|
}
|
|
686
|
-
|
|
687
|
-
const configContent = fs.readFileSync(configPath,
|
|
860
|
+
|
|
861
|
+
const configContent = fs.readFileSync(configPath, "utf8");
|
|
688
862
|
return JSON.parse(configContent);
|
|
689
863
|
} catch (err) {
|
|
690
|
-
console.error(
|
|
864
|
+
console.error("❌ Error loading configuration:", err.message);
|
|
691
865
|
process.exit(1);
|
|
692
866
|
}
|
|
693
867
|
}
|
|
@@ -710,55 +884,62 @@ async function main() {
|
|
|
710
884
|
|
|
711
885
|
if (args.includes("--delete-config")) {
|
|
712
886
|
try {
|
|
713
|
-
const fs = require(
|
|
714
|
-
const path = require(
|
|
715
|
-
const configPath = path.join(process.cwd(),
|
|
716
|
-
|
|
887
|
+
const fs = require("fs");
|
|
888
|
+
const path = require("path");
|
|
889
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
890
|
+
|
|
717
891
|
if (fs.existsSync(configPath)) {
|
|
718
892
|
fs.unlinkSync(configPath);
|
|
719
|
-
console.log(
|
|
720
|
-
console.log(
|
|
893
|
+
console.log("✅ Configuration file deleted successfully!");
|
|
894
|
+
console.log("🔄 Tool will now use default settings");
|
|
721
895
|
} else {
|
|
722
|
-
console.log(
|
|
896
|
+
console.log("ℹ️ No configuration file found - already using defaults");
|
|
723
897
|
}
|
|
724
898
|
} catch (err) {
|
|
725
|
-
console.error(
|
|
899
|
+
console.error("❌ Error deleting configuration:", err.message);
|
|
726
900
|
}
|
|
727
901
|
process.exit(0);
|
|
728
902
|
}
|
|
729
903
|
|
|
730
904
|
// Auto-use config if it exists and no other flags are provided
|
|
731
|
-
const configPath = path.join(process.cwd(),
|
|
905
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
732
906
|
const hasConfig = fs.existsSync(configPath);
|
|
733
|
-
const hasOtherFlags =
|
|
734
|
-
|
|
907
|
+
const hasOtherFlags =
|
|
908
|
+
args.length > 0 &&
|
|
909
|
+
!args.includes("--help") &&
|
|
910
|
+
!args.includes("-h") &&
|
|
911
|
+
!args.includes("--version") &&
|
|
912
|
+
!args.includes("-v");
|
|
913
|
+
|
|
735
914
|
if (hasConfig && !hasOtherFlags) {
|
|
736
915
|
const config = loadConfig();
|
|
737
|
-
|
|
916
|
+
|
|
738
917
|
// Apply config to command line arguments
|
|
739
918
|
const newArgs = [];
|
|
740
|
-
|
|
919
|
+
|
|
741
920
|
// Add max file size if not default
|
|
742
|
-
if (config.maxFileSize !==
|
|
743
|
-
newArgs.push(
|
|
921
|
+
if (config.maxFileSize !== "500KB") {
|
|
922
|
+
newArgs.push("--skip-large", config.maxFileSize);
|
|
744
923
|
}
|
|
745
|
-
|
|
924
|
+
|
|
746
925
|
// Add split method if not none
|
|
747
|
-
if (config.splitMethod !==
|
|
748
|
-
newArgs.push(
|
|
749
|
-
if (config.splitMethod ===
|
|
750
|
-
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);
|
|
751
930
|
}
|
|
752
931
|
}
|
|
753
|
-
|
|
932
|
+
|
|
754
933
|
// Add copy to clipboard if true
|
|
755
934
|
if (config.copyToClipboard) {
|
|
756
|
-
newArgs.push(
|
|
935
|
+
newArgs.push("--copy");
|
|
757
936
|
}
|
|
758
|
-
|
|
937
|
+
|
|
759
938
|
// Replace args with config-based args
|
|
760
939
|
args.splice(0, args.length, ...newArgs);
|
|
761
|
-
console.log(
|
|
940
|
+
console.log(
|
|
941
|
+
"🔧 Using saved configuration (use --delete-config to reset to defaults)",
|
|
942
|
+
);
|
|
762
943
|
}
|
|
763
944
|
|
|
764
945
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -780,7 +961,7 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
780
961
|
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
781
962
|
--copy Copy output to clipboard
|
|
782
963
|
--force Include everything (overrides all ignore patterns)
|
|
783
|
-
--make-config Create interactive configuration
|
|
964
|
+
--make-config Create interactive configuration (with optional .gitignore setup)
|
|
784
965
|
--delete-config Delete configuration and reset to defaults
|
|
785
966
|
--help, -h Show this help message
|
|
786
967
|
--version, -v Show version information
|
|
@@ -817,411 +998,439 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
817
998
|
coverage/
|
|
818
999
|
LICENSE
|
|
819
1000
|
`);
|
|
820
|
-
|
|
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;
|
|
821
1023
|
}
|
|
822
1024
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
let outputArg = null;
|
|
828
|
-
let copyToClipboardFlag = false;
|
|
829
|
-
let forceFlag = false;
|
|
830
|
-
let maxFileSize = 500 * 1024; // Default 500KB
|
|
831
|
-
let noSkipFlag = false;
|
|
832
|
-
let splitMethod = null; // 'folder', 'file', 'size'
|
|
833
|
-
let splitSize = null; // size in bytes
|
|
834
|
-
let rootOnlyInclude = false; // For /include flag
|
|
835
|
-
|
|
836
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
837
|
-
const arg = args[i];
|
|
838
|
-
|
|
839
|
-
if (arg === "--copy") {
|
|
840
|
-
copyToClipboardFlag = true;
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
1025
|
+
if (arg === "--force") {
|
|
1026
|
+
forceFlag = true;
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
843
1029
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1030
|
+
if (arg === "--no-skip") {
|
|
1031
|
+
noSkipFlag = true;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
848
1034
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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);
|
|
852
1041
|
}
|
|
1042
|
+
maxFileSize = parseFileSize(args[i + 1]);
|
|
1043
|
+
i += 1;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
853
1046
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
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);
|
|
862
1054
|
}
|
|
1055
|
+
maxFileSize = parseFileSize(value);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
863
1058
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
maxFileSize = parseFileSize(value);
|
|
871
|
-
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);
|
|
872
1065
|
}
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const method = args[i + 1].toLowerCase();
|
|
880
|
-
if (!['folder', 'file', 'size'].includes(method)) {
|
|
881
|
-
console.error("Error: --split-method must be one of: folder, file, size");
|
|
882
|
-
process.exit(1);
|
|
883
|
-
}
|
|
884
|
-
splitMethod = method;
|
|
885
|
-
i += 1;
|
|
886
|
-
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);
|
|
887
1072
|
}
|
|
1073
|
+
splitMethod = method;
|
|
1074
|
+
i += 1;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
888
1077
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
if (!['folder', 'file', 'size'].includes(method)) {
|
|
897
|
-
console.error("Error: --split-method must be one of: folder, file, size");
|
|
898
|
-
process.exit(1);
|
|
899
|
-
}
|
|
900
|
-
splitMethod = method;
|
|
901
|
-
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);
|
|
902
1085
|
}
|
|
903
|
-
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
splitSize = parseFileSize(args[i + 1]);
|
|
910
|
-
i += 1;
|
|
911
|
-
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);
|
|
912
1092
|
}
|
|
1093
|
+
splitMethod = method;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
913
1096
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
splitSize = parseFileSize(value);
|
|
921
|
-
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);
|
|
922
1103
|
}
|
|
1104
|
+
splitSize = parseFileSize(args[i + 1]);
|
|
1105
|
+
i += 1;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
923
1108
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const cleanFolderName = folderName.slice(1); // Remove leading /
|
|
932
|
-
onlyFolders.add(cleanFolderName);
|
|
933
|
-
rootOnlyInclude = true;
|
|
934
|
-
i += 1;
|
|
935
|
-
consumed += 1;
|
|
936
|
-
} else {
|
|
937
|
-
// Normal folder path (could be nested like folder/subfolder)
|
|
938
|
-
onlyFolders.add(folderName);
|
|
939
|
-
i += 1;
|
|
940
|
-
consumed += 1;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (consumed === 0) {
|
|
944
|
-
console.error("Error: --ignore-folder requires at least one folder name.");
|
|
945
|
-
process.exit(1);
|
|
946
|
-
}
|
|
947
|
-
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);
|
|
948
1116
|
}
|
|
1117
|
+
splitSize = parseFileSize(value);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
949
1120
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
// Normal folder path (could be nested like folder/subfolder)
|
|
966
|
-
onlyFolders.add(value);
|
|
967
|
-
}
|
|
968
|
-
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);
|
|
969
1136
|
}
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
970
1139
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
if (consumed === 0) {
|
|
979
|
-
console.error("Error: --ignore-file requires at least one file name.");
|
|
980
|
-
process.exit(1);
|
|
981
|
-
}
|
|
982
|
-
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);
|
|
983
1147
|
}
|
|
1148
|
+
let folderName = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1149
|
+
ignoreDirs.add(folderName);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
984
1152
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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;
|
|
1159
|
+
}
|
|
1160
|
+
if (consumed === 0) {
|
|
1161
|
+
console.error("Error: --ignore-file requires at least one file name.");
|
|
1162
|
+
process.exit(1);
|
|
995
1163
|
}
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
996
1166
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
|
|
1005
|
-
onlyFolders.add(folderName);
|
|
1006
|
-
i += 1;
|
|
1007
|
-
consumed += 1;
|
|
1008
|
-
}
|
|
1009
|
-
if (consumed === 0) {
|
|
1010
|
-
console.error("Error: --only-folder requires at least one folder name.");
|
|
1011
|
-
process.exit(1);
|
|
1012
|
-
}
|
|
1013
|
-
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);
|
|
1014
1174
|
}
|
|
1175
|
+
ignoreFiles.add(value);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1015
1178
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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)
|
|
1185
|
+
if (folderName.startsWith("/")) {
|
|
1186
|
+
rootOnlyInclude = true;
|
|
1023
1187
|
}
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1026
|
-
folderName = folderName.replace(/\\/g, '/'); // Convert backslashes to forward slashes
|
|
1027
|
-
folderName = folderName.replace(/^\.?\//, ''); // Remove leading ./ or /
|
|
1028
|
-
folderName = folderName.replace(/\/+$/, ''); // Remove trailing slashes
|
|
1188
|
+
folderName = folderName.replace(/^\.?\//, ""); // Remove leading ./ or /
|
|
1189
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1029
1190
|
onlyFolders.add(folderName);
|
|
1030
|
-
|
|
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);
|
|
1031
1199
|
}
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1032
1202
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const cleanFileName = fileName.slice(1); // Remove leading /
|
|
1041
|
-
onlyFiles.add(cleanFileName);
|
|
1042
|
-
rootOnlyInclude = true;
|
|
1043
|
-
i += 1;
|
|
1044
|
-
consumed += 1;
|
|
1045
|
-
} else {
|
|
1046
|
-
// Normal file path (could be nested like folder/file.ext)
|
|
1047
|
-
onlyFiles.add(fileName);
|
|
1048
|
-
i += 1;
|
|
1049
|
-
consumed += 1;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
if (consumed === 0) {
|
|
1053
|
-
console.error("Error: --only-file requires at least one file name.");
|
|
1054
|
-
process.exit(1);
|
|
1055
|
-
}
|
|
1056
|
-
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);
|
|
1057
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];
|
|
1058
1227
|
|
|
1059
|
-
if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
|
|
1060
|
-
const value = arg.startsWith("--only-file=")
|
|
1061
|
-
? arg.slice("--only-file=".length)
|
|
1062
|
-
: arg.slice("-ofi=".length);
|
|
1063
|
-
if (!value) {
|
|
1064
|
-
console.error("Error: --only-file requires a file name.");
|
|
1065
|
-
process.exit(1);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1228
|
// Check for /file syntax (root-only include)
|
|
1069
|
-
if (
|
|
1070
|
-
const cleanFileName =
|
|
1229
|
+
if (fileName.startsWith("/")) {
|
|
1230
|
+
const cleanFileName = fileName.slice(1); // Remove leading /
|
|
1071
1231
|
onlyFiles.add(cleanFileName);
|
|
1072
1232
|
rootOnlyInclude = true;
|
|
1233
|
+
i += 1;
|
|
1234
|
+
consumed += 1;
|
|
1073
1235
|
} else {
|
|
1074
1236
|
// Normal file path (could be nested like folder/file.ext)
|
|
1075
|
-
onlyFiles.add(
|
|
1237
|
+
onlyFiles.add(fileName);
|
|
1238
|
+
i += 1;
|
|
1239
|
+
consumed += 1;
|
|
1076
1240
|
}
|
|
1077
|
-
continue;
|
|
1078
1241
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1242
|
+
if (consumed === 0) {
|
|
1243
|
+
console.error("Error: --only-file requires at least one file name.");
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
}
|
|
1246
|
+
continue;
|
|
1084
1247
|
}
|
|
1085
1248
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
+
}
|
|
1091
1257
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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;
|
|
1095
1268
|
}
|
|
1096
1269
|
|
|
1097
|
-
//
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// ── build tree & collect file paths ───────────────────────────────────────────────
|
|
1104
|
-
|
|
1105
|
-
const { lines: treeLines, filePaths } = collectFiles(
|
|
1106
|
-
folderPath,
|
|
1107
|
-
folderPath,
|
|
1108
|
-
ignoreDirs,
|
|
1109
|
-
ignoreFiles,
|
|
1110
|
-
onlyFolders,
|
|
1111
|
-
onlyFiles,
|
|
1112
|
-
{
|
|
1113
|
-
rootName,
|
|
1114
|
-
txtIgnore,
|
|
1115
|
-
force: forceFlag,
|
|
1116
|
-
hasOnlyFilters: onlyFolders.size > 0 || onlyFiles.size > 0,
|
|
1117
|
-
rootOnlyInclude
|
|
1118
|
-
}
|
|
1119
|
-
);
|
|
1270
|
+
// Unknown argument
|
|
1271
|
+
console.error(`Error: Unknown option "${arg}"`);
|
|
1272
|
+
console.error("Use --help for available options.");
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1120
1275
|
|
|
1121
|
-
|
|
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
|
+
}
|
|
1122
1283
|
|
|
1123
|
-
|
|
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
|
+
}
|
|
1124
1290
|
|
|
1125
|
-
|
|
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
|
+
}
|
|
1126
1348
|
|
|
1127
|
-
|
|
1349
|
+
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
1350
|
+
console.log("");
|
|
1128
1351
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (splitMethod ===
|
|
1135
|
-
|
|
1136
|
-
} else if (splitMethod === 'file') {
|
|
1137
|
-
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
1138
|
-
} else if (splitMethod === 'size') {
|
|
1139
|
-
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}`);
|
|
1140
1359
|
}
|
|
1141
|
-
|
|
1142
|
-
console.log(
|
|
1143
|
-
console.log(
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
console.log(`📦 Part ${result.part}`);
|
|
1152
|
-
}
|
|
1153
|
-
console.log(`📄 Output : ${result.file}`);
|
|
1154
|
-
console.log(`📊 Size : ${result.size} KB`);
|
|
1155
|
-
console.log(`🗂️ Files : ${result.files}`);
|
|
1156
|
-
console.log('');
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
if (copyToClipboardFlag) {
|
|
1160
|
-
console.log('⚠️ --copy flag is not compatible with splitting - clipboard copy skipped');
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
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
|
+
);
|
|
1164
1370
|
}
|
|
1165
1371
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const divider = "=".repeat(80);
|
|
1169
|
-
const subDivider = "-".repeat(80);
|
|
1372
|
+
process.exit(0);
|
|
1373
|
+
}
|
|
1170
1374
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1375
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
1376
|
+
const out = [];
|
|
1377
|
+
const divider = "=".repeat(80);
|
|
1378
|
+
const subDivider = "-".repeat(80);
|
|
1175
1379
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1380
|
+
out.push(divider);
|
|
1381
|
+
out.push(`START OF FOLDER: ${rootName}`);
|
|
1382
|
+
out.push(divider);
|
|
1383
|
+
out.push("");
|
|
1180
1384
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
out.push("");
|
|
1385
|
+
out.push(divider);
|
|
1386
|
+
out.push("PROJECT STRUCTURE");
|
|
1387
|
+
out.push(divider);
|
|
1388
|
+
out.push(`Root: ${folderPath}\n`);
|
|
1186
1389
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1390
|
+
out.push(`${rootName}/`);
|
|
1391
|
+
treeLines.forEach((l) => out.push(l));
|
|
1392
|
+
out.push("");
|
|
1393
|
+
out.push(`Total files: ${filePaths.length}`);
|
|
1394
|
+
out.push("");
|
|
1190
1395
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
out.push(`FILE: ${rel}`);
|
|
1195
|
-
out.push(subDivider);
|
|
1196
|
-
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
1197
|
-
});
|
|
1396
|
+
out.push(divider);
|
|
1397
|
+
out.push("FILE CONTENTS");
|
|
1398
|
+
out.push(divider);
|
|
1198
1399
|
|
|
1400
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
1199
1401
|
out.push("");
|
|
1200
|
-
out.push(
|
|
1201
|
-
out.push(`
|
|
1202
|
-
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);
|
|
1203
1412
|
|
|
1204
|
-
|
|
1413
|
+
fs.writeFileSync(outputFile, out.join("\n"), "utf8");
|
|
1205
1414
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
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}`);
|
|
1211
1420
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1421
|
+
if (copyToClipboardFlag) {
|
|
1422
|
+
const content = fs.readFileSync(outputFile, "utf8");
|
|
1423
|
+
const success = copyToClipboard(content);
|
|
1424
|
+
if (success) {
|
|
1425
|
+
console.log(`📋 Copied to clipboard!`);
|
|
1218
1426
|
}
|
|
1427
|
+
}
|
|
1219
1428
|
|
|
1220
|
-
|
|
1429
|
+
console.log("");
|
|
1221
1430
|
}
|
|
1222
1431
|
|
|
1223
1432
|
// Run the main function
|
|
1224
|
-
main().catch(err => {
|
|
1225
|
-
console.error(
|
|
1433
|
+
main().catch((err) => {
|
|
1434
|
+
console.error("❌ Error:", err.message);
|
|
1226
1435
|
process.exit(1);
|
|
1227
1436
|
});
|