@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/index.tsx
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
4
|
-
import { render, Box, Text, useApp } from 'ink';
|
|
5
|
-
import { createCli } from './cli/parser.js';
|
|
6
|
-
import { resolveConfig } from './cli/config-resolver.js';
|
|
7
|
-
import type { KountConfig } from './cli/config-resolver.js';
|
|
8
|
-
import { Aggregator } from './core/aggregator.js';
|
|
9
|
-
import type { ProjectStats } from './plugins/types.js';
|
|
10
|
-
import { Splash } from './reporters/terminal/Splash.js';
|
|
11
|
-
import { Progress } from './reporters/terminal/Progress.js';
|
|
12
|
-
import { Summary } from './reporters/terminal/Summary.js';
|
|
13
|
-
import { Wizard } from './reporters/terminal/Wizard.js';
|
|
14
|
-
import type { WizardResult } from './reporters/terminal/Wizard.js';
|
|
15
|
-
import { writeMarkdownReport } from './reporters/markdown.js';
|
|
16
|
-
import { serveHtmlDashboard } from './reporters/html.js';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Non-interactive execution (markdown / html modes, or terminal with flags)
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
async function runHeadless(config: KountConfig): Promise<void> {
|
|
23
|
-
const aggregator = new Aggregator(config.rootDir, {
|
|
24
|
-
respectGitignore: config.respectGitignore,
|
|
25
|
-
cacheEnabled: config.cache.enabled,
|
|
26
|
-
clearCache: config.cache.clearFirst,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const stats = await aggregator.run();
|
|
30
|
-
|
|
31
|
-
if (config.outputMode === 'markdown') {
|
|
32
|
-
const outputPath = await writeMarkdownReport(stats, config.outputPath, config.force);
|
|
33
|
-
process.stdout.write(`Markdown report written to ${outputPath}\n`);
|
|
34
|
-
} else if (config.outputMode === 'html') {
|
|
35
|
-
const { url } = await serveHtmlDashboard(stats);
|
|
36
|
-
process.stdout.write(`Dashboard running at ${url}\nPress Ctrl+C to stop.\n`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Ink Terminal UI App
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
type AppPhase = 'splash' | 'wizard' | 'scanning' | 'done';
|
|
45
|
-
|
|
46
|
-
interface AppProps {
|
|
47
|
-
config: KountConfig;
|
|
48
|
-
needsWizard: boolean;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function App({ config: initialConfig, needsWizard }: AppProps): React.ReactElement {
|
|
52
|
-
const { exit } = useApp();
|
|
53
|
-
const [phase, setPhase] = useState<AppPhase>(needsWizard ? 'splash' : 'scanning');
|
|
54
|
-
const [config, setConfig] = useState<KountConfig>(initialConfig);
|
|
55
|
-
const [progress, setProgress] = useState({ current: 0, total: 0, file: '' });
|
|
56
|
-
const [stats, setStats] = useState<ProjectStats | null>(null);
|
|
57
|
-
const [error, setError] = useState<string | null>(null);
|
|
58
|
-
|
|
59
|
-
// Show splash briefly, then move to wizard
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (phase === 'splash') {
|
|
62
|
-
const timer = setTimeout(() => setPhase('wizard'), 1500);
|
|
63
|
-
return () => clearTimeout(timer);
|
|
64
|
-
}
|
|
65
|
-
return undefined;
|
|
66
|
-
}, [phase]);
|
|
67
|
-
|
|
68
|
-
// Run the scan when entering the scanning phase
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (phase !== 'scanning') return;
|
|
71
|
-
|
|
72
|
-
const aggregator = new Aggregator(config.rootDir, {
|
|
73
|
-
respectGitignore: config.respectGitignore,
|
|
74
|
-
cacheEnabled: config.cache.enabled,
|
|
75
|
-
clearCache: config.cache.clearFirst,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
aggregator
|
|
79
|
-
.run((current, total, filePath) => {
|
|
80
|
-
setProgress({ current, total, file: filePath });
|
|
81
|
-
})
|
|
82
|
-
.then((result) => {
|
|
83
|
-
setStats(result);
|
|
84
|
-
setPhase('done');
|
|
85
|
-
})
|
|
86
|
-
.catch((err: unknown) => {
|
|
87
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
88
|
-
setPhase('done');
|
|
89
|
-
});
|
|
90
|
-
}, [phase, config]);
|
|
91
|
-
|
|
92
|
-
const handleWizardComplete = useCallback((result: WizardResult) => {
|
|
93
|
-
setConfig((prev) => ({
|
|
94
|
-
...prev,
|
|
95
|
-
rootDir: result.rootDir,
|
|
96
|
-
outputMode: result.outputMode,
|
|
97
|
-
includeTests: result.includeTests,
|
|
98
|
-
}));
|
|
99
|
-
setPhase('scanning');
|
|
100
|
-
}, []);
|
|
101
|
-
|
|
102
|
-
// Auto-exit after done phase is displayed
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (phase === 'done' && stats) {
|
|
105
|
-
// Give the user a moment to see the results
|
|
106
|
-
const timer = setTimeout(() => exit(), 500);
|
|
107
|
-
return () => clearTimeout(timer);
|
|
108
|
-
}
|
|
109
|
-
return undefined;
|
|
110
|
-
}, [phase, stats, exit]);
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<Box flexDirection= "column" >
|
|
114
|
-
{ phase === 'splash' && <Splash />
|
|
115
|
-
}
|
|
116
|
-
{ phase === 'wizard' && <Wizard onComplete={ handleWizardComplete } /> }
|
|
117
|
-
{
|
|
118
|
-
phase === 'scanning' && (
|
|
119
|
-
<Box flexDirection="column" >
|
|
120
|
-
<Progress
|
|
121
|
-
current={ progress.current }
|
|
122
|
-
total = { progress.total }
|
|
123
|
-
currentFile = { progress.file }
|
|
124
|
-
/>
|
|
125
|
-
</Box>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
{
|
|
129
|
-
phase === 'done' && error && (
|
|
130
|
-
<Box marginY={ 1 }>
|
|
131
|
-
<Text color="red" bold > Error: { error } </Text>
|
|
132
|
-
</Box>
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
{ phase === 'done' && stats && <Summary stats={ stats } /> }
|
|
136
|
-
</Box>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Main
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
async function main(): Promise<void> {
|
|
145
|
-
const cliFlags = createCli(process.argv);
|
|
146
|
-
const config = await resolveConfig(cliFlags);
|
|
147
|
-
|
|
148
|
-
if (config.outputMode === 'terminal') {
|
|
149
|
-
// Determine if we need the wizard (no explicit flags were passed)
|
|
150
|
-
const hasExplicitFlags = cliFlags.rootDir !== undefined || cliFlags.outputMode !== undefined;
|
|
151
|
-
|
|
152
|
-
render(
|
|
153
|
-
React.createElement(App, {
|
|
154
|
-
config,
|
|
155
|
-
needsWizard: !hasExplicitFlags,
|
|
156
|
-
})
|
|
157
|
-
);
|
|
158
|
-
} else {
|
|
159
|
-
// Markdown or HTML — run headless
|
|
160
|
-
await runHeadless(config);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
main().catch((err: unknown) => {
|
|
165
|
-
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
166
|
-
process.exit(1);
|
|
167
|
-
});
|
package/src/plugins/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Counts blank (empty or whitespace-only) lines across all scanned files.
|
|
5
|
-
*/
|
|
6
|
-
export class BlankLinesPlugin implements AnalyzerPlugin {
|
|
7
|
-
readonly name = 'BlankLines';
|
|
8
|
-
|
|
9
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
10
|
-
const perFile = new Map<string, number>();
|
|
11
|
-
let summaryValue = 0;
|
|
12
|
-
|
|
13
|
-
for (const file of files) {
|
|
14
|
-
let blankCount = 0;
|
|
15
|
-
for (const line of file.lines) {
|
|
16
|
-
if (line.trim() === '') {
|
|
17
|
-
blankCount++;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
perFile.set(file.filePath, blankCount);
|
|
21
|
-
summaryValue += blankCount;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return { pluginName: this.name, summaryValue, perFile };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { getCommentSyntax } from '../../utils/language-map.js';
|
|
2
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Counts comment lines across all scanned files.
|
|
6
|
-
* Handles single-line comments (//, #, --, ;) and block comments (/* */, <!-- -->).
|
|
7
|
-
*/
|
|
8
|
-
export class CommentLinesPlugin implements AnalyzerPlugin {
|
|
9
|
-
readonly name = 'CommentLines';
|
|
10
|
-
|
|
11
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
12
|
-
const perFile = new Map<string, number>();
|
|
13
|
-
let summaryValue = 0;
|
|
14
|
-
|
|
15
|
-
for (const file of files) {
|
|
16
|
-
const commentCount = this.countComments(file);
|
|
17
|
-
perFile.set(file.filePath, commentCount);
|
|
18
|
-
summaryValue += commentCount;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return { pluginName: this.name, summaryValue, perFile };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private countComments(file: AnalyzedFileData): number {
|
|
25
|
-
const syntaxes = getCommentSyntax(file.extension);
|
|
26
|
-
if (syntaxes.length === 0) return 0;
|
|
27
|
-
|
|
28
|
-
// Separate single-line and block comment markers
|
|
29
|
-
const singleLineMarkers: string[] = [];
|
|
30
|
-
const blockMarkers: Array<{ open: string; close: string }> = [];
|
|
31
|
-
|
|
32
|
-
for (const syntax of syntaxes) {
|
|
33
|
-
if (syntax.includes(' ')) {
|
|
34
|
-
// Block comment syntax like '/* */' or '<!-- -->'
|
|
35
|
-
const parts = syntax.split(' ');
|
|
36
|
-
if (parts.length === 2) {
|
|
37
|
-
blockMarkers.push({ open: parts[0], close: parts[1] });
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
// Single-line comment syntax like '//' or '#'
|
|
41
|
-
singleLineMarkers.push(syntax);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let commentCount = 0;
|
|
46
|
-
let inBlockComment = false;
|
|
47
|
-
let currentBlockClose = '';
|
|
48
|
-
|
|
49
|
-
for (const line of file.lines) {
|
|
50
|
-
const trimmed = line.trim();
|
|
51
|
-
|
|
52
|
-
if (inBlockComment) {
|
|
53
|
-
commentCount++;
|
|
54
|
-
if (trimmed.includes(currentBlockClose)) {
|
|
55
|
-
inBlockComment = false;
|
|
56
|
-
currentBlockClose = '';
|
|
57
|
-
}
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check for block comment opening
|
|
62
|
-
let isBlockStart = false;
|
|
63
|
-
for (const block of blockMarkers) {
|
|
64
|
-
if (trimmed.includes(block.open)) {
|
|
65
|
-
commentCount++;
|
|
66
|
-
isBlockStart = true;
|
|
67
|
-
// Check if block closes on the same line
|
|
68
|
-
const afterOpen = trimmed.substring(trimmed.indexOf(block.open) + block.open.length);
|
|
69
|
-
if (!afterOpen.includes(block.close)) {
|
|
70
|
-
inBlockComment = true;
|
|
71
|
-
currentBlockClose = block.close;
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (isBlockStart) continue;
|
|
78
|
-
|
|
79
|
-
// Check for single-line comments
|
|
80
|
-
for (const marker of singleLineMarkers) {
|
|
81
|
-
if (trimmed.startsWith(marker)) {
|
|
82
|
-
commentCount++;
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return commentCount;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Computes total file size (bytes) across all scanned files.
|
|
5
|
-
*/
|
|
6
|
-
export class FileSizePlugin implements AnalyzerPlugin {
|
|
7
|
-
readonly name = 'FileSize';
|
|
8
|
-
|
|
9
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
10
|
-
const perFile = new Map<string, number>();
|
|
11
|
-
let summaryValue = 0;
|
|
12
|
-
|
|
13
|
-
for (const file of files) {
|
|
14
|
-
perFile.set(file.filePath, file.size);
|
|
15
|
-
summaryValue += file.size;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return { pluginName: this.name, summaryValue, perFile };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Known extension-to-language name mappings.
|
|
5
|
-
*/
|
|
6
|
-
const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
7
|
-
'.js': 'JavaScript',
|
|
8
|
-
'.jsx': 'JavaScript (JSX)',
|
|
9
|
-
'.ts': 'TypeScript',
|
|
10
|
-
'.tsx': 'TypeScript (TSX)',
|
|
11
|
-
'.py': 'Python',
|
|
12
|
-
'.rb': 'Ruby',
|
|
13
|
-
'.java': 'Java',
|
|
14
|
-
'.c': 'C',
|
|
15
|
-
'.cpp': 'C++',
|
|
16
|
-
'.cs': 'C#',
|
|
17
|
-
'.go': 'Go',
|
|
18
|
-
'.rs': 'Rust',
|
|
19
|
-
'.swift': 'Swift',
|
|
20
|
-
'.kt': 'Kotlin',
|
|
21
|
-
'.dart': 'Dart',
|
|
22
|
-
'.scala': 'Scala',
|
|
23
|
-
'.php': 'PHP',
|
|
24
|
-
'.html': 'HTML',
|
|
25
|
-
'.htm': 'HTML',
|
|
26
|
-
'.css': 'CSS',
|
|
27
|
-
'.scss': 'SCSS',
|
|
28
|
-
'.less': 'LESS',
|
|
29
|
-
'.xml': 'XML',
|
|
30
|
-
'.svg': 'SVG',
|
|
31
|
-
'.json': 'JSON',
|
|
32
|
-
'.jsonc': 'JSONC',
|
|
33
|
-
'.yaml': 'YAML',
|
|
34
|
-
'.yml': 'YAML',
|
|
35
|
-
'.md': 'Markdown',
|
|
36
|
-
'.sh': 'Shell',
|
|
37
|
-
'.bash': 'Shell',
|
|
38
|
-
'.zsh': 'Shell',
|
|
39
|
-
'.sql': 'SQL',
|
|
40
|
-
'.lua': 'Lua',
|
|
41
|
-
'.r': 'R',
|
|
42
|
-
'.m': 'Objective-C',
|
|
43
|
-
'.mm': 'Objective-C++',
|
|
44
|
-
'.pl': 'Perl',
|
|
45
|
-
'.pm': 'Perl',
|
|
46
|
-
'.hs': 'Haskell',
|
|
47
|
-
'.lisp': 'Lisp',
|
|
48
|
-
'.clj': 'Clojure',
|
|
49
|
-
'.scm': 'Scheme',
|
|
50
|
-
'.vue': 'Vue',
|
|
51
|
-
'.toml': 'TOML',
|
|
52
|
-
'.ini': 'INI',
|
|
53
|
-
'.cfg': 'Config',
|
|
54
|
-
'.ps1': 'PowerShell',
|
|
55
|
-
'.ada': 'Ada',
|
|
56
|
-
'.vhdl': 'VHDL',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Computes language distribution across all scanned files.
|
|
61
|
-
* The perFile map stores a value of 1 for each file (used for counting),
|
|
62
|
-
* while summaryValue is the number of distinct languages found.
|
|
63
|
-
*/
|
|
64
|
-
export class LanguageDistributionPlugin implements AnalyzerPlugin {
|
|
65
|
-
readonly name = 'LanguageDistribution';
|
|
66
|
-
|
|
67
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
68
|
-
const langCounts = new Map<string, number>();
|
|
69
|
-
const perFile = new Map<string, number>();
|
|
70
|
-
|
|
71
|
-
for (const file of files) {
|
|
72
|
-
const ext = file.extension.toLowerCase();
|
|
73
|
-
const language = EXTENSION_TO_LANGUAGE[ext] || 'Other';
|
|
74
|
-
langCounts.set(language, (langCounts.get(language) || 0) + 1);
|
|
75
|
-
perFile.set(file.filePath, 1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// summaryValue = number of distinct languages
|
|
79
|
-
return { pluginName: this.name, summaryValue: langCounts.size, perFile };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Helper to retrieve the full language distribution map.
|
|
84
|
-
* Called by the aggregator after analyze().
|
|
85
|
-
*/
|
|
86
|
-
getDistribution(files: AnalyzedFileData[]): Map<string, number> {
|
|
87
|
-
const langCounts = new Map<string, number>();
|
|
88
|
-
for (const file of files) {
|
|
89
|
-
const ext = file.extension.toLowerCase();
|
|
90
|
-
const language = EXTENSION_TO_LANGUAGE[ext] || 'Other';
|
|
91
|
-
langCounts.set(language, (langCounts.get(language) || 0) + 1);
|
|
92
|
-
}
|
|
93
|
-
return langCounts;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_TOP_N = 10;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Identifies the largest files by byte size.
|
|
7
|
-
* summaryValue = the size of the single largest file.
|
|
8
|
-
* perFile = the size of each file (same as FileSize, but sorted/limited).
|
|
9
|
-
*/
|
|
10
|
-
export class LargestFilesPlugin implements AnalyzerPlugin {
|
|
11
|
-
readonly name = 'LargestFiles';
|
|
12
|
-
private topN: number;
|
|
13
|
-
|
|
14
|
-
constructor(topN: number = DEFAULT_TOP_N) {
|
|
15
|
-
this.topN = topN;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
19
|
-
const sorted = [...files].sort((a, b) => b.size - a.size);
|
|
20
|
-
const top = sorted.slice(0, this.topN);
|
|
21
|
-
|
|
22
|
-
const perFile = new Map<string, number>();
|
|
23
|
-
for (const file of top) {
|
|
24
|
-
perFile.set(file.filePath, file.size);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const summaryValue = top.length > 0 ? top[0].size : 0;
|
|
28
|
-
|
|
29
|
-
return { pluginName: this.name, summaryValue, perFile };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Helper to get the ranked list, consumed by reporters.
|
|
34
|
-
*/
|
|
35
|
-
getTopFiles(files: AnalyzedFileData[]): Array<{ filePath: string; size: number }> {
|
|
36
|
-
return [...files]
|
|
37
|
-
.sort((a, b) => b.size - a.size)
|
|
38
|
-
.slice(0, this.topN)
|
|
39
|
-
.map(f => ({ filePath: f.filePath, size: f.size }));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Counts total number of files scanned.
|
|
5
|
-
*/
|
|
6
|
-
export class TotalFilesPlugin implements AnalyzerPlugin {
|
|
7
|
-
readonly name = 'TotalFiles';
|
|
8
|
-
|
|
9
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
10
|
-
const perFile = new Map<string, number>();
|
|
11
|
-
|
|
12
|
-
for (const file of files) {
|
|
13
|
-
perFile.set(file.filePath, 1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return { pluginName: this.name, summaryValue: files.length, perFile };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedFileData, AnalyzerPlugin, PluginResult } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Counts total lines across all scanned files.
|
|
5
|
-
*/
|
|
6
|
-
export class TotalLinesPlugin implements AnalyzerPlugin {
|
|
7
|
-
readonly name = 'TotalLines';
|
|
8
|
-
|
|
9
|
-
analyze(files: AnalyzedFileData[]): PluginResult {
|
|
10
|
-
const perFile = new Map<string, number>();
|
|
11
|
-
let summaryValue = 0;
|
|
12
|
-
|
|
13
|
-
for (const file of files) {
|
|
14
|
-
const lineCount = file.lines.length;
|
|
15
|
-
perFile.set(file.filePath, lineCount);
|
|
16
|
-
summaryValue += lineCount;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return { pluginName: this.name, summaryValue, perFile };
|
|
20
|
-
}
|
|
21
|
-
}
|
package/src/plugins/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { BlankLinesPlugin } from './built-in/blank-lines.js';
|
|
2
|
-
export { CommentLinesPlugin } from './built-in/comment-lines.js';
|
|
3
|
-
export { FileSizePlugin } from './built-in/file-size.js';
|
|
4
|
-
export { LanguageDistributionPlugin } from './built-in/language-distribution.js';
|
|
5
|
-
export { LargestFilesPlugin } from './built-in/largest-files.js';
|
|
6
|
-
export { TotalFilesPlugin } from './built-in/total-files.js';
|
|
7
|
-
export { TotalLinesPlugin } from './built-in/total-lines.js';
|
|
8
|
-
|
|
9
|
-
export type { AnalyzedFileData, AnalyzerPlugin, PluginResult, ProjectStats } from './types.js';
|
|
10
|
-
|
package/src/plugins/types.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Represents the result of a single plugin's analysis on the entire project.
|
|
4
|
-
*/
|
|
5
|
-
export interface PluginResult {
|
|
6
|
-
/** The name of the plugin that produced this result. */
|
|
7
|
-
pluginName: string;
|
|
8
|
-
/** Summary-level metric value (e.g. total lines across all files). */
|
|
9
|
-
summaryValue: number;
|
|
10
|
-
/** Per-file breakdown of the metric, keyed by file path. */
|
|
11
|
-
perFile: Map<string, number>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The contract every analyzer plugin must implement.
|
|
16
|
-
* Plugins receive the full list of scanned files and their line data,
|
|
17
|
-
* then return aggregated results.
|
|
18
|
-
*/
|
|
19
|
-
export interface AnalyzerPlugin {
|
|
20
|
-
/** Unique name of this plugin (e.g. 'TotalLines'). */
|
|
21
|
-
name: string;
|
|
22
|
-
/** Analyze scanned files and return aggregated results. */
|
|
23
|
-
analyze(files: AnalyzedFileData[]): PluginResult;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Enriched file data created by the aggregator after streaming each file.
|
|
28
|
-
* Contains raw line data so plugins don't need to re-read files.
|
|
29
|
-
*/
|
|
30
|
-
export interface AnalyzedFileData {
|
|
31
|
-
/** Absolute path to the scanned file. */
|
|
32
|
-
filePath: string;
|
|
33
|
-
/** File size in bytes (from stat). */
|
|
34
|
-
size: number;
|
|
35
|
-
/** File extension including the dot, e.g. '.ts'. */
|
|
36
|
-
extension: string;
|
|
37
|
-
/** All lines of the file as strings. */
|
|
38
|
-
lines: string[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The final aggregated stats payload produced by the Orchestrator.
|
|
43
|
-
* Consumed by reporters to render output.
|
|
44
|
-
*/
|
|
45
|
-
export interface ProjectStats {
|
|
46
|
-
/** Root directory that was scanned. */
|
|
47
|
-
rootDir: string;
|
|
48
|
-
/** Total number of files scanned. */
|
|
49
|
-
totalFiles: number;
|
|
50
|
-
/** Results from each plugin, keyed by plugin name. */
|
|
51
|
-
pluginResults: Map<string, PluginResult>;
|
|
52
|
-
/** Language distribution: language name -> file count. */
|
|
53
|
-
languageDistribution: Map<string, number>;
|
|
54
|
-
/** Top N largest files by size. */
|
|
55
|
-
largestFiles: Array<{ filePath: string; size: number }>;
|
|
56
|
-
/** Timestamp of when the scan completed. */
|
|
57
|
-
scannedAt: Date;
|
|
58
|
-
}
|
package/src/reporters/.gitkeep
DELETED
|
File without changes
|