@ui-entropy/scanner-core 0.1.0

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/report.js ADDED
@@ -0,0 +1,137 @@
1
+ import { estimateWastedBytes } from './analyze.js';
2
+ /**
3
+ * Generate JSON report from analysis
4
+ */
5
+ export function generateJsonReport(analysis, warnings = []) {
6
+ const totalSelectors = analysis.totalClasses + analysis.totalIds;
7
+ const usedSelectors = analysis.usedClasses + analysis.usedIds;
8
+ const unusedSelectors = analysis.unusedClasses.size + analysis.unusedIds.size;
9
+ return {
10
+ version: '1.0.0',
11
+ timestamp: new Date().toISOString(),
12
+ summary: {
13
+ totalCssBytes: analysis.totalCssBytes,
14
+ totalRules: analysis.totalRules,
15
+ totalSelectors,
16
+ usedSelectors,
17
+ unusedSelectors,
18
+ wastePercentage: analysis.wastePercentage,
19
+ estimatedWastedBytes: estimateWastedBytes(analysis),
20
+ filesScanned: analysis.filesScanned,
21
+ },
22
+ details: {
23
+ classes: {
24
+ total: analysis.totalClasses,
25
+ used: analysis.usedClasses,
26
+ unused: analysis.unusedClasses.size,
27
+ unusedList: Array.from(analysis.unusedClasses).sort(),
28
+ },
29
+ ids: {
30
+ total: analysis.totalIds,
31
+ used: analysis.usedIds,
32
+ unused: analysis.unusedIds.size,
33
+ unusedList: Array.from(analysis.unusedIds).sort(),
34
+ },
35
+ },
36
+ warnings,
37
+ };
38
+ }
39
+ /**
40
+ * Generate human-readable text report
41
+ */
42
+ export function generateTextReport(analysis) {
43
+ const wastedBytes = estimateWastedBytes(analysis);
44
+ const totalSelectors = analysis.totalClasses + analysis.totalIds;
45
+ const usedSelectors = analysis.usedClasses + analysis.usedIds;
46
+ let report = '';
47
+ report += '🔍 UI Entropy Analysis Report\n';
48
+ report += '═══════════════════════════════════════════════════════\n\n';
49
+ // Summary
50
+ report += '📊 Summary\n';
51
+ report += '─────────────────────────────────────────────────────\n';
52
+ report += `Total CSS Size: ${analysis.totalCssBytes.toLocaleString()} bytes\n`;
53
+ report += `Total Rules: ${analysis.totalRules.toLocaleString()}\n`;
54
+ report += `Files Scanned: ${analysis.filesScanned}\n\n`;
55
+ // Selectors
56
+ report += '🎯 Selectors\n';
57
+ report += '─────────────────────────────────────────────────────\n';
58
+ report += `Total Selectors: ${totalSelectors}\n`;
59
+ report += `Used Selectors: ${usedSelectors} (${Math.round((usedSelectors / totalSelectors) * 100)}%)\n`;
60
+ report += `Unused Selectors: ${totalSelectors - usedSelectors}\n\n`;
61
+ // Classes breakdown
62
+ report += `Classes: ${analysis.totalClasses} total\n`;
63
+ report += ` ├─ Used: ${analysis.usedClasses}\n`;
64
+ report += ` └─ Unused: ${analysis.unusedClasses.size}\n\n`;
65
+ // IDs breakdown
66
+ report += `IDs: ${analysis.totalIds} total\n`;
67
+ report += ` ├─ Used: ${analysis.usedIds}\n`;
68
+ report += ` └─ Unused: ${analysis.unusedIds.size}\n\n`;
69
+ // Waste calculation
70
+ report += '💰 CSS Waste Analysis\n';
71
+ report += '─────────────────────────────────────────────────────\n';
72
+ report += `Waste Percentage: ${analysis.wastePercentage}%\n`;
73
+ report += `Estimated Wasted Bytes: ${wastedBytes.toLocaleString()} bytes\n\n`;
74
+ // Top unused classes
75
+ if (analysis.unusedClasses.size > 0) {
76
+ report += '🚨 Top Unused Classes\n';
77
+ report += '─────────────────────────────────────────────────────\n';
78
+ const topUnused = Array.from(analysis.unusedClasses).slice(0, 20);
79
+ topUnused.forEach((cls, i) => {
80
+ report += ` ${i + 1}. .${cls}\n`;
81
+ });
82
+ if (analysis.unusedClasses.size > 20) {
83
+ report += ` ... and ${analysis.unusedClasses.size - 20} more\n`;
84
+ }
85
+ report += '\n';
86
+ }
87
+ // Unused IDs
88
+ if (analysis.unusedIds.size > 0) {
89
+ report += '🚨 Unused IDs\n';
90
+ report += '─────────────────────────────────────────────────────\n';
91
+ Array.from(analysis.unusedIds).forEach((id, i) => {
92
+ report += ` ${i + 1}. #${id}\n`;
93
+ });
94
+ report += '\n';
95
+ }
96
+ // Recommendations
97
+ report += '💡 Recommendations\n';
98
+ report += '─────────────────────────────────────────────────────\n';
99
+ if (analysis.wastePercentage > 50) {
100
+ report += ' ⚠️ HIGH WASTE: Consider using PurgeCSS or removing\n';
101
+ report += ' unused framework imports.\n\n';
102
+ }
103
+ else if (analysis.wastePercentage > 25) {
104
+ report += ' ⚡ MODERATE WASTE: Review and remove unused selectors.\n\n';
105
+ }
106
+ else {
107
+ report += ' ✅ GOOD: CSS waste is within acceptable range.\n\n';
108
+ }
109
+ return report;
110
+ }
111
+ /**
112
+ * Generate a brief summary report (for CLI output)
113
+ */
114
+ export function generateSummaryReport(analysis) {
115
+ const wastedBytes = estimateWastedBytes(analysis);
116
+ const totalSelectors = analysis.totalClasses + analysis.totalIds;
117
+ return `
118
+ CSS Waste: ${analysis.wastePercentage}% | ${wastedBytes.toLocaleString()} bytes wasted
119
+ Total: ${totalSelectors} selectors | Used: ${analysis.usedClasses + analysis.usedIds} | Unused: ${analysis.unusedClasses.size + analysis.unusedIds.size}
120
+ Scanned ${analysis.filesScanned} files | ${analysis.totalCssBytes.toLocaleString()} bytes CSS
121
+ `.trim();
122
+ }
123
+ /**
124
+ * Generate report in specified format
125
+ */
126
+ export function generateReport(analysis, format = 'text', warnings = []) {
127
+ switch (format) {
128
+ case 'json':
129
+ return JSON.stringify(generateJsonReport(analysis, warnings), null, 2);
130
+ case 'summary':
131
+ return generateSummaryReport(analysis);
132
+ case 'text':
133
+ default:
134
+ return generateTextReport(analysis);
135
+ }
136
+ }
137
+ //# sourceMappingURL=report.js.map
package/dist/scan.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { type ParseWarning } from './parse-css.js';
2
+ import { type AnalysisResult } from './analyze.js';
3
+ import { type ReportFormat } from './report.js';
4
+ export interface ScanOptions {
5
+ /**
6
+ * Directory to scan
7
+ * @default process.cwd()
8
+ */
9
+ baseDir?: string;
10
+ /**
11
+ * Report format
12
+ * @default 'text'
13
+ */
14
+ format?: ReportFormat;
15
+ /**
16
+ * Override config file settings
17
+ */
18
+ cssPatterns?: string[];
19
+ sourcePatterns?: string[];
20
+ ignorePatterns?: string[];
21
+ }
22
+ export interface ScanResult {
23
+ analysis: AnalysisResult;
24
+ report: string;
25
+ files: {
26
+ cssFiles: number;
27
+ sourceFiles: number;
28
+ totalBytes: number;
29
+ };
30
+ warnings: ParseWarning[];
31
+ }
32
+ /**
33
+ * Scan a project directory for CSS waste
34
+ *
35
+ * This is the main entry point that orchestrates:
36
+ * 1. Load config
37
+ * 2. Crawl files
38
+ * 3. Extract CSS selectors
39
+ * 4. Extract usage from source files
40
+ * 5. Analyze waste
41
+ * 6. Generate report
42
+ */
43
+ export declare function scan(options?: ScanOptions): ScanResult;
44
+ /**
45
+ * Quick scan that just returns the report as a string
46
+ */
47
+ export declare function quickScan(baseDir?: string): string;
48
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CA+D1D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,MAAsB,GAAG,MAAM,CAGjE"}
package/dist/scan.js ADDED
@@ -0,0 +1,72 @@
1
+ import { crawlSync } from './crawl.js';
2
+ import { getConfig } from './config.js';
3
+ import { extractSelectorsFromMultiple } from './parse-css.js';
4
+ import { extractUsageFromMultiple } from './extract-usage.js';
5
+ import { analyzeWaste } from './analyze.js';
6
+ import { generateReport } from './report.js';
7
+ /**
8
+ * Scan a project directory for CSS waste
9
+ *
10
+ * This is the main entry point that orchestrates:
11
+ * 1. Load config
12
+ * 2. Crawl files
13
+ * 3. Extract CSS selectors
14
+ * 4. Extract usage from source files
15
+ * 5. Analyze waste
16
+ * 6. Generate report
17
+ */
18
+ export function scan(options = {}) {
19
+ const { baseDir = process.cwd(), format = 'text', cssPatterns, sourcePatterns, ignorePatterns, } = options;
20
+ // 1. Load configuration
21
+ const config = getConfig(baseDir);
22
+ // Override with CLI options if provided
23
+ const finalCssPatterns = cssPatterns ?? config.cssPatterns;
24
+ const finalSourcePatterns = sourcePatterns ?? config.sourcePatterns;
25
+ // Merge ignore patterns: CLI patterns are ADDED to config patterns, not replacing them
26
+ const finalIgnorePatterns = ignorePatterns
27
+ ? [...config.ignorePatterns, ...ignorePatterns]
28
+ : config.ignorePatterns;
29
+ // 2. Crawl filesystem
30
+ const crawlResult = crawlSync({
31
+ baseDir,
32
+ cssPatterns: finalCssPatterns,
33
+ sourcePatterns: finalSourcePatterns,
34
+ ignorePatterns: finalIgnorePatterns,
35
+ respectGitignore: config.respectGitignore,
36
+ });
37
+ // Early exit if no files found
38
+ if (crawlResult.cssFiles.length === 0) {
39
+ throw new Error('No CSS files found. Check your cssPatterns configuration.');
40
+ }
41
+ if (crawlResult.sourceFiles.length === 0) {
42
+ throw new Error('No source files found. Check your sourcePatterns configuration.');
43
+ }
44
+ // 3. Extract CSS selectors (pass file objects for better error reporting)
45
+ const selectors = extractSelectorsFromMultiple(crawlResult.cssFiles);
46
+ // 4. Extract usage from source files
47
+ const sourceContents = crawlResult.sourceFiles.map(f => f.content);
48
+ const usage = extractUsageFromMultiple(sourceContents);
49
+ // 5. Analyze waste
50
+ const analysis = analyzeWaste(selectors, usage);
51
+ // 6. Generate report
52
+ const reportFormat = config.output?.format ?? format;
53
+ const report = generateReport(analysis, reportFormat, selectors.warnings);
54
+ return {
55
+ analysis,
56
+ report,
57
+ files: {
58
+ cssFiles: crawlResult.stats.totalCssFiles,
59
+ sourceFiles: crawlResult.stats.totalSourceFiles,
60
+ totalBytes: crawlResult.stats.totalCssBytes + crawlResult.stats.totalSourceBytes,
61
+ },
62
+ warnings: selectors.warnings,
63
+ };
64
+ }
65
+ /**
66
+ * Quick scan that just returns the report as a string
67
+ */
68
+ export function quickScan(baseDir = process.cwd()) {
69
+ const result = scan({ baseDir });
70
+ return result.report;
71
+ }
72
+ //# sourceMappingURL=scan.js.map
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@ui-entropy/scanner-core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CSS waste detection engine - finds unused classes and IDs in your codebase",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "type-check": "tsc --noEmit",
19
+ "demo": "tsx src/demo.ts",
20
+ "scan-demo": "tsx src/scan-demo.ts",
21
+ "test-real": "tsx src/test-real-repo.ts",
22
+ "test-tailwind": "tsx src/test-tailwind.ts"
23
+ },
24
+ "keywords": [
25
+ "css",
26
+ "unused",
27
+ "waste",
28
+ "optimization",
29
+ "bundle-size",
30
+ "tailwind",
31
+ "postcss"
32
+ ],
33
+ "author": "UI Entropy",
34
+ "license": "SEE LICENSE IN LICENSE",
35
+ "homepage": "https://uientropy.com",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/ui-entropy/ui-entropy.git",
39
+ "directory": "packages/scanner-core"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/ui-entropy/ui-entropy/issues"
43
+ },
44
+ "files": [
45
+ "dist/**/*.js",
46
+ "dist/**/*.d.ts",
47
+ "dist/**/*.d.ts.map",
48
+ "!dist/demo.*",
49
+ "!dist/scan-demo.*",
50
+ "!dist/test-*.* ",
51
+ "!dist/quick-test.*",
52
+ "README.md",
53
+ "LICENSE"
54
+ ],
55
+ "packageManager": "pnpm@10.30.0",
56
+ "devDependencies": {
57
+ "@types/node": "^25.2.3",
58
+ "tsx": "^4.21.0",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^4.0.18"
61
+ },
62
+ "dependencies": {
63
+ "fast-glob": "^3.3.3",
64
+ "ignore": "^7.0.5",
65
+ "postcss": "^8.5.6",
66
+ "postcss-selector-parser": "^7.1.1",
67
+ "zod": "^4.3.6"
68
+ }
69
+ }