repomeld 2.0.2 โ 2.0.4
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 +118 -251
- package/bin/cli.js +208 -310
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Meld your entire repo into a single file โ perfect for AI context, code reviews & sharing.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> ## ๐ผ Open to Work
|
|
8
|
+
> **The author is available for freelance & full-time work.**
|
|
9
|
+
> ๐ง [susheelhbti@gmail.com](mailto:susheelhbti@gmail.com) โ reach out anytime!
|
|
6
10
|
|
|
7
11
|
---
|
|
8
12
|
|
|
@@ -21,320 +25,181 @@ cd your-project
|
|
|
21
25
|
repomeld
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
28
|
-
โ repomeld v1.2.0 โ
|
|
29
|
-
โ Meld your repo into one file ๐ฅ โ
|
|
30
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
31
|
-
|
|
32
|
-
๐ผ Author susheelhbti@gmail.com is open to freelance & full-time work
|
|
33
|
-
|
|
34
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
35
|
-
โ Interactive setup โ answer 7 quick questions โ
|
|
36
|
-
โ Press Enter to accept the [default] value โ
|
|
37
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
38
|
-
|
|
39
|
-
๐ 1/7 Output filename? [repomeld_output.txt]
|
|
40
|
-
>
|
|
41
|
-
|
|
42
|
-
๐จ 2/7 Header style?
|
|
43
|
-
1) banner โ clear dividers with file info (default)
|
|
44
|
-
2) markdown โ great for pasting into AI prompts
|
|
45
|
-
3) minimal โ filename only, no extra formatting
|
|
46
|
-
>
|
|
47
|
-
|
|
48
|
-
๐ 3/7 Filter to specific file extensions?
|
|
49
|
-
e.g. js ts php py (leave blank = include ALL files)
|
|
50
|
-
>
|
|
51
|
-
|
|
52
|
-
๐๏ธ 4/7 Enable structure-only mode? (y/n) [n]
|
|
53
|
-
Strips function bodies โ keeps class names + signatures only.
|
|
54
|
-
Cuts a 400-line file to ~30 lines. Perfect for AI architecture review.
|
|
55
|
-
>
|
|
56
|
-
|
|
57
|
-
๐ 5/7 Generate a code review graph? (y/n) [n]
|
|
58
|
-
Adds a Mermaid call graph at the top of the output.
|
|
59
|
-
AI models read Mermaid natively โ shows which files connect to which.
|
|
60
|
-
>
|
|
61
|
-
|
|
62
|
-
๐ 6/7 Include a table of contents? (y/n) [y]
|
|
63
|
-
>
|
|
64
|
-
|
|
65
|
-
๐งช 7/7 Dry run only? (y/n) [n]
|
|
66
|
-
Preview which files would be included without writing anything.
|
|
67
|
-
>
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
After answering, it shows a summary and runs:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
74
|
-
โ Your configuration โ
|
|
75
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
76
|
-
โ Output : repomeld_output.txt โ
|
|
77
|
-
โ Style : markdown โ
|
|
78
|
-
โ Extensions : php ts โ
|
|
79
|
-
โ Structure only : yes โ
โ
|
|
80
|
-
โ Review graph : yes โ
โ
|
|
81
|
-
โ Table of cont. : yes โ
|
|
82
|
-
โ Dry run : no โ
|
|
83
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
84
|
-
```
|
|
28
|
+
That's it. repomeld walks your project, skips noise (node_modules, lock files, build folders, etc.) and writes everything into one readable file.
|
|
85
29
|
|
|
86
30
|
---
|
|
87
31
|
|
|
88
|
-
##
|
|
32
|
+
## Auto-Numbered Output โ No Overwriting
|
|
89
33
|
|
|
90
|
-
|
|
34
|
+
Every time you run repomeld it creates a **new numbered file** so previous runs are never lost:
|
|
91
35
|
|
|
92
|
-
```bash
|
|
93
|
-
repomeld --ext ts tsx --style markdown --output context.md
|
|
94
36
|
```
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
repomeld --wizard
|
|
37
|
+
repomeld_output.txt โ first run
|
|
38
|
+
repomeld_output__2.txt โ second run
|
|
39
|
+
repomeld_output__3.txt โ third run
|
|
100
40
|
```
|
|
101
41
|
|
|
102
|
-
|
|
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.
|
|
103
43
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
### `--structure-only` โ Skeleton mode
|
|
44
|
+
---
|
|
107
45
|
|
|
108
|
-
|
|
46
|
+
## All Options
|
|
109
47
|
|
|
110
|
-
```bash
|
|
111
|
-
repomeld --structure-only
|
|
112
48
|
```
|
|
49
|
+
Usage: repomeld [options]
|
|
113
50
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
$validated = $request->validate([...]);
|
|
119
|
-
$guards = SecurityGuard::with('user')
|
|
120
|
-
->where('is_available', true)
|
|
121
|
-
...
|
|
122
|
-
// 50 more lines
|
|
123
|
-
}
|
|
124
|
-
```
|
|
51
|
+
Options:
|
|
52
|
+
-V, --version Show version number
|
|
53
|
+
-h, --help Show help
|
|
125
54
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
55
|
+
Output:
|
|
56
|
+
-o, --output <filename> Output file name
|
|
57
|
+
Default: "repomeld_output.txt"
|
|
58
|
+
Auto-numbered if the file already exists.
|
|
130
59
|
|
|
131
|
-
|
|
60
|
+
Filtering:
|
|
61
|
+
-e, --ext <exts...> Only include files with these extensions
|
|
62
|
+
e.g. --ext js ts jsx tsx
|
|
132
63
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- Code review focusing on structure, not implementation
|
|
136
|
-
- Onboarding โ understand a codebase in seconds
|
|
64
|
+
--include <patterns...> Only include files whose path matches a pattern
|
|
65
|
+
e.g. --include src/
|
|
137
66
|
|
|
138
|
-
|
|
67
|
+
--exclude <patterns...> Skip files whose path matches a pattern
|
|
68
|
+
e.g. --exclude test spec __tests__
|
|
139
69
|
|
|
140
|
-
|
|
70
|
+
-i, --ignore <names...> Extra folder or file names to ignore
|
|
71
|
+
e.g. --ignore dist .next coverage
|
|
141
72
|
|
|
142
|
-
|
|
73
|
+
--force-include <names...> Force-include something that would normally be ignored
|
|
74
|
+
e.g. --force-include vendor bootstrap
|
|
143
75
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```
|
|
76
|
+
--max-size <kb> Skip files larger than N kilobytes
|
|
77
|
+
Default: 500
|
|
147
78
|
|
|
148
|
-
|
|
79
|
+
Formatting:
|
|
80
|
+
-s, --style <style> Header style for each file block:
|
|
81
|
+
banner โ clear dividers with file info (default)
|
|
82
|
+
markdown โ fenced code blocks, great for AI prompts
|
|
83
|
+
minimal โ filename only, no extra formatting
|
|
149
84
|
|
|
150
|
-
|
|
151
|
-
```mermaid
|
|
152
|
-
flowchart TD
|
|
153
|
-
%% Code Review Graph โ generated by repomeld
|
|
85
|
+
--no-toc Don't include a table of contents at the top
|
|
154
86
|
|
|
155
|
-
|
|
156
|
-
src_utils_js["๐ utils.js\n[formatSize, getLanguage]"]
|
|
157
|
-
src_graph_js["๐ graph.js\n[buildReviewGraph]"]
|
|
87
|
+
--no-meta Hide per-file metadata (line count, size, language)
|
|
158
88
|
|
|
159
|
-
|
|
160
|
-
src_index_js --> src_graph_js
|
|
161
|
-
```
|
|
162
|
-
````
|
|
89
|
+
--trim Trim leading/trailing whitespace from each file
|
|
163
90
|
|
|
164
|
-
|
|
91
|
+
Advanced:
|
|
92
|
+
--lines-before <n> Skip the first N lines of every file
|
|
93
|
+
--lines-after <n> Skip the last N lines of every file
|
|
94
|
+
--dry-run Preview which files would be included โ nothing is written
|
|
95
|
+
```
|
|
165
96
|
|
|
166
97
|
---
|
|
167
98
|
|
|
168
|
-
|
|
99
|
+
## Examples
|
|
169
100
|
|
|
170
101
|
```bash
|
|
171
|
-
|
|
172
|
-
|
|
102
|
+
# Basic โ include everything, auto-numbered output
|
|
103
|
+
repomeld
|
|
173
104
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
2. **The skeleton** โ every class and function signature (structure)
|
|
177
|
-
3. **Zero noise** โ no implementation details
|
|
105
|
+
# Only TypeScript files
|
|
106
|
+
repomeld --ext ts tsx
|
|
178
107
|
|
|
179
|
-
|
|
108
|
+
# Only files inside src/
|
|
109
|
+
repomeld --include src/
|
|
180
110
|
|
|
181
|
-
|
|
111
|
+
# Skip test files
|
|
112
|
+
repomeld --exclude test spec __tests__
|
|
182
113
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
repomeld automatically skips common public libraries and vendor files.
|
|
186
|
-
|
|
187
|
-
| Category | Examples |
|
|
188
|
-
|---|---|
|
|
189
|
-
| Project meta | `package.json`, `README.md`, `yarn.lock`, `.gitignore` |
|
|
190
|
-
| Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/` |
|
|
191
|
-
| Vendor folders | `vendor/`, `libs/`, `plugins/`, `assets/vendor/` |
|
|
192
|
-
| Bootstrap | `bootstrap.min.css`, `bootstrap.bundle.min.js` |
|
|
193
|
-
| jQuery | `jquery.min.js`, `jquery-ui.min.js` |
|
|
194
|
-
| Font Awesome | `all.min.css`, `fontawesome-free/` |
|
|
195
|
-
| Tailwind | `tailwind.min.css`, `tailwindcss/` |
|
|
196
|
-
| Chart / D3 / Three | `chart.min.js`, `d3.min.js`, `three.min.js` |
|
|
197
|
-
| Axios / Lodash | `axios.min.js`, `lodash.min.js` |
|
|
198
|
-
| Rich text editors | `tinymce/`, `ckeditor/`, `codemirror/` |
|
|
199
|
-
| Admin templates | `AdminLTE/`, `metronic/`, `coreui/` |
|
|
200
|
-
| Env / Secrets | `.env`, `.env.local`, `.env.production` |
|
|
114
|
+
# Markdown style โ great for pasting into AI chats
|
|
115
|
+
repomeld --style markdown --output context.md
|
|
201
116
|
|
|
202
|
-
|
|
117
|
+
# Preview what would be included without writing anything
|
|
118
|
+
repomeld --dry-run
|
|
203
119
|
|
|
204
|
-
|
|
120
|
+
# Ignore extra folders on top of defaults
|
|
121
|
+
repomeld --ignore coverage logs tmp
|
|
205
122
|
|
|
206
|
-
|
|
207
|
-
|
|
123
|
+
# Only small files โ skip anything over 100 KB
|
|
124
|
+
repomeld --max-size 100
|
|
208
125
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
-h, --help Show help
|
|
212
|
-
--wizard Interactive step-by-step setup
|
|
213
|
-
|
|
214
|
-
Output:
|
|
215
|
-
-o, --output <filename> Output file name (default: "repomeld_output.txt")
|
|
216
|
-
|
|
217
|
-
Filtering:
|
|
218
|
-
-e, --ext <exts...> Only include specific extensions
|
|
219
|
-
e.g. --ext js ts jsx tsx
|
|
220
|
-
--include <patterns...> Only include files matching patterns
|
|
221
|
-
e.g. --include src/
|
|
222
|
-
--exclude <patterns...> Exclude files matching patterns
|
|
223
|
-
e.g. --exclude test spec
|
|
224
|
-
-i, --ignore <names...> Extra folders/files to ignore
|
|
225
|
-
--force-include <names...> Force-include files ignored by default
|
|
226
|
-
--max-size <kb> Skip files larger than N KB (default: 500)
|
|
227
|
-
|
|
228
|
-
Formatting:
|
|
229
|
-
-s, --style <style> Header style: banner | markdown | minimal
|
|
230
|
-
--no-toc Disable table of contents
|
|
231
|
-
--no-meta Hide file metadata (lines, size, lang)
|
|
232
|
-
--trim Trim leading/trailing whitespace per file
|
|
233
|
-
|
|
234
|
-
Advanced:
|
|
235
|
-
--lines-before <n> Skip first N lines of each file
|
|
236
|
-
--lines-after <n> Skip last N lines of each file
|
|
237
|
-
--dry-run Preview without writing output
|
|
238
|
-
|
|
239
|
-
AI / Code Review:
|
|
240
|
-
--structure-only Extract class + function signatures only (no body)
|
|
241
|
-
--review-graph Prepend a Mermaid call graph to the output
|
|
242
|
-
```
|
|
126
|
+
# Force-include a vendor file that's normally ignored
|
|
127
|
+
repomeld --force-include bootstrap
|
|
243
128
|
|
|
244
|
-
|
|
129
|
+
# No table of contents, no metadata
|
|
130
|
+
repomeld --no-toc --no-meta
|
|
245
131
|
|
|
246
|
-
|
|
132
|
+
# Combine filters
|
|
133
|
+
repomeld --ext php --include Controllers --exclude test --style markdown
|
|
134
|
+
```
|
|
247
135
|
|
|
248
|
-
|
|
249
|
-
# Wizard mode (auto when no flags given)
|
|
250
|
-
repomeld
|
|
136
|
+
---
|
|
251
137
|
|
|
252
|
-
|
|
253
|
-
repomeld --wizard
|
|
138
|
+
## Auto-Ignored by Default
|
|
254
139
|
|
|
255
|
-
|
|
256
|
-
repomeld --ext php --include Controllers --structure-only
|
|
140
|
+
repomeld automatically skips these so your output stays clean:
|
|
257
141
|
|
|
258
|
-
|
|
259
|
-
|
|
142
|
+
| Category | What's skipped |
|
|
143
|
+
|-----------------|-----------------------------------------------------|
|
|
144
|
+
| Dependencies | `node_modules/` |
|
|
145
|
+
| Version control | `.git/` |
|
|
146
|
+
| Env / secrets | `.env`, `.env.local`, `.env.production` |
|
|
147
|
+
| Lock files | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` |
|
|
148
|
+
| Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `.cache/` |
|
|
149
|
+
| OS files | `.DS_Store` |
|
|
150
|
+
| Project meta | `package.json`, `README.md` |
|
|
151
|
+
| repomeld output | `repomeld_output.txt` and all `repomeld_output__N.txt` files |
|
|
260
152
|
|
|
261
|
-
|
|
262
|
-
repomeld --structure-only --review-graph --style markdown --output ai_context.md
|
|
153
|
+
You can add your own permanent ignore rules via a `repomeld.ignore.json` file (see below).
|
|
263
154
|
|
|
264
|
-
|
|
265
|
-
repomeld --ext ts tsx --include src/
|
|
155
|
+
---
|
|
266
156
|
|
|
267
|
-
|
|
268
|
-
repomeld --exclude test spec __tests__
|
|
157
|
+
## Custom Ignore File
|
|
269
158
|
|
|
270
|
-
|
|
271
|
-
repomeld --dry-run
|
|
159
|
+
Create a `repomeld.ignore.json` in your project root:
|
|
272
160
|
|
|
273
|
-
|
|
274
|
-
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"ignore": [
|
|
164
|
+
"coverage",
|
|
165
|
+
"logs",
|
|
166
|
+
"tmp",
|
|
167
|
+
"*.min.js"
|
|
168
|
+
]
|
|
169
|
+
}
|
|
275
170
|
```
|
|
276
171
|
|
|
172
|
+
These are merged with the defaults every time repomeld runs.
|
|
173
|
+
|
|
277
174
|
---
|
|
278
175
|
|
|
279
176
|
## Output Format
|
|
280
177
|
|
|
281
|
-
|
|
178
|
+
Each run produces a file like this:
|
|
282
179
|
|
|
283
180
|
```
|
|
284
|
-
# Generated by repomeld v1.
|
|
285
|
-
# Date :
|
|
181
|
+
# Generated by repomeld v1.0.0
|
|
182
|
+
# Date : 2025-04-20T10:00:00.000Z
|
|
286
183
|
# Source : /your/project
|
|
287
|
-
# Files :
|
|
288
|
-
# Lines :
|
|
289
|
-
# Graph : review-graph enabled
|
|
290
|
-
# Author : susheelhbti@gmail.com (open to work)
|
|
184
|
+
# Files : 12
|
|
185
|
+
# Lines : 847
|
|
291
186
|
|
|
187
|
+
TABLE OF CONTENTS
|
|
292
188
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
293
|
-
|
|
189
|
+
1. src/index.js
|
|
190
|
+
2. src/utils.js
|
|
191
|
+
3. src/config.js
|
|
192
|
+
...
|
|
294
193
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
295
194
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
```
|
|
195
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
196
|
+
FILE: src/index.js [120 lines | 3.2 KB | javascript]
|
|
197
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
300
198
|
|
|
301
|
-
|
|
302
|
-
...
|
|
199
|
+
... file contents ...
|
|
303
200
|
```
|
|
304
201
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
```php
|
|
308
|
-
namespace App\Http\Controllers;
|
|
309
|
-
use App\Models\SecurityGuard;
|
|
310
|
-
use App\Models\SecurityRequest;
|
|
311
|
-
|
|
312
|
-
class GuardController extends Controller
|
|
313
|
-
{
|
|
314
|
-
/** Get guard's own profile */
|
|
315
|
-
public function profile() { ... }
|
|
316
|
-
public function updateProfile(Request $request) { ... }
|
|
317
|
-
public function bookings() { ... }
|
|
318
|
-
public function available(Request $request) { ... }
|
|
319
|
-
public function toggleAvailability() { ... }
|
|
320
|
-
public function rate(Request $request, $id) { ... }
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## Publish / Update
|
|
327
|
-
|
|
328
|
-
```bash
|
|
329
|
-
npm version patch # 1.2.0 โ 1.2.1
|
|
330
|
-
npm publish
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
## Contributing
|
|
336
|
-
|
|
337
|
-
Found a popular library that should be in the ignore list? Open a PR!
|
|
202
|
+
With `--style markdown` each file becomes a fenced code block โ paste directly into Claude, ChatGPT, or any AI tool.
|
|
338
203
|
|
|
339
204
|
---
|
|
340
205
|
|
|
@@ -344,4 +209,6 @@ MIT
|
|
|
344
209
|
|
|
345
210
|
---
|
|
346
211
|
|
|
347
|
-
> ๐ผ
|
|
212
|
+
> ## ๐ผ Hire the Author
|
|
213
|
+
> Built by a developer available for **freelance and full-time opportunities**.
|
|
214
|
+
> Got a project? Let's talk โ ๐ง **[susheelhbti@gmail.com](mailto:susheelhbti@gmail.com)**
|
package/bin/cli.js
CHANGED
|
@@ -2,18 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const readline = require("readline");
|
|
6
5
|
const { program } = require("commander");
|
|
7
6
|
|
|
8
|
-
const VERSION = "1.
|
|
9
|
-
|
|
10
|
-
const AUTHOR_NOTE = " ๐ผ Author susheelhbti@gmail.com is open to freelance & full-time work";
|
|
7
|
+
const VERSION = "1.0.0";
|
|
11
8
|
|
|
12
9
|
const DEFAULT_IGNORE = [
|
|
13
|
-
"node_modules",
|
|
14
|
-
".
|
|
15
|
-
".
|
|
16
|
-
"
|
|
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",
|
|
17
26
|
];
|
|
18
27
|
|
|
19
28
|
function loadIgnoreConfig() {
|
|
@@ -23,7 +32,9 @@ function loadIgnoreConfig() {
|
|
|
23
32
|
if (fs.existsSync(loc)) {
|
|
24
33
|
try {
|
|
25
34
|
const data = JSON.parse(fs.readFileSync(loc, "utf8"));
|
|
26
|
-
if (Array.isArray(data.ignore))
|
|
35
|
+
if (Array.isArray(data.ignore)) {
|
|
36
|
+
return data.ignore;
|
|
37
|
+
}
|
|
27
38
|
} catch {
|
|
28
39
|
console.warn(` โ ๏ธ Could not parse ${loc}, using defaults.`);
|
|
29
40
|
}
|
|
@@ -50,17 +61,31 @@ function getLanguage(filePath) {
|
|
|
50
61
|
|
|
51
62
|
function getAllFiles(dirPath, ignoreList, fileList = []) {
|
|
52
63
|
let entries;
|
|
53
|
-
try {
|
|
54
|
-
|
|
64
|
+
try {
|
|
65
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
return fileList;
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
for (const entry of entries) {
|
|
56
71
|
const fullPath = path.join(dirPath, entry.name);
|
|
57
72
|
const relativePath = path.relative(process.cwd(), fullPath);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|
|
63
87
|
}
|
|
88
|
+
|
|
64
89
|
return fileList;
|
|
65
90
|
}
|
|
66
91
|
|
|
@@ -70,9 +95,13 @@ function isBinaryFile(filePath) {
|
|
|
70
95
|
const fd = fs.openSync(filePath, "r");
|
|
71
96
|
const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
|
|
72
97
|
fs.closeSync(fd);
|
|
73
|
-
for (let i = 0; i < bytesRead; i++)
|
|
98
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
99
|
+
if (buffer[i] === 0) return true;
|
|
100
|
+
}
|
|
74
101
|
return false;
|
|
75
|
-
} catch {
|
|
102
|
+
} catch {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
76
105
|
}
|
|
77
106
|
|
|
78
107
|
function matchesExtensions(filePath, exts) {
|
|
@@ -95,308 +124,184 @@ function formatSize(bytes) {
|
|
|
95
124
|
|
|
96
125
|
function printBanner() {
|
|
97
126
|
console.log(`
|
|
98
|
-
|
|
99
|
-
โ
|
|
100
|
-
โ
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`);
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
function buildHeader(style, relativePath, filePath, lineCount, showMeta) {
|
|
107
137
|
const lang = getLanguage(filePath);
|
|
108
138
|
const size = formatSize(fs.statSync(filePath).size);
|
|
109
139
|
const meta = showMeta ? ` [${lineCount} lines | ${size}${lang ? " | " + lang : ""}]` : "";
|
|
110
|
-
|
|
111
|
-
if (style === "
|
|
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
|
|
112
148
|
const divider = "โ".repeat(60);
|
|
113
149
|
return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
|
|
114
150
|
}
|
|
115
151
|
|
|
116
152
|
function buildFooter(style) {
|
|
117
|
-
|
|
153
|
+
if (style === "markdown") return "\n```\n";
|
|
154
|
+
return "\n";
|
|
118
155
|
}
|
|
119
156
|
|
|
120
157
|
function buildTableOfContents(files, cwd) {
|
|
121
158
|
let toc = "TABLE OF CONTENTS\n" + "โ".repeat(60) + "\n";
|
|
122
159
|
files.forEach((f, i) => {
|
|
123
|
-
|
|
160
|
+
const rel = path.relative(cwd, f);
|
|
161
|
+
toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
|
|
124
162
|
});
|
|
125
163
|
toc += "โ".repeat(60) + "\n\n";
|
|
126
164
|
return toc;
|
|
127
165
|
}
|
|
128
166
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (output.length && output[output.length - 1] !== "") output.push("");
|
|
146
|
-
output.push(line); insideClass = true; classStartDepth = braceDepth;
|
|
147
|
-
}
|
|
148
|
-
braceDepth += (line.match(/\{/g) || []).length;
|
|
149
|
-
braceDepth -= (line.match(/\}/g) || []).length;
|
|
150
|
-
if (insideClass && braceDepth <= classStartDepth && trimmed === "}") {
|
|
151
|
-
output.push("}"); insideClass = false; output.push(""); continue;
|
|
152
|
-
}
|
|
153
|
-
if (trimmed.startsWith("/**") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
|
|
154
|
-
if (insideClass) output.push(line); continue;
|
|
155
|
-
}
|
|
156
|
-
if (insideClass) {
|
|
157
|
-
const methodMatch = trimmed.match(/^(public|protected|private|static|abstract|final|\s)*(function\s+\w+\s*\([^)]*\)(\s*:\s*[\w\?\|\\]+)?)/);
|
|
158
|
-
if (methodMatch) output.push(` ${methodMatch[0].trim()} { ... }`);
|
|
159
|
-
if (trimmed.match(/^(public|protected|private|const)\s+/) && trimmed.endsWith(";") && !trimmed.includes("function"))
|
|
160
|
-
output.push(` ${trimmed}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return output.join("\n");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (["javascript", "typescript"].includes(lang)) {
|
|
167
|
-
for (const line of lines) {
|
|
168
|
-
const trimmed = line.trim();
|
|
169
|
-
if (trimmed.match(/^(export\s+)?(abstract\s+)?class\s+\w+/)) { output.push(line); continue; }
|
|
170
|
-
if (trimmed.match(/^(export\s+)?(async\s+)?function\s+\w+\s*\(/)) {
|
|
171
|
-
output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
|
|
172
|
-
}
|
|
173
|
-
if (trimmed.match(/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/)) {
|
|
174
|
-
output.push(`${line.match(/^\s*/)[0]}${trimmed.split("=>")[0].trim()} => { ... }`); continue;
|
|
175
|
-
}
|
|
176
|
-
if (trimmed.match(/^(async\s+)?(\w+)\s*\([^)]*\)\s*(\:\s*[\w<>\[\]]+)?\s*\{/) &&
|
|
177
|
-
!["if","while","for","switch"].some(k => trimmed.startsWith(k))) {
|
|
178
|
-
output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
|
|
179
|
-
}
|
|
180
|
-
if (trimmed.startsWith("import ")) output.push(line);
|
|
181
|
-
if (trimmed === "}") output.push(line);
|
|
182
|
-
}
|
|
183
|
-
return output.join("\n");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (lang === "python") {
|
|
187
|
-
for (const line of lines) {
|
|
188
|
-
const t = line.trim();
|
|
189
|
-
if (t.startsWith("class ") || t.startsWith("def ") || t.startsWith("async def "))
|
|
190
|
-
output.push(line.replace(/:[\s\S]*$/, ":"));
|
|
191
|
-
if (t.startsWith("import ") || t.startsWith("from ")) output.push(line);
|
|
192
|
-
}
|
|
193
|
-
return output.join("\n");
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return lines.slice(0, 30).join("\n") + "\n... (structure extraction not supported for this language)";
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// โโโ REVIEW GRAPH โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
200
|
-
|
|
201
|
-
function buildReviewGraph(files, cwd) {
|
|
202
|
-
const fileFunctions = {};
|
|
203
|
-
const fileImports = {};
|
|
204
|
-
|
|
205
|
-
for (const filePath of files.slice(0, 25)) {
|
|
206
|
-
const lang = getLanguage(filePath);
|
|
207
|
-
if (!["javascript", "typescript", "php", "python"].includes(lang)) continue;
|
|
208
|
-
let content;
|
|
209
|
-
try { content = fs.readFileSync(filePath, "utf8"); } catch { continue; }
|
|
210
|
-
const rel = path.relative(cwd, filePath);
|
|
211
|
-
const nodeId = rel.replace(/[^a-zA-Z0-9]/g, "_");
|
|
212
|
-
fileFunctions[rel] = { nodeId, functions: [] };
|
|
213
|
-
fileImports[rel] = [];
|
|
214
|
-
for (const line of content.split("\n")) {
|
|
215
|
-
const t = line.trim();
|
|
216
|
-
const fnMatch = t.match(/^(export\s+)?(async\s+)?function\s+(\w+)/) ||
|
|
217
|
-
t.match(/^(public|protected|private|static)?\s*(function\s+(\w+))/) ||
|
|
218
|
-
t.match(/^def\s+(\w+)/);
|
|
219
|
-
if (fnMatch) {
|
|
220
|
-
const name = fnMatch[3] || fnMatch[2] || fnMatch[1];
|
|
221
|
-
if (name && name.length > 2 && !["if","for","while","switch"].includes(name))
|
|
222
|
-
fileFunctions[rel].functions.push(name);
|
|
223
|
-
}
|
|
224
|
-
const imp = t.match(/require\(['"]([^'"]+)['"]\)/) ||
|
|
225
|
-
t.match(/^import\s+.*from\s+['"]([^'"]+)['"]/) ||
|
|
226
|
-
t.match(/^use\s+(App\\[^;]+)/);
|
|
227
|
-
if (imp) fileImports[rel].push(imp[1]);
|
|
228
|
-
}
|
|
229
|
-
fileFunctions[rel].functions = [...new Set(fileFunctions[rel].functions)].slice(0, 5);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const fileList = Object.keys(fileFunctions);
|
|
233
|
-
if (fileList.length === 0) return null;
|
|
234
|
-
|
|
235
|
-
let mermaid = "```mermaid\nflowchart TD\n %% Code Review Graph โ generated by repomeld\n\n";
|
|
236
|
-
for (const rel of fileList) {
|
|
237
|
-
const { nodeId, functions } = fileFunctions[rel];
|
|
238
|
-
const label = path.basename(rel);
|
|
239
|
-
const fnList = functions.length ? `\\n[${functions.join(", ")}]` : "";
|
|
240
|
-
mermaid += ` ${nodeId}["๐ ${label}${fnList}"]\n`;
|
|
241
|
-
}
|
|
242
|
-
mermaid += "\n";
|
|
243
|
-
let edgeCount = 0;
|
|
244
|
-
for (const rel of fileList) {
|
|
245
|
-
for (const imp of fileImports[rel]) {
|
|
246
|
-
const matched = fileList.find(f => {
|
|
247
|
-
const base = path.basename(f, path.extname(f));
|
|
248
|
-
return imp.endsWith(base) || imp.includes(base);
|
|
249
|
-
});
|
|
250
|
-
if (matched && matched !== rel && edgeCount < 40) {
|
|
251
|
-
mermaid += ` ${fileFunctions[rel].nodeId} --> ${fileFunctions[matched].nodeId}\n`;
|
|
252
|
-
edgeCount++;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
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++;
|
|
255
183
|
}
|
|
256
|
-
return mermaid + "```\n";
|
|
257
184
|
}
|
|
258
185
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
263
196
|
}
|
|
264
197
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
269
|
-
console.log(" โ Interactive setup โ answer 7 quick questions โ");
|
|
270
|
-
console.log(" โ Press Enter to accept the [default] value โ");
|
|
271
|
-
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
|
|
272
|
-
|
|
273
|
-
const opts = {};
|
|
274
|
-
|
|
275
|
-
// 1 โ Output file
|
|
276
|
-
const outRaw = await ask(rl, " ๐ 1/7 Output filename? [repomeld_output.txt]\n > ");
|
|
277
|
-
opts.output = outRaw.trim() || "repomeld_output.txt";
|
|
278
|
-
|
|
279
|
-
// 2 โ Style
|
|
280
|
-
console.log("\n ๐จ 2/7 Header style?");
|
|
281
|
-
console.log(" 1) banner โ clear dividers with file info (default)");
|
|
282
|
-
console.log(" 2) markdown โ great for pasting into AI prompts");
|
|
283
|
-
console.log(" 3) minimal โ filename only, no extra formatting");
|
|
284
|
-
const styleRaw = await ask(rl, " > ");
|
|
285
|
-
opts.style = ({ "2": "markdown", "3": "minimal" })[styleRaw.trim()] || "banner";
|
|
286
|
-
|
|
287
|
-
// 3 โ Extensions
|
|
288
|
-
console.log("\n ๐ 3/7 Filter to specific file extensions?");
|
|
289
|
-
console.log(" e.g. js ts php py (leave blank = include ALL files)");
|
|
290
|
-
const extRaw = await ask(rl, " > ");
|
|
291
|
-
opts.ext = extRaw.trim() ? extRaw.trim().split(/\s+/) : [];
|
|
292
|
-
|
|
293
|
-
// 4 โ Structure only
|
|
294
|
-
console.log("\n ๐๏ธ 4/7 Enable structure-only mode? (y/n) [n]");
|
|
295
|
-
console.log(" Strips function bodies โ keeps class names + signatures only.");
|
|
296
|
-
console.log(" Cuts a 400-line file to ~30 lines. Perfect for AI architecture review.");
|
|
297
|
-
const structRaw = await ask(rl, " > ");
|
|
298
|
-
opts.structureOnly = structRaw.trim().toLowerCase() === "y";
|
|
299
|
-
|
|
300
|
-
// 5 โ Review graph
|
|
301
|
-
console.log("\n ๐ 5/7 Generate a code review graph? (y/n) [n]");
|
|
302
|
-
console.log(" Adds a Mermaid call graph at the top of the output.");
|
|
303
|
-
console.log(" AI models read Mermaid natively โ shows which files connect to which.");
|
|
304
|
-
const graphRaw = await ask(rl, " > ");
|
|
305
|
-
opts.reviewGraph = graphRaw.trim().toLowerCase() === "y";
|
|
306
|
-
|
|
307
|
-
// 6 โ Table of contents
|
|
308
|
-
console.log("\n ๐ 6/7 Include a table of contents? (y/n) [y]");
|
|
309
|
-
console.log(" Lists every included file at the top โ useful for large repos.");
|
|
310
|
-
const tocRaw = await ask(rl, " > ");
|
|
311
|
-
opts.noToc = tocRaw.trim().toLowerCase() === "n";
|
|
312
|
-
|
|
313
|
-
// 7 โ Dry run
|
|
314
|
-
console.log("\n ๐งช 7/7 Dry run only? (y/n) [n]");
|
|
315
|
-
console.log(" Preview which files would be included without writing anything.");
|
|
316
|
-
const dryRaw = await ask(rl, " > ");
|
|
317
|
-
opts.dryRun = dryRaw.trim().toLowerCase() === "y";
|
|
318
|
-
|
|
319
|
-
rl.close();
|
|
320
|
-
|
|
321
|
-
// Summary
|
|
322
|
-
console.log("\n โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
323
|
-
console.log(" โ Your configuration โ");
|
|
324
|
-
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค");
|
|
325
|
-
console.log(` โ Output : ${opts.output.padEnd(29)}โ`);
|
|
326
|
-
console.log(` โ Style : ${opts.style.padEnd(29)}โ`);
|
|
327
|
-
console.log(` โ Extensions : ${(opts.ext.length ? opts.ext.join(", ") : "all").padEnd(29)}โ`);
|
|
328
|
-
console.log(` โ Structure only : ${(opts.structureOnly ? "yes โ
" : "no").padEnd(29)}โ`);
|
|
329
|
-
console.log(` โ Review graph : ${(opts.reviewGraph ? "yes โ
" : "no").padEnd(29)}โ`);
|
|
330
|
-
console.log(` โ Table of cont. : ${(opts.noToc ? "no" : "yes").padEnd(29)}โ`);
|
|
331
|
-
console.log(` โ Dry run : ${(opts.dryRun ? "yes ๐งช" : "no").padEnd(29)}โ`);
|
|
332
|
-
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
|
|
333
|
-
|
|
334
|
-
return opts;
|
|
198
|
+
function escapeRegex(str) {
|
|
199
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
335
200
|
}
|
|
336
201
|
|
|
337
|
-
// โโโ CORE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
338
|
-
|
|
339
202
|
function repomeld(options) {
|
|
203
|
+
printBanner();
|
|
204
|
+
|
|
340
205
|
const cwd = process.cwd();
|
|
341
|
-
|
|
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
|
+
|
|
342
212
|
const forceInclude = options.forceInclude || [];
|
|
343
213
|
const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
|
|
344
214
|
const ignoreList = forceInclude.length
|
|
345
|
-
? rawIgnore.filter(ig => !forceInclude.some(fi => ig.includes(fi) || fi.includes(ig)))
|
|
215
|
+
? rawIgnore.filter((ig) => !forceInclude.some((fi) => ig.includes(fi) || fi.includes(ig)))
|
|
346
216
|
: rawIgnore;
|
|
347
|
-
const filterExts
|
|
217
|
+
const filterExts = options.ext || [];
|
|
348
218
|
const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
|
|
349
|
-
const headerStyle
|
|
350
|
-
const showMeta
|
|
351
|
-
const showToc
|
|
352
|
-
const dryRun
|
|
353
|
-
const include
|
|
354
|
-
const exclude
|
|
355
|
-
const linesBefore
|
|
356
|
-
const linesAfter
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
console.log(` ๐ Source : ${cwd}`);
|
|
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}`);
|
|
361
229
|
console.log(` ๐ Output : ${path.relative(cwd, outputFile)}`);
|
|
362
230
|
console.log(` ๐จ Style : ${headerStyle}`);
|
|
363
231
|
if (filterExts.length) console.log(` ๐ Filter : .${filterExts.join(", .")}`);
|
|
364
|
-
if (
|
|
365
|
-
if (
|
|
366
|
-
if (dryRun) console.log(` ๐งช Dry run : no file will be written`);
|
|
232
|
+
if (forceInclude.length) console.log(` ๐ Force : ${forceInclude.join(", ")}`);
|
|
233
|
+
if (dryRun) console.log(` ๐งช Dry run : no file will be written`);
|
|
367
234
|
console.log();
|
|
368
235
|
|
|
369
236
|
let allFiles = getAllFiles(cwd, ignoreList);
|
|
370
|
-
if (filterExts.length) allFiles = allFiles.filter(f => matchesExtensions(f, filterExts));
|
|
371
|
-
if (include.length) allFiles = allFiles.filter(f => matchesPattern(f, include));
|
|
372
|
-
if (exclude.length) allFiles = allFiles.filter(f => !matchesPattern(f, exclude));
|
|
373
|
-
allFiles = allFiles.filter(f => path.resolve(f) !== outputFile);
|
|
374
237
|
|
|
375
|
-
|
|
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
|
+
}
|
|
376
260
|
|
|
377
261
|
let combinedContent = "";
|
|
378
|
-
let skipped = 0
|
|
262
|
+
let skipped = 0;
|
|
263
|
+
let included = 0;
|
|
264
|
+
let totalLines = 0;
|
|
379
265
|
const includedFiles = [];
|
|
380
266
|
|
|
381
267
|
for (const filePath of allFiles) {
|
|
382
268
|
const relativePath = path.relative(cwd, filePath);
|
|
383
|
-
|
|
269
|
+
|
|
270
|
+
if (isBinaryFile(filePath)) {
|
|
271
|
+
console.log(` โญ Binary : ${relativePath}`);
|
|
272
|
+
skipped++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
384
276
|
const stat = fs.statSync(filePath);
|
|
385
|
-
if (stat.size > maxFileSizeBytes) {
|
|
277
|
+
if (stat.size > maxFileSizeBytes) {
|
|
278
|
+
console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`);
|
|
279
|
+
skipped++;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
386
283
|
try {
|
|
387
284
|
let content = fs.readFileSync(filePath, "utf8");
|
|
388
|
-
|
|
285
|
+
|
|
286
|
+
if (options.trim) {
|
|
287
|
+
content = content.trim();
|
|
288
|
+
}
|
|
289
|
+
|
|
389
290
|
if (linesBefore > 0 || linesAfter > 0) {
|
|
390
291
|
const lines = content.split("\n");
|
|
391
|
-
|
|
292
|
+
const start = linesBefore;
|
|
293
|
+
const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
|
|
294
|
+
content = lines.slice(start, end).join("\n");
|
|
392
295
|
}
|
|
393
|
-
|
|
296
|
+
|
|
394
297
|
const lineCount = content.split("\n").length;
|
|
395
298
|
totalLines += lineCount;
|
|
396
299
|
includedFiles.push(filePath);
|
|
300
|
+
|
|
397
301
|
combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
|
|
398
302
|
combinedContent += content;
|
|
399
303
|
combinedContent += buildFooter(headerStyle);
|
|
304
|
+
|
|
400
305
|
console.log(` โ
${relativePath}`);
|
|
401
306
|
included++;
|
|
402
307
|
} catch (err) {
|
|
@@ -405,80 +310,73 @@ function repomeld(options) {
|
|
|
405
310
|
}
|
|
406
311
|
}
|
|
407
312
|
|
|
313
|
+
// Build final output
|
|
408
314
|
let finalOutput = "";
|
|
315
|
+
|
|
316
|
+
const timestamp = new Date().toISOString();
|
|
409
317
|
finalOutput += `# Generated by repomeld v${VERSION}\n`;
|
|
410
|
-
finalOutput += `# Date : ${
|
|
318
|
+
finalOutput += `# Date : ${timestamp}\n`;
|
|
411
319
|
finalOutput += `# Source : ${cwd}\n`;
|
|
412
320
|
finalOutput += `# Files : ${included}\n`;
|
|
413
321
|
finalOutput += `# Lines : ${totalLines}\n`;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (reviewGraph) {
|
|
419
|
-
const graph = buildReviewGraph(includedFiles, cwd);
|
|
420
|
-
if (graph) {
|
|
421
|
-
finalOutput += "โ".repeat(60) + "\n CODE REVIEW GRAPH\n" + "โ".repeat(60) + "\n\n";
|
|
422
|
-
finalOutput += graph + "\n";
|
|
423
|
-
}
|
|
322
|
+
finalOutput += `# Author : susheelhbti@gmail.com โ available for freelance & full-time work\n\n`;
|
|
323
|
+
|
|
324
|
+
if (showToc) {
|
|
325
|
+
finalOutput += buildTableOfContents(includedFiles, cwd);
|
|
424
326
|
}
|
|
425
327
|
|
|
426
|
-
if (showToc) finalOutput += buildTableOfContents(includedFiles, cwd);
|
|
427
328
|
finalOutput += combinedContent;
|
|
428
329
|
|
|
429
|
-
if (!dryRun)
|
|
330
|
+
if (!dryRun) {
|
|
331
|
+
fs.writeFileSync(outputFile, finalOutput, "utf8");
|
|
332
|
+
}
|
|
430
333
|
|
|
431
334
|
const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
|
|
335
|
+
|
|
432
336
|
console.log(`
|
|
433
337
|
โจ repomeld complete!
|
|
434
|
-
|
|
338
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
435
339
|
โ
Included : ${included} files
|
|
436
340
|
โญ Skipped : ${skipped} files
|
|
437
341
|
๐ Lines : ${totalLines}
|
|
438
342
|
๐พ Size : ${outputSize}
|
|
439
|
-
๐ Output : ${
|
|
343
|
+
๐ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run โ not written)" : ""}
|
|
344
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
345
|
+
๐ผ Need a developer? susheelhbti@gmail.com
|
|
440
346
|
`);
|
|
441
|
-
console.log(AUTHOR_NOTE + "\n");
|
|
442
347
|
}
|
|
443
348
|
|
|
444
|
-
// โโโ CLI
|
|
349
|
+
// โโโ CLI Definition โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
445
350
|
|
|
446
351
|
program
|
|
447
352
|
.name("repomeld")
|
|
448
353
|
.description("Meld your entire repo into a single file โ perfect for AI context, code reviews & sharing")
|
|
449
354
|
.version(VERSION)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.option("--
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
.option("
|
|
456
|
-
.option("--
|
|
457
|
-
.option("
|
|
458
|
-
.option("--
|
|
459
|
-
.option("--
|
|
460
|
-
.option("--
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
.option("--
|
|
464
|
-
.option("--
|
|
465
|
-
.option("--
|
|
466
|
-
.option("--
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (options.wizard || !hasFlags) {
|
|
477
|
-
const wizardOpts = await runWizard();
|
|
478
|
-
repomeld({ ...options, ...wizardOpts });
|
|
479
|
-
} else {
|
|
480
|
-
repomeld(options);
|
|
481
|
-
}
|
|
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);
|
|
482
380
|
});
|
|
483
381
|
|
|
484
|
-
program.parse(process.argv);
|
|
382
|
+
program.parse(process.argv);
|