@ovh-ux/manager-static-analysis-kit 0.10.0 โ†’ 0.11.1

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 CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.11.1](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.11.0...@ovh-ux/manager-static-analysis-kit@0.11.1) (2025-10-12)
7
+
8
+ **Note:** Version bump only for package @ovh-ux/manager-static-analysis-kit
9
+
10
+
11
+
12
+
13
+
14
+ # [0.11.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.10.0...@ovh-ux/manager-static-analysis-kit@0.11.0) (2025-10-07)
15
+
16
+
17
+ ### Features
18
+
19
+ * extends static kit to analyse react libraries ([8c2845c](https://github.com/ovh/manager/commit/8c2845cdc3abc13734831fab312d8ad012b30b16)), closes [#MANAGER-19743](https://github.com/ovh/manager/issues/MANAGER-19743)
20
+ * improve documentation ([4141218](https://github.com/ovh/manager/commit/4141218c63abcab05edc5d4db2eb44f7701c04c7)), closes [#MANAGER-19743](https://github.com/ovh/manager/issues/MANAGER-19743)
21
+ * include mrc on new metrics analysis ([456d311](https://github.com/ovh/manager/commit/456d3118a7d9017d63f19e907b45f2833e4a0348)), closes [#MANAGER-19743](https://github.com/ovh/manager/issues/MANAGER-19743)
22
+ * update documentation ([3b37b1b](https://github.com/ovh/manager/commit/3b37b1b51bd9ff06e23e4d2169511f05b4aec345)), closes [#MANAGER-19743](https://github.com/ovh/manager/issues/MANAGER-19743)
23
+
24
+
25
+
26
+
27
+
6
28
  # [0.10.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.9.0...@ovh-ux/manager-static-analysis-kit@0.10.0) (2025-10-02)
7
29
 
8
30
 
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # ๐Ÿงช static-analysis-kit
2
2
 
3
- A modular, extensible, and future-ready toolkit for defining, adapting, and sharing static analysis rules across projects in the `manager` monorepo.
3
+ A modular, extensible, and future-ready toolkit for defining, adapting, and sharing static and dynamic analysis rules across projects in the **Manager monorepo**.
4
4
 
5
5
  ---
6
6
 
7
7
  ## ๐Ÿš€ What Is It?
8
8
 
9
- `static-analysis-kit` provides a unified platform for **static & dynamic quality checks**: linting, duplication, type coverage, tests coverage, and performance budgets.
9
+ `@ovh-ux/manager-static-analysis-kit` provides a unified framework for **code quality automation** across all applications, libraries, and modules โ€” covering both **static** (lint, duplication, type coverage) and **dynamic** (tests, performance) checks.
10
10
 
11
- It separates semantic rule definitions from tool-specific integrations using a clean adapter-based architecture.
11
+ It separates semantic rule definitions from tool-specific adapters to ensure configurability, reusability, and long-term consistency across the monorepo.
12
12
 
13
13
  ---
14
14
 
@@ -33,34 +33,108 @@ Coming soon:
33
33
  > - [Performance Budgets](src/adapters/perf-budgets/README.md)
34
34
  > - [Tests Coverage](src/adapters/tests-coverage/README.md)
35
35
  > - [Types Coverage](src/adapters/types-coverage/README.md)
36
- > - [HTML W3C Validation](src/adapters/html-w3c-validation/README.md)
37
- > - [HTML Accessibility Matcher](src/adapters/html-a11y-validation/README.md)
36
+ > - [HTML Validation](src/adapters/html-w3c-validation/README.md)
37
+ > - [Accessibility Validation](src/adapters/html-a11y-validation/README.md)
38
38
 
39
39
  ---
40
40
 
41
41
  ## ๐Ÿง‘โ€๐Ÿ’ป Combined Quality Checks
42
42
 
43
- The kit also provides a **single entrypoint** for running all static & dynamic quality checks in parallel:
43
+ The kit also provides a **single entrypoint** for running all static & dynamic quality checks:
44
44
 
45
45
  ```bash
46
46
  # Run all quality checks
47
47
  yarn manager-static-dynamic-quality-checks
48
48
 
49
- # Run only tests-related checks
49
+ # Run only test-related checks
50
50
  yarn manager-static-dynamic-quality-checks --tests
51
51
  ```
52
52
 
53
- ### Run by Type
53
+ ---
54
+
55
+ ## ๐Ÿ”ง Launch Modes
56
+
57
+ Each CLI (e.g. `manager-code-duplication`, `manager-types-coverage`) supports **flexible launch modes** for targeting different scopes within the monorepo.
58
+
59
+ | Flag | Target | Path Resolution |
60
+ |------|---------|----------------|
61
+ | `--apps` / `--app` | Manager applications | `manager/apps/*` |
62
+ | `--packages` / `--package` | Package-level analysis | Resolves via `package.json` |
63
+ | `--libraries` / `--library` | Internal libraries | `packages/manager/core`, `packages/manager/modules`, `packages/components` |
64
+
65
+ ---
66
+
67
+ ### Analyze specific apps (by folder name)
68
+
69
+ ```bash
70
+ yarn manager-types-coverage --apps zimbra,container
71
+ ```
72
+
73
+ or single app:
74
+
75
+ ```bash
76
+ yarn manager-types-coverage --app zimbra
77
+ ```
78
+
79
+ > Here you provide **app folders** located under `manager/apps`.
80
+
81
+ ---
82
+
83
+ ### Analyze specific packages (by `package.json` name)
84
+
85
+ ```bash
86
+ yarn manager-types-coverage --packages @ovh-ux/manager-container-app,@ovh-ux/manager-zimbra-app
87
+ ```
88
+
89
+ or single package:
90
+
91
+ ```bash
92
+ yarn manager-types-coverage --package @ovh-ux/manager-container-app
93
+ ```
94
+
95
+ > In this mode:
96
+ > - The CLI resolves the package names to their corresponding **apps**.
97
+ > - Reports are generated per **app folder**.
98
+
99
+ ---
100
+
101
+ ### Analyze specific libraries (by `package.json` name)
102
+
103
+ ```bash
104
+ yarn manager-types-coverage --libraries @ovh-ux/manager-wiki,@ovh-ux/manager-react-components
105
+ ```
54
106
 
55
- You can also target subsets of checks:
107
+ or single library:
56
108
 
57
109
  ```bash
58
- yarn manager-code-duplication
59
- yarn manager-perf-budgets
60
- yarn manager-tests-coverage
61
- yarn manager-types-coverage
110
+ yarn manager-types-coverage --library @ovh-ux/manager-react-components
62
111
  ```
63
112
 
113
+ > In this mode:
114
+ > - The CLI resolves the library names to their corresponding **folders** inside configured library roots:
115
+ > - `packages/manager-react-components/*`
116
+ > - `packages/manager-wiki/*`
117
+ > - Reports are generated per **library folder**.
118
+
119
+ ---
120
+
121
+ ## โš ๏ธ Important Requirements
122
+
123
+ โš ๏ธ **Important:**
124
+
125
+ These analysis tools only process **React-based applications and libraries** โ€” **Angular or legacy frameworks are not supported**.
126
+
127
+ Each analysis type has specific requirements to run successfully:
128
+
129
+ | Analysis Type | Required Configuration | Description |
130
+ |----------------|-------------------------|--------------|
131
+ | **Type Coverage** | `tsconfig.json` | The tool analyzes only TypeScript projects with a valid configuration file. |
132
+ | **Tests Coverage** | Vitest or Jest setup | Requires test files (`*.test.ts[x]`, `*.spec.ts[x]`) and a working Vitest/Jest config. |
133
+ | **Performance Budgets** | `vite.config.ts` or `vite.config.js` | Requires a Vite-based build configuration to extract bundle metrics. |
134
+ | **Code Duplication** | React source files (`.js`, `.ts`, `.tsx`) | Automatically skips non-React or invalid modules. |
135
+
136
+ > Modules that don't meet these conditions are **skipped automatically with a warning**, but the CLI will still analyze all valid React apps and libraries to ensure partial success in mixed environments.
137
+
64
138
  ---
65
139
 
66
140
  ## ๐Ÿงฑ Structure Overview
@@ -68,16 +142,26 @@ yarn manager-types-coverage
68
142
  ```
69
143
  static-analysis-kit/
70
144
  โ”œโ”€โ”€ src/
71
- โ”‚ โ”œโ”€โ”€ code-analysis.ts # Central entry point (exports adapters)
72
- โ”‚ โ”œโ”€โ”€ configs/ # Tool-agnostic semantic rule definitions
145
+ โ”‚ โ”œโ”€โ”€ code-analysis.ts # Central export point
146
+ โ”‚ โ”œโ”€โ”€ configs/ # Tool-agnostic rule definitions
73
147
  โ”‚ โ””โ”€โ”€ adapters/ # Tool integrations
74
148
  โ”‚ โ”œโ”€โ”€ eslint/
75
- โ”‚ โ”œโ”€โ”€ code-duplication/ # Duplication reports
76
- โ”‚ โ”œโ”€โ”€ perf-budgets/ # Performance analyzer
77
- โ”‚ โ”œโ”€โ”€ tests-coverage/ # Test coverage reports
78
- โ”‚ โ”œโ”€โ”€ types-coverage/ # TS coverage reports
79
- โ”‚ โ”œโ”€โ”€ madge/ # Coming soon
80
- โ”‚ โ””โ”€โ”€ code-health-meter/ # Coming soon
149
+ โ”‚ โ”œโ”€โ”€ code-duplication/
150
+ โ”‚ โ”œโ”€โ”€ perf-budgets/
151
+ โ”‚ โ”œโ”€โ”€ tests-coverage/
152
+ โ”‚ โ”œโ”€โ”€ types-coverage/
153
+ โ”‚ โ”œโ”€โ”€ ts-config/
154
+ โ”‚ โ”œโ”€โ”€ html-a11y-validation/
155
+ โ”‚ โ”œโ”€โ”€ html-w3c-validation/
156
+ โ”‚ โ””โ”€โ”€ architecture-analysis/ # Coming soon
157
+ โ”œโ”€โ”€ bin/
158
+ โ”‚ โ”œโ”€โ”€ utils/ # Shared CLI utilities
159
+ โ”‚ โ”‚ โ”œโ”€โ”€ args-parse-utils.js # CLI flags parsing (apps/libs/packages)
160
+ โ”‚ โ”‚ โ”œโ”€โ”€ module-utils.js # Discovery and resolution logic
161
+ โ”‚ โ”‚ โ”œโ”€โ”€ file-utils.js # JSON read/write helpers
162
+ โ”‚ โ”‚ โ””โ”€โ”€ log-utils.js # Colored CLI logging
163
+ โ”‚ โ””โ”€โ”€ cli-path-config.js # Centralized base paths & root dirs
164
+ โ””โ”€โ”€ README.md
81
165
  ```
82
166
 
83
167
  ---
@@ -91,8 +175,66 @@ static-analysis-kit/
91
175
 
92
176
  ---
93
177
 
94
- ## ๐Ÿ“ฌ Feedback Welcome
178
+ ## ๐Ÿงช Validation & CLI Testing
179
+
180
+ Each CLI in the Static Analysis Kit (e.g., `manager-code-duplication`, `manager-perf-budgets`, `manager-tests-coverage`, `manager-types-coverage`) includes automated validation suites that ensure correct argument parsing, module discovery, and exit-code semantics.
181
+
182
+ These tests simulate **real-world usage** across apps, packages, and libraries โ€” both valid and invalid โ€” to guarantee stability and predictable CLI behavior.
183
+
184
+ To execute these tests, from the root run:
185
+
186
+ ```bash
187
+ yarn static-dynamic-quality-check-tests
188
+ ```
189
+
190
+ ---
191
+
192
+ ### ๐Ÿงญ Test Matrix
193
+
194
+ | Category | Example Command | Expected Exit |
195
+ |-----------|----------------|----------------|
196
+ | **Single app** | `yarn manager-code-duplication --app zimbra` | โœ… `0` |
197
+ | **Multiple apps** | `yarn manager-code-duplication --apps container,zimbra` | โœ… `0` |
198
+ | **Single package** | `yarn manager-code-duplication --package @ovh-ux/manager-container-app` | โœ… `0` |
199
+ | **Multiple packages** | `yarn manager-code-duplication --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-pci-workflow-app` | โœ… `0` |
200
+ | **Single library (static)** | `yarn manager-code-duplication --library manager-react-components` | โœ… `0` |
201
+ | **Single library (dynamic)** | `yarn manager-code-duplication --library shell-client` | โœ… `0` |
202
+ | **Multiple libraries** | `yarn manager-code-duplication --libraries manager-wiki,manager-react-components,shell-client` | โœ… `0` |
203
+ | **Mixed valid + invalid apps** | `yarn manager-code-duplication --apps zimbra,unknown-app,container` | โœ… `0` |
204
+ | **All invalid apps** | `yarn manager-code-duplication --apps doesnotexist1,doesnotexist2` | โŒ `1` |
205
+ | **Turbo build simulation** | `yarn manager-code-duplication --app pci-ai-tools` | โœ… `0` |
206
+
207
+ > ๐Ÿงฉ Each case asserts CLI correctness, exit codes, and resilience against unknown modules.
208
+
209
+ ---
210
+
211
+ ### ๐Ÿงฐ Supported CLIs
212
+
213
+ | CLI | Purpose |
214
+ |-----|----------|
215
+ | `manager-code-duplication` | Detects duplicated code across apps and libs |
216
+ | `manager-perf-budgets` | Evaluates bundle and asset sizes |
217
+ | `manager-tests-coverage` | Aggregates Jest/Vitest coverage reports |
218
+ | `manager-types-coverage` | Reports TypeScript type coverage |
219
+
220
+ Each CLI supports:
221
+ - `--apps` / `--app`
222
+ - `--packages` / `--package`
223
+ - `--libraries` / `--library`
224
+
225
+ ---
226
+
227
+ ## ๐Ÿ“ฌ Feedback & Contributions
95
228
 
96
- The goal is to create a **robust, composable, and organization-wide standard** for static analysis and dynamic quality checks.
229
+ The **Static Analysis Kit** aims to become the central standard for static and dynamic quality assurance across all OVHcloud Manager projects.
230
+
231
+ We welcome contributions that:
232
+ - Add new analyzers or integrations
233
+ - Improve performance or modularity
234
+ - Enhance developer experience or CI/CD compatibility
235
+
236
+ ๐Ÿ’ก **Goal:** A composable, maintainable, and organization-wide framework for modern code quality governance.
237
+
238
+ ---
97
239
 
98
- Suggestions & contributions are welcome.
240
+ ยฉ 2025 OVHcloud โ€” Manager Engineering Team
@@ -4,23 +4,40 @@ import { fileURLToPath } from 'node:url';
4
4
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
5
 
6
6
  export const rootDir = path.resolve(__dirname, '../../..');
7
- export const outputRootDir = path.resolve(__dirname, '../../../../static-dynamic-quality-reports');
8
7
 
9
- export const appsDir = path.join(rootDir, 'manager/apps');
8
+ export const appsDir = path.join('.', 'packages/manager/apps');
9
+
10
+ export const librariesBasePaths = [
11
+ path.join('.', 'packages/manager/core'),
12
+ path.join('.', 'packages/manager/modules'),
13
+ path.join('.', 'packages/components'),
14
+ ];
10
15
 
11
16
  export const bundleAnalyzerBinPath = path.resolve(__dirname, '../node_modules/.bin/analyze');
12
17
  export const perfBudgetsReportDirName = 'perf-budgets-reports';
13
18
  export const perfBudgetCombinedJsonReportName = 'perf-budgets-combined-report.json';
14
19
  export const perfBudgetCombinedHtmlReportName = 'perf-budgets-combined-report.html';
20
+ export const perfBudgetOutputRootDir = path.resolve(
21
+ __dirname,
22
+ '../../../../static-dynamic-quality-reports',
23
+ );
15
24
 
16
25
  export const jscpdBinPath = path.resolve(__dirname, '../node_modules/.bin/jscpd');
17
26
  export const codeDupReportsRootDirName = 'code-duplication-reports';
18
27
  export const codeDupCombinedJsonReportName = 'code-duplication-combined-report.json';
19
28
  export const codeDupCombinedHtmlReportName = 'code-duplication-combined-report.html';
29
+ export const codeDupOutputRootDir = path.resolve(
30
+ __dirname,
31
+ '../../../../static-dynamic-quality-reports',
32
+ );
20
33
 
21
34
  export const testsCoverageReportsRootDirName = 'tests-coverage-reports';
22
35
  export const testsCoverageCombinedJsonReportName = 'tests-coverage-combined-report.json';
23
36
  export const testsCoverageCombinedHtmlReportName = 'tests-coverage-combined-report.html';
37
+ export const testsCoverageOutputRootDir = path.resolve(
38
+ __dirname,
39
+ '../../../../static-dynamic-quality-reports',
40
+ );
24
41
 
25
42
  export const tsTypesCoverageBin = path.resolve(
26
43
  __dirname,
@@ -29,3 +46,25 @@ export const tsTypesCoverageBin = path.resolve(
29
46
  export const typesCoverageReportsRootDirName = 'types-coverage-reports';
30
47
  export const typesCoverageCombinedJsonReportName = 'types-coverage-combined-report.json';
31
48
  export const typesCoverageCombinedHtmlReportName = 'types-coverage-combined-report.html';
49
+ export const typesCoverageOutputRootDir = path.resolve(
50
+ __dirname,
51
+ '../../../../static-dynamic-quality-reports',
52
+ );
53
+
54
+ export const combinedReportOutputRootDir = path.resolve(
55
+ __dirname,
56
+ '../../../../static-dynamic-quality-reports',
57
+ );
58
+
59
+ export const staticDiscoveredLibraries = [
60
+ {
61
+ fullPath: 'packages/manager-react-components',
62
+ shortPath: 'manager-react-components',
63
+ packageName: '@ovh-ux/manager-react-components',
64
+ },
65
+ {
66
+ fullPath: 'packages/manager-wiki',
67
+ shortPath: 'manager-wiki',
68
+ packageName: '@ovh-ux/manager-wiki',
69
+ },
70
+ ];
@@ -8,16 +8,15 @@ import {
8
8
  generateCodeDuplicationHtml,
9
9
  } from '../dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js';
10
10
  import {
11
- appsDir,
12
11
  codeDupCombinedHtmlReportName,
13
12
  codeDupCombinedJsonReportName,
13
+ codeDupOutputRootDir,
14
14
  codeDupReportsRootDirName,
15
15
  jscpdBinPath,
16
- outputRootDir,
17
16
  } from './cli-path-config.js';
18
17
  import { buildCodeDuplicationArgs, parseCliTargets } from './utils/args-parse-utils.js';
19
18
  import { logError, logInfo, logWarn } from './utils/log-utils.js';
20
- import { ensureBinExists, runAppsAnalysis, runCommand } from './utils/runner-utils.js';
19
+ import { ensureBinExists, runCommand, runModulesAnalysis } from './utils/runner-utils.js';
21
20
 
22
21
  /**
23
22
  * Run `jscpd` code duplication analysis for a single app.
@@ -28,16 +27,17 @@ import { ensureBinExists, runAppsAnalysis, runCommand } from './utils/runner-uti
28
27
  */
29
28
  function runAppCodeDuplication(appDir, appShortName) {
30
29
  try {
31
- const absoluteOutputDir = path.join(outputRootDir, codeDupReportsRootDirName, appShortName);
30
+ const absoluteOutputDir = path.join(
31
+ codeDupOutputRootDir,
32
+ codeDupReportsRootDirName,
33
+ appShortName,
34
+ );
32
35
  fs.mkdirSync(absoluteOutputDir, { recursive: true });
33
36
 
34
- // Relative output path from the app dir (keeps paths clean inside HTML reports)
35
- const relativeOutputDir = path.relative(appDir, absoluteOutputDir);
36
-
37
37
  logInfo(`Running jscpd for ${appShortName} โ†’ ${absoluteOutputDir}`);
38
38
 
39
- const args = [...buildCodeDuplicationArgs(relativeOutputDir), appDir];
40
- const ok = runCommand(jscpdBinPath, args, appDir);
39
+ const args = [...buildCodeDuplicationArgs(absoluteOutputDir), appDir];
40
+ const ok = runCommand(jscpdBinPath, args, '.');
41
41
 
42
42
  // Validate expected reports
43
43
  const jsonReport = path.join(absoluteOutputDir, 'jscpd-report.json');
@@ -72,20 +72,18 @@ function main() {
72
72
  try {
73
73
  ensureBinExists(jscpdBinPath, 'jscpd');
74
74
 
75
- const { appFolders } = parseCliTargets(appsDir);
75
+ const modules = parseCliTargets();
76
76
 
77
- runAppsAnalysis({
78
- appsDir,
79
- appFolders,
80
- requireReact: true,
77
+ runModulesAnalysis({
78
+ modules,
81
79
  binaryLabel: 'Code duplication',
82
- appRunner: runAppCodeDuplication,
80
+ analysisRunner: runAppCodeDuplication,
83
81
  reportsRootDirName: codeDupReportsRootDirName,
84
82
  combinedJson: codeDupCombinedJsonReportName,
85
83
  combinedHtml: codeDupCombinedHtmlReportName,
86
84
  collectFn: collectCodeDuplication,
87
85
  generateHtmlFn: generateCodeDuplicationHtml,
88
- outputRootDir,
86
+ outputRootDir: codeDupOutputRootDir,
89
87
  });
90
88
  } catch (err) {
91
89
  logError(`Fatal error: ${err.stack || err.message}`);
@@ -5,13 +5,10 @@ import { logError, logInfo } from './utils/log-utils.js';
5
5
 
6
6
  // Define the set of validation test cases for code-duplication CLI
7
7
  const cases = [
8
- // Debug only โ€” very slow, runs across all apps
9
- // { name: 'Auto-discovery (all apps)', cmd: 'yarn manager-code-duplication', expect: 0 },
10
-
11
8
  { name: 'Single app (zimbra)', cmd: 'yarn manager-code-duplication --app zimbra', expect: 0 },
12
9
  {
13
- name: 'Multiple apps (container,zimbra,web-ongoing-operations,key-management-service)',
14
- cmd: 'yarn manager-code-duplication --apps container,zimbra,web-ongoing-operations,key-management-service',
10
+ name: 'Multiple apps (container,zimbra)',
11
+ cmd: 'yarn manager-code-duplication --apps container,zimbra',
15
12
  expect: 0,
16
13
  },
17
14
  {
@@ -24,6 +21,36 @@ const cases = [
24
21
  cmd: 'yarn manager-code-duplication --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-code-duplication --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-code-duplication --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-code-duplication --library shell-client',
37
+ expect: 0,
38
+ },
39
+ {
40
+ name: 'Single library static path by package name (shell-client)',
41
+ cmd: 'yarn manager-code-duplication --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-code-duplication --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-code-duplication --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-code-duplication --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-code-duplication --packages @ovh-ux/manager-zimbra-app,@ovh-ux/manager-ghost-app',
61
+ cmd: 'yarn manager-code-duplication --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-code-duplication --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-code-duplication --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-code-duplication --app pci-ai-tools',
82
+ expect: 0,
83
+ },
47
84
  ];
48
85
 
49
86
  let failed = 0;
package/bin/lint-cli.js CHANGED
@@ -2,74 +2,119 @@
2
2
  import { spawn } from 'node:child_process';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { dirname, join, resolve } from 'node:path';
5
- import process, { cwd } from 'node:process';
6
- import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import process, { cwd, execPath } from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { createRequire } from 'node:module';
7
8
 
8
9
  import { logError, logInfo } from './utils/log-utils.js';
9
10
 
11
+ /**
12
+ * manager-lint
13
+ * - Uses ESLint's real JS entry (no platform-specific .bin)
14
+ * - Auto-picks config: --config arg > local eslint.config.* > shared default
15
+ * - Supports TS flat config by preloading tsx only when needed
16
+ */
17
+
10
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const require = createRequire(import.meta.url);
11
20
  const userCwd = cwd();
12
- const eslintBin = resolve(__dirname, '../node_modules/.bin/eslint');
13
21
 
22
+ // ---------------------------------------------------------------------------
23
+ // Resolve ESLint CLI entry (avoid .bin shims)
24
+ // ---------------------------------------------------------------------------
25
+ let eslintCli;
26
+ try {
27
+ eslintCli = resolve(require.resolve('eslint/package.json'), '../bin/eslint.js');
28
+ } catch {
29
+ logError('[manager-lint] Could not resolve "eslint". Is it installed in this package?');
30
+ process.exit(1);
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Args & config
35
+ // ---------------------------------------------------------------------------
14
36
  const args = process.argv.slice(2);
15
- const hasExplicitConfigArg = args.includes('--config');
16
- const localConfigPath = join(userCwd, 'eslint.config.ts');
17
- const sharedDefaultPath = resolve(
18
- __dirname,
19
- '../dist/adapters/eslint/config/eslint-shared-config.js',
20
- );
21
- const hasLocalConfig = existsSync(localConfigPath);
22
-
23
- // Determine effective config path
24
- const configPath = hasExplicitConfigArg
25
- ? args[args.indexOf('--config') + 1]
26
- : hasLocalConfig
27
- ? localConfigPath
28
- : sharedDefaultPath;
29
-
30
- // Helper: wrap spawn in a promise
31
- function runLint(command, args, cwd) {
32
- return new Promise((resolve, reject) => {
33
- const child = spawn(command, args, {
37
+
38
+ const hasFlag = (name) => args.includes(name);
39
+ const getFlagValue = (name) => {
40
+ const i = args.indexOf(name);
41
+ return i >= 0 ? args[i + 1] : undefined;
42
+ };
43
+ const firstNonFlag = () => args.find((a) => !a.startsWith('--'));
44
+
45
+ const hasExplicitConfig = hasFlag('--config');
46
+ const localConfigCandidates = [
47
+ 'eslint.config.ts', 'eslint.config.mts', 'eslint.config.cts',
48
+ 'eslint.config.mjs', 'eslint.config.cjs', 'eslint.config.js',
49
+ ];
50
+ const localConfigPath = localConfigCandidates
51
+ .map((f) => join(userCwd, f))
52
+ .find((p) => existsSync(p));
53
+
54
+ const sharedDefaultPath = resolve(__dirname, '../dist/adapters/eslint/config/eslint-shared-config.js');
55
+
56
+ const configPath = hasExplicitConfig ? getFlagValue('--config') : (localConfigPath ?? sharedDefaultPath);
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Node preload for TS configs
60
+ // ---------------------------------------------------------------------------
61
+ const needsTsx = /\.(?:mts|cts|ts)$/i.test(configPath || '');
62
+ const nodeMajor = Number(process.versions.node.split('.')[0]);
63
+
64
+ if (needsTsx) {
65
+ // Fail fast with a clear message if tsx isn't available
66
+ try {
67
+ require.resolve('tsx');
68
+ } catch {
69
+ logError('[manager-lint] A TypeScript eslint config was detected, but "tsx" is not installed.');
70
+ logError('Install it in @ovh-ux/manager-static-analysis-kit (or the invoking package) and retry.');
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ const nodePreload = needsTsx
76
+ ? (nodeMajor >= 20 ? ['--import', 'tsx'] : ['--loader', 'tsx'])
77
+ : [];
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Build final ESLint argv
81
+ // - If the user didnโ€™t pass --config, inject our chosen config
82
+ // ---------------------------------------------------------------------------
83
+ const forwarded = hasExplicitConfig ? args : ['--config', configPath, ...args];
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Runner
87
+ // ---------------------------------------------------------------------------
88
+ function run(procArgs) {
89
+ return new Promise((resolveCode) => {
90
+ const child = spawn(execPath, procArgs, {
91
+ cwd: userCwd,
34
92
  stdio: 'inherit',
35
- cwd,
36
- shell: true,
93
+ shell: false,
37
94
  });
38
-
39
95
  child.on('error', (err) => {
40
96
  logError('[manager-lint] Failed to run ESLint');
41
97
  logError(err);
42
- reject(1);
43
- });
44
-
45
- child.on('close', (code) => {
46
- resolve(code ?? 1);
98
+ resolveCode(1);
47
99
  });
100
+ child.on('close', (code) => resolveCode(code ?? 1));
48
101
  });
49
102
  }
50
103
 
51
- (async () => {
52
- // Handle --print-config
53
- if (args.includes('--print-config')) {
54
- const url = pathToFileURL(configPath).href;
55
- try {
56
- const mod = await import(url);
57
- const configContent = mod.default ?? mod;
58
- logInfo('[manager-lint] Loaded ESLint config from:', configPath);
59
- console.dir(configContent, { depth: null });
60
- } catch (err) {
61
- logError(`[manager-lint] Failed to load config at ${configPath}`);
62
- logError(err);
63
- process.exit(1);
64
- }
65
- process.exit(0);
66
- }
67
-
68
- // Construct full args
69
- const fullArgs =
70
- hasExplicitConfigArg || hasLocalConfig ? args : ['--config', sharedDefaultPath, ...args];
104
+ // ---------------------------------------------------------------------------
105
+ // --print-config (delegate to ESLint)
106
+ // ESLint requires a target file after --print-config. Use the first non-flag
107
+ // argument or default to "." if none was provided.
108
+ // ---------------------------------------------------------------------------
109
+ if (hasFlag('--print-config')) {
110
+ const target = firstNonFlag() ?? '.';
111
+ logInfo('[manager-lint] Using config:', configPath);
112
+ const code = await run([...nodePreload, eslintCli, '--config', configPath, '--print-config', target]);
113
+ process.exit(code);
114
+ }
71
115
 
72
- // Run ESLint using tsx and wait for completion
73
- const exitCode = await runLint('tsx', [eslintBin, ...fullArgs], userCwd);
74
- process.exit(exitCode);
75
- })();
116
+ // ---------------------------------------------------------------------------
117
+ // Normal run
118
+ // ---------------------------------------------------------------------------
119
+ const code = await run([...nodePreload, eslintCli, ...forwarded]);
120
+ process.exit(code);