@mandujs/cli 0.9.17 → 0.9.19
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 +224 -98
- package/package.json +2 -2
- package/src/commands/check.ts +204 -0
- package/src/commands/dev.ts +35 -22
- package/src/commands/doctor.ts +2 -2
- package/src/commands/guard-arch.ts +38 -18
- package/src/main.ts +73 -53
- package/src/util/fs.ts +23 -4
- package/src/util/output.ts +41 -0
- package/templates/default/app/api/health/route.ts +13 -0
- package/templates/default/app/page.tsx +60 -0
- package/templates/default/package.json +3 -7
- package/templates/default/spec/routes.manifest.json +2 -16
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>Agent-Native Fullstack Framework CLI</strong><br/>
|
|
9
|
-
|
|
9
|
+
Architecture stays intact even when AI agents write your code
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -16,47 +16,26 @@
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
# Bun required
|
|
20
19
|
bun add -D @mandujs/cli
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Or use directly with `bunx`:
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
# Create a new project
|
|
27
25
|
bunx @mandujs/cli init my-app
|
|
28
|
-
cd my-app
|
|
29
|
-
|
|
30
|
-
# Start development server
|
|
31
|
-
bun run dev
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
### `mandu init <project-name>`
|
|
28
|
+
## Quick Start
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
### 1. Create a New Project
|
|
39
31
|
|
|
40
32
|
```bash
|
|
41
33
|
bunx @mandujs/cli init my-app
|
|
34
|
+
cd my-app
|
|
35
|
+
bun install
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
my-app/
|
|
47
|
-
├── apps/
|
|
48
|
-
│ ├── server/main.ts # Server entry point
|
|
49
|
-
│ └── web/entry.tsx # Client entry point
|
|
50
|
-
├── spec/
|
|
51
|
-
│ └── routes.manifest.json # SSOT - Route definitions
|
|
52
|
-
├── tests/ # Test templates
|
|
53
|
-
├── package.json
|
|
54
|
-
└── tsconfig.json
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### `mandu dev`
|
|
58
|
-
|
|
59
|
-
Starts the development server (with HMR support).
|
|
38
|
+
### 2. Start Development Server
|
|
60
39
|
|
|
61
40
|
```bash
|
|
62
41
|
bun run dev
|
|
@@ -64,117 +43,264 @@ bun run dev
|
|
|
64
43
|
bunx mandu dev
|
|
65
44
|
```
|
|
66
45
|
|
|
67
|
-
|
|
46
|
+
Your app is now running at `http://localhost:3000`.
|
|
47
|
+
|
|
48
|
+
### 3. Create Pages in `app/` Directory
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
app/
|
|
52
|
+
├── page.tsx → /
|
|
53
|
+
├── about/page.tsx → /about
|
|
54
|
+
├── users/[id]/page.tsx → /users/:id
|
|
55
|
+
└── api/hello/route.ts → /api/hello
|
|
56
|
+
```
|
|
68
57
|
|
|
69
|
-
|
|
58
|
+
### 4. Build for Production
|
|
70
59
|
|
|
71
60
|
```bash
|
|
72
|
-
|
|
61
|
+
bunx mandu build
|
|
73
62
|
```
|
|
74
63
|
|
|
75
|
-
|
|
64
|
+
That's it!
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
### Core Commands
|
|
71
|
+
|
|
72
|
+
| Command | Description |
|
|
73
|
+
|---------|-------------|
|
|
74
|
+
| `mandu init [name]` | Create new project |
|
|
75
|
+
| `mandu dev` | Start dev server (FS Routes + HMR) |
|
|
76
|
+
| `mandu build` | Build for production |
|
|
77
|
+
|
|
78
|
+
### FS Routes Commands
|
|
79
|
+
|
|
80
|
+
| Command | Description |
|
|
81
|
+
|---------|-------------|
|
|
82
|
+
| `mandu routes list` | Show all routes |
|
|
83
|
+
| `mandu routes generate` | Generate routes manifest |
|
|
84
|
+
| `mandu routes watch` | Watch for route changes |
|
|
85
|
+
|
|
86
|
+
### Guard Commands
|
|
87
|
+
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---------|-------------|
|
|
90
|
+
| `mandu guard arch` | Run architecture check (default: mandu preset) |
|
|
91
|
+
| `mandu guard arch --watch` | Watch mode |
|
|
92
|
+
| `mandu guard arch --ci` | CI mode (exit 1 on errors) |
|
|
93
|
+
| `mandu guard arch --preset fsd` | Use specific preset |
|
|
94
|
+
| `mandu guard arch --output report.md` | Generate report |
|
|
95
|
+
|
|
96
|
+
### Transaction Commands
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `mandu change begin` | Start transaction (creates snapshot) |
|
|
101
|
+
| `mandu change commit` | Finalize changes |
|
|
102
|
+
| `mandu change rollback` | Restore from snapshot |
|
|
103
|
+
| `mandu change status` | Show current state |
|
|
104
|
+
| `mandu change list` | View history |
|
|
105
|
+
|
|
106
|
+
### Brain Commands
|
|
107
|
+
|
|
108
|
+
| Command | Description |
|
|
109
|
+
|---------|-------------|
|
|
110
|
+
| `mandu doctor` | Analyze Guard failures + suggest patches |
|
|
111
|
+
| `mandu watch` | Real-time file monitoring |
|
|
112
|
+
| `mandu brain setup` | Configure sLLM (optional) |
|
|
113
|
+
| `mandu brain status` | Check Brain status |
|
|
114
|
+
|
|
115
|
+
### Contract & OpenAPI Commands
|
|
116
|
+
|
|
117
|
+
| Command | Description |
|
|
118
|
+
|---------|-------------|
|
|
119
|
+
| `mandu contract create <routeId>` | Create contract for route |
|
|
120
|
+
| `mandu contract validate` | Validate contract-slot consistency |
|
|
121
|
+
| `mandu openapi generate` | Generate OpenAPI 3.0 spec |
|
|
122
|
+
| `mandu openapi serve` | Start Swagger UI server |
|
|
123
|
+
|
|
124
|
+
---
|
|
76
125
|
|
|
77
|
-
|
|
126
|
+
## Workflow
|
|
127
|
+
|
|
128
|
+
### Modern Workflow (Recommended)
|
|
78
129
|
|
|
79
130
|
```bash
|
|
80
|
-
|
|
81
|
-
|
|
131
|
+
# 1. Create project
|
|
132
|
+
bunx @mandujs/cli init my-app
|
|
133
|
+
cd my-app && bun install
|
|
134
|
+
|
|
135
|
+
# 2. Create pages
|
|
136
|
+
# app/page.tsx → /
|
|
137
|
+
# app/users/page.tsx → /users
|
|
138
|
+
# app/api/users/route.ts → /api/users
|
|
82
139
|
|
|
83
|
-
|
|
140
|
+
# 3. Start development
|
|
141
|
+
bun run dev
|
|
142
|
+
```
|
|
84
143
|
|
|
85
|
-
|
|
144
|
+
### With Architecture Guard
|
|
86
145
|
|
|
87
146
|
```bash
|
|
88
|
-
|
|
147
|
+
# Development with Guard watching
|
|
148
|
+
bunx mandu dev --guard
|
|
89
149
|
|
|
90
|
-
#
|
|
91
|
-
bunx mandu guard --
|
|
150
|
+
# Or run Guard separately
|
|
151
|
+
bunx mandu guard arch --watch
|
|
92
152
|
```
|
|
93
153
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`spec/routes.manifest.json` is the Single Source of Truth (SSOT) for all routes.
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
{
|
|
105
|
-
"version": "1.0.0",
|
|
106
|
-
"routes": [
|
|
107
|
-
{
|
|
108
|
-
"id": "getUsers",
|
|
109
|
-
"pattern": "/api/users",
|
|
110
|
-
"kind": "api",
|
|
111
|
-
"module": "apps/server/api/users.ts"
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
"id": "homePage",
|
|
115
|
-
"pattern": "/",
|
|
116
|
-
"kind": "page",
|
|
117
|
-
"module": "apps/server/pages/home.ts",
|
|
118
|
-
"componentModule": "apps/web/pages/Home.tsx"
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
}
|
|
154
|
+
### CI/CD Integration
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Build and check
|
|
158
|
+
bunx mandu build --minify
|
|
159
|
+
bunx mandu guard arch --ci --format json
|
|
122
160
|
```
|
|
123
161
|
|
|
124
|
-
|
|
162
|
+
---
|
|
125
163
|
|
|
126
|
-
|
|
164
|
+
## FS Routes
|
|
127
165
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
Create routes by adding files to `app/`:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
app/
|
|
170
|
+
├── page.tsx → /
|
|
171
|
+
├── layout.tsx → Layout for all pages
|
|
172
|
+
├── users/
|
|
173
|
+
│ ├── page.tsx → /users
|
|
174
|
+
│ ├── [id]/
|
|
175
|
+
│ │ └── page.tsx → /users/:id
|
|
176
|
+
│ └── [...slug]/
|
|
177
|
+
│ └── page.tsx → /users/*
|
|
178
|
+
├── api/
|
|
179
|
+
│ └── users/
|
|
180
|
+
│ └── route.ts → /api/users
|
|
181
|
+
└── (auth)/ → Group (no URL segment)
|
|
182
|
+
└── login/page.tsx → /login
|
|
136
183
|
```
|
|
137
184
|
|
|
138
|
-
|
|
139
|
-
- `*.slot.ts` - Business logic written by developers
|
|
185
|
+
### Special Files
|
|
140
186
|
|
|
141
|
-
|
|
187
|
+
| File | Purpose |
|
|
188
|
+
|------|---------|
|
|
189
|
+
| `page.tsx` | Page component |
|
|
190
|
+
| `layout.tsx` | Shared layout |
|
|
191
|
+
| `route.ts` | API endpoint |
|
|
192
|
+
| `loading.tsx` | Loading state |
|
|
193
|
+
| `error.tsx` | Error boundary |
|
|
194
|
+
| `slot.ts` | Business logic |
|
|
195
|
+
| `client.tsx` | Interactive component (Island) |
|
|
142
196
|
|
|
143
|
-
|
|
144
|
-
# 1. Edit spec
|
|
145
|
-
# 2. Validate spec and update lock
|
|
146
|
-
bun run spec
|
|
197
|
+
---
|
|
147
198
|
|
|
148
|
-
|
|
149
|
-
bun run generate
|
|
199
|
+
## Guard Presets
|
|
150
200
|
|
|
151
|
-
|
|
152
|
-
|
|
201
|
+
| Preset | Description |
|
|
202
|
+
|--------|-------------|
|
|
203
|
+
| `mandu` | FSD + Clean Architecture (default) |
|
|
204
|
+
| `fsd` | Feature-Sliced Design |
|
|
205
|
+
| `clean` | Clean Architecture |
|
|
206
|
+
| `hexagonal` | Hexagonal Architecture |
|
|
207
|
+
| `atomic` | Atomic Design |
|
|
153
208
|
|
|
154
|
-
|
|
155
|
-
|
|
209
|
+
```bash
|
|
210
|
+
# List all presets
|
|
211
|
+
bunx mandu guard arch --list-presets
|
|
156
212
|
|
|
157
|
-
#
|
|
158
|
-
|
|
213
|
+
# Use specific preset
|
|
214
|
+
bunx mandu guard arch --preset fsd
|
|
159
215
|
```
|
|
160
216
|
|
|
161
|
-
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Options Reference
|
|
220
|
+
|
|
221
|
+
### `mandu dev`
|
|
222
|
+
|
|
223
|
+
| Option | Description |
|
|
224
|
+
|--------|-------------|
|
|
225
|
+
| `--port <n>` | Server port (default: 3000) |
|
|
226
|
+
| `--guard` | Enable Guard watching |
|
|
227
|
+
| `--guard-preset <p>` | Guard preset (default: mandu) |
|
|
228
|
+
|
|
229
|
+
### `mandu build`
|
|
230
|
+
|
|
231
|
+
| Option | Description |
|
|
232
|
+
|--------|-------------|
|
|
233
|
+
| `--minify` | Minify output |
|
|
234
|
+
| `--sourcemap` | Generate sourcemaps |
|
|
235
|
+
| `--watch` | Watch mode |
|
|
236
|
+
|
|
237
|
+
### `mandu guard arch`
|
|
238
|
+
|
|
239
|
+
| Option | Description |
|
|
240
|
+
|--------|-------------|
|
|
241
|
+
| `--preset <p>` | Preset: fsd, clean, hexagonal, atomic, mandu |
|
|
242
|
+
| `--watch` | Watch mode |
|
|
243
|
+
| `--ci` | CI mode (exit 1 on errors) |
|
|
244
|
+
| `--quiet` | Summary only |
|
|
245
|
+
| `--format <f>` | Output: console, agent, json |
|
|
246
|
+
| `--output <path>` | Report file path |
|
|
247
|
+
| `--report-format <f>` | Report: json, markdown, html |
|
|
248
|
+
| `--save-stats` | Save for trend analysis |
|
|
249
|
+
| `--show-trend` | Show trend analysis |
|
|
250
|
+
|
|
251
|
+
### `mandu doctor`
|
|
252
|
+
|
|
253
|
+
| Option | Description |
|
|
254
|
+
|--------|-------------|
|
|
255
|
+
| `--format <f>` | Output: console, json, markdown |
|
|
256
|
+
| `--no-llm` | Template mode (no LLM) |
|
|
257
|
+
| `--output <path>` | Output file path |
|
|
258
|
+
|
|
259
|
+
---
|
|
162
260
|
|
|
163
|
-
|
|
261
|
+
## Examples
|
|
164
262
|
|
|
165
263
|
```bash
|
|
166
|
-
|
|
167
|
-
|
|
264
|
+
# Initialize project
|
|
265
|
+
bunx @mandujs/cli init my-app
|
|
266
|
+
|
|
267
|
+
# Development
|
|
268
|
+
bunx mandu dev --port 3000
|
|
269
|
+
bunx mandu dev --guard
|
|
270
|
+
|
|
271
|
+
# Routes
|
|
272
|
+
bunx mandu routes list
|
|
273
|
+
bunx mandu routes generate
|
|
274
|
+
|
|
275
|
+
# Guard
|
|
276
|
+
bunx mandu guard arch
|
|
277
|
+
bunx mandu guard arch --watch
|
|
278
|
+
bunx mandu guard arch --ci --format json
|
|
279
|
+
bunx mandu guard arch --output report.md
|
|
280
|
+
|
|
281
|
+
# Transactions
|
|
282
|
+
bunx mandu change begin --message "Add users API"
|
|
283
|
+
bunx mandu change commit
|
|
284
|
+
bunx mandu change rollback
|
|
285
|
+
|
|
286
|
+
# Doctor
|
|
287
|
+
bunx mandu doctor
|
|
288
|
+
bunx mandu doctor --format json
|
|
289
|
+
|
|
290
|
+
# Build
|
|
291
|
+
bunx mandu build --minify --sourcemap
|
|
168
292
|
```
|
|
169
293
|
|
|
294
|
+
---
|
|
295
|
+
|
|
170
296
|
## Requirements
|
|
171
297
|
|
|
172
298
|
- Bun >= 1.0.0
|
|
173
|
-
- React >= 18.0.0
|
|
174
299
|
|
|
175
300
|
## Related Packages
|
|
176
301
|
|
|
177
302
|
- [@mandujs/core](https://www.npmjs.com/package/@mandujs/core) - Core runtime
|
|
303
|
+
- [@mandujs/mcp](https://www.npmjs.com/package/@mandujs/mcp) - MCP server
|
|
178
304
|
|
|
179
305
|
## License
|
|
180
306
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.19",
|
|
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.38"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"bun": ">=1.0.0"
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mandu check - Workflow Check Command
|
|
3
|
+
*
|
|
4
|
+
* FS Routes + Architecture Guard + Legacy Guard 통합 검사
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
generateManifest,
|
|
9
|
+
scanRoutes,
|
|
10
|
+
checkDirectory,
|
|
11
|
+
printReport,
|
|
12
|
+
getPreset,
|
|
13
|
+
loadManifest,
|
|
14
|
+
runGuardCheck,
|
|
15
|
+
buildGuardReport,
|
|
16
|
+
printReportSummary,
|
|
17
|
+
type GuardConfig,
|
|
18
|
+
type GuardPreset,
|
|
19
|
+
} from "@mandujs/core";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import { resolveFromCwd, isDirectory, pathExists } from "../util/fs";
|
|
22
|
+
import { resolveOutputFormat, type OutputFormat } from "../util/output";
|
|
23
|
+
|
|
24
|
+
export interface CheckOptions {
|
|
25
|
+
preset?: GuardPreset;
|
|
26
|
+
format?: OutputFormat;
|
|
27
|
+
ci?: boolean;
|
|
28
|
+
quiet?: boolean;
|
|
29
|
+
legacy?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function check(options: CheckOptions = {}): Promise<boolean> {
|
|
33
|
+
const rootDir = resolveFromCwd(".");
|
|
34
|
+
const preset = options.preset ?? "mandu";
|
|
35
|
+
const format = resolveOutputFormat(options.format);
|
|
36
|
+
const quiet = options.quiet === true;
|
|
37
|
+
const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
|
|
38
|
+
const specPath = resolveFromCwd("spec/routes.manifest.json");
|
|
39
|
+
const hasSpec = await pathExists(specPath);
|
|
40
|
+
|
|
41
|
+
let success = true;
|
|
42
|
+
|
|
43
|
+
const log = (message: string) => {
|
|
44
|
+
if (format === "console" && !quiet) {
|
|
45
|
+
console.log(message);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const print = (message: string) => {
|
|
50
|
+
if (format === "console") {
|
|
51
|
+
console.log(message);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (format === "console") {
|
|
56
|
+
log("🥟 Mandu Check\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 1) FS Routes 검사
|
|
60
|
+
let routesSummary: { enabled: boolean; count: number; warnings: string[] } = {
|
|
61
|
+
enabled: false,
|
|
62
|
+
count: 0,
|
|
63
|
+
warnings: [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (enableFsRoutes) {
|
|
67
|
+
routesSummary.enabled = true;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if (format === "console") {
|
|
71
|
+
const result = await generateManifest(rootDir, {
|
|
72
|
+
outputPath: ".mandu/routes.manifest.json",
|
|
73
|
+
skipLegacy: true,
|
|
74
|
+
});
|
|
75
|
+
routesSummary.count = result.manifest.routes.length;
|
|
76
|
+
routesSummary.warnings = result.warnings;
|
|
77
|
+
|
|
78
|
+
if (quiet) {
|
|
79
|
+
print(`✅ FS Routes: ${routesSummary.count}개`);
|
|
80
|
+
} else {
|
|
81
|
+
log(`✅ FS Routes: ${routesSummary.count}개`);
|
|
82
|
+
}
|
|
83
|
+
if (routesSummary.warnings.length > 0) {
|
|
84
|
+
if (!quiet) {
|
|
85
|
+
log("⚠️ 경고:");
|
|
86
|
+
}
|
|
87
|
+
for (const warning of routesSummary.warnings) {
|
|
88
|
+
if (!quiet) {
|
|
89
|
+
log(` - ${warning}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!quiet) {
|
|
94
|
+
log("");
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
const scan = await scanRoutes(rootDir);
|
|
98
|
+
routesSummary.count = scan.routes.length;
|
|
99
|
+
routesSummary.warnings = scan.errors.map((e) => `${e.type}: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
success = false;
|
|
103
|
+
routesSummary.warnings.push(
|
|
104
|
+
error instanceof Error ? error.message : String(error)
|
|
105
|
+
);
|
|
106
|
+
if (format === "console") {
|
|
107
|
+
console.error("❌ FS Routes 검사 실패:", error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (quiet) {
|
|
112
|
+
print("ℹ️ app/ 폴더 없음 - FS Routes 검사 스킵");
|
|
113
|
+
} else {
|
|
114
|
+
log("ℹ️ app/ 폴더 없음 - FS Routes 검사 스킵\n");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 2) Architecture Guard 검사
|
|
119
|
+
const guardConfig: GuardConfig = {
|
|
120
|
+
preset,
|
|
121
|
+
srcDir: "src",
|
|
122
|
+
fsRoutes: enableFsRoutes
|
|
123
|
+
? {
|
|
124
|
+
noPageToPage: true,
|
|
125
|
+
pageCanImport: ["widgets", "features", "entities", "shared"],
|
|
126
|
+
layoutCanImport: ["widgets", "shared"],
|
|
127
|
+
}
|
|
128
|
+
: undefined,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const report = await checkDirectory(guardConfig, rootDir);
|
|
132
|
+
if (report.bySeverity.error > 0) {
|
|
133
|
+
success = false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (format === "console") {
|
|
137
|
+
const presetDef = getPreset(preset);
|
|
138
|
+
if (quiet) {
|
|
139
|
+
print(`📊 Architecture: ${report.totalViolations}개 위반 (Errors: ${report.bySeverity.error})`);
|
|
140
|
+
} else {
|
|
141
|
+
printReport(report, presetDef.hierarchy);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 3) Legacy Guard 검사 (spec 파일이 있을 때만)
|
|
146
|
+
let legacySummary: { enabled: boolean; passed: boolean; violations: number; errors?: string[] } = {
|
|
147
|
+
enabled: false,
|
|
148
|
+
passed: true,
|
|
149
|
+
violations: 0,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (hasSpec) {
|
|
153
|
+
legacySummary.enabled = true;
|
|
154
|
+
|
|
155
|
+
const manifestResult = await loadManifest(specPath);
|
|
156
|
+
if (!manifestResult.success || !manifestResult.data) {
|
|
157
|
+
legacySummary.passed = false;
|
|
158
|
+
legacySummary.errors = manifestResult.errors ?? ["Spec 로드 실패"];
|
|
159
|
+
success = false;
|
|
160
|
+
|
|
161
|
+
if (format === "console") {
|
|
162
|
+
console.error("❌ Spec 로드 실패:");
|
|
163
|
+
manifestResult.errors?.forEach((e) => console.error(` - ${e}`));
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
const checkResult = await runGuardCheck(manifestResult.data, rootDir);
|
|
167
|
+
legacySummary.passed = checkResult.passed;
|
|
168
|
+
legacySummary.violations = checkResult.violations.length;
|
|
169
|
+
success = success && checkResult.passed;
|
|
170
|
+
|
|
171
|
+
if (format === "console") {
|
|
172
|
+
const legacyReport = buildGuardReport(checkResult);
|
|
173
|
+
if (quiet) {
|
|
174
|
+
print(`📊 Legacy Guard: ${legacySummary.violations}개 위반`);
|
|
175
|
+
} else {
|
|
176
|
+
printReportSummary(legacyReport);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
if (quiet) {
|
|
182
|
+
print("ℹ️ spec/routes.manifest.json 없음 - 레거시 Guard 스킵");
|
|
183
|
+
} else {
|
|
184
|
+
log("ℹ️ spec/routes.manifest.json 없음 - 레거시 Guard 스킵");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (format !== "console") {
|
|
189
|
+
const summary = {
|
|
190
|
+
ok: success,
|
|
191
|
+
routes: routesSummary,
|
|
192
|
+
architecture: {
|
|
193
|
+
totalViolations: report.totalViolations,
|
|
194
|
+
bySeverity: report.bySeverity,
|
|
195
|
+
byType: report.byType,
|
|
196
|
+
report,
|
|
197
|
+
},
|
|
198
|
+
legacy: legacySummary,
|
|
199
|
+
};
|
|
200
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return options.ci ? success : true;
|
|
204
|
+
}
|
package/src/commands/dev.ts
CHANGED
|
@@ -17,20 +17,23 @@ import {
|
|
|
17
17
|
type GuardConfig,
|
|
18
18
|
type GuardPreset,
|
|
19
19
|
} from "@mandujs/core";
|
|
20
|
-
import { resolveFromCwd } from "../util/fs";
|
|
20
|
+
import { isDirectory, resolveFromCwd } from "../util/fs";
|
|
21
|
+
import { resolveOutputFormat, type OutputFormat } from "../util/output";
|
|
21
22
|
import path from "path";
|
|
22
23
|
|
|
23
|
-
export interface DevOptions {
|
|
24
|
-
port?: number;
|
|
25
|
-
/** HMR 비활성화 */
|
|
26
|
-
noHmr?: boolean;
|
|
27
|
-
/** FS Routes 비활성화 (레거시 모드) */
|
|
28
|
-
legacy?: boolean;
|
|
29
|
-
/** Architecture Guard 활성화 */
|
|
30
|
-
guard?: boolean;
|
|
31
|
-
/** Guard 프리셋 */
|
|
32
|
-
guardPreset?: GuardPreset;
|
|
33
|
-
|
|
24
|
+
export interface DevOptions {
|
|
25
|
+
port?: number;
|
|
26
|
+
/** HMR 비활성화 */
|
|
27
|
+
noHmr?: boolean;
|
|
28
|
+
/** FS Routes 비활성화 (레거시 모드) */
|
|
29
|
+
legacy?: boolean;
|
|
30
|
+
/** Architecture Guard 활성화 */
|
|
31
|
+
guard?: boolean;
|
|
32
|
+
/** Guard 프리셋 */
|
|
33
|
+
guardPreset?: GuardPreset;
|
|
34
|
+
/** Guard 출력 형식 */
|
|
35
|
+
guardFormat?: OutputFormat;
|
|
36
|
+
}
|
|
34
37
|
|
|
35
38
|
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
36
39
|
const rootDir = resolveFromCwd(".");
|
|
@@ -203,16 +206,26 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
203
206
|
bundleManifest: devBundler?.initialBuild.manifest,
|
|
204
207
|
});
|
|
205
208
|
|
|
206
|
-
// Architecture Guard 실시간 감시 (선택적)
|
|
207
|
-
let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
|
|
208
|
-
|
|
209
|
-
if (options.guard) {
|
|
210
|
-
const guardPreset = options.guardPreset || "mandu";
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
// Architecture Guard 실시간 감시 (선택적)
|
|
210
|
+
let archGuardWatcher: ReturnType<typeof createGuardWatcher> | null = null;
|
|
211
|
+
|
|
212
|
+
if (options.guard !== false) {
|
|
213
|
+
const guardPreset = options.guardPreset || "mandu";
|
|
214
|
+
const guardFormat = resolveOutputFormat(options.guardFormat);
|
|
215
|
+
const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
|
|
216
|
+
const guardConfig: GuardConfig = {
|
|
217
|
+
preset: guardPreset,
|
|
218
|
+
srcDir: "src",
|
|
219
|
+
realtime: true,
|
|
220
|
+
realtimeOutput: guardFormat,
|
|
221
|
+
fsRoutes: enableFsRoutes
|
|
222
|
+
? {
|
|
223
|
+
noPageToPage: true,
|
|
224
|
+
pageCanImport: ["widgets", "features", "entities", "shared"],
|
|
225
|
+
layoutCanImport: ["widgets", "shared"],
|
|
226
|
+
}
|
|
227
|
+
: undefined,
|
|
228
|
+
};
|
|
216
229
|
|
|
217
230
|
console.log(`🛡️ Architecture Guard 활성화 (${guardPreset})`);
|
|
218
231
|
|
package/src/commands/doctor.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
runGuardCheck,
|
|
11
11
|
analyzeViolations,
|
|
12
12
|
printDoctorReport,
|
|
13
|
-
|
|
13
|
+
generateDoctorMarkdownReport,
|
|
14
14
|
initializeBrain,
|
|
15
15
|
getBrain,
|
|
16
16
|
} from "../../../core/src/index";
|
|
@@ -108,7 +108,7 @@ export async function doctor(options: DoctorOptions = {}): Promise<boolean> {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
case "markdown": {
|
|
111
|
-
const md =
|
|
111
|
+
const md = generateDoctorMarkdownReport(analysis);
|
|
112
112
|
|
|
113
113
|
if (output) {
|
|
114
114
|
await fs.writeFile(output, md, "utf-8");
|
|
@@ -17,13 +17,15 @@ import {
|
|
|
17
17
|
loadStatistics,
|
|
18
18
|
analyzeTrend,
|
|
19
19
|
calculateLayerStatistics,
|
|
20
|
-
|
|
20
|
+
generateGuardMarkdownReport,
|
|
21
21
|
generateHTMLReport,
|
|
22
22
|
type GuardConfig,
|
|
23
23
|
type GuardPreset,
|
|
24
24
|
} from "@mandujs/core";
|
|
25
25
|
import { writeFile } from "fs/promises";
|
|
26
|
-
import { resolveFromCwd } from "../util/fs";
|
|
26
|
+
import { isDirectory, resolveFromCwd } from "../util/fs";
|
|
27
|
+
import { resolveOutputFormat, type OutputFormat } from "../util/output";
|
|
28
|
+
import path from "path";
|
|
27
29
|
|
|
28
30
|
export interface GuardArchOptions {
|
|
29
31
|
/** 프리셋 이름 */
|
|
@@ -33,7 +35,7 @@ export interface GuardArchOptions {
|
|
|
33
35
|
/** CI 모드 (에러 시 exit 1) */
|
|
34
36
|
ci?: boolean;
|
|
35
37
|
/** 출력 형식: console, agent, json */
|
|
36
|
-
format?:
|
|
38
|
+
format?: OutputFormat;
|
|
37
39
|
/** 조용히 (요약만 출력) */
|
|
38
40
|
quiet?: boolean;
|
|
39
41
|
/** 소스 디렉토리 */
|
|
@@ -55,7 +57,7 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
55
57
|
preset = "mandu",
|
|
56
58
|
watch = false,
|
|
57
59
|
ci = false,
|
|
58
|
-
format
|
|
60
|
+
format,
|
|
59
61
|
quiet = false,
|
|
60
62
|
srcDir = "src",
|
|
61
63
|
listPresets: showPresets = false,
|
|
@@ -66,6 +68,8 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
66
68
|
} = options;
|
|
67
69
|
|
|
68
70
|
const rootDir = resolveFromCwd(".");
|
|
71
|
+
const resolvedFormat = resolveOutputFormat(format);
|
|
72
|
+
const enableFsRoutes = await isDirectory(path.resolve(rootDir, "app"));
|
|
69
73
|
|
|
70
74
|
// 프리셋 목록 출력
|
|
71
75
|
if (showPresets) {
|
|
@@ -86,25 +90,37 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
86
90
|
return true;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (resolvedFormat === "console") {
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log("🛡️ Mandu Guard - Architecture Checker");
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(`📋 Preset: ${preset}`);
|
|
98
|
+
console.log(`📂 Source: ${srcDir}/`);
|
|
99
|
+
console.log(`🔧 Mode: ${watch ? "Watch" : "Check"}`);
|
|
100
|
+
console.log("");
|
|
101
|
+
}
|
|
96
102
|
|
|
97
103
|
// Guard 설정
|
|
98
104
|
const config: GuardConfig = {
|
|
99
105
|
preset,
|
|
100
106
|
srcDir,
|
|
101
107
|
realtime: watch,
|
|
108
|
+
realtimeOutput: resolvedFormat,
|
|
109
|
+
fsRoutes: enableFsRoutes
|
|
110
|
+
? {
|
|
111
|
+
noPageToPage: true,
|
|
112
|
+
pageCanImport: ["widgets", "features", "entities", "shared"],
|
|
113
|
+
layoutCanImport: ["widgets", "shared"],
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
102
116
|
};
|
|
103
117
|
|
|
104
118
|
// 실시간 감시 모드
|
|
105
119
|
if (watch) {
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
if (resolvedFormat === "console") {
|
|
121
|
+
console.log("👁️ Watching for architecture violations...");
|
|
122
|
+
console.log(" Press Ctrl+C to stop\n");
|
|
123
|
+
}
|
|
108
124
|
|
|
109
125
|
const watcher = createGuardWatcher({
|
|
110
126
|
config,
|
|
@@ -113,7 +129,7 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
113
129
|
// 실시간 위반 출력은 watcher 내부에서 처리됨
|
|
114
130
|
},
|
|
115
131
|
onFileAnalyzed: (analysis, violations) => {
|
|
116
|
-
if (violations.length > 0 && !quiet) {
|
|
132
|
+
if (resolvedFormat === "console" && violations.length > 0 && !quiet) {
|
|
117
133
|
const timestamp = new Date().toLocaleTimeString();
|
|
118
134
|
console.log(`[${timestamp}] ${analysis.filePath}: ${violations.length} violation(s)`);
|
|
119
135
|
}
|
|
@@ -124,7 +140,9 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
124
140
|
|
|
125
141
|
// Ctrl+C 핸들링
|
|
126
142
|
process.on("SIGINT", () => {
|
|
127
|
-
|
|
143
|
+
if (resolvedFormat === "console") {
|
|
144
|
+
console.log("\n🛑 Guard stopped");
|
|
145
|
+
}
|
|
128
146
|
watcher.close();
|
|
129
147
|
process.exit(0);
|
|
130
148
|
});
|
|
@@ -134,13 +152,15 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
// 일회성 검사 모드
|
|
137
|
-
|
|
155
|
+
if (resolvedFormat === "console" && !quiet) {
|
|
156
|
+
console.log("🔍 Scanning for architecture violations...\n");
|
|
157
|
+
}
|
|
138
158
|
|
|
139
159
|
const report = await checkDirectory(config, rootDir);
|
|
140
160
|
const presetDef = getPreset(preset);
|
|
141
161
|
|
|
142
162
|
// 출력 형식에 따른 리포트 출력
|
|
143
|
-
switch (
|
|
163
|
+
switch (resolvedFormat) {
|
|
144
164
|
case "json":
|
|
145
165
|
console.log(formatReportAsAgentJSON(report, preset));
|
|
146
166
|
break;
|
|
@@ -209,7 +229,7 @@ export async function guardArch(options: GuardArchOptions = {}): Promise<boolean
|
|
|
209
229
|
break;
|
|
210
230
|
case "markdown":
|
|
211
231
|
default:
|
|
212
|
-
reportContent =
|
|
232
|
+
reportContent = generateGuardMarkdownReport(report, trend, layerStats ?? undefined);
|
|
213
233
|
break;
|
|
214
234
|
}
|
|
215
235
|
|
package/src/main.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { specUpsert } from "./commands/spec-upsert";
|
|
4
|
-
import { generateApply } from "./commands/generate-apply";
|
|
5
|
-
import { guardCheck } from "./commands/guard-check";
|
|
6
|
-
import { guardArch } from "./commands/guard-arch";
|
|
7
|
-
import {
|
|
3
|
+
import { specUpsert } from "./commands/spec-upsert";
|
|
4
|
+
import { generateApply } from "./commands/generate-apply";
|
|
5
|
+
import { guardCheck } from "./commands/guard-check";
|
|
6
|
+
import { guardArch } from "./commands/guard-arch";
|
|
7
|
+
import { check } from "./commands/check";
|
|
8
|
+
import { dev } from "./commands/dev";
|
|
8
9
|
import { init } from "./commands/init";
|
|
9
10
|
import { build } from "./commands/build";
|
|
10
11
|
import { contractCreate, contractValidate } from "./commands/contract";
|
|
@@ -27,13 +28,14 @@ const HELP_TEXT = `
|
|
|
27
28
|
|
|
28
29
|
Usage: bunx mandu <command> [options]
|
|
29
30
|
|
|
30
|
-
Commands:
|
|
31
|
-
init 새 프로젝트 생성
|
|
32
|
-
|
|
33
|
-
routes
|
|
34
|
-
routes
|
|
35
|
-
|
|
36
|
-
dev
|
|
31
|
+
Commands:
|
|
32
|
+
init 새 프로젝트 생성
|
|
33
|
+
check FS Routes + Guard 통합 검사
|
|
34
|
+
routes generate FS Routes 스캔 및 매니페스트 생성
|
|
35
|
+
routes list 현재 라우트 목록 출력
|
|
36
|
+
routes watch 실시간 라우트 감시
|
|
37
|
+
dev 개발 서버 실행 (FS Routes + Guard 기본)
|
|
38
|
+
dev --no-guard Guard 감시 비활성화
|
|
37
39
|
build 클라이언트 번들 빌드 (Hydration)
|
|
38
40
|
guard Guard 규칙 검사 (레거시 Spec 기반)
|
|
39
41
|
guard arch 아키텍처 위반 검사 (FSD/Clean/Hexagonal)
|
|
@@ -63,16 +65,19 @@ Commands:
|
|
|
63
65
|
change list 변경 이력 조회
|
|
64
66
|
change prune 오래된 스냅샷 정리
|
|
65
67
|
|
|
66
|
-
Options:
|
|
67
|
-
--name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
|
|
68
|
-
--file <path> spec-upsert 시 사용할 spec 파일 경로
|
|
69
|
-
--port <port> dev/openapi serve 포트 (기본: 3000/8080)
|
|
70
|
-
--guard dev 시 Architecture Guard 실시간 감시 활성화
|
|
71
|
-
--guard
|
|
72
|
-
--
|
|
73
|
-
--
|
|
74
|
-
--
|
|
75
|
-
--
|
|
68
|
+
Options:
|
|
69
|
+
--name <name> init 시 프로젝트 이름 (기본: my-mandu-app)
|
|
70
|
+
--file <path> spec-upsert 시 사용할 spec 파일 경로
|
|
71
|
+
--port <port> dev/openapi serve 포트 (기본: 3000/8080)
|
|
72
|
+
--guard dev 시 Architecture Guard 실시간 감시 활성화 (기본: ON)
|
|
73
|
+
--no-guard dev 시 Guard 비활성화
|
|
74
|
+
--guard-preset <p> dev --guard 시 프리셋 (기본: mandu)
|
|
75
|
+
--guard-format <f> dev --guard 출력 형식: console, json, agent (기본: 자동)
|
|
76
|
+
--legacy FS Routes 비활성화 (레거시 모드)
|
|
77
|
+
--no-auto-correct guard 시 자동 수정 비활성화
|
|
78
|
+
--preset <name> guard/check 프리셋 (기본: mandu) - fsd, clean, hexagonal, atomic 선택 가능
|
|
79
|
+
--ci guard/check CI 모드 (에러 시 exit 1)
|
|
80
|
+
--quiet guard/check 요약만 출력
|
|
76
81
|
--report-format guard arch 리포트 형식: json, markdown, html
|
|
77
82
|
--save-stats guard arch 통계 저장 (트렌드 분석용)
|
|
78
83
|
--show-trend guard arch 트렌드 분석 표시
|
|
@@ -83,19 +88,22 @@ Options:
|
|
|
83
88
|
--id <id> change rollback 시 특정 변경 ID
|
|
84
89
|
--keep <n> change prune 시 유지할 스냅샷 수 (기본: 5)
|
|
85
90
|
--output <path> openapi/doctor 출력 경로
|
|
86
|
-
--format <fmt>
|
|
91
|
+
--format <fmt> guard/check 출력 형식: console, json, agent (기본: 자동)
|
|
92
|
+
--format <fmt> doctor 출력 형식: console, json, markdown (기본: console)
|
|
87
93
|
--no-llm doctor에서 LLM 사용 안 함 (템플릿 모드)
|
|
88
94
|
--model <name> brain setup 시 모델 이름 (기본: llama3.2)
|
|
89
95
|
--url <url> brain setup 시 Ollama URL
|
|
90
96
|
--verbose 상세 출력
|
|
91
97
|
--help, -h 도움말 표시
|
|
92
98
|
|
|
93
|
-
Examples:
|
|
94
|
-
bunx mandu init --name my-app
|
|
95
|
-
bunx mandu
|
|
96
|
-
bunx mandu routes
|
|
97
|
-
bunx mandu
|
|
98
|
-
bunx mandu
|
|
99
|
+
Examples:
|
|
100
|
+
bunx mandu init --name my-app
|
|
101
|
+
bunx mandu check
|
|
102
|
+
bunx mandu routes list
|
|
103
|
+
bunx mandu routes generate
|
|
104
|
+
bunx mandu dev --port 3000
|
|
105
|
+
bunx mandu dev --no-guard
|
|
106
|
+
bunx mandu build --minify
|
|
99
107
|
bunx mandu guard
|
|
100
108
|
bunx mandu guard arch --preset fsd
|
|
101
109
|
bunx mandu guard arch --watch
|
|
@@ -187,23 +195,33 @@ async function main(): Promise<void> {
|
|
|
187
195
|
success = await specUpsert({ file: options.file });
|
|
188
196
|
break;
|
|
189
197
|
|
|
190
|
-
case "generate":
|
|
191
|
-
success = await generateApply();
|
|
192
|
-
break;
|
|
193
|
-
|
|
194
|
-
case "
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
case "generate":
|
|
199
|
+
success = await generateApply();
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case "check":
|
|
203
|
+
success = await check({
|
|
204
|
+
preset: options.preset as any,
|
|
205
|
+
format: options.format as any,
|
|
206
|
+
ci: options.ci === "true",
|
|
207
|
+
quiet: options.quiet === "true",
|
|
208
|
+
legacy: options.legacy === "true",
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case "guard": {
|
|
213
|
+
const subCommand = args[1];
|
|
214
|
+
switch (subCommand) {
|
|
215
|
+
case "arch":
|
|
216
|
+
success = await guardArch({
|
|
217
|
+
preset: (options.preset as any) || "fsd",
|
|
218
|
+
watch: options.watch === "true",
|
|
219
|
+
ci: options.ci === "true",
|
|
220
|
+
format: options.format as any,
|
|
221
|
+
quiet: options.quiet === "true",
|
|
222
|
+
srcDir: options["src-dir"],
|
|
223
|
+
listPresets: options["list-presets"] === "true",
|
|
224
|
+
output: options.output,
|
|
207
225
|
reportFormat: (options["report-format"] as any) || "markdown",
|
|
208
226
|
saveStats: options["save-stats"] === "true",
|
|
209
227
|
showTrend: options["show-trend"] === "true",
|
|
@@ -226,13 +244,15 @@ async function main(): Promise<void> {
|
|
|
226
244
|
});
|
|
227
245
|
break;
|
|
228
246
|
|
|
229
|
-
case "dev":
|
|
230
|
-
await dev({
|
|
231
|
-
port: parsePort(options.port),
|
|
232
|
-
guard: options
|
|
233
|
-
guardPreset: options["guard-preset"] as any,
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
case "dev":
|
|
248
|
+
await dev({
|
|
249
|
+
port: parsePort(options.port),
|
|
250
|
+
guard: options["no-guard"] === "true" ? false : options.guard !== "false",
|
|
251
|
+
guardPreset: options["guard-preset"] as any,
|
|
252
|
+
guardFormat: options["guard-format"] as any,
|
|
253
|
+
legacy: options.legacy === "true",
|
|
254
|
+
});
|
|
255
|
+
break;
|
|
236
256
|
|
|
237
257
|
case "routes": {
|
|
238
258
|
const subCommand = args[1];
|
package/src/util/fs.ts
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
import path from "path";
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
2
3
|
|
|
3
4
|
export function resolveFromCwd(...paths: string[]): string {
|
|
4
5
|
return path.resolve(process.cwd(), ...paths);
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
export function getRootDir(): string {
|
|
8
|
-
return process.cwd();
|
|
9
|
-
}
|
|
8
|
+
export function getRootDir(): string {
|
|
9
|
+
return process.cwd();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function pathExists(targetPath: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(targetPath);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function isDirectory(targetPath: string): Promise<boolean> {
|
|
22
|
+
try {
|
|
23
|
+
const stat = await fs.stat(targetPath);
|
|
24
|
+
return stat.isDirectory();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type OutputFormat = "console" | "agent" | "json";
|
|
2
|
+
|
|
3
|
+
function normalizeFormat(value?: string): OutputFormat | undefined {
|
|
4
|
+
if (!value) return undefined;
|
|
5
|
+
if (value === "console" || value === "agent" || value === "json") {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveOutputFormat(explicit?: OutputFormat): OutputFormat {
|
|
12
|
+
const env = process.env;
|
|
13
|
+
|
|
14
|
+
const direct = normalizeFormat(explicit) ?? normalizeFormat(env.MANDU_OUTPUT);
|
|
15
|
+
if (direct) return direct;
|
|
16
|
+
|
|
17
|
+
const agentSignals = [
|
|
18
|
+
"MANDU_AGENT",
|
|
19
|
+
"CODEX_AGENT",
|
|
20
|
+
"CODEX",
|
|
21
|
+
"CLAUDE_CODE",
|
|
22
|
+
"ANTHROPIC_CLAUDE_CODE",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const key of agentSignals) {
|
|
26
|
+
const value = env[key];
|
|
27
|
+
if (value === "1" || value === "true") {
|
|
28
|
+
return "json";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (env.CI === "true") {
|
|
33
|
+
return "json";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.stdout && !process.stdout.isTTY) {
|
|
37
|
+
return "json";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "console";
|
|
41
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home Page
|
|
3
|
+
*
|
|
4
|
+
* Edit this file and see changes at http://localhost:3000
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default function HomePage() {
|
|
8
|
+
return (
|
|
9
|
+
<html lang="ko">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charSet="UTF-8" />
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
13
|
+
<title>Mandu App</title>
|
|
14
|
+
<style>{`
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
align-items: center;
|
|
20
|
+
min-height: 100vh;
|
|
21
|
+
margin: 0;
|
|
22
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
23
|
+
color: white;
|
|
24
|
+
}
|
|
25
|
+
.container {
|
|
26
|
+
text-align: center;
|
|
27
|
+
padding: 2rem;
|
|
28
|
+
}
|
|
29
|
+
h1 {
|
|
30
|
+
font-size: 3rem;
|
|
31
|
+
margin-bottom: 1rem;
|
|
32
|
+
}
|
|
33
|
+
p {
|
|
34
|
+
font-size: 1.2rem;
|
|
35
|
+
opacity: 0.9;
|
|
36
|
+
}
|
|
37
|
+
code {
|
|
38
|
+
background: rgba(255,255,255,0.2);
|
|
39
|
+
padding: 0.2rem 0.5rem;
|
|
40
|
+
border-radius: 4px;
|
|
41
|
+
}
|
|
42
|
+
a {
|
|
43
|
+
color: white;
|
|
44
|
+
text-decoration: underline;
|
|
45
|
+
}
|
|
46
|
+
`}</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<div className="container">
|
|
50
|
+
<h1>🥟 Mandu</h1>
|
|
51
|
+
<p>Welcome to your new Mandu project!</p>
|
|
52
|
+
<p>Edit <code>app/page.tsx</code> to get started.</p>
|
|
53
|
+
<p>
|
|
54
|
+
<a href="/api/health">Check API Health →</a>
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -5,19 +5,15 @@
|
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "mandu dev",
|
|
7
7
|
"build": "mandu build",
|
|
8
|
-
"
|
|
9
|
-
"guard": "mandu guard",
|
|
10
|
-
"spec": "mandu spec-upsert",
|
|
11
|
-
"test": "bun test",
|
|
12
|
-
"test:watch": "bun test --watch"
|
|
8
|
+
"test": "bun test"
|
|
13
9
|
},
|
|
14
10
|
"dependencies": {
|
|
15
|
-
"@mandujs/core": "^0.
|
|
11
|
+
"@mandujs/core": "^0.9.38",
|
|
16
12
|
"react": "^18.2.0",
|
|
17
13
|
"react-dom": "^18.2.0"
|
|
18
14
|
},
|
|
19
15
|
"devDependencies": {
|
|
20
|
-
"@mandujs/cli": "^0.
|
|
16
|
+
"@mandujs/cli": "^0.9.18",
|
|
21
17
|
"@types/react": "^18.2.0",
|
|
22
18
|
"@types/react-dom": "^18.2.0",
|
|
23
19
|
"typescript": "^5.0.0"
|
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version":
|
|
3
|
-
"routes": [
|
|
4
|
-
{
|
|
5
|
-
"id": "home",
|
|
6
|
-
"pattern": "/",
|
|
7
|
-
"kind": "page",
|
|
8
|
-
"module": "apps/server/generated/routes/home.route.ts",
|
|
9
|
-
"componentModule": "apps/web/generated/routes/home.route.tsx"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"id": "health",
|
|
13
|
-
"pattern": "/api/health",
|
|
14
|
-
"kind": "api",
|
|
15
|
-
"module": "apps/server/generated/routes/health.route.ts"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
2
|
+
"version": 2,
|
|
3
|
+
"routes": []
|
|
18
4
|
}
|