repomeld 2.0.5 → 3.0.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.
Files changed (3) hide show
  1. package/README.md +51 -116
  2. package/bin/cli.js +16 -480
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -15,16 +15,18 @@
15
15
 
16
16
  ## ✨ Features
17
17
 
18
- - šŸš€ **Fast & Efficient** - Async scanning with real-time progress
18
+ - šŸš€ **Fast & Efficient** - Async scanning with real-time progress and binary caching
19
19
  - šŸŽØ **Multiple Styles** - Banner, Markdown, or Minimal output
20
20
  - šŸ” **Smart Filtering** - Extension, pattern, and size-based filtering
21
21
  - šŸ“ **Gitignore Support** - Respects your .gitignore rules automatically
22
- - šŸ’¾ **Binary Detection** - Intelligently skips binary files
22
+ - šŸ’¾ **Binary Detection** - Intelligent caching for binary file detection
23
23
  - šŸ“¦ **Single File Output** - Perfect for AI context windows
24
24
  - šŸ”„ **Auto-Numbering** - Never overwrites existing files
25
- - šŸ’æ **Zip Backup** - Creates timestamped backups of all included files
26
- - šŸ”” **Update Notifications** - Know when new versions are available
25
+ - šŸ’æ **Zip Backup** - Creates auto-numbered backups in `repomeld_zips/` folder
26
+ - šŸ”” **Update Notifications** - Non-intrusive version checking
27
27
  - šŸŽÆ **Force Include** - Override ignore rules when needed
28
+ - šŸ“Š **Dependency Graph** - Optional Mermaid diagram of file dependencies
29
+ - šŸŒ **Cross-Platform** - Works perfectly on Windows, macOS, and Linux
28
30
 
29
31
  ---
30
32
 
@@ -49,7 +51,7 @@ cd your-project
49
51
  repomeld
50
52
  ```
51
53
 
52
- That's it. repomeld walks your project, respects `.gitignore`, skips binary files, and writes everything into one readable file.
54
+ That's it. repomeld walks your project, respects `.gitignore`, skips binary files, and writes everything into one readable file with optional dependency graphs.
53
55
 
54
56
  ---
55
57
 
@@ -61,9 +63,13 @@ Every time you run repomeld it creates a **new numbered file** so previous runs
61
63
  repomeld_output.txt ← first run
62
64
  repomeld_output__2.txt ← second run
63
65
  repomeld_output__3.txt ← third run
66
+ repomeld_zips/ ← backup folder
67
+ ā”œā”€ā”€ repomeld_output.zip
68
+ ā”œā”€ā”€ repomeld_output__2.zip
69
+ └── repomeld_output__3.zip
64
70
  ```
65
71
 
66
- All previous output files are also **automatically excluded** from the next run's content — so you'll never get repomeld's own output included inside itself.
72
+ All previous output files and zips are **automatically excluded** from the next run — so you'll never get repomeld's own output included inside itself.
67
73
 
68
74
  ---
69
75
 
@@ -191,16 +197,20 @@ repomeld automatically skips these so your output stays clean:
191
197
 
192
198
  ### Method 1: repomeld.ignore.json
193
199
 
194
- Create a `repomeld.ignore.json` in your project root:
200
+ Create a `repomeld.ignore.json` in your project root for comprehensive ignore patterns:
195
201
 
196
202
  ```json
197
203
  {
204
+ "_comment": "repomeld.ignore.json — auto-ignored files and folders",
198
205
  "ignore": [
199
206
  "coverage",
200
207
  "logs",
201
208
  "tmp",
202
209
  "*.min.js",
203
- "**/generated/**"
210
+ "**/generated/**",
211
+ "vendor/**/bootstrap*",
212
+ "**/jquery*",
213
+ "**/fontawesome*"
204
214
  ]
205
215
  }
206
216
  ```
@@ -223,19 +233,19 @@ repomeld --ignore temp logs "*.tmp"
223
233
 
224
234
  ## Backup Zip Files
225
235
 
