@ovh-ux/manager-static-analysis-kit 0.10.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 +14 -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 -27
- package/bin/types-coverage-cli.js +8 -11
- package/bin/types-coverage-tests-cli.js +44 -7
- package/bin/utils/args-parse-utils.js +69 -106
- package/bin/utils/module-utils.js +121 -0
- package/bin/utils/runner-utils.js +117 -22
- package/bin/utils/turbo-utils.js +1 -71
- 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/configs/shared-exclusion-patterns.d.ts.map +1 -1
- package/dist/configs/shared-exclusion-patterns.js +1 -0
- 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,34 +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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
);
|
|
112
122
|
|
|
113
123
|
// 3) Per-app harvest + combined reports
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
appFolders,
|
|
117
|
-
requireReact: false,
|
|
124
|
+
runModulesAnalysis({
|
|
125
|
+
modules,
|
|
118
126
|
binaryLabel: 'Tests coverage',
|
|
119
|
-
|
|
127
|
+
analysisRunner: harvestAppCoverage,
|
|
120
128
|
reportsRootDirName: testsCoverageReportsRootDirName,
|
|
121
129
|
combinedJson: testsCoverageCombinedJsonReportName,
|
|
122
130
|
combinedHtml: testsCoverageCombinedHtmlReportName,
|
|
@@ -124,9 +132,9 @@ function main() {
|
|
|
124
132
|
generateHtmlFn: (combined) =>
|
|
125
133
|
generateTestsCoverageHtml(
|
|
126
134
|
combined,
|
|
127
|
-
path.join(
|
|
135
|
+
path.join(testsCoverageOutputRootDir, testsCoverageReportsRootDirName),
|
|
128
136
|
),
|
|
129
|
-
outputRootDir,
|
|
137
|
+
outputRootDir: testsCoverageOutputRootDir,
|
|
130
138
|
});
|
|
131
139
|
} catch (err) {
|
|
132
140
|
logError(`Fatal error: ${err.stack || err.message}`);
|
|
@@ -9,16 +9,15 @@ import {
|
|
|
9
9
|
generateTypesCoverageHtml,
|
|
10
10
|
} from '../dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js';
|
|
11
11
|
import {
|
|
12
|
-
appsDir,
|
|
13
|
-
outputRootDir,
|
|
14
12
|
tsTypesCoverageBin,
|
|
15
13
|
typesCoverageCombinedHtmlReportName,
|
|
16
14
|
typesCoverageCombinedJsonReportName,
|
|
15
|
+
typesCoverageOutputRootDir,
|
|
17
16
|
typesCoverageReportsRootDirName,
|
|
18
17
|
} from './cli-path-config.js';
|
|
19
18
|
import { buildTypesCoverageArgs, parseCliTargets } from './utils/args-parse-utils.js';
|
|
20
19
|
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
21
|
-
import { ensureBinExists,
|
|
20
|
+
import { ensureBinExists, runCommand, runModulesAnalysis } from './utils/runner-utils.js';
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* Temporarily strip "extends" from tsconfig.json before analysis,
|
|
@@ -131,7 +130,7 @@ export function countLooseTypes(appDir) {
|
|
|
131
130
|
function runAppTypesCoverage(appDir, appShortName) {
|
|
132
131
|
return patchTsConfig(appDir, () => {
|
|
133
132
|
const absoluteOutputDir = path.join(
|
|
134
|
-
|
|
133
|
+
typesCoverageOutputRootDir,
|
|
135
134
|
typesCoverageReportsRootDirName,
|
|
136
135
|
appShortName,
|
|
137
136
|
);
|
|
@@ -171,20 +170,18 @@ function main() {
|
|
|
171
170
|
try {
|
|
172
171
|
ensureBinExists(tsTypesCoverageBin, 'typescript-coverage-report');
|
|
173
172
|
|
|
174
|
-
const
|
|
173
|
+
const modules = parseCliTargets();
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
appFolders,
|
|
179
|
-
requireReact: true,
|
|
175
|
+
runModulesAnalysis({
|
|
176
|
+
modules,
|
|
180
177
|
binaryLabel: 'Type coverage',
|
|
181
|
-
|
|
178
|
+
analysisRunner: runAppTypesCoverage,
|
|
182
179
|
reportsRootDirName: typesCoverageReportsRootDirName,
|
|
183
180
|
combinedJson: typesCoverageCombinedJsonReportName,
|
|
184
181
|
combinedHtml: typesCoverageCombinedHtmlReportName,
|
|
185
182
|
collectFn: collectTypesCoverage,
|
|
186
183
|
generateHtmlFn: generateTypesCoverageHtml,
|
|
187
|
-
outputRootDir,
|
|
184
|
+
outputRootDir: typesCoverageOutputRootDir,
|
|
188
185
|
});
|
|
189
186
|
} catch (err) {
|
|
190
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.
|
|
92
|
+
*
|
|
93
|
+
* This function simply delegates to {@link parseArgs} and returns the same result.
|
|
129
94
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* 2. Explicit `--packages` flag
|
|
133
|
-
* 3. Auto-discovery fallback
|
|
95
|
+
* @returns {ModuleDescriptor[] | null}
|
|
96
|
+
* Array of resolved module descriptors, or `null` if no valid flags were provided.
|
|
134
97
|
*
|
|
135
|
-
* @
|
|
136
|
-
*
|
|
137
|
-
*
|
|
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
|
/**
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { appsDir, librariesBasePaths, staticDiscoveredLibraries } from '../cli-path-config.js';
|
|
5
|
+
import { readJsonSafe } from './file-utils.js';
|
|
6
|
+
import { logWarn } from './log-utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Determine whether a given package directory represents a React-based module.
|
|
10
|
+
*
|
|
11
|
+
* This function inspects the `package.json` file located at `pkgPath`
|
|
12
|
+
* and checks whether any of its dependencies, devDependencies, or peerDependencies
|
|
13
|
+
* include `react`. If the file does not exist, cannot be parsed, or the
|
|
14
|
+
* dependency is missing, it returns `false`.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} pkgPath - Absolute path to a `package.json` file.
|
|
17
|
+
* @returns {boolean} `true` if the module depends on React, otherwise `false`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* isReactModule('/path/to/package.json');
|
|
21
|
+
* // → true if the package declares React as a dependency
|
|
22
|
+
*/
|
|
23
|
+
export function isReactModule(pkgPath) {
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
26
|
+
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
28
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
29
|
+
|
|
30
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
|
|
31
|
+
return Boolean(deps.react);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
logWarn(`Failed to read or parse ${pkgPath}: ${err.message}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Discover all valid application or library modules by scanning predefined directories.
|
|
40
|
+
*
|
|
41
|
+
* Depending on the `isLibrary` flag, this function inspects:
|
|
42
|
+
* - Libraries under: `packages/manager/core`, `packages/manager/modules`, and `packages/components`
|
|
43
|
+
* - Applications under: `manager/apps`
|
|
44
|
+
*
|
|
45
|
+
* For each folder containing a `package.json` that represents a React module,
|
|
46
|
+
* a module descriptor object is created with metadata.
|
|
47
|
+
*
|
|
48
|
+
* @param {boolean} [isLibrary=false] - Whether to scan for libraries (`true`) or apps (`false`).
|
|
49
|
+
* @returns {Array<{ fullPath: string, shortPath: string, packageName: string }>}
|
|
50
|
+
* Array of discovered module descriptors.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Discover apps
|
|
54
|
+
* discoverModules();
|
|
55
|
+
* // → [{ fullPath: '/.../manager/apps/zimbra', shortPath: 'zimbra', packageName: '@ovh-ux/manager-zimbra-app' }]
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Discover libraries
|
|
59
|
+
* discoverModules(true);
|
|
60
|
+
* // → [{ fullPath: '/.../packages/manager/core/core-shell', shortPath: 'core-shell', packageName: '@ovh-ux/manager-core-shell' }]
|
|
61
|
+
*/
|
|
62
|
+
export function discoverModules(isLibrary = false) {
|
|
63
|
+
const discovered = [];
|
|
64
|
+
const modulesPaths = isLibrary ? librariesBasePaths : [appsDir];
|
|
65
|
+
|
|
66
|
+
for (const moduleBasePath of modulesPaths) {
|
|
67
|
+
if (!fs.existsSync(moduleBasePath)) continue;
|
|
68
|
+
|
|
69
|
+
const items = fs.readdirSync(moduleBasePath, { withFileTypes: true });
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
if (item.isDirectory()) {
|
|
72
|
+
const appFolder = path.join(moduleBasePath, item.name);
|
|
73
|
+
const pkgPath = path.join(appFolder, 'package.json');
|
|
74
|
+
if (fs.existsSync(pkgPath) && isReactModule(pkgPath)) {
|
|
75
|
+
const pkgDetails = readJsonSafe(pkgPath);
|
|
76
|
+
discovered.push({
|
|
77
|
+
fullPath: appFolder,
|
|
78
|
+
shortPath: item.name,
|
|
79
|
+
packageName: pkgDetails.name,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return isLibrary ? [...discovered, ...staticDiscoveredLibraries] : discovered;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Retrieve a single module (app or library) matching a given pattern.
|
|
91
|
+
*
|
|
92
|
+
* This function searches through discovered modules (via {@link discoverModules})
|
|
93
|
+
* and returns the first one matching any of the following:
|
|
94
|
+
* - The module's absolute path (`fullPath`)
|
|
95
|
+
* - The folder name (`shortPath`)
|
|
96
|
+
* - The npm package name (`packageName`)
|
|
97
|
+
*
|
|
98
|
+
* @param {string} modulePattern - Pattern to match against module identifiers.
|
|
99
|
+
* @param {boolean} [isLibrary=false] - Whether to search within libraries (`true`) or apps (`false`).
|
|
100
|
+
* @returns {{ fullPath: string, shortPath: string, packageName: string }}
|
|
101
|
+
* The matched module descriptor, or `undefined` if no match is found.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* // Match by folder name
|
|
105
|
+
* getModule('zimbra');
|
|
106
|
+
* // → { fullPath: '/.../manager/apps/zimbra', shortPath: 'zimbra', packageName: '@ovh-ux/manager-zimbra-app' }
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* // Match by package name
|
|
110
|
+
* getModule('@ovh-ux/manager-core-shell', true);
|
|
111
|
+
* // → { fullPath: '/.../packages/manager/core/core-shell', shortPath: 'core-shell', packageName: '@ovh-ux/manager-core-shell' }
|
|
112
|
+
*/
|
|
113
|
+
export function getModule(modulePattern, isLibrary = false) {
|
|
114
|
+
const discoveredLibraries = discoverModules(isLibrary) || [];
|
|
115
|
+
return discoveredLibraries.find(
|
|
116
|
+
(library) =>
|
|
117
|
+
library.fullPath === modulePattern ||
|
|
118
|
+
library.shortPath === modulePattern ||
|
|
119
|
+
library.packageName === modulePattern,
|
|
120
|
+
);
|
|
121
|
+
}
|