@ovh-ux/manager-static-analysis-kit 0.9.0 → 0.10.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 (29) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/bin/perf-budgets-cli.js +1 -1
  3. package/bin/tests-coverage-cli.js +4 -2
  4. package/bin/types-coverage-cli.js +68 -0
  5. package/bin/utils/args-parse-utils.js +8 -1
  6. package/bin/utils/turbo-utils.js +14 -3
  7. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +4 -1
  8. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -1
  9. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +72 -18
  10. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts +3 -0
  11. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts.map +1 -1
  12. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts.map +1 -1
  13. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js +31 -8
  14. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts.map +1 -1
  15. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js +121 -48
  16. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts +8 -0
  17. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts.map +1 -1
  18. package/dist/configs/code-duplication-config.d.ts +1 -0
  19. package/dist/configs/code-duplication-config.d.ts.map +1 -1
  20. package/dist/configs/code-duplication-config.js +2 -1
  21. package/dist/configs/shared-exclusion-patterns.d.ts +1 -0
  22. package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
  23. package/dist/configs/shared-exclusion-patterns.js +20 -0
  24. package/dist/configs/tests-coverage-config.js +1 -1
  25. package/dist/configs/types-coverage-config.d.ts +14 -0
  26. package/dist/configs/types-coverage-config.d.ts.map +1 -1
  27. package/dist/configs/types-coverage-config.js +13 -16
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
@@ -22,6 +22,34 @@ function extractWorstFiles(report) {
22
22
  .slice(0, count)
23
23
  .map((f) => [f.file, { covered: f.covered, total: f.total, percentage: f.percentage }]);
24
24
  }
