repomeld 2.0.3 โ 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 +209 -332
- 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) {
|
|
@@ -93,329 +122,186 @@ function formatSize(bytes) {
|
|
|
93
122
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
94
123
|
}
|
|
95
124
|
|
|
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
|
-
|
|
112
125
|
function printBanner() {
|
|
113
126
|
console.log(`
|
|
114
|
-
|
|
115
|
-
โ
|
|
116
|
-
โ
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`);
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
function buildHeader(relativePath, filePath, lineCount, showMeta) {
|
|
136
|
+
function buildHeader(style, relativePath, filePath, lineCount, showMeta) {
|
|
125
137
|
const lang = getLanguage(filePath);
|
|
126
138
|
const size = formatSize(fs.statSync(filePath).size);
|
|
127
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
|
|
128
148
|
const divider = "โ".repeat(60);
|
|
129
149
|
return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
|
|
130
150
|
}
|
|
131
151
|
|
|
132
|
-
function buildFooter() {
|
|
152
|
+
function buildFooter(style) {
|
|
153
|
+
if (style === "markdown") return "\n```\n";
|
|
133
154
|
return "\n";
|
|
134
155
|
}
|
|
135
156
|
|
|
136
|
-
// โโโ TABLE OF CONTENTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
137
|
-
|
|
138
157
|
function buildTableOfContents(files, cwd) {
|
|
139
158
|
let toc = "TABLE OF CONTENTS\n" + "โ".repeat(60) + "\n";
|
|
140
159
|
files.forEach((f, i) => {
|
|
141
|
-
|
|
160
|
+
const rel = path.relative(cwd, f);
|
|
161
|
+
toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
|
|
142
162
|
});
|
|
143
163
|
toc += "โ".repeat(60) + "\n\n";
|
|
144
164
|
return toc;
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
}
|
|
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++;
|
|
304
183
|
}
|
|
305
|
-
return mermaid + "```\n";
|
|
306
184
|
}
|
|
307
185
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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);
|
|
312
196
|
}
|
|
313
197
|
|
|
314
|
-
|
|
315
|
-
|
|
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;
|
|
198
|
+
function escapeRegex(str) {
|
|
199
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
351
200
|
}
|
|
352
201
|
|
|
353
|
-
// โโโ CORE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
354
|
-
|
|
355
202
|
function repomeld(options) {
|
|
203
|
+
printBanner();
|
|
204
|
+
|
|
356
205
|
const cwd = process.cwd();
|
|
357
206
|
|
|
358
|
-
//
|
|
359
|
-
options.
|
|
360
|
-
|
|
361
|
-
options.
|
|
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);
|
|
362
211
|
|
|
363
|
-
const outputFile = resolveOutputPath(cwd, options.output || "repomeld_output.txt");
|
|
364
212
|
const forceInclude = options.forceInclude || [];
|
|
365
213
|
const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
|
|
366
214
|
const ignoreList = forceInclude.length
|
|
367
|
-
? 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)))
|
|
368
216
|
: rawIgnore;
|
|
369
|
-
const filterExts
|
|
217
|
+
const filterExts = options.ext || [];
|
|
370
218
|
const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
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}`);
|
|
380
229
|
console.log(` ๐ Output : ${path.relative(cwd, outputFile)}`);
|
|
381
|
-
console.log(` ๐จ Style :
|
|
230
|
+
console.log(` ๐จ Style : ${headerStyle}`);
|
|
382
231
|
if (filterExts.length) console.log(` ๐ Filter : .${filterExts.join(", .")}`);
|
|
383
|
-
if (
|
|
384
|
-
console.log(`
|
|
385
|
-
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`);
|
|
386
234
|
console.log();
|
|
387
235
|
|
|
388
236
|
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);
|
|
393
237
|
|
|
394
|
-
|
|
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
|
+
}
|
|
395
260
|
|
|
396
261
|
let combinedContent = "";
|
|
397
|
-
let skipped = 0
|
|
262
|
+
let skipped = 0;
|
|
263
|
+
let included = 0;
|
|
264
|
+
let totalLines = 0;
|
|
398
265
|
const includedFiles = [];
|
|
399
266
|
|
|
400
267
|
for (const filePath of allFiles) {
|
|
401
268
|
const relativePath = path.relative(cwd, filePath);
|
|
402
|
-
|
|
269
|
+
|
|
270
|
+
if (isBinaryFile(filePath)) {
|
|
271
|
+
console.log(` โญ Binary : ${relativePath}`);
|
|
272
|
+
skipped++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
403
276
|
const stat = fs.statSync(filePath);
|
|
404
|
-
if (stat.size > maxFileSizeBytes) {
|
|
277
|
+
if (stat.size > maxFileSizeBytes) {
|
|
278
|
+
console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`);
|
|
279
|
+
skipped++;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
405
283
|
try {
|
|
406
284
|
let content = fs.readFileSync(filePath, "utf8");
|
|
407
|
-
|
|
285
|
+
|
|
286
|
+
if (options.trim) {
|
|
287
|
+
content = content.trim();
|
|
288
|
+
}
|
|
289
|
+
|
|
408
290
|
if (linesBefore > 0 || linesAfter > 0) {
|
|
409
291
|
const lines = content.split("\n");
|
|
410
|
-
|
|
292
|
+
const start = linesBefore;
|
|
293
|
+
const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
|
|
294
|
+
content = lines.slice(start, end).join("\n");
|
|
411
295
|
}
|
|
412
|
-
|
|
296
|
+
|
|
413
297
|
const lineCount = content.split("\n").length;
|
|
414
298
|
totalLines += lineCount;
|
|
415
299
|
includedFiles.push(filePath);
|
|
416
|
-
|
|
300
|
+
|
|
301
|
+
combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
|
|
417
302
|
combinedContent += content;
|
|
418
|
-
combinedContent += buildFooter();
|
|
303
|
+
combinedContent += buildFooter(headerStyle);
|
|
304
|
+
|
|
419
305
|
console.log(` โ
${relativePath}`);
|
|
420
306
|
included++;
|
|
421
307
|
} catch (err) {
|
|
@@ -424,82 +310,73 @@ function repomeld(options) {
|
|
|
424
310
|
}
|
|
425
311
|
}
|
|
426
312
|
|
|
427
|
-
// Build final output
|
|
313
|
+
// Build final output
|
|
428
314
|
let finalOutput = "";
|
|
315
|
+
|
|
316
|
+
const timestamp = new Date().toISOString();
|
|
429
317
|
finalOutput += `# Generated by repomeld v${VERSION}\n`;
|
|
430
|
-
finalOutput += `# Date : ${
|
|
318
|
+
finalOutput += `# Date : ${timestamp}\n`;
|
|
431
319
|
finalOutput += `# Source : ${cwd}\n`;
|
|
432
320
|
finalOutput += `# Files : ${included}\n`;
|
|
433
321
|
finalOutput += `# Lines : ${totalLines}\n`;
|
|
434
|
-
|
|
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";
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// File tree (always)
|
|
446
|
-
finalOutput += buildFileTree(includedFiles, cwd);
|
|
322
|
+
finalOutput += `# Author : susheelhbti@gmail.com โ available for freelance & full-time work\n\n`;
|
|
447
323
|
|
|
448
|
-
|
|
449
|
-
|
|
324
|
+
if (showToc) {
|
|
325
|
+
finalOutput += buildTableOfContents(includedFiles, cwd);
|
|
326
|
+
}
|
|
450
327
|
|
|
451
|
-
// File contents
|
|
452
328
|
finalOutput += combinedContent;
|
|
453
329
|
|
|
454
|
-
if (!dryRun)
|
|
330
|
+
if (!dryRun) {
|
|
331
|
+
fs.writeFileSync(outputFile, finalOutput, "utf8");
|
|
332
|
+
}
|
|
455
333
|
|
|
456
334
|
const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
|
|
335
|
+
|
|
457
336
|
console.log(`
|
|
458
337
|
โจ repomeld complete!
|
|
459
|
-
|
|
338
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
460
339
|
โ
Included : ${included} files
|
|
461
340
|
โญ Skipped : ${skipped} files
|
|
462
341
|
๐ Lines : ${totalLines}
|
|
463
342
|
๐พ Size : ${outputSize}
|
|
464
343
|
๐ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run โ not written)" : ""}
|
|
344
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
345
|
+
๐ผ Need a developer? susheelhbti@gmail.com
|
|
465
346
|
`);
|
|
466
|
-
console.log(AUTHOR_NOTE + "\n");
|
|
467
347
|
}
|
|
468
348
|
|
|
469
|
-
// โโโ CLI
|
|
349
|
+
// โโโ CLI Definition โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
470
350
|
|
|
471
351
|
program
|
|
472
352
|
.name("repomeld")
|
|
473
353
|
.description("Meld your entire repo into a single file โ perfect for AI context, code reviews & sharing")
|
|
474
354
|
.version(VERSION)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
.option("--
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
.option("
|
|
481
|
-
.option("--
|
|
482
|
-
.option("--
|
|
483
|
-
.option("--
|
|
484
|
-
.option("--
|
|
485
|
-
.option("--
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
.option("--
|
|
489
|
-
.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
} else {
|
|
501
|
-
repomeld(options);
|
|
502
|
-
}
|
|
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);
|
|
503
380
|
});
|
|
504
381
|
|
|
505
|
-
program.parse(process.argv);
|
|
382
|
+
program.parse(process.argv);
|