226
- When repomeld runs, it automatically creates a backup zip file in the `repomeld_repomeld/` folder:
236
+ When repomeld runs, it automatically creates a backup zip file in the `repomeld_zips/` folder:
227
237
 
228
238
  ```
229
239
  repomeld_output.txt
230
- repomeld_repomeld/
240
+ repomeld_zips/
231
241
  └── repomeld_output.zip ← contains all included files + output
232
242
 
233
243
  repomeld_output__2.txt
234
- repomeld_repomeld/
244
+ repomeld_zips/
235
245
  └── repomeld_output__2.zip ← corresponding backup
236
246
 
237
247
  repomeld_output__3.txt
238
- repomeld_repomeld/
248
+ repomeld_zips/
239
249
  └── repomeld_output__3.zip ← and so on...
240
250
  ```
241
251
 
@@ -246,74 +256,18 @@ The zip file contains:
246
256
  To disable backups: `repomeld --no-backup`
247
257
 
248
258
  ---
259
+
249
260
 
250
- ## Output Format
251
-
252
- Each run produces a file like this:
253
-
254
- ```
255
- # Generated by repomeld v2.0.4
256
- # Date : 2025-04-23T10:00:00.000Z
257
- # Source : /your/project
258
- # Files : 12
259
- # Lines : 847
260
-
261
- TABLE OF CONTENTS
262
- ════════════════════════════════════════════════════════════
263
- 1. src/index.js
264
- 2. src/utils.js
265
- 3. src/config.js
266
- ...
267
- ════════════════════════════════════════════════════════════
268
-
269
- ────────────────────────────────────────────────────────────
270
- FILE: src/index.js [120 lines | 3.2 KB | javascript]
271
- ────────────────────────────────────────────────────────────
272
-
273
- ... file contents ...
274
- ```
275
-
276
- ### Markdown Style Example
277
-
278
- With `--style markdown` each file becomes a fenced code block:
279
-
280
- ```markdown
281
- ## šŸ“„ src/index.js [120 lines | 3.2 KB | javascript]
282
-
283
- ```javascript
284
- // Your code here
285
- ```
286
-
287
- ## šŸ“„ src/utils.js [45 lines | 1.1 KB | javascript]
288
-
289
- ```javascript
290
- // More code here
291
- ```
292
- ```
293
-
294
- Perfect for pasting directly into Claude, ChatGPT, Cursor, or any AI tool!
295
-
296
- ### Minimal Style Example
297
-
298
- With `--style minimal`:
299
-
300
- ```
301
- # src/index.js
302
- your code here
303
-
304
- # src/utils.js
305
- more code here
306
- ```
307
-
308
- ---
309
-
310
- ## Performance
261
+ ## Performance Optimizations
311
262
 
312
263
  repomeld is optimized for large codebases:
313
- - Async file scanning (non-blocking)
314
- - Real-time progress indicator with ETA
315
- - Memory-efficient streaming
316
- - Handles repos with 10,000+ files easily
264
+
265
+ - **Async file scanning** - Non-blocking operations
266
+ - **Binary caching** - Extension-based detection cache
267
+ - **Real-time progress** - Shows ETA and completion percentage
268
+ - **Memory efficient** - Processes files in streams
269
+ - **Smart filtering** - Early filtering to reduce processing
270
+ - **Handles 50,000+ files** - Tested on large monorepos
317
271
 
318
272
  Example output:
319
273
  ```
@@ -322,8 +276,13 @@ Example output:
322
276
 
323
277
  šŸ“ Processing 2453 files...
324
278
 
325
- [1245/2453] files (50.7%) | 2.3s elapsed | ETA: 2s
326
- āœ… Completed 2453/2453 files in 4.7s
279
+ Processing: 1245/2453 files (50.7%) | 2.3s elapsed
280
+ Processing: āœ… Completed 2453/2453 files in 4.7s
281
+ ```
282
+
283
+ **Memory warning** for extremely large repos (>20,000 files):
284
+ ```
285
+ āš ļø Large repository detected (~25347 files). Memory usage may be high.
327
286
  ```
