@mf-toolkit/shared-inspector 0.4.0 → 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 (2) hide show
  1. package/README.md +198 -102
  2. package/package.json +4 -2
package/README.md CHANGED
@@ -6,81 +6,108 @@
6
6
 
7
7
  **Stop debugging Module Federation in production.**
8
8
 
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.
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.
10
10
 
11
11
  ## The problem
12
12
 
13
13
  Module Federation teams manually manage `shared` config and make three kinds of mistakes:
14
14
 
15
15
  - **Over-sharing** — packages listed in `shared` that the microfrontend never imports. Creates artificial version coupling between independent teams.
16
- - **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).
17
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.
18
18
 
19
19
  Existing tools (webpack-bundle-analyzer, source-map-explorer) show *what ended up in the bundle*, not *why shared config is suboptimal*. Different questions.
20
20
 
21
- ## Installation
21
+ ## Why not bundle analyzer?
22
22
 
23
- ```bash
24
- npm install --save-dev @mf-toolkit/shared-inspector
25
- ```
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.
26
24
 
27
- ## CLI
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 |
28
34
 
29
- The fastest way to get started no config file, no webpack required:
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.
30
36
 
31
- ```bash
32
- # Analyse the current project (auto-reads name from package.json)
33
- npx @mf-toolkit/shared-inspector
37
+ ## Example
34
38
 
35
- # Step-by-step interactive wizard answers guide you through all options
36
- npx @mf-toolkit/shared-inspector --interactive
39
+ A `shell` host app (React 18) and a `checkout` remote have been developed by separate teams. Their `shared` configs have drifted:
37
40
 
38
- # Pass options directly
39
- npx @mf-toolkit/shared-inspector --source ./src --shared react,react-dom --fail-on mismatch
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
+ }
40
48
 
41
- # Load shared config from a JSON file
42
- npx @mf-toolkit/shared-inspector --shared ./shared-config.json --write-manifest
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:
43
59
 
44
- # Cross-MF federation analysis from saved manifests
45
- npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
46
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
+ }
47
71
 
48
- ### Interactive wizard
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
49
83
 
84
+ ────────────────────────────────────────────────────────────
85
+ Score: 69/100 🟠 RISKY
50
86
  ```
51
- $ npx @mf-toolkit/shared-inspector --interactive
52
87
 
53
- [MfSharedInspector] Interactive setup
88
+ **After manually updating the config based on the suggestions above** — `react` version aligned, `zustand` added to `shared`, `lodash` removed:
54
89
 
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
90
+ ```
91
+ Score: 100/100 ✅ HEALTHY
62
92
  ```
63
93
 
64
- ### CLI reference
94
+ The cross-team federation report also clears: `shell` and `checkout` now negotiate a single React instance and a single Zustand store at runtime.
65
95
 
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 |
96
+ ## Installation
80
97
 
81
- ## Terminal output
98
+ ```bash
99
+ npm install --save-dev @mf-toolkit/shared-inspector
100
+ ```
101
+
102
+ ## Quick start
82
103
 
83
- Each finding is rendered as a diagnostic card: what's wrong, what breaks at runtime, and a ready-to-paste fix.
104
+ Run against any MF project no config file needed:
105
+
106
+ ```bash
107
+ npx @mf-toolkit/shared-inspector
108
+ ```
109
+
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:
84
111
 
85
112
  ```
86
113
  mf-inspector v0.4.0
@@ -104,7 +131,7 @@ Each finding is rendered as a diagnostic card: what's wrong, what breaks at runt
104
131
  💡 Fix: Remove "lodash" from shared config
105
132
 
106
133
  → 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
134
+ → Risk: Each MF may get its own MobX instance — observables and reactions can fail to sync between MFs
108
135
  💡 Fix:
109
136
  shared: {
110
137
  mobx: { singleton: true }
@@ -123,7 +150,7 @@ Total: 12 shared, 10 used, 1 unused, 1 candidates, 1 mismatch, 0 eager risks
123
150
 
124
151
  Colors are auto-applied in TTY terminals and disabled in CI / piped output (`NO_COLOR` / `TERM=dumb` respected).
125
152
 
126
- ## Risk scoring
153
+ ### Risk scoring
127
154
 
128
155
  Every report ends with a score out of 100:
129
156
 
@@ -140,7 +167,43 @@ Every report ends with a score out of 100:
140
167
  | 40–69 | 🟠 RISKY |
141
168
  | 0–39 | 🔴 CRITICAL |
142
169
 
143
- Use `scoreProjectReport` / `scoreFederationReport` programmatically to integrate with dashboards or custom CI gates:
170
+ ## CI mode
171
+
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:
183
+
184
+ ```typescript
185
+ import { MfSharedInspectorPlugin } from '@mf-toolkit/shared-inspector/webpack';
186
+
187
+ module.exports = {
188
+ plugins: [
189
+ new ModuleFederationPlugin({
190
+ name: 'checkout',
191
+ shared: { react: { singleton: true }, mobx: { singleton: true } },
192
+ }),
193
+
194
+ // sharedConfig not needed — auto-extracted from ModuleFederationPlugin above
195
+ new MfSharedInspectorPlugin({
196
+ sourceDirs: ['./src'],
197
+ failOn: 'mismatch', // 'mismatch' | 'unused' | 'any'
198
+ warn: true,
199
+ }),
200
+ ],
201
+ };
202
+ ```
203
+
204
+ ### Gating on score
205
+
206
+ Use `scoreProjectReport` programmatically to set custom thresholds:
144
207
 
145
208
  ```typescript
