@ovh-ux/manager-static-analysis-kit 0.9.0 → 0.11.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 +29 -0
- package/README.md +166 -24
- package/bin/cli-path-config.js +41 -2
- package/bin/code-duplication-cli.js +14 -16
- package/bin/code-duplication-tests-cli.js +43 -6
- package/bin/perf-budgets-cli.js +15 -15
- package/bin/perf-budgets-tests-cli.js +36 -3
- package/bin/static-dynamic-quality-checks.js +2 -2
- package/bin/tests-coverage-cli-tests.js +44 -6
- package/bin/tests-coverage-cli.js +35 -25
- package/bin/types-coverage-cli.js +76 -11
- package/bin/types-coverage-tests-cli.js +44 -7
- package/bin/utils/args-parse-utils.js +77 -107
- package/bin/utils/module-utils.js +121 -0
- package/bin/utils/runner-utils.js +117 -22
- package/bin/utils/turbo-utils.js +15 -74
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +4 -1
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -1
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +72 -18
- package/dist/adapters/code-duplication/types/CodeDuplication.d.ts +3 -0
- package/dist/adapters/code-duplication/types/CodeDuplication.d.ts.map +1 -1
- package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.d.ts.map +1 -1
- package/dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.js +8 -4
- package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts.map +1 -1
- package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js +31 -8
- package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts.map +1 -1
- package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js +121 -48
- package/dist/adapters/types-coverage/types/TypesCoverage.d.ts +8 -0
- package/dist/adapters/types-coverage/types/TypesCoverage.d.ts.map +1 -1
- package/dist/configs/code-duplication-config.d.ts +1 -0
- package/dist/configs/code-duplication-config.d.ts.map +1 -1
- package/dist/configs/code-duplication-config.js +2 -1
- package/dist/configs/shared-exclusion-patterns.d.ts +1 -0
- package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
- package/dist/configs/shared-exclusion-patterns.js +21 -0
- package/dist/configs/tests-coverage-config.js +1 -1
- package/dist/configs/types-coverage-config.d.ts +14 -0
- package/dist/configs/types-coverage-config.d.ts.map +1 -1
- package/dist/configs/types-coverage-config.js +13 -16
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/bin/utils/apps-utils.js +0 -64
|
@@ -3,14 +3,12 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
|
|
4
4
|
import { logError, logInfo } from './utils/log-utils.js';
|
|
5
5
|
|
|
6
|
+
// Define the set of validation test cases
|
|
6
7
|
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
8
|
{ name: 'Single app (zimbra)', cmd: 'yarn manager-tests-coverage --app zimbra', expect: 0 },
|
|
11
9
|
{
|
|
12
|
-
name: 'Multiple apps (container,zimbra
|
|
13
|
-
cmd: 'yarn manager-tests-coverage --apps container,zimbra
|
|
10
|
+
name: 'Multiple apps (container,zimbra)',
|
|
11
|
+
cmd: 'yarn manager-tests-coverage --apps container,zimbra',
|
|
14
12
|
expect: 0,
|
|
15
13
|
},
|
|
16
14
|
{
|
|
@@ -23,6 +21,36 @@ const cases = [
|
|
|
23
21
|
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app',
|
|
24
22
|
expect: 0,
|
|
25
23
|
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Single library static path by folder name (manager-react-components)',
|
|
26
|
+
cmd: 'yarn manager-tests-coverage --library manager-react-components',
|
|
27
|
+
expect: 0,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Single library static path by package name (manager-react-components)',
|
|
31
|
+
cmd: 'yarn manager-tests-coverage --library @ovh-ux/manager-react-components',
|
|
32
|
+
expect: 0,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Single library dynamic path by folder name (shell-client)',
|
|
36
|
+
cmd: 'yarn manager-tests-coverage --library shell-client',
|
|
37
|
+
expect: 0,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Single library static path by package name (shell-client)',
|
|
41
|
+
cmd: 'yarn manager-tests-coverage --library @ovh-ux/manager-react-shell-client',
|
|
42
|
+
expect: 0,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Multiple libraries by folder names (manager-wiki,manager-react-components,shell-client,logs-to-customer)',
|
|
46
|
+
cmd: 'yarn manager-tests-coverage --libraries manager-wiki,manager-react-components,shell-client,logs-to-customer',
|
|
47
|
+
expect: 0,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Multiple libraries by package names (manager-wiki,manager-react-components,shell-client,logs-to-customer)',
|
|
51
|
+
cmd: 'yarn manager-tests-coverage --libraries @ovh-ux/manager-wiki,@ovh-ux/manager-react-components,@ovh-ux/manager-react-shell-client,@ovh-ux/logs-to-customer',
|
|
52
|
+
expect: 0,
|
|
53
|
+
},
|
|
26
54
|
{
|
|
27
55
|
name: 'Mixed valid+invalid apps',
|
|
28
56
|
cmd: 'yarn manager-tests-coverage --apps zimbra,unknown-app,container',
|
|
@@ -30,7 +58,12 @@ const cases = [
|
|
|
30
58
|
},
|
|
31
59
|
{
|
|
32
60
|
name: 'Mixed valid+invalid packages',
|
|
33
|
-
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-
|
|
61
|
+
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-wiki,manager-react-components,@ovh-ux/manager-ghost-app',
|
|
62
|
+
expect: 0,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Mixed valid+invalid libraries',
|
|
66
|
+
cmd: 'yarn manager-tests-coverage --libraries @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
|
|
34
67
|
expect: 0,
|
|
35
68
|
},
|
|
36
69
|
{
|
|
@@ -43,6 +76,11 @@ const cases = [
|
|
|
43
76
|
cmd: 'yarn manager-tests-coverage --packages @ovh-ux/manager-ghost-app,@ovh-ux/manager-missing-app',
|
|
44
77
|
expect: 1,
|
|
45
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Turbo build failure simulation (pci-ai-tools)',
|
|
81
|
+
cmd: 'yarn manager-tests-coverage --app pci-ai-tools',
|
|
82
|
+
expect: 0,
|
|
83
|
+
},
|
|
46
84
|
];
|
|
47
85
|
|
|
48
86
|
let failed = 0;
|
|
@@ -8,17 +8,16 @@ import {
|
|
|
8
8
|
generateTestsCoverageHtml,
|
|
9
9
|
} from '../dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js';
|
|
10
10
|
import {
|
|
11
|
-
appsDir,
|
|
12
|
-
outputRootDir,
|
|
13
11
|
rootDir,
|
|
14
12
|
testsCoverageCombinedHtmlReportName,
|
|
15
13
|
testsCoverageCombinedJsonReportName,
|
|
14
|
+
testsCoverageOutputRootDir,
|
|
16
15
|
testsCoverageReportsRootDirName,
|
|
17
16
|
} from './cli-path-config.js';
|
|
18
17
|
import { parseCliTargets } from './utils/args-parse-utils.js';
|
|
19
18
|
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import { runModulesAnalysis } from './utils/runner-utils.js';
|
|
20
|
+
import { runTurboBuild, runTurboTests } from './utils/turbo-utils.js';
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* Locate common coverage artifacts inside an app directory.
|
|
@@ -63,7 +62,11 @@ function findCoverageFiles(appDir) {
|
|
|
63
62
|
*/
|
|
64
63
|
function harvestAppCoverage(appDir, appShortName) {
|
|
65
64
|
const { summary, lcov } = findCoverageFiles(appDir);
|
|
66
|
-
const outDir = path.join(
|
|
65
|
+
const outDir = path.join(
|
|
66
|
+
testsCoverageOutputRootDir,
|
|
67
|
+
testsCoverageReportsRootDirName,
|
|
68
|
+
appShortName,
|
|
69
|
+
);
|
|
67
70
|
fs.mkdirSync(outDir, { recursive: true });
|
|
68
71
|
|
|
69
72
|
if (!summary) {
|
|
@@ -89,32 +92,39 @@ function harvestAppCoverage(appDir, appShortName) {
|
|
|
89
92
|
*/
|
|
90
93
|
function main() {
|
|
91
94
|
try {
|
|
92
|
-
const
|
|
93
|
-
const turboFilters = resolveTurboFilters({ appsDir, appFolders, packageNames });
|
|
95
|
+
const modules = parseCliTargets();
|
|
94
96
|
|
|
95
97
|
// 1) Build all selected targets (best-effort)
|
|
96
|
-
runTurboBuild(
|
|
98
|
+
runTurboBuild(
|
|
99
|
+
rootDir,
|
|
100
|
+
modules.map((item) => item.packageName),
|
|
101
|
+
['--continue'],
|
|
102
|
+
);
|
|
97
103
|
|
|
98
104
|
// 2) Run tests with coverage; keep args minimal for Vitest/Jest compatibility
|
|
99
105
|
// (Projects should already be configured to emit json-summary/lcov.)
|
|
100
|
-
runTurboTests(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
runTurboTests(
|
|
107
|
+
rootDir,
|
|
108
|
+
modules.map((item) => item.packageName),
|
|
109
|
+
[
|
|
110
|
+
'--continue', // turbo: keep going even if some tests fail
|
|
111
|
+
'--', // pass the rest to the test runner
|
|
112
|
+
'--coverage',
|
|
113
|
+
'--coverage.reportOnFailure',
|
|
114
|
+
'--coverage.reporter',
|
|
115
|
+
'json-summary',
|
|
116
|
+
'--coverage.reporter',
|
|
117
|
+
'lcov',
|
|
118
|
+
'--coverage.reporter',
|
|
119
|
+
'html',
|
|
120
|
+
],
|
|
121
|
+
);
|
|
110
122
|
|
|
111
123
|
// 3) Per-app harvest + combined reports
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
appFolders,
|
|
115
|
-
requireReact: false,
|
|
124
|
+
runModulesAnalysis({
|
|
125
|
+
modules,
|
|
116
126
|
binaryLabel: 'Tests coverage',
|
|
117
|
-
|
|
127
|
+
analysisRunner: harvestAppCoverage,
|
|
118
128
|
reportsRootDirName: testsCoverageReportsRootDirName,
|
|
119
129
|
combinedJson: testsCoverageCombinedJsonReportName,
|
|
120
130
|
combinedHtml: testsCoverageCombinedHtmlReportName,
|
|
@@ -122,9 +132,9 @@ function main() {
|
|
|
122
132
|
generateHtmlFn: (combined) =>
|
|
123
133
|
generateTestsCoverageHtml(
|
|
124
134
|
combined,
|
|
125
|
-
path.join(
|
|
135
|
+
path.join(testsCoverageOutputRootDir, testsCoverageReportsRootDirName),
|
|
126
136
|
),
|
|
127
|
-
outputRootDir,
|
|
137
|
+
outputRootDir: testsCoverageOutputRootDir,
|
|
128
138
|
});
|
|
129
139
|
} catch (err) {
|
|
130
140
|
logError(`Fatal error: ${err.stack || err.message}`);
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
|
+
import ts from 'typescript';
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
collectTypesCoverage,
|
|
8
9
|
generateTypesCoverageHtml,
|
|
9
10
|
} from '../dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js';
|
|
10
11
|
import {
|
|
11
|
-
appsDir,
|
|
12
|
-
outputRootDir,
|
|
13
12
|
tsTypesCoverageBin,
|
|
14
13
|
typesCoverageCombinedHtmlReportName,
|
|
15
14
|
typesCoverageCombinedJsonReportName,
|
|
15
|
+
typesCoverageOutputRootDir,
|
|
16
16
|
typesCoverageReportsRootDirName,
|
|
17
17
|
} from './cli-path-config.js';
|
|
18
18
|
import { buildTypesCoverageArgs, parseCliTargets } from './utils/args-parse-utils.js';
|
|
19
19
|
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
20
|
-
import { ensureBinExists,
|
|
20
|
+
import { ensureBinExists, runCommand, runModulesAnalysis } from './utils/runner-utils.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Temporarily strip "extends" from tsconfig.json before analysis,
|
|
@@ -60,6 +60,66 @@ function patchTsConfig(appDir, fn) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Count loose TypeScript constructs (`as`, `any`, `unknown`) in src/.
|
|
65
|
+
*
|
|
66
|
+
* Uses the TypeScript compiler API to avoid false positives from strings, comments, etc.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} appDir Absolute path to app root
|
|
69
|
+
* @returns {{ as: number; any: number; unknown: number }}
|
|
70
|
+
*/
|
|
71
|
+
export function countLooseTypes(appDir) {
|
|
72
|
+
const srcDir = path.join(appDir, 'src');
|
|
73
|
+
let asCount = 0;
|
|
74
|
+
let anyCount = 0;
|
|
75
|
+
let unknownCount = 0;
|
|
76
|
+
|
|
77
|
+
function analyzeFile(filePath) {
|
|
78
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
79
|
+
const source = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
80
|
+
|
|
81
|
+
function visit(node) {
|
|
82
|
+
// "as" expressions: foo as string
|
|
83
|
+
if (ts.isAsExpression(node)) {
|
|
84
|
+
asCount++;
|
|
85
|
+
if (node.type.kind === ts.SyntaxKind.AnyKeyword) {
|
|
86
|
+
anyCount++;
|
|
87
|
+
} else if (node.type.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
88
|
+
unknownCount++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Type annotations like let x: any or function foo(x: unknown)
|
|
93
|
+
if (node.kind === ts.SyntaxKind.AnyKeyword) {
|
|
94
|
+
anyCount++;
|
|
95
|
+
} else if (node.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
96
|
+
unknownCount++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ts.forEachChild(node, visit);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
visit(source);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function walk(dir) {
|
|
106
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
107
|
+
const fullPath = path.join(dir, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
walk(fullPath);
|
|
110
|
+
} else if (/\.(ts|tsx)$/.test(entry.name)) {
|
|
111
|
+
analyzeFile(fullPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(srcDir)) {
|
|
117
|
+
walk(srcDir);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { as: asCount, any: anyCount, unknown: unknownCount };
|
|
121
|
+
}
|
|
122
|
+
|
|
63
123
|
/**
|
|
64
124
|
* Run TypeScript coverage for one app.
|
|
65
125
|
*
|
|
@@ -70,7 +130,7 @@ function patchTsConfig(appDir, fn) {
|
|
|
70
130
|
function runAppTypesCoverage(appDir, appShortName) {
|
|
71
131
|
return patchTsConfig(appDir, () => {
|
|
72
132
|
const absoluteOutputDir = path.join(
|
|
73
|
-
|
|
133
|
+
typesCoverageOutputRootDir,
|
|
74
134
|
typesCoverageReportsRootDirName,
|
|
75
135
|
appShortName,
|
|
76
136
|
);
|
|
@@ -82,6 +142,10 @@ function runAppTypesCoverage(appDir, appShortName) {
|
|
|
82
142
|
const args = buildTypesCoverageArgs(relativeOutputDir);
|
|
83
143
|
const ok = runCommand(tsTypesCoverageBin, args, appDir);
|
|
84
144
|
|
|
145
|
+
const looseTypes = countLooseTypes(appDir);
|
|
146
|
+
const looseTypeReport = path.join(absoluteOutputDir, 'loose-types.json');
|
|
147
|
+
fs.writeFileSync(looseTypeReport, JSON.stringify(looseTypes, null, 2));
|
|
148
|
+
|
|
85
149
|
const jsonReport = path.join(absoluteOutputDir, 'typescript-coverage.json');
|
|
86
150
|
const htmlReport = path.join(absoluteOutputDir, 'index.html');
|
|
87
151
|
|
|
@@ -91,6 +155,9 @@ function runAppTypesCoverage(appDir, appShortName) {
|
|
|
91
155
|
if (!fs.existsSync(htmlReport)) {
|
|
92
156
|
logWarn(`⚠️ Missing index.html at ${htmlReport}`);
|
|
93
157
|
}
|
|
158
|
+
if (!fs.existsSync(looseTypeReport)) {
|
|
159
|
+
logWarn(`⚠️ Missing loose-types.json at ${looseTypeReport}`);
|
|
160
|
+
}
|
|
94
161
|
|
|
95
162
|
return ok;
|
|
96
163
|
});
|
|
@@ -103,20 +170,18 @@ function main() {
|
|
|
103
170
|
try {
|
|
104
171
|
ensureBinExists(tsTypesCoverageBin, 'typescript-coverage-report');
|
|
105
172
|
|
|
106
|
-
const
|
|
173
|
+
const modules = parseCliTargets();
|
|
107
174
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
appFolders,
|
|
111
|
-
requireReact: true,
|
|
175
|
+
runModulesAnalysis({
|
|
176
|
+
modules,
|
|
112
177
|
binaryLabel: 'Type coverage',
|
|
113
|
-
|
|
178
|
+
analysisRunner: runAppTypesCoverage,
|
|
114
179
|
reportsRootDirName: typesCoverageReportsRootDirName,
|
|
115
180
|
combinedJson: typesCoverageCombinedJsonReportName,
|
|
116
181
|
combinedHtml: typesCoverageCombinedHtmlReportName,
|
|
117
182
|
collectFn: collectTypesCoverage,
|
|
118
183
|
generateHtmlFn: generateTypesCoverageHtml,
|
|
119
|
-
outputRootDir,
|
|
184
|
+
outputRootDir: typesCoverageOutputRootDir,
|
|
120
185
|
});
|
|
121
186
|
} catch (err) {
|
|
122
187
|
logError(`Fatal error: ${err.stack || err.message}`);
|
|
@@ -3,15 +3,12 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
|
|
4
4
|
import { logError, logInfo } from './utils/log-utils.js';
|
|
5
5
|
|
|
6
|
-
// Define the set of validation test cases
|
|
6
|
+
// Define the set of validation test cases
|
|
7
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
8
|
{ name: 'Single app (zimbra)', cmd: 'yarn manager-types-coverage --app zimbra', expect: 0 },
|
|
12
9
|
{
|
|
13
|
-
name: 'Multiple apps (container,zimbra
|
|
14
|
-
cmd: 'yarn manager-types-coverage --apps container,zimbra
|
|
10
|
+
name: 'Multiple apps (container,zimbra)',
|
|
11
|
+
cmd: 'yarn manager-types-coverage --apps container,zimbra',
|
|
15
12
|
expect: 0,
|
|
16
13
|
},
|
|
17
14
|
{
|
|
@@ -24,6 +21,36 @@ const cases = [
|
|
|
24
21
|
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app',
|
|
25
22
|
expect: 0,
|
|
26
23
|
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Single library static path by folder name (manager-react-components)',
|
|
26
|
+
cmd: 'yarn manager-types-coverage --library manager-react-components',
|
|
27
|
+
expect: 0,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Single library static path by package name (manager-react-components)',
|
|
31
|
+
cmd: 'yarn manager-types-coverage --library @ovh-ux/manager-react-components',
|
|
32
|
+
expect: 0,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Single library dynamic path by folder name (shell-client)',
|
|
36
|
+
cmd: 'yarn manager-types-coverage --library shell-client',
|
|
37
|
+
expect: 0,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Single library static path by package name (shell-client)',
|
|
41
|
+
cmd: 'yarn manager-types-coverage --library @ovh-ux/manager-react-shell-client',
|
|
42
|
+
expect: 0,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Multiple libraries by folder names (manager-wiki,manager-react-components,shell-client,logs-to-customer)',
|
|
46
|
+
cmd: 'yarn manager-types-coverage --libraries manager-wiki,manager-react-components,shell-client,logs-to-customer',
|
|
47
|
+
expect: 0,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Multiple libraries by package names (manager-wiki,manager-react-components,shell-client,logs-to-customer)',
|
|
51
|
+
cmd: 'yarn manager-types-coverage --libraries @ovh-ux/manager-wiki,@ovh-ux/manager-react-components,@ovh-ux/manager-react-shell-client,@ovh-ux/logs-to-customer',
|
|
52
|
+
expect: 0,
|
|
53
|
+
},
|
|
27
54
|
{
|
|
28
55
|
name: 'Mixed valid+invalid apps',
|
|
29
56
|
cmd: 'yarn manager-types-coverage --apps zimbra,unknown-app,container',
|
|
@@ -31,7 +58,12 @@ const cases = [
|
|
|
31
58
|
},
|
|
32
59
|
{
|
|
33
60
|
name: 'Mixed valid+invalid packages',
|
|
34
|
-
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-
|
|
61
|
+
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-wiki,manager-react-components,@ovh-ux/manager-ghost-app',
|
|
62
|
+
expect: 0,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Mixed valid+invalid libraries',
|
|
66
|
+
cmd: 'yarn manager-types-coverage --libraries @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
|
|
35
67
|
expect: 0,
|
|
36
68
|
},
|
|
37
69
|
{
|
|
@@ -44,6 +76,11 @@ const cases = [
|
|
|
44
76
|
cmd: 'yarn manager-types-coverage --packages @ovh-ux/manager-ghost-app,@ovh-ux/manager-missing-app',
|
|
45
77
|
expect: 1,
|
|
46
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Turbo build failure simulation (pci-ai-tools)',
|
|
81
|
+
cmd: 'yarn manager-types-coverage --app pci-ai-tools',
|
|
82
|
+
expect: 0,
|
|
83
|
+
},
|
|
47
84
|
];
|
|
48
85
|
|
|
49
86
|
let failed = 0;
|
|
@@ -1,143 +1,106 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import process from 'node:process';
|
|
4
2
|
|
|
5
3
|
import { codeDuplicationConfig } from '../../dist/configs/code-duplication-config.js';
|
|
6
4
|
import { typesCoverageConfig } from '../../dist/configs/types-coverage-config.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { logError, logInfo } from './log-utils.js';
|
|
6
|
+
import { getModule } from './module-utils.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
9
|
+
* Parse CLI arguments to resolve target modules (apps, packages, or libraries).
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* This function looks for one of the supported flags in `process.argv`:
|
|
12
|
+
* - `--apps` or `--app`
|
|
13
|
+
* - `--packages` or `--package`
|
|
14
|
+
* - `--libraries` or `--library`
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*/
|
|
19
|
-
function extractAppFolderFromPackageName(packageName) {
|
|
20
|
-
const match = packageName.match(/^@ovh-ux\/manager-(.+)-app$/);
|
|
21
|
-
if (!match) {
|
|
22
|
-
logError(`❌ Invalid package name format: ${packageName}`);
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
return match[1];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Parse CLI arguments for `--apps` or `--app` flags.
|
|
16
|
+
* Each flag must be followed by a comma-separated list of module identifiers.
|
|
17
|
+
* Each identifier is resolved using {@link getModule}, which returns a module descriptor.
|
|
30
18
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
19
|
+
* For every valid pattern:
|
|
20
|
+
* - The corresponding module descriptor (containing `fullPath`, `shortPath`, and `packageName`)
|
|
21
|
+
* is added to the result list.
|
|
22
|
+
* - If a module cannot be resolved, an error is logged and it is skipped.
|
|
33
23
|
*
|
|
34
|
-
* @
|
|
35
|
-
* @
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*/
|
|
39
|
-
function parseAppsArg(appsDir) {
|
|
40
|
-
const appsArg = process.argv.find((arg) => arg === '--apps' || arg === '--app');
|
|
41
|
-
if (!appsArg) return null;
|
|
42
|
-
|
|
43
|
-
const index = process.argv.indexOf(appsArg);
|
|
44
|
-
const rawValue = process.argv[index + 1] || '';
|
|
45
|
-
const appFolders = rawValue
|
|
46
|
-
.split(',')
|
|
47
|
-
.map((n) => n.trim())
|
|
48
|
-
.filter(Boolean);
|
|
49
|
-
|
|
50
|
-
const validFolders = appFolders.filter((folder) => {
|
|
51
|
-
if (!fs.existsSync(path.join(appsDir, folder))) {
|
|
52
|
-
logError(`❌ App not found: ${folder} → skipping`);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
return true;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
logInfo(`Running in apps mode for: ${validFolders.join(', ')}`);
|
|
59
|
-
return { appFolders: validFolders, packageNames: [] };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse CLI arguments for `--packages` or `--package` flags.
|
|
24
|
+
* @typedef {Object} ModuleDescriptor
|
|
25
|
+
* @property {string} fullPath - Absolute path to the module directory.
|
|
26
|
+
* @property {string} shortPath - Folder name (short identifier) of the module.
|
|
27
|
+
* @property {string} packageName - Full npm package name (e.g. "@ovh-ux/manager-core-shell").
|
|
64
28
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
29
|
+
* @returns {ModuleDescriptor[] | null}
|
|
30
|
+
* Array of resolved module descriptors, or `null` if no supported CLI flags were provided.
|
|
67
31
|
*
|
|
68
|
-
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Example 1: Apps mode
|
|
34
|
+
* // CLI: --apps zimbra,web
|
|
35
|
+
* parseArgs();
|
|
36
|
+
* // → [
|
|
37
|
+
* // { fullPath: "/.../manager/apps/zimbra", shortPath: "zimbra", packageName: "@ovh-ux/manager-zimbra-app" },
|
|
38
|
+
* // { fullPath: "/.../manager/apps/web", shortPath: "web", packageName: "@ovh-ux/manager-web-app" }
|
|
39
|
+
* // ]
|
|
69
40
|
*
|
|
70
|
-
* @
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Example 2: Libraries mode
|
|
43
|
+
* // CLI: --libraries core-shell,react-components
|
|
44
|
+
* parseArgs();
|
|
45
|
+
* // → [
|
|
46
|
+
* // { fullPath: "/.../packages/manager/core/core-shell", shortPath: "core-shell", packageName: "@ovh-ux/manager-core-shell" },
|
|
47
|
+
* // { fullPath: "/.../packages/components/react-components", shortPath: "react-components", packageName: "@ovh-ux/manager-react-components" }
|
|
48
|
+
* // ]
|
|
74
49
|
*/
|
|
75
|
-
function
|
|
50
|
+
export function parseArgs() {
|
|
51
|
+
const libsArg = process.argv.find((arg) => arg === '--libraries' || arg === '--library');
|
|
76
52
|
const packagesArg = process.argv.find((arg) => arg === '--packages' || arg === '--package');
|
|
77
|
-
|
|
53
|
+
const appsArg = process.argv.find((arg) => arg === '--apps' || arg === '--app');
|
|
54
|
+
|
|
55
|
+
const moduleModeArgs = libsArg || packagesArg || appsArg;
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
if (!moduleModeArgs) return null;
|
|
58
|
+
|
|
59
|
+
const index = process.argv.indexOf(moduleModeArgs);
|
|
80
60
|
const rawValue = process.argv[index + 1] || '';
|
|
81
|
-
|
|
61
|
+
|
|
62
|
+
const modulePatterns = rawValue
|
|
82
63
|
.split(',')
|
|
83
64
|
.map((n) => n.trim())
|
|
84
65
|
.filter(Boolean);
|
|
85
66
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
67
|
+
/** @type {ModuleDescriptor[]} */
|
|
68
|
+
const modules = [];
|
|
69
|
+
|
|
70
|
+
for (const modulePattern of modulePatterns) {
|
|
71
|
+
const discoveredModule = getModule(modulePattern, !!libsArg);
|
|
72
|
+
if (discoveredModule) {
|
|
73
|
+
modules.push(discoveredModule);
|
|
91
74
|
} else {
|
|
92
|
-
logError(
|
|
75
|
+
logError(
|
|
76
|
+
`❌ Module ${modulePattern} → folder ${discoveredModule?.shortPath || 'unknown'} not found, skipping`,
|
|
77
|
+
);
|
|
93
78
|
}
|
|
94
79
|
}
|
|
95
80
|
|
|
96
81
|
logInfo(
|
|
97
|
-
`
|
|
82
|
+
`patterns ${modulePatterns.join(', ')} → folders: ${modules
|
|
83
|
+
.map((item) => item.fullPath)
|
|
84
|
+
.join(', ')}`,
|
|
98
85
|
);
|
|
99
|
-
return { appFolders: mappedFolders, packageNames };
|
|
100
|
-
}
|
|
101
86
|
|
|
102
|
-
|
|
103
|
-
* Discover all React apps in the apps directory when no CLI flags are provided.
|
|
104
|
-
*
|
|
105
|
-
* Only apps that contain a `package.json` file and are identified as React apps
|
|
106
|
-
* (via `isReactApp`) will be included.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} appsDir - Absolute path to the apps root directory.
|
|
109
|
-
* @returns {{appFolders: string[], packageNames: string[]}}
|
|
110
|
-
* Object containing discovered app folders and an empty `packageNames` array.
|
|
111
|
-
*/
|
|
112
|
-
function discoverApps(appsDir) {
|
|
113
|
-
const discoveredAppFolders = fs.readdirSync(appsDir).filter((dir) => {
|
|
114
|
-
const pkgPath = path.join(appsDir, dir, 'package.json');
|
|
115
|
-
return fs.existsSync(pkgPath) && isReactApp(pkgPath);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (discoveredAppFolders.length === 0) {
|
|
119
|
-
logWarn('⚠️ No React apps found to analyze.');
|
|
120
|
-
return { appFolders: [], packageNames: [] };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
logInfo(`Running in auto-discovery mode (${discoveredAppFolders.length} apps).`);
|
|
124
|
-
return { appFolders: discoveredAppFolders, packageNames: [] };
|
|
87
|
+
return modules;
|
|
125
88
|
}
|
|
126
89
|
|
|
127
90
|
/**
|
|
128
|
-
*
|
|
91
|
+
* Alias for {@link parseArgs}, used for semantic clarity in CLI entrypoints.
|
|
129
92
|
*
|
|
130
|
-
*
|
|
131
|
-
* 1. Explicit `--apps` flag
|
|
132
|
-
* 2. Explicit `--packages` flag
|
|
133
|
-
* 3. Auto-discovery fallback
|
|
93
|
+
* This function simply delegates to {@link parseArgs} and returns the same result.
|
|
134
94
|
*
|
|
135
|
-
* @
|
|
136
|
-
*
|
|
137
|
-
*
|
|
95
|
+
* @returns {ModuleDescriptor[] | null}
|
|
96
|
+
* Array of resolved module descriptors, or `null` if no valid flags were provided.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* parseCliTargets();
|
|
100
|
+
* // Equivalent to parseArgs(), typically used in CLI commands.
|
|
138
101
|
*/
|
|
139
|
-
export function parseCliTargets(
|
|
140
|
-
return
|
|
102
|
+
export function parseCliTargets() {
|
|
103
|
+
return parseArgs();
|
|
141
104
|
}
|
|
142
105
|
|
|
143
106
|
/**
|
|
@@ -217,6 +180,13 @@ export function buildCodeDuplicationArgs(outputDir) {
|
|
|
217
180
|
// https://github.com/kucherenko/jscpd/blob/master/apps/jscpd/README.md#ignore
|
|
218
181
|
args.push('--ignore', cfg.ignore.join(','));
|
|
219
182
|
}
|
|
220
|
-
|
|
183
|
+
// jscpd accepts regex patterns via `--ignore-pattern` (singular).
|
|
184
|
+
// Multiple regexes should be joined by comma.
|
|
185
|
+
// https://github.com/kucherenko/jscpd/blob/master/apps/jscpd/README.md#ignore-pattern
|
|
186
|
+
if (Array.isArray(cfg.ignorePatterns) && cfg.ignorePatterns.length > 0) {
|
|
187
|
+
for (const pat of cfg.ignorePatterns) {
|
|
188
|
+
args.push('--ignore-pattern', `'${pat}'`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
221
191
|
return args;
|
|
222
192
|
}
|