25
+ function readLooseTypes(appReportDir) {
26
+ const file = path.join(appReportDir, 'loose-types.json');
27
+ if (!fs.existsSync(file))
28
+ return undefined;
29
+ try {
30
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ function computeAppCoverage(report) {
37
+ const covered = report.covered ?? 0;
38
+ const total = report.total ?? 0;
39
+ const percentage = total > 0 ? (covered / total) * 100 : 0;
40
+ const fileEntries = Object.entries(report.fileCounts ?? {}).map(([file, f]) => normalizeFileEntry(file, f));
41
+ const percentages = fileEntries.filter((f) => f.total > 0).map((f) => f.percentage);
42
+ const minPercentage = percentages.length ? Math.min(...percentages) : percentage;
43
+ const maxPercentage = percentages.length ? Math.max(...percentages) : percentage;
44
+ return {
45
+ covered,
46
+ total,
47
+ percentage,
48
+ minPercentage,
49
+ maxPercentage,
50
+ worstFiles: extractWorstFiles(report),
51
+ };
52
+ }
25
53
  export function collectTypesCoverage(reportOutputDir) {
26
54
  const apps = {};
27
55
  let totalCovered = 0;
@@ -33,23 +61,17 @@ export function collectTypesCoverage(reportOutputDir) {
33
61
  .readdirSync(reportOutputDir)
34
62
  .filter((entry) => fs.statSync(path.join(reportOutputDir, entry)).isDirectory());
35
63
  for (const appDirName of appDirs) {
36
- const jsonPath = path.join(reportOutputDir, appDirName, 'typescript-coverage.json');
64
+ const appReportDir = path.join(reportOutputDir, appDirName);
65
+ const jsonPath = path.join(appReportDir, 'typescript-coverage.json');
37
66
  if (!fs.existsSync(jsonPath))
38
67
  continue;
39
68
  try {
40
69
  const report = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
41
- const covered = report.covered ?? 0;
42
- const total = report.total ?? 0;
43
- const percentage = total > 0 ? (covered / total) * 100 : 0;
44
- const appShortName = appDirName.replace(/^manager-/, '').replace(/-app$/, '');
45
- apps[appShortName] = {
46
- covered,
47
- total,
48
- percentage,
49
- worstFiles: extractWorstFiles(report),
50
- };
51
- totalCovered += covered;
52
- totalTypes += total;
70
+ const stats = computeAppCoverage(report);
71
+ stats.looseTypes = readLooseTypes(appReportDir);
72
+ apps[appDirName] = stats;
73
+ totalCovered += stats.covered;
74
+ totalTypes += stats.total;
53
75
  }
54
76
  catch (err) {
55
77
  apps[appDirName] = {
@@ -75,52 +97,79 @@ function getReportStatusColor(percentage) {
75
97
  return 'orange';
76
98
  return 'red';
77
99
  }
78
- function renderWorstFiles(app, worstFiles = []) {
100
+ function getLooseStatusColor(value, thresholds) {
101
+ if (value <= thresholds.green)
102
+ return 'green';
103
+ if (value <= thresholds.orange)
104
+ return 'orange';
105
+ return 'red';
106
+ }
107
+ function renderColoredPercentage(value) {
108
+ return `<span style="color:${getReportStatusColor(value)};">${value.toFixed(2)}%</span>`;
109
+ }
110
+ function renderLooseCell(value, thresholds) {
111
+ const color = getLooseStatusColor(value, thresholds);
112
+ return `<td style="font-weight:bold;color:${color};">${value}</td>`;
113
+ }
114
+ function renderCoverageCell(stats) {
115
+ const min = stats.minPercentage ?? 0;
116
+ const max = stats.maxPercentage ?? 0;
117
+ return `
118
+ ${stats.percentage.toFixed(2)}% (range:
119
+ ${renderColoredPercentage(min)} – ${renderColoredPercentage(max)})
120
+ `;
121
+ }
122
+ function renderWorstFilesTable(worstFiles = []) {
79
123
  if (!worstFiles.length) {
80
- return `
81
- <details>
82
- <summary>⚠️ Worst covered files</summary>
83
- <p style="margin:0.5rem 0; color:#888;">No files below threshold 🎉</p>
84
- </details>`;
124
+ return `<p class="coverage-empty">No type coverage issues 🎉</p>`;
85
125
  }
86
126
  const rows = worstFiles
87
127
  .map(([file, f]) => `
88
128
  <tr>
89
- <td colspan="2">${file}</td>
129
+ <td>${file}</td>
90
130
  <td>${f.covered}/${f.total}</td>
91
- <td style="color:${getReportStatusColor(f.percentage)}">
92
- ${f.percentage.toFixed(2)}%
93
- </td>
131
+ <td>${renderColoredPercentage(f.percentage)}</td>
94
132
  </tr>`)
95
133
  .join('');
96
134
  return `
97
- <details>
98
- <summary>⚠️ Worst covered files (below ${typesCoverageConfig.worstFiles.threshold}%)</summary>
99
- <table style="margin:0.5rem 0; border:1px solid #ccc; width:95%;">
100
- <tbody>${rows}</tbody>
101
- </table>
135
+ <table class="coverage-subtable">
136
+ <thead><tr><th>File</th><th>Covered/Total</th><th>%</th></tr></thead>
137
+ <tbody>${rows}</tbody>
138
+ </table>`;
139
+ }
140
+ function renderAppDetails(app, stats) {
141
+ return `
142
+ <details class="coverage-details">
143
+ <summary>📊 Type Coverage Overview for <strong>${app}</strong></summary>
144
+ <div class="coverage-card">
145
+ <h4>Type Coverage Worst Files</h4>
146
+ ${renderWorstFilesTable(stats.worstFiles)}
147
+ </div>
102
148
  </details>`;
103
149
  }
150
+ function renderAppRow(app, stats) {
151
+ if (stats.error) {
152
+ return `<tr style="background:#ffe0e0">
153
+ <td>${app}</td>
154
+ <td colspan="7" style="color:red">${stats.error}</td>
155
+ </tr>`;
156
+ }
157
+ const loose = stats.looseTypes || { as: 0, any: 0, unknown: 0 };
158
+ return `
159
+ <tr style="background:#f0f0f0">
160
+ <td>${app}</td>
161
+ <td>${stats.covered}</td>
162
+ <td>${stats.total}</td>
163
+ <td style="font-weight:bold;">${renderCoverageCell(stats)}</td>
164
+ ${renderLooseCell(loose.as, typesCoverageConfig.looseThresholds.as)}
165
+ ${renderLooseCell(loose.any, typesCoverageConfig.looseThresholds.any)}
166
+ ${renderLooseCell(loose.unknown, typesCoverageConfig.looseThresholds.unknown)}
167
+ </tr>
168
+ <tr><td colspan="7">${renderAppDetails(app, stats)}</td></tr>`;
169
+ }
104
170
  function buildRows(apps) {
105
171
  return Object.entries(apps)
106
- .map(([app, stats]) => {
107
- if (stats.error) {
108
- return `<tr style="background:#ffe0e0">
109
- <td>${app}</td>
110
- <td colspan="3" style="color:red">${stats.error}</td>
111
- </tr>`;
112
- }
113
- const status = getReportStatusColor(stats.percentage);
114
- const statusBg = status === 'green' ? '#e6ffed' : status === 'orange' ? '#fff8e1' : '#ffe6e6';
115
- return `
116
- <tr style="background:${statusBg}">
117
- <td>${app}</td>
118
- <td>${stats.covered}</td>
119
- <td>${stats.total}</td>
120
- <td style="font-weight:bold;">${stats.percentage.toFixed(2)}%</td>
121
- </tr>
122
- <tr><td colspan="4">${renderWorstFiles(app, stats.worstFiles)}</td></tr>`;
123
- })
172
+ .map(([app, stats]) => renderAppRow(app, stats))
124
173
  .join('');
125
174
  }
126
175
  export function generateTypesCoverageHtml(summary) {
@@ -135,7 +184,28 @@ export function generateTypesCoverageHtml(summary) {
135
184
  table { border-collapse: collapse; width: 100%; margin-bottom: 2rem; }
136
185
  th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; vertical-align: top; }
137
186
  th { background: #f0f0f0; }
138
- details summary { cursor: pointer; font-weight: bold; }
187
+ .coverage-details {
188
+ margin: 0.3rem 0 0.8rem 1.5rem;
189
+ padding: 0.5rem;
190
+ border-left: 3px solid #ccc;
191
+ background: #fafafa;
192
+ border-radius: 4px;
193
+ }
194
+ .coverage-details summary {
195
+ font-weight: bold;
196
+ cursor: pointer;
197
+ }
198
+ .coverage-subtable {
199
+ margin-top: 0.5rem;
200
+ border: 1px solid #ddd;
201
+ width: 95%;
202
+ font-size: 0.9em;
203
+ }
204
+ .coverage-empty {
205
+ margin: 0.5rem 0;
206
+ color: #888;
207
+ font-style: italic;
208
+ }
139
209
  </style>
140
210
  </head>
141
211
  <body>
@@ -148,7 +218,10 @@ export function generateTypesCoverageHtml(summary) {
148
218
  <th>App</th>
149
219
  <th>Covered</th>
150
220
  <th>Total</th>
151
- <th>Coverage</th>
221
+ <th>Coverage (range)</th>
222
+ <th>Number of as</th>
223
+ <th>Number of any</th>
224
+ <th>Number of unknown</th>
152
225
  </tr>
153
226
  </thead>
154
227
  <tbody>${buildRows(summary.apps)}</tbody>
@@ -14,12 +14,20 @@ export interface FileCoverage {
14
14
  percentage: number;
15
15
  }
16
16
  export type WorstFileEntry = [string, FileCoverage];
17
+ export interface LooseTypesSummary {
18
+ as: number;
19
+ any: number;
20
+ unknown: number;
21
+ }
17
22
  export interface AppCoverageSummary {
18
23
  covered: number;
19
24
  total: number;
20
25
  percentage: number;
26
+ minPercentage?: number;
27
+ maxPercentage?: number;
21
28
  error?: string;
22
29
  worstFiles?: WorstFileEntry[];
30
+ looseTypes?: LooseTypesSummary;
23
31
  }
24
32
  export interface CoverageSummary {
25
33
  apps: Record<string, AppCoverageSummary>;
@@ -1 +1 @@
1
- {"version":3,"file":"TypesCoverage.d.ts","sourceRoot":"","sources":["../../../../src/adapters/types-coverage/types/TypesCoverage.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CACjB,MAAM,EACN;QACE,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CACF,CAAC;CACH;AAKD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAKpD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAKD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"TypesCoverage.d.ts","sourceRoot":"","sources":["../../../../src/adapters/types-coverage/types/TypesCoverage.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CACjB,MAAM,EACN;QACE,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CACF,CAAC;CACH;AAKD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAKpD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAG9B,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAKD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -8,6 +8,7 @@ export declare const codeDuplicationConfig: {
8
8
  absolute: boolean;
9
9
  formats: string[];
10
10
  ignore: string[];
11
+ ignorePatterns: string[];
11
12
  reporters: string[];
12
13
  worstFiles: {
13
14
  threshold: number;
@@ -1 +1 @@
1
- {"version":3,"file":"code-duplication-config.d.ts","sourceRoot":"","sources":["../../src/configs/code-duplication-config.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB;;;;;eAcb,MAAM,GAAG,IAAI;UAMhB,MAAM,GAAG,MAAM,GAAG,QAAQ;;aAW3B,MAAM,EAAE;;;;;;;;;;;CAgCxB,CAAC"}
1
+ {"version":3,"file":"code-duplication-config.d.ts","sourceRoot":"","sources":["../../src/configs/code-duplication-config.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB;;;;;eAcb,MAAM,GAAG,IAAI;UAMhB,MAAM,GAAG,MAAM,GAAG,QAAQ;;aAW3B,MAAM,EAAE;;;;;;;;;;;;CA4CxB,CAAC"}
@@ -9,10 +9,11 @@ export const codeDuplicationConfig = {
9
9
  absolute: true,
10
10
  formats: [],
11
11
  ignore: sharedExclusionPatterns,
12
+ ignorePatterns: ["import .* from '.*';"],
12
13
  reporters: ['html', 'json', 'console'],
13
14
  worstFiles: {
14
15
  threshold: 5,
15
- count: 10,
16
+ count: 15,
16
17
  },
17
18
  thresholds: {
18
19
  green: 10,
@@ -1,2 +1,3 @@
1
1
  export declare const sharedExclusionPatterns: string[];
2
+ export declare const sharedTypesExclusionPatterns: string[];
2
3
  //# sourceMappingURL=shared-exclusion-patterns.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-exclusion-patterns.d.ts","sourceRoot":"","sources":["../../src/configs/shared-exclusion-patterns.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,UAsCnC,CAAC"}
1
+ {"version":3,"file":"shared-exclusion-patterns.d.ts","sourceRoot":"","sources":["../../src/configs/shared-exclusion-patterns.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,UAuCnC,CAAC;AAyBF,eAAO,MAAM,4BAA4B,UAuBxC,CAAC"}
@@ -2,6 +2,7 @@ export const sharedExclusionPatterns = [
2
2
  '**/node_modules/**',
3
3
  '**/target/**',
4
4
  '**/dist/**',
5
+ '**/coverage/**',
5
6
  '**/.next/**',
6
7
  '**/next*/**',
7
8
  '**/.husky/**',
@@ -37,3 +38,22 @@ export const sharedExclusionPatterns = [
37
38
  '**/Messages_*_*.json',
38
39
  '**/**.md',
39
40
  ];
41
+ export const sharedTypesExclusionPatterns = [
42
+ '**/node_modules/**',
43
+ '**/dist/**',
44
+ '**/target/**',
45
+ '**/.next/**',
46
+ '**/.husky/**',
47
+ '**/.vscode/**',
48
+ '**/.idea/**',
49
+ '**/.gitlab/**',
50
+ '**/.github/**',
51
+ '**/webpack*.*',
52
+ '**/babel*.*',
53
+ '**/tailwind*.*',
54
+ '**/turbo.json',
55
+ '**/tsconfig.json',
56
+ '**/*.svg',
57
+ '**/**.md',
58
+ '**/Messages_*_*.json',
59
+ ];
@@ -2,7 +2,7 @@ export const testsCoverageConfig = {
2
2
  worstFiles: {
3
3
  metric: 'lines',
4
4
  threshold: 60,
5
- count: 10,
5
+ count: 15,
6
6
  },
7
7
  thresholds: {
8
8
  green: 80,
@@ -11,6 +11,20 @@ export declare const typesCoverageConfig: {
11
11
  green: number;
12
12
  orange: number;
13
13
  };
14
+ looseThresholds: {
15
+ as: {
16
+ green: number;
17
+ orange: number;
18
+ };
19
+ any: {
20
+ green: number;
21
+ orange: number;
22
+ };
23
+ unknown: {
24
+ green: number;
25
+ orange: number;
26
+ };
27
+ };
14
28
  worstFiles: {
15
29
  count: number;
16
30
  threshold: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types-coverage-config.d.ts","sourceRoot":"","sources":["../../src/configs/types-coverage-config.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;CAoD/B,CAAC"}
1
+ {"version":3,"file":"types-coverage-config.d.ts","sourceRoot":"","sources":["../../src/configs/types-coverage-config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqD/B,CAAC"}
@@ -1,27 +1,24 @@
1
+ import { sharedTypesExclusionPatterns } from './shared-exclusion-patterns.js';
1
2
  export const typesCoverageConfig = {
2
- threshold: 80,
3
+ threshold: 60,
3
4
  outputDir: 'coverage-ts',
4
5
  strict: true,
5
6
  debug: false,
6
7
  cache: true,
7
8
  project: 'tsconfig.json',
8
- ignoreFiles: [
9
- '**/*.stories.tsx',
10
- '**/*.stories.ts',
11
- '**/*.spec.ts',
12
- '**/*.spec.tsx',
13
- '**/jest.config.ts',
14
- '**/vitest.config.*',
15
- '**/dist/**',
16
- '**/.generated/**',
17
- ],
18
- ignoreUnread: true,
9
+ ignoreFiles: sharedTypesExclusionPatterns,
10
+ ignoreUnread: false,
19
11
  thresholds: {
20
- green: 90,
21
- orange: 70,
12
+ green: 80,
13
+ orange: 60,
14
+ },
15
+ looseThresholds: {
16
+ as: { green: 0, orange: 50 },
17
+ any: { green: 0, orange: 5 },
18
+ unknown: { green: 0, orange: 20 },
22
19
  },
23
20
  worstFiles: {
24
- count: 10,
25
- threshold: 80,
21
+ count: 15,
22
+ threshold: 100,
26
23
  },
27
24
  };