repomeld 2.0.2 → 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/bin/cli.js +98 -77
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -93,6 +93,22 @@ function formatSize(bytes) {
|
|
|
93
93
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
94
94
|
}
|
|
95
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
|
+
|
|
96
112
|
function printBanner() {
|
|
97
113
|
console.log(`
|
|
98
114
|
╔══════════════════════════════════════╗
|
|
@@ -103,20 +119,22 @@ function printBanner() {
|
|
|
103
119
|
console.log();
|
|
104
120
|
}
|
|
105
121
|
|
|
106
|
-
|
|
122
|
+
// ─── HEADER / FOOTER ─────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
function buildHeader(relativePath, filePath, lineCount, showMeta) {
|
|
107
125
|
const lang = getLanguage(filePath);
|
|
108
126
|
const size = formatSize(fs.statSync(filePath).size);
|
|
109
127
|
const meta = showMeta ? ` [${lineCount} lines | ${size}${lang ? " | " + lang : ""}]` : "";
|
|
110
|
-
if (style === "markdown") return `\n## 📄 ${relativePath}${meta}\n\n\`\`\`${lang}\n`;
|
|
111
|
-
if (style === "minimal") return `\n# ${relativePath}\n`;
|
|
112
128
|
const divider = "─".repeat(60);
|
|
113
129
|
return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
function buildFooter(
|
|
117
|
-
return
|
|
132
|
+
function buildFooter() {
|
|
133
|
+
return "\n";
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
// ─── TABLE OF CONTENTS ────────────────────────────────────────────────────────
|
|
137
|
+
|
|
120
138
|
function buildTableOfContents(files, cwd) {
|
|
121
139
|
let toc = "TABLE OF CONTENTS\n" + "═".repeat(60) + "\n";
|
|
122
140
|
files.forEach((f, i) => {
|
|
@@ -126,6 +144,37 @@ function buildTableOfContents(files, cwd) {
|
|
|
126
144
|
return toc;
|
|
127
145
|
}
|
|
128
146
|
|
|
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
|
+
|
|
129
178
|
// ─── STRUCTURE EXTRACTION ─────────────────────────────────────────────────────
|
|
130
179
|
|
|
131
180
|
function extractStructure(content, filePath) {
|
|
@@ -174,7 +223,7 @@ function extractStructure(content, filePath) {
|
|
|
174
223
|
output.push(`${line.match(/^\s*/)[0]}${trimmed.split("=>")[0].trim()} => { ... }`); continue;
|
|
175
224
|
}
|
|
176
225
|
if (trimmed.match(/^(async\s+)?(\w+)\s*\([^)]*\)\s*(\:\s*[\w<>\[\]]+)?\s*\{/) &&
|
|
177
|
-
!["if","while","for","switch"].some(k => trimmed.startsWith(k))) {
|
|
226
|
+
!["if", "while", "for", "switch"].some(k => trimmed.startsWith(k))) {
|
|
178
227
|
output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
|
|
179
228
|
}
|
|
180
229
|
if (trimmed.startsWith("import ")) output.push(line);
|
|
@@ -218,7 +267,7 @@ function buildReviewGraph(files, cwd) {
|
|
|
218
267
|
t.match(/^def\s+(\w+)/);
|
|
219
268
|
if (fnMatch) {
|
|
220
269
|
const name = fnMatch[3] || fnMatch[2] || fnMatch[1];
|
|
221
|
-
if (name && name.length > 2 && !["if","for","while","switch"].includes(name))
|
|
270
|
+
if (name && name.length > 2 && !["if", "for", "while", "switch"].includes(name))
|
|
222
271
|
fileFunctions[rel].functions.push(name);
|
|
223
272
|
}
|
|
224
273
|
const imp = t.match(/require\(['"]([^'"]+)['"]\)/) ||
|
|
@@ -266,55 +315,25 @@ async function runWizard() {
|
|
|
266
315
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
267
316
|
|
|
268
317
|
console.log(" ┌─────────────────────────────────────────────────┐");
|
|
269
|
-
console.log(" │ Interactive setup — answer
|
|
318
|
+
console.log(" │ Interactive setup — answer 1 quick question │");
|
|
270
319
|
console.log(" │ Press Enter to accept the [default] value │");
|
|
271
320
|
console.log(" └─────────────────────────────────────────────────┘\n");
|
|
272
321
|
|
|
273
322
|
const opts = {};
|
|
274
323
|
|
|
275
|
-
// 1 —
|
|
276
|
-
|
|
277
|
-
|
|
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)");
|
|
324
|
+
// 1 — Extensions
|
|
325
|
+
console.log(" 🔍 1/1 Filter to specific extensions?");
|
|
326
|
+
console.log(" e.g. js ts py (leave blank = all files)");
|
|
290
327
|
const extRaw = await ask(rl, " > ");
|
|
291
328
|
opts.ext = extRaw.trim() ? extRaw.trim().split(/\s+/) : [];
|
|
292
329
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
opts.
|
|
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";
|
|
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;
|
|
318
337
|
|
|
319
338
|
rl.close();
|
|
320
339
|
|
|
@@ -322,13 +341,10 @@ async function runWizard() {
|
|
|
322
341
|
console.log("\n ┌─────────────────────────────────────────────────┐");
|
|
323
342
|
console.log(" │ Your configuration │");
|
|
324
343
|
console.log(" ├─────────────────────────────────────────────────┤");
|
|
325
|
-
console.log(` │
|
|
326
|
-
console.log(` │ Style
|
|
327
|
-
console.log(` │
|
|
328
|
-
console.log(` │
|
|
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)}│`);
|
|
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) │`);
|
|
332
348
|
console.log(" └─────────────────────────────────────────────────┘\n");
|
|
333
349
|
|
|
334
350
|
return opts;
|
|
@@ -338,7 +354,13 @@ async function runWizard() {
|
|
|
338
354
|
|
|
339
355
|
function repomeld(options) {
|
|
340
356
|
const cwd = process.cwd();
|
|
341
|
-
|
|
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");
|
|
342
364
|
const forceInclude = options.forceInclude || [];
|
|
343
365
|
const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
|
|
344
366
|
const ignoreList = forceInclude.length
|
|
@@ -346,23 +368,20 @@ function repomeld(options) {
|
|
|
346
368
|
: rawIgnore;
|
|
347
369
|
const filterExts = options.ext || [];
|
|
348
370
|
const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
|
|
349
|
-
const headerStyle = options.style || "banner";
|
|
350
371
|
const showMeta = options.noMeta !== true;
|
|
351
|
-
const showToc = options.noToc !== true;
|
|
352
372
|
const dryRun = options.dryRun || false;
|
|
353
373
|
const include = options.include || [];
|
|
354
374
|
const exclude = options.exclude || [];
|
|
355
375
|
const linesBefore = parseInt(options.linesBefore) || 0;
|
|
356
376
|
const linesAfter = parseInt(options.linesAfter) || 0;
|
|
357
377
|
const structureOnly = options.structureOnly || false;
|
|
358
|
-
const reviewGraph = options.reviewGraph || false;
|
|
359
378
|
|
|
360
379
|
console.log(` 📂 Source : ${cwd}`);
|
|
361
380
|
console.log(` 📄 Output : ${path.relative(cwd, outputFile)}`);
|
|
362
|
-
console.log(` 🎨 Style :
|
|
381
|
+
console.log(` 🎨 Style : banner`);
|
|
363
382
|
if (filterExts.length) console.log(` 🔍 Filter : .${filterExts.join(", .")}`);
|
|
364
383
|
if (structureOnly) console.log(` 🏗️ Mode : structure only`);
|
|
365
|
-
|
|
384
|
+
console.log(` 🔗 Graph : review graph enabled`);
|
|
366
385
|
if (dryRun) console.log(` 🧪 Dry run : no file will be written`);
|
|
367
386
|
console.log();
|
|
368
387
|
|
|
@@ -394,9 +413,9 @@ function repomeld(options) {
|
|
|
394
413
|
const lineCount = content.split("\n").length;
|
|
395
414
|
totalLines += lineCount;
|
|
396
415
|
includedFiles.push(filePath);
|
|
397
|
-
combinedContent += buildHeader(
|
|
416
|
+
combinedContent += buildHeader(relativePath, filePath, lineCount, showMeta);
|
|
398
417
|
combinedContent += content;
|
|
399
|
-
combinedContent += buildFooter(
|
|
418
|
+
combinedContent += buildFooter();
|
|
400
419
|
console.log(` ✅ ${relativePath}`);
|
|
401
420
|
included++;
|
|
402
421
|
} catch (err) {
|
|
@@ -405,6 +424,7 @@ function repomeld(options) {
|
|
|
405
424
|
}
|
|
406
425
|
}
|
|
407
426
|
|
|
427
|
+
// Build final output: metadata → review graph → file tree → TOC → file contents
|
|
408
428
|
let finalOutput = "";
|
|
409
429
|
finalOutput += `# Generated by repomeld v${VERSION}\n`;
|
|
410
430
|
finalOutput += `# Date : ${new Date().toISOString()}\n`;
|
|
@@ -412,18 +432,23 @@ function repomeld(options) {
|
|
|
412
432
|
finalOutput += `# Files : ${included}\n`;
|
|
413
433
|
finalOutput += `# Lines : ${totalLines}\n`;
|
|
414
434
|
if (structureOnly) finalOutput += `# Mode : structure-only (signatures)\n`;
|
|
415
|
-
|
|
435
|
+
finalOutput += `# Graph : review-graph enabled\n`;
|
|
416
436
|
finalOutput += `# Author : susheelhbti@gmail.com (open to work)\n\n`;
|
|
417
437
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
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";
|
|
424
443
|
}
|
|
425
444
|
|
|
426
|
-
|
|
445
|
+
// File tree (always)
|
|
446
|
+
finalOutput += buildFileTree(includedFiles, cwd);
|
|
447
|
+
|
|
448
|
+
// Table of contents (always)
|
|
449
|
+
finalOutput += buildTableOfContents(includedFiles, cwd);
|
|
450
|
+
|
|
451
|
+
// File contents
|
|
427
452
|
finalOutput += combinedContent;
|
|
428
453
|
|
|
429
454
|
if (!dryRun) fs.writeFileSync(outputFile, finalOutput, "utf8");
|
|
@@ -436,7 +461,7 @@ function repomeld(options) {
|
|
|
436
461
|
⏭ Skipped : ${skipped} files
|
|
437
462
|
📏 Lines : ${totalLines}
|
|
438
463
|
💾 Size : ${outputSize}
|
|
439
|
-
📄 Output : ${
|
|
464
|
+
📄 Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run — not written)" : ""}
|
|
440
465
|
`);
|
|
441
466
|
console.log(AUTHOR_NOTE + "\n");
|
|
442
467
|
}
|
|
@@ -454,23 +479,19 @@ program
|
|
|
454
479
|
.option("-i, --ignore <names...>", "Extra folders/files to ignore")
|
|
455
480
|
.option("--force-include <names...>", "Force-include files ignored by default")
|
|
456
481
|
.option("--max-size <kb>", "Skip files larger than N KB", "500")
|
|
457
|
-
.option("-s, --style <style>", "Header style: banner | markdown | minimal","banner")
|
|
458
|
-
.option("--no-toc", "Disable table of contents")
|
|
459
482
|
.option("--no-meta", "Hide file metadata")
|
|
460
483
|
.option("--trim", "Trim leading/trailing whitespace per file")
|
|
461
484
|
.option("--lines-before <n>", "Skip first N lines of each file")
|
|
462
485
|
.option("--lines-after <n>", "Skip last N lines of each file")
|
|
463
486
|
.option("--dry-run", "Preview without writing output")
|
|
464
487
|
.option("--structure-only", "Extract signatures only — no body code")
|
|
465
|
-
.option("--review-graph", "Prepend a Mermaid call graph to output")
|
|
466
488
|
.option("--wizard", "Interactive step-by-step setup (auto when no flags given)")
|
|
467
489
|
.action(async (options) => {
|
|
468
490
|
printBanner();
|
|
469
491
|
|
|
470
492
|
// Auto-wizard when user runs plain `repomeld` with no flags
|
|
471
493
|
const hasFlags = options.ext || options.include || options.exclude ||
|
|
472
|
-
options.structureOnly || options.
|
|
473
|
-
options.style !== "banner" || options.output !== "repomeld_output.txt" ||
|
|
494
|
+
options.structureOnly || options.output !== "repomeld_output.txt" ||
|
|
474
495
|
options.dryRun;
|
|
475
496
|
|
|
476
497
|
if (options.wizard || !hasFlags) {
|