328
287
 
329
288
  ---
@@ -355,29 +314,15 @@ repomeld --ext md --include docs --style markdown --output documentation.md
355
314
  repomeld --include feature-name --ext js css --output feature-context.txt
356
315
  ```
357
316
 
358
- ---
359
-
360
- ## FAQ
361
-
362
- **Q: Why are package.json and README.md included now?**
363
- A: They were removed in early versions but added back because they provide essential context for AI tools and code reviewers.
364
-
365
- **Q: How do I ignore package.json?**
366
- A: Add it to `repomeld.ignore.json` or use `--ignore package.json`
367
-
368
- **Q: Can I use this in CI/CD?**
369
- A: Yes! Use `--no-update-check` and `--no-backup` for automated environments.
370
-
371
- **Q: Does it work on Windows?**
372
- A: Yes! Paths are normalized for cross-platform compatibility.
317
+ ### šŸ—ŗļø Dependency Analysis
318
+ ```bash
319
+ repomeld --include src --style markdown --output analysis.md
320
+ # Then render the Mermaid graph in the output
321
+ ```
373
322
 
374
- **Q: How do I get just the file list without content?**
375
- A: Use `--dry-run` to preview without writing.
323
+ ---
376
324
 
377
- **Q: My binary files are being included?**
378
- A: repomeld uses intelligent binary detection. If something slips through, use `--ext` to filter specific extensions.
379
325
 
380
- ---
381
326
 
382
327
  ## Development
383
328
 
@@ -395,8 +340,14 @@ npm start -- --dry-run
395
340
  # Link for global testing
396
341
  npm link
397
342
  repomeld --help
343
+
344
+ # Run tests
345
+ npm test
398
346
  ```
399
347
 
348
+ ---
349
+
350
+
400
351
  ---
401
352
 
402
353
  ## Contributing
@@ -411,23 +362,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
411
362
 
412
363
  ---
413
364
 
414
- ## Changelog
415
-
416
- ### v2.0.4 (Current)
417
- - āœ… Added gitignore support with `ignore` package
418
- - āœ… Added zip backup feature (`repomeld_repomeld/` folder)
419
- - āœ… Added update notifications (non-intrusive)
420
- - āœ… Improved performance with async operations
421
- - āœ… Added progress indicator with ETA
422
- - āœ… Fixed Windows path compatibility
423
- - āœ… Improved binary detection
424
- - āœ… Added force-include for override scenarios
425
- - āœ… Removed `package.json` and `README.md` from default ignores
426
365
 
427
- ### v1.0.0
428
- - Initial release with basic functionality
429
-
430
- ---
431
366
 
432
367
  ## License
433
368
 
@@ -455,4 +390,4 @@ MIT Ā© [Susheel](mailto:susheelhbti@gmail.com)
455
390
 
456
391
  ---
457
392
 
458
- **Made with ā¤ļø for developers who need better context for AI tools**
393
+ **Made with ā¤ļø for developers who need better context for AI tools**
package/bin/cli.js CHANGED
@@ -1,499 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs").promises;
4
- const fsSync = require("fs");
5
- const path = require("path");
6
3
  const { program } = require("commander");
