make-folder-txt 1.4.1 → 1.4.3

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.
Files changed (2) hide show
  1. package/bin/make-folder-txt.js +397 -392
  2. package/package.json +1 -1
@@ -1,392 +1,397 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
- const { version } = require("../package.json");
6
-
7
- // ── config ────────────────────────────────────────────────────────────────────
8
- const IGNORE_DIRS = new Set(["node_modules", ".git", ".next", "dist", "build", ".cache"]);
9
- const IGNORE_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".txtignore"]);
10
-
11
- const BINARY_EXTS = new Set([
12
- ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg", ".webp",
13
- ".pdf", ".zip", ".tar", ".gz", ".rar", ".7z",
14
- ".exe", ".dll", ".so", ".dylib", ".bin",
15
- ".mp3", ".mp4", ".wav", ".avi", ".mov",
16
- ".woff", ".woff2", ".ttf", ".eot", ".otf",
17
- ".lock",
18
- ]);
19
-
20
- // ── helpers ───────────────────────────────────────────────────────────────────
21
-
22
- function readTxtIgnore(rootDir) {
23
- const txtIgnorePath = path.join(rootDir, '.txtignore');
24
- const ignorePatterns = new Set();
25
-
26
- try {
27
- const content = fs.readFileSync(txtIgnorePath, 'utf8');
28
- const lines = content.split('\n')
29
- .map(line => line.trim())
30
- .filter(line => line && !line.startsWith('#'));
31
-
32
- lines.forEach(line => ignorePatterns.add(line));
33
- } catch (err) {
34
- // .txtignore doesn't exist or can't be read - that's fine
35
- }
36
-
37
- return ignorePatterns;
38
- }
39
-
40
- function collectFiles(
41
- dir,
42
- rootDir,
43
- ignoreDirs,
44
- ignoreFiles,
45
- onlyFolders,
46
- onlyFiles,
47
- options = {},
48
- ) {
49
- const {
50
- indent = "",
51
- lines = [],
52
- filePaths = [],
53
- inSelectedFolder = false,
54
- hasOnlyFilters = false,
55
- rootName = "",
56
- txtIgnore = new Set(),
57
- } = options;
58
-
59
- let entries;
60
- try {
61
- entries = fs.readdirSync(dir, { withFileTypes: true });
62
- } catch {
63
- return { lines, filePaths, hasIncluded: false };
64
- }
65
-
66
- entries.sort((a, b) => {
67
- if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
68
- return a.isDirectory() ? -1 : 1;
69
- });
70
-
71
- entries.forEach((entry, idx) => {
72
- const isLast = idx === entries.length - 1;
73
- const connector = isLast ? "└── " : "├── ";
74
- const childIndent = indent + (isLast ? " " : "│ ");
75
-
76
- if (entry.isDirectory()) {
77
- if (ignoreDirs.has(entry.name)) {
78
- if (!hasOnlyFilters) {
79
- lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
80
- }
81
- return;
82
- }
83
-
84
- // Get relative path for .txtignore pattern matching
85
- const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
86
-
87
- // Check against .txtignore patterns (both dirname and relative path)
88
- if (txtIgnore.has(entry.name) || txtIgnore.has(`${entry.name}/`) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`${relPathForIgnore}/`) || txtIgnore.has(`/${relPathForIgnore}/`)) {
89
- if (!hasOnlyFilters) {
90
- lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
91
- }
92
- return;
93
- }
94
-
95
- const childPath = path.join(dir, entry.name);
96
- const childInSelectedFolder = inSelectedFolder || onlyFolders.has(entry.name);
97
- const childLines = [];
98
- const childFiles = [];
99
- const child = collectFiles(
100
- childPath,
101
- rootDir,
102
- ignoreDirs,
103
- ignoreFiles,
104
- onlyFolders,
105
- onlyFiles,
106
- {
107
- indent: childIndent,
108
- lines: childLines,
109
- filePaths: childFiles,
110
- inSelectedFolder: childInSelectedFolder,
111
- hasOnlyFilters,
112
- rootName,
113
- txtIgnore,
114
- },
115
- );
116
-
117
- const explicitlySelectedFolder = hasOnlyFilters && onlyFolders.has(entry.name);
118
- const shouldIncludeDir = !hasOnlyFilters || child.hasIncluded || explicitlySelectedFolder;
119
-
120
- if (shouldIncludeDir) {
121
- lines.push(`${indent}${connector}${entry.name}/`);
122
- lines.push(...child.lines);
123
- filePaths.push(...child.filePaths);
124
- }
125
- } else {
126
- if (ignoreFiles.has(entry.name)) return;
127
-
128
- // Get relative path for .txtignore pattern matching
129
- const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
130
-
131
- // Check against .txtignore patterns (both filename and relative path)
132
- if (txtIgnore.has(entry.name) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`/${relPathForIgnore}`)) {
133
- return;
134
- }
135
-
136
- // Ignore .txt files that match the folder name (e.g., foldername.txt)
137
- if (entry.name.endsWith('.txt') && entry.name === `${rootName}.txt`) return;
138
-
139
- const shouldIncludeFile = !hasOnlyFilters || inSelectedFolder || onlyFiles.has(entry.name);
140
- if (!shouldIncludeFile) return;
141
-
142
- lines.push(`${indent}${connector}${entry.name}`);
143
- const relPath = "/" + path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
144
- filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
145
- }
146
- });
147
-
148
- return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
149
- }
150
-
151
- function readContent(absPath) {
152
- const ext = path.extname(absPath).toLowerCase();
153
- if (BINARY_EXTS.has(ext)) return "[binary / skipped]";
154
- try {
155
- const stat = fs.statSync(absPath);
156
- if (stat.size > 500 * 1024) {
157
- return `[file too large: ${(stat.size / 1024).toFixed(1)} KB – skipped]`;
158
- }
159
- return fs.readFileSync(absPath, "utf8");
160
- } catch (err) {
161
- return `[could not read file: ${err.message}]`;
162
- }
163
- }
164
-
165
- // ── main ──────────────────────────────────────────────────────────────────────
166
-
167
- const args = process.argv.slice(2);
168
-
169
- if (args.includes("-v") || args.includes("--version")) {
170
- console.log(`v${version}`);
171
- console.log("Built by Muhammad Saad Amin");
172
- process.exit(0);
173
- }
174
-
175
- if (args.includes("--help") || args.includes("-h")) {
176
- console.log(`
177
- \x1b[33mmake-folder-txt\x1b[0m
178
- Dump an entire project folder into a single readable .txt file.
179
- \x1b[33mUSAGE\x1b[0m
180
- make-folder-txt [options]
181
- \x1b[33mOPTIONS\x1b[0m
182
- --ignore-folder <names...> Ignore specific folders by name
183
- --ignore-file <names...> Ignore specific files by name
184
- --only-folder <names...> Include only specific folders
185
- --only-file <names...> Include only specific files
186
- --help, -h Show this help message
187
- --version, -v Show version information
188
- \x1b[33mEXAMPLES\x1b[0m
189
- make-folder-txt
190
- make-folder-txt --ignore-folder node_modules dist
191
- make-folder-txt --ignore-file .env .env.local
192
- make-folder-txt --only-folder src docs
193
- make-folder-txt --only-file package.json README.md
194
- \x1b[33m.TXTIGNORE FILE\x1b[0m
195
- Create a .txtignore file in your project root to specify files/folders to ignore.
196
- Works like .gitignore - supports file names, path patterns, and comments.
197
- Example .txtignore:
198
- node_modules/
199
- *.log
200
- .env
201
- coverage/
202
- LICENSE
203
- `);
204
- process.exit(0);
205
- }
206
-
207
- const ignoreDirs = new Set(IGNORE_DIRS);
208
- const ignoreFiles = new Set(IGNORE_FILES);
209
- const onlyFolders = new Set();
210
- const onlyFiles = new Set();
211
- let outputArg = null;
212
-
213
- for (let i = 0; i < args.length; i += 1) {
214
- const arg = args[i];
215
-
216
- if (arg === "--ignore-folder") {
217
- let consumed = 0;
218
- while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
219
- ignoreDirs.add(args[i + 1]);
220
- i += 1;
221
- consumed += 1;
222
- }
223
- if (consumed === 0) {
224
- console.error("Error: --ignore-folder requires at least one folder name.");
225
- process.exit(1);
226
- }
227
- continue;
228
- }
229
-
230
- if (arg.startsWith("--ignore-folder=")) {
231
- const value = arg.slice("--ignore-folder=".length);
232
- if (!value) {
233
- console.error("Error: --ignore-folder requires a folder name.");
234
- process.exit(1);
235
- }
236
- ignoreDirs.add(value);
237
- continue;
238
- }
239
-
240
- if (arg === "--ignore-file") {
241
- let consumed = 0;
242
- while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
243
- ignoreFiles.add(args[i + 1]);
244
- i += 1;
245
- consumed += 1;
246
- }
247
- if (consumed === 0) {
248
- console.error("Error: --ignore-file requires at least one file name.");
249
- process.exit(1);
250
- }
251
- continue;
252
- }
253
-
254
- if (arg.startsWith("--ignore-file=")) {
255
- const value = arg.slice("--ignore-file=".length);
256
- if (!value) {
257
- console.error("Error: --ignore-file requires a file name.");
258
- process.exit(1);
259
- }
260
- ignoreFiles.add(value);
261
- continue;
262
- }
263
-
264
- if (arg === "--only-folder") {
265
- let consumed = 0;
266
- while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
267
- onlyFolders.add(args[i + 1]);
268
- i += 1;
269
- consumed += 1;
270
- }
271
- if (consumed === 0) {
272
- console.error("Error: --only-folder requires at least one folder name.");
273
- process.exit(1);
274
- }
275
- continue;
276
- }
277
-
278
- if (arg.startsWith("--only-folder=")) {
279
- const value = arg.slice("--only-folder=".length);
280
- if (!value) {
281
- console.error("Error: --only-folder requires a folder name.");
282
- process.exit(1);
283
- }
284
- onlyFolders.add(value);
285
- continue;
286
- }
287
-
288
- if (arg === "--only-file") {
289
- let consumed = 0;
290
- while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
291
- onlyFiles.add(args[i + 1]);
292
- i += 1;
293
- consumed += 1;
294
- }
295
- if (consumed === 0) {
296
- console.error("Error: --only-file requires at least one file name.");
297
- process.exit(1);
298
- }
299
- continue;
300
- }
301
-
302
- if (arg.startsWith("--only-file=")) {
303
- const value = arg.slice("--only-file=".length);
304
- if (!value) {
305
- console.error("Error: --only-file requires a file name.");
306
- process.exit(1);
307
- }
308
- onlyFiles.add(value);
309
- continue;
310
- }
311
-
312
- if (arg.startsWith("-")) {
313
- console.error(`Error: Unknown option "${arg}".`);
314
- process.exit(1);
315
- }
316
-
317
- if (!outputArg) {
318
- outputArg = arg;
319
- continue;
320
- }
321
-
322
- console.error(`Error: Unexpected argument "${arg}".`);
323
- process.exit(1);
324
- }
325
-
326
- const folderPath = process.cwd();
327
- const rootName = path.basename(folderPath);
328
-
329
- // Read .txtignore file if it exists
330
- const txtIgnore = readTxtIgnore(folderPath);
331
-
332
- const outputFile = outputArg
333
- ? path.resolve(outputArg)
334
- : path.join(process.cwd(), `${rootName}.txt`);
335
-
336
- console.log(`\n📂 Scanning: ${folderPath}`);
337
-
338
- const hasOnlyFilters = onlyFolders.size > 0 || onlyFiles.size > 0;
339
- const { lines: treeLines, filePaths } = collectFiles(
340
- folderPath,
341
- folderPath,
342
- ignoreDirs,
343
- ignoreFiles,
344
- onlyFolders,
345
- onlyFiles,
346
- { hasOnlyFilters, rootName, txtIgnore },
347
- );
348
-
349
- // ── build output ──────────────────────────────────────────────────────────────
350
- const out = [];
351
- const divider = "=".repeat(80);
352
- const subDivider = "-".repeat(80);
353
-
354
- out.push(divider);
355
- out.push(`START OF FOLDER: ${rootName}`);
356
- out.push(divider);
357
- out.push("");
358
-
359
- out.push(divider);
360
- out.push("PROJECT STRUCTURE");
361
- out.push(divider);
362
- out.push(`Root: ${folderPath}\n`);
363
- out.push(`${rootName}/`);
364
- treeLines.forEach(l => out.push(l));
365
- out.push("");
366
- out.push(`Total files: ${filePaths.length}`);
367
- out.push("");
368
-
369
- out.push(divider);
370
- out.push("FILE CONTENTS");
371
- out.push(divider);
372
-
373
- filePaths.forEach(({ abs, rel }) => {
374
- out.push("");
375
- out.push(subDivider);
376
- out.push(`FILE: ${rel}`);
377
- out.push(subDivider);
378
- out.push(readContent(abs));
379
- });
380
-
381
- out.push("");
382
- out.push(divider);
383
- out.push(`END OF FOLDER: ${rootName}`);
384
- out.push(divider);
385
-
386
- fs.writeFileSync(outputFile, out.join("\n"), "utf8");
387
-
388
- const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
389
- console.log(`✅ Done!`);
390
- console.log(`📄 Output : ${outputFile}`);
391
- console.log(`📊 Size : ${sizeKB} KB`);
392
- console.log(`🗂️ Files : ${filePaths.length}\n`);
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { version } = require("../package.json");
6
+
7
+ // ── config ────────────────────────────────────────────────────────────────────
8
+ const IGNORE_DIRS = new Set(["node_modules", ".git", ".next", "dist", "build", ".cache"]);
9
+ const IGNORE_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".txtignore"]);
10
+
11
+ const BINARY_EXTS = new Set([
12
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg", ".webp",
13
+ ".pdf", ".zip", ".tar", ".gz", ".rar", ".7z",
14
+ ".exe", ".dll", ".so", ".dylib", ".bin",
15
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
16
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
17
+ ".lock",
18
+ ]);
19
+
20
+ // ── helpers ───────────────────────────────────────────────────────────────────
21
+
22
+ function readTxtIgnore(rootDir) {
23
+ const txtIgnorePath = path.join(rootDir, '.txtignore');
24
+ const ignorePatterns = new Set();
25
+
26
+ try {
27
+ const content = fs.readFileSync(txtIgnorePath, 'utf8');
28
+ const lines = content.split('\n')
29
+ .map(line => line.trim())
30
+ .filter(line => line && !line.startsWith('#'));
31
+
32
+ lines.forEach(line => ignorePatterns.add(line));
33
+ } catch (err) {
34
+ // .txtignore doesn't exist or can't be read - that's fine
35
+ }
36
+
37
+ return ignorePatterns;
38
+ }
39
+
40
+ function collectFiles(
41
+ dir,
42
+ rootDir,
43
+ ignoreDirs,
44
+ ignoreFiles,
45
+ onlyFolders,
46
+ onlyFiles,
47
+ options = {},
48
+ ) {
49
+ const {
50
+ indent = "",
51
+ lines = [],
52
+ filePaths = [],
53
+ inSelectedFolder = false,
54
+ hasOnlyFilters = false,
55
+ rootName = "",
56
+ txtIgnore = new Set(),
57
+ } = options;
58
+
59
+ let entries;
60
+ try {
61
+ entries = fs.readdirSync(dir, { withFileTypes: true });
62
+ } catch {
63
+ return { lines, filePaths, hasIncluded: false };
64
+ }
65
+
66
+ entries.sort((a, b) => {
67
+ if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
68
+ return a.isDirectory() ? -1 : 1;
69
+ });
70
+
71
+ entries.forEach((entry, idx) => {
72
+ const isLast = idx === entries.length - 1;
73
+ const connector = isLast ? "└── " : "├── ";
74
+ const childIndent = indent + (isLast ? " " : "│ ");
75
+
76
+ if (entry.isDirectory()) {
77
+ if (ignoreDirs.has(entry.name)) {
78
+ if (!hasOnlyFilters) {
79
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
80
+ }
81
+ return;
82
+ }
83
+
84
+ // Get relative path for .txtignore pattern matching
85
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
86
+
87
+ // Check against .txtignore patterns (both dirname and relative path)
88
+ if (txtIgnore.has(entry.name) || txtIgnore.has(`${entry.name}/`) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`${relPathForIgnore}/`) || txtIgnore.has(`/${relPathForIgnore}/`)) {
89
+ if (!hasOnlyFilters) {
90
+ lines.push(`${indent}${connector}${entry.name}/ [skipped]`);
91
+ }
92
+ return;
93
+ }
94
+
95
+ const childPath = path.join(dir, entry.name);
96
+ const childInSelectedFolder = inSelectedFolder || onlyFolders.has(entry.name);
97
+ const childLines = [];
98
+ const childFiles = [];
99
+ const child = collectFiles(
100
+ childPath,
101
+ rootDir,
102
+ ignoreDirs,
103
+ ignoreFiles,
104
+ onlyFolders,
105
+ onlyFiles,
106
+ {
107
+ indent: childIndent,
108
+ lines: childLines,
109
+ filePaths: childFiles,
110
+ inSelectedFolder: childInSelectedFolder,
111
+ hasOnlyFilters,
112
+ rootName,
113
+ txtIgnore,
114
+ },
115
+ );
116
+
117
+ const explicitlySelectedFolder = hasOnlyFilters && onlyFolders.has(entry.name);
118
+ const shouldIncludeDir = !hasOnlyFilters || child.hasIncluded || explicitlySelectedFolder;
119
+
120
+ if (shouldIncludeDir) {
121
+ lines.push(`${indent}${connector}${entry.name}/`);
122
+ lines.push(...child.lines);
123
+ filePaths.push(...child.filePaths);
124
+ }
125
+ } else {
126
+ if (ignoreFiles.has(entry.name)) return;
127
+
128
+ // Get relative path for .txtignore pattern matching
129
+ const relPathForIgnore = path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
130
+
131
+ // Check against .txtignore patterns (both filename and relative path)
132
+ if (txtIgnore.has(entry.name) || txtIgnore.has(relPathForIgnore) || txtIgnore.has(`/${relPathForIgnore}`)) {
133
+ return;
134
+ }
135
+
136
+ // Ignore .txt files that match the folder name (e.g., foldername.txt)
137
+ if (entry.name.endsWith('.txt') && entry.name === `${rootName}.txt`) return;
138
+
139
+ const shouldIncludeFile = !hasOnlyFilters || inSelectedFolder || onlyFiles.has(entry.name);
140
+ if (!shouldIncludeFile) return;
141
+
142
+ lines.push(`${indent}${connector}${entry.name}`);
143
+ const relPath = "/" + path.relative(rootDir, path.join(dir, entry.name)).split(path.sep).join("/");
144
+ filePaths.push({ abs: path.join(dir, entry.name), rel: relPath });
145
+ }
146
+ });
147
+
148
+ return { lines, filePaths, hasIncluded: filePaths.length > 0 || lines.length > 0 };
149
+ }
150
+
151
+ function readContent(absPath) {
152
+ const ext = path.extname(absPath).toLowerCase();
153
+ if (BINARY_EXTS.has(ext)) return "[binary / skipped]";
154
+ try {
155
+ const stat = fs.statSync(absPath);
156
+ if (stat.size > 500 * 1024) {
157
+ return `[file too large: ${(stat.size / 1024).toFixed(1)} KB – skipped]`;
158
+ }
159
+ return fs.readFileSync(absPath, "utf8");
160
+ } catch (err) {
161
+ return `[could not read file: ${err.message}]`;
162
+ }
163
+ }
164
+
165
+ // ── main ──────────────────────────────────────────────────────────────────────
166
+
167
+ const args = process.argv.slice(2);
168
+
169
+ if (args.includes("-v") || args.includes("--version")) {
170
+ console.log(`v${version}`);
171
+ console.log("Built by Muhammad Saad Amin");
172
+ process.exit(0);
173
+ }
174
+
175
+ if (args.includes("--help") || args.includes("-h")) {
176
+ console.log(`
177
+ \x1b[33mmake-folder-txt\x1b[0m
178
+ Dump an entire project folder into a single readable .txt file.
179
+
180
+ \x1b[33mUSAGE\x1b[0m
181
+ make-folder-txt [options]
182
+
183
+ \x1b[33mOPTIONS\x1b[0m
184
+ --ignore-folder <names...> Ignore specific folders by name
185
+ --ignore-file <names...> Ignore specific files by name
186
+ --only-folder <names...> Include only specific folders
187
+ --only-file <names...> Include only specific files
188
+ --help, -h Show this help message
189
+ --version, -v Show version information
190
+
191
+ \x1b[33mEXAMPLES\x1b[0m
192
+ make-folder-txt
193
+ make-folder-txt --ignore-folder node_modules dist
194
+ make-folder-txt --ignore-file .env .env.local
195
+ make-folder-txt --only-folder src docs
196
+ make-folder-txt --only-file package.json README.md
197
+
198
+ \x1b[33m.TXTIGNORE FILE\x1b[0m
199
+ Create a .txtignore file in your project root to specify files/folders to ignore.
200
+ Works like .gitignore - supports file names, path patterns, and comments.
201
+
202
+ Example .txtignore:
203
+ node_modules/
204
+ *.log
205
+ .env
206
+ coverage/
207
+ LICENSE
208
+ `);
209
+ process.exit(0);
210
+ }
211
+
212
+ const ignoreDirs = new Set(IGNORE_DIRS);
213
+ const ignoreFiles = new Set(IGNORE_FILES);
214
+ const onlyFolders = new Set();
215
+ const onlyFiles = new Set();
216
+ let outputArg = null;
217
+
218
+ for (let i = 0; i < args.length; i += 1) {
219
+ const arg = args[i];
220
+
221
+ if (arg === "--ignore-folder") {
222
+ let consumed = 0;
223
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
224
+ ignoreDirs.add(args[i + 1]);
225
+ i += 1;
226
+ consumed += 1;
227
+ }
228
+ if (consumed === 0) {
229
+ console.error("Error: --ignore-folder requires at least one folder name.");
230
+ process.exit(1);
231
+ }
232
+ continue;
233
+ }
234
+
235
+ if (arg.startsWith("--ignore-folder=")) {
236
+ const value = arg.slice("--ignore-folder=".length);
237
+ if (!value) {
238
+ console.error("Error: --ignore-folder requires a folder name.");
239
+ process.exit(1);
240
+ }
241
+ ignoreDirs.add(value);
242
+ continue;
243
+ }
244
+
245
+ if (arg === "--ignore-file") {
246
+ let consumed = 0;
247
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
248
+ ignoreFiles.add(args[i + 1]);
249
+ i += 1;
250
+ consumed += 1;
251
+ }
252
+ if (consumed === 0) {
253
+ console.error("Error: --ignore-file requires at least one file name.");
254
+ process.exit(1);
255
+ }
256
+ continue;
257
+ }
258
+
259
+ if (arg.startsWith("--ignore-file=")) {
260
+ const value = arg.slice("--ignore-file=".length);
261
+ if (!value) {
262
+ console.error("Error: --ignore-file requires a file name.");
263
+ process.exit(1);
264
+ }
265
+ ignoreFiles.add(value);
266
+ continue;
267
+ }
268
+
269
+ if (arg === "--only-folder") {
270
+ let consumed = 0;
271
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
272
+ onlyFolders.add(args[i + 1]);
273
+ i += 1;
274
+ consumed += 1;
275
+ }
276
+ if (consumed === 0) {
277
+ console.error("Error: --only-folder requires at least one folder name.");
278
+ process.exit(1);
279
+ }
280
+ continue;
281
+ }
282
+
283
+ if (arg.startsWith("--only-folder=")) {
284
+ const value = arg.slice("--only-folder=".length);
285
+ if (!value) {
286
+ console.error("Error: --only-folder requires a folder name.");
287
+ process.exit(1);
288
+ }
289
+ onlyFolders.add(value);
290
+ continue;
291
+ }
292
+
293
+ if (arg === "--only-file") {
294
+ let consumed = 0;
295
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
296
+ onlyFiles.add(args[i + 1]);
297
+ i += 1;
298
+ consumed += 1;
299
+ }
300
+ if (consumed === 0) {
301
+ console.error("Error: --only-file requires at least one file name.");
302
+ process.exit(1);
303
+ }
304
+ continue;
305
+ }
306
+
307
+ if (arg.startsWith("--only-file=")) {
308
+ const value = arg.slice("--only-file=".length);
309
+ if (!value) {
310
+ console.error("Error: --only-file requires a file name.");
311
+ process.exit(1);
312
+ }
313
+ onlyFiles.add(value);
314
+ continue;
315
+ }
316
+
317
+ if (arg.startsWith("-")) {
318
+ console.error(`Error: Unknown option "${arg}".`);
319
+ process.exit(1);
320
+ }
321
+
322
+ if (!outputArg) {
323
+ outputArg = arg;
324
+ continue;
325
+ }
326
+
327
+ console.error(`Error: Unexpected argument "${arg}".`);
328
+ process.exit(1);
329
+ }
330
+
331
+ const folderPath = process.cwd();
332
+ const rootName = path.basename(folderPath);
333
+
334
+ // Read .txtignore file if it exists
335
+ const txtIgnore = readTxtIgnore(folderPath);
336
+
337
+ const outputFile = outputArg
338
+ ? path.resolve(outputArg)
339
+ : path.join(process.cwd(), `${rootName}.txt`);
340
+
341
+ console.log(`\n📂 Scanning: ${folderPath}`);
342
+
343
+ const hasOnlyFilters = onlyFolders.size > 0 || onlyFiles.size > 0;
344
+ const { lines: treeLines, filePaths } = collectFiles(
345
+ folderPath,
346
+ folderPath,
347
+ ignoreDirs,
348
+ ignoreFiles,
349
+ onlyFolders,
350
+ onlyFiles,
351
+ { hasOnlyFilters, rootName, txtIgnore },
352
+ );
353
+
354
+ // ── build output ──────────────────────────────────────────────────────────────
355
+ const out = [];
356
+ const divider = "=".repeat(80);
357
+ const subDivider = "-".repeat(80);
358
+
359
+ out.push(divider);
360
+ out.push(`START OF FOLDER: ${rootName}`);
361
+ out.push(divider);
362
+ out.push("");
363
+
364
+ out.push(divider);
365
+ out.push("PROJECT STRUCTURE");
366
+ out.push(divider);
367
+ out.push(`Root: ${folderPath}\n`);
368
+ out.push(`${rootName}/`);
369
+ treeLines.forEach(l => out.push(l));
370
+ out.push("");
371
+ out.push(`Total files: ${filePaths.length}`);
372
+ out.push("");
373
+
374
+ out.push(divider);
375
+ out.push("FILE CONTENTS");
376
+ out.push(divider);
377
+
378
+ filePaths.forEach(({ abs, rel }) => {
379
+ out.push("");
380
+ out.push(subDivider);
381
+ out.push(`FILE: ${rel}`);
382
+ out.push(subDivider);
383
+ out.push(readContent(abs));
384
+ });
385
+
386
+ out.push("");
387
+ out.push(divider);
388
+ out.push(`END OF FOLDER: ${rootName}`);
389
+ out.push(divider);
390
+
391
+ fs.writeFileSync(outputFile, out.join("\n"), "utf8");
392
+
393
+ const sizeKB = (fs.statSync(outputFile).size / 1024).toFixed(1);
394
+ console.log(`✅ Done!`);
395
+ console.log(`📄 Output : ${outputFile}`);
396
+ console.log(`📊 Size : ${sizeKB} KB`);
397
+ console.log(`🗂️ Files : ${filePaths.length}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-folder-txt",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Generate a single .txt file containing the full folder structure and file contents of any project, ignoring node_modules and other junk.",
5
5
  "main": "bin/make-folder-txt.js",
6
6
  "bin": {