repomeld 2.0.1 โ 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -124
- package/bin/cli.js +341 -178
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Meld your entire repo into a single file โ perfect for AI context, code reviews & sharing.
|
|
4
4
|
|
|
5
|
+
> ๐ผ **Author [`susheelhbti@gmail.com`](mailto:susheelhbti@gmail.com) is open to freelance & full-time work.**
|
|
6
|
+
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## Install
|
|
@@ -19,88 +21,183 @@ cd your-project
|
|
|
19
21
|
repomeld
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
**No flags needed.** repomeld will walk you through 7 quick questions and configure itself:
|
|
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
|
+
```
|
|
23
85
|
|
|
24
86
|
---
|
|
25
87
|
|
|
26
|
-
##
|
|
88
|
+
## Skip the Wizard (Power Users)
|
|
89
|
+
|
|
90
|
+
If you pass any flags, repomeld skips the wizard and runs directly:
|
|
27
91
|
|
|
28
|
-
|
|
92
|
+
```bash
|
|
93
|
+
repomeld --ext ts tsx --style markdown --output context.md
|
|
94
|
+
```
|
|
29
95
|
|
|
30
|
-
|
|
96
|
+
Force the wizard at any time with:
|
|
31
97
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/` |
|
|
36
|
-
| Vendor folders | `vendor/`, `libs/`, `plugins/`, `assets/vendor/`, `wwwroot/lib/` |
|
|
37
|
-
| Bootstrap | `bootstrap.min.css`, `bootstrap.bundle.min.js`, `bootstrap-icons` |
|
|
38
|
-
| jQuery | `jquery.min.js`, `jquery-ui.min.js`, `jquery.validate.min.js` |
|
|
39
|
-
| Font Awesome | `all.min.css`, `fa-solid-900.woff2`, `fontawesome-free/` |
|
|
40
|
-
| Tailwind CSS | `tailwind.min.css`, `tailwindcss/` |
|
|
41
|
-
| Materialize | `materialize.min.css`, `materialize.min.js` |
|
|
42
|
-
| Bulma | `bulma.min.css` |
|
|
43
|
-
| Semantic UI | `semantic.min.css`, `semantic.min.js` |
|
|
44
|
-
| Foundation | `foundation.min.css`, `foundation.min.js` |
|
|
45
|
-
| UIkit | `uikit.min.css`, `uikit.min.js` |
|
|
46
|
-
| Animate.css | `animate.min.css`, `animate.css` |
|
|
47
|
-
| GSAP | `gsap.min.js`, `ScrollTrigger.min.js`, `TweenMax.min.js` |
|
|
48
|
-
| Swiper / Slick / Owl | `swiper-bundle.min.js`, `slick.min.js`, `owl.carousel.min.js` |
|
|
49
|
-
| Chart.js / D3 / Three.js | `chart.min.js`, `d3.min.js`, `three.min.js` |
|
|
50
|
-
| Axios / Lodash / Moment | `axios.min.js`, `lodash.min.js`, `moment.min.js` |
|
|
51
|
-
| Select2 / Flatpickr | `select2.min.js`, `flatpickr.min.js` |
|
|
52
|
-
| DataTables | `datatables.min.js`, `jquery.dataTables.min.js` |
|
|
53
|
-
| Toastr / SweetAlert | `toastr.min.js`, `sweetalert2.min.js` |
|
|
54
|
-
| Lightbox / Fancybox | `lightbox.min.js`, `glightbox.min.js` |
|
|
55
|
-
| Rich text editors | `quill.min.js`, `tinymce/`, `ckeditor/`, `codemirror/` |
|
|
56
|
-
| Syntax highlighting | `prism.min.js`, `highlight.min.js` |
|
|
57
|
-
| Video / Audio players | `video.js`, `plyr.min.js` |
|
|
58
|
-
| Maps | `leaflet.min.js`, `mapbox-gl.min.js` |
|
|
59
|
-
| Icon sets | `remixicon.css`, `boxicons.css`, `ionicons.css`, `lucide.min.js` |
|
|
60
|
-
| Socket.IO | `socket.io.min.js` |
|
|
61
|
-
| Misc utilities | `lazysizes.js`, `lottie.min.js`, `particles.js`, `typed.js`, `sortable.min.js`, `masonry.js` |
|
|
62
|
-
| Admin templates | `AdminLTE/`, `metronic/`, `coreui/`, `gentelella/` |
|
|
63
|
-
| Env / Secrets | `.env`, `.env.local`, `.env.production` |
|
|
98
|
+
```bash
|
|
99
|
+
repomeld --wizard
|
|
100
|
+
```
|
|
64
101
|
|
|
65
102
|
---
|
|
66
103
|
|
|
67
|
-
##
|
|
104
|
+
## New Features in v1.2.0
|
|
105
|
+
|
|
106
|
+
### `--structure-only` โ Skeleton mode
|
|
68
107
|
|
|
69
|
-
|
|
108
|
+
Extracts **class names + function signatures** from every file. Strips all body code.
|
|
70
109
|
|
|
71
110
|
```bash
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
repomeld --structure-only
|
|
112
|
+
```
|
|
74
113
|
|
|
75
|
-
|
|
76
|
-
|
|
114
|
+
**Before** (400 lines of PHP):
|
|
115
|
+
```php
|
|
116
|
+
public function available(Request $request)
|
|
117
|
+
{
|
|
118
|
+
$validated = $request->validate([...]);
|
|
119
|
+
$guards = SecurityGuard::with('user')
|
|
120
|
+
->where('is_available', true)
|
|
121
|
+
...
|
|
122
|
+
// 50 more lines
|
|
123
|
+
}
|
|
124
|
+
```
|
|
77
125
|
|
|
78
|
-
|
|
79
|
-
|
|
126
|
+
**After** (1 line):
|
|
127
|
+
```php
|
|
128
|
+
public function available(Request $request) { ... }
|
|
80
129
|
```
|
|
81
130
|
|
|
82
|
-
|
|
131
|
+
Supports **PHP, JavaScript, TypeScript, Python**. Other languages fall back to the first 30 lines.
|
|
132
|
+
|
|
133
|
+
Perfect for:
|
|
134
|
+
- Giving AI a full architecture overview without hitting context limits
|
|
135
|
+
- Code review focusing on structure, not implementation
|
|
136
|
+
- Onboarding โ understand a codebase in seconds
|
|
83
137
|
|
|
84
138
|
---
|
|
85
139
|
|
|
86
|
-
|
|
140
|
+
### `--review-graph` โ Mermaid call graph
|
|
87
141
|
|
|
88
|
-
|
|
142
|
+
Generates a **Mermaid flowchart** at the top of your output showing which files import/call which.
|
|
89
143
|
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
144
|
+
```bash
|
|
145
|
+
repomeld --review-graph
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Output (inside the file, readable by AI natively):
|
|
149
|
+
|
|
150
|
+
````
|
|
151
|
+
```mermaid
|
|
152
|
+
flowchart TD
|
|
153
|
+
%% Code Review Graph โ generated by repomeld
|
|
154
|
+
|
|
155
|
+
src_index_js["๐ index.js\n[repomeld, getAllFiles, buildHeader]"]
|
|
156
|
+
src_utils_js["๐ utils.js\n[formatSize, getLanguage]"]
|
|
157
|
+
src_graph_js["๐ graph.js\n[buildReviewGraph]"]
|
|
158
|
+
|
|
159
|
+
src_index_js --> src_utils_js
|
|
160
|
+
src_index_js --> src_graph_js
|
|
161
|
+
```
|
|
162
|
+
````
|
|
163
|
+
|
|
164
|
+
AI models like Claude, GPT-4, and Gemini read Mermaid natively โ no image needed.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### Power combo: both together
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
repomeld --structure-only --review-graph
|
|
98
172
|
```
|
|
99
173
|
|
|
100
|
-
|
|
101
|
-
1.
|
|
102
|
-
2.
|
|
103
|
-
3.
|
|
174
|
+
AI gets:
|
|
175
|
+
1. **The map** โ which files connect to which (graph)
|
|
176
|
+
2. **The skeleton** โ every class and function signature (structure)
|
|
177
|
+
3. **Zero noise** โ no implementation details
|
|
178
|
+
|
|
179
|
+
This is the best way to give AI a complete understanding of a large codebase without wasting context.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Smart Auto-Ignore
|
|
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` |
|
|
104
201
|
|
|
105
202
|
---
|
|
106
203
|
|
|
@@ -112,6 +209,7 @@ Usage: repomeld [options]
|
|
|
112
209
|
Options:
|
|
113
210
|
-V, --version Show version
|
|
114
211
|
-h, --help Show help
|
|
212
|
+
--wizard Interactive step-by-step setup
|
|
115
213
|
|
|
116
214
|
Output:
|
|
117
215
|
-o, --output <filename> Output file name (default: "repomeld_output.txt")
|
|
@@ -124,14 +222,11 @@ Options:
|
|
|
124
222
|
--exclude <patterns...> Exclude files matching patterns
|
|
125
223
|
e.g. --exclude test spec
|
|
126
224
|
-i, --ignore <names...> Extra folders/files to ignore
|
|
127
|
-
e.g. --ignore dist .next
|
|
128
225
|
--force-include <names...> Force-include files ignored by default
|
|
129
|
-
e.g. --force-include vendor bootstrap jquery
|
|
130
226
|
--max-size <kb> Skip files larger than N KB (default: 500)
|
|
131
227
|
|
|
132
228
|
Formatting:
|
|
133
229
|
-s, --style <style> Header style: banner | markdown | minimal
|
|
134
|
-
(default: banner)
|
|
135
230
|
--no-toc Disable table of contents
|
|
136
231
|
--no-meta Hide file metadata (lines, size, lang)
|
|
137
232
|
--trim Trim leading/trailing whitespace per file
|
|
@@ -139,7 +234,11 @@ Options:
|
|
|
139
234
|
Advanced:
|
|
140
235
|
--lines-before <n> Skip first N lines of each file
|
|
141
236
|
--lines-after <n> Skip last N lines of each file
|
|
142
|
-
--dry-run Preview
|
|
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
|
|
143
242
|
```
|
|
144
243
|
|
|
145
244
|
---
|
|
@@ -147,99 +246,87 @@ Options:
|
|
|
147
246
|
## Examples
|
|
148
247
|
|
|
149
248
|
```bash
|
|
150
|
-
#
|
|
249
|
+
# Wizard mode (auto when no flags given)
|
|
151
250
|
repomeld
|
|
152
251
|
|
|
153
|
-
#
|
|
154
|
-
repomeld --
|
|
155
|
-
|
|
156
|
-
# Exclude test files
|
|
157
|
-
repomeld --exclude test spec __tests__
|
|
158
|
-
|
|
159
|
-
# Force-include your customized jQuery even though it is ignored by default
|
|
160
|
-
repomeld --force-include jquery
|
|
161
|
-
|
|
162
|
-
# Force-include multiple libs
|
|
163
|
-
repomeld --force-include vendor bootstrap select2
|
|
252
|
+
# Force wizard even with flags
|
|
253
|
+
repomeld --wizard
|
|
164
254
|
|
|
165
|
-
#
|
|
166
|
-
repomeld --
|
|
255
|
+
# Only PHP controllers, structure only
|
|
256
|
+
repomeld --ext php --include Controllers --structure-only
|
|
167
257
|
|
|
168
|
-
#
|
|
169
|
-
repomeld --
|
|
258
|
+
# Full code + call graph
|
|
259
|
+
repomeld --review-graph
|
|
170
260
|
|
|
171
|
-
#
|
|
172
|
-
repomeld --output
|
|
261
|
+
# Best for AI: skeleton + graph, markdown style
|
|
262
|
+
repomeld --structure-only --review-graph --style markdown --output ai_context.md
|
|
173
263
|
|
|
174
|
-
#
|
|
175
|
-
repomeld --
|
|
264
|
+
# Only TypeScript inside src/
|
|
265
|
+
repomeld --ext ts tsx --include src/
|
|
176
266
|
|
|
177
|
-
#
|
|
178
|
-
repomeld --
|
|
267
|
+
# Exclude tests
|
|
268
|
+
repomeld --exclude test spec __tests__
|
|
179
269
|
|
|
180
|
-
# Dry run โ preview
|
|
270
|
+
# Dry run โ preview only
|
|
181
271
|
repomeld --dry-run
|
|
182
272
|
|
|
183
|
-
#
|
|
184
|
-
repomeld --
|
|
185
|
-
|
|
186
|
-
# Combine options
|
|
187
|
-
repomeld --ext ts tsx --include src/ --style markdown --output ai_context.md
|
|
273
|
+
# Large repo: structure only, skip files over 100 KB
|
|
274
|
+
repomeld --structure-only --max-size 100
|
|
188
275
|
```
|
|
189
276
|
|
|
190
277
|
---
|
|
191
278
|
|
|
192
|
-
## Output Format
|
|
279
|
+
## Output Format
|
|
280
|
+
|
|
281
|
+
### With `--review-graph`
|
|
193
282
|
|
|
194
283
|
```
|
|
195
|
-
# Generated by repomeld v1.
|
|
284
|
+
# Generated by repomeld v1.2.0
|
|
196
285
|
# Date : 2024-01-01T00:00:00.000Z
|
|
197
286
|
# Source : /your/project
|
|
198
|
-
# Files :
|
|
199
|
-
# Lines :
|
|
287
|
+
# Files : 8
|
|
288
|
+
# Lines : 312
|
|
289
|
+
# Graph : review-graph enabled
|
|
290
|
+
# Author : susheelhbti@gmail.com (open to work)
|
|
200
291
|
|
|
201
|
-
TABLE OF CONTENTS
|
|
202
292
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
203
|
-
|
|
204
|
-
2. src/utils/helper.ts
|
|
205
|
-
...
|
|
293
|
+
CODE REVIEW GRAPH
|
|
206
294
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
207
295
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<file contents>
|
|
296
|
+
```mermaid
|
|
297
|
+
flowchart TD
|
|
298
|
+
...
|
|
213
299
|
```
|
|
214
300
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
## Output Format (markdown style)
|
|
218
|
-
|
|
219
|
-
````
|
|
220
|
-
## ๐ src/index.ts [42 lines | 1.2 KB | typescript]
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
<file contents>
|
|
301
|
+
TABLE OF CONTENTS
|
|
302
|
+
...
|
|
224
303
|
```
|
|
225
|
-
````
|
|
226
304
|
|
|
227
|
-
|
|
305
|
+
### With `--structure-only` (PHP example)
|
|
228
306
|
|
|
229
|
-
|
|
307
|
+
```php
|
|
308
|
+
namespace App\Http\Controllers;
|
|
309
|
+
use App\Models\SecurityGuard;
|
|
310
|
+
use App\Models\SecurityRequest;
|
|
230
311
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
```
|
|
236
323
|
|
|
237
324
|
---
|
|
238
325
|
|
|
239
326
|
## Publish / Update
|
|
240
327
|
|
|
241
328
|
```bash
|
|
242
|
-
npm version patch # 1.
|
|
329
|
+
npm version patch # 1.2.0 โ 1.2.1
|
|
243
330
|
npm publish
|
|
244
331
|
```
|
|
245
332
|
|
|
@@ -247,10 +334,14 @@ npm publish
|
|
|
247
334
|
|
|
248
335
|
## Contributing
|
|
249
336
|
|
|
250
|
-
Found a popular library that should be in the ignore list? Open a PR
|
|
337
|
+
Found a popular library that should be in the ignore list? Open a PR!
|
|
251
338
|
|
|
252
339
|
---
|
|
253
340
|
|
|
254
341
|
## License
|
|
255
342
|
|
|
256
|
-
MIT
|
|
343
|
+
MIT
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
> ๐ผ **Looking to hire?** The author [`susheelhbti@gmail.com`](mailto:susheelhbti@gmail.com) is available for freelance and full-time work.
|
package/bin/cli.js
CHANGED
|
@@ -2,28 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const readline = require("readline");
|
|
5
6
|
const { program } = require("commander");
|
|
6
7
|
|
|
7
|
-
const VERSION = "1.
|
|
8
|
+
const VERSION = "1.2.0";
|
|
9
|
+
|
|
10
|
+
const AUTHOR_NOTE = " ๐ผ Author susheelhbti@gmail.com is open to freelance & full-time work";
|
|
8
11
|
|
|
9
12
|
const DEFAULT_IGNORE = [
|
|
10
|
-
"node_modules",
|
|
11
|
-
".
|
|
12
|
-
".
|
|
13
|
-
".
|
|
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
|
-
"repomeld_output.txt",
|
|
13
|
+
"node_modules", ".git", ".env", ".env.local", ".env.production",
|
|
14
|
+
".DS_Store", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
15
|
+
".next", ".nuxt", "dist", "build", ".cache", "package.json",
|
|
16
|
+
"README.md", "repomeld_output.txt",
|
|
27
17
|
];
|
|
28
18
|
|
|
29
19
|
function loadIgnoreConfig() {
|
|
@@ -33,9 +23,7 @@ function loadIgnoreConfig() {
|
|
|
33
23
|
if (fs.existsSync(loc)) {
|
|
34
24
|
try {
|
|
35
25
|
const data = JSON.parse(fs.readFileSync(loc, "utf8"));
|
|
36
|
-
if (Array.isArray(data.ignore))
|
|
37
|
-
return data.ignore;
|
|
38
|
-
}
|
|
26
|
+
if (Array.isArray(data.ignore)) return data.ignore;
|
|
39
27
|
} catch {
|
|
40
28
|
console.warn(` โ ๏ธ Could not parse ${loc}, using defaults.`);
|
|
41
29
|
}
|
|
@@ -62,31 +50,17 @@ function getLanguage(filePath) {
|
|
|
62
50
|
|
|
63
51
|
function getAllFiles(dirPath, ignoreList, fileList = []) {
|
|
64
52
|
let entries;
|
|
65
|
-
try {
|
|
66
|
-
|
|
67
|
-
} catch {
|
|
68
|
-
return fileList;
|
|
69
|
-
}
|
|
70
|
-
|
|
53
|
+
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); }
|
|
54
|
+
catch { return fileList; }
|
|
71
55
|
for (const entry of entries) {
|
|
72
56
|
const fullPath = path.join(dirPath, entry.name);
|
|
73
57
|
const relativePath = path.relative(process.cwd(), fullPath);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (entry.isDirectory()) {
|
|
84
|
-
getAllFiles(fullPath, ignoreList, fileList);
|
|
85
|
-
} else if (entry.isFile()) {
|
|
86
|
-
fileList.push(fullPath);
|
|
87
|
-
}
|
|
58
|
+
if (ignoreList.some(
|
|
59
|
+
(ig) => entry.name === ig || relativePath.startsWith(ig + path.sep) || relativePath === ig
|
|
60
|
+
)) continue;
|
|
61
|
+
if (entry.isDirectory()) getAllFiles(fullPath, ignoreList, fileList);
|
|
62
|
+
else if (entry.isFile()) fileList.push(fullPath);
|
|
88
63
|
}
|
|
89
|
-
|
|
90
64
|
return fileList;
|
|
91
65
|
}
|
|
92
66
|
|
|
@@ -96,13 +70,9 @@ function isBinaryFile(filePath) {
|
|
|
96
70
|
const fd = fs.openSync(filePath, "r");
|
|
97
71
|
const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
|
|
98
72
|
fs.closeSync(fd);
|
|
99
|
-
for (let i = 0; i < bytesRead; i++)
|
|
100
|
-
if (buffer[i] === 0) return true;
|
|
101
|
-
}
|
|
73
|
+
for (let i = 0; i < bytesRead; i++) if (buffer[i] === 0) return true;
|
|
102
74
|
return false;
|
|
103
|
-
} catch {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
75
|
+
} catch { return true; }
|
|
106
76
|
}
|
|
107
77
|
|
|
108
78
|
function matchesExtensions(filePath, exts) {
|
|
@@ -123,147 +93,329 @@ function formatSize(bytes) {
|
|
|
123
93
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
124
94
|
}
|
|
125
95
|
|
|
96
|
+
// โโโ AUTO-INCREMENT OUTPUT FILENAME โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
97
|
+
|
|
98
|
+
function resolveOutputPath(cwd, filename) {
|
|
99
|
+
const ext = path.extname(filename);
|
|
100
|
+
const base = path.basename(filename, ext);
|
|
101
|
+
let candidate = path.resolve(cwd, filename);
|
|
102
|
+
let counter = 1;
|
|
103
|
+
while (fs.existsSync(candidate)) {
|
|
104
|
+
candidate = path.resolve(cwd, `${base}_${counter}${ext}`);
|
|
105
|
+
counter++;
|
|
106
|
+
}
|
|
107
|
+
return candidate;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// โโโ BANNER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
111
|
+
|
|
126
112
|
function printBanner() {
|
|
127
113
|
console.log(`
|
|
128
|
-
|
|
129
|
-
โ
|
|
130
|
-
โ Meld your repo into one file โ
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(
|
|
114
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
115
|
+
โ repomeld v${VERSION} โ
|
|
116
|
+
โ Meld your repo into one file ๐ฅ โ
|
|
117
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`);
|
|
118
|
+
console.log(AUTHOR_NOTE);
|
|
119
|
+
console.log();
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
|
|
122
|
+
// โโโ HEADER / FOOTER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
123
|
+
|
|
124
|
+
function buildHeader(relativePath, filePath, lineCount, showMeta) {
|
|
137
125
|
const lang = getLanguage(filePath);
|
|
138
126
|
const size = formatSize(fs.statSync(filePath).size);
|
|
139
127
|
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
128
|
const divider = "โ".repeat(60);
|
|
149
129
|
return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
|
|
150
130
|
}
|
|
151
131
|
|
|
152
|
-
function buildFooter(
|
|
153
|
-
if (style === "markdown") return "\n```\n";
|
|
132
|
+
function buildFooter() {
|
|
154
133
|
return "\n";
|
|
155
134
|
}
|
|
156
135
|
|
|
136
|
+
// โโโ TABLE OF CONTENTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
137
|
+
|
|
157
138
|
function buildTableOfContents(files, cwd) {
|
|
158
139
|
let toc = "TABLE OF CONTENTS\n" + "โ".repeat(60) + "\n";
|
|
159
140
|
files.forEach((f, i) => {
|
|
160
|
-
|
|
161
|
-
toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
|
|
141
|
+
toc += ` ${String(i + 1).padStart(3, " ")}. ${path.relative(cwd, f)}\n`;
|
|
162
142
|
});
|
|
163
143
|
toc += "โ".repeat(60) + "\n\n";
|
|
164
144
|
return toc;
|
|
165
145
|
}
|
|
166
146
|
|
|
167
|
-
|
|
168
|
-
|
|
147
|
+
// โโโ FILE TREE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
148
|
+
|
|
149
|
+
function buildFileTree(files, cwd) {
|
|
150
|
+
const tree = {};
|
|
151
|
+
for (const f of files) {
|
|
152
|
+
const parts = path.relative(cwd, f).split(path.sep);
|
|
153
|
+
let node = tree;
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
node[part] = node[part] || {};
|
|
156
|
+
node = node[part];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function render(node, prefix = "") {
|
|
161
|
+
const keys = Object.keys(node);
|
|
162
|
+
return keys.map((key, i) => {
|
|
163
|
+
const isLast = i === keys.length - 1;
|
|
164
|
+
const connector = isLast ? "โโโ " : "โโโ ";
|
|
165
|
+
const childPfx = isLast ? " " : "โ ";
|
|
166
|
+
const children = Object.keys(node[key]).length
|
|
167
|
+
? "\n" + render(node[key], prefix + childPfx)
|
|
168
|
+
: "";
|
|
169
|
+
return prefix + connector + key + children;
|
|
170
|
+
}).join("\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return "FILE TREE\n" + "โ".repeat(60) + "\n"
|
|
174
|
+
+ render(tree) + "\n"
|
|
175
|
+
+ "โ".repeat(60) + "\n\n";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// โโโ STRUCTURE EXTRACTION โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
179
|
+
|
|
180
|
+
function extractStructure(content, filePath) {
|
|
181
|
+
const lang = getLanguage(filePath);
|
|
182
|
+
const lines = content.split("\n");
|
|
183
|
+
const output = [];
|
|
184
|
+
|
|
185
|
+
if (lang === "php") {
|
|
186
|
+
let insideClass = false;
|
|
187
|
+
let braceDepth = 0;
|
|
188
|
+
let classStartDepth = 0;
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
const trimmed = line.trim();
|
|
191
|
+
if (trimmed.startsWith("namespace ") || trimmed.startsWith("use ")) { output.push(line); continue; }
|
|
192
|
+
const classMatch = trimmed.match(/^(abstract\s+class|class|interface|trait)\s+(\w+)(\s+extends\s+\w+)?(\s+implements\s+[\w,\s]+)?/);
|
|
193
|
+
if (classMatch) {
|
|
194
|
+
if (output.length && output[output.length - 1] !== "") output.push("");
|
|
195
|
+
output.push(line); insideClass = true; classStartDepth = braceDepth;
|
|
196
|
+
}
|
|
197
|
+
braceDepth += (line.match(/\{/g) || []).length;
|
|
198
|
+
braceDepth -= (line.match(/\}/g) || []).length;
|
|
199
|
+
if (insideClass && braceDepth <= classStartDepth && trimmed === "}") {
|
|
200
|
+
output.push("}"); insideClass = false; output.push(""); continue;
|
|
201
|
+
}
|
|
202
|
+
if (trimmed.startsWith("/**") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
|
|
203
|
+
if (insideClass) output.push(line); continue;
|
|
204
|
+
}
|
|
205
|
+
if (insideClass) {
|
|
206
|
+
const methodMatch = trimmed.match(/^(public|protected|private|static|abstract|final|\s)*(function\s+\w+\s*\([^)]*\)(\s*:\s*[\w\?\|\\]+)?)/);
|
|
207
|
+
if (methodMatch) output.push(` ${methodMatch[0].trim()} { ... }`);
|
|
208
|
+
if (trimmed.match(/^(public|protected|private|const)\s+/) && trimmed.endsWith(";") && !trimmed.includes("function"))
|
|
209
|
+
output.push(` ${trimmed}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return output.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (["javascript", "typescript"].includes(lang)) {
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const trimmed = line.trim();
|
|
218
|
+
if (trimmed.match(/^(export\s+)?(abstract\s+)?class\s+\w+/)) { output.push(line); continue; }
|
|
219
|
+
if (trimmed.match(/^(export\s+)?(async\s+)?function\s+\w+\s*\(/)) {
|
|
220
|
+
output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
|
|
221
|
+
}
|
|
222
|
+
if (trimmed.match(/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/)) {
|
|
223
|
+
output.push(`${line.match(/^\s*/)[0]}${trimmed.split("=>")[0].trim()} => { ... }`); continue;
|
|
224
|
+
}
|
|
225
|
+
if (trimmed.match(/^(async\s+)?(\w+)\s*\([^)]*\)\s*(\:\s*[\w<>\[\]]+)?\s*\{/) &&
|
|
226
|
+
!["if", "while", "for", "switch"].some(k => trimmed.startsWith(k))) {
|
|
227
|
+
output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
|
|
228
|
+
}
|
|
229
|
+
if (trimmed.startsWith("import ")) output.push(line);
|
|
230
|
+
if (trimmed === "}") output.push(line);
|
|
231
|
+
}
|
|
232
|
+
return output.join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (lang === "python") {
|
|
236
|
+
for (const line of lines) {
|
|
237
|
+
const t = line.trim();
|
|
238
|
+
if (t.startsWith("class ") || t.startsWith("def ") || t.startsWith("async def "))
|
|
239
|
+
output.push(line.replace(/:[\s\S]*$/, ":"));
|
|
240
|
+
if (t.startsWith("import ") || t.startsWith("from ")) output.push(line);
|
|
241
|
+
}
|
|
242
|
+
return output.join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return lines.slice(0, 30).join("\n") + "\n... (structure extraction not supported for this language)";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// โโโ REVIEW GRAPH โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
249
|
+
|
|
250
|
+
function buildReviewGraph(files, cwd) {
|
|
251
|
+
const fileFunctions = {};
|
|
252
|
+
const fileImports = {};
|
|
253
|
+
|
|
254
|
+
for (const filePath of files.slice(0, 25)) {
|
|
255
|
+
const lang = getLanguage(filePath);
|
|
256
|
+
if (!["javascript", "typescript", "php", "python"].includes(lang)) continue;
|
|
257
|
+
let content;
|
|
258
|
+
try { content = fs.readFileSync(filePath, "utf8"); } catch { continue; }
|
|
259
|
+
const rel = path.relative(cwd, filePath);
|
|
260
|
+
const nodeId = rel.replace(/[^a-zA-Z0-9]/g, "_");
|
|
261
|
+
fileFunctions[rel] = { nodeId, functions: [] };
|
|
262
|
+
fileImports[rel] = [];
|
|
263
|
+
for (const line of content.split("\n")) {
|
|
264
|
+
const t = line.trim();
|
|
265
|
+
const fnMatch = t.match(/^(export\s+)?(async\s+)?function\s+(\w+)/) ||
|
|
266
|
+
t.match(/^(public|protected|private|static)?\s*(function\s+(\w+))/) ||
|
|
267
|
+
t.match(/^def\s+(\w+)/);
|
|
268
|
+
if (fnMatch) {
|
|
269
|
+
const name = fnMatch[3] || fnMatch[2] || fnMatch[1];
|
|
270
|
+
if (name && name.length > 2 && !["if", "for", "while", "switch"].includes(name))
|
|
271
|
+
fileFunctions[rel].functions.push(name);
|
|
272
|
+
}
|
|
273
|
+
const imp = t.match(/require\(['"]([^'"]+)['"]\)/) ||
|
|
274
|
+
t.match(/^import\s+.*from\s+['"]([^'"]+)['"]/) ||
|
|
275
|
+
t.match(/^use\s+(App\\[^;]+)/);
|
|
276
|
+
if (imp) fileImports[rel].push(imp[1]);
|
|
277
|
+
}
|
|
278
|
+
fileFunctions[rel].functions = [...new Set(fileFunctions[rel].functions)].slice(0, 5);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const fileList = Object.keys(fileFunctions);
|
|
282
|
+
if (fileList.length === 0) return null;
|
|
283
|
+
|
|
284
|
+
let mermaid = "```mermaid\nflowchart TD\n %% Code Review Graph โ generated by repomeld\n\n";
|
|
285
|
+
for (const rel of fileList) {
|
|
286
|
+
const { nodeId, functions } = fileFunctions[rel];
|
|
287
|
+
const label = path.basename(rel);
|
|
288
|
+
const fnList = functions.length ? `\\n[${functions.join(", ")}]` : "";
|
|
289
|
+
mermaid += ` ${nodeId}["๐ ${label}${fnList}"]\n`;
|
|
290
|
+
}
|
|
291
|
+
mermaid += "\n";
|
|
292
|
+
let edgeCount = 0;
|
|
293
|
+
for (const rel of fileList) {
|
|
294
|
+
for (const imp of fileImports[rel]) {
|
|
295
|
+
const matched = fileList.find(f => {
|
|
296
|
+
const base = path.basename(f, path.extname(f));
|
|
297
|
+
return imp.endsWith(base) || imp.includes(base);
|
|
298
|
+
});
|
|
299
|
+
if (matched && matched !== rel && edgeCount < 40) {
|
|
300
|
+
mermaid += ` ${fileFunctions[rel].nodeId} --> ${fileFunctions[matched].nodeId}\n`;
|
|
301
|
+
edgeCount++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return mermaid + "```\n";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// โโโ INTERACTIVE WIZARD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
169
309
|
|
|
310
|
+
function ask(rl, question) {
|
|
311
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function runWizard() {
|
|
315
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
316
|
+
|
|
317
|
+
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
318
|
+
console.log(" โ Interactive setup โ answer 1 quick question โ");
|
|
319
|
+
console.log(" โ Press Enter to accept the [default] value โ");
|
|
320
|
+
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
|
|
321
|
+
|
|
322
|
+
const opts = {};
|
|
323
|
+
|
|
324
|
+
// 1 โ Extensions
|
|
325
|
+
console.log(" ๐ 1/1 Filter to specific extensions?");
|
|
326
|
+
console.log(" e.g. js ts py (leave blank = all files)");
|
|
327
|
+
const extRaw = await ask(rl, " > ");
|
|
328
|
+
opts.ext = extRaw.trim() ? extRaw.trim().split(/\s+/) : [];
|
|
329
|
+
|
|
330
|
+
// hardcoded defaults โ not optional
|
|
331
|
+
opts.output = "repomeld_output.txt";
|
|
332
|
+
opts.style = "banner";
|
|
333
|
+
opts.reviewGraph = true;
|
|
334
|
+
opts.noToc = false;
|
|
335
|
+
opts.dryRun = false;
|
|
336
|
+
opts.structureOnly = false;
|
|
337
|
+
|
|
338
|
+
rl.close();
|
|
339
|
+
|
|
340
|
+
// Summary
|
|
341
|
+
console.log("\n โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
342
|
+
console.log(" โ Your configuration โ");
|
|
343
|
+
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค");
|
|
344
|
+
console.log(` โ Extensions : ${(opts.ext.length ? opts.ext.join(", ") : "all").padEnd(33)}โ`);
|
|
345
|
+
console.log(` โ Style : banner (always) โ`);
|
|
346
|
+
console.log(` โ Graph : yes (always) โ`);
|
|
347
|
+
console.log(` โ TOC : yes (always) โ`);
|
|
348
|
+
console.log(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
|
|
349
|
+
|
|
350
|
+
return opts;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// โโโ CORE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
354
|
+
|
|
355
|
+
function repomeld(options) {
|
|
170
356
|
const cwd = process.cwd();
|
|
171
|
-
|
|
357
|
+
|
|
358
|
+
// Always banner, always review graph, always TOC
|
|
359
|
+
options.style = "banner";
|
|
360
|
+
options.reviewGraph = true;
|
|
361
|
+
options.noToc = false;
|
|
362
|
+
|
|
363
|
+
const outputFile = resolveOutputPath(cwd, options.output || "repomeld_output.txt");
|
|
172
364
|
const forceInclude = options.forceInclude || [];
|
|
173
365
|
const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
|
|
174
366
|
const ignoreList = forceInclude.length
|
|
175
|
-
? rawIgnore.filter(
|
|
367
|
+
? rawIgnore.filter(ig => !forceInclude.some(fi => ig.includes(fi) || fi.includes(ig)))
|
|
176
368
|
: rawIgnore;
|
|
177
|
-
const filterExts
|
|
369
|
+
const filterExts = options.ext || [];
|
|
178
370
|
const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log(`\n ๐ Source : ${cwd}`);
|
|
371
|
+
const showMeta = options.noMeta !== true;
|
|
372
|
+
const dryRun = options.dryRun || false;
|
|
373
|
+
const include = options.include || [];
|
|
374
|
+
const exclude = options.exclude || [];
|
|
375
|
+
const linesBefore = parseInt(options.linesBefore) || 0;
|
|
376
|
+
const linesAfter = parseInt(options.linesAfter) || 0;
|
|
377
|
+
const structureOnly = options.structureOnly || false;
|
|
378
|
+
|
|
379
|
+
console.log(` ๐ Source : ${cwd}`);
|
|
189
380
|
console.log(` ๐ Output : ${path.relative(cwd, outputFile)}`);
|
|
190
|
-
console.log(` ๐จ Style :
|
|
381
|
+
console.log(` ๐จ Style : banner`);
|
|
191
382
|
if (filterExts.length) console.log(` ๐ Filter : .${filterExts.join(", .")}`);
|
|
192
|
-
if (
|
|
193
|
-
|
|
383
|
+
if (structureOnly) console.log(` ๐๏ธ Mode : structure only`);
|
|
384
|
+
console.log(` ๐ Graph : review graph enabled`);
|
|
385
|
+
if (dryRun) console.log(` ๐งช Dry run : no file will be written`);
|
|
194
386
|
console.log();
|
|
195
387
|
|
|
196
388
|
let allFiles = getAllFiles(cwd, ignoreList);
|
|
389
|
+
if (filterExts.length) allFiles = allFiles.filter(f => matchesExtensions(f, filterExts));
|
|
390
|
+
if (include.length) allFiles = allFiles.filter(f => matchesPattern(f, include));
|
|
391
|
+
if (exclude.length) allFiles = allFiles.filter(f => !matchesPattern(f, exclude));
|
|
392
|
+
allFiles = allFiles.filter(f => path.resolve(f) !== outputFile);
|
|
197
393
|
|
|
198
|
-
|
|
199
|
-
if (filterExts.length) {
|
|
200
|
-
allFiles = allFiles.filter((f) => matchesExtensions(f, filterExts));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Include pattern filter
|
|
204
|
-
if (include.length) {
|
|
205
|
-
allFiles = allFiles.filter((f) => matchesPattern(f, include));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Exclude pattern filter
|
|
209
|
-
if (exclude.length) {
|
|
210
|
-
allFiles = allFiles.filter((f) => !matchesPattern(f, exclude));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Remove output file from list
|
|
214
|
-
allFiles = allFiles.filter((f) => path.resolve(f) !== outputFile);
|
|
215
|
-
|
|
216
|
-
if (allFiles.length === 0) {
|
|
217
|
-
console.log(" โ ๏ธ No matching files found.\n");
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
394
|
+
if (allFiles.length === 0) { console.log(" โ ๏ธ No matching files found.\n"); return; }
|
|
220
395
|
|
|
221
396
|
let combinedContent = "";
|
|
222
|
-
let skipped = 0;
|
|
223
|
-
let included = 0;
|
|
224
|
-
let totalLines = 0;
|
|
397
|
+
let skipped = 0, included = 0, totalLines = 0;
|
|
225
398
|
const includedFiles = [];
|
|
226
399
|
|
|
227
400
|
for (const filePath of allFiles) {
|
|
228
401
|
const relativePath = path.relative(cwd, filePath);
|
|
229
|
-
|
|
230
|
-
if (isBinaryFile(filePath)) {
|
|
231
|
-
console.log(` โญ Binary : ${relativePath}`);
|
|
232
|
-
skipped++;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
|
|
402
|
+
if (isBinaryFile(filePath)) { console.log(` โญ Binary : ${relativePath}`); skipped++; continue; }
|
|
236
403
|
const stat = fs.statSync(filePath);
|
|
237
|
-
if (stat.size > maxFileSizeBytes) {
|
|
238
|
-
console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`);
|
|
239
|
-
skipped++;
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
404
|
+
if (stat.size > maxFileSizeBytes) { console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`); skipped++; continue; }
|
|
243
405
|
try {
|
|
244
406
|
let content = fs.readFileSync(filePath, "utf8");
|
|
245
|
-
|
|
246
|
-
// Trim leading/trailing blank lines if requested
|
|
247
|
-
if (options.trim) {
|
|
248
|
-
content = content.trim();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Slice specific lines
|
|
407
|
+
if (options.trim) content = content.trim();
|
|
252
408
|
if (linesBefore > 0 || linesAfter > 0) {
|
|
253
409
|
const lines = content.split("\n");
|
|
254
|
-
|
|
255
|
-
const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
|
|
256
|
-
content = lines.slice(start, end).join("\n");
|
|
410
|
+
content = lines.slice(linesBefore, linesAfter > 0 ? lines.length - linesAfter : lines.length).join("\n");
|
|
257
411
|
}
|
|
258
|
-
|
|
412
|
+
if (structureOnly) content = extractStructure(content, filePath);
|
|
259
413
|
const lineCount = content.split("\n").length;
|
|
260
414
|
totalLines += lineCount;
|
|
261
415
|
includedFiles.push(filePath);
|
|
262
|
-
|
|
263
|
-
combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
|
|
416
|
+
combinedContent += buildHeader(relativePath, filePath, lineCount, showMeta);
|
|
264
417
|
combinedContent += content;
|
|
265
|
-
combinedContent += buildFooter(
|
|
266
|
-
|
|
418
|
+
combinedContent += buildFooter();
|
|
267
419
|
console.log(` โ
${relativePath}`);
|
|
268
420
|
included++;
|
|
269
421
|
} catch (err) {
|
|
@@ -272,29 +424,36 @@ function repomeld(options) {
|
|
|
272
424
|
}
|
|
273
425
|
}
|
|
274
426
|
|
|
275
|
-
// Build final output
|
|
427
|
+
// Build final output: metadata โ review graph โ file tree โ TOC โ file contents
|
|
276
428
|
let finalOutput = "";
|
|
277
|
-
|
|
278
|
-
// Top-level comment
|
|
279
|
-
const timestamp = new Date().toISOString();
|
|
280
429
|
finalOutput += `# Generated by repomeld v${VERSION}\n`;
|
|
281
|
-
finalOutput += `# Date : ${
|
|
430
|
+
finalOutput += `# Date : ${new Date().toISOString()}\n`;
|
|
282
431
|
finalOutput += `# Source : ${cwd}\n`;
|
|
283
432
|
finalOutput += `# Files : ${included}\n`;
|
|
284
|
-
finalOutput += `# Lines : ${totalLines}\n
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
433
|
+
finalOutput += `# Lines : ${totalLines}\n`;
|
|
434
|
+
if (structureOnly) finalOutput += `# Mode : structure-only (signatures)\n`;
|
|
435
|
+
finalOutput += `# Graph : review-graph enabled\n`;
|
|
436
|
+
finalOutput += `# Author : susheelhbti@gmail.com (open to work)\n\n`;
|
|
437
|
+
|
|
438
|
+
// Review graph (always)
|
|
439
|
+
const graph = buildReviewGraph(includedFiles, cwd);
|
|
440
|
+
if (graph) {
|
|
441
|
+
finalOutput += "โ".repeat(60) + "\n CODE REVIEW GRAPH\n" + "โ".repeat(60) + "\n\n";
|
|
442
|
+
finalOutput += graph + "\n";
|
|
288
443
|
}
|
|
289
444
|
|
|
445
|
+
// File tree (always)
|
|
446
|
+
finalOutput += buildFileTree(includedFiles, cwd);
|
|
447
|
+
|
|
448
|
+
// Table of contents (always)
|
|
449
|
+
finalOutput += buildTableOfContents(includedFiles, cwd);
|
|
450
|
+
|
|
451
|
+
// File contents
|
|
290
452
|
finalOutput += combinedContent;
|
|
291
453
|
|
|
292
|
-
if (!dryRun)
|
|
293
|
-
fs.writeFileSync(outputFile, finalOutput, "utf8");
|
|
294
|
-
}
|
|
454
|
+
if (!dryRun) fs.writeFileSync(outputFile, finalOutput, "utf8");
|
|
295
455
|
|
|
296
456
|
const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
|
|
297
|
-
|
|
298
457
|
console.log(`
|
|
299
458
|
โจ repomeld complete!
|
|
300
459
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -302,41 +461,45 @@ function repomeld(options) {
|
|
|
302
461
|
โญ Skipped : ${skipped} files
|
|
303
462
|
๐ Lines : ${totalLines}
|
|
304
463
|
๐พ Size : ${outputSize}
|
|
305
|
-
๐ Output : ${
|
|
464
|
+
๐ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run โ not written)" : ""}
|
|
306
465
|
`);
|
|
466
|
+
console.log(AUTHOR_NOTE + "\n");
|
|
307
467
|
}
|
|
308
468
|
|
|
309
|
-
// โโโ CLI
|
|
469
|
+
// โโโ CLI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
310
470
|
|
|
311
471
|
program
|
|
312
472
|
.name("repomeld")
|
|
313
473
|
.description("Meld your entire repo into a single file โ perfect for AI context, code reviews & sharing")
|
|
314
474
|
.version(VERSION)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.option("
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.option("-
|
|
321
|
-
.option("--
|
|
322
|
-
.option("--
|
|
323
|
-
.option("
|
|
324
|
-
.option("--
|
|
325
|
-
.option("--
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
.option("
|
|
329
|
-
.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
475
|
+
.option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
|
|
476
|
+
.option("-e, --ext <exts...>", "Only include specific extensions")
|
|
477
|
+
.option("--include <patterns...>", "Only include files matching patterns")
|
|
478
|
+
.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 ignored by default")
|
|
481
|
+
.option("--max-size <kb>", "Skip files larger than N KB", "500")
|
|
482
|
+
.option("--no-meta", "Hide file metadata")
|
|
483
|
+
.option("--trim", "Trim leading/trailing whitespace per file")
|
|
484
|
+
.option("--lines-before <n>", "Skip first N lines of each file")
|
|
485
|
+
.option("--lines-after <n>", "Skip last N lines of each file")
|
|
486
|
+
.option("--dry-run", "Preview without writing output")
|
|
487
|
+
.option("--structure-only", "Extract signatures only โ no body code")
|
|
488
|
+
.option("--wizard", "Interactive step-by-step setup (auto when no flags given)")
|
|
489
|
+
.action(async (options) => {
|
|
490
|
+
printBanner();
|
|
491
|
+
|
|
492
|
+
// Auto-wizard when user runs plain `repomeld` with no flags
|
|
493
|
+
const hasFlags = options.ext || options.include || options.exclude ||
|
|
494
|
+
options.structureOnly || options.output !== "repomeld_output.txt" ||
|
|
495
|
+
options.dryRun;
|
|
496
|
+
|
|
497
|
+
if (options.wizard || !hasFlags) {
|
|
498
|
+
const wizardOpts = await runWizard();
|
|
499
|
+
repomeld({ ...options, ...wizardOpts });
|
|
500
|
+
} else {
|
|
501
|
+
repomeld(options);
|
|
502
|
+
}
|
|
340
503
|
});
|
|
341
504
|
|
|
342
505
|
program.parse(process.argv);
|