7
- const ignore = require("ignore");
8
- const { isBinaryFile } = require("isbinaryfile");
9
- const readline = require("readline");
10
- const os = require("os");
11
-
12
- const VERSION = "1.0.0";
13
- const PACKAGE_NAME = "repomeld";
14
-
15
- // Normalize paths for cross-platform compatibility
16
- const normalizePath = (p) => p.split(path.sep).join('/');
17
-
18
- const DEFAULT_IGNORE = [
19
- "node_modules",
20
- ".git",
21
- ".env",
22
- ".env.local",
23
- ".env.production",
24
- ".DS_Store",
25
- "package-lock.json",
26
- "yarn.lock",
27
- "pnpm-lock.yaml",
28
- ".next",
29
- ".nuxt",
30
- "dist",
31
- "build",
32
- ".cache"
33
-
34
- ];
35
-
36
- const LANGUAGE_MAP = {
37
- js: "javascript", jsx: "javascript", ts: "typescript", tsx: "typescript",
38
- py: "python", rb: "ruby", java: "java", cpp: "cpp", c: "c",
39
- cs: "csharp", go: "go", rs: "rust", php: "php", swift: "swift",
40
- kt: "kotlin", html: "html", css: "css", scss: "scss", json: "json",
41
- yaml: "yaml", yml: "yaml", md: "markdown", sh: "bash", bash: "bash",
42
- toml: "toml", xml: "xml", sql: "sql", graphql: "graphql",
43
- };
44
-
45
- async function loadIgnoreConfig() {
46
- const configPath = path.resolve(process.cwd(), "repomeld.ignore.json");
47
- try {
48
- const data = JSON.parse(await fs.readFile(configPath, "utf8"));
49
- if (Array.isArray(data.ignore)) return data.ignore;
50
- } catch {
51
- // File doesn't exist or invalid JSON, use defaults
52
- }
53
- return [];
54
- }
55
-
56
- function getLanguage(filePath) {
57
- const ext = path.extname(filePath).slice(1).toLowerCase();
58
- return LANGUAGE_MAP[ext] || "";
59
- }
60
-
61
- async function getAllFilesWithIgnore(dirPath, ig, forceIncludePatterns, rootDir = process.cwd(), progress = null) {
62
- const fileList = [];
63
- const stack = [{ dirPath, relativePath: '.' }];
64
-
65
- while (stack.length) {
66
- const { dirPath: currentDir, relativePath: currentRelative } = stack.pop();
67
-
68
- let entries;
69
- try {
70
- entries = await fs.readdir(currentDir, { withFileTypes: true });
71
- } catch (err) {
72
- continue;
73
- }
74
-
75
- for (const entry of entries) {
76
- const fullPath = path.join(currentDir, entry.name);
77
- const relativePath = path.join(currentRelative, entry.name);
78
- const normalizedPath = normalizePath(relativePath);
79
-
80
- // Check force-include first - these always go through
81
- const isForceIncluded = forceIncludePatterns && forceIncludePatterns.some(pattern =>
82
- normalizedPath.includes(pattern) || entry.name.includes(pattern)
83
- );
84
-
85
- // Only check ignore if not force-included
86
- if (!isForceIncluded && ig.ignores(normalizedPath)) {
87
- continue;
88
- }
89
-
90
- if (entry.isDirectory()) {
91
- stack.push({ dirPath: fullPath, relativePath });
92
- } else if (entry.isFile()) {
93
- fileList.push(fullPath);
94
- if (progress && fileList.length % 100 === 0) {
95
- progress.update(fileList.length);
96
- }
97
- }
98
- }
99
- }
100
-
101
- return fileList;
102
- }
103
-
104
- async function buildIgnoreFilter(options) {
105
- const ig = ignore();
106
-
107
- // Add default ignores
108
- ig.add(DEFAULT_IGNORE);
109
-
110
- // Add custom config ignores
111
- const customIgnores = await loadIgnoreConfig();
112
- ig.add(customIgnores);
113
-
114
- // Add CLI ignores
115
- if (options.ignore && options.ignore.length) {
116
- ig.add(options.ignore);
117
- }
118
-
119
- // Add .gitignore patterns if not disabled
120
- if (!options.noGitignore) {
121
- try {
122
- const gitignorePath = path.join(process.cwd(), ".gitignore");
123
- const gitignoreContent = await fs.readFile(gitignorePath, "utf8");
124
- ig.add(gitignoreContent);
125
- } catch {
126
- // No .gitignore file, ignore silently
127
- }
128
- }
129
-
130
- return { ig, forceInclude: options.forceInclude || null };
131
- }
132
-
133
- function matchesExtensions(filePath, exts) {
134
- if (!exts || exts.length === 0) return true;
135
- const ext = path.extname(filePath).slice(1).toLowerCase();
136
- return exts.map((e) => e.replace(/^\./, "").toLowerCase()).includes(ext);
137
- }
138
-
139
- function matchesPattern(filePath, patterns) {
140
- if (!patterns || patterns.length === 0) return false;
141
- const rel = path.relative(process.cwd(), filePath);
142
- return patterns.some((p) => rel.includes(p) || path.basename(filePath).includes(p));
143
- }
144
-
145
- function formatSize(bytes) {
146
- if (bytes < 1024) return `${bytes} B`;
147
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
148
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
149
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
150
- }
151
-
152
- function formatDuration(ms) {
153
- if (ms < 1000) return `${ms}ms`;
154
- const seconds = (ms / 1000).toFixed(1);
155
- return `${seconds}s`;
156
- }
157
-
158
- class ProgressIndicator {
159
- constructor(total, prefix = '') {
160
- this.total = total;
161
- this.prefix = prefix;
162
- this.current = 0;
163
- this.startTime = Date.now();
164
- }
165
-
166
- update(current) {
167
- this.current = current;
168
- this.render();
169
- }
170
-
171
- increment() {
172
- this.current++;
173
- this.render();
174
- }
175
-
176
- render() {
177
- const percent = (this.current / this.total * 100).toFixed(1);
178
- const elapsed = Date.now() - this.startTime;
179
- const rate = this.current / (elapsed / 1000);
180
- const eta = rate > 0 ? ((this.total - this.current) / rate).toFixed(0) : '?';
181
-
182
- process.stdout.write(`\r${this.prefix} ${this.current}/${this.total} files (${percent}%) | ${formatDuration(elapsed)} elapsed | ETA: ${eta}s`);
183
- }
184
-
185
- finish() {
186
- const elapsed = Date.now() - this.startTime;
187
- console.log(`\r${this.prefix} āœ… Completed ${this.current}/${this.total} files in ${formatDuration(elapsed)}`);
188
- }
189
- }
190
-
191
- function printBanner() {
192
- console.log(`
193
- ╔══════════════════════════════════════════════════════╗
194
- ā•‘ repomeld v${VERSION} ā•‘
195
- ā•‘ Meld your repo into one file šŸ”„ ā•‘
196
- ╠══════════════════════════════════════════════════════╣
197
- ā•‘ šŸ’¼ Author available for freelance & full-time work ā•‘
198
- ā•‘ šŸ“§ susheelhbti@gmail.com ā•‘
199
- ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•`);
200
- }
201
-
202
- function buildHeader(style, relativePath, filePath, lineCount, showMeta, stats) {
203
- const lang = getLanguage(filePath);
204
- const meta = showMeta ? ` [${lineCount} lines | ${formatSize(stats.size)}${lang ? " | " + lang : ""}]` : "";
205
-
206
- if (style === "markdown") {
207
- return `\n## šŸ“„ ${relativePath}${meta}\n\n\`\`\`${lang}\n`;
208
- }
209
- if (style === "minimal") {
210
- return `\n# ${relativePath}\n`;
211
- }
212
- const divider = "─".repeat(60);
213
- return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
214
- }
215
-
216
- function buildFooter(style) {
217
- if (style === "markdown") return "\n```\n";
218
- return "\n";
219
- }
220
-
221
- function buildTableOfContents(files, cwd) {
222
- let toc = "TABLE OF CONTENTS\n" + "═".repeat(60) + "\n";
223
- files.forEach((f, i) => {
224
- const rel = path.relative(cwd, f);
225
- toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
226
- });
227
- toc += "═".repeat(60) + "\n\n";
228
- return toc;
229
- }
230
-
231
- async function resolveOutputPath(desiredPath) {
232
- try {
233
- await fs.access(desiredPath);
234
- const ext = path.extname(desiredPath);
235
- const base = desiredPath.slice(0, desiredPath.length - ext.length);
236
- let counter = 2;
237
- while (true) {
238
- const candidate = `${base}__${counter}${ext}`;
239
- try {
240
- await fs.access(candidate);
241
- counter++;
242
- } catch {
243
- return { path: candidate, number: counter };
244
- }
245
- }
246
- } catch {
247
- return { path: desiredPath, number: null };
248
- }
249
- }
250
-
251
- async function isRepomeldOutput(filePath, baseOutputName) {
252
- const fileName = path.basename(filePath);
253
- const ext = path.extname(baseOutputName);
254
- const base = path.basename(baseOutputName, ext);
255
- const pattern = new RegExp(`^${escapeRegex(base)}(__\\d+)?${escapeRegex(ext)}$`);
256
- return pattern.test(fileName);
257
- }
258
-
259
- function escapeRegex(str) {
260
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
261
- }
262
-
263
- async function checkForUpdates() {
264
- return new Promise((resolve) => {
265
- const http = require('http');
266
- const options = {
267
- hostname: 'registry.npmjs.org',
268
- path: `/${PACKAGE_NAME}/latest`,
269
- method: 'GET',
270
- timeout: 3000
271
- };
272
-
273
- const req = http.request(options, (res) => {
274
- let data = '';
275
- res.on('data', chunk => data += chunk);
276
- res.on('end', () => {
277
- try {
278
- const json = JSON.parse(data);
279
- if (json.version && json.version !== VERSION) {
280
- resolve({ hasUpdate: true, latestVersion: json.version });
281
- } else {
282
- resolve({ hasUpdate: false });
283
- }
284
- } catch {
285
- resolve({ hasUpdate: false });
286
- }
287
- });
288
- });
289
-
290
- req.on('error', () => resolve({ hasUpdate: false }));
291
- req.on('timeout', () => {
292
- req.destroy();
293
- resolve({ hasUpdate: false });
294
- });
295
- req.end();
296
- });
297
- }
298
-
299
- function showUpdateMessage(currentVersion, latestVersion) {
300
- console.log(`\n${'═'.repeat(60)}`);
301
- console.log(` ⭐ New version available: ${currentVersion} → ${latestVersion}`);
302
- console.log(` šŸ“¦ Update with: npm install -g ${PACKAGE_NAME}@latest`);
303
- console.log(`${'═'.repeat(60)}\n`);
304
- }
305
-
306
- async function repomeld(options) {
307
- const startTime = Date.now();
308
- printBanner();
309
-
310
- const cwd = process.cwd();
311
-
312
- const { path: outputFile, number: outputNumber } = await resolveOutputPath(path.resolve(cwd, options.output));
313
- const outputBaseName = path.basename(options.output);
314
-
315
- const { ig, forceInclude } = await buildIgnoreFilter(options);
316
- const filterExts = options.ext || [];
317
- const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
318
- const headerStyle = options.style || "banner";
319
- const showMeta = !options.noMeta;
320
- const showToc = !options.noToc;
321
- const dryRun = options.dryRun || false;
322
- const include = options.include || [];
323
- const exclude = options.exclude || [];
324
- const linesBefore = parseInt(options.linesBefore) || 0;
325
- const linesAfter = parseInt(options.linesAfter) || 0;
326
-
327
- console.log(`\n šŸ“‚ Source : ${cwd}`);
328
- console.log(` šŸ“„ Output : ${path.relative(cwd, outputFile)}`);
329
- console.log(` šŸŽØ Style : ${headerStyle}`);
330
- if (!options.noGitignore) console.log(` šŸ“ .gitignore respected`);
331
- if (filterExts.length) console.log(` šŸ” Filter : .${filterExts.join(", .")}`);
332
- if (forceInclude && forceInclude.length) console.log(` šŸ“Œ Force : ${forceInclude.join(", ")}`);
333
- if (dryRun) console.log(` 🧪 Dry run : no file will be written`);
334
- console.log();
335
-
336
- console.log(` šŸ” Scanning files...`);
337
- const scanStartTime = Date.now();
338
- let allFiles = await getAllFilesWithIgnore(cwd, ig, forceInclude, cwd);
339
- console.log(` āœ… Found ${allFiles.length} files in ${formatDuration(Date.now() - scanStartTime)}`);
340
-
341
- // Apply additional filters
342
- if (filterExts.length) {
343
- allFiles = allFiles.filter(f => matchesExtensions(f, filterExts));
344
- }
345
- if (include.length) {
346
- allFiles = allFiles.filter(f => matchesPattern(f, include));
347
- }
348
- if (exclude.length) {
349
- allFiles = allFiles.filter(f => !matchesPattern(f, exclude));
350
- }
351
-
352
- // Remove repomeld output files
353
- const filteredFiles = [];
354
- for (const file of allFiles) {
355
- const isOutput = await isRepomeldOutput(file, outputBaseName);
356
- if (!isOutput) filteredFiles.push(file);
357
- }
358
- allFiles = filteredFiles;
359
-
360
- if (allFiles.length === 0) {
361
- console.log(" āš ļø No matching files found.\n");
362
- return;
363
- }
364
-
365
- console.log(` šŸ“ Processing ${allFiles.length} files...\n`);
366
-
367
- let combinedContent = "";
368
- let skipped = 0;
369
- let included = 0;
370
- let totalLines = 0;
371
- const includedFiles = [];
372
-
373
- const progress = new ProgressIndicator(allFiles.length, ' ');
374
-
375
- for (let i = 0; i < allFiles.length; i++) {
376
- const filePath = allFiles[i];
377
- const relativePath = path.relative(cwd, filePath);
378
-
379
- progress.update(i + 1);
380
-
381
- const isBinary = await isBinaryFile(filePath).catch(() => true);
382
- if (isBinary) {
383
- skipped++;
384
- continue;
385
- }
386
-
387
- const stats = await fs.stat(filePath);
388
- if (stats.size > maxFileSizeBytes) {
389
- skipped++;
390
- continue;
391
- }
392
-
393
- try {
394
- let content = await fs.readFile(filePath, "utf8");
395
-
396
- if (options.trim) {
397
- content = content.trim();
398
- }
399
-
400
- if (linesBefore > 0 || linesAfter > 0) {
401
- const lines = content.split("\n");
402
- const start = Math.min(linesBefore, lines.length);
403
- const end = linesAfter > 0 ? Math.max(0, lines.length - linesAfter) : lines.length;
404
- content = lines.slice(start, end).join("\n");
405
- }
406
-
407
- const lineCount = content.split("\n").length;
408
- totalLines += lineCount;
409
- includedFiles.push(filePath);
410
-
411
- combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta, stats);
412
- combinedContent += content;
413
- combinedContent += buildFooter(headerStyle);
414
-
415
- included++;
416
- } catch (err) {
417
- skipped++;
418
- }
419
- }
420
-
421
- progress.finish();
422
-
423
- // Build final output
424
- let finalOutput = "";
425
- const timestamp = new Date().toISOString();
426
- finalOutput += `# Generated by repomeld v${VERSION}\n`;
427
- finalOutput += `# Date : ${timestamp}\n`;
428
- finalOutput += `# Source : ${cwd}\n`;
429
- finalOutput += `# Files : ${included}\n`;
430
- finalOutput += `# Lines : ${totalLines}\n`;
431
- finalOutput += `# Author : susheelhbti@gmail.com — available for freelance & full-time work\n\n`;
432
-
433
- if (showToc && includedFiles.length > 0) {
434
- finalOutput += buildTableOfContents(includedFiles, cwd);
435
- }
436
-
437
- finalOutput += combinedContent;
438
-
439
- if (!dryRun && includedFiles.length > 0) {
440
- await fs.writeFile(outputFile, finalOutput, "utf8");
441
- }
442
-
443
- const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
444
- const totalTime = Date.now() - startTime;
445
-
446
- console.log(`
447
- ✨ repomeld complete!
448
- ─────────────────────────────────────────────────
449
- āœ… Included : ${included} files
450
- ā­ Skipped : ${skipped} files
451
- šŸ“ Lines : ${totalLines}
452
- šŸ’¾ Size : ${outputSize}
453
- ā±ļø Time : ${formatDuration(totalTime)}
454
- šŸ“„ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run — not written)" : ""}
455
- ─────────────────────────────────────────────────`);
456
-
457
- console.log(`\n šŸ’¼ Need a developer? susheelhbti@gmail.com`);
458
-
459
- // Check for updates (non-blocking, no prompt)
460
- if (!options.noUpdateCheck) {
461
- const updateInfo = await checkForUpdates();
462
- if (updateInfo.hasUpdate) {
463
- showUpdateMessage(VERSION, updateInfo.latestVersion);
464
- }
465
- }
466
- }
467
-
468
- // ─── CLI Definition ───────────────────────────────────────────
4
+ const { repomeld } = require("../src/index");
5
+ const { VERSION } = require("../src/utils/constants");
469
6
 
