@ovh-ux/manager-static-analysis-kit 0.8.0 → 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 +22 -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 +1 -1
- package/bin/perf-budgets-cli.js +58 -297
- package/bin/perf-budgets-tests-cli.js +1 -3
- 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-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 +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 +2 -3
- 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/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 +0 -3
- package/dist/configs/file-globs-config.d.ts.map +1 -1
- package/dist/configs/file-globs-config.js +0 -3
- 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 +32 -7
- package/bin/cli-utils.js +0 -56
- 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
package/bin/perf-budgets-cli.js
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
2
|
import fs from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import process from 'node:process';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
5
|
|
|
8
6
|
import {
|
|
9
7
|
collectPerfBudgets,
|
|
10
8
|
generatePerfBudgetsHtml,
|
|
11
9
|
} from '../dist/adapters/perf-budgets/helpers/perfs-bundle-analysis-helper.js';
|
|
12
10
|
import { bundleAnalysisConfig } from '../dist/configs/bundle-analysis-config.js';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
name: '@ovh-ux/manager-react-components',
|
|
28
|
-
folder: 'manager-react-components',
|
|
29
|
-
path: path.join(rootDir, 'packages/manager-react-components'),
|
|
30
|
-
},
|
|
31
|
-
];
|
|
11
|
+
import {
|
|
12
|
+
appsDir,
|
|
13
|
+
bundleAnalyzerBinPath,
|
|
14
|
+
outputRootDir,
|
|
15
|
+
perfBudgetCombinedHtmlReportName,
|
|
16
|
+
perfBudgetCombinedJsonReportName,
|
|
17
|
+
perfBudgetsReportDirName,
|
|
18
|
+
rootDir,
|
|
19
|
+
} from './cli-path-config.js';
|
|
20
|
+
import { parseCliTargets } from './utils/args-parse-utils.js';
|
|
21
|
+
import { logError, logInfo, logWarn } from './utils/log-utils.js';
|
|
22
|
+
import { ensureBinExists, runAppsAnalysis, runCommand } from './utils/runner-utils.js';
|
|
23
|
+
import { resolveTurboFilters, runTurboBuild } from './utils/turbo-utils.js';
|
|
32
24
|
|
|
33
25
|
/**
|
|
34
|
-
* Run vite-bundle-analyzer for given mode (html or json).
|
|
26
|
+
* Run vite-bundle-analyzer for a given mode (html or json).
|
|
27
|
+
*
|
|
28
|
+
* @param {string} appDir - Absolute app directory
|
|
29
|
+
* @param {string} appShortName - Shortened app name
|
|
30
|
+
* @param {"static"|"json"} mode - Analyzer mode
|
|
31
|
+
* @returns {boolean} True if analyzer produced output, false otherwise
|
|
35
32
|
*/
|
|
36
|
-
function
|
|
33
|
+
function runAppBundleAnalyzer(appDir, appShortName, mode) {
|
|
37
34
|
const reportFile = `${bundleAnalysisConfig?.reportFile || 'bundle-report'}${
|
|
38
35
|
mode === 'json' ? '.json' : '.html'
|
|
39
36
|
}`;
|
|
@@ -50,306 +47,70 @@ function runBundleAnalyzer(appDirectory, appShortName, mode) {
|
|
|
50
47
|
bundleAnalysisConfig?.reportFile || 'bundle-report',
|
|
51
48
|
];
|
|
52
49
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
stdio: 'inherit',
|
|
56
|
-
shell: true,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (result.status !== 0) {
|
|
50
|
+
const ok = runCommand(bundleAnalyzerBinPath, analyzerArgs, appDir);
|
|
51
|
+
if (!ok) {
|
|
60
52
|
logError(`❌ Analyzer (${mode}) failed for ${appShortName}`);
|
|
61
|
-
return;
|
|
53
|
+
return false;
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
const distReport = path.join(
|
|
56
|
+
const distReport = path.join(appDir, 'dist', reportFile);
|
|
65
57
|
const targetReport = path.join(outputDir, reportFile);
|
|
66
58
|
|
|
67
59
|
if (fs.existsSync(distReport)) {
|
|
68
60
|
fs.copyFileSync(distReport, targetReport);
|
|
69
61
|
logInfo(`✅ ${mode.toUpperCase()} report copied to ${targetReport}`);
|
|
70
|
-
return
|
|
62
|
+
return true;
|
|
71
63
|
} else {
|
|
72
64
|
logWarn(`⚠️ Expected ${mode} report not found at ${distReport}`);
|
|
65
|
+
return false;
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
68
|
|
|
76
69
|
/**
|
|
77
|
-
*
|
|
78
|
-
*/
|
|
79
|
-
function runTurboBuild(filters) {
|
|
80
|
-
if (filters.length === 0) {
|
|
81
|
-
logWarn('⚠️ No Turbo build filters provided, skipping build step.');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const turboArgs = ['run', 'build', ...filters.flatMap((pkgName) => ['--filter', pkgName])];
|
|
86
|
-
logInfo(`Building with Turbo (filters: ${filters.join(', ')})`);
|
|
87
|
-
|
|
88
|
-
const buildResult = spawnSync('turbo', turboArgs, {
|
|
89
|
-
cwd: rootDir,
|
|
90
|
-
stdio: 'inherit',
|
|
91
|
-
shell: true,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
if (buildResult.status !== 0) {
|
|
95
|
-
logWarn('⚠️ Turbo build failed for one or more targets. Will still try to analyze apps.');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Analyze all given app folders.
|
|
101
|
-
*/
|
|
102
|
-
function analyzeAppFolders(appFolders) {
|
|
103
|
-
const analyzed = [];
|
|
104
|
-
for (const appFolder of appFolders) {
|
|
105
|
-
try {
|
|
106
|
-
const appDir = path.join(appsDir, appFolder);
|
|
107
|
-
if (!fs.existsSync(appDir)) {
|
|
108
|
-
logError(`❌ App folder not found: ${appFolder} → skipping`);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf-8'));
|
|
113
|
-
const appName = pkg.name;
|
|
114
|
-
const appShortName = appName.replace(/^@ovh-ux\//, '');
|
|
115
|
-
|
|
116
|
-
runBundleAnalyzer(appDir, appShortName, 'static');
|
|
117
|
-
runBundleAnalyzer(appDir, appShortName, 'json');
|
|
118
|
-
|
|
119
|
-
analyzed.push(appName);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
logError(`❌ Failed to analyze ${appFolder}: ${err.message}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return analyzed;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Remove old combined perf budget reports.
|
|
129
|
-
*/
|
|
130
|
-
function removeOldCombinedReports() {
|
|
131
|
-
const combinedJsonPath = path.join(
|
|
132
|
-
outputRootDir,
|
|
133
|
-
perfBudgetsReportDirName,
|
|
134
|
-
perfBudgetCombinedJsonReportName,
|
|
135
|
-
);
|
|
136
|
-
const combinedHtmlPath = path.join(
|
|
137
|
-
outputRootDir,
|
|
138
|
-
perfBudgetsReportDirName,
|
|
139
|
-
perfBudgetCombinedHtmlReportName,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
[combinedJsonPath, combinedHtmlPath].forEach((file) => {
|
|
143
|
-
if (fs.existsSync(file)) {
|
|
144
|
-
fs.unlinkSync(file);
|
|
145
|
-
logInfo(`🗑️ Removed old report: ${file}`);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Collect budgets and regenerate combined reports.
|
|
152
|
-
*/
|
|
153
|
-
function generateCombinedReports(root) {
|
|
154
|
-
removeOldCombinedReports();
|
|
155
|
-
|
|
156
|
-
const summary = collectPerfBudgets(root);
|
|
157
|
-
|
|
158
|
-
const combinedJsonPath = path.join(
|
|
159
|
-
root,
|
|
160
|
-
perfBudgetsReportDirName,
|
|
161
|
-
perfBudgetCombinedJsonReportName,
|
|
162
|
-
);
|
|
163
|
-
fs.writeFileSync(combinedJsonPath, JSON.stringify(summary, null, 2));
|
|
164
|
-
logInfo(`✅ Combined JSON report written to ${combinedJsonPath}`);
|
|
165
|
-
|
|
166
|
-
const combinedHtmlPath = path.join(
|
|
167
|
-
root,
|
|
168
|
-
perfBudgetsReportDirName,
|
|
169
|
-
perfBudgetCombinedHtmlReportName,
|
|
170
|
-
);
|
|
171
|
-
fs.writeFileSync(combinedHtmlPath, generatePerfBudgetsHtml(summary));
|
|
172
|
-
logInfo(`✅ Combined HTML report written to ${combinedHtmlPath}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Guess app folder name from package name.
|
|
177
|
-
* Example: @ovh-ux/manager-zimbra-app -> zimbra
|
|
178
|
-
*/
|
|
179
|
-
function extractAppFolderFromPackageName(packageName) {
|
|
180
|
-
const match = packageName.match(/^@ovh-ux\/manager-(.+)-app$/);
|
|
181
|
-
if (!match) {
|
|
182
|
-
logError(`❌ Invalid package name format: ${packageName}`);
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
return match[1];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Convert app folders to Turbo filters (package names).
|
|
190
|
-
*/
|
|
191
|
-
function resolveTurboFiltersFromAppFolders(appFolders) {
|
|
192
|
-
return appFolders
|
|
193
|
-
.map((appFolder) => {
|
|
194
|
-
const pkgPath = path.join(appsDir, appFolder, 'package.json');
|
|
195
|
-
if (!fs.existsSync(pkgPath)) {
|
|
196
|
-
logError(`❌ No package.json for app folder ${appFolder}, skipping`);
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
200
|
-
return pkg.name;
|
|
201
|
-
})
|
|
202
|
-
.filter(Boolean);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Parse CLI arguments for `--apps` or `--app` flags.
|
|
207
|
-
*
|
|
208
|
-
* @returns {{appFolders: string[], packageNames: string[]} | null}
|
|
209
|
-
* An object with valid app folder names and an empty packageNames array,
|
|
210
|
-
* or null if the flag is not present.
|
|
211
|
-
*/
|
|
212
|
-
function parseAppsArg() {
|
|
213
|
-
const appsArg = process.argv.find((arg) => arg === '--apps' || arg === '--app');
|
|
214
|
-
if (!appsArg) return null;
|
|
215
|
-
|
|
216
|
-
const index = process.argv.indexOf(appsArg);
|
|
217
|
-
const rawValue = process.argv[index + 1] || '';
|
|
218
|
-
const appFolders = rawValue
|
|
219
|
-
.split(',')
|
|
220
|
-
.map((n) => n.trim())
|
|
221
|
-
.filter(Boolean);
|
|
222
|
-
|
|
223
|
-
const validFolders = appFolders.filter((folder) => {
|
|
224
|
-
if (!fs.existsSync(path.join(appsDir, folder))) {
|
|
225
|
-
logError(`❌ App not found: ${folder} → skipping`);
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
logInfo(`Running in apps mode for: ${validFolders.join(', ')}`);
|
|
232
|
-
return { appFolders: validFolders, packageNames: [] };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Parse CLI arguments for `--packages` or `--package` flags.
|
|
237
|
-
*
|
|
238
|
-
* @returns {{appFolders: string[], packageNames: string[]} | null}
|
|
239
|
-
* An object mapping package names to their corresponding app folders,
|
|
240
|
-
* or null if the flag is not present.
|
|
241
|
-
*/
|
|
242
|
-
function parsePackagesArg() {
|
|
243
|
-
const packagesArg = process.argv.find((arg) => arg === '--packages' || arg === '--package');
|
|
244
|
-
if (!packagesArg) return null;
|
|
245
|
-
|
|
246
|
-
const index = process.argv.indexOf(packagesArg);
|
|
247
|
-
const rawValue = process.argv[index + 1] || '';
|
|
248
|
-
const packageNames = rawValue
|
|
249
|
-
.split(',')
|
|
250
|
-
.map((n) => n.trim())
|
|
251
|
-
.filter(Boolean);
|
|
252
|
-
|
|
253
|
-
const mappedFolders = [];
|
|
254
|
-
for (const pkgName of packageNames) {
|
|
255
|
-
const folder = extractAppFolderFromPackageName(pkgName);
|
|
256
|
-
if (folder && fs.existsSync(path.join(appsDir, folder))) {
|
|
257
|
-
mappedFolders.push(folder);
|
|
258
|
-
} else {
|
|
259
|
-
logError(`❌ Package ${pkgName} → app folder ${folder} not found, skipping`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
logInfo(
|
|
264
|
-
`Running in packages mode for: ${packageNames.join(', ')} → apps: ${mappedFolders.join(', ')}`,
|
|
265
|
-
);
|
|
266
|
-
return { appFolders: mappedFolders, packageNames };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Discover all React apps automatically when no CLI flags are provided.
|
|
70
|
+
* Run perf budgets analysis for one app (both static + json).
|
|
271
71
|
*
|
|
272
|
-
* @
|
|
273
|
-
*
|
|
72
|
+
* @param {string} appDir
|
|
73
|
+
* @param {string} appShortName
|
|
74
|
+
* @returns {boolean} True if at least one report succeeded
|
|
274
75
|
*/
|
|
275
|
-
function
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
if (discoveredAppFolders.length === 0) {
|
|
282
|
-
logWarn('⚠️ No React apps found to analyze.');
|
|
283
|
-
return { appFolders: [], packageNames: [] };
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
logInfo(`Running in auto-discovery mode (${discoveredAppFolders.length} apps).`);
|
|
287
|
-
return { appFolders: discoveredAppFolders, packageNames: [] };
|
|
76
|
+
function runAppPerfBudgets(appDir, appShortName) {
|
|
77
|
+
const okStatic = runAppBundleAnalyzer(appDir, appShortName, 'static');
|
|
78
|
+
const okJson = runAppBundleAnalyzer(appDir, appShortName, 'json');
|
|
79
|
+
return okStatic || okJson;
|
|
288
80
|
}
|
|
289
81
|
|
|
290
82
|
/**
|
|
291
|
-
*
|
|
83
|
+
* Main CLI entrypoint for perf budgets analysis.
|
|
292
84
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* @returns {{appFolders: string[], packageNames: string[]}}
|
|
299
|
-
* Object containing app folder names and package names for analysis.
|
|
300
|
-
*/
|
|
301
|
-
function parseCliTargets() {
|
|
302
|
-
return parseAppsArg() || parsePackagesArg() || discoverApps();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Resolve Turbo filters (package names) depending on mode.
|
|
307
|
-
*/
|
|
308
|
-
function resolveTurboFilters({ appFolders, packageNames }) {
|
|
309
|
-
let filters = [];
|
|
310
|
-
if (appFolders.length > 0 && packageNames.length === 0) {
|
|
311
|
-
filters = resolveTurboFiltersFromAppFolders(appFolders);
|
|
312
|
-
} else if (packageNames.length > 0) {
|
|
313
|
-
filters = packageNames;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Always include shared packages for build
|
|
317
|
-
for (const pkg of sharedPackages) {
|
|
318
|
-
if (fs.existsSync(pkg.path) && !filters.includes(pkg.name)) {
|
|
319
|
-
filters.push(pkg.name);
|
|
320
|
-
logInfo(`Including shared dependency in build: ${pkg.name}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return filters;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Main CLI entrypoint.
|
|
85
|
+
* - Ensures analyzer binary is available
|
|
86
|
+
* - Runs a turbo build for the selected apps
|
|
87
|
+
* - Executes analyzer per app via {@link runAppPerfBudgets}
|
|
88
|
+
* - Generates combined JSON + HTML reports
|
|
329
89
|
*/
|
|
330
90
|
function main() {
|
|
331
91
|
try {
|
|
332
|
-
|
|
333
|
-
logError(`❌ Analyzer binary not found at ${bundleAnalyzerBin}`);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
92
|
+
ensureBinExists(bundleAnalyzerBinPath, 'vite-bundle-analyzer');
|
|
336
93
|
|
|
337
|
-
const { appFolders, packageNames } = parseCliTargets();
|
|
338
|
-
const turboFilters = resolveTurboFilters({ appFolders, packageNames });
|
|
94
|
+
const { appFolders, packageNames } = parseCliTargets(appsDir);
|
|
95
|
+
const turboFilters = resolveTurboFilters({ appsDir, appFolders, packageNames });
|
|
339
96
|
|
|
340
97
|
// Step 1: build
|
|
341
|
-
runTurboBuild(turboFilters);
|
|
342
|
-
|
|
343
|
-
// Step 2: analyze
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
98
|
+
runTurboBuild(rootDir, turboFilters);
|
|
99
|
+
|
|
100
|
+
// Step 2: analyze apps + combined
|
|
101
|
+
runAppsAnalysis({
|
|
102
|
+
appsDir,
|
|
103
|
+
appFolders,
|
|
104
|
+
requireReact: false,
|
|
105
|
+
binaryLabel: 'Perf budgets',
|
|
106
|
+
appRunner: runAppPerfBudgets,
|
|
107
|
+
reportsRootDirName: perfBudgetsReportDirName,
|
|
108
|
+
combinedJson: perfBudgetCombinedJsonReportName,
|
|
109
|
+
combinedHtml: perfBudgetCombinedHtmlReportName,
|
|
110
|
+
collectFn: collectPerfBudgets,
|
|
111
|
+
generateHtmlFn: generatePerfBudgetsHtml,
|
|
112
|
+
outputRootDir,
|
|
113
|
+
});
|
|
353
114
|
} catch (err) {
|
|
354
115
|
logError(`Fatal error: ${err.stack || err.message}`);
|
|
355
116
|
process.exit(1);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import process from 'node:process';
|
|
4
3
|
|
|
5
|
-
import { logError, logInfo } from './
|
|
4
|
+
import { logError, logInfo } from './utils/log-utils.js';
|
|
6
5
|
|
|
7
6
|
// Define the set of validation test cases
|
|
8
7
|
const cases = [
|
|
@@ -68,7 +67,6 @@ for (const test of cases) {
|
|
|
68
67
|
|
|
69
68
|
if (failed > 0) {
|
|
70
69
|
logError(`\n❌ ${failed} test(s) failed.`);
|
|
71
|
-
process.exit(1);
|
|
72
70
|
} else {
|
|
73
71
|
logInfo('\n🎉 All perf-budgets CLI validation tests passed!');
|
|
74
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
|
+
}
|