repomeld 2.0.4 → 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 +206 -27
  2. package/bin/cli.js +29 -373
  3. package/package.json +33 -6
package/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  > Meld your entire repo into a single file — perfect for AI context, code reviews & sharing.
4
4
 
5
+ [![npm version](https://badge.fury.io/js/repomeld.svg)](https://www.npmjs.com/package/repomeld)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
5
8
  ---
6
9
 
7
10
  > ## šŸ’¼ Open to Work
@@ -10,12 +13,35 @@
10
13
 
11
14
  ---
12
15
 
16
+ ## ✨ Features
17
+
18
+ - šŸš€ **Fast & Efficient** - Async scanning with real-time progress and binary caching
19
+ - šŸŽØ **Multiple Styles** - Banner, Markdown, or Minimal output
20
+ - šŸ” **Smart Filtering** - Extension, pattern, and size-based filtering
21
+ - šŸ“ **Gitignore Support** - Respects your .gitignore rules automatically
22
+ - šŸ’¾ **Binary Detection** - Intelligent caching for binary file detection
23
+ - šŸ“¦ **Single File Output** - Perfect for AI context windows
24
+ - šŸ”„ **Auto-Numbering** - Never overwrites existing files
25
+ - šŸ’æ **Zip Backup** - Creates auto-numbered backups in `repomeld_zips/` folder
26
+ - šŸ”” **Update Notifications** - Non-intrusive version checking
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
30
+
31
+ ---
32
+
13
33
  ## Install
14
34
 
15
35
  ```bash
16
36
  npm install -g repomeld
17
37
  ```
18
38
 
39
+ Or use without installing:
40
+
41
+ ```bash
42
+ npx repomeld
43
+ ```
44
+
19
45
  ---
20
46
 
21
47
  ## Quick Start
@@ -25,7 +51,7 @@ cd your-project
25
51
  repomeld
26
52
  ```
27
53
 
28
- That's it. repomeld walks your project, skips noise (node_modules, lock files, build folders, etc.) 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.
29
55
 
30
56
  ---
31
57
 
@@ -37,9 +63,13 @@ Every time you run repomeld it creates a **new numbered file** so previous runs
37
63
  repomeld_output.txt ← first run
38
64
  repomeld_output__2.txt ← second run
39
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
40
70
  ```
41
71
 
42
- 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.
43
73
 
44
74
  ---
45
75
 
@@ -76,6 +106,8 @@ Filtering:
76
106
  --max-size <kb> Skip files larger than N kilobytes
77
107
  Default: 500
78
108
 
109
+ --no-gitignore Ignore .gitignore file (include everything)
110
+
79
111
  Formatting:
80
112
  -s, --style <style> Header style for each file block:
81
113
  banner — clear dividers with file info (default)
@@ -92,6 +124,8 @@ Advanced:
92
124
  --lines-before <n> Skip the first N lines of every file
93
125
  --lines-after <n> Skip the last N lines of every file
94
126
  --dry-run Preview which files would be included — nothing is written
127
+ --no-backup Skip creating backup zip file
128
+ --no-update-check Skip checking for updates
95
129
  ```
96
130
 
97
131
  ---
@@ -120,6 +154,9 @@ repomeld --dry-run
120
154
  # Ignore extra folders on top of defaults
121
155
  repomeld --ignore coverage logs tmp
122
156
 
157
+ # Respect gitignore (default) or ignore it
158
+ repomeld --no-gitignore # include everything
159
+
123
160
  # Only small files — skip anything over 100 KB
124
161
  repomeld --max-size 100
125
162
 
@@ -131,6 +168,9 @@ repomeld --no-toc --no-meta
131
168
 
132
169
  # Combine filters
133
170
  repomeld --ext php --include Controllers --exclude test --style markdown
171
+
172
+ # Skip backup creation
173
+ repomeld --no-backup
134
174
  ```
135
175
 
136
176
  ---
@@ -147,68 +187,207 @@ repomeld automatically skips these so your output stays clean:
147
187
  | Lock files | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` |
148
188
  | Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `.cache/` |
149
189
  | OS files | `.DS_Store` |
150
- | Project meta | `package.json`, `README.md` |
151
190
  | repomeld output | `repomeld_output.txt` and all `repomeld_output__N.txt` files |
152
191
 
153
- You can add your own permanent ignore rules via a `repomeld.ignore.json` file (see below).
192
+ **Note:** `package.json` and `README.md` are **NOT** ignored by default — they contain important context for AI tools and code reviews.
154
193
 
155
194
  ---
156
195
 
157
- ## Custom Ignore File
196
+ ## Custom Ignore Rules
158
197
 
159
- Create a `repomeld.ignore.json` in your project root:
198
+ ### Method 1: repomeld.ignore.json
199
+
200
+ Create a `repomeld.ignore.json` in your project root for comprehensive ignore patterns:
160
201
 
161
202
  ```json
162
203
  {
204
+ "_comment": "repomeld.ignore.json — auto-ignored files and folders",
163
205
  "ignore": [
164
206
  "coverage",
165
207
  "logs",
166
208
  "tmp",
167
- "*.min.js"
209
+ "*.min.js",
210
+ "**/generated/**",
211
+ "vendor/**/bootstrap*",
212
+ "**/jquery*",
213
+ "**/fontawesome*"
168
214
  ]
169
215
  }
170
216
  ```
171
217
 
172
218
  These are merged with the defaults every time repomeld runs.
173
219
 
220
+ ### Method 2: .gitignore
221
+
222
+ repomeld automatically respects your `.gitignore` file. Use `--no-gitignore` to override.
223
+
224
+ ### Method 3: CLI --ignore
225
+
226
+ Override on the command line:
227
+
228
+ ```bash
229
+ repomeld --ignore temp logs "*.tmp"
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Backup Zip Files
235
+
236
+ When repomeld runs, it automatically creates a backup zip file in the `repomeld_zips/` folder:
237
+
238
+ ```
239
+ repomeld_output.txt
240
+ repomeld_zips/
241
+ └── repomeld_output.zip ← contains all included files + output
242
+
243
+ repomeld_output__2.txt
244
+ repomeld_zips/
245
+ └── repomeld_output__2.zip ← corresponding backup
246
+
247
+ repomeld_output__3.txt
248
+ repomeld_zips/
249
+ └── repomeld_output__3.zip ← and so on...
250
+ ```
251
+
252
+ The zip file contains:
253
+ - All source files included in the run (preserving folder structure)
254
+ - The repomeld output file itself
255
+
256
+ To disable backups: `repomeld --no-backup`
257
+
258
+ ---
259
+
260
+
261
+ ## Performance Optimizations
262
+
263
+ repomeld is optimized for large codebases:
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
271
+
272
+ Example output:
273
+ ```
274
+ šŸ” Scanning files...
275
+ āœ… Found 2453 files in 1.2s
276
+
277
+ šŸ“ Processing 2453 files...
278
+
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.
286
+ ```
287
+
174
288
  ---
175
289
 
176
- ## Output Format
290
+ ## Use Cases
177
291
 
178
- Each run produces a file like this:
292
+ ### šŸ¤– AI Context Preparation
293
+ ```bash
294
+ repomeld --ext js ts jsx py --style markdown --max-size 200
295
+ ```
179
296
 
297
+ ### šŸ“‹ Code Review
298
+ ```bash
299
+ repomeld --include src/ --exclude test --style minimal --no-meta
180
300
  ```
181
- # Generated by repomeld v1.0.0
182
- # Date : 2025-04-20T10:00:00.000Z
183
- # Source : /your/project
184
- # Files : 12
185
- # Lines : 847
186
301
 
187
- TABLE OF CONTENTS
188
- ════════════════════════════════════════════════════════════
189
- 1. src/index.js
190
- 2. src/utils.js
191
- 3. src/config.js
192
- ...
193
- ════════════════════════════════════════════════════════════
302
+ ### šŸ’¾ Full Project Backup
303
+ ```bash
304
+ repomeld --force-include . --max-size 10000 --no-toc --no-meta
305
+ ```
194
306
 
195
- ────────────────────────────────────────────────────────────
196
- FILE: src/index.js [120 lines | 3.2 KB | javascript]
197
- ────────────────────────────────────────────────────────────
307
+ ### šŸ“š Documentation Generation
308
+ ```bash
309
+ repomeld --ext md --include docs --style markdown --output documentation.md
310
+ ```
198
311
 
199
- ... file contents ...
312
+ ### šŸ” Debug Specific Feature
313
+ ```bash
314
+ repomeld --include feature-name --ext js css --output feature-context.txt
315
+ ```
316
+
317
+ ### šŸ—ŗļø Dependency Analysis
318
+ ```bash
319
+ repomeld --include src --style markdown --output analysis.md
320
+ # Then render the Mermaid graph in the output
200
321
  ```
201
322
 
202
- With `--style markdown` each file becomes a fenced code block — paste directly into Claude, ChatGPT, or any AI tool.
323
+ ---
324
+
325
+
326
+
327
+ ## Development
328
+
329
+ ```bash
330
+ # Clone the repo
331
+ git clone https://github.com/susheel/repomeld.git
332
+ cd repomeld
333
+
334
+ # Install dependencies
335
+ npm install
336
+
337
+ # Run locally
338
+ npm start -- --dry-run
339
+
340
+ # Link for global testing
341
+ npm link
342
+ repomeld --help
343
+
344
+ # Run tests
345
+ npm test
346
+ ```
203
347
 
204
348
  ---
205
349
 
350
+
351
+ ---
352
+
353
+ ## Contributing
354
+
355
+ Contributions are welcome! Please feel free to submit a Pull Request.
356
+
357
+ 1. Fork the repository
358
+ 2. Create your feature branch (`git checkout -b feature/amazing`)
359
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
360
+ 4. Push to the branch (`git push origin feature/amazing`)
361
+ 5. Open a Pull Request
362
+
363
+ ---
364
+
365
+
366
+
206
367
  ## License
207
368
 
208
- MIT
369
+ MIT Ā© [Susheel](mailto:susheelhbti@gmail.com)
370
+
371
+ ---
372
+
373
+ ## Support & Contact
374
+
375
+ - šŸ› **Issues**: [GitHub Issues](https://github.com/susheel/repomeld/issues)
376
+ - šŸ“§ **Email**: [susheelhbti@gmail.com](mailto:susheelhbti@gmail.com)
377
+ - šŸ’¼ **Hire Me**: Available for freelance and full-time opportunities
209
378
 
210
379
  ---
211
380
 
212
381
  > ## šŸ’¼ Hire the Author
213
382
  > Built by a developer available for **freelance and full-time opportunities**.
214
383
  > Got a project? Let's talk — šŸ“§ **[susheelhbti@gmail.com](mailto:susheelhbti@gmail.com)**
384
+
385
+ ---
386
+
387
+ ## Star History
388
+
389
+ [![Star History Chart](https://api.star-history.com/svg?repos=susheel/repomeld&type=Date)](https://star-history.com/#susheel/repomeld&Date)
390
+
391
+ ---
392
+
393
+ **Made with ā¤ļø for developers who need better context for AI tools**
package/bin/cli.js CHANGED
@@ -1,382 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
5
3
  const { program } = require("commander");
6
-
7
- const VERSION = "1.0.0";
8
-
9
- const DEFAULT_IGNORE = [
10
- "node_modules",
11
- ".git",
12
- ".env",
13
- ".env.local",
14
- ".env.production",
15
- ".DS_Store",
16
- "package-lock.json",
17
- "yarn.lock",
18
- "pnpm-lock.yaml",
19
- ".next",
20
- ".nuxt",
21
- "dist",
22
- "build",
23
- ".cache",
24
- "package.json",
25
- "README.md",
26
- ];
27
-
28
- function loadIgnoreConfig() {
29
- const configPath = path.resolve(process.cwd(), "repomeld.ignore.json");
30
- const pkgDir = path.resolve(__dirname, "..", "repomeld.ignore.json");
31
- for (const loc of [configPath, pkgDir]) {
32
- if (fs.existsSync(loc)) {
33
- try {
34
- const data = JSON.parse(fs.readFileSync(loc, "utf8"));
35
- if (Array.isArray(data.ignore)) {
36
- return data.ignore;
37
- }
38
- } catch {
39
- console.warn(` āš ļø Could not parse ${loc}, using defaults.`);
40
- }
41
- }
42
- }
43
- return [];
44
- }
45
-
46
- const IGNORE_FROM_CONFIG = loadIgnoreConfig();
47
-
48
- const LANGUAGE_MAP = {
49
- js: "javascript", jsx: "javascript", ts: "typescript", tsx: "typescript",
50
- py: "python", rb: "ruby", java: "java", cpp: "cpp", c: "c",
51
- cs: "csharp", go: "go", rs: "rust", php: "php", swift: "swift",
52
- kt: "kotlin", html: "html", css: "css", scss: "scss", json: "json",
53
- yaml: "yaml", yml: "yaml", md: "markdown", sh: "bash", bash: "bash",
54
- toml: "toml", xml: "xml", sql: "sql", graphql: "graphql",
55
- };
56
-
57
- function getLanguage(filePath) {
58
- const ext = path.extname(filePath).slice(1).toLowerCase();
59
- return LANGUAGE_MAP[ext] || "";
60
- }
61
-
62
- function getAllFiles(dirPath, ignoreList, fileList = []) {
63
- let entries;
64
- try {
65
- entries = fs.readdirSync(dirPath, { withFileTypes: true });
66
- } catch {
67
- return fileList;
68
- }
69
-
70
- for (const entry of entries) {
71
- const fullPath = path.join(dirPath, entry.name);
72
- const relativePath = path.relative(process.cwd(), fullPath);
73
-
74
- if (
75
- ignoreList.some(
76
- (ig) => entry.name === ig || relativePath.startsWith(ig + path.sep) || relativePath === ig
77
- )
78
- ) {
79
- continue;
80
- }
81
-
82
- if (entry.isDirectory()) {
83
- getAllFiles(fullPath, ignoreList, fileList);
84
- } else if (entry.isFile()) {
85
- fileList.push(fullPath);
86
- }
87
- }
88
-
89
- return fileList;
90
- }
91
-
92
- function isBinaryFile(filePath) {
93
- try {
94
- const buffer = Buffer.alloc(512);
95
- const fd = fs.openSync(filePath, "r");
96
- const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
97
- fs.closeSync(fd);
98
- for (let i = 0; i < bytesRead; i++) {
99
- if (buffer[i] === 0) return true;
100
- }
101
- return false;
102
- } catch {
103
- return true;
104
- }
105
- }
106
-
107
- function matchesExtensions(filePath, exts) {
108
- if (!exts || exts.length === 0) return true;
109
- const ext = path.extname(filePath).slice(1).toLowerCase();
110
- return exts.map((e) => e.replace(/^\./, "").toLowerCase()).includes(ext);
111
- }
112
-
113
- function matchesPattern(filePath, patterns) {
114
- if (!patterns || patterns.length === 0) return false;
115
- const rel = path.relative(process.cwd(), filePath);
116
- return patterns.some((p) => rel.includes(p) || path.basename(filePath).includes(p));
117
- }
118
-
119
- function formatSize(bytes) {
120
- if (bytes < 1024) return `${bytes} B`;
121
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
122
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
123
- }
124
-
125
- function printBanner() {
126
- console.log(`
127
- ╔══════════════════════════════════════════════════════╗
128
- ā•‘ repomeld v${VERSION} ā•‘
129
- ā•‘ Meld your repo into one file šŸ”„ ā•‘
130
- ╠══════════════════════════════════════════════════════╣
131
- ā•‘ šŸ’¼ Author available for freelance & full-time work ā•‘
132
- ā•‘ šŸ“§ susheelhbti@gmail.com ā•‘
133
- ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•`);
134
- }
135
-
136
- function buildHeader(style, relativePath, filePath, lineCount, showMeta) {
137
- const lang = getLanguage(filePath);
138
- const size = formatSize(fs.statSync(filePath).size);
139
- const meta = showMeta ? ` [${lineCount} lines | ${size}${lang ? " | " + lang : ""}]` : "";
140
-
141
- if (style === "markdown") {
142
- return `\n## šŸ“„ ${relativePath}${meta}\n\n\`\`\`${lang}\n`;
143
- }
144
- if (style === "minimal") {
145
- return `\n# ${relativePath}\n`;
146
- }
147
- // default: banner style
148
- const divider = "─".repeat(60);
149
- return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
150
- }
151
-
152
- function buildFooter(style) {
153
- if (style === "markdown") return "\n```\n";
154
- return "\n";
155
- }
156
-
157
- function buildTableOfContents(files, cwd) {
158
- let toc = "TABLE OF CONTENTS\n" + "═".repeat(60) + "\n";
159
- files.forEach((f, i) => {
160
- const rel = path.relative(cwd, f);
161
- toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
162
- });
163
- toc += "═".repeat(60) + "\n\n";
164
- return toc;
165
- }
166
-
167
- /**
168
- * Given a desired output path like "repomeld_output.txt", returns a
169
- * numbered path that does not yet exist, e.g. "repomeld_output__2.txt".
170
- * If the base name doesn't exist yet, returns it unchanged.
171
- */
172
- function resolveOutputPath(desiredPath) {
173
- if (!fs.existsSync(desiredPath)) return desiredPath;
174
-
175
- const ext = path.extname(desiredPath);
176
- const base = desiredPath.slice(0, desiredPath.length - ext.length);
177
-
178
- let counter = 2;
179
- while (true) {
180
- const candidate = `${base}__${counter}${ext}`;
181
- if (!fs.existsSync(candidate)) return candidate;
182
- counter++;
183
- }
184
- }
185
-
186
- /**
187
- * Returns true if the given filePath looks like a repomeld output file
188
- * (matches the base name pattern with optional __N suffix).
189
- */
190
- function isRepomeldOutput(filePath, baseOutputName) {
191
- const fileName = path.basename(filePath);
192
- const ext = path.extname(baseOutputName);
193
- const base = path.basename(baseOutputName, ext);
194
- const pattern = new RegExp(`^${escapeRegex(base)}(__\\d+)?${escapeRegex(ext)}$`);
195
- return pattern.test(fileName);
196
- }
197
-
198
- function escapeRegex(str) {
199
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
200
- }
201
-
202
- function repomeld(options) {
203
- printBanner();
204
-
205
- const cwd = process.cwd();
206
-
207
- // Resolve a unique (non-colliding) output path
208
- const desiredOutput = path.resolve(cwd, options.output);
209
- const outputFile = resolveOutputPath(desiredOutput);
210
- const outputBaseName = path.basename(options.output);
211
-
212
- const forceInclude = options.forceInclude || [];
213
- const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
214
- const ignoreList = forceInclude.length
215
- ? rawIgnore.filter((ig) => !forceInclude.some((fi) => ig.includes(fi) || fi.includes(ig)))
216
- : rawIgnore;
217
- const filterExts = options.ext || [];
218
- const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
219
- const headerStyle = options.style || "banner";
220
- const showMeta = !options.noMeta;
221
- const showToc = !options.noToc;
222
- const dryRun = options.dryRun || false;
223
- const include = options.include || [];
224
- const exclude = options.exclude || [];
225
- const linesBefore = parseInt(options.linesBefore) || 0;
226
- const linesAfter = parseInt(options.linesAfter) || 0;
227
-
228
- console.log(`\n šŸ“‚ Source : ${cwd}`);
229
- console.log(` šŸ“„ Output : ${path.relative(cwd, outputFile)}`);
230
- console.log(` šŸŽØ Style : ${headerStyle}`);
231
- if (filterExts.length) console.log(` šŸ” Filter : .${filterExts.join(", .")}`);
232
- if (forceInclude.length) console.log(` šŸ“Œ Force : ${forceInclude.join(", ")}`);
233
- if (dryRun) console.log(` 🧪 Dry run : no file will be written`);
234
- console.log();
235
-
236
- let allFiles = getAllFiles(cwd, ignoreList);
237
-
238
- // Filter by extension
239
- if (filterExts.length) {
240
- allFiles = allFiles.filter((f) => matchesExtensions(f, filterExts));
241
- }
242
-
243
- // Include pattern filter
244
- if (include.length) {
245
- allFiles = allFiles.filter((f) => matchesPattern(f, include));
246
- }
247
-
248
- // Exclude pattern filter
249
- if (exclude.length) {
250
- allFiles = allFiles.filter((f) => !matchesPattern(f, exclude));
251
- }
252
-
253
- // Remove ALL repomeld output files (current run + any previous numbered ones)
254
- allFiles = allFiles.filter((f) => !isRepomeldOutput(f, outputBaseName));
255
-
256
- if (allFiles.length === 0) {
257
- console.log(" āš ļø No matching files found.\n");
258
- return;
259
- }
260
-
261
- let combinedContent = "";
262
- let skipped = 0;
263
- let included = 0;
264
- let totalLines = 0;
265
- const includedFiles = [];
266
-
267
- for (const filePath of allFiles) {
268
- const relativePath = path.relative(cwd, filePath);
269
-
270
- if (isBinaryFile(filePath)) {
271
- console.log(` ā­ Binary : ${relativePath}`);
272
- skipped++;
273
- continue;
274
- }
275
-
276
- const stat = fs.statSync(filePath);
277
- if (stat.size > maxFileSizeBytes) {
278
- console.log(` ā­ Too large: ${relativePath} (${formatSize(stat.size)})`);
279
- skipped++;
280
- continue;
281
- }
282
-
283
- try {
284
- let content = fs.readFileSync(filePath, "utf8");
285
-
286
- if (options.trim) {
287
- content = content.trim();
288
- }
289
-
290
- if (linesBefore > 0 || linesAfter > 0) {
291
- const lines = content.split("\n");
292
- const start = linesBefore;
293
- const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
294
- content = lines.slice(start, end).join("\n");
295
- }
296
-
297
- const lineCount = content.split("\n").length;
298
- totalLines += lineCount;
299
- includedFiles.push(filePath);
300
-
301
- combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
302
- combinedContent += content;
303
- combinedContent += buildFooter(headerStyle);
304
-
305
- console.log(` āœ… ${relativePath}`);
306
- included++;
307
- } catch (err) {
308
- console.log(` āŒ Error: ${relativePath} — ${err.message}`);
309
- skipped++;
310
- }
311
- }
312
-
313
- // Build final output
314
- let finalOutput = "";
315
-
316
- const timestamp = new Date().toISOString();
317
- finalOutput += `# Generated by repomeld v${VERSION}\n`;
318
- finalOutput += `# Date : ${timestamp}\n`;
319
- finalOutput += `# Source : ${cwd}\n`;
320
- finalOutput += `# Files : ${included}\n`;
321
- finalOutput += `# Lines : ${totalLines}\n`;
322
- finalOutput += `# Author : susheelhbti@gmail.com — available for freelance & full-time work\n\n`;
323
-
324
- if (showToc) {
325
- finalOutput += buildTableOfContents(includedFiles, cwd);
326
- }
327
-
328
- finalOutput += combinedContent;
329
-
330
- if (!dryRun) {
331
- fs.writeFileSync(outputFile, finalOutput, "utf8");
332
- }
333
-
334
- const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
335
-
336
- console.log(`
337
- ✨ repomeld complete!
338
- ─────────────────────────────────────────────────
339
- āœ… Included : ${included} files
340
- ā­ Skipped : ${skipped} files
341
- šŸ“ Lines : ${totalLines}
342
- šŸ’¾ Size : ${outputSize}
343
- šŸ“„ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run — not written)" : ""}
344
- ─────────────────────────────────────────────────
345
- šŸ’¼ Need a developer? susheelhbti@gmail.com
346
- `);
347
- }
348
-
349
- // ─── CLI Definition ───────────────────────────────────────────
4
+ const { repomeld } = require("../src/index");
5
+ const { VERSION } = require("../src/utils/constants");
350
6
 
351
7
  program
352
8
  .name("repomeld")
353
- .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")
354
10
  .version(VERSION)
355
-
356
- // Output
357
- .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
358
-
359
- // Filtering
360
- .option("-e, --ext <exts...>", "Only include specific extensions e.g. --ext js ts jsx")
361
- .option("--include <patterns...>", "Only include files matching patterns e.g. --include src/")
362
- .option("--exclude <patterns...>", "Exclude files matching patterns e.g. --exclude test spec")
363
- .option("-i, --ignore <names...>", "Extra folders/files to ignore e.g. --ignore dist .next")
364
- .option("--force-include <names...>", "Force-include files ignored by default e.g. --force-include vendor")
365
- .option("--max-size <kb>", "Skip files larger than N KB (default 500)","500")
366
-
367
- // Formatting
368
- .option("-s, --style <style>", "Header style: banner | markdown | minimal (default: banner)", "banner")
369
- .option("--no-toc", "Disable table of contents")
370
- .option("--no-meta", "Hide file metadata (lines, size, lang)")
371
- .option("--trim", "Trim leading/trailing whitespace per file")
372
-
373
- // Advanced
374
- .option("--lines-before <n>", "Skip first N lines of each file")
375
- .option("--lines-after <n>", "Skip last N lines of each file")
376
- .option("--dry-run", "Preview what would be included — don't write output")
377
-
378
- .action((options) => {
379
- repomeld(options);
11
+ .option("-o, --output <filename>", "Output filename", "repomeld_output.txt")
12
+ .option("-e, --ext <exts...>", "Only include specific extensions")
13
+ .option("--include <patterns...>", "Force include files matching patterns")
14
+ .option("--exclude <patterns...>", "Exclude files matching patterns")
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")
19
+ .option("-s, --style <style>", "Header style: banner | markdown | minimal", "banner")
20
+ .option("--no-toc", "Disable table of contents")
21
+ .option("--no-meta", "Hide file metadata")
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")
28
+ .action(async (options) => {
29
+ try {
30
+ await repomeld(options);
31
+ } catch (error) {
32
+ console.error(`\nāŒ Error: ${error.message}`);
33
+ if (process.env.DEBUG) console.error(error);
34
+ process.exit(1);
35
+ }
380
36
  });
381
37
 
382
- program.parse(process.argv);
38
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "repomeld",
3
- "version": "2.0.4",
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": {
7
7
  "repomeld": "bin/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node bin/cli.js"
10
+ "start": "node bin/cli.js",
11
+ "test": "node bin/cli.js --dry-run",
12
+ "prepublishOnly": "npm run test",
13
+ "postinstall": "node -e \"console.log('\\nšŸ“¦ repomeld installed successfully! Run: repomeld --help\\n')\""
11
14
  },
12
15
  "keywords": [
13
16
  "cli",
@@ -18,15 +21,39 @@
18
21
  "concat",
19
22
  "ai-context",
20
23
  "code-review",
21
- "repomeld"
24
+ "repomeld",
25
+ "git",
26
+ "repository"
22
27
  ],
23
- "author": "",
28
+ "author": "Susheel <susheelhbti@gmail.com>",
24
29
  "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/sakshsky/repomeld.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/sakshsky/repomeld/issues"
36
+ },
37
+ "homepage": "https://github.com/sakshsky/repomeld#readme",
25
38
  "dependencies": {
26
- "commander": "^11.0.0"
27
-
39
+ "archiver": "^7.0.1",
40
+ "commander": "^11.1.0",
41
+ "ignore": "^5.3.2",
42
+ "isbinaryfile": "^5.0.7"
43
+ },
44
+ "devDependencies": {
45
+ "eslint": "^8.56.0",
46
+ "prettier": "^3.1.1"
28
47
  },
29
48
  "engines": {
30
49
  "node": ">=14.0.0"
50
+ },
51
+ "files": [
52
+ "bin/",
53
+ "README.md",
54
+ "LICENSE"
55
+ ],
56
+ "publishConfig": {
57
+ "access": "public"
31
58
  }
32
59
  }