@mandujs/cli 0.9.21 → 0.9.23
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.ko.md +57 -4
- package/README.md +62 -15
- package/package.json +2 -2
- package/src/commands/check.ts +41 -5
- package/src/commands/contract.ts +135 -9
- package/src/commands/dev.ts +155 -95
- package/src/commands/guard-arch.ts +39 -9
- package/src/commands/guard-check.ts +3 -3
- package/src/commands/init.ts +264 -9
- package/src/commands/monitor.ts +301 -0
- package/src/main.ts +421 -361
- package/templates/default/app/globals.css +37 -0
- package/templates/default/app/layout.tsx +27 -0
- package/templates/default/app/page.tsx +27 -49
- package/templates/default/package.json +20 -11
- package/templates/default/postcss.config.js +6 -0
- package/templates/default/src/client/app/index.ts +1 -0
- package/templates/default/src/client/entities/index.ts +1 -0
- package/templates/default/src/client/features/index.ts +1 -0
- package/templates/default/src/client/pages/index.ts +1 -0
- package/templates/default/src/client/shared/index.ts +1 -0
- package/templates/default/src/client/shared/lib/utils.ts +16 -0
- package/templates/default/src/client/shared/ui/button.tsx +57 -0
- package/templates/default/src/client/shared/ui/card.tsx +78 -0
- package/templates/default/src/client/shared/ui/index.ts +21 -0
- package/templates/default/src/client/shared/ui/input.tsx +24 -0
- package/templates/default/src/client/widgets/index.ts +1 -0
- package/templates/default/src/server/api/index.ts +1 -0
- package/templates/default/src/server/application/index.ts +1 -0
- package/templates/default/src/server/core/index.ts +1 -0
- package/templates/default/src/server/domain/index.ts +1 -0
- package/templates/default/src/server/infra/index.ts +1 -0
- package/templates/default/src/shared/contracts/index.ts +1 -0
- package/templates/default/src/shared/env/index.ts +1 -0
- package/templates/default/src/shared/schema/index.ts +1 -0
- package/templates/default/src/shared/types/index.ts +1 -0
- package/templates/default/src/shared/utils/client/index.ts +1 -0
- package/templates/default/src/shared/utils/server/index.ts +1 -0
- package/templates/default/tailwind.config.ts +64 -0
- package/templates/default/tsconfig.json +14 -3
package/README.ko.md
CHANGED
|
@@ -44,12 +44,35 @@ bunx @mandujs/cli init my-app
|
|
|
44
44
|
생성되는 구조:
|
|
45
45
|
```
|
|
46
46
|
my-app/
|
|
47
|
+
├── app/ # FS Routes
|
|
48
|
+
│ └── page.tsx # /
|
|
49
|
+
├── src/ # 아키텍처 레이어
|
|
50
|
+
│ ├── client/ # 클라이언트 (FSD)
|
|
51
|
+
│ │ ├── app/
|
|
52
|
+
│ │ ├── pages/
|
|
53
|
+
│ │ ├── widgets/
|
|
54
|
+
│ │ ├── features/
|
|
55
|
+
│ │ ├── entities/
|
|
56
|
+
│ │ └── shared/
|
|
57
|
+
│ ├── server/ # 서버 (Clean)
|
|
58
|
+
│ │ ├── api/
|
|
59
|
+
│ │ ├── application/
|
|
60
|
+
│ │ ├── domain/
|
|
61
|
+
│ │ ├── infra/
|
|
62
|
+
│ │ └── core/
|
|
63
|
+
│ └── shared/ # 공용
|
|
64
|
+
│ ├── contracts/ # client-safe 계약
|
|
65
|
+
│ ├── types/
|
|
66
|
+
│ ├── utils/
|
|
67
|
+
│ │ ├── client/ # 클라이언트 safe 유틸
|
|
68
|
+
│ │ └── server/ # 서버 전용 유틸
|
|
69
|
+
│ ├── schema/ # 서버 전용 스키마
|
|
70
|
+
│ └── env/ # 서버 전용 환경
|
|
47
71
|
├── apps/
|
|
48
72
|
│ ├── server/main.ts # 서버 진입점
|
|
49
73
|
│ └── web/entry.tsx # 클라이언트 진입점
|
|
50
74
|
├── spec/
|
|
51
75
|
│ └── routes.manifest.json # SSOT - 라우트 정의
|
|
52
|
-
├── tests/ # 테스트 템플릿
|
|
53
76
|
├── package.json
|
|
54
77
|
└── tsconfig.json
|
|
55
78
|
```
|
|
@@ -82,20 +105,50 @@ bun run generate
|
|
|
82
105
|
|
|
83
106
|
### `mandu guard`
|
|
84
107
|
|
|
85
|
-
아키텍처 규칙을
|
|
108
|
+
아키텍처 규칙을 검사합니다. (기본: mandu 프리셋)
|
|
86
109
|
|
|
87
110
|
```bash
|
|
88
111
|
bun run guard
|
|
89
112
|
|
|
113
|
+
# 프리셋 변경
|
|
114
|
+
bunx mandu guard --preset fsd
|
|
115
|
+
|
|
116
|
+
# CI 모드 (warning도 실패 처리)
|
|
117
|
+
bunx mandu guard --ci
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `mandu guard legacy`
|
|
121
|
+
|
|
122
|
+
레거시 Spec Guard 검사 + 자동 수정입니다.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
bunx mandu guard legacy
|
|
126
|
+
|
|
90
127
|
# 자동 수정 비활성화
|
|
91
|
-
bunx mandu guard --no-auto-correct
|
|
128
|
+
bunx mandu guard legacy --no-auto-correct
|
|
92
129
|
```
|
|
93
130
|
|
|
94
|
-
자동 수정 가능한
|
|
131
|
+
자동 수정 가능한 규칙(legacy):
|
|
95
132
|
- `SPEC_HASH_MISMATCH` → lock 파일 갱신
|
|
96
133
|
- `GENERATED_MANUAL_EDIT` → 코드 재생성
|
|
97
134
|
- `SLOT_NOT_FOUND` → slot 파일 생성
|
|
98
135
|
|
|
136
|
+
### `mandu contract build`
|
|
137
|
+
|
|
138
|
+
계약 레지스트리(`.mandu/contracts.json`)를 생성합니다.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
bunx mandu contract build
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `mandu contract diff`
|
|
145
|
+
|
|
146
|
+
계약 변경사항(major/minor/patch)을 비교합니다.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bunx mandu contract diff
|
|
150
|
+
```
|
|
151
|
+
|
|
99
152
|
## Spec 파일 작성
|
|
100
153
|
|
|
101
154
|
`spec/routes.manifest.json`이 모든 라우트의 단일 진실 공급원(SSOT)입니다.
|
package/README.md
CHANGED
|
@@ -61,6 +61,34 @@ app/
|
|
|
61
61
|
bunx mandu build
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
### Default Architecture Layout
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
app/ # FS Routes
|
|
68
|
+
src/
|
|
69
|
+
client/ # Client (FSD)
|
|
70
|
+
app/
|
|
71
|
+
pages/
|
|
72
|
+
widgets/
|
|
73
|
+
features/
|
|
74
|
+
entities/
|
|
75
|
+
shared/
|
|
76
|
+
server/ # Server (Clean)
|
|
77
|
+
api/
|
|
78
|
+
application/
|
|
79
|
+
domain/
|
|
80
|
+
infra/
|
|
81
|
+
core/
|
|
82
|
+
shared/ # Universal shared
|
|
83
|
+
contracts/ # Client-safe contracts
|
|
84
|
+
types/
|
|
85
|
+
utils/
|
|
86
|
+
client/ # Client-safe utils
|
|
87
|
+
server/ # Server-only utils
|
|
88
|
+
schema/ # Server-only schema
|
|
89
|
+
env/ # Server-only env
|
|
90
|
+
```
|
|
91
|
+
|
|
64
92
|
That's it!
|
|
65
93
|
|
|
66
94
|
---
|
|
@@ -87,11 +115,12 @@ That's it!
|
|
|
87
115
|
|
|
88
116
|
| Command | Description |
|
|
89
117
|
|---------|-------------|
|
|
90
|
-
| `mandu guard
|
|
91
|
-
| `mandu guard
|
|
92
|
-
| `mandu guard
|
|
93
|
-
| `mandu guard
|
|
94
|
-
| `mandu guard
|
|
118
|
+
| `mandu guard` | Run architecture check (default: mandu preset) |
|
|
119
|
+
| `mandu guard --watch` | Watch mode |
|
|
120
|
+
| `mandu guard --ci` | CI mode (exit 1 on errors/warnings) |
|
|
121
|
+
| `mandu guard --preset fsd` | Use specific preset |
|
|
122
|
+
| `mandu guard --output report.md` | Generate report |
|
|
123
|
+
| `mandu guard legacy` | Legacy Spec guard (auto-correct) |
|
|
95
124
|
|
|
96
125
|
### Transaction Commands
|
|
97
126
|
|
|
@@ -109,6 +138,7 @@ That's it!
|
|
|
109
138
|
|---------|-------------|
|
|
110
139
|
| `mandu doctor` | Analyze Guard failures + suggest patches |
|
|
111
140
|
| `mandu watch` | Real-time file monitoring |
|
|
141
|
+
| `mandu monitor` | MCP Activity Monitor log stream |
|
|
112
142
|
| `mandu brain setup` | Configure sLLM (optional) |
|
|
113
143
|
| `mandu brain status` | Check Brain status |
|
|
114
144
|
|
|
@@ -118,6 +148,8 @@ That's it!
|
|
|
118
148
|
|---------|-------------|
|
|
119
149
|
| `mandu contract create <routeId>` | Create contract for route |
|
|
120
150
|
| `mandu contract validate` | Validate contract-slot consistency |
|
|
151
|
+
| `mandu contract build` | Build contract registry |
|
|
152
|
+
| `mandu contract diff` | Diff contracts against registry |
|
|
121
153
|
| `mandu openapi generate` | Generate OpenAPI 3.0 spec |
|
|
122
154
|
| `mandu openapi serve` | Start Swagger UI server |
|
|
123
155
|
|
|
@@ -148,7 +180,7 @@ bun run dev
|
|
|
148
180
|
bunx mandu dev --guard
|
|
149
181
|
|
|
150
182
|
# Or run Guard separately
|
|
151
|
-
bunx mandu guard
|
|
183
|
+
bunx mandu guard --watch
|
|
152
184
|
```
|
|
153
185
|
|
|
154
186
|
### CI/CD Integration
|
|
@@ -156,7 +188,7 @@ bunx mandu guard arch --watch
|
|
|
156
188
|
```bash
|
|
157
189
|
# Build and check
|
|
158
190
|
bunx mandu build --minify
|
|
159
|
-
bunx mandu guard
|
|
191
|
+
bunx mandu guard --ci --format json
|
|
160
192
|
```
|
|
161
193
|
|
|
162
194
|
---
|
|
@@ -208,10 +240,10 @@ app/
|
|
|
208
240
|
|
|
209
241
|
```bash
|
|
210
242
|
# List all presets
|
|
211
|
-
bunx mandu guard
|
|
243
|
+
bunx mandu guard --list-presets
|
|
212
244
|
|
|
213
245
|
# Use specific preset
|
|
214
|
-
bunx mandu guard
|
|
246
|
+
bunx mandu guard --preset fsd
|
|
215
247
|
```
|
|
216
248
|
|
|
217
249
|
---
|
|
@@ -234,13 +266,13 @@ bunx mandu guard arch --preset fsd
|
|
|
234
266
|
| `--sourcemap` | Generate sourcemaps |
|
|
235
267
|
| `--watch` | Watch mode |
|
|
236
268
|
|
|
237
|
-
### `mandu guard
|
|
269
|
+
### `mandu guard`
|
|
238
270
|
|
|
239
271
|
| Option | Description |
|
|
240
272
|
|--------|-------------|
|
|
241
273
|
| `--preset <p>` | Preset: fsd, clean, hexagonal, atomic, mandu |
|
|
242
274
|
| `--watch` | Watch mode |
|
|
243
|
-
| `--ci` | CI mode (exit 1 on errors) |
|
|
275
|
+
| `--ci` | CI mode (exit 1 on errors/warnings) |
|
|
244
276
|
| `--quiet` | Summary only |
|
|
245
277
|
| `--format <f>` | Output: console, agent, json |
|
|
246
278
|
| `--output <path>` | Report file path |
|
|
@@ -256,6 +288,16 @@ bunx mandu guard arch --preset fsd
|
|
|
256
288
|
| `--no-llm` | Template mode (no LLM) |
|
|
257
289
|
| `--output <path>` | Output file path |
|
|
258
290
|
|
|
291
|
+
### `mandu monitor`
|
|
292
|
+
|
|
293
|
+
| Option | Description |
|
|
294
|
+
|--------|-------------|
|
|
295
|
+
| `--format <f>` | Output: console, agent, json |
|
|
296
|
+
| `--summary` | Print summary (JSON log only) |
|
|
297
|
+
| `--since <d>` | Summary window: 5m, 30s, 1h |
|
|
298
|
+
| `--follow <bool>` | Follow mode (default: true) |
|
|
299
|
+
| `--file <path>` | Use custom log file |
|
|
300
|
+
|
|
259
301
|
---
|
|
260
302
|
|
|
261
303
|
## Examples
|
|
@@ -273,10 +315,11 @@ bunx mandu routes list
|
|
|
273
315
|
bunx mandu routes generate
|
|
274
316
|
|
|
275
317
|
# Guard
|
|
276
|
-
bunx mandu guard
|
|
277
|
-
bunx mandu guard
|
|
278
|
-
bunx mandu guard
|
|
279
|
-
bunx mandu guard
|
|
318
|
+
bunx mandu guard
|
|
319
|
+
bunx mandu guard --watch
|
|
320
|
+
bunx mandu guard --ci --format json
|
|
321
|
+
bunx mandu guard --output report.md
|
|
322
|
+
bunx mandu guard legacy
|
|
280
323
|
|
|
281
324
|
# Transactions
|
|
282
325
|
bunx mandu change begin --message "Add users API"
|
|
@@ -287,6 +330,10 @@ bunx mandu change rollback
|
|
|
287
330
|
bunx mandu doctor
|
|
288
331
|
bunx mandu doctor --format json
|
|
289
332
|
|
|
333
|
+
# Monitor
|
|
334
|
+
bunx mandu monitor
|
|
335
|
+
bunx mandu monitor --summary --since 5m
|
|
336
|
+
|
|
290
337
|
# Build
|
|
291
338
|
bunx mandu build --minify --sourcemap
|
|
292
339
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.23",
|
|
4
4
|
"description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mandujs/core": "0.9.
|
|
35
|
+
"@mandujs/core": "0.9.39"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"bun": ">=1.0.0"
|
package/src/commands/check.ts
CHANGED
|
@@ -34,6 +34,7 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
34
34
|
const preset = options.preset ?? "mandu";
|
|
35
35
|
const format = resolveOutputFormat(options.format);
|
|
36
36
|
const quiet = options.quiet === true;
|
|
37
|
+
const strictWarnings = options.ci === true;
|
|
37
38
|
const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
|
|
38
39
|
const specPath = resolveFromCwd("spec/routes.manifest.json");
|
|
39
40
|
const hasSpec = await pathExists(specPath);
|
|
@@ -122,14 +123,45 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
122
123
|
fsRoutes: enableFsRoutes
|
|
123
124
|
? {
|
|
124
125
|
noPageToPage: true,
|
|
125
|
-
pageCanImport: [
|
|
126
|
-
|
|
126
|
+
pageCanImport: [
|
|
127
|
+
"client/pages",
|
|
128
|
+
"client/widgets",
|
|
129
|
+
"client/features",
|
|
130
|
+
"client/entities",
|
|
131
|
+
"client/shared",
|
|
132
|
+
"shared/contracts",
|
|
133
|
+
"shared/types",
|
|
134
|
+
"shared/utils/client",
|
|
135
|
+
],
|
|
136
|
+
layoutCanImport: [
|
|
137
|
+
"client/app",
|
|
138
|
+
"client/widgets",
|
|
139
|
+
"client/shared",
|
|
140
|
+
"shared/contracts",
|
|
141
|
+
"shared/types",
|
|
142
|
+
"shared/utils/client",
|
|
143
|
+
],
|
|
144
|
+
routeCanImport: [
|
|
145
|
+
"server/api",
|
|
146
|
+
"server/application",
|
|
147
|
+
"server/domain",
|
|
148
|
+
"server/infra",
|
|
149
|
+
"server/core",
|
|
150
|
+
"shared/contracts",
|
|
151
|
+
"shared/schema",
|
|
152
|
+
"shared/types",
|
|
153
|
+
"shared/utils/client",
|
|
154
|
+
"shared/utils/server",
|
|
155
|
+
"shared/env",
|
|
156
|
+
],
|
|
127
157
|
}
|
|
128
158
|
: undefined,
|
|
129
159
|
};
|
|
130
160
|
|
|
131
161
|
const report = await checkDirectory(guardConfig, rootDir);
|
|
132
|
-
|
|
162
|
+
const hasArchErrors = report.bySeverity.error > 0;
|
|
163
|
+
const hasArchWarnings = report.bySeverity.warn > 0;
|
|
164
|
+
if (hasArchErrors || (strictWarnings && hasArchWarnings)) {
|
|
133
165
|
success = false;
|
|
134
166
|
}
|
|
135
167
|
|
|
@@ -166,7 +198,11 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
166
198
|
const checkResult = await runGuardCheck(manifestResult.data, rootDir);
|
|
167
199
|
legacySummary.passed = checkResult.passed;
|
|
168
200
|
legacySummary.violations = checkResult.violations.length;
|
|
169
|
-
|
|
201
|
+
if (strictWarnings && checkResult.violations.length > 0) {
|
|
202
|
+
success = false;
|
|
203
|
+
} else {
|
|
204
|
+
success = success && checkResult.passed;
|
|
205
|
+
}
|
|
170
206
|
|
|
171
207
|
if (format === "console") {
|
|
172
208
|
const legacyReport = buildGuardReport(checkResult);
|
|
@@ -200,5 +236,5 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
|
200
236
|
console.log(JSON.stringify(summary, null, 2));
|
|
201
237
|
}
|
|
202
238
|
|
|
203
|
-
return
|
|
239
|
+
return success;
|
|
204
240
|
}
|
package/src/commands/contract.ts
CHANGED
|
@@ -3,17 +3,36 @@
|
|
|
3
3
|
* Contract 생성 및 검증 명령어
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
loadManifest,
|
|
8
|
+
runContractGuardCheck,
|
|
9
|
+
generateContractTemplate,
|
|
10
|
+
buildContractRegistry,
|
|
11
|
+
writeContractRegistry,
|
|
12
|
+
readContractRegistry,
|
|
13
|
+
diffContractRegistry,
|
|
14
|
+
} from "@mandujs/core";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import fs from "fs/promises";
|
|
9
17
|
|
|
10
18
|
interface ContractCreateOptions {
|
|
11
19
|
routeId: string;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
interface ContractValidateOptions {
|
|
15
|
-
verbose?: boolean;
|
|
16
|
-
}
|
|
22
|
+
interface ContractValidateOptions {
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ContractBuildOptions {
|
|
27
|
+
output?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ContractDiffOptions {
|
|
31
|
+
from?: string;
|
|
32
|
+
to?: string;
|
|
33
|
+
output?: string;
|
|
34
|
+
json?: boolean;
|
|
35
|
+
}
|
|
17
36
|
|
|
18
37
|
/**
|
|
19
38
|
* Create a new contract file for a route
|
|
@@ -85,7 +104,7 @@ export async function contractCreate(options: ContractCreateOptions): Promise<bo
|
|
|
85
104
|
/**
|
|
86
105
|
* Validate all contracts against their slot implementations
|
|
87
106
|
*/
|
|
88
|
-
export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
|
|
107
|
+
export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
|
|
89
108
|
const rootDir = process.cwd();
|
|
90
109
|
const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
|
|
91
110
|
|
|
@@ -148,5 +167,112 @@ export async function contractValidate(options: ContractValidateOptions = {}): P
|
|
|
148
167
|
console.log(`💡 Use --verbose for fix suggestions\n`);
|
|
149
168
|
}
|
|
150
169
|
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build contract registry (.mandu/contracts.json)
|
|
175
|
+
*/
|
|
176
|
+
export async function contractBuild(options: ContractBuildOptions = {}): Promise<boolean> {
|
|
177
|
+
const rootDir = process.cwd();
|
|
178
|
+
const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
|
|
179
|
+
const outputPath = options.output || path.join(rootDir, ".mandu", "contracts.json");
|
|
180
|
+
|
|
181
|
+
console.log(`\n📦 Building contract registry...\n`);
|
|
182
|
+
|
|
183
|
+
const manifestResult = await loadManifest(manifestPath);
|
|
184
|
+
if (!manifestResult.success) {
|
|
185
|
+
console.error("❌ Failed to load manifest:", manifestResult.errors);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const manifest = manifestResult.data!;
|
|
190
|
+
const { registry, warnings } = await buildContractRegistry(manifest, rootDir);
|
|
191
|
+
|
|
192
|
+
if (warnings.length > 0) {
|
|
193
|
+
console.log(`⚠️ ${warnings.length} warning(s):`);
|
|
194
|
+
for (const warning of warnings) {
|
|
195
|
+
console.log(` - ${warning}`);
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
201
|
+
await writeContractRegistry(outputPath, registry);
|
|
202
|
+
|
|
203
|
+
console.log(`✅ Registry generated: ${path.relative(rootDir, outputPath)}`);
|
|
204
|
+
console.log(`📊 Contracts: ${registry.contracts.length}`);
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Diff current contracts against a registry
|
|
211
|
+
*/
|
|
212
|
+
export async function contractDiff(options: ContractDiffOptions = {}): Promise<boolean> {
|
|
213
|
+
const rootDir = process.cwd();
|
|
214
|
+
const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
|
|
215
|
+
const fromPath = options.from || path.join(rootDir, ".mandu", "contracts.json");
|
|
216
|
+
|
|
217
|
+
console.log(`\n🔍 Diffing contracts...\n`);
|
|
218
|
+
|
|
219
|
+
const fromRegistry = await readContractRegistry(fromPath);
|
|
220
|
+
if (!fromRegistry) {
|
|
221
|
+
console.error(`❌ Registry not found: ${path.relative(rootDir, fromPath)}`);
|
|
222
|
+
console.log(`💡 Run \`mandu contract build\` first.`);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let toRegistry = options.to ? await readContractRegistry(options.to) : null;
|
|
227
|
+
|
|
228
|
+
if (!toRegistry) {
|
|
229
|
+
const manifestResult = await loadManifest(manifestPath);
|
|
230
|
+
if (!manifestResult.success) {
|
|
231
|
+
console.error("❌ Failed to load manifest:", manifestResult.errors);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const { registry } = await buildContractRegistry(manifestResult.data!, rootDir);
|
|
235
|
+
toRegistry = registry;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const diff = diffContractRegistry(fromRegistry, toRegistry);
|
|
239
|
+
|
|
240
|
+
if (options.output) {
|
|
241
|
+
await fs.mkdir(path.dirname(options.output), { recursive: true });
|
|
242
|
+
await Bun.write(options.output, JSON.stringify(diff, null, 2));
|
|
243
|
+
console.log(`✅ Diff saved: ${path.relative(rootDir, options.output)}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (options.json) {
|
|
247
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
248
|
+
return diff.summary.major === 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`📊 Summary: major ${diff.summary.major}, minor ${diff.summary.minor}, patch ${diff.summary.patch}`);
|
|
252
|
+
|
|
253
|
+
if (diff.added.length > 0) {
|
|
254
|
+
console.log(`\n🟢 Added (${diff.added.length})`);
|
|
255
|
+
for (const entry of diff.added) {
|
|
256
|
+
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (diff.removed.length > 0) {
|
|
261
|
+
console.log(`\n🔴 Removed (${diff.removed.length})`);
|
|
262
|
+
for (const entry of diff.removed) {
|
|
263
|
+
console.log(` - ${entry.id} (${entry.routeId})`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (diff.changed.length > 0) {
|
|
268
|
+
console.log(`\n🟡 Changed (${diff.changed.length})`);
|
|
269
|
+
for (const change of diff.changed) {
|
|
270
|
+
console.log(` - ${change.id} (${change.routeId}) [${change.severity}]`);
|
|
271
|
+
for (const detail of change.changes) {
|
|
272
|
+
console.log(` • ${detail}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return diff.summary.major === 0;
|
|
278
|
+
}
|