470
7
  program
471
8
  .name("repomeld")
472
- .description("Meld your entire repo into a single file — perfect for AI context, code reviews & sharing")
9
+ .description("Meld your entire repo into a single file — perfect for AI context & code reviews")
473
10
  .version(VERSION)
474
-
475
- .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
11
+ .option("-o, --output <filename>", "Output filename", "repomeld_output.txt")
476
12
  .option("-e, --ext <exts...>", "Only include specific extensions")
477
- .option("--include <patterns...>", "Only include files matching patterns")
13
+ .option("--include <patterns...>", "Force include files matching patterns")
478
14
  .option("--exclude <patterns...>", "Exclude files matching patterns")
479
- .option("-i, --ignore <names...>", "Extra folders/files to ignore")
480
- .option("--force-include <names...>", "Force-include files even if ignored")
481
- .option("--max-size <kb>", "Skip files larger than N KB", "500")
482
- .option("--no-gitignore", "Ignore .gitignore file")
15
+ .option("-i, --ignore <names...>", "Additional ignore patterns")
16
+ .option("--force-include <names...>", "Force include even if ignored")
17
+ .option("--max-size <kb>", "Maximum file size in KB", "500")
18
+ .option("--no-gitignore", "Don't respect .gitignore")
483
19
  .option("-s, --style <style>", "Header style: banner | markdown | minimal", "banner")
