@mf-toolkit/shared-inspector 0.3.2 → 0.4.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.
Files changed (66) hide show
  1. package/README.md +320 -156
  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 +1 -0
  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 +8 -3
package/README.md CHANGED
@@ -4,18 +4,95 @@
4
4
  [![license](https://img.shields.io/npm/l/@mf-toolkit/shared-inspector?color=blue)](https://github.com/zvitaly7/mf-toolkit/blob/main/LICENSE)
5
5
  [![node](https://img.shields.io/node/v/@mf-toolkit/shared-inspector?color=339933&logo=node.js)](https://nodejs.org)
6
6
 
7
- Build-time analyser for Module Federation `shared` dependencies. Two-phase architecture: **collect facts → analyse facts**.
7
+ **Stop debugging Module Federation in production.**
8
+
9
+ `shared` config breaks in silence — wrong versions ship, duplicate React copies can end up in the bundle, singleton negotiation fails, 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.
8
10
 
9
11
  ## The problem
10
12
 
11
13
  Module Federation teams manually manage `shared` config and make three kinds of mistakes:
12
14
 
13
15
  - **Over-sharing** — packages listed in `shared` that the microfrontend never imports. Creates artificial version coupling between independent teams.
14
- - **Under-sharing** — packages used by both host and remote but missing from `shared`. Each microfrontend bundles its own copy (10× React = 10× 130 KB).
16
+ - **Under-sharing** — packages used by both host and remote but missing from `shared`. Each microfrontend may bundle its own copy (e.g. multiple React instances, each ~130 KB).
15
17
  - **Version mismatch** — `requiredVersion` doesn't match the installed version. Module Federation silently falls back to a local bundle. For packages with global state (React, styled-components) this causes "Invalid hook call" in production.
16
18
 
17
19
  Existing tools (webpack-bundle-analyzer, source-map-explorer) show *what ended up in the bundle*, not *why shared config is suboptimal*. Different questions.
18
20
 
21
+ ## Why not bundle analyzer?
22
+
23
+ Bundle analyzers (webpack-bundle-analyzer, source-map-explorer, stats.json inspection) answer a different question: *what is in the output?* They are useful for auditing final bundle size, but they don't model Module Federation's shared dependency negotiation.
24
+
25
+ | Question | Bundle analyzer | shared-inspector |
26
+ |----------|----------------|-----------------|
27
+ | Which packages are large? | ✅ | — |
28
+ | Is React duplicated across MFs? | Visible after the fact | ✅ Detected before build ships |
29
+ | Is `requiredVersion` out of sync with the installed version? | ✗ | ✅ |
30
+ | Is a package marked `singleton` in one MF but not another? | ✗ | ✅ |
31
+ | Which packages are declared `shared` but never imported? | ✗ | ✅ |
32
+ | Which used packages are missing from `shared` entirely? | ✗ | ✅ |
33
+ | Cross-MF version conflicts across teams? | ✗ | ✅ via federation manifests |
34
+
35
+ In short: bundle analyzers are useful for post-build inspection. `shared-inspector` is focused on the `shared` config itself — catching misconfiguration at build time and explaining what the runtime consequences would be.
36
+
37
+ ## Example
38
+
39
+ A `shell` host app (React 18) and a `checkout` remote have been developed by separate teams. Their `shared` configs have drifted:
40
+
41
+ ```js
42
+ // shell — webpack.config.js
43
+ shared: {
44
+ react: { singleton: true, requiredVersion: '^18.2.0' },
45
+ 'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
46
+ zustand: { singleton: true },
47
+ }
48
+
49
+ // checkout — webpack.config.js
50
+ shared: {
51
+ react: { singleton: true, requiredVersion: '^17.0.2' }, // ← stale version
52
+ 'react-dom': { singleton: true, requiredVersion: '^17.0.2' },
53
+ lodash: {}, // ← never imported
54
+ // zustand: missing — checkout imports it, but it's not in shared
55
+ }
56
+ ```
57
+
58
+ Running `npx @mf-toolkit/shared-inspector` in the `checkout` project:
59
+
60
+ ```
61
+ [MfSharedInspector] checkout (depth: local-graph, 34 files scanned)
62
+ ────────────────────────────────────────────────────────────
63
+
64
+ ⚠ Version Mismatch — react
65
+ configured: ^17.0.2 | installed: 17.0.2
66
+ → Risk: Invalid hook call, broken context across MFs
67
+ 💡 Fix:
68
+ shared: {
69
+ react: { singleton: true, requiredVersion: "^18.2.0" }
70
+ }
71
+
72
+ → Not Shared — zustand (8 imports in 5 files)
73
+ → Risk: Each MF may get its own store instance — state changes may not propagate across MFs
74
+ 💡 Fix:
75
+ shared: {
76
+ zustand: { singleton: true }
77
+ }
78
+
79
+ ✗ Unused Shared — lodash
80
+ 0 imports, shared without singleton
81
+ → Wastes bundle negotiation overhead with no benefit
82
+ 💡 Fix: Remove "lodash" from shared config
83
+
84
+ ────────────────────────────────────────────────────────────
85
+ Score: 69/100 🟠 RISKY
86
+ ```
87
+
88
+ **After manually updating the config based on the suggestions above** — `react` version aligned, `zustand` added to `shared`, `lodash` removed:
89
+
90
+ ```
91
+ Score: 100/100 ✅ HEALTHY
92
+ ```
93
+
94
+ The cross-team federation report also clears: `shell` and `checkout` now negotiate a single React instance and a single Zustand store at runtime.
95
+
19
96
  ## Installation
20
97
 
21
98
  ```bash
@@ -24,58 +101,85 @@ npm install --save-dev @mf-toolkit/shared-inspector
24
101
 
25
102
  ## Quick start
26
103
 
27
- ### Programmatic API (two-phase)
104
+ Run against any MF project — no config file needed:
28
105
 
29
- ```typescript
30
- import { buildProjectManifest, analyzeProject } from '@mf-toolkit/shared-inspector';
106
+ ```bash
107
+ npx @mf-toolkit/shared-inspector
108
+ ```
31
109
 
32
- // Phase 1: collect facts
33
- const manifest = await buildProjectManifest({
34
- name: 'checkout',
35
- sourceDirs: ['./src'],
36
- sharedConfig: {
37
- react: { singleton: true, requiredVersion: '^19.0.0' },
38
- 'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
39
- lodash: {},
40
- },
41
- // depth: 'local-graph' ← default, follows barrel re-exports
42
- // tsconfigPath: './tsconfig.json' ← optional, resolves @alias/* imports
43
- // workspacePackages: ['@my-org/*'] ← optional, excludes local monorepo packages
44
- });
110
+ The tool scans `./src`, reads installed versions from `package.json`, and prints a diagnostic report. Each finding includes what's wrong, what breaks at runtime, and a ready-to-paste fix:
45
111
 
46
- // Phase 2: analyse facts
47
- const report = analyzeProject(manifest, {
48
- alwaysShared: ['react', 'react-dom'],
49
- });
112
+ ```
113
+ mf-inspector v0.4.0
50
114
 
51
- console.log(report.unused);
52
- // [{ package: 'lodash', singleton: false }]
115
+ ✓ Scanned 47 files
53
116
 
54
- console.log(report.candidates);
55
- // [{ package: 'mobx', importCount: 12, via: 'reexport', files: ['src/shared/index.ts'] }]
117
+ [MfSharedInspector] checkout (depth: local-graph, 47 files scanned)
118
+ ────────────────────────────────────────────────────────────
119
+
120
+ ⚠ Version Mismatch — react
121
+ configured: ^18.0.0 | installed: 17.0.2
122
+ → Risk: Invalid hook call, broken context across MFs
123
+ 💡 Fix:
124
+ shared: {
125
+ react: { singleton: true, requiredVersion: "^18.0.0" }
126
+ }
127
+
128
+ ✗ Unused Shared — lodash
129
+ 0 imports, shared without singleton
130
+ → Wastes bundle negotiation overhead with no benefit
131
+ 💡 Fix: Remove "lodash" from shared config
132
+
133
+ → Not Shared — mobx (12 imports in 8 files via re-export in src/shared/index.ts)
134
+ → Risk: Each MF may get its own MobX instance — observables and reactions can fail to sync between MFs
135
+ 💡 Fix:
136
+ shared: {
137
+ mobx: { singleton: true }
138
+ }
139
+
140
+ ────────────────────────────────────────────────────────────
141
+ Score: 62/100 🟠 RISKY
142
+
143
+ Issues:
144
+ 🔴 1 high — version mismatch
145
+ 🟠 1 medium — singleton gaps, duplicate libs
146
+ 🟡 1 low — over-sharing
147
+
148
+ Total: 12 shared, 10 used, 1 unused, 1 candidates, 1 mismatch, 0 eager risks
149
+ ```
56
150
 
57
- console.log(report.mismatched);
58
- // [{ package: 'react', configured: '^19.0.0', installed: '18.3.1' }]
151
+ Colors are auto-applied in TTY terminals and disabled in CI / piped output (`NO_COLOR` / `TERM=dumb` respected).
59
152
 
60
- console.log(report.eagerRisks);
61
- // [{ package: 'react-dom' }] ← eager: true without singleton: true
62
- ```
153
+ ### Risk scoring
63
154
 
64
- ### Shortcut API
155
+ Every report ends with a score out of 100:
65
156
 
66
- ```typescript
67
- import { inspect } from '@mf-toolkit/shared-inspector';
157
+ | Severity | Penalty | Covers |
158
+ |----------|---------|--------|
159
+ | 🔴 HIGH | −20 each | Version mismatches, cross-MF version conflicts |
160
+ | 🟠 MEDIUM | −8 each | Singleton risks, eager risks, duplicate libs, host gaps |
161
+ | 🟡 LOW | −3 each | Unused shared packages, ghost shares |
68
162
 
69
- const report = await inspect({
70
- name: 'checkout',
71
- sourceDirs: ['./src'],
72
- sharedConfig: { /* ... */ },
73
- });
74
- ```
163
+ | Score | Label |
164
+ |-------|-------|
165
+ | 90–100 | ✅ HEALTHY |
166
+ | 70–89 | 🟡 GOOD |
167
+ | 40–69 | 🟠 RISKY |
168
+ | 0–39 | 🔴 CRITICAL |
75
169
 
76
- ### Webpack plugin
170
+ ## CI mode
77
171
 
78
- `sharedConfig` is optional the plugin auto-extracts it from `ModuleFederationPlugin` when not provided:
172
+ Integrate into build pipelines to fail on findings, gate on score, or emit manifests for later federation analysis.
173
+
174
+ ### Failing the build
175
+
176
+ Use `--fail-on` to exit with code 1 when specific findings are detected:
177
+
178
+ ```bash
179
+ npx @mf-toolkit/shared-inspector --source ./src --shared react,react-dom --fail-on mismatch
180
+ ```
181
+
182
+ With the webpack plugin:
79
183
 
80
184
  ```typescript
81
185
  import { MfSharedInspectorPlugin } from '@mf-toolkit/shared-inspector/webpack';
@@ -90,144 +194,206 @@ module.exports = {
90
194
  // sharedConfig not needed — auto-extracted from ModuleFederationPlugin above
91
195
  new MfSharedInspectorPlugin({
92
196
  sourceDirs: ['./src'],
197
+ failOn: 'mismatch', // 'mismatch' | 'unused' | 'any'
93
198
  warn: true,
94
- writeManifest: true, // writes project-manifest.json for CI aggregation
95
199
  }),
96
200
  ],
97
201
  };
98
202
  ```
99
203
 
100
- Pass `sharedConfig` explicitly to override auto-extraction:
204
+ ### Gating on score
205
+
206
+ Use `scoreProjectReport` programmatically to set custom thresholds:
101
207
 
102
208
  ```typescript
103
- new MfSharedInspectorPlugin({
104
- sourceDirs: ['./src'],
105
- sharedConfig: { react: { singleton: true, requiredVersion: '^18.0.0' } },
106
- })
107
- ```
209
+ import { analyzeProject, scoreProjectReport } from '@mf-toolkit/shared-inspector';
108
210
 
109
- ## Analysis depth
211
+ const report = analyzeProject(manifest);
212
+ const { score, label, high, medium, low } = scoreProjectReport(report);
110
213
 
111
- | Depth | What it finds | Speed |
112
- |-------|--------------|-------|
113
- | `'direct'` | Explicit `import` / `require` statements | Fast (ms) |
114
- | `'local-graph'` *(default)* | + packages reachable via barrel re-exports and local wrappers | Slower (seconds) |
214
+ if (score < 70) {
215
+ console.error(`Shared config score: ${score}/100 (${label})`);
216
+ process.exit(1);
217
+ }
218
+ ```
115
219
 
116
- The difference matters when your project uses barrel files:
220
+ ### Writing manifests for federation analysis
117
221
 
118
- ```ts
119
- // src/shared/index.ts
120
- export { observer } from 'mobx-react'; // re-export
121
- export { makeAutoObservable } from 'mobx'; // re-export
222
+ Each MF can emit a `project-manifest.json` as a build artifact to enable cross-team analysis in a later step:
223
+
224
+ ```bash
225
+ npx @mf-toolkit/shared-inspector --shared ./shared-config.json --write-manifest
122
226
  ```
123
227
 
124
- ```ts
125
- // src/app.tsx
126
- import { observer } from './shared'; // relative import — direct mode stops here
228
+ With the webpack plugin:
229
+
230
+ ```typescript
231
+ new MfSharedInspectorPlugin({
232
+ sourceDirs: ['./src'],
233
+ writeManifest: true, // writes project-manifest.json for CI aggregation
234
+ warn: true,
235
+ })
127
236
  ```
128
237
 
129
- - **`depth: 'direct'`** scans `app.tsx`, sees `./shared` (relative) → skips. `mobx` not found.
130
- - **`depth: 'local-graph'`** follows `./shared` → `shared/index.ts` → finds `mobx` and `mobx-react` via re-export.
238
+ Upload the manifest as a CI artifact:
131
239
 
132
- ## TypeScript path aliases
240
+ ```yaml
241
+ # .github/workflows/build.yml
242
+ jobs:
243
+ build-checkout:
244
+ steps:
245
+ - run: npm run build # MfSharedInspectorPlugin writes project-manifest.json
246
+ - uses: actions/upload-artifact@v4
247
+ with: { name: manifest-checkout, path: project-manifest.json }
248
+ ```
133
249
 
134
- 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:
250
+ ## Federation mode
135
251
 
136
- ```typescript
137
- // tsconfig.json
138
- { "compilerOptions": { "baseUrl": ".", "paths": { "@components/*": ["src/components/*"] } } }
252
+ Once each MF has emitted its manifest, aggregate them to detect cross-team conflicts: version mismatches, singleton inconsistencies, and shared-config gaps across host and remotes.
139
253
 
140
- // src/app.tsx
141
- import { Button } from '@components/Button'; // ← followed as local file, not external package
254
+ ### CLI
255
+
256
+ ```bash
257
+ npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
142
258
  ```
143
259
 
260
+ ### Programmatic
261
+
144
262
  ```typescript
145
- await buildProjectManifest({
146
- sourceDirs: ['./src'],
147
- tsconfigPath: './tsconfig.json', // enables alias resolution
148
- });
149
- ```
263
+ import { analyzeFederation, formatFederationReport, scoreFederationReport } from '@mf-toolkit/shared-inspector';
150
264
 
151
- Without `tsconfigPath`, `@components/Button` is treated as an external package name and packages imported inside it are invisible in local-graph mode.
265
+ const report = analyzeFederation([checkoutManifest, catalogManifest, cartManifest]);
266
+ const { score, label } = scoreFederationReport(report);
267
+
268
+ console.log(formatFederationReport(report));
269
+ // ⚠ Version Conflict — react
270
+ // checkout: ^17.0.0
271
+ // catalog: ^18.0.0
272
+ // → Risk: MF singleton negotiation may silently load the wrong version → Invalid hook call
273
+ // 💡 Fix: shared: { react: { singleton: true, requiredVersion: "^18.0.0" } }
274
+ //
275
+ // Score: 60/100 🟠 RISKY
276
+ ```
152
277
 
153
- ## Workspace packages
278
+ ## Programmatic API
154
279
 
155
- In a monorepo where local packages import each other, use `workspacePackages` to prevent internal packages from appearing in `resolvedPackages`:
280
+ ### Two-phase API
156
281
 
157
282
  ```typescript
158
- await buildProjectManifest({
283
+ import { buildProjectManifest, analyzeProject, formatReport } from '@mf-toolkit/shared-inspector';
284
+
285
+ // Phase 1: collect facts
286
+ const manifest = await buildProjectManifest({
287
+ name: 'checkout',
159
288
  sourceDirs: ['./src'],
160
- workspacePackages: ['@my-org/design-system', '@my-org/*'],
289
+ sharedConfig: {
290
+ react: { singleton: true, requiredVersion: '^18.0.0' },
291
+ 'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
292
+ lodash: {},
293
+ },
294
+ // depth: 'local-graph' ← default, follows barrel re-exports
295
+ // tsconfigPath: './tsconfig.json' ← optional, resolves @alias/* imports
296
+ // workspacePackages: ['@my-org/*'] ← optional, excludes local monorepo packages
161
297
  });
162
- ```
163
298
 
164
- ## Terminal output
299
+ // Phase 2: analyse facts
300
+ const report = analyzeProject(manifest);
165
301
 
166
- ```
167
- [MfSharedInspector] checkout (depth: local-graph, 47 files scanned)
302
+ console.log(report.mismatched);
303
+ // [{ package: 'react', configured: '^18.0.0', installed: '17.0.2' }]
168
304
 
169
- Version mismatch (sharing silently broken):
170
- react requires ^19.0.0, installed 18.3.1
305
+ console.log(report.candidates);
306
+ // [{ package: 'mobx', importCount: 12, via: 'reexport', files: ['src/shared/index.ts'] }]
171
307
 
172
- Unused shared (safe to remove):
173
- lodash 0 imports, shared without singleton
174
- ✗ @tanstack/react-query — 0 imports, shared as singleton
308
+ console.log(report.unused);
309
+ // [{ package: 'lodash', singleton: false }]
175
310
 
176
- Candidates (consider adding to shared):
177
- → mobx (12 imports in 8 files, via re-export in src/shared/index.ts)
178
- → react-router-dom (6 imports in 4 files)
311
+ // Human-readable output with risk descriptions and fix snippets
312
+ console.log(formatReport(report, { name: manifest.project.name }));
313
+ ```
179
314
 
180
- Singleton risks (add singleton: true):
181
- ⚠ react-router-dom — manages global state, singleton: true recommended
315
+ ### Shortcut API
182
316
 
183
- Eager risks (add singleton: true or remove eager: true):
184
- react-dom eager: true without singleton: true, risk of duplicate instances
317
+ ```typescript
318
+ import { inspect } from '@mf-toolkit/shared-inspector';
185
319
 
186
- Total: 12 shared, 10 used, 2 unused, 2 candidates, 1 mismatch, 1 eager risks
320
+ const report = await inspect({
321
+ name: 'checkout',
322
+ sourceDirs: ['./src'],
323
+ sharedConfig: { /* ... */ },
324
+ });
187
325
  ```
188
326
 
189
- ## CI pipeline: project → federation
327
+ ## Analysis depth
328
+
329
+ | Depth | What it finds | Speed |
330
+ |-------|--------------|-------|
331
+ | `'direct'` | Explicit `import` / `require` statements | Fast (ms) |
332
+ | `'local-graph'` *(default)* | + packages reachable via barrel re-exports and local wrappers | Slower (seconds) |
333
+
334
+ The difference matters when your project uses barrel files:
190
335
 
191
- Each microfrontend generates a manifest as a build artifact:
336
+ ```ts
337
+ // src/shared/index.ts
338
+ export { observer } from 'mobx-react'; // re-export
339
+ export { makeAutoObservable } from 'mobx'; // re-export
192
340
 
193
- ```yaml
194
- jobs:
195
- build-checkout:
196
- steps:
197
- - run: npm run build # MfSharedInspectorPlugin writes project-manifest.json
198
- - uses: actions/upload-artifact@v4
199
- with:
200
- name: manifest-checkout
201
- path: project-manifest.json
341
+ // src/app.tsx
342
+ import { observer } from './shared'; // relative import — direct mode stops here
202
343
  ```
203
344
 
204
- Each MF manifest can then be aggregated for cross-team analysis:
345
+ - **`depth: 'direct'`** sees `./shared` (relative) skips. `mobx` not found.
346
+ - **`depth: 'local-graph'`** follows `./shared` → finds `mobx` and `mobx-react` via re-export.
347
+
348
+ ## TypeScript path aliases
205
349
 
206
350
  ```typescript
207
- import { analyzeFederation, formatFederationReport } from '@mf-toolkit/shared-inspector';
351
+ // tsconfig.json
352
+ { "compilerOptions": { "paths": { "@components/*": ["src/components/*"] } } }
208
353
 
209
- const report = analyzeFederation([checkoutManifest, catalogManifest, cartManifest]);
210
- console.log(formatFederationReport(report));
354
+ await buildProjectManifest({
355
+ sourceDirs: ['./src'],
356
+ tsconfigPath: './tsconfig.json', // enables alias resolution
357
+ });
211
358
  ```
212
359
 
213
- ```
214
- [MfSharedInspector] federation analysis (3 MFs)
360
+ Without `tsconfigPath`, `@components/Button` is treated as an external package and packages imported inside it are invisible in `local-graph` mode.
215
361
 
216
- Version conflicts (singleton negotiation will fail):
217
- ⚠ react — checkout: ^17.0.0, catalog: ^18.0.0
362
+ ## Interactive wizard
218
363
 
219
- Singleton mismatches (add singleton: true to all MFs):
220
- ⚠ mobx — singleton in [checkout], not singleton in [catalog, cart]
364
+ Not sure which flags to pass? Run the step-by-step wizard:
221
365
 
222
- Host gaps (add to shared — each MF bundles its own copy):
223
- axios used by [checkout, catalog], not in shared
366
+ ```
367
+ $ npx @mf-toolkit/shared-inspector --interactive
224
368
 
225
- Ghost shares (remove from shared — no other MF benefits):
226
- ✗ lodash — shared only by cart, unused by all other MFs
369
+ [MfSharedInspector] Interactive setup
227
370
 
228
- Total: 3 MFs, 1 version conflicts, 1 singleton mismatches, 1 host gaps, 1 ghost shares
371
+ Source directories to scan (default: ./src):
372
+ Scan depth — direct or local-graph (default: local-graph):
373
+ Shared packages — comma-separated names or path to .json (empty to skip): react,react-dom,mobx
374
+ Path to tsconfig.json for alias resolution (empty to skip):
375
+ Workspace packages to exclude, comma-separated (empty to skip):
376
+ Fail build on findings — mismatch / unused / any / none (default: none): mismatch
377
+ Write project-manifest.json? (y/N): n
229
378
  ```
230
379
 
380
+ ## CLI reference
381
+
382
+ | Flag | Default | Description |
383
+ |------|---------|-------------|
384
+ | `--source, -s <dirs>` | `./src` | Source dirs to scan, comma-separated |
385
+ | `--depth <depth>` | `local-graph` | Scan depth: `direct` \| `local-graph` |
386
+ | `--shared <packages\|file>` | — | Comma-separated package names or path to `.json` config |
387
+ | `--tsconfig <path>` | — | tsconfig.json for path alias resolution |
388
+ | `--workspace-packages <pkgs>` | — | Comma-separated workspace packages to exclude |
389
+ | `--name <name>` | auto from `package.json` | Project name |
390
+ | `--fail-on <rule>` | — | Exit 1 when findings match: `mismatch` \| `unused` \| `any` |
391
+ | `--write-manifest` | `false` | Write `project-manifest.json` to output dir |
392
+ | `--output-dir <dir>` | `.` | Output directory for manifest |
393
+ | `--interactive, -i` | — | Launch step-by-step wizard |
394
+ | `--version, -v` | — | Print version and exit |
395
+ | `--help, -h` | — | Show help |
396
+
231
397
  ## API reference
232
398
 
233
399
  ### `buildProjectManifest(options)`
@@ -253,6 +419,20 @@ console.log(formatFederationReport(report));
253
419
  | `additionalCandidates` | `string[]` | `[]` | Extend built-in candidates list |
254
420
  | `additionalSingletonRisks` | `string[]` | `[]` | Extend built-in singleton-risk list |
255
421
 
422
+ ### `scoreProjectReport(report)` / `scoreFederationReport(report)`
423
+
424
+ Returns a `RiskScore`:
425
+
426
+ ```typescript
427
+ interface RiskScore {
428
+ score: number; // 0–100, higher is better
429
+ label: 'HEALTHY' | 'GOOD' | 'RISKY' | 'CRITICAL';
430
+ high: number; // count of high-severity findings
431
+ medium: number; // count of medium-severity findings
432
+ low: number; // count of low-severity findings
433
+ }
434
+ ```
435
+
256
436
  ### `analyzeFederation(manifests, options?)`
257
437
 
258
438
  Accepts N `ProjectManifest` objects (one per microfrontend) and returns a `FederationReport`.
@@ -261,20 +441,12 @@ Accepts N `ProjectManifest` objects (one per microfrontend) and returns a `Feder
261
441
  |--------|------|---------|-------------|
262
442
  | `alwaysShared` | `string[]` | `['react','react-dom']` | Exclude from ghost/gap detection |
263
443
 
264
- ### `formatFederationReport(report)`
265
-
266
- Formats a `FederationReport` as a human-readable terminal string.
267
-
268
444
  ### `MfSharedInspectorPlugin` options
269
445
 
270
446
  Extends all `buildProjectManifest` options (except `name`, auto-resolved from compiler) plus:
271
447
 
272
448
  | Option | Type | Default | Description |
273
449
  |--------|------|---------|-------------|
274
- | `sourceDirs` | `string[]` | — | Directories to scan |
275
- | `sharedConfig` | `Record<string, SharedDepConfig>` | auto-extracted | Override auto-extraction from `ModuleFederationPlugin` |
276
- | `tsconfigPath` | `string` | `undefined` | tsconfig.json for path alias resolution |
277
- | `workspacePackages` | `string[]` | `[]` | Local monorepo packages to exclude |
278
450
  | `warn` | `boolean` | `true` | Print findings to console |
279
451
  | `failOn` | `'mismatch' \| 'unused' \| 'any'` | `undefined` | Fail the build when findings match |
280
452
  | `writeManifest` | `boolean` | `false` | Write `project-manifest.json` to `outputDir` |
@@ -285,37 +457,29 @@ Extends all `buildProjectManifest` options (except `name`, auto-resolved from co
285
457
 
286
458
  ### Per-project (`analyzeProject`)
287
459
 
288
- | Category | Type | Description |
289
- |----------|------|-------------|
290
- | `mismatched` | Deterministic | `requiredVersion` doesn't satisfy installed version |
291
- | `unused` | Deterministic* | In `shared` config but not observed in scanned sources |
292
- | `candidates` | Heuristic | Observed packages not in `shared` that are typically shared |
293
- | `singletonRisks` | Heuristic | Global-state packages shared without `singleton: true` |
294
- | `eagerRisks` | Heuristic | `eager: true` without `singleton: true` risk of duplicate instances |
295
-
296
- *Within the visibility of the chosen depth.*
460
+ | Category | Severity | Description |
461
+ |----------|----------|-------------|
462
+ | `mismatched` | 🔴 HIGH | `requiredVersion` doesn't satisfy installed version |
463
+ | `singletonRisks` | 🟠 MEDIUM | Global-state packages shared without `singleton: true` |
464
+ | `eagerRisks` | 🟠 MEDIUM | `eager: true` without `singleton: true` |
465
+ | `candidates` | 🟠 MEDIUM | Used packages missing from `shared` (each MF bundles own copy) |
466
+ | `unused` | 🟡 LOW | In `shared` config but not observed in scanned sources |
297
467
 
298
468
  ### Cross-MF (`analyzeFederation`)
299
469
 
300
- | Category | Type | Description |
301
- |----------|------|-------------|
302
- | `versionConflicts` | Deterministic | `requiredVersion` ranges across MFs have no overlap |
303
- | `singletonMismatches` | Deterministic | `singleton: true` in some MFs, absent in others |
304
- | `hostGaps` | Heuristic | Package used by 2+ MFs but not declared in `shared` by anyone |
305
- | `ghostShares` | Heuristic | Package in `shared` of one MF, unused/unshared by all others |
470
+ | Category | Severity | Description |
471
+ |----------|----------|-------------|
472
+ | `versionConflicts` | 🔴 HIGH | `requiredVersion` ranges across MFs have no overlap |
473
+ | `singletonMismatches` | 🟠 MEDIUM | `singleton: true` in some MFs, absent in others |
474
+ | `hostGaps` | 🟠 MEDIUM | Package used by 2+ MFs but not declared in `shared` by anyone |
475
+ | `ghostShares` | 🟡 LOW | Package in `shared` of one MF, unused/unshared by all others |
306
476
 
307
477
  ## Known limitations
308
478
 
309
- - **TypeScript path aliases without `tsconfigPath`**: aliased imports are treated as external package names. Pass `tsconfigPath` to resolve them correctly.
479
+ - **TypeScript path aliases without `tsconfigPath`**: aliased imports are treated as external package names.
310
480
  - **Dynamic imports with variables** (`import(moduleName)`): not analysed — requires runtime information.
311
- - **Exact tsconfig alias patterns** (non-wildcard, e.g. `"@root": ["."]`): not supported, only `"@alias/*"` wildcard form.
312
- - **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.
313
-
314
- ## Demo
315
-
316
- ```bash
317
- npx tsx packages/shared-inspector/demo/run.ts
318
- ```
481
+ - **Exact tsconfig alias patterns** (non-wildcard): only `"@alias/*"` wildcard form is supported.
482
+ - **Subclassed `ModuleFederationPlugin`**: auto-extraction matches by constructor name — pass `sharedConfig` explicitly for custom subclasses.
319
483
 
320
484
  ## License
321
485
 
@@ -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"}