@mf-toolkit/shared-inspector 0.3.1 → 0.4.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.
Files changed (66) hide show
  1. package/README.md +196 -130
  2. package/dist/cli/args.d.ts +5 -0
  3. package/dist/cli/args.d.ts.map +1 -0
  4. package/dist/cli/args.js +98 -0
  5. package/dist/cli/args.js.map +1 -0
  6. package/dist/cli/colorize-report.d.ts +6 -0
  7. package/dist/cli/colorize-report.d.ts.map +1 -0
  8. package/dist/cli/colorize-report.js +71 -0
  9. package/dist/cli/colorize-report.js.map +1 -0
  10. package/dist/cli/colors.d.ts +14 -0
  11. package/dist/cli/colors.d.ts.map +1 -0
  12. package/dist/cli/colors.js +26 -0
  13. package/dist/cli/colors.js.map +1 -0
  14. package/dist/cli/help.d.ts +2 -0
  15. package/dist/cli/help.d.ts.map +1 -0
  16. package/dist/cli/help.js +30 -0
  17. package/dist/cli/help.js.map +1 -0
  18. package/dist/cli/index.d.ts +8 -0
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +62 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/interactive.d.ts +8 -0
  23. package/dist/cli/interactive.d.ts.map +1 -0
  24. package/dist/cli/interactive.js +41 -0
  25. package/dist/cli/interactive.js.map +1 -0
  26. package/dist/cli/run-federation.d.ts +3 -0
  27. package/dist/cli/run-federation.d.ts.map +1 -0
  28. package/dist/cli/run-federation.js +41 -0
  29. package/dist/cli/run-federation.js.map +1 -0
  30. package/dist/cli/run-project.d.ts +5 -0
  31. package/dist/cli/run-project.d.ts.map +1 -0
  32. package/dist/cli/run-project.js +57 -0
  33. package/dist/cli/run-project.js.map +1 -0
  34. package/dist/cli/spinner.d.ts +10 -0
  35. package/dist/cli/spinner.d.ts.map +1 -0
  36. package/dist/cli/spinner.js +45 -0
  37. package/dist/cli/spinner.js.map +1 -0
  38. package/dist/cli/types.d.ts +17 -0
  39. package/dist/cli/types.d.ts.map +1 -0
  40. package/dist/cli/types.js +2 -0
  41. package/dist/cli/types.js.map +1 -0
  42. package/dist/cli.d.ts +3 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +13 -0
  45. package/dist/cli.js.map +1 -0
  46. package/dist/index.d.ts +2 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/reporter/diagnostics.d.ts +23 -0
  51. package/dist/reporter/diagnostics.d.ts.map +1 -0
  52. package/dist/reporter/diagnostics.js +138 -0
  53. package/dist/reporter/diagnostics.js.map +1 -0
  54. package/dist/reporter/format-federation-report.d.ts +11 -5
  55. package/dist/reporter/format-federation-report.d.ts.map +1 -1
  56. package/dist/reporter/format-federation-report.js +78 -37
  57. package/dist/reporter/format-federation-report.js.map +1 -1
  58. package/dist/reporter/format-report.d.ts +14 -7
  59. package/dist/reporter/format-report.d.ts.map +1 -1
  60. package/dist/reporter/format-report.js +67 -38
  61. package/dist/reporter/format-report.js.map +1 -1
  62. package/dist/reporter/scoring.d.ts +28 -0
  63. package/dist/reporter/scoring.d.ts.map +1 -0
  64. package/dist/reporter/scoring.js +74 -0
  65. package/dist/reporter/scoring.js.map +1 -0
  66. package/package.json +5 -2
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # `@mf-toolkit/shared-inspector`
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@mf-toolkit/shared-inspector?color=CB3837&logo=npm)](https://www.npmjs.com/package/@mf-toolkit/shared-inspector)
4
- [![license](https://img.shields.io/badge/license-MIT-blue)](https://github.com/zvitaly7/mf-toolkit/blob/main/LICENSE)
5
- [![node](https://img.shields.io/badge/node-%E2%89%A518-339933?logo=node.js)](https://nodejs.org)
4
+ [![license](https://img.shields.io/npm/l/@mf-toolkit/shared-inspector?color=blue)](https://github.com/zvitaly7/mf-toolkit/blob/main/LICENSE)
5
+ [![node](https://img.shields.io/node/v/@mf-toolkit/shared-inspector?color=339933&logo=node.js)](https://nodejs.org)
6
6
 
7
- > **v0.3.0** published to npm. 252 tests. API is stable; minor changes possible before v1.0.
7
+ **Stop debugging Module Federation in production.**
8
8
 
9
- Build-time analyser for Module Federation `shared` dependencies. Two-phase architecture: **collect facts analyse facts**.
9
+ `shared` config breaks in silence — wrong versions ship, 10× React ends up in the bundle, singleton chains collapse, and teams get paged for "Invalid hook call" on Friday night. `shared-inspector` catches these mistakes at build time. Every finding comes with a risk score and a ready-to-paste fix.
10
10
 
11
11
  ## The problem
12
12
 
@@ -24,43 +24,171 @@ Existing tools (webpack-bundle-analyzer, source-map-explorer) show *what ended u
24
24
  npm install --save-dev @mf-toolkit/shared-inspector
25
25
  ```
26
26
 
27
+ ## CLI
28
+
29
+ The fastest way to get started — no config file, no webpack required:
30
+
31
+ ```bash
32
+ # Analyse the current project (auto-reads name from package.json)
33
+ npx @mf-toolkit/shared-inspector
34
+
35
+ # Step-by-step interactive wizard — answers guide you through all options
36
+ npx @mf-toolkit/shared-inspector --interactive
37
+
38
+ # Pass options directly
39
+ npx @mf-toolkit/shared-inspector --source ./src --shared react,react-dom --fail-on mismatch
40
+
41
+ # Load shared config from a JSON file
42
+ npx @mf-toolkit/shared-inspector --shared ./shared-config.json --write-manifest
43
+
44
+ # Cross-MF federation analysis from saved manifests
45
+ npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
46
+ ```
47
+
48
+ ### Interactive wizard
49
+
50
+ ```
51
+ $ npx @mf-toolkit/shared-inspector --interactive
52
+
53
+ [MfSharedInspector] Interactive setup
54
+
55
+ Source directories to scan (default: ./src):
56
+ Scan depth — direct or local-graph (default: local-graph):
57
+ Shared packages — comma-separated names or path to .json (empty to skip): react,react-dom,mobx
58
+ Path to tsconfig.json for alias resolution (empty to skip):
59
+ Workspace packages to exclude, comma-separated (empty to skip):
60
+ Fail build on findings — mismatch / unused / any / none (default: none): mismatch
61
+ Write project-manifest.json? (y/N): n
62
+ ```
63
+
64
+ ### CLI reference
65
+
66
+ | Flag | Default | Description |
67
+ |------|---------|-------------|
68
+ | `--source, -s <dirs>` | `./src` | Source dirs to scan, comma-separated |
69
+ | `--depth <depth>` | `local-graph` | Scan depth: `direct` \| `local-graph` |
70
+ | `--shared <packages\|file>` | — | Comma-separated package names or path to `.json` config |
71
+ | `--tsconfig <path>` | — | tsconfig.json for path alias resolution |
72
+ | `--workspace-packages <pkgs>` | — | Comma-separated workspace packages to exclude |
73
+ | `--name <name>` | auto from `package.json` | Project name |
74
+ | `--fail-on <rule>` | — | Exit 1 when findings match: `mismatch` \| `unused` \| `any` |
75
+ | `--write-manifest` | `false` | Write `project-manifest.json` to output dir |
76
+ | `--output-dir <dir>` | `.` | Output directory for manifest |
77
+ | `--interactive, -i` | — | Launch step-by-step wizard |
78
+ | `--version, -v` | — | Print version and exit |
79
+ | `--help, -h` | — | Show help |
80
+
81
+ ## Terminal output
82
+
83
+ Each finding is rendered as a diagnostic card: what's wrong, what breaks at runtime, and a ready-to-paste fix.
84
+
85
+ ```
86
+ mf-inspector v0.4.0
87
+
88
+ ✓ Scanned 47 files
89
+
90
+ [MfSharedInspector] checkout (depth: local-graph, 47 files scanned)
91
+ ────────────────────────────────────────────────────────────
92
+
93
+ ⚠ Version Mismatch — react
94
+ configured: ^18.0.0 | installed: 17.0.2
95
+ → Risk: Invalid hook call, broken context across MFs
96
+ 💡 Fix:
97
+ shared: {
98
+ react: { singleton: true, requiredVersion: "^18.0.0" }
99
+ }
100
+
101
+ ✗ Unused Shared — lodash
102
+ 0 imports, shared without singleton
103
+ → Wastes bundle negotiation overhead with no benefit
104
+ 💡 Fix: Remove "lodash" from shared config
105
+
106
+ → Not Shared — mobx (12 imports in 8 files via re-export in src/shared/index.ts)
107
+ → Risk: Each MF gets its own MobX — observables and reactions won't sync between MFs
108
+ 💡 Fix:
109
+ shared: {
110
+ mobx: { singleton: true }
111
+ }
112
+
113
+ ────────────────────────────────────────────────────────────
114
+ Score: 62/100 🟠 RISKY
115
+
116
+ Issues:
117
+ 🔴 1 high — version mismatch
118
+ 🟠 1 medium — singleton gaps, duplicate libs
119
+ 🟡 1 low — over-sharing
120
+
121
+ Total: 12 shared, 10 used, 1 unused, 1 candidates, 1 mismatch, 0 eager risks
122
+ ```
123
+
124
+ Colors are auto-applied in TTY terminals and disabled in CI / piped output (`NO_COLOR` / `TERM=dumb` respected).
125
+
126
+ ## Risk scoring
127
+
128
+ Every report ends with a score out of 100:
129
+
130
+ | Severity | Penalty | Covers |
131
+ |----------|---------|--------|
132
+ | 🔴 HIGH | −20 each | Version mismatches, cross-MF version conflicts |
133
+ | 🟠 MEDIUM | −8 each | Singleton risks, eager risks, duplicate libs, host gaps |
134
+ | 🟡 LOW | −3 each | Unused shared packages, ghost shares |
135
+
136
+ | Score | Label |
137
+ |-------|-------|
138
+ | 90–100 | ✅ HEALTHY |
139
+ | 70–89 | 🟡 GOOD |
140
+ | 40–69 | 🟠 RISKY |
141
+ | 0–39 | 🔴 CRITICAL |
142
+
143
+ Use `scoreProjectReport` / `scoreFederationReport` programmatically to integrate with dashboards or custom CI gates:
144
+
145
+ ```typescript
146
+ import { analyzeProject, scoreProjectReport } from '@mf-toolkit/shared-inspector';
147
+
148
+ const report = analyzeProject(manifest);
149
+ const { score, label, high, medium, low } = scoreProjectReport(report);
150
+
151
+ if (score < 70) {
152
+ console.error(`Shared config score: ${score}/100 (${label})`);
153
+ process.exit(1);
154
+ }
155
+ ```
156
+
27
157
  ## Quick start
28
158
 
29
159
  ### Programmatic API (two-phase)
30
160
 
31
161
  ```typescript
32
- import { buildProjectManifest, analyzeProject } from '@mf-toolkit/shared-inspector';
162
+ import { buildProjectManifest, analyzeProject, formatReport } from '@mf-toolkit/shared-inspector';
33
163
 
34
164
  // Phase 1: collect facts
35
165
  const manifest = await buildProjectManifest({
36
166
  name: 'checkout',
37
167
  sourceDirs: ['./src'],
38
168
  sharedConfig: {
39
- react: { singleton: true, requiredVersion: '^19.0.0' },
40
- 'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
169
+ react: { singleton: true, requiredVersion: '^18.0.0' },
170
+ 'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
41
171
  lodash: {},
42
172
  },
43
- // depth: 'local-graph' ← default, follows barrel re-exports
44
- // tsconfigPath: './tsconfig.json' ← optional, resolves @alias/* imports
173
+ // depth: 'local-graph' ← default, follows barrel re-exports
174
+ // tsconfigPath: './tsconfig.json' ← optional, resolves @alias/* imports
45
175
  // workspacePackages: ['@my-org/*'] ← optional, excludes local monorepo packages
46
176
  });
47
177
 
48
178
  // Phase 2: analyse facts
49
- const report = analyzeProject(manifest, {
50
- alwaysShared: ['react', 'react-dom'],
51
- });
179
+ const report = analyzeProject(manifest);
52
180
 
53
- console.log(report.unused);
54
- // [{ package: 'lodash', singleton: false }]
181
+ console.log(report.mismatched);
182
+ // [{ package: 'react', configured: '^18.0.0', installed: '17.0.2' }]
55
183
 
56
184
  console.log(report.candidates);
57
185
  // [{ package: 'mobx', importCount: 12, via: 'reexport', files: ['src/shared/index.ts'] }]
58
186
 
59
- console.log(report.mismatched);
60
- // [{ package: 'react', configured: '^19.0.0', installed: '18.3.1' }]
187
+ console.log(report.unused);
188
+ // [{ package: 'lodash', singleton: false }]
61
189
 
62
- console.log(report.eagerRisks);
63
- // [{ package: 'react-dom' }] ← eager: true without singleton: true
190
+ // Human-readable output with risk descriptions and fix snippets
191
+ console.log(formatReport(report, { name: manifest.project.name }));
64
192
  ```
65
193
 
66
194
  ### Shortcut API
@@ -99,15 +227,6 @@ module.exports = {
99
227
  };
100
228
  ```
101
229
 
102
- Pass `sharedConfig` explicitly to override auto-extraction:
103
-
104
- ```typescript
105
- new MfSharedInspectorPlugin({
106
- sourceDirs: ['./src'],
107
- sharedConfig: { react: { singleton: true, requiredVersion: '^18.0.0' } },
108
- })
109
- ```
110
-
111
230
  ## Analysis depth
112
231
 
113
232
  | Depth | What it finds | Speed |
@@ -121,113 +240,62 @@ The difference matters when your project uses barrel files:
121
240
  // src/shared/index.ts
122
241
  export { observer } from 'mobx-react'; // re-export
123
242
  export { makeAutoObservable } from 'mobx'; // re-export
124
- ```
125
243
 
126
- ```ts
127
244
  // src/app.tsx
128
245
  import { observer } from './shared'; // relative import — direct mode stops here
129
246
  ```
130
247
 
131
- - **`depth: 'direct'`** scans `app.tsx`, sees `./shared` (relative) → skips. `mobx` not found.
132
- - **`depth: 'local-graph'`** follows `./shared` → `shared/index.ts` → finds `mobx` and `mobx-react` via re-export.
248
+ - **`depth: 'direct'`** sees `./shared` (relative) → skips. `mobx` not found.
249
+ - **`depth: 'local-graph'`** follows `./shared` → finds `mobx` and `mobx-react` via re-export.
133
250
 
134
251
  ## TypeScript path aliases
135
252
 
136
- When your project uses `paths` in `tsconfig.json`, pass `tsconfigPath` so the traverser follows aliased imports into local files instead of treating them as external packages:
137
-
138
253
  ```typescript
139
254
  // tsconfig.json
140
- { "compilerOptions": { "baseUrl": ".", "paths": { "@components/*": ["src/components/*"] } } }
255
+ { "compilerOptions": { "paths": { "@components/*": ["src/components/*"] } } }
141
256
 
142
- // src/app.tsx
143
- import { Button } from '@components/Button'; // ← followed as local file, not external package
144
- ```
145
-
146
- ```typescript
147
257
  await buildProjectManifest({
148
258
  sourceDirs: ['./src'],
149
259
  tsconfigPath: './tsconfig.json', // enables alias resolution
150
260
  });
151
261
  ```
152
262
 
153
- Without `tsconfigPath`, `@components/Button` is treated as an external package name and packages imported inside it are invisible in local-graph mode.
154
-
155
- ## Workspace packages
156
-
157
- In a monorepo where local packages import each other, use `workspacePackages` to prevent internal packages from appearing in `resolvedPackages`:
158
-
159
- ```typescript
160
- await buildProjectManifest({
161
- sourceDirs: ['./src'],
162
- workspacePackages: ['@my-org/design-system', '@my-org/*'],
163
- });
164
- ```
165
-
166
- ## Terminal output
167
-
168
- ```
169
- [MfSharedInspector] checkout (depth: local-graph, 47 files scanned)
170
-
171
- Version mismatch (sharing silently broken):
172
- ⚠ react — requires ^19.0.0, installed 18.3.1
173
-
174
- Unused shared (safe to remove):
175
- ✗ lodash — 0 imports, shared without singleton
176
- ✗ @tanstack/react-query — 0 imports, shared as singleton
177
-
178
- Candidates (consider adding to shared):
179
- → mobx (12 imports in 8 files, via re-export in src/shared/index.ts)
180
- → react-router-dom (6 imports in 4 files)
181
-
182
- Singleton risks (add singleton: true):
183
- ⚠ react-router-dom — manages global state, singleton: true recommended
184
-
185
- Eager risks (add singleton: true or remove eager: true):
186
- ⚠ react-dom — eager: true without singleton: true, risk of duplicate instances
187
-
188
- Total: 12 shared, 10 used, 2 unused, 2 candidates, 1 mismatch, 1 eager risks
189
- ```
263
+ Without `tsconfigPath`, `@components/Button` is treated as an external package and packages imported inside it are invisible in `local-graph` mode.
190
264
 
191
265
  ## CI pipeline: project → federation
192
266
 
193
- Each microfrontend generates a manifest as a build artifact:
267
+ Each microfrontend generates a manifest as a build artifact, then they're aggregated for cross-team analysis:
194
268
 
195
269
  ```yaml
270
+ # .github/workflows/build.yml
196
271
  jobs:
197
272
  build-checkout:
198
273
  steps:
199
274
  - run: npm run build # MfSharedInspectorPlugin writes project-manifest.json
200
275
  - uses: actions/upload-artifact@v4
201
- with:
202
- name: manifest-checkout
203
- path: project-manifest.json
276
+ with: { name: manifest-checkout, path: project-manifest.json }
204
277
  ```
205
278
 
206
- Each MF manifest can then be aggregated for cross-team analysis:
207
-
208
279
  ```typescript
209
- import { analyzeFederation, formatFederationReport } from '@mf-toolkit/shared-inspector';
280
+ import { analyzeFederation, formatFederationReport, scoreFederationReport } from '@mf-toolkit/shared-inspector';
210
281
 
211
282
  const report = analyzeFederation([checkoutManifest, catalogManifest, cartManifest]);
212
- console.log(formatFederationReport(report));
213
- ```
283
+ const { score, label } = scoreFederationReport(report);
214
284
 
285
+ console.log(formatFederationReport(report));
286
+ // ⚠ Version Conflict — react
287
+ // checkout: ^17.0.0
288
+ // catalog: ^18.0.0
289
+ // → Risk: MF singleton negotiation will silently load wrong version → Invalid hook call
290
+ // 💡 Fix: shared: { react: { singleton: true, requiredVersion: "^18.0.0" } }
291
+ //
292
+ // Score: 60/100 🟠 RISKY
215
293
  ```
216
- [MfSharedInspector] federation analysis (3 MFs)
217
-
218
- Version conflicts (singleton negotiation will fail):
219
- ⚠ react — checkout: ^17.0.0, catalog: ^18.0.0
220
294
 
221
- Singleton mismatches (add singleton: true to all MFs):
222
- ⚠ mobx — singleton in [checkout], not singleton in [catalog, cart]
295
+ Or use the CLI directly:
223
296
 
224
- Host gaps (add to shared — each MF bundles its own copy):
225
- axios used by [checkout, catalog], not in shared
226
-
227
- Ghost shares (remove from shared — no other MF benefits):
228
- ✗ lodash — shared only by cart, unused by all other MFs
229
-
230
- Total: 3 MFs, 1 version conflicts, 1 singleton mismatches, 1 host gaps, 1 ghost shares
297
+ ```bash
298
+ npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
231
299
  ```
232
300
 
233
301
  ## API reference
@@ -255,6 +323,20 @@ console.log(formatFederationReport(report));
255
323
  | `additionalCandidates` | `string[]` | `[]` | Extend built-in candidates list |
256
324
  | `additionalSingletonRisks` | `string[]` | `[]` | Extend built-in singleton-risk list |
257
325
 
326
+ ### `scoreProjectReport(report)` / `scoreFederationReport(report)`
327
+
328
+ Returns a `RiskScore`:
329
+
330
+ ```typescript
331
+ interface RiskScore {
332
+ score: number; // 0–100, higher is better
333
+ label: 'HEALTHY' | 'GOOD' | 'RISKY' | 'CRITICAL';
334
+ high: number; // count of high-severity findings
335
+ medium: number; // count of medium-severity findings
336
+ low: number; // count of low-severity findings
337
+ }
338
+ ```
339
+
258
340
  ### `analyzeFederation(manifests, options?)`
259
341
 
260
342
  Accepts N `ProjectManifest` objects (one per microfrontend) and returns a `FederationReport`.
@@ -263,20 +345,12 @@ Accepts N `ProjectManifest` objects (one per microfrontend) and returns a `Feder
263
345
  |--------|------|---------|-------------|
264
346
  | `alwaysShared` | `string[]` | `['react','react-dom']` | Exclude from ghost/gap detection |
265
347
 
266
- ### `formatFederationReport(report)`
267
-
268
- Formats a `FederationReport` as a human-readable terminal string.
269
-
270
348
  ### `MfSharedInspectorPlugin` options
271
349
 
272
350
  Extends all `buildProjectManifest` options (except `name`, auto-resolved from compiler) plus:
273
351
 
274
352
  | Option | Type | Default | Description |
275
353
  |--------|------|---------|-------------|
276
- | `sourceDirs` | `string[]` | — | Directories to scan |
277
- | `sharedConfig` | `Record<string, SharedDepConfig>` | auto-extracted | Override auto-extraction from `ModuleFederationPlugin` |
278
- | `tsconfigPath` | `string` | `undefined` | tsconfig.json for path alias resolution |
279
- | `workspacePackages` | `string[]` | `[]` | Local monorepo packages to exclude |
280
354
  | `warn` | `boolean` | `true` | Print findings to console |
281
355
  | `failOn` | `'mismatch' \| 'unused' \| 'any'` | `undefined` | Fail the build when findings match |
282
356
  | `writeManifest` | `boolean` | `false` | Write `project-manifest.json` to `outputDir` |
@@ -287,37 +361,29 @@ Extends all `buildProjectManifest` options (except `name`, auto-resolved from co
287
361
 
288
362
  ### Per-project (`analyzeProject`)
289
363
 
290
- | Category | Type | Description |
291
- |----------|------|-------------|
292
- | `mismatched` | Deterministic | `requiredVersion` doesn't satisfy installed version |
293
- | `unused` | Deterministic* | In `shared` config but not observed in scanned sources |
294
- | `candidates` | Heuristic | Observed packages not in `shared` that are typically shared |
295
- | `singletonRisks` | Heuristic | Global-state packages shared without `singleton: true` |
296
- | `eagerRisks` | Heuristic | `eager: true` without `singleton: true` risk of duplicate instances |
297
-
298
- *Within the visibility of the chosen depth.*
364
+ | Category | Severity | Description |
365
+ |----------|----------|-------------|
366
+ | `mismatched` | 🔴 HIGH | `requiredVersion` doesn't satisfy installed version |
367
+ | `singletonRisks` | 🟠 MEDIUM | Global-state packages shared without `singleton: true` |
368
+ | `eagerRisks` | 🟠 MEDIUM | `eager: true` without `singleton: true` |
369
+ | `candidates` | 🟠 MEDIUM | Used packages missing from `shared` (each MF bundles own copy) |
370
+ | `unused` | 🟡 LOW | In `shared` config but not observed in scanned sources |
299
371
 
300
372
  ### Cross-MF (`analyzeFederation`)
301
373
 
302
- | Category | Type | Description |
303
- |----------|------|-------------|
304
- | `versionConflicts` | Deterministic | `requiredVersion` ranges across MFs have no overlap |
305
- | `singletonMismatches` | Deterministic | `singleton: true` in some MFs, absent in others |
306
- | `hostGaps` | Heuristic | Package used by 2+ MFs but not declared in `shared` by anyone |
307
- | `ghostShares` | Heuristic | Package in `shared` of one MF, unused/unshared by all others |
374
+ | Category | Severity | Description |
375
+ |----------|----------|-------------|
376
+ | `versionConflicts` | 🔴 HIGH | `requiredVersion` ranges across MFs have no overlap |
377
+ | `singletonMismatches` | 🟠 MEDIUM | `singleton: true` in some MFs, absent in others |
378
+ | `hostGaps` | 🟠 MEDIUM | Package used by 2+ MFs but not declared in `shared` by anyone |
379
+ | `ghostShares` | 🟡 LOW | Package in `shared` of one MF, unused/unshared by all others |
308
380
 
309
381
  ## Known limitations
310
382
 
311
- - **TypeScript path aliases without `tsconfigPath`**: aliased imports are treated as external package names. Pass `tsconfigPath` to resolve them correctly.
383
+ - **TypeScript path aliases without `tsconfigPath`**: aliased imports are treated as external package names.
312
384
  - **Dynamic imports with variables** (`import(moduleName)`): not analysed — requires runtime information.
313
- - **Exact tsconfig alias patterns** (non-wildcard, e.g. `"@root": ["."]`): not supported, only `"@alias/*"` wildcard form.
314
- - **Subclassed `ModuleFederationPlugin`**: auto-extraction matches by constructor name. A custom subclass (`class MyMFP extends ModuleFederationPlugin`) won't be detected — pass `sharedConfig` explicitly in that case.
315
-
316
- ## Demo
317
-
318
- ```bash
319
- npx tsx packages/shared-inspector/demo/run.ts
320
- ```
385
+ - **Exact tsconfig alias patterns** (non-wildcard): only `"@alias/*"` wildcard form is supported.
386
+ - **Subclassed `ModuleFederationPlugin`**: auto-extraction matches by constructor name — pass `sharedConfig` explicitly for custom subclasses.
321
387
 
322
388
  ## License
323
389
 
@@ -0,0 +1,5 @@
1
+ import type { CliArgs } from './types.js';
2
+ import type { SharedDepConfig } from '../types.js';
3
+ export declare function parseArgs(argv: string[]): CliArgs;
4
+ export declare function parseSharedValue(value: string): Record<string, SharedDepConfig>;
5
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAuFjD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAS/E"}
@@ -0,0 +1,98 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ const VALID_DEPTHS = new Set(['direct', 'local-graph']);
4
+ const VALID_FAIL_ON = new Set(['mismatch', 'unused', 'any']);
5
+ export function parseArgs(argv) {
6
+ const args = {
7
+ command: 'project',
8
+ interactive: false,
9
+ sourceDirs: [],
10
+ depth: 'local-graph',
11
+ workspacePackages: [],
12
+ writeManifest: false,
13
+ outputDir: '.',
14
+ manifestFiles: [],
15
+ };
16
+ if (argv[0] === 'federation') {
17
+ args.command = 'federation';
18
+ for (let i = 1; i < argv.length; i++) {
19
+ if (argv[i] === '--help' || argv[i] === '-h') {
20
+ args.command = 'help';
21
+ break;
22
+ }
23
+ if (!argv[i].startsWith('-')) {
24
+ args.manifestFiles.push(argv[i]);
25
+ }
26
+ }
27
+ return args;
28
+ }
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const arg = argv[i];
31
+ switch (arg) {
32
+ case '--help':
33
+ case '-h':
34
+ args.command = 'help';
35
+ break;
36
+ case '--version':
37
+ case '-v':
38
+ args.command = 'version';
39
+ break;
40
+ case '--interactive':
41
+ case '-i':
42
+ args.interactive = true;
43
+ break;
44
+ case '--source':
45
+ case '-s':
46
+ args.sourceDirs.push(...(argv[++i] ?? '').split(',').filter(Boolean));
47
+ break;
48
+ case '--depth': {
49
+ const val = argv[++i] ?? '';
50
+ if (!VALID_DEPTHS.has(val)) {
51
+ throw new Error(`Invalid --depth value "${val}". Expected: direct | local-graph`);
52
+ }
53
+ args.depth = val;
54
+ break;
55
+ }
56
+ case '--shared':
57
+ args.sharedConfig = parseSharedValue(argv[++i] ?? '');
58
+ break;
59
+ case '--tsconfig':
60
+ args.tsconfigPath = argv[++i];
61
+ break;
62
+ case '--workspace-packages':
63
+ args.workspacePackages.push(...(argv[++i] ?? '').split(',').filter(Boolean));
64
+ break;
65
+ case '--fail-on': {
66
+ const val = argv[++i] ?? '';
67
+ if (!VALID_FAIL_ON.has(val)) {
68
+ throw new Error(`Invalid --fail-on value "${val}". Expected: mismatch | unused | any`);
69
+ }
70
+ args.failOn = val;
71
+ break;
72
+ }
73
+ case '--write-manifest':
74
+ args.writeManifest = true;
75
+ break;
76
+ case '--output-dir':
77
+ args.outputDir = argv[++i] ?? '.';
78
+ break;
79
+ case '--name':
80
+ args.name = argv[++i];
81
+ break;
82
+ }
83
+ }
84
+ if (!args.interactive && args.sourceDirs.length === 0) {
85
+ args.sourceDirs = ['./src'];
86
+ }
87
+ return args;
88
+ }
89
+ export function parseSharedValue(value) {
90
+ if (!value)
91
+ return {};
92
+ if (value.endsWith('.json')) {
93
+ const content = readFileSync(resolve(process.cwd(), value), 'utf-8');
94
+ return JSON.parse(content);
95
+ }
96
+ return Object.fromEntries(value.split(',').filter(Boolean).map((p) => [p.trim(), {}]));
97
+ }
98
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Adds ANSI colors to a formatted report string.
3
+ * Returns the original string unchanged when stdout is not a TTY.
4
+ */
5
+ export declare function colorizeReport(text: string): string;
6
+ //# sourceMappingURL=colorize-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colorize-report.d.ts","sourceRoot":"","sources":["../../src/cli/colorize-report.ts"],"names":[],"mappings":"AAmDA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD"}
@@ -0,0 +1,71 @@
1
+ // ─── Report colorizer ─────────────────────────────────────────────────────────
2
+ //
3
+ // Post-processes the plain-text output of formatReport / formatFederationReport,
4
+ // adding ANSI colors line-by-line.
5
+ //
6
+ // Separated from the reporters so the library API stays color-free
7
+ // (useful for programmatic consumers, JSON pipelines, etc.)
8
+ import { isTTY, bold, dim, red, yellow, green, cyan, gray, combine } from './colors.js';
9
+ // ─── Line matchers ────────────────────────────────────────────────────────────
10
+ function colorizeLine(line) {
11
+ // ── Issue titles ────────────────────────────────────────────────────────────
12
+ if (/^⚠ Version (Mismatch|Conflict)/.test(line))
13
+ return combine(bold, red)(line);
14
+ if (/^⚠ (Singleton Risk|Singleton Mismatch|Eager Risk)/.test(line))
15
+ return combine(bold, yellow)(line);
16
+ if (/^→ (Not Shared|Host Gap)/.test(line))
17
+ return combine(bold, cyan)(line);
18
+ if (/^✗ (Unused Shared|Ghost Share)/.test(line))
19
+ return dim(line);
20
+ // ── Card detail lines ───────────────────────────────────────────────────────
21
+ if (/→ Risk:/.test(line))
22
+ return line.replace('→ Risk:', red('→ Risk:'));
23
+ if (/💡 Fix:/.test(line))
24
+ return green(line);
25
+ if (/Remove ".+" from/.test(line))
26
+ return green(line);
27
+ // ── Score block ─────────────────────────────────────────────────────────────
28
+ if (/^Score:\s+\d+\/100/.test(line)) {
29
+ if (line.includes('CRITICAL'))
30
+ return combine(bold, red)(line);
31
+ if (line.includes('RISKY'))
32
+ return combine(bold, yellow)(line);
33
+ if (line.includes('GOOD'))
34
+ return combine(bold, cyan)(line);
35
+ if (line.includes('HEALTHY'))
36
+ return combine(bold, green)(line);
37
+ return bold(line);
38
+ }
39
+ // ── Section markers ─────────────────────────────────────────────────────────
40
+ if (/^\[MfSharedInspector\]/.test(line))
41
+ return bold(line);
42
+ if (/^[─]{10,}/.test(line))
43
+ return gray(line);
44
+ if (/^Total:/.test(line))
45
+ return dim(line);
46
+ if (/^Issues:/.test(line))
47
+ return bold(line);
48
+ // ── Score issue rows ─────────────────────────────────────────────────────────
49
+ if (/🔴/.test(line))
50
+ return line; // emoji already colored in supported terminals
51
+ if (/🟠/.test(line))
52
+ return line;
53
+ if (/🟡/.test(line))
54
+ return line;
55
+ // ── Clean / no-issues ────────────────────────────────────────────────────────
56
+ if (/✓\s+No issues/.test(line))
57
+ return combine(bold, green)(line);
58
+ if (/✓\s+No federation/.test(line))
59
+ return combine(bold, green)(line);
60
+ return line;
61
+ }
62
+ /**
63
+ * Adds ANSI colors to a formatted report string.
64
+ * Returns the original string unchanged when stdout is not a TTY.
65
+ */
66
+ export function colorizeReport(text) {
67
+ if (!isTTY)
68
+ return text;
69
+ return text.split('\n').map(colorizeLine).join('\n');
70
+ }
71
+ //# sourceMappingURL=colorize-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colorize-report.js","sourceRoot":"","sources":["../../src/cli/colorize-report.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,EAAE;AACF,iFAAiF;AACjF,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,4DAA4D;AAE5D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExF,iFAAiF;AAEjF,SAAS,YAAY,CAAC,IAAY;IAChC,+EAA+E;IAC/E,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACnF,IAAI,oDAAoD,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;IACxG,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAS,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;IACpF,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IAEpE,+EAA+E;IAC/E,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAkB,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IACzF,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAkB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7D,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAS,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7D,+EAA+E;IAC/E,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,OAAO,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAK,OAAO,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;QAClE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAM,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAG,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,+EAA+E;IAC/E,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAI,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAiB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAmB,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5D,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAkB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IAE7D,gFAAgF;IAChF,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,IAAI,CAAC,CAAG,+CAA+C;IACpF,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,IAAI,CAAC;IAClC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,IAAI,CAAC;IAElC,gFAAgF;IAChF,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAG,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IAEtE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC"}