484
20
  .option("--no-toc", "Disable table of contents")
485
21
  .option("--no-meta", "Hide file metadata")
486
- .option("--trim", "Trim leading/trailing whitespace per file")
487
- .option("--lines-before <n>", "Skip first N lines of each file", parseInt)
488
- .option("--lines-after <n>", "Skip last N lines of each file", parseInt)
489
- .option("--dry-run", "Preview without writing output")
490
- .option("--no-update-check", "Skip checking for updates")
491
-
22
+ .option("--trim", "Trim whitespace from each file")
23
+ .option("--lines-before <n>", "Skip first N lines", parseInt)
24
+ .option("--lines-after <n>", "Skip last N lines", parseInt)
25
+ .option("--dry-run", "Preview without writing files")
26
+ .option("--no-backup", "Skip backup zip creation")
27
+ .option("--no-update-check", "Skip update check")
492
28
  .action(async (options) => {
493
29
  try {
494
30
  await repomeld(options);
495
31
  } catch (error) {
496
- console.error(`\n āŒ Error: ${error.message}`);
32
+ console.error(`\nāŒ Error: ${error.message}`);
497
33
  if (process.env.DEBUG) console.error(error);
498
34
  process.exit(1);
499
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repomeld",
3
- "version": "2.0.5",
3
+ "version": "3.0.0",
4
4
  "description": "Meld your entire repo into a single file — perfect for AI context, code reviews & sharing",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -36,9 +36,10 @@
36
36
  },
37
37
  "homepage": "https://github.com/sakshsky/repomeld#readme",
38
38
  "dependencies": {
39
+ "archiver": "^7.0.1",
39
40
  "commander": "^11.1.0",
40
41
  "ignore": "^5.3.2",
41
- "isbinaryfile": "^5.0.0"
42
+ "isbinaryfile": "^5.0.7"
42
43
  },
43
44
  "devDependencies": {
44
45
  "eslint": "^8.56.0",