@mandujs/core 0.9.31 → 0.9.38
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 +382 -95
- package/package.json +3 -1
- package/src/brain/architecture/analyzer.ts +15 -7
- package/src/brain/doctor/index.ts +1 -1
- package/src/brain/doctor/reporter.ts +3 -3
- package/src/bundler/dev.ts +35 -27
- package/src/guard/analyzer.ts +350 -0
- package/src/guard/ast-analyzer.ts +806 -0
- package/src/guard/index.ts +174 -0
- package/src/guard/presets/atomic.ts +70 -0
- package/src/guard/presets/clean.ts +77 -0
- package/src/guard/presets/fsd.ts +79 -0
- package/src/guard/presets/hexagonal.ts +68 -0
- package/src/guard/presets/index.ts +155 -0
- package/src/guard/reporter.ts +445 -0
- package/src/guard/statistics.ts +572 -0
- package/src/guard/suggestions.ts +345 -0
- package/src/guard/types.ts +335 -0
- package/src/guard/validator.ts +683 -0
- package/src/guard/watcher.ts +393 -0
- package/src/index.ts +1 -0
- package/src/router/fs-patterns.ts +380 -0
- package/src/router/fs-routes.ts +389 -0
- package/src/router/fs-scanner.ts +513 -0
- package/src/router/fs-types.ts +278 -0
- package/src/router/index.ts +81 -0
- package/src/runtime/boundary.tsx +232 -0
- package/src/runtime/index.ts +1 -0
- package/src/runtime/router.test.ts +53 -0
- package/src/runtime/router.ts +143 -46
- package/src/runtime/server.ts +233 -2
- package/src/spec/schema.ts +11 -0
- package/src/watcher/rules.ts +4 -4
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>Mandu Framework Core</strong><br/>
|
|
9
|
-
|
|
9
|
+
Runtime, FS Routes, Guard, Bundler, Contract, Filling
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -21,170 +21,456 @@ bun add @mandujs/core
|
|
|
21
21
|
|
|
22
22
|
> Typically used through `@mandujs/cli`. Direct usage is for advanced use cases.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Basic API Handler (Filling)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Mandu } from "@mandujs/core";
|
|
30
|
+
|
|
31
|
+
export default Mandu.filling()
|
|
32
|
+
.guard((ctx) => {
|
|
33
|
+
if (!ctx.get("user")) return ctx.unauthorized("Login required");
|
|
34
|
+
})
|
|
35
|
+
.get(async (ctx) => {
|
|
36
|
+
const users = await db.users.findMany();
|
|
37
|
+
return ctx.ok({ data: users });
|
|
38
|
+
})
|
|
39
|
+
.post(async (ctx) => {
|
|
40
|
+
const body = await ctx.body<{ name: string }>();
|
|
41
|
+
const user = await db.users.create({ data: body });
|
|
42
|
+
return ctx.created({ data: user });
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Type-Safe Contract
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { Mandu } from "@mandujs/core";
|
|
50
|
+
import { z } from "zod";
|
|
51
|
+
|
|
52
|
+
const userContract = Mandu.contract({
|
|
53
|
+
request: {
|
|
54
|
+
GET: { query: z.object({ id: z.string() }) },
|
|
55
|
+
POST: { body: z.object({ name: z.string() }) }
|
|
56
|
+
},
|
|
57
|
+
response: {
|
|
58
|
+
200: z.object({ data: z.any() }),
|
|
59
|
+
400: z.object({ error: z.string() })
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const handlers = Mandu.handler(userContract, {
|
|
64
|
+
GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
|
|
65
|
+
POST: (ctx) => ({ data: createUser(ctx.body) })
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Module Overview
|
|
25
72
|
|
|
26
73
|
```
|
|
27
74
|
@mandujs/core
|
|
28
|
-
├──
|
|
29
|
-
├──
|
|
30
|
-
├──
|
|
31
|
-
├──
|
|
32
|
-
|
|
75
|
+
├── router/ # FS Routes - file-system based routing
|
|
76
|
+
├── guard/ # Mandu Guard - architecture enforcement
|
|
77
|
+
├── runtime/ # Server, SSR, streaming
|
|
78
|
+
├── filling/ # Handler chain API (Mandu.filling())
|
|
79
|
+
├── contract/ # Type-safe API contracts
|
|
80
|
+
├── bundler/ # Client bundling, HMR
|
|
81
|
+
├── client/ # Island hydration, client router
|
|
82
|
+
├── brain/ # Doctor, Watcher, Architecture analyzer
|
|
83
|
+
├── change/ # Transaction & history
|
|
84
|
+
└── spec/ # Manifest schema & validation
|
|
33
85
|
```
|
|
34
86
|
|
|
35
|
-
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## FS Routes
|
|
36
90
|
|
|
37
|
-
|
|
91
|
+
File-system based routing system.
|
|
38
92
|
|
|
39
93
|
```typescript
|
|
40
|
-
import {
|
|
94
|
+
import { scanRoutes, generateManifest, watchFSRoutes } from "@mandujs/core/router";
|
|
41
95
|
|
|
42
|
-
//
|
|
43
|
-
const result = await
|
|
96
|
+
// Scan routes from app/ directory
|
|
97
|
+
const result = await scanRoutes("/path/to/project");
|
|
98
|
+
console.log(result.routes);
|
|
44
99
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
manifest.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
100
|
+
// Generate manifest
|
|
101
|
+
const { manifest } = await generateManifest("/path/to/project", {
|
|
102
|
+
outputPath: ".mandu/manifest.json"
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Watch for changes
|
|
106
|
+
const watcher = await watchFSRoutes("/path/to/project", {
|
|
107
|
+
onChange: (result) => console.log("Routes updated!", result.routes.length)
|
|
108
|
+
});
|
|
51
109
|
```
|
|
52
110
|
|
|
53
|
-
###
|
|
111
|
+
### Route Patterns
|
|
54
112
|
|
|
55
113
|
```typescript
|
|
56
|
-
import {
|
|
114
|
+
import { pathToPattern, parseSegments } from "@mandujs/core/router";
|
|
115
|
+
|
|
116
|
+
// Convert path to URL pattern
|
|
117
|
+
pathToPattern("users/[id]/posts"); // → "/users/:id/posts"
|
|
118
|
+
pathToPattern("docs/[...slug]"); // → "/docs/:slug*"
|
|
119
|
+
pathToPattern("(auth)/login"); // → "/login" (group ignored)
|
|
120
|
+
|
|
121
|
+
// Parse segments
|
|
122
|
+
parseSegments("[id]"); // → [{ type: "dynamic", name: "id" }]
|
|
123
|
+
parseSegments("[...slug]"); // → [{ type: "catch-all", name: "slug" }]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
57
127
|
|
|
58
|
-
|
|
59
|
-
const lock = await writeLock("spec/spec.lock.json", manifest);
|
|
60
|
-
console.log(lock.routesHash);
|
|
128
|
+
## Mandu Guard
|
|
61
129
|
|
|
62
|
-
|
|
63
|
-
|
|
130
|
+
Real-time architecture enforcement with preset support.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import {
|
|
134
|
+
createGuardWatcher,
|
|
135
|
+
checkDirectory,
|
|
136
|
+
getPreset,
|
|
137
|
+
listPresets
|
|
138
|
+
} from "@mandujs/core/guard";
|
|
139
|
+
|
|
140
|
+
// One-time check
|
|
141
|
+
const report = await checkDirectory(
|
|
142
|
+
{ preset: "mandu" },
|
|
143
|
+
process.cwd()
|
|
144
|
+
);
|
|
145
|
+
console.log(`Violations: ${report.totalViolations}`);
|
|
146
|
+
|
|
147
|
+
// Real-time watching
|
|
148
|
+
const watcher = createGuardWatcher({
|
|
149
|
+
config: { preset: "mandu", srcDir: "src" },
|
|
150
|
+
rootDir: process.cwd(),
|
|
151
|
+
onViolation: (v) => console.log(`${v.filePath}: ${v.ruleDescription}`),
|
|
152
|
+
});
|
|
153
|
+
watcher.start();
|
|
154
|
+
|
|
155
|
+
// List available presets
|
|
156
|
+
listPresets().forEach(p => console.log(p.name, p.description));
|
|
64
157
|
```
|
|
65
158
|
|
|
66
|
-
|
|
159
|
+
### Presets
|
|
160
|
+
|
|
161
|
+
| Preset | Layers | Use Case |
|
|
162
|
+
|--------|--------|----------|
|
|
163
|
+
| `mandu` | app, pages, widgets, features, entities, api, application, domain, infra, core, shared | Fullstack (default) |
|
|
164
|
+
| `fsd` | app, pages, widgets, features, entities, shared | Frontend |
|
|
165
|
+
| `clean` | api, application, domain, infra, shared | Backend |
|
|
166
|
+
| `hexagonal` | adapters, ports, application, domain | DDD |
|
|
167
|
+
| `atomic` | pages, templates, organisms, molecules, atoms | UI |
|
|
67
168
|
|
|
68
|
-
|
|
169
|
+
### AST-based Analysis
|
|
69
170
|
|
|
70
171
|
```typescript
|
|
71
|
-
import {
|
|
172
|
+
import { extractImportsAST, analyzeModuleAST } from "@mandujs/core/guard";
|
|
72
173
|
|
|
73
|
-
|
|
174
|
+
// Extract imports with AST (more accurate than regex)
|
|
175
|
+
const imports = extractImportsAST(code);
|
|
176
|
+
// → [{ path: "./utils", type: "static", line: 1, namedImports: ["foo"] }]
|
|
74
177
|
|
|
75
|
-
|
|
76
|
-
|
|
178
|
+
// Full module analysis
|
|
179
|
+
const analysis = analyzeModuleAST(code, "src/features/user/api.ts");
|
|
77
180
|
```
|
|
78
181
|
|
|
79
|
-
###
|
|
182
|
+
### Statistics & Trends
|
|
80
183
|
|
|
81
184
|
```typescript
|
|
82
185
|
import {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} from "@mandujs/core";
|
|
186
|
+
createScanRecord,
|
|
187
|
+
addScanRecord,
|
|
188
|
+
analyzeTrend,
|
|
189
|
+
generateGuardMarkdownReport
|
|
190
|
+
} from "@mandujs/core/guard";
|
|
191
|
+
|
|
192
|
+
// Save scan for trend analysis
|
|
193
|
+
const record = createScanRecord(report, "mandu");
|
|
194
|
+
await addScanRecord(rootDir, record);
|
|
195
|
+
|
|
196
|
+
// Analyze improvement trend
|
|
197
|
+
const trend = analyzeTrend(records, 7); // 7 days
|
|
198
|
+
console.log(trend.trend); // "improving" | "stable" | "degrading"
|
|
199
|
+
|
|
200
|
+
// Generate reports
|
|
201
|
+
const markdown = generateGuardMarkdownReport(report, trend);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
88
205
|
|
|
89
|
-
|
|
90
|
-
|
|
206
|
+
## Filling API
|
|
207
|
+
|
|
208
|
+
Handler chain for business logic.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { Mandu } from "@mandujs/core";
|
|
212
|
+
|
|
213
|
+
export default Mandu.filling()
|
|
214
|
+
// Lifecycle hooks
|
|
215
|
+
.onRequest((ctx) => {
|
|
216
|
+
ctx.set("requestId", crypto.randomUUID());
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Guard (return Response to block)
|
|
220
|
+
.guard((ctx) => {
|
|
221
|
+
if (!ctx.get("user")) return ctx.unauthorized("Login required");
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Handlers
|
|
225
|
+
.get(async (ctx) => {
|
|
226
|
+
return ctx.ok({ users: await fetchUsers() });
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
.post(async (ctx) => {
|
|
230
|
+
const body = await ctx.body();
|
|
231
|
+
return ctx.created({ user: await createUser(body) });
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// After response
|
|
235
|
+
.afterResponse((ctx) => {
|
|
236
|
+
console.log("Request completed:", ctx.get("requestId"));
|
|
237
|
+
});
|
|
238
|
+
```
|
|
91
239
|
|
|
92
|
-
|
|
93
|
-
const codeWithSlot = generateApiHandlerWithSlot(route);
|
|
240
|
+
### Middleware (Compose-style)
|
|
94
241
|
|
|
95
|
-
|
|
96
|
-
|
|
242
|
+
```typescript
|
|
243
|
+
export default Mandu.filling()
|
|
244
|
+
.middleware(async (ctx, next) => {
|
|
245
|
+
console.log("before");
|
|
246
|
+
await next();
|
|
247
|
+
console.log("after");
|
|
248
|
+
})
|
|
249
|
+
.get((ctx) => ctx.ok({ ok: true }));
|
|
97
250
|
```
|
|
98
251
|
|
|
99
|
-
|
|
252
|
+
### Context API
|
|
253
|
+
|
|
254
|
+
| Method | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `ctx.ok(data)` | 200 OK |
|
|
257
|
+
| `ctx.created(data)` | 201 Created |
|
|
258
|
+
| `ctx.noContent()` | 204 No Content |
|
|
259
|
+
| `ctx.error(message)` | 400 Bad Request |
|
|
260
|
+
| `ctx.unauthorized(message)` | 401 Unauthorized |
|
|
261
|
+
| `ctx.forbidden(message)` | 403 Forbidden |
|
|
262
|
+
| `ctx.notFound(message)` | 404 Not Found |
|
|
263
|
+
| `ctx.fail(message)` | 500 Internal Server Error |
|
|
264
|
+
| `ctx.body<T>()` | Parse request body |
|
|
265
|
+
| `ctx.params` | Route parameters |
|
|
266
|
+
| `ctx.query` | Query parameters |
|
|
267
|
+
| `ctx.set(key, value)` | Store in context |
|
|
268
|
+
| `ctx.get<T>(key)` | Retrieve from context |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Contract API
|
|
100
273
|
|
|
101
|
-
|
|
274
|
+
Type-safe API contracts with Zod.
|
|
102
275
|
|
|
103
276
|
```typescript
|
|
104
|
-
import {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
277
|
+
import { Mandu } from "@mandujs/core";
|
|
278
|
+
import { z } from "zod";
|
|
279
|
+
|
|
280
|
+
// Define contract
|
|
281
|
+
const userContract = Mandu.contract({
|
|
282
|
+
request: {
|
|
283
|
+
GET: { query: z.object({ id: z.string() }) },
|
|
284
|
+
POST: { body: z.object({ name: z.string() }) }
|
|
285
|
+
},
|
|
286
|
+
response: {
|
|
287
|
+
200: z.object({ data: z.any() }),
|
|
288
|
+
400: z.object({ error: z.string() })
|
|
289
|
+
}
|
|
290
|
+
});
|
|
110
291
|
|
|
111
|
-
//
|
|
112
|
-
const
|
|
292
|
+
// Create typed handlers
|
|
293
|
+
const handlers = Mandu.handler(userContract, {
|
|
294
|
+
GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
|
|
295
|
+
POST: (ctx) => ({ data: createUser(ctx.body) })
|
|
296
|
+
});
|
|
113
297
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
298
|
+
// Type-safe client
|
|
299
|
+
const client = Mandu.client(userContract, { baseUrl: "/api/users" });
|
|
300
|
+
const result = await client.GET({ query: { id: "123" } });
|
|
301
|
+
```
|
|
118
302
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Runtime
|
|
306
|
+
|
|
307
|
+
Server and SSR.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { startServer, registerApiHandler, registerPageLoader } from "@mandujs/core";
|
|
311
|
+
|
|
312
|
+
// Register handlers
|
|
313
|
+
registerApiHandler("getUsers", async (req) => ({ users: [] }));
|
|
314
|
+
registerPageLoader("home", () => import("./pages/Home"));
|
|
315
|
+
|
|
316
|
+
// Start server
|
|
317
|
+
const server = startServer(manifest, { port: 3000 });
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Streaming SSR
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { renderToStream } from "@mandujs/core";
|
|
324
|
+
|
|
325
|
+
const stream = await renderToStream(<App />, {
|
|
326
|
+
bootstrapScripts: ["/client.js"],
|
|
327
|
+
onError: (err) => console.error(err)
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Client (Islands & Router)
|
|
334
|
+
|
|
335
|
+
### Island Hydration
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { createIsland, partial } from "@mandujs/core/client";
|
|
339
|
+
|
|
340
|
+
// Define island
|
|
341
|
+
const CounterIsland = createIsland({
|
|
342
|
+
name: "counter",
|
|
343
|
+
component: Counter,
|
|
344
|
+
priority: "visible"
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Partial (smaller than island)
|
|
348
|
+
const ButtonPartial = partial("submit-btn", SubmitButton);
|
|
124
349
|
```
|
|
125
350
|
|
|
126
|
-
###
|
|
351
|
+
### Client Router
|
|
127
352
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
353
|
+
```typescript
|
|
354
|
+
import { useRouter, useParams, Link, NavLink } from "@mandujs/core/client";
|
|
355
|
+
|
|
356
|
+
function Navigation() {
|
|
357
|
+
const router = useRouter();
|
|
358
|
+
const params = useParams();
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<nav>
|
|
362
|
+
<NavLink href="/users" activeClass="active">Users</NavLink>
|
|
363
|
+
<button onClick={() => router.push("/settings")}>Settings</button>
|
|
364
|
+
</nav>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
135
368
|
|
|
136
|
-
|
|
369
|
+
---
|
|
137
370
|
|
|
138
|
-
|
|
371
|
+
## Brain (AI Assistant)
|
|
372
|
+
|
|
373
|
+
Doctor and architecture analyzer.
|
|
139
374
|
|
|
140
375
|
```typescript
|
|
141
376
|
import {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
377
|
+
initializeBrain,
|
|
378
|
+
getBrain,
|
|
379
|
+
analyzeViolations,
|
|
380
|
+
initializeArchitectureAnalyzer
|
|
145
381
|
} from "@mandujs/core";
|
|
146
382
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
383
|
+
// Initialize
|
|
384
|
+
await initializeBrain();
|
|
385
|
+
const brain = getBrain();
|
|
386
|
+
|
|
387
|
+
// Analyze violations with suggestions
|
|
388
|
+
const analysis = await analyzeViolations(violations, { useLLM: true });
|
|
389
|
+
console.log(analysis.patches); // Suggested fixes
|
|
390
|
+
|
|
391
|
+
// Architecture analyzer
|
|
392
|
+
const analyzer = initializeArchitectureAnalyzer(rootDir);
|
|
393
|
+
const locationResult = await analyzer.checkLocation({ path: "src/features/user.ts" });
|
|
394
|
+
const importResult = await analyzer.checkImports({
|
|
395
|
+
sourceFile: "src/features/user.ts",
|
|
396
|
+
imports: ["../entities/product"]
|
|
150
397
|
});
|
|
398
|
+
```
|
|
151
399
|
|
|
152
|
-
|
|
153
|
-
registerPageLoader("homePage", () => import("./pages/Home"));
|
|
400
|
+
---
|
|
154
401
|
|
|
155
|
-
|
|
156
|
-
|
|
402
|
+
## Bundler
|
|
403
|
+
|
|
404
|
+
Client bundling with HMR.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { buildClientBundle, createDevBundler } from "@mandujs/core/bundler";
|
|
157
408
|
|
|
158
|
-
//
|
|
159
|
-
|
|
409
|
+
// Production build
|
|
410
|
+
const result = await buildClientBundle(manifest, {
|
|
411
|
+
outDir: ".mandu/client",
|
|
412
|
+
minify: true,
|
|
413
|
+
sourcemap: true
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Development with HMR
|
|
417
|
+
const devBundler = await createDevBundler(manifest, {
|
|
418
|
+
rootDir: process.cwd(),
|
|
419
|
+
isDev: true
|
|
420
|
+
});
|
|
160
421
|
```
|
|
161
422
|
|
|
162
|
-
|
|
423
|
+
---
|
|
163
424
|
|
|
164
|
-
|
|
425
|
+
## Transaction API
|
|
426
|
+
|
|
427
|
+
Atomic changes with rollback.
|
|
165
428
|
|
|
166
429
|
```typescript
|
|
167
|
-
import {
|
|
430
|
+
import { beginChange, commitChange, rollbackChange } from "@mandujs/core";
|
|
431
|
+
|
|
432
|
+
// Start transaction
|
|
433
|
+
const { changeId, snapshotId } = await beginChange(rootDir, "Add user API");
|
|
434
|
+
|
|
435
|
+
// Make changes...
|
|
168
436
|
|
|
169
|
-
|
|
170
|
-
|
|
437
|
+
// Commit or rollback
|
|
438
|
+
await commitChange(rootDir);
|
|
439
|
+
// or
|
|
440
|
+
await rollbackChange(rootDir);
|
|
171
441
|
```
|
|
172
442
|
|
|
443
|
+
---
|
|
444
|
+
|
|
173
445
|
## Types
|
|
174
446
|
|
|
175
447
|
```typescript
|
|
176
448
|
import type {
|
|
449
|
+
// Spec
|
|
177
450
|
RoutesManifest,
|
|
178
451
|
RouteSpec,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
452
|
+
|
|
453
|
+
// Guard
|
|
454
|
+
GuardPreset,
|
|
455
|
+
GuardConfig,
|
|
456
|
+
Violation,
|
|
457
|
+
ViolationReport,
|
|
458
|
+
|
|
459
|
+
// Router
|
|
460
|
+
ScanResult,
|
|
461
|
+
FSRouteConfig,
|
|
462
|
+
|
|
463
|
+
// Contract
|
|
464
|
+
ContractDefinition,
|
|
465
|
+
ContractHandlers,
|
|
466
|
+
|
|
467
|
+
// Filling
|
|
468
|
+
ManduContext,
|
|
185
469
|
} from "@mandujs/core";
|
|
186
470
|
```
|
|
187
471
|
|
|
472
|
+
---
|
|
473
|
+
|
|
188
474
|
## Requirements
|
|
189
475
|
|
|
190
476
|
- Bun >= 1.0.0
|
|
@@ -194,6 +480,7 @@ import type {
|
|
|
194
480
|
## Related Packages
|
|
195
481
|
|
|
196
482
|
- [@mandujs/cli](https://www.npmjs.com/package/@mandujs/cli) - CLI tool
|
|
483
|
+
- [@mandujs/mcp](https://www.npmjs.com/package/@mandujs/mcp) - MCP server for AI agents
|
|
197
484
|
|
|
198
485
|
## License
|
|
199
486
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.38",
|
|
4
4
|
"description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"chokidar": "^5.0.0",
|
|
52
|
+
"glob": "^13.0.0",
|
|
53
|
+
"minimatch": "^10.1.1",
|
|
52
54
|
"ollama": "^0.6.3"
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -173,9 +173,9 @@ export class ArchitectureAnalyzer {
|
|
|
173
173
|
/**
|
|
174
174
|
* 파일 위치 검증
|
|
175
175
|
*/
|
|
176
|
-
async checkLocation(request: CheckLocationRequest): Promise<CheckLocationResult> {
|
|
177
|
-
const violations: ArchitectureViolation[] = [];
|
|
178
|
-
const normalizedPath = request.path
|
|
176
|
+
async checkLocation(request: CheckLocationRequest): Promise<CheckLocationResult> {
|
|
177
|
+
const violations: ArchitectureViolation[] = [];
|
|
178
|
+
const normalizedPath = this.toRelativePath(request.path);
|
|
179
179
|
|
|
180
180
|
// 1. readonly 폴더 검사
|
|
181
181
|
for (const [key, rule] of Object.entries(this.config.folders || {})) {
|
|
@@ -288,14 +288,14 @@ export class ArchitectureAnalyzer {
|
|
|
288
288
|
/**
|
|
289
289
|
* Import 검증
|
|
290
290
|
*/
|
|
291
|
-
async checkImports(request: CheckImportRequest): Promise<CheckImportResult> {
|
|
291
|
+
async checkImports(request: CheckImportRequest): Promise<CheckImportResult> {
|
|
292
292
|
const violations: Array<{
|
|
293
293
|
import: string;
|
|
294
294
|
reason: string;
|
|
295
295
|
suggestion?: string;
|
|
296
296
|
}> = [];
|
|
297
297
|
|
|
298
|
-
const normalizedSource = request.sourceFile
|
|
298
|
+
const normalizedSource = this.toRelativePath(request.sourceFile);
|
|
299
299
|
|
|
300
300
|
for (const importPath of request.imports) {
|
|
301
301
|
for (const rule of this.config.imports || []) {
|
|
@@ -368,7 +368,7 @@ export class ArchitectureAnalyzer {
|
|
|
368
368
|
/**
|
|
369
369
|
* 코드에서 import 문 추출
|
|
370
370
|
*/
|
|
371
|
-
private extractImports(content: string): string[] {
|
|
371
|
+
private extractImports(content: string): string[] {
|
|
372
372
|
const imports: string[] = [];
|
|
373
373
|
|
|
374
374
|
// ES6 import
|
|
@@ -385,7 +385,15 @@ export class ArchitectureAnalyzer {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
return imports;
|
|
388
|
-
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private toRelativePath(filePath: string): string {
|
|
391
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
392
|
+
if (path.isAbsolute(normalized)) {
|
|
393
|
+
return path.relative(this.rootDir, normalized).replace(/\\/g, "/");
|
|
394
|
+
}
|
|
395
|
+
return normalized;
|
|
396
|
+
}
|
|
389
397
|
|
|
390
398
|
/**
|
|
391
399
|
* 폴더 스캔
|
|
@@ -222,9 +222,9 @@ export function generateJsonReport(analysis: DoctorAnalysis): string {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
|
-
* Generate a Markdown report
|
|
225
|
+
* Generate a Markdown report (Doctor Analysis)
|
|
226
226
|
*/
|
|
227
|
-
export function
|
|
227
|
+
export function generateDoctorMarkdownReport(analysis: DoctorAnalysis): string {
|
|
228
228
|
const lines: string[] = [];
|
|
229
229
|
|
|
230
230
|
lines.push("# 🩺 Mandu Doctor Report");
|
|
@@ -328,7 +328,7 @@ export function formatDoctorReport(
|
|
|
328
328
|
return generateJsonReport(analysis);
|
|
329
329
|
|
|
330
330
|
case "markdown":
|
|
331
|
-
return
|
|
331
|
+
return generateDoctorMarkdownReport(analysis);
|
|
332
332
|
|
|
333
333
|
default:
|
|
334
334
|
printDoctorReport(analysis);
|