apiforge-analyzer 1.2.1 → 1.3.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 +166 -197
- package/dist/cjs/arch-detect.js +133 -0
- package/dist/cjs/cli.js +497 -45
- package/dist/cjs/export.js +2 -0
- package/dist/cjs/frameworks/nestjs.js +38 -0
- package/dist/cjs/index.js +41 -3
- package/dist/cjs/modules/express.js +65 -0
- package/dist/cjs/modules/nestjs.js +50 -0
- package/dist/cjs/uploader.js +3 -1
- package/dist/esm/arch-detect.js +130 -0
- package/dist/esm/cli.js +498 -46
- package/dist/esm/export.js +2 -0
- package/dist/esm/frameworks/nestjs.js +35 -0
- package/dist/esm/index.js +39 -3
- package/dist/esm/modules/express.js +60 -0
- package/dist/esm/modules/nestjs.js +47 -0
- package/dist/esm/uploader.js +3 -1
- package/dist/types/arch-detect.d.ts +9 -0
- package/dist/types/export.d.ts +2 -0
- package/dist/types/frameworks/nestjs.d.ts +10 -0
- package/dist/types/index.d.ts +11 -1
- package/dist/types/modules/express.d.ts +22 -0
- package/dist/types/modules/nestjs.d.ts +6 -0
- package/dist/types/uploader.d.ts +7 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,279 +1,248 @@
|
|
|
1
|
-
#
|
|
1
|
+
# apiforge-analyzer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI and library for auto-generating API documentation from live Express, NestJS, Next.js, Vite, and React Router apps.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Routes are extracted at runtime by walking the framework's internal route stack — no static analysis, no decorators, no changes to your production code.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## CLI usage
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
npx apiforge
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run this from your project root. The CLI will:
|
|
14
|
+
|
|
15
|
+
1. Detect your architecture (monolithic / modular-monolithic / microservices)
|
|
16
|
+
2. Find your server entry file (or ask)
|
|
17
|
+
3. Inject a temporary analysis snippet
|
|
18
|
+
4. Start your server with the command you choose
|
|
19
|
+
5. Collect all registered routes
|
|
20
|
+
6. Upload to your APIForge dashboard for review
|
|
21
|
+
7. Remove the injected code
|
|
22
|
+
|
|
23
|
+
For **microservices**, if an `apiforge.config.json` exists the CLI iterates every service sequentially, collects routes from each, then uploads everything in one batch. If no config file exists, the CLI scans your `services/` directory, shows what it found, and writes the config file for you — so the next run is fully automatic.
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
// apiforge.config.json — auto-created on first run, commit this file
|
|
27
|
+
{
|
|
28
|
+
"projectName": "My Backend",
|
|
29
|
+
"architectureType": "microservices",
|
|
30
|
+
"services": [
|
|
31
|
+
{ "name": "auth-service", "dir": "./services/auth-service" },
|
|
32
|
+
{ "name": "user-service", "dir": "./services/user-service" },
|
|
33
|
+
{ "name": "order-service", "dir": "./services/order-service" }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each service entry can also include `"startCommand"` to override the default (`node <entryFile>`):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{ "name": "auth-service", "dir": "./services/auth", "startCommand": "npx tsx services/auth/src/index.ts" }
|
|
11
42
|
```
|
|
12
43
|
|
|
13
|
-
##
|
|
44
|
+
## Programmatic API
|
|
45
|
+
|
|
46
|
+
Install:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install apiforge-analyzer
|
|
50
|
+
```
|
|
14
51
|
|
|
15
|
-
### Express
|
|
52
|
+
### Express
|
|
16
53
|
|
|
17
|
-
```
|
|
54
|
+
```js
|
|
18
55
|
const express = require('express')
|
|
19
56
|
const { analyze } = require('apiforge-analyzer')
|
|
20
57
|
|
|
21
58
|
const app = express()
|
|
59
|
+
app.use(express.json())
|
|
22
60
|
|
|
23
|
-
app.get('/api/users',
|
|
24
|
-
app.post('/api/users',
|
|
61
|
+
app.get('/api/users', getUsers)
|
|
62
|
+
app.post('/api/users', createUser)
|
|
63
|
+
app.get('/api/users/:id', getUserById)
|
|
25
64
|
|
|
65
|
+
// Call after all routes are registered
|
|
26
66
|
analyze(app, {
|
|
27
67
|
projectName: 'My API',
|
|
28
|
-
apiKey: process.env.
|
|
29
|
-
|
|
68
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
69
|
+
baseUrl: 'https://api.myapp.com',
|
|
70
|
+
upload: true,
|
|
30
71
|
})
|
|
31
72
|
|
|
32
73
|
app.listen(3000)
|
|
33
74
|
```
|
|
34
75
|
|
|
35
|
-
###
|
|
76
|
+
### NestJS
|
|
36
77
|
|
|
37
|
-
```
|
|
38
|
-
import express from 'express'
|
|
78
|
+
```ts
|
|
39
79
|
import { analyze } from 'apiforge-analyzer'
|
|
40
80
|
|
|
41
|
-
const app =
|
|
42
|
-
|
|
43
|
-
app.get('/api/users', (req, res) => res.json({ users: [] }))
|
|
81
|
+
const app = await NestFactory.create(AppModule)
|
|
82
|
+
await app.init()
|
|
44
83
|
|
|
45
|
-
await analyze(app, {
|
|
46
|
-
projectName: 'My API',
|
|
47
|
-
apiKey: process.env.
|
|
84
|
+
await analyze(app.getHttpServer(), {
|
|
85
|
+
projectName: 'My NestJS API',
|
|
86
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
87
|
+
upload: true,
|
|
48
88
|
})
|
|
49
89
|
|
|
50
|
-
app.listen(3000)
|
|
90
|
+
await app.listen(3000)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Microservices — per-service
|
|
94
|
+
|
|
95
|
+
When running each service independently, pass `service` to tag routes with the service name:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const { analyze } = require('apiforge-analyzer')
|
|
99
|
+
|
|
100
|
+
analyze(app, {
|
|
101
|
+
projectName: 'My Platform',
|
|
102
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
103
|
+
architectureType: 'microservices',
|
|
104
|
+
service: 'auth-service',
|
|
105
|
+
upload: true,
|
|
106
|
+
})
|
|
51
107
|
```
|
|
52
108
|
|
|
53
109
|
### Next.js Pages Router
|
|
54
110
|
|
|
55
|
-
```
|
|
56
|
-
// pages/api/analyze.ts
|
|
111
|
+
```ts
|
|
57
112
|
import { analyzeNextPages } from 'apiforge-analyzer'
|
|
58
113
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
res.json({ success: true })
|
|
67
|
-
}
|
|
114
|
+
await analyzeNextPages({
|
|
115
|
+
projectName: 'My Next.js API',
|
|
116
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
117
|
+
nextDir: 'pages/api',
|
|
118
|
+
upload: true,
|
|
119
|
+
})
|
|
68
120
|
```
|
|
69
121
|
|
|
70
122
|
### Next.js App Router
|
|
71
123
|
|
|
72
|
-
```
|
|
73
|
-
// app/api/analyze/route.ts
|
|
124
|
+
```ts
|
|
74
125
|
import { analyzeNextApp } from 'apiforge-analyzer'
|
|
75
126
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return Response.json({ success: true })
|
|
84
|
-
}
|
|
127
|
+
await analyzeNextApp({
|
|
128
|
+
projectName: 'My Next.js API',
|
|
129
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
130
|
+
nextDir: 'app',
|
|
131
|
+
upload: true,
|
|
132
|
+
})
|
|
85
133
|
```
|
|
86
134
|
|
|
87
135
|
### Vite
|
|
88
136
|
|
|
89
|
-
```
|
|
90
|
-
// vite-analyze.js
|
|
137
|
+
```ts
|
|
91
138
|
import { analyzeVite } from 'apiforge-analyzer'
|
|
92
139
|
|
|
93
|
-
// Analyzes proxy routes in vite.config.js
|
|
94
140
|
await analyzeVite({
|
|
95
|
-
projectName: 'My
|
|
96
|
-
apiKey: process.env.
|
|
97
|
-
|
|
141
|
+
projectName: 'My App',
|
|
142
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
143
|
+
nextDir: 'vite.config.ts',
|
|
144
|
+
upload: true,
|
|
98
145
|
})
|
|
99
146
|
```
|
|
100
147
|
|
|
101
|
-
**Example Vite Config:**
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
// vite.config.js
|
|
105
|
-
export default {
|
|
106
|
-
server: {
|
|
107
|
-
proxy: {
|
|
108
|
-
'/api': 'http://localhost:3000',
|
|
109
|
-
'/auth': 'http://localhost:3001'
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
148
|
### React Router
|
|
116
149
|
|
|
117
|
-
```
|
|
118
|
-
// analyze-routes.js
|
|
150
|
+
```ts
|
|
119
151
|
import { analyzeReactRouter } from 'apiforge-analyzer'
|
|
120
152
|
|
|
121
|
-
// Analyzes routes from src/routes.tsx
|
|
122
153
|
await analyzeReactRouter({
|
|
123
|
-
projectName: 'My
|
|
124
|
-
apiKey: process.env.
|
|
125
|
-
|
|
154
|
+
projectName: 'My App',
|
|
155
|
+
apiKey: process.env.APIFORGE_API_KEY,
|
|
156
|
+
nextDir: 'src/routes.tsx',
|
|
157
|
+
upload: true,
|
|
126
158
|
})
|
|
127
159
|
```
|
|
128
160
|
|
|
129
|
-
|
|
161
|
+
## Options
|
|
130
162
|
|
|
131
|
-
```
|
|
132
|
-
// src/routes.tsx
|
|
133
|
-
import { createBrowserRouter } from 'react-router-dom'
|
|
134
|
-
|
|
135
|
-
export const router = createBrowserRouter([
|
|
136
|
-
{ path: '/', element: <Home /> },
|
|
137
|
-
{ path: '/users/:id', element: <UserDetail /> },
|
|
138
|
-
{ path: '/about', element: <About /> }
|
|
139
|
-
])
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## ⚙️ Configuration
|
|
143
|
-
|
|
144
|
-
### Options
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
163
|
+
```ts
|
|
147
164
|
interface AnalyzerOptions {
|
|
148
|
-
//
|
|
149
|
-
projectName: string // Name of your project
|
|
150
|
-
|
|
151
|
-
// Optional
|
|
152
|
-
baseUrl?: string // Base URL of your API
|
|
153
|
-
apiKey?: string // APIForge API key
|
|
154
|
-
apiForgeUrl?: string // APIForge server URL (default: http://localhost:3000)
|
|
155
|
-
upload?: boolean // Upload to APIForge (default: true if apiKey provided)
|
|
156
|
-
saveJson?: boolean // Save routes to JSON file
|
|
157
|
-
jsonPath?: string // Path for JSON file (default: api-export.json)
|
|
158
|
-
framework?: string // Force framework detection
|
|
159
|
-
nextDir?: string // Next.js directory or config file path
|
|
160
|
-
skipInProduction?: boolean // Skip in production (default: true)
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Environment Variables
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
APIFORGE_KEY=your-api-key
|
|
168
|
-
APIFORGE_URL=http://localhost:3000 # or https://apiforge.dev
|
|
169
|
-
NODE_ENV=development # Analysis skipped if production
|
|
170
|
-
```
|
|
165
|
+
projectName: string // required
|
|
171
166
|
|
|
172
|
-
|
|
167
|
+
apiKey?: string // APIForge API key — also read from APIFORGE_API_KEY env var
|
|
168
|
+
baseUrl?: string // base URL shown in generated docs (e.g. https://api.myapp.com)
|
|
169
|
+
apiForgeUrl?: string // override backend URL (default: https://apiforgeapi.brainfogagency.com)
|
|
173
170
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| Next.js Pages | ✅ Full | `analyzeNextPages` |
|
|
178
|
-
| Next.js App Router | ✅ Full | `analyzeNextApp` |
|
|
179
|
-
| Vite | ✅ Full | `analyzeVite` |
|
|
180
|
-
| React Router | ✅ Full | `analyzeReactRouter` |
|
|
171
|
+
upload?: boolean // upload to dashboard (default: true when apiKey is set)
|
|
172
|
+
saveJson?: boolean // write routes to a local JSON file
|
|
173
|
+
jsonPath?: string // path for the JSON file (default: api-export.json)
|
|
181
174
|
|
|
182
|
-
|
|
175
|
+
architectureType?: 'monolithic' | 'modular-monolithic' | 'microservices'
|
|
176
|
+
service?: string // service name for microservices mode — stamped on every route
|
|
183
177
|
|
|
184
|
-
|
|
178
|
+
framework?: string // skip auto-detection and force a framework
|
|
179
|
+
nextDir?: string // directory or config file path for Next.js / Vite / React Router
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await analyze(app, {
|
|
190
|
-
projectName: 'My API',
|
|
191
|
-
saveJson: true,
|
|
192
|
-
jsonPath: './docs/api.json',
|
|
193
|
-
upload: false // Don't upload
|
|
194
|
-
})
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Auto-Detection
|
|
198
|
-
|
|
199
|
-
```javascript
|
|
200
|
-
// Framework is auto-detected
|
|
201
|
-
import { analyze } from 'apiforge-analyzer'
|
|
202
|
-
|
|
203
|
-
await analyze(app, { projectName: 'My API' })
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Custom Config Paths
|
|
207
|
-
|
|
208
|
-
```javascript
|
|
209
|
-
// Vite with custom config
|
|
210
|
-
await analyzeVite({
|
|
211
|
-
projectName: 'My App',
|
|
212
|
-
nextDir: 'vite.config.ts' // Custom path
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
// React Router with custom routes file
|
|
216
|
-
await analyzeReactRouter({
|
|
217
|
-
projectName: 'My App',
|
|
218
|
-
nextDir: 'src/app/routes.tsx' // Custom path
|
|
219
|
-
})
|
|
181
|
+
skipInProduction?: boolean // skip when NODE_ENV=production (default: true)
|
|
182
|
+
runOnce?: boolean // debounce — skip if analyzed within the last 30 s (default: true)
|
|
183
|
+
}
|
|
220
184
|
```
|
|
221
185
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
186
|
+
## Architecture detection
|
|
187
|
+
|
|
188
|
+
The CLI scores structural signals to pick the right architecture automatically:
|
|
189
|
+
|
|
190
|
+
| Signal | Architecture |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `apiforge.config.json` with `services[]` | microservices |
|
|
193
|
+
| `docker-compose.yml` with 3+ node services | microservices |
|
|
194
|
+
| Multiple `package.json` files in sibling dirs | microservices |
|
|
195
|
+
| `src/modules/` or `src/features/` directory | modular-monolithic |
|
|
196
|
+
| `*.module.ts` files (NestJS `@Module`) | modular-monolithic |
|
|
197
|
+
| None of the above | monolithic |
|
|
198
|
+
|
|
199
|
+
For modular-monolithic projects, routes are automatically grouped by module using path-prefix matching — `/api/users/*` gets tagged as the `users` module.
|
|
200
|
+
|
|
201
|
+
## Output format
|
|
202
|
+
|
|
203
|
+
Each collected route:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
{
|
|
207
|
+
method: 'GET',
|
|
208
|
+
path: '/api/users/:id',
|
|
209
|
+
tag: 'users',
|
|
210
|
+
operationId: 'get-api-users-id',
|
|
211
|
+
summary: 'Get api users',
|
|
212
|
+
middleware: ['authenticate'],
|
|
213
|
+
authRequired: true,
|
|
214
|
+
pathParams: ['id'],
|
|
215
|
+
queryParams: [],
|
|
216
|
+
requestBodyFields: [],
|
|
217
|
+
responseStatuses: [200, 404],
|
|
218
|
+
framework: 'express',
|
|
219
|
+
moduleName?: 'users', // modular-monolithic only
|
|
220
|
+
serviceName?: 'auth-service' // microservices only
|
|
221
|
+
}
|
|
230
222
|
```
|
|
231
223
|
|
|
232
|
-
##
|
|
233
|
-
|
|
234
|
-
1. Sign up at [APIForge](https://apiforge.dev)
|
|
235
|
-
2. Go to Dashboard → API Keys
|
|
236
|
-
3. Generate a new key
|
|
237
|
-
4. Set `APIFORGE_KEY=your-key` in `.env`
|
|
238
|
-
|
|
239
|
-
## 🛠️ Module Support
|
|
224
|
+
## ESM and CommonJS
|
|
240
225
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```javascript
|
|
226
|
+
```js
|
|
244
227
|
// ESM
|
|
245
228
|
import { analyze } from 'apiforge-analyzer'
|
|
246
229
|
|
|
247
230
|
// CommonJS
|
|
248
231
|
const { analyze } = require('apiforge-analyzer')
|
|
249
|
-
|
|
250
|
-
// Default import (CommonJS)
|
|
251
|
-
const apiforge = require('apiforge-analyzer').default
|
|
252
232
|
```
|
|
253
233
|
|
|
254
|
-
##
|
|
255
|
-
|
|
256
|
-
- ✅ Route paths
|
|
257
|
-
- ✅ HTTP methods (GET, POST, PUT, DELETE, etc.)
|
|
258
|
-
- ✅ Path parameters (`:id`, `:userId`)
|
|
259
|
-
- ✅ Middleware names
|
|
260
|
-
- ✅ Route tags/groups
|
|
261
|
-
- ✅ Response codes
|
|
262
|
-
- ✅ Proxy configurations (Vite)
|
|
263
|
-
- ✅ Frontend routes (React Router)
|
|
264
|
-
|
|
265
|
-
## 🧪 Testing Locally
|
|
266
|
-
|
|
267
|
-
See [NPM_LINK_TEST.md](NPM_LINK_TEST.md) for detailed testing instructions using `npm link`.
|
|
268
|
-
|
|
269
|
-
## 🤝 Contributing
|
|
234
|
+
## Environment variables
|
|
270
235
|
|
|
271
|
-
|
|
236
|
+
| Variable | Description |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `APIFORGE_API_KEY` | API key — loaded automatically, no dotenv required |
|
|
239
|
+
| `APIFORGE_URL` | Override backend URL |
|
|
240
|
+
| `NODE_ENV` | Set to `production` to disable analysis |
|
|
272
241
|
|
|
273
|
-
##
|
|
242
|
+
## Get an API key
|
|
274
243
|
|
|
275
|
-
|
|
244
|
+
[apiforge.brainfogagency.com/dashboard](https://apiforge.brainfogagency.com/dashboard)
|
|
276
245
|
|
|
277
|
-
|
|
246
|
+
## License
|
|
278
247
|
|
|
279
|
-
|
|
248
|
+
MIT
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectArchitectureType = detectArchitectureType;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
function detectArchitectureType(rootDir) {
|
|
7
|
+
let microScore = 0;
|
|
8
|
+
let modularScore = 0;
|
|
9
|
+
const evidence = [];
|
|
10
|
+
const servicePaths = [];
|
|
11
|
+
const moduleNames = [];
|
|
12
|
+
// ── Microservices signals ────────────────────────────────────────────────────
|
|
13
|
+
// Definitive: config file lists services[]
|
|
14
|
+
const configPaths = [(0, path_1.join)(rootDir, 'apiforge.config.ts'), (0, path_1.join)(rootDir, '.apiforge.json')];
|
|
15
|
+
for (const configPath of configPaths) {
|
|
16
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const raw = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
19
|
+
if (raw.includes('"services"') || raw.includes("'services'") || raw.includes('services:')) {
|
|
20
|
+
microScore += 50;
|
|
21
|
+
evidence.push(`Config file with services[]: ${(0, path_1.basename)(configPath)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// docker-compose.yml with multiple node service entries
|
|
28
|
+
const composePath = (0, path_1.join)(rootDir, 'docker-compose.yml');
|
|
29
|
+
if ((0, fs_1.existsSync)(composePath)) {
|
|
30
|
+
try {
|
|
31
|
+
const compose = (0, fs_1.readFileSync)(composePath, 'utf-8');
|
|
32
|
+
const serviceMatches = compose.match(/^\s{2}\w[\w-]+:/gm) || [];
|
|
33
|
+
if (serviceMatches.length >= 3) {
|
|
34
|
+
microScore += 35;
|
|
35
|
+
evidence.push(`docker-compose.yml with ${serviceMatches.length} services`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
// Multiple sibling package.json at depth 1
|
|
41
|
+
const siblingPackages = findSiblingPackages(rootDir);
|
|
42
|
+
if (siblingPackages.length >= 2) {
|
|
43
|
+
microScore += 40;
|
|
44
|
+
siblingPackages.forEach(p => servicePaths.push(p));
|
|
45
|
+
evidence.push(`${siblingPackages.length} sibling package.json files (${siblingPackages.join(', ')})`);
|
|
46
|
+
}
|
|
47
|
+
// ── Modular Monolithic signals ────────────────────────────────────────────────
|
|
48
|
+
// NestJS module files anywhere in src/
|
|
49
|
+
const srcDir = (0, path_1.join)(rootDir, 'src');
|
|
50
|
+
if ((0, fs_1.existsSync)(srcDir)) {
|
|
51
|
+
const nestModules = findFilesRecursive(srcDir, '.module.ts');
|
|
52
|
+
if (nestModules.length > 0) {
|
|
53
|
+
modularScore += 40;
|
|
54
|
+
nestModules.forEach(f => {
|
|
55
|
+
const name = (0, path_1.basename)(f).replace('.module.ts', '');
|
|
56
|
+
if (!moduleNames.includes(name))
|
|
57
|
+
moduleNames.push(name);
|
|
58
|
+
});
|
|
59
|
+
evidence.push(`NestJS @Module files: ${nestModules.map(f => (0, path_1.basename)(f)).join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
// src/modules/, src/features/, or src/domains/
|
|
62
|
+
for (const dir of ['modules', 'features', 'domains']) {
|
|
63
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(srcDir, dir))) {
|
|
64
|
+
modularScore += 35;
|
|
65
|
+
evidence.push(`Feature-based directory: src/${dir}/`);
|
|
66
|
+
const subdirs = (0, fs_1.readdirSync)((0, path_1.join)(srcDir, dir), { withFileTypes: true })
|
|
67
|
+
.filter(d => d.isDirectory())
|
|
68
|
+
.map(d => d.name);
|
|
69
|
+
subdirs.forEach(name => { if (!moduleNames.includes(name))
|
|
70
|
+
moduleNames.push(name); });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// nest-cli.json at root
|
|
76
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(rootDir, 'nest-cli.json'))) {
|
|
77
|
+
modularScore += 20;
|
|
78
|
+
evidence.push('nest-cli.json found');
|
|
79
|
+
}
|
|
80
|
+
// Classify
|
|
81
|
+
let type = 'monolithic';
|
|
82
|
+
let confidence = 0.6;
|
|
83
|
+
if (microScore >= 40) {
|
|
84
|
+
type = 'microservices';
|
|
85
|
+
confidence = Math.min(0.5 + microScore / 100, 0.98);
|
|
86
|
+
}
|
|
87
|
+
else if (modularScore >= 35) {
|
|
88
|
+
type = 'modular-monolithic';
|
|
89
|
+
confidence = Math.min(0.5 + modularScore / 100, 0.95);
|
|
90
|
+
}
|
|
91
|
+
if (evidence.length === 0) {
|
|
92
|
+
evidence.push('Single package.json, flat route structure');
|
|
93
|
+
}
|
|
94
|
+
return { type, confidence, evidence, moduleNames, servicePaths };
|
|
95
|
+
}
|
|
96
|
+
function findSiblingPackages(rootDir) {
|
|
97
|
+
const results = [];
|
|
98
|
+
try {
|
|
99
|
+
const entries = (0, fs_1.readdirSync)(rootDir, { withFileTypes: true });
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (!entry.isDirectory())
|
|
102
|
+
continue;
|
|
103
|
+
if (['node_modules', '.git', 'dist', '.next', 'build'].includes(entry.name))
|
|
104
|
+
continue;
|
|
105
|
+
const pkgPath = (0, path_1.join)(rootDir, entry.name, 'package.json');
|
|
106
|
+
if ((0, fs_1.existsSync)(pkgPath)) {
|
|
107
|
+
results.push(`./${entry.name}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
function findFilesRecursive(dir, suffix, maxDepth = 6, depth = 0) {
|
|
115
|
+
if (depth > maxDepth)
|
|
116
|
+
return [];
|
|
117
|
+
const results = [];
|
|
118
|
+
try {
|
|
119
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.name === 'node_modules' || entry.name === 'dist')
|
|
122
|
+
continue;
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
results.push(...findFilesRecursive((0, path_1.join)(dir, entry.name), suffix, maxDepth, depth + 1));
|
|
125
|
+
}
|
|
126
|
+
else if (entry.isFile() && entry.name.endsWith(suffix)) {
|
|
127
|
+
results.push((0, path_1.join)(dir, entry.name));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
return results;
|
|
133
|
+
}
|