@cod3vil/kount-cli 1.0.0 → 1.0.1
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/dist/kount +0 -0
- package/dist/kount.js +44606 -0
- package/package.json +5 -5
- package/bin/kount.js +0 -2
- package/src/cli/.gitkeep +0 -0
- package/src/cli/config-resolver.ts +0 -175
- package/src/cli/parser.ts +0 -52
- package/src/core/.gitkeep +0 -0
- package/src/core/aggregator.ts +0 -204
- package/src/core/cache.ts +0 -130
- package/src/index.tsx +0 -167
- package/src/plugins/.gitkeep +0 -0
- package/src/plugins/built-in/blank-lines.ts +0 -26
- package/src/plugins/built-in/comment-lines.ts +0 -90
- package/src/plugins/built-in/file-size.ts +0 -20
- package/src/plugins/built-in/language-distribution.ts +0 -95
- package/src/plugins/built-in/largest-files.ts +0 -41
- package/src/plugins/built-in/total-files.ts +0 -18
- package/src/plugins/built-in/total-lines.ts +0 -21
- package/src/plugins/index.ts +0 -10
- package/src/plugins/types.ts +0 -58
- package/src/reporters/.gitkeep +0 -0
- package/src/reporters/html.ts +0 -385
- package/src/reporters/markdown.ts +0 -129
- package/src/reporters/terminal/Progress.tsx +0 -39
- package/src/reporters/terminal/Splash.tsx +0 -32
- package/src/reporters/terminal/Summary.tsx +0 -135
- package/src/reporters/terminal/Wizard.tsx +0 -125
- package/src/reporters/terminal/index.ts +0 -6
- package/src/scanner/.gitkeep +0 -0
- package/src/scanner/ignore-parser.ts +0 -168
- package/src/scanner/stream-reader.ts +0 -99
- package/src/utils/.gitkeep +0 -0
- package/src/utils/language-map.ts +0 -79
package/src/reporters/html.ts
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import http from 'node:http';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import type { ProjectStats } from '../plugins/types.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generates the full HTML dashboard page with injected data.
|
|
8
|
-
* Uses Tailwind CSS (CDN) and Alpine.js (CDN) for styling and interactivity.
|
|
9
|
-
*/
|
|
10
|
-
function generateHtmlDashboard(stats: ProjectStats): string {
|
|
11
|
-
const totalLines = stats.pluginResults.get('TotalLines')?.summaryValue ?? 0;
|
|
12
|
-
const blankLines = stats.pluginResults.get('BlankLines')?.summaryValue ?? 0;
|
|
13
|
-
const commentLines = stats.pluginResults.get('CommentLines')?.summaryValue ?? 0;
|
|
14
|
-
const totalBytes = stats.pluginResults.get('FileSize')?.summaryValue ?? 0;
|
|
15
|
-
const codeLines = totalLines - blankLines - commentLines;
|
|
16
|
-
const codeRatio = totalLines > 0 ? ((codeLines / totalLines) * 100).toFixed(1) : '0.0';
|
|
17
|
-
|
|
18
|
-
const formatSize = (bytes: number): string => {
|
|
19
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
20
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
21
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Prepare language data for Alpine.js
|
|
25
|
-
const langData = [...stats.languageDistribution.entries()]
|
|
26
|
-
.sort((a, b) => b[1] - a[1])
|
|
27
|
-
.map(([lang, count]) => ({
|
|
28
|
-
lang,
|
|
29
|
-
count,
|
|
30
|
-
pct: ((count / stats.totalFiles) * 100).toFixed(1),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
// Prepare largest files data
|
|
34
|
-
const largestData = stats.largestFiles.map((f, i) => ({
|
|
35
|
-
rank: i + 1,
|
|
36
|
-
path: path.relative(stats.rootDir, f.filePath),
|
|
37
|
-
size: formatSize(f.size),
|
|
38
|
-
rawSize: f.size,
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
const jsonData = JSON.stringify({
|
|
42
|
-
summary: {
|
|
43
|
-
files: stats.totalFiles,
|
|
44
|
-
totalLines,
|
|
45
|
-
codeLines,
|
|
46
|
-
commentLines,
|
|
47
|
-
blankLines,
|
|
48
|
-
codeRatio,
|
|
49
|
-
totalSize: formatSize(totalBytes),
|
|
50
|
-
},
|
|
51
|
-
languages: langData,
|
|
52
|
-
largestFiles: largestData,
|
|
53
|
-
scannedAt: stats.scannedAt.toISOString(),
|
|
54
|
-
rootDir: stats.rootDir,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return `<!DOCTYPE html>
|
|
58
|
-
<html lang="en">
|
|
59
|
-
<head>
|
|
60
|
-
<meta charset="UTF-8">
|
|
61
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
62
|
-
<title>KOUNT Dashboard</title>
|
|
63
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
64
|
-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
65
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
66
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
67
|
-
<style>
|
|
68
|
-
body { font-family: 'Inter', system-ui, sans-serif; }
|
|
69
|
-
[x-cloak] { display: none !important; }
|
|
70
|
-
</style>
|
|
71
|
-
</head>
|
|
72
|
-
<body class="bg-gray-950 text-white min-h-screen">
|
|
73
|
-
<div x-data="dashboard()" x-cloak class="max-w-6xl mx-auto px-6 py-8">
|
|
74
|
-
|
|
75
|
-
<!-- Header with Tab Navigation -->
|
|
76
|
-
<div class="mb-8">
|
|
77
|
-
<div class="flex items-center justify-between">
|
|
78
|
-
<div>
|
|
79
|
-
<h1 class="text-3xl font-bold text-cyan-400 tracking-tight">KOUNT</h1>
|
|
80
|
-
<p class="text-gray-400 mt-1">Project Intelligence for <span class="text-white font-mono" x-text="data.rootDir"></span></p>
|
|
81
|
-
</div>
|
|
82
|
-
<nav class="flex gap-1 bg-gray-900 rounded-lg p-1 border border-gray-800">
|
|
83
|
-
<button
|
|
84
|
-
@click="currentTab = 'dashboard'"
|
|
85
|
-
:class="currentTab === 'dashboard' ? 'bg-cyan-500/20 text-cyan-400' : 'text-gray-500 hover:text-gray-300'"
|
|
86
|
-
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200"
|
|
87
|
-
>Dashboard</button>
|
|
88
|
-
<button
|
|
89
|
-
@click="currentTab = 'help'"
|
|
90
|
-
:class="currentTab === 'help' ? 'bg-cyan-500/20 text-cyan-400' : 'text-gray-500 hover:text-gray-300'"
|
|
91
|
-
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200"
|
|
92
|
-
>Help</button>
|
|
93
|
-
</nav>
|
|
94
|
-
</div>
|
|
95
|
-
<p class="text-gray-600 text-sm mt-1" x-show="currentTab === 'dashboard'">Scanned <span x-text="new Date(data.scannedAt).toLocaleString()"></span></p>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<!-- ==================== DASHBOARD TAB ==================== -->
|
|
99
|
-
<div x-show="currentTab === 'dashboard'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-1" x-transition:enter-end="opacity-100 translate-y-0">
|
|
100
|
-
|
|
101
|
-
<!-- Summary Cards -->
|
|
102
|
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
|
103
|
-
<template x-for="card in summaryCards" :key="card.label">
|
|
104
|
-
<div class="bg-gray-900 rounded-xl p-4 border border-gray-800">
|
|
105
|
-
<p class="text-gray-400 text-sm" x-text="card.label"></p>
|
|
106
|
-
<p class="text-2xl font-bold mt-1" :class="card.color" x-text="card.value"></p>
|
|
107
|
-
</div>
|
|
108
|
-
</template>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
<!-- Language Distribution -->
|
|
112
|
-
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800 mb-8" x-show="data.languages.length > 0">
|
|
113
|
-
<div class="flex items-center justify-between mb-4">
|
|
114
|
-
<h2 class="text-lg font-semibold text-blue-400">Language Distribution</h2>
|
|
115
|
-
<button
|
|
116
|
-
@click="langSort = langSort === 'count' ? 'name' : 'count'"
|
|
117
|
-
class="text-sm text-gray-500 hover:text-white transition-colors px-3 py-1 rounded bg-gray-800"
|
|
118
|
-
x-text="'Sort by ' + (langSort === 'count' ? 'Name' : 'Count')"
|
|
119
|
-
></button>
|
|
120
|
-
</div>
|
|
121
|
-
<div class="space-y-2">
|
|
122
|
-
<template x-for="lang in sortedLanguages" :key="lang.lang">
|
|
123
|
-
<div class="flex items-center gap-3">
|
|
124
|
-
<span class="w-28 text-sm text-gray-300 truncate" x-text="lang.lang"></span>
|
|
125
|
-
<div class="flex-1 bg-gray-800 rounded-full h-5 overflow-hidden">
|
|
126
|
-
<div class="h-full bg-gradient-to-r from-cyan-600 to-blue-500 rounded-full transition-all duration-300"
|
|
127
|
-
:style="'width:' + lang.pct + '%'"></div>
|
|
128
|
-
</div>
|
|
129
|
-
<span class="text-sm text-gray-400 w-20 text-right" x-text="lang.count + ' (' + lang.pct + '%)'"></span>
|
|
130
|
-
</div>
|
|
131
|
-
</template>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<!-- Largest Files Table -->
|
|
136
|
-
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800" x-show="data.largestFiles.length > 0">
|
|
137
|
-
<h2 class="text-lg font-semibold text-purple-400 mb-4">Top Largest Files</h2>
|
|
138
|
-
<table class="w-full text-sm">
|
|
139
|
-
<thead>
|
|
140
|
-
<tr class="text-gray-500 border-b border-gray-800">
|
|
141
|
-
<th class="text-left py-2 w-12">#</th>
|
|
142
|
-
<th class="text-left py-2">File</th>
|
|
143
|
-
<th class="text-right py-2 w-24">Size</th>
|
|
144
|
-
</tr>
|
|
145
|
-
</thead>
|
|
146
|
-
<tbody>
|
|
147
|
-
<template x-for="file in data.largestFiles" :key="file.rank">
|
|
148
|
-
<tr class="border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors">
|
|
149
|
-
<td class="py-2 text-gray-500" x-text="file.rank"></td>
|
|
150
|
-
<td class="py-2 font-mono text-gray-300 truncate max-w-md" x-text="file.path"></td>
|
|
151
|
-
<td class="py-2 text-right text-yellow-400" x-text="file.size"></td>
|
|
152
|
-
</tr>
|
|
153
|
-
</template>
|
|
154
|
-
</tbody>
|
|
155
|
-
</table>
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<!-- ==================== HELP TAB ==================== -->
|
|
161
|
-
<div x-show="currentTab === 'help'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-1" x-transition:enter-end="opacity-100 translate-y-0">
|
|
162
|
-
|
|
163
|
-
<!-- About Kount -->
|
|
164
|
-
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800 mb-6">
|
|
165
|
-
<h2 class="text-lg font-semibold text-cyan-400 mb-3">About KOUNT</h2>
|
|
166
|
-
<p class="text-gray-300 leading-relaxed mb-4">
|
|
167
|
-
KOUNT is a codebase intelligence terminal tool that analyzes your projects with precision.
|
|
168
|
-
It streams files efficiently, respects <code class="text-cyan-300 bg-gray-800 px-1.5 py-0.5 rounded text-xs">.gitignore</code> rules,
|
|
169
|
-
caches results for speed, and outputs beautiful reports in your terminal, as Markdown, or as an interactive HTML dashboard.
|
|
170
|
-
</p>
|
|
171
|
-
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
172
|
-
<div class="bg-gray-800/50 rounded-lg p-3 border border-gray-700/50">
|
|
173
|
-
<p class="text-cyan-400 font-semibold text-sm">Stream-Based</p>
|
|
174
|
-
<p class="text-gray-400 text-xs mt-1">Files are read chunk-by-chunk, never fully loaded into memory.</p>
|
|
175
|
-
</div>
|
|
176
|
-
<div class="bg-gray-800/50 rounded-lg p-3 border border-gray-700/50">
|
|
177
|
-
<p class="text-green-400 font-semibold text-sm">Incremental Cache</p>
|
|
178
|
-
<p class="text-gray-400 text-xs mt-1">Uses mtime + size invalidation to skip unchanged files on re-runs.</p>
|
|
179
|
-
</div>
|
|
180
|
-
<div class="bg-gray-800/50 rounded-lg p-3 border border-gray-700/50">
|
|
181
|
-
<p class="text-purple-400 font-semibold text-sm">Plugin Architecture</p>
|
|
182
|
-
<p class="text-gray-400 text-xs mt-1">7 built-in analyzers: lines, blanks, comments, size, files, languages, largest.</p>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
|
|
187
|
-
<!-- CLI Commands & Flags -->
|
|
188
|
-
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800 mb-6">
|
|
189
|
-
<h2 class="text-lg font-semibold text-blue-400 mb-4">CLI Commands & Flags</h2>
|
|
190
|
-
<div class="bg-gray-800/50 rounded-lg p-4 mb-4 font-mono text-sm">
|
|
191
|
-
<p class="text-gray-400 mb-1"># Basic usage</p>
|
|
192
|
-
<p class="text-green-400">$ kount</p>
|
|
193
|
-
<p class="text-gray-400 mt-3 mb-1"># Scan a specific directory and output Markdown</p>
|
|
194
|
-
<p class="text-green-400">$ kount --root-dir ./my-project --output-mode markdown</p>
|
|
195
|
-
<p class="text-gray-400 mt-3 mb-1"># Open an HTML dashboard</p>
|
|
196
|
-
<p class="text-green-400">$ kount -o html</p>
|
|
197
|
-
</div>
|
|
198
|
-
<table class="w-full text-sm">
|
|
199
|
-
<thead>
|
|
200
|
-
<tr class="text-gray-500 border-b border-gray-800">
|
|
201
|
-
<th class="text-left py-2">Flag</th>
|
|
202
|
-
<th class="text-left py-2">Alias</th>
|
|
203
|
-
<th class="text-left py-2">Description</th>
|
|
204
|
-
</tr>
|
|
205
|
-
</thead>
|
|
206
|
-
<tbody class="text-gray-300">
|
|
207
|
-
<tr class="border-b border-gray-800/50">
|
|
208
|
-
<td class="py-2 font-mono text-cyan-300">--root-dir <path></td>
|
|
209
|
-
<td class="py-2 font-mono text-gray-500">-d</td>
|
|
210
|
-
<td class="py-2">Root directory to scan (default: current directory)</td>
|
|
211
|
-
</tr>
|
|
212
|
-
<tr class="border-b border-gray-800/50">
|
|
213
|
-
<td class="py-2 font-mono text-cyan-300">--output-mode <mode></td>
|
|
214
|
-
<td class="py-2 font-mono text-gray-500">-o</td>
|
|
215
|
-
<td class="py-2">Output mode: <span class="text-white">terminal</span>, <span class="text-white">markdown</span>, or <span class="text-white">html</span></td>
|
|
216
|
-
</tr>
|
|
217
|
-
<tr class="border-b border-gray-800/50">
|
|
218
|
-
<td class="py-2 font-mono text-cyan-300">--include-tests</td>
|
|
219
|
-
<td class="py-2 font-mono text-gray-500">-t</td>
|
|
220
|
-
<td class="py-2">Include test files in the analysis</td>
|
|
221
|
-
</tr>
|
|
222
|
-
<tr class="border-b border-gray-800/50">
|
|
223
|
-
<td class="py-2 font-mono text-cyan-300">--force</td>
|
|
224
|
-
<td class="py-2 font-mono text-gray-500">-f</td>
|
|
225
|
-
<td class="py-2">Force overwrite output files (markdown mode)</td>
|
|
226
|
-
</tr>
|
|
227
|
-
<tr class="border-b border-gray-800/50">
|
|
228
|
-
<td class="py-2 font-mono text-cyan-300">--output <path></td>
|
|
229
|
-
<td class="py-2 font-mono text-gray-500"></td>
|
|
230
|
-
<td class="py-2">Specify output file path (markdown mode)</td>
|
|
231
|
-
</tr>
|
|
232
|
-
<tr class="border-b border-gray-800/50">
|
|
233
|
-
<td class="py-2 font-mono text-cyan-300">--no-gitignore</td>
|
|
234
|
-
<td class="py-2 font-mono text-gray-500"></td>
|
|
235
|
-
<td class="py-2">Ignore .gitignore rules during scanning</td>
|
|
236
|
-
</tr>
|
|
237
|
-
<tr class="border-b border-gray-800/50">
|
|
238
|
-
<td class="py-2 font-mono text-cyan-300">--no-cache</td>
|
|
239
|
-
<td class="py-2 font-mono text-gray-500"></td>
|
|
240
|
-
<td class="py-2">Disable caching for this run</td>
|
|
241
|
-
</tr>
|
|
242
|
-
<tr class="border-b border-gray-800/50">
|
|
243
|
-
<td class="py-2 font-mono text-cyan-300">--clear-cache</td>
|
|
244
|
-
<td class="py-2 font-mono text-gray-500"></td>
|
|
245
|
-
<td class="py-2">Clear the cache before scanning</td>
|
|
246
|
-
</tr>
|
|
247
|
-
<tr>
|
|
248
|
-
<td class="py-2 font-mono text-cyan-300">--version</td>
|
|
249
|
-
<td class="py-2 font-mono text-gray-500">-V</td>
|
|
250
|
-
<td class="py-2">Display version number</td>
|
|
251
|
-
</tr>
|
|
252
|
-
</tbody>
|
|
253
|
-
</table>
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
<!-- Developer Info -->
|
|
257
|
-
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800">
|
|
258
|
-
<h2 class="text-lg font-semibold text-purple-400 mb-4">Developer</h2>
|
|
259
|
-
<div class="flex flex-col md:flex-row gap-6">
|
|
260
|
-
<div class="flex-1">
|
|
261
|
-
<h3 class="text-xl font-bold text-white mb-2">Michael Nji</h3>
|
|
262
|
-
<p class="text-gray-400 text-sm leading-relaxed mb-4">
|
|
263
|
-
Full stack web developer with a passion for building beautiful and robust web projects.
|
|
264
|
-
Coding since 2022, with 3+ years of experience building with modern web technologies.
|
|
265
|
-
Active open source contributor — including contributions to
|
|
266
|
-
<a href="https://github.com/biomejs/biome" target="_blank" class="text-cyan-400 hover:text-cyan-300 underline underline-offset-2">Biome</a> and
|
|
267
|
-
<a href="https://github.com/stepci/stepci" target="_blank" class="text-cyan-400 hover:text-cyan-300 underline underline-offset-2">StepCI</a>.
|
|
268
|
-
</p>
|
|
269
|
-
<div class="flex flex-wrap gap-3">
|
|
270
|
-
<a href="https://michaelnji.codes" target="_blank"
|
|
271
|
-
class="inline-flex items-center gap-2 px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 rounded-lg text-cyan-400 text-sm hover:bg-cyan-500/20 transition-all duration-200">
|
|
272
|
-
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/></svg>
|
|
273
|
-
Portfolio
|
|
274
|
-
</a>
|
|
275
|
-
<a href="https://github.com/michaelnji" target="_blank"
|
|
276
|
-
class="inline-flex items-center gap-2 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-gray-300 text-sm hover:bg-gray-700 transition-all duration-200">
|
|
277
|
-
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
|
278
|
-
GitHub
|
|
279
|
-
</a>
|
|
280
|
-
<a href="https://michaelnji.codes/blog" target="_blank"
|
|
281
|
-
class="inline-flex items-center gap-2 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-gray-300 text-sm hover:bg-gray-700 transition-all duration-200">
|
|
282
|
-
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/></svg>
|
|
283
|
-
Blog
|
|
284
|
-
</a>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
<!-- Footer -->
|
|
293
|
-
<div class="mt-8 text-center text-gray-700 text-sm">
|
|
294
|
-
Generated by KOUNT — Project Intelligence for Codebases
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
<script>
|
|
300
|
-
function dashboard() {
|
|
301
|
-
const data = ${jsonData};
|
|
302
|
-
return {
|
|
303
|
-
data,
|
|
304
|
-
currentTab: 'dashboard',
|
|
305
|
-
langSort: 'count',
|
|
306
|
-
get summaryCards() {
|
|
307
|
-
const s = this.data.summary;
|
|
308
|
-
return [
|
|
309
|
-
{ label: 'Files', value: s.files.toLocaleString(), color: 'text-cyan-400' },
|
|
310
|
-
{ label: 'Total Lines', value: s.totalLines.toLocaleString(), color: 'text-white' },
|
|
311
|
-
{ label: 'Code Lines', value: s.codeLines.toLocaleString(), color: 'text-green-400' },
|
|
312
|
-
{ label: 'Code Ratio', value: s.codeRatio + '%', color: 'text-green-400' },
|
|
313
|
-
{ label: 'Comment Lines', value: s.commentLines.toLocaleString(), color: 'text-yellow-400' },
|
|
314
|
-
{ label: 'Blank Lines', value: s.blankLines.toLocaleString(), color: 'text-gray-400' },
|
|
315
|
-
{ label: 'Total Size', value: s.totalSize, color: 'text-cyan-400' },
|
|
316
|
-
];
|
|
317
|
-
},
|
|
318
|
-
get sortedLanguages() {
|
|
319
|
-
const langs = [...this.data.languages];
|
|
320
|
-
if (this.langSort === 'name') {
|
|
321
|
-
langs.sort((a, b) => a.lang.localeCompare(b.lang));
|
|
322
|
-
} else {
|
|
323
|
-
langs.sort((a, b) => b.count - a.count);
|
|
324
|
-
}
|
|
325
|
-
return langs;
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
</script>
|
|
330
|
-
</body>
|
|
331
|
-
</html>`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Spins up a temporary HTTP server, serves the HTML dashboard,
|
|
336
|
-
* and auto-opens the user's default browser.
|
|
337
|
-
*
|
|
338
|
-
* @param stats The aggregated project statistics.
|
|
339
|
-
* @param port The port to serve on (defaults to 0 for auto-assign).
|
|
340
|
-
* @returns A cleanup function to shut down the server.
|
|
341
|
-
*/
|
|
342
|
-
export async function serveHtmlDashboard(
|
|
343
|
-
stats: ProjectStats,
|
|
344
|
-
port: number = 0
|
|
345
|
-
): Promise<{ url: string; close: () => void }> {
|
|
346
|
-
const html = generateHtmlDashboard(stats);
|
|
347
|
-
|
|
348
|
-
return new Promise((resolve, reject) => {
|
|
349
|
-
const server = http.createServer((_req, res) => {
|
|
350
|
-
res.writeHead(200, {
|
|
351
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
352
|
-
'Cache-Control': 'no-store',
|
|
353
|
-
});
|
|
354
|
-
res.end(html);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
server.listen(port, '127.0.0.1', () => {
|
|
358
|
-
const address = server.address();
|
|
359
|
-
if (!address || typeof address === 'string') {
|
|
360
|
-
reject(new Error('Failed to get server address'));
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const url = `http://127.0.0.1:${address.port}`;
|
|
365
|
-
|
|
366
|
-
// Auto-open the default browser (platform-independent)
|
|
367
|
-
const openCmd = process.platform === 'darwin'
|
|
368
|
-
? `open "${url}"`
|
|
369
|
-
: process.platform === 'win32'
|
|
370
|
-
? `start "${url}"`
|
|
371
|
-
: `xdg-open "${url}"`;
|
|
372
|
-
|
|
373
|
-
exec(openCmd);
|
|
374
|
-
|
|
375
|
-
resolve({
|
|
376
|
-
url,
|
|
377
|
-
close: () => server.close(),
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
server.on('error', reject);
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
export { generateHtmlDashboard };
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import fsp from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import type { ProjectStats } from '../plugins/types.js';
|
|
4
|
-
|
|
5
|
-
const KOUNT_HEADER = '<!-- KOUNT:START -->';
|
|
6
|
-
const KOUNT_FOOTER = '<!-- KOUNT:END -->';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generates a Markdown report string from ProjectStats.
|
|
10
|
-
*/
|
|
11
|
-
function generateMarkdownReport(stats: ProjectStats): string {
|
|
12
|
-
const totalLines = stats.pluginResults.get('TotalLines')?.summaryValue ?? 0;
|
|
13
|
-
const blankLines = stats.pluginResults.get('BlankLines')?.summaryValue ?? 0;
|
|
14
|
-
const commentLines = stats.pluginResults.get('CommentLines')?.summaryValue ?? 0;
|
|
15
|
-
const totalBytes = stats.pluginResults.get('FileSize')?.summaryValue ?? 0;
|
|
16
|
-
const codeLines = totalLines - blankLines - commentLines;
|
|
17
|
-
const codeRatio = totalLines > 0 ? ((codeLines / totalLines) * 100).toFixed(1) : '0.0';
|
|
18
|
-
|
|
19
|
-
const formatSize = (bytes: number): string => {
|
|
20
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
21
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
22
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const lines: string[] = [];
|
|
26
|
-
lines.push(KOUNT_HEADER);
|
|
27
|
-
lines.push('');
|
|
28
|
-
lines.push('## Codebase Statistics');
|
|
29
|
-
lines.push('');
|
|
30
|
-
lines.push(`> Generated by [kount](https://github.com/kount) on ${stats.scannedAt.toLocaleDateString()}`);
|
|
31
|
-
lines.push('');
|
|
32
|
-
|
|
33
|
-
// Summary table
|
|
34
|
-
lines.push('### Summary');
|
|
35
|
-
lines.push('');
|
|
36
|
-
lines.push('| Metric | Value |');
|
|
37
|
-
lines.push('|--------|-------|');
|
|
38
|
-
lines.push(`| Files | ${stats.totalFiles.toLocaleString()} |`);
|
|
39
|
-
lines.push(`| Total Lines | ${totalLines.toLocaleString()} |`);
|
|
40
|
-
lines.push(`| Code Lines | ${codeLines.toLocaleString()} |`);
|
|
41
|
-
lines.push(`| Comment Lines | ${commentLines.toLocaleString()} |`);
|
|
42
|
-
lines.push(`| Blank Lines | ${blankLines.toLocaleString()} |`);
|
|
43
|
-
lines.push(`| Code Ratio | ${codeRatio}% |`);
|
|
44
|
-
lines.push(`| Total Size | ${formatSize(totalBytes)} |`);
|
|
45
|
-
lines.push('');
|
|
46
|
-
|
|
47
|
-
// Language distribution (descending %)
|
|
48
|
-
if (stats.languageDistribution.size > 0) {
|
|
49
|
-
const sortedLangs = [...stats.languageDistribution.entries()]
|
|
50
|
-
.sort((a, b) => b[1] - a[1]);
|
|
51
|
-
|
|
52
|
-
lines.push('### Language Distribution');
|
|
53
|
-
lines.push('');
|
|
54
|
-
lines.push('| Language | Files | % |');
|
|
55
|
-
lines.push('|----------|-------|---|');
|
|
56
|
-
|
|
57
|
-
for (const [lang, count] of sortedLangs) {
|
|
58
|
-
const pct = ((count / stats.totalFiles) * 100).toFixed(1);
|
|
59
|
-
lines.push(`| ${lang} | ${count} | ${pct}% |`);
|
|
60
|
-
}
|
|
61
|
-
lines.push('');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Top 10 largest files
|
|
65
|
-
if (stats.largestFiles.length > 0) {
|
|
66
|
-
lines.push('### Top 10 Largest Files');
|
|
67
|
-
lines.push('');
|
|
68
|
-
lines.push('| # | File | Size |');
|
|
69
|
-
lines.push('|---|------|------|');
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < stats.largestFiles.length; i++) {
|
|
72
|
-
const file = stats.largestFiles[i];
|
|
73
|
-
const relPath = path.relative(stats.rootDir, file.filePath);
|
|
74
|
-
lines.push(`| ${i + 1} | \`${relPath}\` | ${formatSize(file.size)} |`);
|
|
75
|
-
}
|
|
76
|
-
lines.push('');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
lines.push(KOUNT_FOOTER);
|
|
80
|
-
return lines.join('\n');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Writes the Markdown report to a file.
|
|
85
|
-
* - By default, if the file exists, appends/replaces the KOUNT section.
|
|
86
|
-
* - If `force` is true, overwrites the entire file.
|
|
87
|
-
*
|
|
88
|
-
* @param stats The aggregated project statistics.
|
|
89
|
-
* @param outputPath The target markdown file path (defaults to README.md in rootDir).
|
|
90
|
-
* @param force If true, overwrite the entire file instead of appending.
|
|
91
|
-
*/
|
|
92
|
-
export async function writeMarkdownReport(
|
|
93
|
-
stats: ProjectStats,
|
|
94
|
-
outputPath?: string,
|
|
95
|
-
force: boolean = false
|
|
96
|
-
): Promise<string> {
|
|
97
|
-
const targetPath = outputPath ?? path.join(stats.rootDir, 'README.md');
|
|
98
|
-
const report = generateMarkdownReport(stats);
|
|
99
|
-
|
|
100
|
-
if (force) {
|
|
101
|
-
await fsp.writeFile(targetPath, report, 'utf8');
|
|
102
|
-
return targetPath;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Try to read existing file and replace KOUNT section, or append
|
|
106
|
-
try {
|
|
107
|
-
const existing = await fsp.readFile(targetPath, 'utf8');
|
|
108
|
-
|
|
109
|
-
const startIdx = existing.indexOf(KOUNT_HEADER);
|
|
110
|
-
const endIdx = existing.indexOf(KOUNT_FOOTER);
|
|
111
|
-
|
|
112
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
113
|
-
// Replace existing KOUNT section
|
|
114
|
-
const before = existing.substring(0, startIdx);
|
|
115
|
-
const after = existing.substring(endIdx + KOUNT_FOOTER.length);
|
|
116
|
-
await fsp.writeFile(targetPath, before + report + after, 'utf8');
|
|
117
|
-
} else {
|
|
118
|
-
// Append to the end
|
|
119
|
-
await fsp.writeFile(targetPath, existing + '\n\n' + report, 'utf8');
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
// File doesn't exist — create new
|
|
123
|
-
await fsp.writeFile(targetPath, report, 'utf8');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return targetPath;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export { generateMarkdownReport };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from 'ink';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface ProgressProps {
|
|
5
|
-
current: number;
|
|
6
|
-
total: number;
|
|
7
|
-
currentFile: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Real-time progress indicator for the streaming scan.
|
|
12
|
-
* Shows a progress bar, percentage, and current file being processed.
|
|
13
|
-
* Follows ux-progress-indicators and render-partial-updates guidelines.
|
|
14
|
-
*/
|
|
15
|
-
export function Progress({ current, total, currentFile }: ProgressProps): React.ReactElement {
|
|
16
|
-
const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
17
|
-
const barWidth = 30;
|
|
18
|
-
const filled = Math.round((percentage / 100) * barWidth);
|
|
19
|
-
const empty = barWidth - filled;
|
|
20
|
-
|
|
21
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
22
|
-
|
|
23
|
-
// Color transitions: red < 33%, yellow < 66%, green >= 66%
|
|
24
|
-
const barColor = percentage < 33 ? 'red' : percentage < 66 ? 'yellow' : 'green';
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Box flexDirection="column" marginY={1}>
|
|
28
|
-
<Box>
|
|
29
|
-
<Text color="white" bold>Scanning: </Text>
|
|
30
|
-
<Text color={barColor}>{bar}</Text>
|
|
31
|
-
<Text color="white"> {percentage}%</Text>
|
|
32
|
-
<Text color="gray"> ({current}/{total})</Text>
|
|
33
|
-
</Box>
|
|
34
|
-
<Box marginTop={0}>
|
|
35
|
-
<Text color="gray" wrap="truncate-end"> {currentFile}</Text>
|
|
36
|
-
</Box>
|
|
37
|
-
</Box>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from 'ink';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
const LOGO = `
|
|
5
|
-
██╗ ██╗ ██████╗ ██╗ ██╗███╗ ██╗████████╗
|
|
6
|
-
██║ ██╔╝██╔═══██╗██║ ██║████╗ ██║╚══██╔══╝
|
|
7
|
-
█████╔╝ ██║ ██║██║ ██║██╔██╗ ██║ ██║
|
|
8
|
-
██╔═██╗ ██║ ██║██║ ██║██║╚██╗██║ ██║
|
|
9
|
-
██║ ██╗╚██████╔╝╚██████╔╝██║ ╚████║ ██║
|
|
10
|
-
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝`;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Splash screen component that displays the KOUNT ASCII logo.
|
|
14
|
-
* Uses Ink's Box + Text for structured terminal rendering.
|
|
15
|
-
* Follows render-single-write and tuicomp-border-styles guidelines.
|
|
16
|
-
*/
|
|
17
|
-
export function Splash(): React.ReactElement {
|
|
18
|
-
return (
|
|
19
|
-
<Box
|
|
20
|
-
flexDirection="column"
|
|
21
|
-
borderStyle="round"
|
|
22
|
-
borderColor="cyan"
|
|
23
|
-
paddingX={2}
|
|
24
|
-
paddingY={1}
|
|
25
|
-
>
|
|
26
|
-
<Text color="cyan" bold>{LOGO}</Text>
|
|
27
|
-
<Box marginTop={1}>
|
|
28
|
-
<Text color="white" dimColor> Project Intelligence for Codebases</Text>
|
|
29
|
-
</Box>
|
|
30
|
-
</Box>
|
|
31
|
-
);
|
|
32
|
-
}
|