@mf-toolkit/shared-inspector 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -0
- package/dist/analyzer/analyze-project.d.ts +9 -0
- package/dist/analyzer/analyze-project.d.ts.map +1 -0
- package/dist/analyzer/analyze-project.js +38 -0
- package/dist/analyzer/analyze-project.js.map +1 -0
- package/dist/analyzer/detect-issues.d.ts +33 -0
- package/dist/analyzer/detect-issues.d.ts.map +1 -0
- package/dist/analyzer/detect-issues.js +69 -0
- package/dist/analyzer/detect-issues.js.map +1 -0
- package/dist/analyzer/policy.d.ts +27 -0
- package/dist/analyzer/policy.d.ts.map +1 -0
- package/dist/analyzer/policy.js +84 -0
- package/dist/analyzer/policy.js.map +1 -0
- package/dist/collector/build-project-manifest.d.ts +10 -0
- package/dist/collector/build-project-manifest.d.ts.map +1 -0
- package/dist/collector/build-project-manifest.js +82 -0
- package/dist/collector/build-project-manifest.js.map +1 -0
- package/dist/collector/collect-imports.d.ts +17 -0
- package/dist/collector/collect-imports.d.ts.map +1 -0
- package/dist/collector/collect-imports.js +87 -0
- package/dist/collector/collect-imports.js.map +1 -0
- package/dist/collector/parse-declarations.d.ts +26 -0
- package/dist/collector/parse-declarations.d.ts.map +1 -0
- package/dist/collector/parse-declarations.js +134 -0
- package/dist/collector/parse-declarations.js.map +1 -0
- package/dist/collector/parse-shared-config.d.ts +12 -0
- package/dist/collector/parse-shared-config.d.ts.map +1 -0
- package/dist/collector/parse-shared-config.js +56 -0
- package/dist/collector/parse-shared-config.js.map +1 -0
- package/dist/collector/resolve-tsconfig-paths.d.ts +39 -0
- package/dist/collector/resolve-tsconfig-paths.d.ts.map +1 -0
- package/dist/collector/resolve-tsconfig-paths.js +89 -0
- package/dist/collector/resolve-tsconfig-paths.js.map +1 -0
- package/dist/collector/resolve-versions.d.ts +15 -0
- package/dist/collector/resolve-versions.d.ts.map +1 -0
- package/dist/collector/resolve-versions.js +49 -0
- package/dist/collector/resolve-versions.js.map +1 -0
- package/dist/collector/traverse-local-modules.d.ts +27 -0
- package/dist/collector/traverse-local-modules.d.ts.map +1 -0
- package/dist/collector/traverse-local-modules.js +119 -0
- package/dist/collector/traverse-local-modules.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +12 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +15 -0
- package/dist/inspect.js.map +1 -0
- package/dist/plugins/webpack.d.ts +40 -0
- package/dist/plugins/webpack.d.ts.map +1 -0
- package/dist/plugins/webpack.js +95 -0
- package/dist/plugins/webpack.js.map +1 -0
- package/dist/reporter/format-report.d.ts +18 -0
- package/dist/reporter/format-report.d.ts.map +1 -0
- package/dist/reporter/format-report.js +87 -0
- package/dist/reporter/format-report.js.map +1 -0
- package/dist/reporter/write-report.d.ts +12 -0
- package/dist/reporter/write-report.d.ts.map +1 -0
- package/dist/reporter/write-report.js +19 -0
- package/dist/reporter/write-report.js.map +1 -0
- package/dist/types.d.ts +179 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/webpack.d.ts +3 -0
- package/dist/webpack.d.ts.map +1 -0
- package/dist/webpack.js +2 -0
- package/dist/webpack.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# `@mf-toolkit/shared-inspector`
|
|
2
|
+
|
|
3
|
+
[](https://github.com/zvitaly7/mf-toolkit)
|
|
4
|
+
[](https://github.com/zvitaly7/mf-toolkit)
|
|
5
|
+
[](https://github.com/zvitaly7/mf-toolkit/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
> ⚠️ **Work in progress.** This package is feature-complete and fully tested (179 tests) but has not yet been published to npm. The API is stable but may receive minor changes before the official release. Do not use in production until v0.1.0 is tagged.
|
|
9
|
+
|
|
10
|
+
Build-time analyser for Module Federation `shared` dependencies. Two-phase architecture: **collect facts → analyse facts**.
|
|
11
|
+
|
|
12
|
+
## The problem
|
|
13
|
+
|
|
14
|
+
Module Federation teams manually manage `shared` config and make three kinds of mistakes:
|
|
15
|
+
|
|
16
|
+
- **Over-sharing** — packages listed in `shared` that the microfrontend never imports. Creates artificial version coupling between independent teams.
|
|
17
|
+
- **Under-sharing** — packages used by both host and remote but missing from `shared`. Each microfrontend bundles its own copy (10× React = 10× 130 KB).
|
|
18
|
+
- **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.
|
|
19
|
+
|
|
20
|
+
Existing tools (webpack-bundle-analyzer, source-map-explorer) show *what ended up in the bundle*, not *why shared config is suboptimal*. Different questions.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install --save-dev @mf-toolkit/shared-inspector
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
### Programmatic API (two-phase)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { buildProjectManifest, analyzeProject } from '@mf-toolkit/shared-inspector';
|
|
34
|
+
|
|
35
|
+
// Phase 1: collect facts
|
|
36
|
+
const manifest = await buildProjectManifest({
|
|
37
|
+
name: 'checkout',
|
|
38
|
+
sourceDirs: ['./src'],
|
|
39
|
+
sharedConfig: {
|
|
40
|
+
react: { singleton: true, requiredVersion: '^19.0.0' },
|
|
41
|
+
'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
|
|
42
|
+
lodash: {},
|
|
43
|
+
},
|
|
44
|
+
// depth: 'local-graph' ← default, follows barrel re-exports
|
|
45
|
+
// tsconfigPath: './tsconfig.json' ← optional, resolves @alias/* imports
|
|
46
|
+
// workspacePackages: ['@my-org/*'] ← optional, excludes local monorepo packages
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Phase 2: analyse facts
|
|
50
|
+
const report = analyzeProject(manifest, {
|
|
51
|
+
alwaysShared: ['react', 'react-dom'],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(report.unused);
|
|
55
|
+
// [{ package: 'lodash', singleton: false }]
|
|
56
|
+
|
|
57
|
+
console.log(report.candidates);
|
|
58
|
+
// [{ package: 'mobx', importCount: 12, via: 'reexport', files: ['src/shared/index.ts'] }]
|
|
59
|
+
|
|
60
|
+
console.log(report.mismatched);
|
|
61
|
+
// [{ package: 'react', configured: '^19.0.0', installed: '18.3.1' }]
|
|
62
|
+
|
|
63
|
+
console.log(report.eagerRisks);
|
|
64
|
+
// [{ package: 'react-dom' }] ← eager: true without singleton: true
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Shortcut API
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { inspect } from '@mf-toolkit/shared-inspector';
|
|
71
|
+
|
|
72
|
+
const report = await inspect({
|
|
73
|
+
name: 'checkout',
|
|
74
|
+
sourceDirs: ['./src'],
|
|
75
|
+
sharedConfig: { /* ... */ },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Webpack plugin
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { MfSharedInspectorPlugin } from '@mf-toolkit/shared-inspector/webpack';
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
plugins: [
|
|
86
|
+
new ModuleFederationPlugin({
|
|
87
|
+
name: 'checkout',
|
|
88
|
+
shared: { react: { singleton: true }, mobx: { singleton: true } },
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
new MfSharedInspectorPlugin({
|
|
92
|
+
sourceDirs: ['./src'],
|
|
93
|
+
sharedConfig: {
|
|
94
|
+
react: { singleton: true, requiredVersion: '^19.0.0' },
|
|
95
|
+
mobx: { singleton: true },
|
|
96
|
+
},
|
|
97
|
+
tsconfigPath: './tsconfig.json', // resolve @alias/* imports
|
|
98
|
+
warn: true,
|
|
99
|
+
writeManifest: true, // writes project-manifest.json for CI
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Analysis depth
|
|
106
|
+
|
|
107
|
+
| Depth | What it finds | Speed |
|
|
108
|
+
|-------|--------------|-------|
|
|
109
|
+
| `'direct'` | Explicit `import` / `require` statements | Fast (ms) |
|
|
110
|
+
| `'local-graph'` *(default)* | + packages reachable via barrel re-exports and local wrappers | Slower (seconds) |
|
|
111
|
+
|
|
112
|
+
The difference matters when your project uses barrel files:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// src/shared/index.ts
|
|
116
|
+
export { observer } from 'mobx-react'; // re-export
|
|
117
|
+
export { makeAutoObservable } from 'mobx'; // re-export
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// src/app.tsx
|
|
122
|
+
import { observer } from './shared'; // relative import — direct mode stops here
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- **`depth: 'direct'`** scans `app.tsx`, sees `./shared` (relative) → skips. `mobx` not found.
|
|
126
|
+
- **`depth: 'local-graph'`** follows `./shared` → `shared/index.ts` → finds `mobx` and `mobx-react` via re-export.
|
|
127
|
+
|
|
128
|
+
## TypeScript path aliases
|
|
129
|
+
|
|
130
|
+
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:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// tsconfig.json
|
|
134
|
+
{ "compilerOptions": { "baseUrl": ".", "paths": { "@components/*": ["src/components/*"] } } }
|
|
135
|
+
|
|
136
|
+
// src/app.tsx
|
|
137
|
+
import { Button } from '@components/Button'; // ← followed as local file, not external package
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
await buildProjectManifest({
|
|
142
|
+
sourceDirs: ['./src'],
|
|
143
|
+
tsconfigPath: './tsconfig.json', // enables alias resolution
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Without `tsconfigPath`, `@components/Button` is treated as an external package name and packages imported inside it are invisible in local-graph mode.
|
|
148
|
+
|
|
149
|
+
## Workspace packages
|
|
150
|
+
|
|
151
|
+
In a monorepo where local packages import each other, use `workspacePackages` to prevent internal packages from appearing in `resolvedPackages`:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
await buildProjectManifest({
|
|
155
|
+
sourceDirs: ['./src'],
|
|
156
|
+
workspacePackages: ['@my-org/design-system', '@my-org/*'],
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Terminal output
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
[MfSharedInspector] checkout (depth: local-graph, 47 files scanned)
|
|
164
|
+
|
|
165
|
+
Version mismatch (sharing silently broken):
|
|
166
|
+
⚠ react — requires ^19.0.0, installed 18.3.1
|
|
167
|
+
|
|
168
|
+
Unused shared (safe to remove):
|
|
169
|
+
✗ lodash — 0 imports, shared without singleton
|
|
170
|
+
✗ @tanstack/react-query — 0 imports, shared as singleton
|
|
171
|
+
|
|
172
|
+
Candidates (consider adding to shared):
|
|
173
|
+
→ mobx (12 imports in 8 files, via re-export in src/shared/index.ts)
|
|
174
|
+
→ react-router-dom (6 imports in 4 files)
|
|
175
|
+
|
|
176
|
+
Singleton risks (add singleton: true):
|
|
177
|
+
⚠ react-router-dom — manages global state, singleton: true recommended
|
|
178
|
+
|
|
179
|
+
Eager risks (add singleton: true or remove eager: true):
|
|
180
|
+
⚠ react-dom — eager: true without singleton: true, risk of duplicate instances
|
|
181
|
+
|
|
182
|
+
Total: 12 shared, 10 used, 2 unused, 2 candidates, 1 mismatch, 1 eager risks
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## CI pipeline: project → federation
|
|
186
|
+
|
|
187
|
+
Each microfrontend generates a manifest as a build artifact:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
jobs:
|
|
191
|
+
build-checkout:
|
|
192
|
+
steps:
|
|
193
|
+
- run: npm run build # MfSharedInspectorPlugin writes project-manifest.json
|
|
194
|
+
- uses: actions/upload-artifact@v4
|
|
195
|
+
with:
|
|
196
|
+
name: manifest-checkout
|
|
197
|
+
path: project-manifest.json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`analyzeFederation()` (v0.2) will accept N manifests and detect cross-MF issues: ghost sharing, host gaps, version conflicts.
|
|
201
|
+
|
|
202
|
+
## API reference
|
|
203
|
+
|
|
204
|
+
### `buildProjectManifest(options)`
|
|
205
|
+
|
|
206
|
+
| Option | Type | Default | Description |
|
|
207
|
+
|--------|------|---------|-------------|
|
|
208
|
+
| `name` | `string` | — | Project name |
|
|
209
|
+
| `sourceDirs` | `string[]` | — | Directories to scan |
|
|
210
|
+
| `depth` | `'direct' \| 'local-graph'` | `'local-graph'` | Scan depth |
|
|
211
|
+
| `sharedConfig` | `Record<string, SharedDepConfig>` | `{}` | MF shared config |
|
|
212
|
+
| `packageJsonPath` | `string` | `'./package.json'` | Path to package.json |
|
|
213
|
+
| `extensions` | `string[]` | `['.ts','.tsx','.js','.jsx']` | File extensions |
|
|
214
|
+
| `ignore` | `string[]` | `[]` | Packages to exclude (supports `@scope/*`) |
|
|
215
|
+
| `tsconfigPath` | `string` | `undefined` | tsconfig.json for path alias resolution |
|
|
216
|
+
| `workspacePackages` | `string[]` | `[]` | Local monorepo packages to exclude |
|
|
217
|
+
| `kind` | `'host' \| 'remote' \| 'unknown'` | `'unknown'` | Project role |
|
|
218
|
+
|
|
219
|
+
### `analyzeProject(manifest, options?)`
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
|--------|------|---------|-------------|
|
|
223
|
+
| `alwaysShared` | `string[]` | `['react','react-dom']` | Never flag as unused |
|
|
224
|
+
| `additionalCandidates` | `string[]` | `[]` | Extend built-in candidates list |
|
|
225
|
+
| `additionalSingletonRisks` | `string[]` | `[]` | Extend built-in singleton-risk list |
|
|
226
|
+
|
|
227
|
+
## Detection categories
|
|
228
|
+
|
|
229
|
+
| Category | Type | Description |
|
|
230
|
+
|----------|------|-------------|
|
|
231
|
+
| `mismatched` | Deterministic | `requiredVersion` doesn't satisfy installed version |
|
|
232
|
+
| `unused` | Deterministic* | In `shared` config but not observed in scanned sources |
|
|
233
|
+
| `candidates` | Heuristic | Observed packages not in `shared` that are typically shared |
|
|
234
|
+
| `singletonRisks` | Heuristic | Global-state packages shared without `singleton: true` |
|
|
235
|
+
| `eagerRisks` | Heuristic | `eager: true` without `singleton: true` — risk of duplicate instances |
|
|
236
|
+
|
|
237
|
+
*Within the visibility of the chosen depth.*
|
|
238
|
+
|
|
239
|
+
## Known limitations
|
|
240
|
+
|
|
241
|
+
- **TypeScript path aliases without `tsconfigPath`**: aliased imports are treated as external package names. Pass `tsconfigPath` to resolve them correctly.
|
|
242
|
+
- **Dynamic imports with variables** (`import(moduleName)`): not analysed — requires runtime information.
|
|
243
|
+
- **Exact tsconfig alias patterns** (non-wildcard, e.g. `"@root": ["."]`): not supported, only `"@alias/*"` wildcard form.
|
|
244
|
+
- **`analyzeFederation()`** (cross-MF analysis): v0.2.
|
|
245
|
+
|
|
246
|
+
## Demo
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npx tsx packages/shared-inspector/demo/run.ts
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProjectManifest, ProjectReport, AnalysisOptions } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Analyze a single ProjectManifest and return a ProjectReport.
|
|
4
|
+
*
|
|
5
|
+
* Pure function — no I/O. All findings are scoped to what the collector
|
|
6
|
+
* observed at the chosen depth (manifest.source.depth).
|
|
7
|
+
*/
|
|
8
|
+
export declare function analyzeProject(manifest: ProjectManifest, options?: AnalysisOptions): ProjectReport;
|
|
9
|
+
//# sourceMappingURL=analyze-project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-project.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyze-project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInF;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,eAAe,GACxB,aAAa,CAiCf"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { detectIssues } from './detect-issues.js';
|
|
2
|
+
import { mergePolicy } from './policy.js';
|
|
3
|
+
/**
|
|
4
|
+
* Analyze a single ProjectManifest and return a ProjectReport.
|
|
5
|
+
*
|
|
6
|
+
* Pure function — no I/O. All findings are scoped to what the collector
|
|
7
|
+
* observed at the chosen depth (manifest.source.depth).
|
|
8
|
+
*/
|
|
9
|
+
export function analyzeProject(manifest, options) {
|
|
10
|
+
const policy = mergePolicy(options);
|
|
11
|
+
const { unused, candidates, mismatched, singletonRisks, eagerRisks } = detectIssues({
|
|
12
|
+
resolvedPackages: manifest.usage.resolvedPackages,
|
|
13
|
+
packageDetails: manifest.usage.packageDetails,
|
|
14
|
+
sharedDeclared: manifest.shared.declared,
|
|
15
|
+
installedVersions: manifest.versions.installed,
|
|
16
|
+
policy,
|
|
17
|
+
});
|
|
18
|
+
const totalShared = Object.keys(manifest.shared.declared).length;
|
|
19
|
+
const resolvedSet = new Set(manifest.usage.resolvedPackages);
|
|
20
|
+
const usedShared = Object.keys(manifest.shared.declared).filter(pkg => resolvedSet.has(pkg)).length;
|
|
21
|
+
return {
|
|
22
|
+
unused,
|
|
23
|
+
candidates,
|
|
24
|
+
mismatched,
|
|
25
|
+
singletonRisks,
|
|
26
|
+
eagerRisks,
|
|
27
|
+
summary: {
|
|
28
|
+
totalShared,
|
|
29
|
+
usedShared,
|
|
30
|
+
unusedCount: unused.length,
|
|
31
|
+
candidatesCount: candidates.length,
|
|
32
|
+
mismatchedCount: mismatched.length,
|
|
33
|
+
singletonRisksCount: singletonRisks.length,
|
|
34
|
+
eagerRisksCount: eagerRisks.length,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=analyze-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-project.js","sourceRoot":"","sources":["../../src/analyzer/analyze-project.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAyB,EACzB,OAAyB;IAEzB,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,GAAG,YAAY,CAAC;QAClF,gBAAgB,EAAE,QAAQ,CAAC,KAAK,CAAC,gBAAgB;QACjD,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,cAAc;QAC7C,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ;QACxC,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,CAAC,SAAS;QAC9C,MAAM;KACP,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IACjE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACpE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CACrB,CAAC,MAAM,CAAC;IAET,OAAO;QACL,MAAM;QACN,UAAU;QACV,UAAU;QACV,cAAc;QACd,UAAU;QACV,OAAO,EAAE;YACP,WAAW;YACX,UAAU;YACV,WAAW,EAAE,MAAM,CAAC,MAAM;YAC1B,eAAe,EAAE,UAAU,CAAC,MAAM;YAClC,eAAe,EAAE,UAAU,CAAC,MAAM;YAClC,mBAAmB,EAAE,cAAc,CAAC,MAAM;YAC1C,eAAe,EAAE,UAAU,CAAC,MAAM;SACnC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { UnusedEntry, CandidateEntry, MismatchedEntry, SingletonRiskEntry, EagerRiskEntry } from '../types.js';
|
|
2
|
+
import type { ResolvedPolicy } from './policy.js';
|
|
3
|
+
export interface DetectIssuesInput {
|
|
4
|
+
resolvedPackages: string[];
|
|
5
|
+
packageDetails: Array<{
|
|
6
|
+
package: string;
|
|
7
|
+
importCount: number;
|
|
8
|
+
files: string[];
|
|
9
|
+
via: 'direct' | 'reexport';
|
|
10
|
+
}>;
|
|
11
|
+
sharedDeclared: Record<string, {
|
|
12
|
+
singleton?: boolean;
|
|
13
|
+
eager?: boolean;
|
|
14
|
+
requiredVersion?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** From node_modules. Empty object = not accessible, mismatch checks skipped for absent entries. */
|
|
17
|
+
installedVersions: Record<string, string>;
|
|
18
|
+
policy: ResolvedPolicy;
|
|
19
|
+
}
|
|
20
|
+
export interface DetectIssuesResult {
|
|
21
|
+
unused: UnusedEntry[];
|
|
22
|
+
candidates: CandidateEntry[];
|
|
23
|
+
mismatched: MismatchedEntry[];
|
|
24
|
+
singletonRisks: SingletonRiskEntry[];
|
|
25
|
+
eagerRisks: EagerRiskEntry[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pure function — no I/O.
|
|
29
|
+
* Cross-checks manifest data against policy to produce findings.
|
|
30
|
+
* All results are scoped to what the collector observed at the chosen depth.
|
|
31
|
+
*/
|
|
32
|
+
export declare function detectIssues(input: DetectIssuesInput): DetectIssuesResult;
|
|
33
|
+
//# sourceMappingURL=detect-issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-issues.d.ts","sourceRoot":"","sources":["../../src/analyzer/detect-issues.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,cAAc,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,KAAK,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,GAAG,EAAE,QAAQ,GAAG,UAAU,CAAC;KAC5B,CAAC,CAAC;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;QAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,oGAAoG;IACpG,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,cAAc,EAAE,kBAAkB,EAAE,CAAC;IACrC,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CA8DzE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
// ─── Core detection ───────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Pure function — no I/O.
|
|
5
|
+
* Cross-checks manifest data against policy to produce findings.
|
|
6
|
+
* All results are scoped to what the collector observed at the chosen depth.
|
|
7
|
+
*/
|
|
8
|
+
export function detectIssues(input) {
|
|
9
|
+
const resolvedSet = new Set(input.resolvedPackages);
|
|
10
|
+
const sharedEntries = Object.entries(input.sharedDeclared);
|
|
11
|
+
const detailsMap = new Map(input.packageDetails.map(d => [d.package, d]));
|
|
12
|
+
// Unused: in shared config but not observed in resolvedPackages
|
|
13
|
+
// Packages in alwaysShared are always excluded from this list.
|
|
14
|
+
const unused = sharedEntries
|
|
15
|
+
.filter(([pkg]) => !resolvedSet.has(pkg) && !input.policy.alwaysShared.has(pkg))
|
|
16
|
+
.map(([pkg, config]) => ({
|
|
17
|
+
package: pkg,
|
|
18
|
+
singleton: config.singleton ?? false,
|
|
19
|
+
}));
|
|
20
|
+
// Candidates: observed but not shared, and in the built-in share-candidates list
|
|
21
|
+
const candidates = input.resolvedPackages
|
|
22
|
+
.filter(pkg => !input.sharedDeclared[pkg] && input.policy.shareCandidates.has(pkg))
|
|
23
|
+
.map(pkg => {
|
|
24
|
+
const detail = detailsMap.get(pkg);
|
|
25
|
+
return {
|
|
26
|
+
package: pkg,
|
|
27
|
+
importCount: detail?.importCount ?? 1,
|
|
28
|
+
files: detail?.files ?? [],
|
|
29
|
+
via: detail?.via ?? 'direct',
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
// Mismatched: requiredVersion does not satisfy the installed version.
|
|
33
|
+
// Skipped when installed version is unknown (installedVersions[pkg] absent).
|
|
34
|
+
const mismatched = [];
|
|
35
|
+
for (const [pkg, config] of sharedEntries) {
|
|
36
|
+
if (!config.requiredVersion)
|
|
37
|
+
continue;
|
|
38
|
+
const installed = input.installedVersions[pkg];
|
|
39
|
+
if (!installed)
|
|
40
|
+
continue;
|
|
41
|
+
// semver.validRange returns null for invalid ranges; skip to avoid false positives
|
|
42
|
+
if (!semver.validRange(config.requiredVersion))
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
if (!semver.satisfies(installed, config.requiredVersion)) {
|
|
46
|
+
mismatched.push({
|
|
47
|
+
package: pkg,
|
|
48
|
+
configured: config.requiredVersion,
|
|
49
|
+
installed,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Defensive: skip if satisfies throws unexpectedly
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Singleton risks: in the singleton-risk list but shared without singleton: true
|
|
58
|
+
const singletonRisks = sharedEntries
|
|
59
|
+
.filter(([pkg, config]) => input.policy.singletonRisks.has(pkg) && !config.singleton)
|
|
60
|
+
.map(([pkg]) => ({ package: pkg }));
|
|
61
|
+
// Eager risks: eager: true without singleton: true.
|
|
62
|
+
// Eager-loading without singleton can produce duplicate module instances
|
|
63
|
+
// when multiple MFs initialise the same package before version negotiation.
|
|
64
|
+
const eagerRisks = sharedEntries
|
|
65
|
+
.filter(([_, config]) => config.eager === true && config.singleton !== true)
|
|
66
|
+
.map(([pkg]) => ({ package: pkg }));
|
|
67
|
+
return { unused, candidates, mismatched, singletonRisks, eagerRisks };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=detect-issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-issues.js","sourceRoot":"","sources":["../../src/analyzer/detect-issues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnalysisOptions } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Packages that are never flagged as unused by default.
|
|
4
|
+
* React is consumed transitively by JSX Transform (no explicit import needed).
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_ALWAYS_SHARED: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Packages with global state — should be shared with singleton: true.
|
|
9
|
+
* Duplicating these causes runtime errors (e.g. "Invalid hook call").
|
|
10
|
+
*/
|
|
11
|
+
export declare const SINGLETON_RISK_PACKAGES: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Packages that are typically shared across microfrontends.
|
|
14
|
+
* Used for the candidates heuristic — presence in this list + not in shared config = suggestion.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SHARE_CANDIDATE_PACKAGES: readonly string[];
|
|
17
|
+
export interface ResolvedPolicy {
|
|
18
|
+
alwaysShared: Set<string>;
|
|
19
|
+
singletonRisks: Set<string>;
|
|
20
|
+
shareCandidates: Set<string>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Merge built-in policy with user-supplied AnalysisOptions.
|
|
24
|
+
* User lists extend (not replace) the built-in lists.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mergePolicy(options?: AnalysisOptions): ResolvedPolicy;
|
|
27
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/analyzer/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAGlD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAepD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EA2BrD,CAAC;AAIF,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAgBrE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ─── Built-in lists ───────────────────────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Packages that are never flagged as unused by default.
|
|
4
|
+
* React is consumed transitively by JSX Transform (no explicit import needed).
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_ALWAYS_SHARED = [
|
|
7
|
+
'react',
|
|
8
|
+
'react-dom',
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Packages with global state — should be shared with singleton: true.
|
|
12
|
+
* Duplicating these causes runtime errors (e.g. "Invalid hook call").
|
|
13
|
+
*/
|
|
14
|
+
export const SINGLETON_RISK_PACKAGES = [
|
|
15
|
+
'react',
|
|
16
|
+
'react-dom',
|
|
17
|
+
'react-router',
|
|
18
|
+
'react-router-dom',
|
|
19
|
+
'vue',
|
|
20
|
+
'vue-router',
|
|
21
|
+
'mobx',
|
|
22
|
+
'mobx-react',
|
|
23
|
+
'mobx-react-lite',
|
|
24
|
+
'styled-components',
|
|
25
|
+
'@emotion/react',
|
|
26
|
+
'@emotion/styled',
|
|
27
|
+
'redux',
|
|
28
|
+
'@reduxjs/toolkit',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Packages that are typically shared across microfrontends.
|
|
32
|
+
* Used for the candidates heuristic — presence in this list + not in shared config = suggestion.
|
|
33
|
+
*/
|
|
34
|
+
export const SHARE_CANDIDATE_PACKAGES = [
|
|
35
|
+
// Frameworks
|
|
36
|
+
'react',
|
|
37
|
+
'react-dom',
|
|
38
|
+
'vue',
|
|
39
|
+
'svelte',
|
|
40
|
+
'solid-js',
|
|
41
|
+
// Routing
|
|
42
|
+
'react-router',
|
|
43
|
+
'react-router-dom',
|
|
44
|
+
'vue-router',
|
|
45
|
+
// State management
|
|
46
|
+
'mobx',
|
|
47
|
+
'mobx-react',
|
|
48
|
+
'mobx-react-lite',
|
|
49
|
+
'redux',
|
|
50
|
+
'@reduxjs/toolkit',
|
|
51
|
+
'zustand',
|
|
52
|
+
'jotai',
|
|
53
|
+
'recoil',
|
|
54
|
+
// Data fetching
|
|
55
|
+
'@tanstack/react-query',
|
|
56
|
+
'swr',
|
|
57
|
+
// Styling
|
|
58
|
+
'styled-components',
|
|
59
|
+
'@emotion/react',
|
|
60
|
+
'@emotion/styled',
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* Merge built-in policy with user-supplied AnalysisOptions.
|
|
64
|
+
* User lists extend (not replace) the built-in lists.
|
|
65
|
+
*/
|
|
66
|
+
export function mergePolicy(options) {
|
|
67
|
+
const alwaysShared = new Set(DEFAULT_ALWAYS_SHARED);
|
|
68
|
+
const singletonRisks = new Set(SINGLETON_RISK_PACKAGES);
|
|
69
|
+
const shareCandidates = new Set(SHARE_CANDIDATE_PACKAGES);
|
|
70
|
+
if (options?.alwaysShared) {
|
|
71
|
+
for (const pkg of options.alwaysShared)
|
|
72
|
+
alwaysShared.add(pkg);
|
|
73
|
+
}
|
|
74
|
+
if (options?.additionalSingletonRisks) {
|
|
75
|
+
for (const pkg of options.additionalSingletonRisks)
|
|
76
|
+
singletonRisks.add(pkg);
|
|
77
|
+
}
|
|
78
|
+
if (options?.additionalCandidates) {
|
|
79
|
+
for (const pkg of options.additionalCandidates)
|
|
80
|
+
shareCandidates.add(pkg);
|
|
81
|
+
}
|
|
82
|
+
return { alwaysShared, singletonRisks, shareCandidates };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/analyzer/policy.ts"],"names":[],"mappings":"AAEA,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,OAAO;IACP,WAAW;CACZ,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,OAAO;IACP,WAAW;IACX,cAAc;IACd,kBAAkB;IAClB,KAAK;IACL,YAAY;IACZ,MAAM;IACN,YAAY;IACZ,iBAAiB;IACjB,mBAAmB;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,OAAO;IACP,kBAAkB;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,aAAa;IACb,OAAO;IACP,WAAW;IACX,KAAK;IACL,QAAQ;IACR,UAAU;IACV,UAAU;IACV,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,mBAAmB;IACnB,MAAM;IACN,YAAY;IACZ,iBAAiB;IACjB,OAAO;IACP,kBAAkB;IAClB,SAAS;IACT,OAAO;IACP,QAAQ;IACR,gBAAgB;IAChB,uBAAuB;IACvB,KAAK;IACL,UAAU;IACV,mBAAmB;IACnB,gBAAgB;IAChB,iBAAiB;CAClB,CAAC;AAUF;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAyB;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS,qBAAqB,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,IAAI,GAAG,CAAS,uBAAuB,CAAC,CAAC;IAChE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAS,wBAAwB,CAAC,CAAC;IAElE,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,YAAY;YAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,OAAO,EAAE,wBAAwB,EAAE,CAAC;QACtC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,wBAAwB;YAAE,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,OAAO,EAAE,oBAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,oBAAoB;YAAE,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CollectorOptions, ProjectManifest } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase 1 of the two-phase pipeline: collect observable dependency facts
|
|
4
|
+
* and assemble them into a self-contained ProjectManifest.
|
|
5
|
+
*
|
|
6
|
+
* The manifest captures facts at the chosen depth; it does not make
|
|
7
|
+
* any policy decisions — that is the analyzer's job.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildProjectManifest(options: CollectorOptions): Promise<ProjectManifest>;
|
|
10
|
+
//# sourceMappingURL=build-project-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-project-manifest.d.ts","sourceRoot":"","sources":["../../src/collector/build-project-manifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AAQxF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CA2F1B"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { resolve, dirname } from 'node:path';
|
|
2
|
+
import { collectImports, scanFiles } from './collect-imports.js';
|
|
3
|
+
import { traverseLocalModules } from './traverse-local-modules.js';
|
|
4
|
+
import { parseSharedConfig } from './parse-shared-config.js';
|
|
5
|
+
import { resolveVersions } from './resolve-versions.js';
|
|
6
|
+
const DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
7
|
+
/**
|
|
8
|
+
* Phase 1 of the two-phase pipeline: collect observable dependency facts
|
|
9
|
+
* and assemble them into a self-contained ProjectManifest.
|
|
10
|
+
*
|
|
11
|
+
* The manifest captures facts at the chosen depth; it does not make
|
|
12
|
+
* any policy decisions — that is the analyzer's job.
|
|
13
|
+
*/
|
|
14
|
+
export async function buildProjectManifest(options) {
|
|
15
|
+
const { name, sourceDirs, depth = 'local-graph', sharedConfig, kind = 'unknown', packageJsonPath = './package.json', extensions = DEFAULT_EXTENSIONS, ignore, tsconfigPath, workspacePackages, } = options;
|
|
16
|
+
const resolvedPkgJsonPath = resolve(packageJsonPath);
|
|
17
|
+
const root = dirname(resolvedPkgJsonPath);
|
|
18
|
+
// ── Step 1: scan files (for filesScanned count) ───────────────────────────
|
|
19
|
+
const allFiles = await scanFiles(sourceDirs, extensions);
|
|
20
|
+
// ── Step 2: collect package occurrences ───────────────────────────────────
|
|
21
|
+
let occurrences;
|
|
22
|
+
let effectiveDepth;
|
|
23
|
+
if (depth === 'local-graph') {
|
|
24
|
+
occurrences = await traverseLocalModules({ sourceDirs, extensions, ignore, tsconfigPath, workspacePackages });
|
|
25
|
+
effectiveDepth = 'local-graph';
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
occurrences = await collectImports({ sourceDirs, extensions, ignore, workspacePackages });
|
|
29
|
+
effectiveDepth = 'direct';
|
|
30
|
+
}
|
|
31
|
+
// ── Step 3: aggregate occurrences into packageDetails ────────────────────
|
|
32
|
+
const byPackage = new Map();
|
|
33
|
+
for (const occ of occurrences) {
|
|
34
|
+
if (!byPackage.has(occ.package)) {
|
|
35
|
+
byPackage.set(occ.package, { files: new Set([occ.file]), via: occ.via });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const entry = byPackage.get(occ.package);
|
|
39
|
+
entry.files.add(occ.file);
|
|
40
|
+
// Direct import takes precedence over reexport at the manifest level too
|
|
41
|
+
if (occ.via === 'direct')
|
|
42
|
+
entry.via = 'direct';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const packageDetails = Array.from(byPackage.entries()).map(([pkg, { files, via }]) => ({
|
|
46
|
+
package: pkg,
|
|
47
|
+
importCount: files.size,
|
|
48
|
+
files: [...files].sort(),
|
|
49
|
+
via,
|
|
50
|
+
}));
|
|
51
|
+
const directPackages = packageDetails
|
|
52
|
+
.filter((d) => d.via === 'direct')
|
|
53
|
+
.map((d) => d.package);
|
|
54
|
+
// resolvedPackages = all observed packages (direct + reexport)
|
|
55
|
+
const resolvedPackages = packageDetails.map((d) => d.package);
|
|
56
|
+
// ── Step 4: parse shared config ───────────────────────────────────────────
|
|
57
|
+
const sharedDeclared = parseSharedConfig(sharedConfig);
|
|
58
|
+
// ── Step 5: resolve versions ──────────────────────────────────────────────
|
|
59
|
+
const versions = await resolveVersions(resolvedPkgJsonPath);
|
|
60
|
+
// ── Assemble manifest ─────────────────────────────────────────────────────
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
generatedAt: new Date().toISOString(),
|
|
64
|
+
project: { name, root, kind },
|
|
65
|
+
source: {
|
|
66
|
+
depth: effectiveDepth,
|
|
67
|
+
sourceDirs,
|
|
68
|
+
filesScanned: allFiles.length,
|
|
69
|
+
},
|
|
70
|
+
usage: {
|
|
71
|
+
directPackages,
|
|
72
|
+
resolvedPackages,
|
|
73
|
+
packageDetails,
|
|
74
|
+
},
|
|
75
|
+
shared: {
|
|
76
|
+
declared: sharedDeclared,
|
|
77
|
+
source: 'explicit',
|
|
78
|
+
},
|
|
79
|
+
versions,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=build-project-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-project-manifest.js","sourceRoot":"","sources":["../../src/collector/build-project-manifest.ts"],"names":[],"mappings":"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"}
|