@ovh-ux/manager-static-analysis-kit 0.7.1 → 0.9.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +59 -69
  3. package/bin/cli-path-config.js +31 -0
  4. package/bin/code-duplication-cli.js +96 -0
  5. package/bin/code-duplication-tests-cli.js +68 -0
  6. package/bin/lint-cli.js +18 -16
  7. package/bin/perf-budgets-cli.js +120 -0
  8. package/bin/perf-budgets-tests-cli.js +72 -0
  9. package/bin/static-dynamic-quality-checks.js +138 -0
  10. package/bin/tests-coverage-cli-tests.js +67 -0
  11. package/bin/tests-coverage-cli.js +135 -0
  12. package/bin/types-coverage-cli.js +127 -0
  13. package/bin/types-coverage-tests-cli.js +68 -0
  14. package/bin/utils/apps-utils.js +64 -0
  15. package/bin/utils/args-parse-utils.js +222 -0
  16. package/bin/utils/file-utils.js +52 -0
  17. package/bin/utils/log-utils.js +29 -0
  18. package/bin/utils/runner-utils.js +85 -0
  19. package/bin/utils/turbo-utils.js +139 -0
  20. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +6 -0
  21. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -0
  22. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +240 -0
  23. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts +60 -0
  24. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts.map +1 -0
  25. package/dist/adapters/code-duplication/types/CodeDuplication.js +1 -0
  26. package/dist/adapters/eslint/rules/eslint-naming-conventions.d.ts.map +1 -1
  27. package/dist/adapters/eslint/rules/eslint-naming-conventions.js +1 -0
  28. package/dist/adapters/eslint/rules/eslint-prettier.js +1 -1
  29. package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.d.ts +62 -0
  30. package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.d.ts.map +1 -0
  31. package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.js +73 -0
  32. package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.d.ts +19 -0
  33. package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.d.ts.map +1 -0
  34. package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.js +32 -0
  35. package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.d.ts +4 -0
  36. package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.d.ts.map +1 -0
  37. package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.js +89 -0
  38. package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.d.ts +6 -0
  39. package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.d.ts.map +1 -0
  40. package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.js +1 -0
  41. package/dist/adapters/html-w3c-validation/types/globals/vnu-jar.d.ts +5 -0
  42. package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.d.ts +7 -0
  43. package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.d.ts.map +1 -0
  44. package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.js +191 -0
  45. package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.d.ts +10 -0
  46. package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.d.ts.map +1 -0
  47. package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.js +20 -0
  48. package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.d.ts +5 -0
  49. package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.d.ts.map +1 -0
  50. package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.js +35 -0
  51. package/dist/adapters/perf-budgets/types/PerfBudget.d.ts +22 -0
  52. package/dist/adapters/perf-budgets/types/PerfBudget.d.ts.map +1 -0
  53. package/dist/adapters/perf-budgets/types/PerfBudget.js +1 -0
  54. package/dist/adapters/perf-budgets/types/PerfBudgetAssets.d.ts +13 -0
  55. package/dist/adapters/perf-budgets/types/PerfBudgetAssets.d.ts.map +1 -0
  56. package/dist/adapters/perf-budgets/types/PerfBudgetAssets.js +1 -0
  57. package/dist/adapters/perf-budgets/types/PerfBudgetScore.d.ts +13 -0
  58. package/dist/adapters/perf-budgets/types/PerfBudgetScore.d.ts.map +1 -0
  59. package/dist/adapters/perf-budgets/types/PerfBudgetScore.js +1 -0
  60. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts +4 -0
  61. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts.map +1 -0
  62. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js +192 -0
  63. package/dist/adapters/tests-coverage/types/TestsCoverage.d.ts +36 -0
  64. package/dist/adapters/tests-coverage/types/TestsCoverage.d.ts.map +1 -0
  65. package/dist/adapters/tests-coverage/types/TestsCoverage.js +6 -0
  66. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts +4 -0
  67. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts.map +1 -0
  68. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js +158 -0
  69. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts +30 -0
  70. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts.map +1 -0
  71. package/dist/adapters/types-coverage/types/TypesCoverage.js +1 -0
  72. package/dist/configs/architecture-analysis-config.d.ts +5 -0
  73. package/dist/configs/architecture-analysis-config.d.ts.map +1 -0
  74. package/dist/configs/{madge-config.js → architecture-analysis-config.js} +1 -1
  75. package/dist/configs/assets-budgets-config.d.ts +28 -0
  76. package/dist/configs/assets-budgets-config.d.ts.map +1 -0
  77. package/dist/configs/assets-budgets-config.js +45 -0
  78. package/dist/configs/bundle-analysis-config.d.ts +8 -0
  79. package/dist/configs/bundle-analysis-config.d.ts.map +1 -0
  80. package/dist/configs/bundle-analysis-config.js +7 -0
  81. package/dist/configs/code-duplication-config.d.ts +21 -0
  82. package/dist/configs/code-duplication-config.d.ts.map +1 -0
  83. package/dist/configs/code-duplication-config.js +21 -0
  84. package/dist/configs/{prettier-config.d.ts → code-formatter-config.d.ts} +1 -1
  85. package/dist/configs/code-formatter-config.d.ts.map +1 -0
  86. package/dist/configs/file-globs-config.d.ts +1 -4
  87. package/dist/configs/file-globs-config.d.ts.map +1 -1
  88. package/dist/configs/file-globs-config.js +1 -4
  89. package/dist/configs/naming-conventions-config.d.ts.map +1 -1
  90. package/dist/configs/naming-conventions-config.js +2 -0
  91. package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
  92. package/dist/configs/shared-exclusion-patterns.js +3 -1
  93. package/dist/configs/tests-coverage-config.d.ts +12 -0
  94. package/dist/configs/tests-coverage-config.d.ts.map +1 -0
  95. package/dist/configs/tests-coverage-config.js +11 -0
  96. package/dist/configs/types-coverage-config.d.ts +19 -0
  97. package/dist/configs/types-coverage-config.d.ts.map +1 -0
  98. package/dist/configs/types-coverage-config.js +27 -0
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +35 -7
  101. package/dist/configs/jscpd-config.d.ts +0 -7
  102. package/dist/configs/jscpd-config.d.ts.map +0 -1
  103. package/dist/configs/jscpd-config.js +0 -7
  104. package/dist/configs/madge-config.d.ts +0 -5
  105. package/dist/configs/madge-config.d.ts.map +0 -1
  106. package/dist/configs/prettier-config.d.ts.map +0 -1
  107. /package/dist/configs/{prettier-config.js → code-formatter-config.js} +0 -0
