@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.
- package/CHANGELOG.md +48 -0
- package/README.md +59 -69
- package/bin/cli-path-config.js +31 -0
- package/bin/code-duplication-cli.js +96 -0
- package/bin/code-duplication-tests-cli.js +68 -0
- package/bin/lint-cli.js +18 -16
- package/bin/perf-budgets-cli.js +120 -0
- package/bin/perf-budgets-tests-cli.js +72 -0
- package/bin/static-dynamic-quality-checks.js +138 -0
- package/bin/tests-coverage-cli-tests.js +67 -0
- package/bin/tests-coverage-cli.js +135 -0
- package/bin/types-coverage-cli.js +127 -0
- package/bin/types-coverage-tests-cli.js +68 -0
- package/bin/utils/apps-utils.js +64 -0
- package/bin/utils/args-parse-utils.js +222 -0
- package/bin/utils/file-utils.js +52 -0
- package/bin/utils/log-utils.js +29 -0
- package/bin/utils/runner-utils.js +85 -0
- package/bin/utils/turbo-utils.js +139 -0
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +6 -0
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -0
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +240 -0
- package/dist/adapters/code-duplication/types/CodeDuplication.d.ts +60 -0
- package/dist/adapters/code-duplication/types/CodeDuplication.d.ts.map +1 -0
- package/dist/adapters/code-duplication/types/CodeDuplication.js +1 -0
- package/dist/adapters/eslint/rules/eslint-naming-conventions.d.ts.map +1 -1
- package/dist/adapters/eslint/rules/eslint-naming-conventions.js +1 -0
- package/dist/adapters/eslint/rules/eslint-prettier.js +1 -1
- package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.d.ts +62 -0
- package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.d.ts.map +1 -0
- package/dist/adapters/html-a11y-validation/helpers/html-a11y-tests-setup.js +73 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.d.ts +19 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.d.ts.map +1 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.js +32 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.d.ts +4 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.d.ts.map +1 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-validator.js +89 -0
- package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.d.ts +6 -0
- package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.d.ts.map +1 -0
- package/dist/adapters/html-w3c-validation/types/HtmlW3CValidator.js +1 -0
- package/dist/adapters/html-w3c-validation/types/globals/vnu-jar.d.ts +5 -0
- package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.d.ts +7 -0
- package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.js +191 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.d.ts +10 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-rules.js +20 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.d.ts +5 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/rules/perfs-budgets-scoring.js +35 -0
- package/dist/adapters/perf-budgets/types/PerfBudget.d.ts +22 -0
- package/dist/adapters/perf-budgets/types/PerfBudget.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/types/PerfBudget.js +1 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetAssets.d.ts +13 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetAssets.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetAssets.js +1 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetScore.d.ts +13 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetScore.d.ts.map +1 -0
- package/dist/adapters/perf-budgets/types/PerfBudgetScore.js +1 -0
- package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts +4 -0
- package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts.map +1 -0
- package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js +192 -0
- package/dist/adapters/tests-coverage/types/TestsCoverage.d.ts +36 -0
- package/dist/adapters/tests-coverage/types/TestsCoverage.d.ts.map +1 -0
- package/dist/adapters/tests-coverage/types/TestsCoverage.js +6 -0
- package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts +4 -0
- package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts.map +1 -0
- package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js +158 -0
- package/dist/adapters/types-coverage/types/TypesCoverage.d.ts +30 -0
- package/dist/adapters/types-coverage/types/TypesCoverage.d.ts.map +1 -0
- package/dist/adapters/types-coverage/types/TypesCoverage.js +1 -0
- package/dist/configs/architecture-analysis-config.d.ts +5 -0
- package/dist/configs/architecture-analysis-config.d.ts.map +1 -0
- package/dist/configs/{madge-config.js → architecture-analysis-config.js} +1 -1
- package/dist/configs/assets-budgets-config.d.ts +28 -0
- package/dist/configs/assets-budgets-config.d.ts.map +1 -0
- package/dist/configs/assets-budgets-config.js +45 -0
- package/dist/configs/bundle-analysis-config.d.ts +8 -0
- package/dist/configs/bundle-analysis-config.d.ts.map +1 -0
- package/dist/configs/bundle-analysis-config.js +7 -0
- package/dist/configs/code-duplication-config.d.ts +21 -0
- package/dist/configs/code-duplication-config.d.ts.map +1 -0
- package/dist/configs/code-duplication-config.js +21 -0
- package/dist/configs/{prettier-config.d.ts → code-formatter-config.d.ts} +1 -1
- package/dist/configs/code-formatter-config.d.ts.map +1 -0
- package/dist/configs/file-globs-config.d.ts +1 -4
- package/dist/configs/file-globs-config.d.ts.map +1 -1
- package/dist/configs/file-globs-config.js +1 -4
- package/dist/configs/naming-conventions-config.d.ts.map +1 -1
- package/dist/configs/naming-conventions-config.js +2 -0
- package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
- package/dist/configs/shared-exclusion-patterns.js +3 -1
- package/dist/configs/tests-coverage-config.d.ts +12 -0
- package/dist/configs/tests-coverage-config.d.ts.map +1 -0
- package/dist/configs/tests-coverage-config.js +11 -0
- package/dist/configs/types-coverage-config.d.ts +19 -0
- package/dist/configs/types-coverage-config.d.ts.map +1 -0
- package/dist/configs/types-coverage-config.js +27 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -7
- package/dist/configs/jscpd-config.d.ts +0 -7
- package/dist/configs/jscpd-config.d.ts.map +0 -1
- package/dist/configs/jscpd-config.js +0 -7
- package/dist/configs/madge-config.d.ts +0 -5
- package/dist/configs/madge-config.d.ts.map +0 -1
- package/dist/configs/prettier-config.d.ts.map +0 -1
- /package/dist/configs/{prettier-config.js → code-formatter-config.js} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
import { logError, logInfo } from './utils/log-utils.js';
|
|
5
|
+
|
|
6
|
+
// Define the set of validation test cases
|
|
7
|
+
const cases = [
|
|
8
|
+
// This case take too much time since it builds all apps, use it only for debug
|
|
9
|
+
// { name: 'Auto-discovery (all apps)', cmd: 'yarn manager-perf-budgets', expect: 0 },
|
|
10
|
+
{ name: 'Single app (zimbra)', cmd: 'yarn manager-perf-budgets --app zimbra', expect: 0 },
|
|
11
|
+
{
|
|
12
|
+
name: 'Multiple apps (container,zimbra)',
|
|
13
|
+
cmd: 'yarn manager-perf-budgets --apps container,zimbra',
|
|
14
|
+
expect: 0,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Single package (container)',
|
|
18
|
+
cmd: 'yarn manager-perf-budgets --package @ovh-ux/manager-container-app',
|
|
19
|
+
expect: 0,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Multiple packages (zimbra,pci-workflow)',
|
|
23
|
+
cmd: 'yarn manager-perf-budgets --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app',
|
|
24
|
+
expect: 0,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Mixed valid+invalid apps',
|
|
28
|
+
cmd: 'yarn manager-perf-budgets --apps zimbra,unknown-app,container',
|
|
29
|
+
expect: 0,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Mixed valid+invalid packages',
|
|
33
|
+
cmd: 'yarn manager-perf-budgets --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
|
|
34
|
+
expect: 0,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'All invalid apps',
|
|
38
|
+
cmd: 'yarn manager-perf-budgets --apps doesnotexist1,doesnotexist2',
|
|
39
|
+
expect: 1,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'All invalid packages',
|
|
43
|
+
cmd: 'yarn manager-perf-budgets --packages @ovh-ux/manager-ghost-app,@ovh-ux/manager-missing-app',
|
|
44
|
+
expect: 1,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Turbo build failure simulation (pci-ai-tools)',
|
|
48
|
+
cmd: 'yarn manager-perf-budgets --app pci-ai-tools',
|
|
49
|
+
expect: 0,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
let failed = 0;
|
|
54
|
+
|
|
55
|
+
for (const test of cases) {
|
|
56
|
+
logInfo(`\n🔎 Running: ${test.name}`);
|
|
57
|
+
const [bin, ...args] = test.cmd.split(' ');
|
|
58
|
+
const result = spawnSync(bin, args, { stdio: 'inherit', shell: true });
|
|
59
|
+
|
|
60
|
+
if (result.status !== test.expect) {
|
|
61
|
+
logError(`❌ ${test.name} failed (expected exit ${test.expect}, got ${result.status})`);
|
|
62
|
+
failed++;
|
|
63
|
+
} else {
|
|
64
|
+
logInfo(`✅ ${test.name} passed`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (failed > 0) {
|
|
69
|
+
logError(`\n❌ ${failed} test(s) failed.`);
|
|
70
|
+
} else {
|
|
71
|
+
logInfo('\n🎉 All perf-budgets CLI validation tests passed!');
|
|
72
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { writeFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
import { outputRootDir } from './cli-path-config.js';
|
|
8
|
+
|
|
9
|
+
const qualityCheckEntryFile = resolve(outputRootDir, 'index.html');
|
|
10
|
+
|
|
11
|
+
const isTests = process.argv.includes('--tests');
|
|
12
|
+
|
|
13
|
+
const mainScripts = [
|
|
14
|
+
'manager-code-duplication',
|
|
15
|
+
'manager-types-coverage',
|
|
16
|
+
'manager-tests-coverage',
|
|
17
|
+
'manager-perf-budgets',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const testScripts = [
|
|
21
|
+
'manager-code-duplication-tests',
|
|
22
|
+
'manager-types-coverage-tests',
|
|
23
|
+
'manager-tests-coverage-tests',
|
|
24
|
+
'manager-perf-budgets-tests',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const scripts = isTests ? testScripts : mainScripts;
|
|
28
|
+
|
|
29
|
+
const reports = [
|
|
30
|
+
{
|
|
31
|
+
name: `Code Duplication${isTests ? ' (Tests)' : ''}`,
|
|
32
|
+
folder: 'code-duplication-reports',
|
|
33
|
+
base: 'code-duplication-combined-report',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: `Performance Budgets${isTests ? ' (Tests)' : ''}`,
|
|
37
|
+
folder: 'perf-budgets-reports',
|
|
38
|
+
base: 'perf-budgets-combined-report',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: `Tests Coverage${isTests ? ' (Tests)' : ''}`,
|
|
42
|
+
folder: 'tests-coverage-reports',
|
|
43
|
+
base: 'tests-coverage-combined-report',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: `Types Coverage${isTests ? ' (Tests)' : ''}`,
|
|
47
|
+
folder: 'types-coverage-reports',
|
|
48
|
+
base: 'types-coverage-combined-report',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
console.log(`🚀 Running static & dynamic quality ${isTests ? 'tests' : 'checks'}...\n`);
|
|
53
|
+
|
|
54
|
+
let remaining = scripts.length;
|
|
55
|
+
let failed = false;
|
|
56
|
+
const statuses = {};
|
|
57
|
+
|
|
58
|
+
function generateEntryReport() {
|
|
59
|
+
const links = reports
|
|
60
|
+
.map((report) => {
|
|
61
|
+
const status = statuses[report.name] ?? 'unknown';
|
|
62
|
+
const badge =
|
|
63
|
+
status === 'success'
|
|
64
|
+
? '<span class="badge success">✅ Success</span>'
|
|
65
|
+
: status === 'fail'
|
|
66
|
+
? '<span class="badge fail">❌ Failed</span>'
|
|
67
|
+
: '<span class="badge unknown">❔ Unknown</span>';
|
|
68
|
+
|
|
69
|
+
return `
|
|
70
|
+
<li>
|
|
71
|
+
<strong>${report.name}</strong> ${badge}<br/>
|
|
72
|
+
<a href="./${report.folder}/${report.base}.html">📊 HTML Report</a> |
|
|
73
|
+
<a href="./${report.folder}/${report.base}.json">📄 JSON Report</a>
|
|
74
|
+
</li>`;
|
|
75
|
+
})
|
|
76
|
+
.join('\n');
|
|
77
|
+
|
|
78
|
+
const html = `<!DOCTYPE html>
|
|
79
|
+
<html>
|
|
80
|
+
<head>
|
|
81
|
+
<meta charset="UTF-8">
|
|
82
|
+
<title>Static & Dynamic Quality ${isTests ? 'Tests' : 'Checks'} Reports</title>
|
|
83
|
+
<style>
|
|
84
|
+
body { font-family: Arial, sans-serif; margin: 2rem; background: #f9f9f9; color: #333; }
|
|
85
|
+
h1 { color: #2c3e50; }
|
|
86
|
+
p.note { font-size: 0.9rem; color: #666; margin-bottom: 1.5rem; }
|
|
87
|
+
ul { list-style-type: none; padding: 0; }
|
|
88
|
+
li { margin: 1rem 0; padding: 1rem; background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
89
|
+
a { text-decoration: none; margin-right: 1rem; }
|
|
90
|
+
.badge { padding: 0.2rem 0.6rem; border-radius: 6px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
91
|
+
.success { background: #2ecc71; color: #fff; }
|
|
92
|
+
.fail { background: #e74c3c; color: #fff; }
|
|
93
|
+
.unknown { background: #95a5a6; color: #fff; }
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<h1>Static & Dynamic Quality ${isTests ? 'Tests' : 'Checks'} Reports</h1>
|
|
98
|
+
<p class="note">ℹ️ Status badges below reflect the <strong>script execution status</strong> (whether the CLI completed without error), <br/>not the actual content or results of the analysis.</p>
|
|
99
|
+
<ul>
|
|
100
|
+
${links}
|
|
101
|
+
</ul>
|
|
102
|
+
</body>
|
|
103
|
+
</html>`;
|
|
104
|
+
|
|
105
|
+
writeFileSync(qualityCheckEntryFile, html, 'utf-8');
|
|
106
|
+
console.log(`📄 Entry report generated at ${qualityCheckEntryFile}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scripts.forEach((cmd, idx) => {
|
|
110
|
+
const child = spawn(cmd, { stdio: 'inherit', shell: true });
|
|
111
|
+
|
|
112
|
+
child.on('exit', (code) => {
|
|
113
|
+
console.log(`\n${code === 0 ? '✅' : '❌'} ${cmd} finished with code ${code}`);
|
|
114
|
+
if (code === 0) {
|
|
115
|
+
statuses[reports[idx].name] = 'success';
|
|
116
|
+
} else {
|
|
117
|
+
statuses[reports[idx].name] = 'fail';
|
|
118
|
+
failed = true;
|
|
119
|
+
}
|
|
120
|
+
remaining -= 1;
|
|
121
|
+
|
|
122
|
+
if (remaining === 0) {
|
|
123
|
+
console.log(
|
|
124
|
+
failed
|
|
125
|
+
? '\n⚠️ Some quality scripts failed, but reports will still be generated.'
|
|
126
|
+
: `\n🎉 All quality ${isTests ? 'tests' : 'checks'} completed successfully`,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
generateEntryReport();
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error('❌ Failed to generate entry report:', err);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
process.exit(failed ? 1 : 0);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
import { logError, logInfo } from './utils/log-utils.js';
|
|
5
|
+
|
|
6
|
+
const cases = [
|
|
7
|
+
// Debug only — very slow, runs across all apps
|
|
8
|
+
// { name: 'Auto-discovery (all apps)', cmd: 'yarn manager-tests-coverage', expect: 0 },
|
|
9
|
+
|
|
10
|
+
{ name: 'Single app (zimbra)', cmd: 'yarn manager-tests-coverage --app zimbra', expect: 0 },
|
|
11
|
+
{
|
|
12
|
+
name: 'Multiple apps (container,zimbra,web-ongoing-operations,key-management-service)',
|
|
13
|
+
cmd: 'yarn manager-tests-coverage --apps container,zimbra,web-ongoing-operations,key-management-service',
|
|
14
|
+
expect: 0,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Single package (container)',
|
|
18
|
+
cmd: 'yarn manager-tests-coverage --package @ovh-ux/manager-container-app',
|
|
19
|
+
expect: 0,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Multiple packages (zimbra,pci-workflow)',
|
|
23
|
+
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app',
|
|
24
|
+
expect: 0,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Mixed valid+invalid apps',
|
|
28
|
+
cmd: 'yarn manager-tests-coverage --apps zimbra,unknown-app,container',
|
|
29
|
+
expect: 0,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Mixed valid+invalid packages',
|
|
33
|
+
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
|
|
34
|
+
expect: 0,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'All invalid apps',
|
|
38
|
+
cmd: 'yarn manager-tests-coverage --apps doesnotexist1,doesnotexist2',
|
|
39
|
+
expect: 1,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'All invalid packages',
|
|
43
|
+
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-ghost-app,@ovh-ux/manager-missing-app',
|
|
44
|
+
expect: 1,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
let failed = 0;
|
|
49
|
+
|
|
50
|
+
for (const test of cases) {
|
|
51
|
+
logInfo(`\n🔎 Running: ${test.name}`);
|
|
52
|
+
const [bin, ...args] = test.cmd.split(' ');
|
|
53
|
+
const result = spawnSync(bin, args, { stdio: 'inherit', shell: true });
|
|
54
|
+
|
|
55
|
+
if (result.status !== test.expect) {
|
|
56
|
+
logError(`❌ ${test.name} failed (expected exit ${test.expect}, got ${result.status})`);
|
|
57
|
+
failed++;
|
|
58
|
+
} else {
|
|
59
|
+
logInfo(`✅ ${test.name} passed`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (failed > 0) {
|
|
64
|
+
logError(`\n❌ ${failed} test(s) failed.`);
|
|
65
|
+
} else {
|
|
66
|
+
logInfo('\n🎉 All tests-coverage CLI validation tests passed!');
|
|
67
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
collectTestsCoverage,
|
|
8
|
+
generateTestsCoverageHtml,
|
|
9
|
+
} from '../dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js';
|
|
10
|
+
import {
|
|
11
|
+
appsDir,
|
|
12
|
+
outputRootDir,
|
|
13
|
+
rootDir,
|
|
14
|
+
testsCoverageCombinedHtmlReportName,
|
|
15
|
+
testsCoverageCombinedJsonReportName,
|
|
16
|
+
testsCoverageReportsRootDirName,
|
|
17
|
+
} from './cli-path-config.js';
|
|
18
|
+
import { parseCliTargets } from './utils/args-parse-utils.js';
|
|
19
|
+
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
20
|
+
import { runAppsAnalysis } from './utils/runner-utils.js';
|
|
21
|
+
import { resolveTurboFilters, runTurboBuild, runTurboTests } from './utils/turbo-utils.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Locate common coverage artifacts inside an app directory.
|
|
25
|
+
* @param {string} appDir
|
|
26
|
+
* @returns {{ summary: string | null, lcov: string | null }}
|
|
27
|
+
*/
|
|
28
|
+
function findCoverageFiles(appDir) {
|
|
29
|
+
const candidates = [
|
|
30
|
+
// Vitest & Jest default
|
|
31
|
+
path.join(appDir, 'coverage', 'coverage-summary.json'),
|
|
32
|
+
// Sometimes coverage is nested (e.g., project subdir)
|
|
33
|
+
// We’ll scan one level deep for coverage-summary.json
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
let summary = candidates.find((p) => fs.existsSync(p));
|
|
37
|
+
if (!summary) {
|
|
38
|
+
// one-level deep scan
|
|
39
|
+
for (const entry of fs.readdirSync(appDir, { withFileTypes: true })) {
|
|
40
|
+
if (!entry.isDirectory()) continue;
|
|
41
|
+
if (['node_modules', 'dist', 'build', '.next', 'coverage'].includes(entry.name)) continue;
|
|
42
|
+
const p = path.join(appDir, entry.name, 'coverage', 'coverage-summary.json');
|
|
43
|
+
if (fs.existsSync(p)) {
|
|
44
|
+
summary = p;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Best-effort extras (not mandatory)
|
|
51
|
+
const lcov =
|
|
52
|
+
(summary && path.join(path.dirname(summary), 'lcov.info')) ||
|
|
53
|
+
path.join(appDir, 'coverage', 'lcov.info');
|
|
54
|
+
|
|
55
|
+
return { summary, lcov: fs.existsSync(lcov) ? lcov : null };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Copy an app’s coverage files into tests-coverage-reports/<app>.
|
|
60
|
+
* @param {string} appDir
|
|
61
|
+
* @param {string} appShortName
|
|
62
|
+
* @returns {boolean} True if a summary was found and copied
|
|
63
|
+
*/
|
|
64
|
+
function harvestAppCoverage(appDir, appShortName) {
|
|
65
|
+
const { summary, lcov } = findCoverageFiles(appDir);
|
|
66
|
+
const outDir = path.join(outputRootDir, testsCoverageReportsRootDirName, appShortName);
|
|
67
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
68
|
+
|
|
69
|
+
if (!summary) {
|
|
70
|
+
logWarn(`⚠️ No coverage-summary.json found for ${appShortName}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const targetSummary = path.join(outDir, 'coverage-summary.json');
|
|
75
|
+
fs.copyFileSync(summary, targetSummary);
|
|
76
|
+
logInfo(`✅ Copied summary → ${targetSummary}`);
|
|
77
|
+
|
|
78
|
+
if (lcov) {
|
|
79
|
+
const targetLcov = path.join(outDir, 'lcov.info');
|
|
80
|
+
fs.copyFileSync(lcov, targetLcov);
|
|
81
|
+
logInfo(`✅ Copied lcov → ${targetLcov}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Main CLI entrypoint: build → test (with coverage) → harvest → combine.
|
|
89
|
+
*/
|
|
90
|
+
function main() {
|
|
91
|
+
try {
|
|
92
|
+
const { appFolders, packageNames } = parseCliTargets(appsDir);
|
|
93
|
+
const turboFilters = resolveTurboFilters({ appsDir, appFolders, packageNames });
|
|
94
|
+
|
|
95
|
+
// 1) Build all selected targets (best-effort)
|
|
96
|
+
runTurboBuild(rootDir, turboFilters);
|
|
97
|
+
|
|
98
|
+
// 2) Run tests with coverage; keep args minimal for Vitest/Jest compatibility
|
|
99
|
+
// (Projects should already be configured to emit json-summary/lcov.)
|
|
100
|
+
runTurboTests(rootDir, turboFilters, [
|
|
101
|
+
'--',
|
|
102
|
+
'--coverage',
|
|
103
|
+
'--coverage.reporter',
|
|
104
|
+
'json-summary',
|
|
105
|
+
'--coverage.reporter',
|
|
106
|
+
'lcov',
|
|
107
|
+
'--coverage.reporter',
|
|
108
|
+
'html',
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
// 3) Per-app harvest + combined reports
|
|
112
|
+
runAppsAnalysis({
|
|
113
|
+
appsDir,
|
|
114
|
+
appFolders,
|
|
115
|
+
requireReact: false,
|
|
116
|
+
binaryLabel: 'Tests coverage',
|
|
117
|
+
appRunner: harvestAppCoverage,
|
|
118
|
+
reportsRootDirName: testsCoverageReportsRootDirName,
|
|
119
|
+
combinedJson: testsCoverageCombinedJsonReportName,
|
|
120
|
+
combinedHtml: testsCoverageCombinedHtmlReportName,
|
|
121
|
+
collectFn: collectTestsCoverage,
|
|
122
|
+
generateHtmlFn: (combined) =>
|
|
123
|
+
generateTestsCoverageHtml(
|
|
124
|
+
combined,
|
|
125
|
+
path.join(outputRootDir, testsCoverageReportsRootDirName),
|
|
126
|
+
),
|
|
127
|
+
outputRootDir,
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
logError(`Fatal error: ${err.stack || err.message}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
main();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
collectTypesCoverage,
|
|
8
|
+
generateTypesCoverageHtml,
|
|
9
|
+
} from '../dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js';
|
|
10
|
+
import {
|
|
11
|
+
appsDir,
|
|
12
|
+
outputRootDir,
|
|
13
|
+
tsTypesCoverageBin,
|
|
14
|
+
typesCoverageCombinedHtmlReportName,
|
|
15
|
+
typesCoverageCombinedJsonReportName,
|
|
16
|
+
typesCoverageReportsRootDirName,
|
|
17
|
+
} from './cli-path-config.js';
|
|
18
|
+
import { buildTypesCoverageArgs, parseCliTargets } from './utils/args-parse-utils.js';
|
|
19
|
+
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
20
|
+
import { ensureBinExists, runAppsAnalysis, runCommand } from './utils/runner-utils.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Temporarily strip "extends" from tsconfig.json before analysis,
|
|
24
|
+
* then restore it after.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} appDir - Absolute app directory
|
|
27
|
+
* @param {() => boolean} fn - Callback running the analysis
|
|
28
|
+
* @returns {boolean} Result of the callback
|
|
29
|
+
*/
|
|
30
|
+
function patchTsConfig(appDir, fn) {
|
|
31
|
+
const tsconfigPath = path.join(appDir, 'tsconfig.json');
|
|
32
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
33
|
+
return fn();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const original = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(original);
|
|
40
|
+
} catch {
|
|
41
|
+
logWarn(`⚠️ Invalid tsconfig.json in ${appDir}, skipping patch`);
|
|
42
|
+
return fn();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let modified = null;
|
|
46
|
+
if (parsed.extends) {
|
|
47
|
+
modified = { ...parsed };
|
|
48
|
+
delete modified.extends;
|
|
49
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(modified, null, 2));
|
|
50
|
+
logInfo(`⏳ Patched tsconfig.json in ${appDir} (removed extends)`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
return fn();
|
|
55
|
+
} finally {
|
|
56
|
+
if (modified) {
|
|
57
|
+
fs.writeFileSync(tsconfigPath, original);
|
|
58
|
+
logInfo(`🔄 Restored tsconfig.json in ${appDir}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run TypeScript coverage for one app.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} appDir - Absolute app directory
|
|
67
|
+
* @param {string} appShortName - Shortened app name
|
|
68
|
+
* @returns {boolean} True if analysis completed successfully
|
|
69
|
+
*/
|
|
70
|
+
function runAppTypesCoverage(appDir, appShortName) {
|
|
71
|
+
return patchTsConfig(appDir, () => {
|
|
72
|
+
const absoluteOutputDir = path.join(
|
|
73
|
+
outputRootDir,
|
|
74
|
+
typesCoverageReportsRootDirName,
|
|
75
|
+
appShortName,
|
|
76
|
+
);
|
|
77
|
+
fs.mkdirSync(absoluteOutputDir, { recursive: true });
|
|
78
|
+
|
|
79
|
+
const relativeOutputDir = path.relative(appDir, absoluteOutputDir);
|
|
80
|
+
logInfo(`Running TypeScript coverage for ${appShortName} → ${absoluteOutputDir}`);
|
|
81
|
+
|
|
82
|
+
const args = buildTypesCoverageArgs(relativeOutputDir);
|
|
83
|
+
const ok = runCommand(tsTypesCoverageBin, args, appDir);
|
|
84
|
+
|
|
85
|
+
const jsonReport = path.join(absoluteOutputDir, 'typescript-coverage.json');
|
|
86
|
+
const htmlReport = path.join(absoluteOutputDir, 'index.html');
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(jsonReport)) {
|
|
89
|
+
logWarn(`⚠️ Missing typescript-coverage.json at ${jsonReport}`);
|
|
90
|
+
}
|
|
91
|
+
if (!fs.existsSync(htmlReport)) {
|
|
92
|
+
logWarn(`⚠️ Missing index.html at ${htmlReport}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return ok;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Main CLI entrypoint for TypeScript coverage analysis.
|
|
101
|
+
*/
|
|
102
|
+
function main() {
|
|
103
|
+
try {
|
|
104
|
+
ensureBinExists(tsTypesCoverageBin, 'typescript-coverage-report');
|
|
105
|
+
|
|
106
|
+
const { appFolders } = parseCliTargets(appsDir);
|
|
107
|
+
|
|
108
|
+
runAppsAnalysis({
|
|
109
|
+
appsDir,
|
|
110
|
+
appFolders,
|
|
111
|
+
requireReact: true,
|
|
112
|
+
binaryLabel: 'Type coverage',
|
|
113
|
+
appRunner: runAppTypesCoverage,
|
|
114
|
+
reportsRootDirName: typesCoverageReportsRootDirName,
|
|
115
|
+
combinedJson: typesCoverageCombinedJsonReportName,
|
|
116
|
+
combinedHtml: typesCoverageCombinedHtmlReportName,
|
|
117
|
+
collectFn: collectTypesCoverage,
|
|
118
|
+
generateHtmlFn: generateTypesCoverageHtml,
|
|
119
|
+
outputRootDir,
|
|
120
|
+
});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
logError(`Fatal error: ${err.stack || err.message}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
import { logError, logInfo } from './utils/log-utils.js';
|
|
5
|
+
|
|
6
|
+
// Define the set of validation test cases for types-coverage-tests CLI
|
|
7
|
+
const cases = [
|
|
8
|
+
// Debug only — very slow, runs across all apps
|
|
9
|
+
// { name: 'Auto-discovery (all apps)', cmd: 'yarn manager-types-coverage', expect: 0 },
|
|
10
|
+
|
|
11
|
+
{ name: 'Single app (zimbra)', cmd: 'yarn manager-types-coverage --app zimbra', expect: 0 },
|
|
12
|
+
{
|
|
13
|
+
name: 'Multiple apps (container,zimbra,web-ongoing-operations,key-management-service)',
|
|
14
|
+
cmd: 'yarn manager-types-coverage --apps container,zimbra,web-ongoing-operations,key-management-service',
|
|
15
|
+
expect: 0,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Single package (container)',
|
|
19
|
+
cmd: 'yarn manager-types-coverage --package @ovh-ux/manager-container-app',
|
|
20
|
+
expect: 0,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'Multiple packages (zimbra,pci-workflow)',
|
|
24
|
+
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app',
|
|
25
|
+
expect: 0,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Mixed valid+invalid apps',
|
|
29
|
+
cmd: 'yarn manager-types-coverage --apps zimbra,unknown-app,container',
|
|
30
|
+
expect: 0,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Mixed valid+invalid packages',
|
|
34
|
+
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
|
|
35
|
+
expect: 0,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'All invalid apps',
|
|
39
|
+
cmd: 'yarn manager-types-coverage --apps doesnotexist1,doesnotexist2',
|
|
40
|
+
expect: 1,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'All invalid packages',
|
|
44
|
+
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-ghost-app,@ovh-ux/manager-missing-app',
|
|
45
|
+
expect: 1,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
let failed = 0;
|
|
50
|
+
|
|
51
|
+
for (const test of cases) {
|
|
52
|
+
logInfo(`\n🔎 Running: ${test.name}`);
|
|
53
|
+
const [bin, ...args] = test.cmd.split(' ');
|
|
54
|
+
const result = spawnSync(bin, args, { stdio: 'inherit', shell: true });
|
|
55
|
+
|
|
56
|
+
if (result.status !== test.expect) {
|
|
57
|
+
logError(`❌ ${test.name} failed (expected exit ${test.expect}, got ${result.status})`);
|
|
58
|
+
failed++;
|
|
59
|
+
} else {
|
|
60
|
+
logInfo(`✅ ${test.name} passed`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (failed > 0) {
|
|
65
|
+
logError(`\n❌ ${failed} test(s) failed.`);
|
|
66
|
+
} else {
|
|
67
|
+
logInfo('\n🎉 All types-coverage-tests CLI validation tests passed!');
|
|
68
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { readJsonSafe } from './file-utils.js';
|
|
5
|
+
import { logError, logWarn } from './log-utils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if a given package.json belongs to a React app.
|
|
9
|
+
*/
|
|
10
|
+
export function isReactApp(pkgPath) {
|
|
11
|
+
try {
|
|
12
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
13
|
+
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
15
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
16
|
+
|
|
17
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
|
|
18
|
+
return Boolean(deps.react);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
logWarn(`Failed to read or parse ${pkgPath}: ${err.message}`);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve app short name from package.json (fallback to folder).
|
|
27
|
+
*/
|
|
28
|
+
export function getAppShortName(pkgPath, folder) {
|
|
29
|
+
const pkg = readJsonSafe(pkgPath) || {};
|
|
30
|
+
return (pkg.name && String(pkg.name).replace(/^@ovh-ux\//, '')) || folder;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unified app info resolver.
|
|
35
|
+
* - Ensures folder exists
|
|
36
|
+
* - Ensures package.json exists
|
|
37
|
+
* - Optionally enforces React app
|
|
38
|
+
*/
|
|
39
|
+
export function resolveAppInfo(appsDir, appFolder, { requireReact = false } = {}) {
|
|
40
|
+
const appDir = path.join(appsDir, appFolder);
|
|
41
|
+
if (!fs.existsSync(appDir)) {
|
|
42
|
+
logError(`❌ App folder not found: ${appFolder} → skipping`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pkgPath = path.join(appDir, 'package.json');
|
|
47
|
+
if (!fs.existsSync(pkgPath)) {
|
|
48
|
+
logWarn(`⚠️ No package.json for ${appFolder}, skipping`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (requireReact && !isReactApp(pkgPath)) {
|
|
53
|
+
logWarn(`⚠️ ${appFolder} is not a React app, skipping`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
appDir,
|
|
59
|
+
pkgPath,
|
|
60
|
+
appFolder,
|
|
61
|
+
appShortName: getAppShortName(pkgPath, appFolder),
|
|
62
|
+
pkg: readJsonSafe(pkgPath),
|
|
63
|
+
};
|
|
64
|
+
}
|