make-folder-txt 2.2.9 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/bin/make-folder-txt.js +961 -612
- package/package.json +1 -1
- package/make-folder-txt.txt +0 -1258
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();
|
|
@@ -680,28 +847,171 @@ function createInteractiveConfig() {
|
|
|
680
847
|
});
|
|
681
848
|
}
|
|
682
849
|
|
|
683
|
-
function loadConfig() {
|
|
684
|
-
const fs = require(
|
|
685
|
-
const path = require(
|
|
686
|
-
|
|
850
|
+
function loadConfig() {
|
|
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
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function sanitizeRootName(name) {
|
|
870
|
+
return name
|
|
871
|
+
.replace(/[<>:"/\\|?*\x00-\x1F]/g, "-")
|
|
872
|
+
.replace(/^\.+$/, "restored-folder")
|
|
873
|
+
.trim();
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function getReverseTargetPath(args) {
|
|
877
|
+
const reverseIndex = args.indexOf("--reverse");
|
|
878
|
+
const nextArg = args[reverseIndex + 1];
|
|
879
|
+
|
|
880
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
881
|
+
return path.resolve(process.cwd(), nextArg);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return path.join(process.cwd(), `${path.basename(process.cwd())}.txt`);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function parseReverseDump(content, txtPath) {
|
|
888
|
+
const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
889
|
+
const dividerLine = /^={80}$/;
|
|
890
|
+
const fileDividerLine = /^-{80}$/;
|
|
891
|
+
const startLine = lines.find((line) => line.startsWith("START OF FOLDER: "));
|
|
892
|
+
const rootNameFromHeader = startLine
|
|
893
|
+
? startLine
|
|
894
|
+
.slice("START OF FOLDER: ".length)
|
|
895
|
+
.replace(/\s+\(Part \d+\)$/, "")
|
|
896
|
+
.trim()
|
|
897
|
+
: "";
|
|
898
|
+
const rootName =
|
|
899
|
+
sanitizeRootName(rootNameFromHeader) ||
|
|
900
|
+
sanitizeRootName(path.basename(txtPath, path.extname(txtPath)));
|
|
901
|
+
const files = [];
|
|
902
|
+
|
|
903
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
904
|
+
if (
|
|
905
|
+
!lines[i].startsWith("FILE: ") ||
|
|
906
|
+
i === 0 ||
|
|
907
|
+
i + 1 >= lines.length ||
|
|
908
|
+
!fileDividerLine.test(lines[i - 1]) ||
|
|
909
|
+
!fileDividerLine.test(lines[i + 1])
|
|
910
|
+
) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const rel = lines[i]
|
|
915
|
+
.slice("FILE: ".length)
|
|
916
|
+
.replace(/\\/g, "/")
|
|
917
|
+
.replace(/^\/+/, "");
|
|
918
|
+
|
|
919
|
+
if (!rel || rel.includes("\0")) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let end = lines.length;
|
|
924
|
+
for (let j = i + 2; j < lines.length; j += 1) {
|
|
925
|
+
const startsNextFile =
|
|
926
|
+
fileDividerLine.test(lines[j]) &&
|
|
927
|
+
j + 2 < lines.length &&
|
|
928
|
+
lines[j + 1].startsWith("FILE: ") &&
|
|
929
|
+
fileDividerLine.test(lines[j + 2]);
|
|
930
|
+
const startsFooter =
|
|
931
|
+
dividerLine.test(lines[j]) &&
|
|
932
|
+
j + 1 < lines.length &&
|
|
933
|
+
(lines[j + 1].startsWith("END OF FOLDER: ") ||
|
|
934
|
+
lines[j + 1].startsWith("END OF FILE: "));
|
|
935
|
+
|
|
936
|
+
if (startsNextFile || startsFooter) {
|
|
937
|
+
end = j;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const contentLines = lines.slice(i + 2, end);
|
|
943
|
+
if (contentLines[contentLines.length - 1] === "") {
|
|
944
|
+
contentLines.pop();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
files.push({
|
|
948
|
+
rel,
|
|
949
|
+
content: contentLines.join("\n"),
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return { rootName, files };
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function reverseTxtToFolder(txtPath, options = {}) {
|
|
957
|
+
if (!fs.existsSync(txtPath)) {
|
|
958
|
+
console.error(`Error: Reverse input file not found: ${txtPath}`);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const content = fs.readFileSync(txtPath, "utf8");
|
|
963
|
+
const { rootName, files } = parseReverseDump(content, txtPath);
|
|
964
|
+
|
|
965
|
+
if (files.length === 0) {
|
|
966
|
+
console.error(
|
|
967
|
+
"Error: No file content blocks found. Is this a make-folder-txt output file?",
|
|
968
|
+
);
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const outputDir = path.join(process.cwd(), rootName);
|
|
973
|
+
const outputRoot = path.resolve(outputDir);
|
|
974
|
+
let written = 0;
|
|
975
|
+
let skipped = 0;
|
|
976
|
+
|
|
977
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
978
|
+
|
|
979
|
+
files.forEach(({ rel, content: fileContent }) => {
|
|
980
|
+
const destination = path.resolve(outputRoot, rel);
|
|
981
|
+
|
|
982
|
+
if (
|
|
983
|
+
destination !== outputRoot &&
|
|
984
|
+
!destination.startsWith(outputRoot + path.sep)
|
|
985
|
+
) {
|
|
986
|
+
skipped += 1;
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
if (fs.existsSync(destination) && !options.force) {
|
|
991
|
+
skipped += 1;
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
996
|
+
fs.writeFileSync(destination, fileContent, "utf8");
|
|
997
|
+
written += 1;
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
console.log(`✅ Done!`);
|
|
1001
|
+
console.log(`📁 Folder : ${outputRoot}`);
|
|
1002
|
+
console.log(`📄 Source : ${txtPath}`);
|
|
1003
|
+
console.log(`🗂️ Files : ${written}`);
|
|
1004
|
+
|
|
1005
|
+
if (skipped > 0) {
|
|
1006
|
+
console.log(
|
|
1007
|
+
`⚠️ Skipped: ${skipped} file${skipped === 1 ? "" : "s"} (use --force to overwrite existing files)`,
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// ── main ──────────────────────────────────────────────────────────────────────
|
|
1013
|
+
|
|
1014
|
+
async function main() {
|
|
705
1015
|
const args = process.argv.slice(2);
|
|
706
1016
|
|
|
707
1017
|
if (args.includes("-v") || args.includes("--version")) {
|
|
@@ -715,57 +1025,70 @@ async function main() {
|
|
|
715
1025
|
process.exit(0);
|
|
716
1026
|
}
|
|
717
1027
|
|
|
718
|
-
if (args.includes("--delete-config")) {
|
|
1028
|
+
if (args.includes("--delete-config")) {
|
|
719
1029
|
try {
|
|
720
|
-
const fs = require(
|
|
721
|
-
const path = require(
|
|
722
|
-
const configPath = path.join(process.cwd(),
|
|
723
|
-
|
|
1030
|
+
const fs = require("fs");
|
|
1031
|
+
const path = require("path");
|
|
1032
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
1033
|
+
|
|
724
1034
|
if (fs.existsSync(configPath)) {
|
|
725
1035
|
fs.unlinkSync(configPath);
|
|
726
|
-
console.log(
|
|
727
|
-
console.log(
|
|
1036
|
+
console.log("✅ Configuration file deleted successfully!");
|
|
1037
|
+
console.log("🔄 Tool will now use default settings");
|
|
728
1038
|
} else {
|
|
729
|
-
console.log(
|
|
1039
|
+
console.log("ℹ️ No configuration file found - already using defaults");
|
|
730
1040
|
}
|
|
731
1041
|
} catch (err) {
|
|
732
|
-
console.error(
|
|
1042
|
+
console.error("❌ Error deleting configuration:", err.message);
|
|
733
1043
|
}
|
|
734
|
-
process.exit(0);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1044
|
+
process.exit(0);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (args.includes("--reverse")) {
|
|
1048
|
+
const txtPath = getReverseTargetPath(args);
|
|
1049
|
+
reverseTxtToFolder(txtPath, { force: args.includes("--force") });
|
|
1050
|
+
process.exit(0);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Auto-use config if it exists and no other flags are provided
|
|
1054
|
+
const configPath = path.join(process.cwd(), ".txtconfig");
|
|
739
1055
|
const hasConfig = fs.existsSync(configPath);
|
|
740
|
-
const hasOtherFlags =
|
|
741
|
-
|
|
1056
|
+
const hasOtherFlags =
|
|
1057
|
+
args.length > 0 &&
|
|
1058
|
+
!args.includes("--help") &&
|
|
1059
|
+
!args.includes("-h") &&
|
|
1060
|
+
!args.includes("--version") &&
|
|
1061
|
+
!args.includes("-v");
|
|
1062
|
+
|
|
742
1063
|
if (hasConfig && !hasOtherFlags) {
|
|
743
1064
|
const config = loadConfig();
|
|
744
|
-
|
|
1065
|
+
|
|
745
1066
|
// Apply config to command line arguments
|
|
746
1067
|
const newArgs = [];
|
|
747
|
-
|
|
1068
|
+
|
|
748
1069
|
// Add max file size if not default
|
|
749
|
-
if (config.maxFileSize !==
|
|
750
|
-
newArgs.push(
|
|
1070
|
+
if (config.maxFileSize !== "500KB") {
|
|
1071
|
+
newArgs.push("--skip-large", config.maxFileSize);
|
|
751
1072
|
}
|
|
752
|
-
|
|
1073
|
+
|
|
753
1074
|
// Add split method if not none
|
|
754
|
-
if (config.splitMethod !==
|
|
755
|
-
newArgs.push(
|
|
756
|
-
if (config.splitMethod ===
|
|
757
|
-
newArgs.push(
|
|
1075
|
+
if (config.splitMethod !== "none") {
|
|
1076
|
+
newArgs.push("--split-method", config.splitMethod);
|
|
1077
|
+
if (config.splitMethod === "size") {
|
|
1078
|
+
newArgs.push("--split-size", config.splitSize);
|
|
758
1079
|
}
|
|
759
1080
|
}
|
|
760
|
-
|
|
1081
|
+
|
|
761
1082
|
// Add copy to clipboard if true
|
|
762
1083
|
if (config.copyToClipboard) {
|
|
763
|
-
newArgs.push(
|
|
1084
|
+
newArgs.push("--copy");
|
|
764
1085
|
}
|
|
765
|
-
|
|
1086
|
+
|
|
766
1087
|
// Replace args with config-based args
|
|
767
1088
|
args.splice(0, args.length, ...newArgs);
|
|
768
|
-
console.log(
|
|
1089
|
+
console.log(
|
|
1090
|
+
"🔧 Using saved configuration (use --delete-config to reset to defaults)",
|
|
1091
|
+
);
|
|
769
1092
|
}
|
|
770
1093
|
|
|
771
1094
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -784,11 +1107,12 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
784
1107
|
--skip-large <size> Skip files larger than specified size (default: 500KB)
|
|
785
1108
|
--no-skip Include all files regardless of size
|
|
786
1109
|
--split-method <method> Split output: folder, file, or size
|
|
787
|
-
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
788
|
-
--copy Copy output to clipboard
|
|
789
|
-
--force Include everything (overrides all ignore patterns)
|
|
790
|
-
--make-
|
|
791
|
-
--
|
|
1110
|
+
--split-size <size> Split output when size exceeds limit (requires --split-method size)
|
|
1111
|
+
--copy Copy output to clipboard
|
|
1112
|
+
--force Include everything (overrides all ignore patterns)
|
|
1113
|
+
--reverse [file.txt] Recreate a folder from a make-folder-txt output file
|
|
1114
|
+
--make-config Create interactive configuration (with optional .gitignore setup)
|
|
1115
|
+
--delete-config Delete configuration and reset to defaults
|
|
792
1116
|
--help, -h Show this help message
|
|
793
1117
|
--version, -v Show version information
|
|
794
1118
|
|
|
@@ -799,10 +1123,13 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
799
1123
|
make-folder-txt --skip-large 400KB
|
|
800
1124
|
make-folder-txt --skip-large 5GB
|
|
801
1125
|
make-folder-txt --no-skip
|
|
802
|
-
make-folder-txt --split-method folder
|
|
803
|
-
make-folder-txt --split-method file
|
|
804
|
-
make-folder-txt --split-method size --split-size 5MB
|
|
805
|
-
make-folder-txt --
|
|
1126
|
+
make-folder-txt --split-method folder
|
|
1127
|
+
make-folder-txt --split-method file
|
|
1128
|
+
make-folder-txt --split-method size --split-size 5MB
|
|
1129
|
+
make-folder-txt --reverse
|
|
1130
|
+
make-folder-txt --reverse my-project.txt
|
|
1131
|
+
make-folder-txt --reverse my-project.txt --force
|
|
1132
|
+
make-folder-txt --ignore-folder node_modules dist
|
|
806
1133
|
make-folder-txt -ifo node_modules dist
|
|
807
1134
|
make-folder-txt --ignore-file .env .env.local
|
|
808
1135
|
make-folder-txt -ifi .env .env.local
|
|
@@ -824,417 +1151,439 @@ Dump an entire project folder into a single readable .txt file.
|
|
|
824
1151
|
coverage/
|
|
825
1152
|
LICENSE
|
|
826
1153
|
`);
|
|
827
|
-
|
|
1154
|
+
process.exit(0);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const ignoreDirs = new Set(IGNORE_DIRS);
|
|
1158
|
+
const ignoreFiles = new Set(IGNORE_FILES);
|
|
1159
|
+
const onlyFolders = new Set();
|
|
1160
|
+
const onlyFiles = new Set();
|
|
1161
|
+
let outputArg = null;
|
|
1162
|
+
let copyToClipboardFlag = false;
|
|
1163
|
+
let forceFlag = false;
|
|
1164
|
+
let maxFileSize = 500 * 1024; // Default 500KB
|
|
1165
|
+
let noSkipFlag = false;
|
|
1166
|
+
let splitMethod = null; // 'folder', 'file', 'size'
|
|
1167
|
+
let splitSize = null; // size in bytes
|
|
1168
|
+
let rootOnlyInclude = false; // For /include flag
|
|
1169
|
+
|
|
1170
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1171
|
+
const arg = args[i];
|
|
1172
|
+
|
|
1173
|
+
if (arg === "--copy") {
|
|
1174
|
+
copyToClipboardFlag = true;
|
|
1175
|
+
continue;
|
|
828
1176
|
}
|
|
829
1177
|
|
|
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
|
-
}
|
|
1178
|
+
if (arg === "--force") {
|
|
1179
|
+
forceFlag = true;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
850
1182
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1183
|
+
if (arg === "--no-skip") {
|
|
1184
|
+
noSkipFlag = true;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
855
1187
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1188
|
+
if (arg === "--skip-large") {
|
|
1189
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1190
|
+
console.error(
|
|
1191
|
+
"Error: --skip-large requires a size value (e.g., 400KB, 5GB).",
|
|
1192
|
+
);
|
|
1193
|
+
process.exit(1);
|
|
859
1194
|
}
|
|
1195
|
+
maxFileSize = parseFileSize(args[i + 1]);
|
|
1196
|
+
i += 1;
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
860
1199
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
continue;
|
|
1200
|
+
if (arg.startsWith("--skip-large=")) {
|
|
1201
|
+
const value = arg.slice("--skip-large=".length);
|
|
1202
|
+
if (!value) {
|
|
1203
|
+
console.error(
|
|
1204
|
+
"Error: --skip-large requires a size value (e.g., 400KB, 5GB).",
|
|
1205
|
+
);
|
|
1206
|
+
process.exit(1);
|
|
869
1207
|
}
|
|
1208
|
+
maxFileSize = parseFileSize(value);
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
870
1211
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
maxFileSize = parseFileSize(value);
|
|
878
|
-
continue;
|
|
1212
|
+
if (arg === "--split-method") {
|
|
1213
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1214
|
+
console.error(
|
|
1215
|
+
"Error: --split-method requires a method (folder, file, or size).",
|
|
1216
|
+
);
|
|
1217
|
+
process.exit(1);
|
|
879
1218
|
}
|
|
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;
|
|
1219
|
+
const method = args[i + 1].toLowerCase();
|
|
1220
|
+
if (!["folder", "file", "size"].includes(method)) {
|
|
1221
|
+
console.error(
|
|
1222
|
+
"Error: --split-method must be one of: folder, file, size",
|
|
1223
|
+
);
|
|
1224
|
+
process.exit(1);
|
|
894
1225
|
}
|
|
1226
|
+
splitMethod = method;
|
|
1227
|
+
i += 1;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
895
1230
|
|
|
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;
|
|
1231
|
+
if (arg.startsWith("--split-method=")) {
|
|
1232
|
+
const value = arg.slice("--split-method=".length);
|
|
1233
|
+
if (!value) {
|
|
1234
|
+
console.error(
|
|
1235
|
+
"Error: --split-method requires a method (folder, file, or size).",
|
|
1236
|
+
);
|
|
1237
|
+
process.exit(1);
|
|
909
1238
|
}
|
|
910
|
-
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
splitSize = parseFileSize(args[i + 1]);
|
|
917
|
-
i += 1;
|
|
918
|
-
continue;
|
|
1239
|
+
const method = value.toLowerCase();
|
|
1240
|
+
if (!["folder", "file", "size"].includes(method)) {
|
|
1241
|
+
console.error(
|
|
1242
|
+
"Error: --split-method must be one of: folder, file, size",
|
|
1243
|
+
);
|
|
1244
|
+
process.exit(1);
|
|
919
1245
|
}
|
|
1246
|
+
splitMethod = method;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
920
1249
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
splitSize = parseFileSize(value);
|
|
928
|
-
continue;
|
|
1250
|
+
if (arg === "--split-size") {
|
|
1251
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
|
|
1252
|
+
console.error(
|
|
1253
|
+
"Error: --split-size requires a size value (e.g., 5MB, 10MB).",
|
|
1254
|
+
);
|
|
1255
|
+
process.exit(1);
|
|
929
1256
|
}
|
|
1257
|
+
splitSize = parseFileSize(args[i + 1]);
|
|
1258
|
+
i += 1;
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
930
1261
|
|
|
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;
|
|
1262
|
+
if (arg.startsWith("--split-size=")) {
|
|
1263
|
+
const value = arg.slice("--split-size=".length);
|
|
1264
|
+
if (!value) {
|
|
1265
|
+
console.error(
|
|
1266
|
+
"Error: --split-size requires a size value (e.g., 5MB, 10MB).",
|
|
1267
|
+
);
|
|
1268
|
+
process.exit(1);
|
|
955
1269
|
}
|
|
1270
|
+
splitSize = parseFileSize(value);
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
956
1273
|
|
|
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;
|
|
1274
|
+
if (arg === "--ignore-folder" || arg === "-ifo") {
|
|
1275
|
+
let consumed = 0;
|
|
1276
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1277
|
+
let folderName = args[i + 1];
|
|
1278
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1279
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1280
|
+
ignoreDirs.add(folderName);
|
|
1281
|
+
i += 1;
|
|
1282
|
+
consumed += 1;
|
|
1283
|
+
}
|
|
1284
|
+
if (consumed === 0) {
|
|
1285
|
+
console.error(
|
|
1286
|
+
"Error: --ignore-folder requires at least one folder name.",
|
|
1287
|
+
);
|
|
1288
|
+
process.exit(1);
|
|
976
1289
|
}
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
977
1292
|
|
|
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;
|
|
1293
|
+
if (arg.startsWith("--ignore-folder=") || arg.startsWith("-ifo=")) {
|
|
1294
|
+
const value = arg.startsWith("--ignore-folder=")
|
|
1295
|
+
? arg.slice("--ignore-folder=".length)
|
|
1296
|
+
: arg.slice("-ifo=".length);
|
|
1297
|
+
if (!value) {
|
|
1298
|
+
console.error("Error: --ignore-folder requires a folder name.");
|
|
1299
|
+
process.exit(1);
|
|
990
1300
|
}
|
|
1301
|
+
let folderName = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1302
|
+
ignoreDirs.add(folderName);
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
991
1305
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1306
|
+
if (arg === "--ignore-file" || arg === "-ifi") {
|
|
1307
|
+
let consumed = 0;
|
|
1308
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1309
|
+
ignoreFiles.add(args[i + 1]);
|
|
1310
|
+
i += 1;
|
|
1311
|
+
consumed += 1;
|
|
1312
|
+
}
|
|
1313
|
+
if (consumed === 0) {
|
|
1314
|
+
console.error("Error: --ignore-file requires at least one file name.");
|
|
1315
|
+
process.exit(1);
|
|
1002
1316
|
}
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1003
1319
|
|
|
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;
|
|
1320
|
+
if (arg.startsWith("--ignore-file=") || arg.startsWith("-ifi=")) {
|
|
1321
|
+
const value = arg.startsWith("--ignore-file=")
|
|
1322
|
+
? arg.slice("--ignore-file=".length)
|
|
1323
|
+
: arg.slice("-ifi=".length);
|
|
1324
|
+
if (!value) {
|
|
1325
|
+
console.error("Error: --ignore-file requires a file name.");
|
|
1326
|
+
process.exit(1);
|
|
1024
1327
|
}
|
|
1328
|
+
ignoreFiles.add(value);
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1025
1331
|
|
|
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
|
|
1332
|
+
if (arg === "--only-folder" || arg === "-ofo") {
|
|
1333
|
+
let consumed = 0;
|
|
1334
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1335
|
+
let folderName = args[i + 1];
|
|
1336
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1337
|
+
// Detect leading / as root-only signal (same as --only-file behavior)
|
|
1037
1338
|
if (folderName.startsWith("/")) {
|
|
1038
1339
|
rootOnlyInclude = true;
|
|
1039
1340
|
}
|
|
1040
|
-
folderName = folderName.replace(/^\.?\//,
|
|
1041
|
-
folderName = folderName.replace(/\/+$/,
|
|
1341
|
+
folderName = folderName.replace(/^\.?\//, ""); // Remove leading ./ or /
|
|
1342
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1042
1343
|
onlyFolders.add(folderName);
|
|
1043
|
-
|
|
1344
|
+
i += 1;
|
|
1345
|
+
consumed += 1;
|
|
1044
1346
|
}
|
|
1347
|
+
if (consumed === 0) {
|
|
1348
|
+
console.error(
|
|
1349
|
+
"Error: --only-folder requires at least one folder name.",
|
|
1350
|
+
);
|
|
1351
|
+
process.exit(1);
|
|
1352
|
+
}
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1045
1355
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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;
|
|
1356
|
+
if (arg.startsWith("--only-folder=") || arg.startsWith("-ofo=")) {
|
|
1357
|
+
const value = arg.startsWith("--only-folder=")
|
|
1358
|
+
? arg.slice("--only-folder=".length)
|
|
1359
|
+
: arg.slice("-ofo=".length);
|
|
1360
|
+
if (!value) {
|
|
1361
|
+
console.error("Error: --only-folder requires a folder name.");
|
|
1362
|
+
process.exit(1);
|
|
1363
|
+
}
|
|
1364
|
+
let folderName = value;
|
|
1365
|
+
folderName = folderName.replace(/\\/g, "/"); // Convert backslashes to forward slashes
|
|
1366
|
+
// Detect leading / as root-only signal
|
|
1367
|
+
if (folderName.startsWith("/")) {
|
|
1368
|
+
rootOnlyInclude = true;
|
|
1070
1369
|
}
|
|
1370
|
+
folderName = folderName.replace(/^\.?\//, ""); // Remove leading ./ or /
|
|
1371
|
+
folderName = folderName.replace(/\/+$/, ""); // Remove trailing slashes
|
|
1372
|
+
onlyFolders.add(folderName);
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (arg === "--only-file" || arg === "-ofi") {
|
|
1377
|
+
let consumed = 0;
|
|
1378
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1379
|
+
let fileName = args[i + 1];
|
|
1071
1380
|
|
|
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
1381
|
// Check for /file syntax (root-only include)
|
|
1082
|
-
if (
|
|
1083
|
-
const cleanFileName =
|
|
1382
|
+
if (fileName.startsWith("/")) {
|
|
1383
|
+
const cleanFileName = fileName.slice(1); // Remove leading /
|
|
1084
1384
|
onlyFiles.add(cleanFileName);
|
|
1085
1385
|
rootOnlyInclude = true;
|
|
1386
|
+
i += 1;
|
|
1387
|
+
consumed += 1;
|
|
1086
1388
|
} else {
|
|
1087
1389
|
// Normal file path (could be nested like folder/file.ext)
|
|
1088
|
-
onlyFiles.add(
|
|
1390
|
+
onlyFiles.add(fileName);
|
|
1391
|
+
i += 1;
|
|
1392
|
+
consumed += 1;
|
|
1089
1393
|
}
|
|
1090
|
-
continue;
|
|
1091
1394
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1395
|
+
if (consumed === 0) {
|
|
1396
|
+
console.error("Error: --only-file requires at least one file name.");
|
|
1397
|
+
process.exit(1);
|
|
1398
|
+
}
|
|
1399
|
+
continue;
|
|
1097
1400
|
}
|
|
1098
1401
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1402
|
+
if (arg.startsWith("--only-file=") || arg.startsWith("-ofi=")) {
|
|
1403
|
+
const value = arg.startsWith("--only-file=")
|
|
1404
|
+
? arg.slice("--only-file=".length)
|
|
1405
|
+
: arg.slice("-ofi=".length);
|
|
1406
|
+
if (!value) {
|
|
1407
|
+
console.error("Error: --only-file requires a file name.");
|
|
1408
|
+
process.exit(1);
|
|
1409
|
+
}
|
|
1104
1410
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1411
|
+
// Check for /file syntax (root-only include)
|
|
1412
|
+
if (value.startsWith("/")) {
|
|
1413
|
+
const cleanFileName = value.slice(1); // Remove leading /
|
|
1414
|
+
onlyFiles.add(cleanFileName);
|
|
1415
|
+
rootOnlyInclude = true;
|
|
1416
|
+
} else {
|
|
1417
|
+
// Normal file path (could be nested like folder/file.ext)
|
|
1418
|
+
onlyFiles.add(value);
|
|
1419
|
+
}
|
|
1420
|
+
continue;
|
|
1108
1421
|
}
|
|
1109
1422
|
|
|
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
|
-
);
|
|
1423
|
+
// Unknown argument
|
|
1424
|
+
console.error(`Error: Unknown option "${arg}"`);
|
|
1425
|
+
console.error("Use --help for available options.");
|
|
1426
|
+
process.exit(1);
|
|
1427
|
+
}
|
|
1133
1428
|
|
|
1134
|
-
|
|
1429
|
+
// Validate split options
|
|
1430
|
+
if (splitMethod === "size" && !splitSize) {
|
|
1431
|
+
console.error(
|
|
1432
|
+
"Error: --split-method size requires --split-size to be specified",
|
|
1433
|
+
);
|
|
1434
|
+
process.exit(1);
|
|
1435
|
+
}
|
|
1135
1436
|
|
|
1136
|
-
|
|
1437
|
+
if (splitSize && splitMethod !== "size") {
|
|
1438
|
+
console.error(
|
|
1439
|
+
"Error: --split-size can only be used with --split-method size",
|
|
1440
|
+
);
|
|
1441
|
+
process.exit(1);
|
|
1442
|
+
}
|
|
1137
1443
|
|
|
1138
|
-
|
|
1444
|
+
// ── config ────────────────────────────────────────────────────────────────────────
|
|
1445
|
+
|
|
1446
|
+
const folderPath = process.cwd();
|
|
1447
|
+
const rootName = path.basename(folderPath);
|
|
1448
|
+
const txtIgnore = readTxtIgnore(folderPath);
|
|
1449
|
+
|
|
1450
|
+
// ── build tree & collect file paths ───────────────────────────────────────────────
|
|
1451
|
+
|
|
1452
|
+
const { lines: treeLines, filePaths } = collectFiles(
|
|
1453
|
+
folderPath,
|
|
1454
|
+
folderPath,
|
|
1455
|
+
ignoreDirs,
|
|
1456
|
+
ignoreFiles,
|
|
1457
|
+
onlyFolders,
|
|
1458
|
+
onlyFiles,
|
|
1459
|
+
{
|
|
1460
|
+
rootName,
|
|
1461
|
+
txtIgnore,
|
|
1462
|
+
force: forceFlag,
|
|
1463
|
+
hasOnlyFilters: onlyFolders.size > 0 || onlyFiles.size > 0,
|
|
1464
|
+
rootOnlyInclude,
|
|
1465
|
+
},
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
// ── build output filename ───────────────────────────────────────────────────────────
|
|
1469
|
+
|
|
1470
|
+
let outputFile = outputArg || path.join(folderPath, `${rootName}.txt`);
|
|
1471
|
+
|
|
1472
|
+
// ── handle output splitting ─────────────────────────────────────────────────────────
|
|
1473
|
+
|
|
1474
|
+
const effectiveMaxSize = noSkipFlag ? Infinity : maxFileSize;
|
|
1475
|
+
|
|
1476
|
+
if (splitMethod) {
|
|
1477
|
+
console.log(`🔧 Splitting output by: ${splitMethod}`);
|
|
1478
|
+
|
|
1479
|
+
let results;
|
|
1480
|
+
|
|
1481
|
+
if (splitMethod === "folder") {
|
|
1482
|
+
results = splitByFolders(
|
|
1483
|
+
treeLines,
|
|
1484
|
+
filePaths,
|
|
1485
|
+
rootName,
|
|
1486
|
+
effectiveMaxSize,
|
|
1487
|
+
forceFlag,
|
|
1488
|
+
);
|
|
1489
|
+
} else if (splitMethod === "file") {
|
|
1490
|
+
results = splitByFiles(filePaths, rootName, effectiveMaxSize, forceFlag);
|
|
1491
|
+
} else if (splitMethod === "size") {
|
|
1492
|
+
results = splitBySize(
|
|
1493
|
+
treeLines,
|
|
1494
|
+
filePaths,
|
|
1495
|
+
rootName,
|
|
1496
|
+
splitSize,
|
|
1497
|
+
effectiveMaxSize,
|
|
1498
|
+
forceFlag,
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1139
1501
|
|
|
1140
|
-
|
|
1502
|
+
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
1503
|
+
console.log("");
|
|
1141
1504
|
|
|
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);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
console.log(`✅ Done! Created ${results.length} split files:`);
|
|
1156
|
-
console.log('');
|
|
1157
|
-
|
|
1158
|
-
results.forEach((result, index) => {
|
|
1159
|
-
if (splitMethod === 'folder') {
|
|
1160
|
-
console.log(`📁 Folder: ${result.folder}`);
|
|
1161
|
-
} else if (splitMethod === 'file') {
|
|
1162
|
-
console.log(`📄 File: ${result.fileName}`);
|
|
1163
|
-
} else if (splitMethod === 'size') {
|
|
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');
|
|
1505
|
+
results.forEach((result, index) => {
|
|
1506
|
+
if (splitMethod === "folder") {
|
|
1507
|
+
console.log(`📁 Folder: ${result.folder}`);
|
|
1508
|
+
} else if (splitMethod === "file") {
|
|
1509
|
+
console.log(`📄 File: ${result.fileName}`);
|
|
1510
|
+
} else if (splitMethod === "size") {
|
|
1511
|
+
console.log(`📦 Part ${result.part}`);
|
|
1174
1512
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1513
|
+
console.log(`📄 Output : ${result.file}`);
|
|
1514
|
+
console.log(`📊 Size : ${result.size} KB`);
|
|
1515
|
+
console.log(`🗂️ Files : ${result.files}`);
|
|
1516
|
+
console.log("");
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
if (copyToClipboardFlag) {
|
|
1520
|
+
console.log(
|
|
1521
|
+
"⚠️ --copy flag is not compatible with splitting - clipboard copy skipped",
|
|
1522
|
+
);
|
|
1177
1523
|
}
|
|
1178
1524
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const divider = "=".repeat(80);
|
|
1182
|
-
const subDivider = "-".repeat(80);
|
|
1525
|
+
process.exit(0);
|
|
1526
|
+
}
|
|
1183
1527
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1528
|
+
// ── build output (no splitting) ───────────────────────────────────────────────────
|
|
1529
|
+
const out = [];
|
|
1530
|
+
const divider = "=".repeat(80);
|
|
1531
|
+
const subDivider = "-".repeat(80);
|
|
1188
1532
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1533
|
+
out.push(divider);
|
|
1534
|
+
out.push(`START OF FOLDER: ${rootName}`);
|
|
1535
|
+
out.push(divider);
|
|
1536
|
+
out.push("");
|
|
1193
1537
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
out.push("");
|
|
1538
|
+
out.push(divider);
|
|
1539
|
+
out.push("PROJECT STRUCTURE");
|
|
1540
|
+
out.push(divider);
|
|
1541
|
+
out.push(`Root: ${folderPath}\n`);
|
|
1199
1542
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1543
|
+
out.push(`${rootName}/`);
|
|
1544
|
+
treeLines.forEach((l) => out.push(l));
|
|
1545
|
+
out.push("");
|
|
1546
|
+
out.push(`Total files: ${filePaths.length}`);
|
|
1547
|
+
out.push("");
|
|
1203
1548
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
out.push(`FILE: ${rel}`);
|
|
1208
|
-
out.push(subDivider);
|
|
1209
|
-
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
1210
|
-
});
|
|
1549
|
+
out.push(divider);
|
|
1550
|
+
out.push("FILE CONTENTS");
|
|
1551
|
+
out.push(divider);
|
|
1211
1552
|
|
|
1553
|
+
filePaths.forEach(({ abs, rel }) => {
|
|
1212
1554
|
out.push("");
|
|
1213
|
-
out.push(
|
|
1214
|
-
out.push(`
|
|
1215
|
-
out.push(
|
|
1555
|
+
out.push(subDivider);
|
|
1556
|
+
out.push(`FILE: ${rel}`);
|
|
1557
|
+
out.push(subDivider);
|
|
1558
|
+
out.push(readContent(abs, forceFlag, effectiveMaxSize));
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
out.push("");
|
|
1562
|
+
out.push(divider);
|
|
1563
|
+
out.push(`END OF FOLDER: ${rootName}`);
|
|
1564
|
+
out.push(divider);
|
|
1216
1565
|
|
|
1217
|
-
|
|
1566
|
+
fs.writeFileSync(outputFile, out.join("\n"), "utf8");
|
|
1218
1567
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1568
|
+
const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
|
|
1569
|
+
console.log(`✅ Done!`);
|
|
1570
|
+
console.log(`📄 Output : ${outputFile}`);
|
|
1571
|
+
console.log(`📊 Size : ${sizeKB} KB`);
|
|
1572
|
+
console.log(`🗂️ Files : ${filePaths.length}`);
|
|
1224
1573
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1574
|
+
if (copyToClipboardFlag) {
|
|
1575
|
+
const content = fs.readFileSync(outputFile, "utf8");
|
|
1576
|
+
const success = copyToClipboard(content);
|
|
1577
|
+
if (success) {
|
|
1578
|
+
console.log(`📋 Copied to clipboard!`);
|
|
1231
1579
|
}
|
|
1580
|
+
}
|
|
1232
1581
|
|
|
1233
|
-
|
|
1582
|
+
console.log("");
|
|
1234
1583
|
}
|
|
1235
1584
|
|
|
1236
1585
|
// Run the main function
|
|
1237
|
-
main().catch(err => {
|
|
1238
|
-
console.error(
|
|
1586
|
+
main().catch((err) => {
|
|
1587
|
+
console.error("❌ Error:", err.message);
|
|
1239
1588
|
process.exit(1);
|
|
1240
|
-
});
|
|
1589
|
+
});
|