@@ -0,0 +1,191 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { assetsBudgetsConfig } from '../../../configs/assets-budgets-config.js';
4
+ import { evaluatePerfBudgets } from '../rules/perfs-budgets-rules.js';
5
+ import { scoreAllPerfBudgets } from '../rules/perfs-budgets-scoring.js';
6
+ export function escapeHtml(str) {
7
+ return String(str)
8
+ .replace(/&/g, '&')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;')
11
+ .replace(/"/g, '&quot;')
12
+ .replace(/'/g, '&#039;');
13
+ }
14
+ const buildAssetsInfos = (data) => data.map((entry) => {
15
+ const filename = entry?.filename ?? 'unknown';
16
+ const sizeKb = typeof entry?.parsedSize === 'number' ? entry.parsedSize / 1024 : 0;
17
+ let type = 'other';
18
+ if (/\.js$/i.test(filename))
19
+ type = 'js';
20
+ else if (/\.css$/i.test(filename))
21
+ type = 'css';
22
+ else if (/\.html$/i.test(filename))
23
+ type = 'html';
24
+ else if (/\.(png|jpe?g|gif|svg|webp)$/i.test(filename))
25
+ type = 'img';
26
+ return { filename, type, sizeKb };
27
+ });
28
+ export function parseAnalyzerJson(raw) {
29
+ if (!Array.isArray(raw)) {
30
+ return { jsKb: 0, cssKb: 0, htmlKb: 0, imgKb: 0, assets: [] };
31
+ }
32
+ const assets = buildAssetsInfos(raw);
33
+ const sumByType = (type) => assets.filter((a) => a.type === type).reduce((sum, a) => sum + a.sizeKb, 0);
34
+ return {
35
+ jsKb: sumByType('js'),
36
+ cssKb: sumByType('css'),
37
+ htmlKb: sumByType('html'),
38
+ imgKb: sumByType('img'),
39
+ assets,
40
+ };
41
+ }
42
+ export function collectPerfBudgets(reportOutputDir) {
43
+ const reportsRoot = path.join(reportOutputDir);
44
+ if (!fs.existsSync(reportsRoot)) {
45
+ throw new Error(`Reports directory not found: ${reportsRoot}`);
46
+ }
47
+ const apps = fs.readdirSync(reportsRoot).filter((d) => {
48
+ const appJson = path.join(reportsRoot, d, 'bundle-report.json');
49
+ return fs.existsSync(appJson);
50
+ });
51
+ const results = [];
52
+ for (const app of apps) {
53
+ const jsonPath = path.join(reportsRoot, app, 'bundle-report.json');
54
+ const raw = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
55
+ const { jsKb, cssKb, htmlKb, imgKb, assets } = parseAnalyzerJson(raw);
56
+ const appResult = {
57
+ ...evaluatePerfBudgets({ app, jsKb, cssKb, htmlKb, imgKb }),
58
+ assets,
59
+ };
60
+ results.push(appResult);
61
+ }
62
+ const scores = scoreAllPerfBudgets(results);
63
+ return {
64
+ results,
65
+ scores,
66
+ medians: assetsBudgetsConfig.medians,
67
+ };
68
+ }
69
+ export function generatePerfBudgetsHtml(summary) {
70
+ const { results, scores, medians } = summary;
71
+ const heavyCount = assetsBudgetsConfig.heavyAssetsCount;
72
+ function colorizeClass(status) {
73
+ switch (status) {
74
+ case 'ok':
75
+ return 'status-ok';
76
+ case 'near':
77
+ return 'status-near';
78
+ case 'exceed':
79
+ return 'status-exceed';
80
+ }
81
+ }
82
+ function heaviestOfType(assets, type) {
83
+ return assets.filter((a) => a.type === type).sort((a, b) => b.sizeKb - a.sizeKb)[0];
84
+ }
85
+ function formatCell(total, status, type, assets) {
86
+ const heavy = heaviestOfType(assets, type);
87
+ const heavyInfo = heavy
88
+ ? `<br/><small><b>Largest ${type.toUpperCase()} asset:</b> <code>${escapeHtml(heavy.filename)}</code> — ${heavy.sizeKb.toFixed(1)} KB</small>`
89
+ : '';
90
+ return `<td class="${colorizeClass(status)}">
91
+ <b>Total ${type.toUpperCase()} assets size:</b> ${total.toFixed(1)} KB
92
+ ${heavyInfo}
93
+ </td>`;
94
+ }
95
+ const rows = scores
96
+ .map((s) => {
97
+ const result = results.find((r) => r.app === s.app);
98
+ const heavyAssets = [...result.assets]
99
+ .sort((a, b) => b.sizeKb - a.sizeKb)
100
+ .slice(0, heavyCount);
101
+ const assetsHtml = heavyAssets
102
+ .map((a) => `<li><code>${escapeHtml(a.filename)}</code> — <strong>${a.sizeKb.toFixed(1)} KB</strong> (${a.type})</li>`)
103
+ .join('\n');
104
+ return `
105
+ <tr>
106
+ <td><strong>${escapeHtml(s.app)}</strong></td>
107
+ ${formatCell(result.jsKb, result.jsStatus, 'js', result.assets)}
108
+ ${formatCell(result.cssKb, result.cssStatus, 'css', result.assets)}
109
+ ${formatCell(result.htmlKb, result.htmlStatus, 'html', result.assets)}
110
+ ${formatCell(result.imgKb, result.imgStatus, 'img', result.assets)}
111
+ <td><b>${escapeHtml(s.status.toUpperCase())}</b></td>
112
+ <td>
113
+ <details>
114
+ <summary>Top ${heavyCount} Assets</summary>
115
+ <ul>${assetsHtml || '<li>No assets found</li>'}</ul>
116
+ </details>
117
+ </td>
118
+ </tr>
119
+ `;
120
+ })
121
+ .join('\n');
122
+ const linksHtml = assetsBudgetsConfig.links.httpArchive
123
+ .map(({ label, url }) => `<li><a href="${escapeHtml(url)}" target="_blank">${escapeHtml(label)}</a></li>`)
124
+ .join('\n');
125
+ const tipsHtml = assetsBudgetsConfig.links.tips
126
+ .map(({ label, desc }) => `<li><strong>${escapeHtml(label)}:</strong> ${escapeHtml(desc)}</li>`)
127
+ .join('\n');
128
+ const optimizationHtml = assetsBudgetsConfig.links.optimization
129
+ .map(({ label, url }) => `<li><a href="${escapeHtml(url)}" target="_blank">${escapeHtml(label)}</a></li>`)
130
+ .join('\n');
131
+ return `
132
+ <html lang="en">
133
+ <head>
134
+ <meta charset="UTF-8" />
135
+ <title>Perf Budgets Report</title>
136
+ <style>
137
+ body { font-family: Arial, sans-serif; padding: 20px; background: #fafafa; }
138
+ h1, h2 { text-align: center; }
139
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
140
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
141
+ th { background: #f0f0f0; }
142
+ tr:nth-child(even) { background: #f9f9f9; }
143
+ tr:hover { background: #f1f7ff; }
144
+ .status-ok { background: #e6f9e6; color: #207520; }
145
+ .status-near { background: #fff8e1; color: #a87900; }
146
+ .status-exceed { background: #fdecea; color: #b71c1c; }
147
+ code { font-family: monospace; background: #f5f5f5; padding: 1px 3px; border-radius: 3px; }
148
+ details summary { cursor: pointer; }
149
+ .box { border: 1px solid #ccc; background: #fff; padding: 10px; margin: 15px 0; border-radius: 6px; }
150
+ </style>
151
+ </head>
152
+ <body>
153
+ <h1>Performance Budgets Report</h1>
154
+ <div class="box">
155
+ <h2>Medians (Desktop)</h2>
156
+ <p>
157
+ JS: ${medians.jsKb} KB ·
158
+ CSS: ${medians.cssKb} KB ·
159
+ HTML: ${medians.htmlKb} KB ·
160
+ IMG: ${medians.imgKb} KB
161
+ </p>
162
+ <p><b>Links:</b></p>
163
+ <ul>${linksHtml}</ul>
164
+ </div>
165
+
166
+ <div class="box">
167
+ <details open>
168
+ <summary><b>Optimization & Best Practices</b></summary>
169
+ <h3>Tips</h3>
170
+ <ul>${tipsHtml}</ul>
171
+ <h3>Resources</h3>
172
+ <ul>${optimizationHtml}</ul>
173
+ </details>
174
+ </div>
175
+
176
+ <h2>Applications</h2>
177
+ <table>
178
+ <tr>
179
+ <th>App</th>
180
+ <th>JS</th>
181
+ <th>CSS</th>
182
+ <th>HTML</th>
183
+ <th>IMG</th>
184
+ <th>Status</th>
185
+ <th>Details</th>
186
+ </tr>
187
+ ${rows}
188
+ </table>
189
+ </body>
190
+ </html>`;
191
+ }
@@ -0,0 +1,10 @@
1
+ import { PerfBudgetResult } from '../types/PerfBudget.js';
2
+ export declare function evaluateAsset(sizeKb: number, medianKb: number): 'ok' | 'near' | 'exceed';
3
+ export declare function evaluatePerfBudgets(appReport: {
4
+ app: string;
5
+ jsKb: number;
6
+ cssKb: number;
7
+ htmlKb: number;
8
+ imgKb: number;
9
+ }): PerfBudgetResult;
10
+ //# sourceMappingURL=perfs-budgets-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perfs-budgets-rules.d.ts","sourceRoot":"","sources":["../../../../src/adapters/perf-budgets/rules/perfs-budgets-rules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAQvD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,QAAQ,CASxF;AAOD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,gBAAgB,CAQnB"}
@@ -0,0 +1,20 @@
1
+ import { assetsBudgetsConfig } from '../../../configs/assets-budgets-config.js';
2
+ export function evaluateAsset(sizeKb, medianKb) {
3
+ const { nearFactor, exceedFactor } = assetsBudgetsConfig.thresholds;
4
+ if (sizeKb > medianKb * exceedFactor) {
5
+ return 'exceed';
6
+ }
7
+ else if (sizeKb >= medianKb * nearFactor) {
8
+ return 'near';
9
+ }
10
+ return 'ok';
11
+ }
12
+ export function evaluatePerfBudgets(appReport) {
13
+ return {
14
+ ...appReport,
15
+ jsStatus: evaluateAsset(appReport.jsKb, assetsBudgetsConfig.medians.jsKb),
16
+ cssStatus: evaluateAsset(appReport.cssKb, assetsBudgetsConfig.medians.cssKb),
17
+ htmlStatus: evaluateAsset(appReport.htmlKb, assetsBudgetsConfig.medians.htmlKb),
18
+ imgStatus: evaluateAsset(appReport.imgKb, assetsBudgetsConfig.medians.imgKb),
19
+ };
20
+ }
@@ -0,0 +1,5 @@
1
+ import { PerfBudgetResult } from '../types/PerfBudget.js';
2
+ import { PerfBudgetScoreSummary } from '../types/PerfBudgetScore.js';
3
+ export declare function scorePerfBudget(result: PerfBudgetResult): PerfBudgetScoreSummary;
4
+ export declare function scoreAllPerfBudgets(results: PerfBudgetResult[]): PerfBudgetScoreSummary[];
5
+ //# sourceMappingURL=perfs-budgets-scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perfs-budgets-scoring.d.ts","sourceRoot":"","sources":["../../../../src/adapters/perf-budgets/rules/perfs-budgets-scoring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAwB,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAuBxF,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,sBAAsB,CAqBhF;AAOD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,sBAAsB,EAAE,CAEzF"}
@@ -0,0 +1,35 @@
1
+ function statusToScore(status) {
2
+ switch (status) {
3
+ case 'ok':
4
+ return 0;
5
+ case 'near':
6
+ return 1;
7
+ case 'exceed':
8
+ return 2;
9
+ }
10
+ }
11
+ export function scorePerfBudget(result) {
12
+ const scores = {
13
+ js: statusToScore(result.jsStatus),
14
+ css: statusToScore(result.cssStatus),
15
+ html: statusToScore(result.htmlStatus),
16
+ img: statusToScore(result.imgStatus),
17
+ };
18
+ const totalScore = Object.values(scores).reduce((sum, v) => sum + v, 0);
19
+ let status;
20
+ if (totalScore === 0)
21
+ status = 'pass';
22
+ else if (totalScore <= 2)
23
+ status = 'warn';
24
+ else
25
+ status = 'fail';
26
+ return {
27
+ app: result.app,
28
+ scores,
29
+ totalScore,
30
+ status,
31
+ };
32
+ }
33
+ export function scoreAllPerfBudgets(results) {
34
+ return results.map(scorePerfBudget);
35
+ }
@@ -0,0 +1,22 @@
1
+ import { assetsBudgetsConfig } from '../../../configs/assets-budgets-config.js';
2
+ import { ParsedAsset } from './PerfBudgetAssets.js';
3
+ import { PerfBudgetScoreSummary } from './PerfBudgetScore.js';
4
+ export interface PerfBudgetResult {
5
+ app: string;
6
+ jsKb: number;
7
+ cssKb: number;
8
+ htmlKb: number;
9
+ imgKb: number;
10
+ jsStatus: 'ok' | 'near' | 'exceed';
11
+ cssStatus: 'ok' | 'near' | 'exceed';
12
+ htmlStatus: 'ok' | 'near' | 'exceed';
13
+ imgStatus: 'ok' | 'near' | 'exceed';
14
+ }
15
+ export interface PerfBudgetsSummary {
16
+ results: (PerfBudgetResult & {
17
+ assets: ParsedAsset[];
18
+ })[];
19
+ scores: PerfBudgetScoreSummary[];
20
+ medians: typeof assetsBudgetsConfig.medians;
21
+ }
22
+ //# sourceMappingURL=PerfBudget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerfBudget.d.ts","sourceRoot":"","sources":["../../../../src/adapters/perf-budgets/types/PerfBudget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAK3D,MAAM,WAAW,gBAAgB;IAE/B,GAAG,EAAE,MAAM,CAAC;IAEZ,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,KAAK,EAAE,MAAM,CAAC;IAGd,QAAQ,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEnC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEpC,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IAErC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;CACrC;AAgDD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,gBAAgB,GAAG;QAAE,MAAM,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,EAAE,CAAC;IAC1D,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,OAAO,EAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC;CAC7C"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface ParsedAsset {
2
+ filename: string;
3
+ type: 'js' | 'css' | 'html' | 'img' | 'other';
4
+ sizeKb: number;
5
+ }
6
+ export interface ParsedBundle {
7
+ jsKb: number;
8
+ cssKb: number;
9
+ htmlKb: number;
10
+ imgKb: number;
11
+ assets: ParsedAsset[];
12
+ }
13
+ //# sourceMappingURL=PerfBudgetAssets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerfBudgetAssets.d.ts","sourceRoot":"","sources":["../../../../src/adapters/perf-budgets/types/PerfBudgetAssets.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAE1B,QAAQ,EAAE,MAAM,CAAC;IAEjB,IAAI,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;IAE9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD,MAAM,WAAW,YAAY;IAE3B,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB"}
@@ -0,0 +1,13 @@
1
+ export type PerfBudgetScoreLevel = 0 | 1 | 2;
2
+ export interface PerfBudgetScoreSummary {
3
+ app: string;
4
+ scores: {
5
+ js: PerfBudgetScoreLevel;
6
+ css: PerfBudgetScoreLevel;
7
+ html: PerfBudgetScoreLevel;
8
+ img: PerfBudgetScoreLevel;
9
+ };
10
+ totalScore: number;
11
+ status: 'pass' | 'warn' | 'fail';
12
+ }
13
+ //# sourceMappingURL=PerfBudgetScore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerfBudgetScore.d.ts","sourceRoot":"","sources":["../../../../src/adapters/perf-budgets/types/PerfBudgetScore.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAK7C,MAAM,WAAW,sBAAsB;IAErC,GAAG,EAAE,MAAM,CAAC;IAEZ,MAAM,EAAE;QACN,EAAE,EAAE,oBAAoB,CAAC;QACzB,GAAG,EAAE,oBAAoB,CAAC;QAC1B,IAAI,EAAE,oBAAoB,CAAC;QAC3B,GAAG,EAAE,oBAAoB,CAAC;KAC3B,CAAC;IAEF,UAAU,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAClC"}
@@ -0,0 +1,4 @@
1
+ import { CombinedTestsCoverage } from '../types/TestsCoverage.js';
2
+ export declare function collectTestsCoverage(reportRoot: string): CombinedTestsCoverage;
3
+ export declare function generateTestsCoverageHtml(summary: CombinedTestsCoverage, reportRoot: string): string;
4
+ //# sourceMappingURL=tests-coverage-analysis-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tests-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,qBAAqB,EAKtB,MAAM,wBAAwB,CAAC;AA2HhC,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAmC9E;AAiED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,MAAM,GACjB,MAAM,CAuER"}
@@ -0,0 +1,192 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { testsCoverageConfig } from '../../../configs/tests-coverage-config.js';
4
+ import { TESTS_COVERAGE_KEYS, } from '../types/TestsCoverage.js';
5
+ function readJsonSafe(file) {
6
+ try {
7
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function readAppSummaryAt(reportRoot, app) {
14
+ return readJsonSafe(path.join(reportRoot, app, 'coverage-summary.json'));
15
+ }
16
+ function normalizePct(pct) {
17
+ const n = Number(pct);
18
+ if (!Number.isFinite(n) || n < 0)
19
+ return 0;
20
+ return +n.toFixed(2);
21
+ }
22
+ function extractWorstFilesForApp(reportRoot, app, metric = testsCoverageConfig.worstFiles.metric, threshold = testsCoverageConfig.worstFiles.threshold, count = testsCoverageConfig.worstFiles.count) {
23
+ const summary = readAppSummaryAt(reportRoot, app);
24
+ if (!summary)
25
+ return [];
26
+ const rows = [];
27
+ for (const [key, value] of Object.entries(summary)) {
28
+ if (key === 'total')
29
+ continue;
30
+ const bucket = value?.[metric];
31
+ if (!bucket)
32
+ continue;
33
+ const total = bucket.total ?? 0;
34
+ const covered = bucket.covered ?? 0;
35
+ if (!Number.isFinite(total) || total <= 0)
36
+ continue;
37
+ const percentage = normalizePct(bucket.pct);
38
+ if (percentage >= threshold)
39
+ continue;
40
+ rows.push([key, { covered, total, percentage }]);
41
+ }
42
+ rows.sort((a, b) => a[1].percentage - b[1].percentage || b[1].total - a[1].total || a[0].localeCompare(b[0]));
43
+ return rows.slice(0, Math.max(0, count));
44
+ }
45
+ function addTotals(a, b) {
46
+ const out = {};
47
+ for (const k of TESTS_COVERAGE_KEYS) {
48
+ const A = a[k] ?? { total: 0, covered: 0, pct: 0 };
49
+ const B = b[k] ?? { total: 0, covered: 0, pct: 0 };
50
+ const total = (A.total ?? 0) + (B.total ?? 0);
51
+ const covered = (A.covered ?? 0) + (B.covered ?? 0);
52
+ out[k] = {
53
+ total,
54
+ covered,
55
+ pct: total > 0 ? normalizePct((100 * covered) / total) : 0,
56
+ };
57
+ }
58
+ return out;
59
+ }
60
+ export function collectTestsCoverage(reportRoot) {
61
+ if (!fs.existsSync(reportRoot))
62
+ return { apps: {}, totals: undefined };
63
+ const appDirs = (() => {
64
+ try {
65
+ return fs
66
+ .readdirSync(reportRoot, { withFileTypes: true })
67
+ .filter((d) => d.isDirectory())
68
+ .map((d) => d.name);
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ })();
74
+ const apps = {};
75
+ let totals;
76
+ for (const app of appDirs) {
77
+ const summary = readAppSummaryAt(reportRoot, app);
78
+ const total = summary?.total;
79
+ if (!total)
80
+ continue;
81
+ const row = {};
82
+ for (const k of TESTS_COVERAGE_KEYS) {
83
+ const g = total[k];
84
+ row[k] = g
85
+ ? { total: g.total ?? 0, covered: g.covered ?? 0, pct: normalizePct(g.pct) }
86
+ : { total: 0, covered: 0, pct: 0 };
87
+ }
88
+ apps[app] = row;
89
+ totals = totals ? addTotals(totals, row) : row;
90
+ }
91
+ return { apps, totals };
92
+ }
93
+ function getTestsCoverageStatusColor(pct) {
94
+ const { green, orange } = testsCoverageConfig.thresholds;
95
+ if (pct >= green)
96
+ return 'green';
97
+ if (pct >= orange)
98
+ return 'orange';
99
+ return 'red';
100
+ }
101
+ function renderWorstFiles(app, worst) {
102
+ const { threshold, metric } = testsCoverageConfig.worstFiles;
103
+ if (!worst.length) {
104
+ return `
105
+ <details>
106
+ <summary>⚠️ Worst covered files (${metric})</summary>
107
+ <p style="margin:0.5rem 0; color:#888;">No files below threshold 🎉</p>
108
+ </details>`;
109
+ }
110
+ const rows = worst
111
+ .map(([file, f]) => `
112
+ <tr>
113
+ <td colspan="2">${file}</td>
114
+ <td>${f.covered}/${f.total}</td>
115
+ <td style="color:${getTestsCoverageStatusColor(f.percentage)}">${f.percentage.toFixed(2)}%</td>
116
+ </tr>`)
117
+ .join('');
118
+ return `
119
+ <details>
120
+ <summary>⚠️ Worst covered files (below ${threshold}% — ${metric})</summary>
121
+ <table style="margin:0.5rem 0; border:1px solid #ccc; width:95%;">
122
+ <tbody>${rows}</tbody>
123
+ </table>
124
+ </details>`;
125
+ }
126
+ export function generateTestsCoverageHtml(summary, reportRoot) {
127
+ const head = `
128
+ <style>
129
+ body { font-family: Arial, sans-serif; margin: 2rem; }
130
+ h1 { margin-bottom: 1rem; }
131
+ table { border-collapse: collapse; width: 100%; margin-bottom: 2rem; }
132
+ th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; vertical-align: top; }
133
+ th { background: #f0f0f0; }
134
+ details summary { cursor: pointer; font-weight: bold; }
135
+ .green { color: #0a0; }
136
+ .orange { color: #c80; }
137
+ .red { color: #c00; }
138
+ .row-green { background:#e6ffed; }
139
+ .row-orange { background:#fff8e1; }
140
+ .row-red { background:#ffe6e6; }
141
+ </style>
142
+ <h1>Tests Coverage</h1>
143
+ `;
144
+ const appRow = (app, r) => {
145
+ const status = getTestsCoverageStatusColor(r.lines?.pct ?? 0);
146
+ return `
147
+ <tr class="row-${status}">
148
+ <td>${app}</td>
149
+ ${TESTS_COVERAGE_KEYS.map((k) => {
150
+ const pct = r[k]?.pct ?? 0;
151
+ const cls = getTestsCoverageStatusColor(pct);
152
+ return `<td class="${cls}" style="font-weight:600">${pct}%</td>`;
153
+ }).join('')}
154
+ </tr>
155
+ <tr>
156
+ <td colspan="5">
157
+ ${renderWorstFiles(app, extractWorstFilesForApp(reportRoot, app))}
158
+ </td>
159
+ </tr>
160
+ `;
161
+ };
162
+ const totalsRow = summary.totals
163
+ ? `
164
+ <tr>
165
+ <td><b>Totals</b></td>
166
+ ${TESTS_COVERAGE_KEYS.map((k) => {
167
+ const pct = summary.totals?.[k]?.pct ?? 0;
168
+ const cls = getTestsCoverageStatusColor(pct);
169
+ return `<td class="${cls}"><b>${pct}%</b></td>`;
170
+ }).join('')}
171
+ </tr>`
172
+ : '';
173
+ const table = `
174
+ <table>
175
+ <thead>
176
+ <tr>
177
+ <th>Application</th>
178
+ <th>Lines %</th>
179
+ <th>Branches %</th>
180
+ <th>Functions %</th>
181
+ <th>Statements %</th>
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ ${Object.entries(summary.apps)
186
+ .map(([app, r]) => appRow(app, r))
187
+ .join('')}
188
+ ${totalsRow}
189
+ </tbody>
190
+ </table>`;
191
+ return `<!doctype html><meta charset="utf-8">${head}${table}`;
192
+ }
@@ -0,0 +1,36 @@
1
+ export type TestsCoverageKey = 'lines' | 'branches' | 'functions' | 'statements';
2
+ export declare const TESTS_COVERAGE_KEYS: readonly TestsCoverageKey[];
3
+ export interface TestsCoverageBucket {
4
+ total: number;
5
+ covered: number;
6
+ pct: number;
7
+ }
8
+ export type TestsCoverageRow = Record<TestsCoverageKey, TestsCoverageBucket>;
9
+ export interface CombinedTestsCoverage {
10
+ apps: Record<string, TestsCoverageRow>;
11
+ totals?: TestsCoverageRow;
12
+ }
13
+ export interface TestsCoverageSummary {
14
+ total?: Partial<Record<TestsCoverageKey, Partial<TestsCoverageBucket>>>;
15
+ }
16
+ export type TestsCoverageMetric = keyof TestsCoverageRow;
17
+ export interface WorstFilesOptions {
18
+ reportRoot: string;
19
+ count: number;
20
+ metric: TestsCoverageMetric;
21
+ }
22
+ export interface FileCoverageRow {
23
+ file: string;
24
+ total: number;
25
+ covered: number;
26
+ pct: number;
27
+ }
28
+ export type WorstFileEntry = readonly [
29
+ file: string,
30
+ stats: {
31
+ covered: number;
32
+ total: number;
33
+ percentage: number;
34
+ }
35
+ ];
36
+ //# sourceMappingURL=TestsCoverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestsCoverage.d.ts","sourceRoot":"","sources":["../../../../src/adapters/tests-coverage/types/TestsCoverage.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC;AAOjF,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAKjD,CAAC;AAOX,MAAM,WAAW,mBAAmB;IAElC,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,MAAM,CAAC;IAEhB,GAAG,EAAE,MAAM,CAAC;CACb;AAOD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;AAQ7E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAmBD,MAAM,WAAW,oBAAoB;IAEnC,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;CACzE;AAMD,MAAM,MAAM,mBAAmB,GAAG,MAAM,gBAAgB,CAAC;AAKzD,MAAM,WAAW,iBAAiB;IAEhC,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAOD,MAAM,WAAW,eAAe;IAE9B,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,MAAM,CAAC;IAEhB,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,MAAM,MAAM,cAAc,GAAG,SAAS;IACpC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAC9D,CAAC"}
@@ -0,0 +1,6 @@
1
+ export const TESTS_COVERAGE_KEYS = [
2
+ 'lines',
3
+ 'branches',
4
+ 'functions',
5
+ 'statements',
6
+ ];
@@ -0,0 +1,4 @@
1
+ import { CoverageSummary } from '../types/TypesCoverage.js';
2
+ export declare function collectTypesCoverage(reportOutputDir: string): CoverageSummary;
3
+ export declare function generateTypesCoverageHtml(summary: CoverageSummary): string;
4
+ //# sourceMappingURL=types-coverage-analysis-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/types-coverage/helpers/types-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,eAAe,EAGhB,MAAM,wBAAwB,CAAC;AAoChC,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAkD7E;AA6ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAgC1E"}