146
209
  import { analyzeProject, scoreProjectReport } from '@mf-toolkit/shared-inspector';
@@ -154,9 +217,67 @@ if (score < 70) {
154
217
  }
155
218
  ```
156
219
 
157
- ## Quick start
220
+ ### Writing manifests for federation analysis
221
+
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
226
+ ```
227
+
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
+ })
236
+ ```
237
+
238
+ Upload the manifest as a CI artifact:
239
+
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
+ ```
249
+
250
+ ## Federation mode
251
+
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.
253
+
254
+ ### CLI
255
+
256
+ ```bash
257
+ npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
258
+ ```
259
+
260
+ ### Programmatic
261
+
262
+ ```typescript
263
+ import { analyzeFederation, formatFederationReport, scoreFederationReport } from '@mf-toolkit/shared-inspector';
264
+
265
+ const report = analyzeFederation([checkoutManifest, catalogManifest, cartManifest]);
266
+ const { score, label } = scoreFederationReport(report);
158
267
 
159
- ### Programmatic API (two-phase)
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
+ ```
277
+
278
+ ## Programmatic API
279
+
280
+ ### Two-phase API
160
281
 
161
282
  ```typescript
162
283
  import { buildProjectManifest, analyzeProject, formatReport } from '@mf-toolkit/shared-inspector';
@@ -203,30 +324,6 @@ const report = await inspect({
203
324
  });
204
325
  ```
205
326
 
206
- ### Webpack plugin
207
-
208
- `sharedConfig` is optional — the plugin auto-extracts it from `ModuleFederationPlugin` when not provided:
209
-
210
- ```typescript
211
- import { MfSharedInspectorPlugin } from '@mf-toolkit/shared-inspector/webpack';
212
-
213
- module.exports = {
214
- plugins: [
215
- new ModuleFederationPlugin({
216
- name: 'checkout',
217
- shared: { react: { singleton: true }, mobx: { singleton: true } },
218
- }),
219
-
220
- // sharedConfig not needed — auto-extracted from ModuleFederationPlugin above
221
- new MfSharedInspectorPlugin({
222
- sourceDirs: ['./src'],
223
- warn: true,
224
- writeManifest: true, // writes project-manifest.json for CI aggregation
225
- }),
226
- ],
227
- };
228
- ```
229
-
230
327
  ## Analysis depth
231
328
 
232
329
  | Depth | What it finds | Speed |
@@ -262,41 +359,40 @@ await buildProjectManifest({
262
359
 
263
360
  Without `tsconfigPath`, `@components/Button` is treated as an external package and packages imported inside it are invisible in `local-graph` mode.
264
361
 
265
- ## CI pipeline: project → federation
362
+ ## Interactive wizard
266
363
 
267
- Each microfrontend generates a manifest as a build artifact, then they're aggregated for cross-team analysis:
364
+ Not sure which flags to pass? Run the step-by-step wizard:
268
365
 
269
- ```yaml
270
- # .github/workflows/build.yml
271
- jobs:
272
- build-checkout:
273
- steps:
274
- - run: npm run build # MfSharedInspectorPlugin writes project-manifest.json
275
- - uses: actions/upload-artifact@v4
276
- with: { name: manifest-checkout, path: project-manifest.json }
277
366
  ```
367
+ $ npx @mf-toolkit/shared-inspector --interactive
278
368
 
279
- ```typescript
280
- import { analyzeFederation, formatFederationReport, scoreFederationReport } from '@mf-toolkit/shared-inspector';
281
-
282
- const report = analyzeFederation([checkoutManifest, catalogManifest, cartManifest]);
283
- const { score, label } = scoreFederationReport(report);
369
+ [MfSharedInspector] Interactive setup
284
370
 
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
371
+ Source directories to scan (default: ./src):
372
+ Scan depthdirect 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
293
378
  ```
294
379
 
295
- Or use the CLI directly:
380
+ ## CLI reference
296
381
 
297
- ```bash
298
- npx @mf-toolkit/shared-inspector federation checkout.json catalog.json cart.json
299
- ```
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 |
300
396
 
301
397
  ## API reference
302
398
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mf-toolkit/shared-inspector",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Stop debugging Module Federation in production. Audit shared config at build time.",
5
5
  "author": "Vitaly Zheltko",
6
6
  "license": "MIT",
@@ -48,7 +48,9 @@
48
48
  "typescript": ">=4.7"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
- "typescript": { "optional": true }
51
+ "typescript": {
52
+ "optional": true
53
+ }
52
54
  },
53
55
  "devDependencies": {
54
56
  "@types/semver": "^7.5.8",