@mandujs/skills 1.0.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/.claude-plugin/plugin.json +85 -0
- package/README.md +77 -0
- package/package.json +52 -0
- package/skills/mandu-create-api/SKILL.md +145 -0
- package/skills/mandu-create-feature/SKILL.md +99 -0
- package/skills/mandu-debug/SKILL.md +109 -0
- package/skills/mandu-deploy/SKILL.md +174 -0
- package/skills/mandu-explain/SKILL.md +121 -0
- package/skills/mandu-fs-routes/SKILL.md +131 -0
- package/skills/mandu-guard-guide/SKILL.md +150 -0
- package/skills/mandu-hydration/SKILL.md +134 -0
- package/skills/mandu-slot/SKILL.md +132 -0
- package/src/cli.ts +125 -0
- package/src/index.ts +239 -0
- package/src/init-integration.ts +106 -0
- package/templates/.claude/settings.json +37 -0
- package/templates/.mcp.json +9 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-deploy
|
|
3
|
+
description: "프로덕션 배포 파이프라인. Docker/CI-CD/nginx"
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Mandu Deploy
|
|
8
|
+
|
|
9
|
+
프로덕션 배포 파이프라인 가이드.
|
|
10
|
+
Build -> Test -> Deploy 체크리스트와 Docker/CI-CD/nginx 설정을 다룹니다.
|
|
11
|
+
|
|
12
|
+
## Build -> Test -> Deploy Checklist
|
|
13
|
+
|
|
14
|
+
### 1. Pre-Deploy Checks
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Architecture validation
|
|
18
|
+
bunx mandu guard arch --ci
|
|
19
|
+
|
|
20
|
+
# TypeScript check
|
|
21
|
+
bunx tsc --noEmit
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
bun test
|
|
25
|
+
|
|
26
|
+
# Production build
|
|
27
|
+
bun run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Dockerfile
|
|
31
|
+
|
|
32
|
+
```dockerfile
|
|
33
|
+
FROM oven/bun:1.2-alpine AS base
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
|
|
36
|
+
# Install dependencies
|
|
37
|
+
FROM base AS deps
|
|
38
|
+
COPY package.json bun.lock* ./
|
|
39
|
+
RUN bun install --frozen-lockfile --production
|
|
40
|
+
|
|
41
|
+
# Build
|
|
42
|
+
FROM base AS build
|
|
43
|
+
COPY package.json bun.lock* ./
|
|
44
|
+
RUN bun install --frozen-lockfile
|
|
45
|
+
COPY . .
|
|
46
|
+
RUN bun run build
|
|
47
|
+
|
|
48
|
+
# Production
|
|
49
|
+
FROM base AS production
|
|
50
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
51
|
+
COPY --from=build /app/.mandu ./.mandu
|
|
52
|
+
COPY --from=build /app/app ./app
|
|
53
|
+
COPY --from=build /app/src ./src
|
|
54
|
+
COPY --from=build /app/spec ./spec
|
|
55
|
+
COPY --from=build /app/package.json ./
|
|
56
|
+
|
|
57
|
+
ENV NODE_ENV=production
|
|
58
|
+
ENV PORT=3333
|
|
59
|
+
EXPOSE 3333
|
|
60
|
+
|
|
61
|
+
CMD ["bun", "run", "start"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. docker-compose.yml
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
version: "3.8"
|
|
68
|
+
services:
|
|
69
|
+
app:
|
|
70
|
+
build: .
|
|
71
|
+
ports:
|
|
72
|
+
- "3333:3333"
|
|
73
|
+
environment:
|
|
74
|
+
- NODE_ENV=production
|
|
75
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
76
|
+
restart: unless-stopped
|
|
77
|
+
healthcheck:
|
|
78
|
+
test: ["CMD", "curl", "-f", "http://localhost:3333/api/health"]
|
|
79
|
+
interval: 30s
|
|
80
|
+
timeout: 10s
|
|
81
|
+
retries: 3
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4. GitHub Actions CI/CD
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# .github/workflows/deploy.yml
|
|
88
|
+
name: Deploy
|
|
89
|
+
on:
|
|
90
|
+
push:
|
|
91
|
+
branches: [main]
|
|
92
|
+
|
|
93
|
+
jobs:
|
|
94
|
+
deploy:
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
- uses: oven-sh/setup-bun@v2
|
|
99
|
+
with:
|
|
100
|
+
bun-version: latest
|
|
101
|
+
|
|
102
|
+
- run: bun install --frozen-lockfile
|
|
103
|
+
- run: bunx mandu guard arch --ci
|
|
104
|
+
- run: bun test
|
|
105
|
+
- run: bun run build
|
|
106
|
+
|
|
107
|
+
- name: Deploy
|
|
108
|
+
run: |
|
|
109
|
+
# docker build & push, or platform-specific deploy
|
|
110
|
+
docker build -t myapp:${{ github.sha }} .
|
|
111
|
+
docker push myapp:${{ github.sha }}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5. nginx Reverse Proxy
|
|
115
|
+
|
|
116
|
+
```nginx
|
|
117
|
+
upstream mandu_app {
|
|
118
|
+
server 127.0.0.1:3333;
|
|
119
|
+
keepalive 32;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
server {
|
|
123
|
+
listen 80;
|
|
124
|
+
server_name example.com;
|
|
125
|
+
|
|
126
|
+
# Static assets (long cache)
|
|
127
|
+
location /.mandu/client/ {
|
|
128
|
+
proxy_pass http://mandu_app;
|
|
129
|
+
proxy_cache_valid 200 30d;
|
|
130
|
+
add_header Cache-Control "public, max-age=2592000, immutable";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# API routes
|
|
134
|
+
location /api/ {
|
|
135
|
+
proxy_pass http://mandu_app;
|
|
136
|
+
proxy_http_version 1.1;
|
|
137
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
138
|
+
proxy_set_header Connection "upgrade";
|
|
139
|
+
proxy_set_header Host $host;
|
|
140
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# All other routes (SSR)
|
|
144
|
+
location / {
|
|
145
|
+
proxy_pass http://mandu_app;
|
|
146
|
+
proxy_http_version 1.1;
|
|
147
|
+
proxy_set_header Host $host;
|
|
148
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
149
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
150
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Environment Variables
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# .env.production
|
|
159
|
+
NODE_ENV=production
|
|
160
|
+
PORT=3333
|
|
161
|
+
DATABASE_URL=postgresql://user:pass@host:5432/db
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
IMPORTANT: `.env` 파일은 절대 git에 커밋하지 않기. `.gitignore`에 추가 확인.
|
|
165
|
+
|
|
166
|
+
## Production Optimizations
|
|
167
|
+
|
|
168
|
+
| Area | Action |
|
|
169
|
+
|------|--------|
|
|
170
|
+
| Build | `bun run build` (minification, tree-shaking) |
|
|
171
|
+
| CSS | Tailwind purge (자동) |
|
|
172
|
+
| Islands | `"visible"` or `"idle"` priority (not `"immediate"`) |
|
|
173
|
+
| Static | nginx에서 `.mandu/client/` 장기 캐시 |
|
|
174
|
+
| Health | `/api/health` endpoint 필수 |
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-explain
|
|
3
|
+
description: |
|
|
4
|
+
Mandu 개념 설명. Island/Filling/Guard/Contract/Slot/SSR/ISR 등 '뭐야'/'어떻게' 시 자동 호출
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Mandu Explain
|
|
8
|
+
|
|
9
|
+
Mandu 프레임워크의 핵심 개념 18가지를 설명하는 레퍼런스.
|
|
10
|
+
|
|
11
|
+
## Core Concepts
|
|
12
|
+
|
|
13
|
+
### 1. Island Architecture
|
|
14
|
+
페이지의 대부분은 정적 HTML로 서버 렌더링하고, 인터랙티브한 부분만 JavaScript를 로드하는 패턴.
|
|
15
|
+
```
|
|
16
|
+
[Static HTML] [Static HTML] [Island: JS] [Static HTML]
|
|
17
|
+
```
|
|
18
|
+
장점: 초기 로딩 속도, 작은 번들 크기, SEO 친화적.
|
|
19
|
+
|
|
20
|
+
### 2. Filling (Mandu.filling())
|
|
21
|
+
API 핸들러를 체이닝으로 작성하는 API. Express의 미들웨어와 유사하지만 타입 안전.
|
|
22
|
+
```typescript
|
|
23
|
+
Mandu.filling().guard(authCheck).get(handler).post(handler)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 3. Guard
|
|
27
|
+
아키텍처 규칙을 코드 레벨에서 강제하는 시스템. import 방향, 파일 위치, 금지된 의존성을 검사.
|
|
28
|
+
```bash
|
|
29
|
+
bunx mandu guard arch # 전체 프로젝트 검사
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 4. Contract
|
|
33
|
+
Zod 스키마 기반 API 계약. 클라이언트-서버 간 타입을 공유하고 런타임 validation을 수행.
|
|
34
|
+
```typescript
|
|
35
|
+
// src/shared/contracts/user.contract.ts
|
|
36
|
+
export const CreateUser = z.object({ name: z.string(), email: z.string().email() });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 5. Slot
|
|
40
|
+
서버에서 렌더링 전에 실행되는 데이터 로더. `spec/slots/*.slot.ts`에 위치.
|
|
41
|
+
```typescript
|
|
42
|
+
// spec/slots/dashboard.slot.ts - page 렌더링 전에 데이터를 fetch
|
|
43
|
+
export default Mandu.filling().get(async (ctx) => ctx.ok({ stats: await getStats() }));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 6. SSR (Server-Side Rendering)
|
|
47
|
+
모든 페이지를 서버에서 HTML로 렌더링. `app/layout.tsx`에서 `<html>/<head>/<body>` 태그 불필요 (자동 생성).
|
|
48
|
+
|
|
49
|
+
### 7. Streaming SSR
|
|
50
|
+
서버에서 HTML을 청크 단위로 스트리밍. 첫 바이트 시간(TTFB)을 단축.
|
|
51
|
+
|
|
52
|
+
### 8. Hydration
|
|
53
|
+
서버 렌더링된 HTML에 JavaScript 인터랙티비티를 부착하는 과정.
|
|
54
|
+
Priority: `immediate` > `visible` > `idle` > `interaction`
|
|
55
|
+
|
|
56
|
+
### 9. FS Routes
|
|
57
|
+
파일 시스템 기반 라우팅. `app/` 폴더 구조가 URL이 됨.
|
|
58
|
+
- `app/page.tsx` -> `/`
|
|
59
|
+
- `app/users/[id]/page.tsx` -> `/users/:id`
|
|
60
|
+
|
|
61
|
+
### 10. Layout
|
|
62
|
+
페이지를 감싸는 공통 UI 래퍼. `<html>/<head>/<body>` 사용 금지.
|
|
63
|
+
```tsx
|
|
64
|
+
export default function Layout({ children }) {
|
|
65
|
+
return <div className="min-h-screen">{children}</div>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 11. Route Groups
|
|
70
|
+
`(name)` 괄호로 감싼 폴더는 URL에 영향 없이 라우트를 그룹화.
|
|
71
|
+
```
|
|
72
|
+
app/(auth)/login/page.tsx -> /login
|
|
73
|
+
app/(auth)/signup/page.tsx -> /signup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 12. MCP (Model Context Protocol)
|
|
77
|
+
AI 에이전트가 프레임워크 도구를 직접 호출하는 인터페이스.
|
|
78
|
+
`@mandujs/mcp`가 50+ 도구를 제공: negotiate, generate, guard, brain 등.
|
|
79
|
+
|
|
80
|
+
### 13. ATE (Automated Test Engine)
|
|
81
|
+
API 엔드포인트의 테스트를 자동 생성하는 엔진.
|
|
82
|
+
|
|
83
|
+
### 14. Brain
|
|
84
|
+
코드 분석 및 진단 시스템. 프로젝트 구조, 의존성, 성능 이슈를 분석.
|
|
85
|
+
|
|
86
|
+
### 15. Presets
|
|
87
|
+
Guard가 사용하는 아키텍처 템플릿: `mandu`, `fsd`, `clean`, `hexagonal`, `atomic`, `cqrs`
|
|
88
|
+
|
|
89
|
+
### 16. Lockfile (.mandu/lockfile.json)
|
|
90
|
+
프로젝트 설정의 무결성을 보장하는 해시 기반 잠금 파일.
|
|
91
|
+
|
|
92
|
+
### 17. useServerData
|
|
93
|
+
Island에서 서버 Slot이 주입한 데이터를 읽는 클라이언트 훅.
|
|
94
|
+
```typescript
|
|
95
|
+
const data = useServerData<UserData>("user-profile");
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 18. useIslandEvent
|
|
99
|
+
Island 간 통신을 위한 이벤트 시스템.
|
|
100
|
+
```typescript
|
|
101
|
+
const { emit, on } = useIslandEvent();
|
|
102
|
+
emit("cart:updated", { count: 5 });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Architecture Layers
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
app/ UI (pages, layouts, islands)
|
|
109
|
+
src/client/ Client-side code
|
|
110
|
+
src/server/ Server-side business logic
|
|
111
|
+
src/shared/ Shared types, contracts, utils
|
|
112
|
+
spec/ Slots, tests
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Key Import Rules
|
|
116
|
+
|
|
117
|
+
| From | Import |
|
|
118
|
+
|------|--------|
|
|
119
|
+
| Island files | `@mandujs/core/client` |
|
|
120
|
+
| Server files | `@mandujs/core` |
|
|
121
|
+
| Shared code | `@/shared/*` |
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-fs-routes
|
|
3
|
+
description: |
|
|
4
|
+
파일 시스템 라우팅 규칙. layout에 html/head/body 금지.
|
|
5
|
+
app/ 폴더, page.tsx, route.ts, layout.tsx, [id] 작업 시 자동 호출.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mandu FS Routes
|
|
9
|
+
|
|
10
|
+
파일 시스템 기반 라우팅. `app/` 폴더의 파일 구조가 URL이 됩니다.
|
|
11
|
+
|
|
12
|
+
## File Naming Conventions
|
|
13
|
+
|
|
14
|
+
| File | Purpose | Export |
|
|
15
|
+
|------|---------|--------|
|
|
16
|
+
| `page.tsx` | Page component | `default` function component |
|
|
17
|
+
| `route.ts` | API handler | `default` Mandu.filling() chain |
|
|
18
|
+
| `layout.tsx` | Shared layout | `default` function with `children` prop |
|
|
19
|
+
| `loading.tsx` | Loading UI | `default` function component |
|
|
20
|
+
| `error.tsx` | Error UI | `default` function component |
|
|
21
|
+
|
|
22
|
+
## URL Mapping
|
|
23
|
+
|
|
24
|
+
| File Path | URL |
|
|
25
|
+
|-----------|-----|
|
|
26
|
+
| `app/page.tsx` | `/` |
|
|
27
|
+
| `app/about/page.tsx` | `/about` |
|
|
28
|
+
| `app/blog/[slug]/page.tsx` | `/blog/:slug` |
|
|
29
|
+
| `app/users/[id]/page.tsx` | `/users/:id` |
|
|
30
|
+
| `app/docs/[...path]/page.tsx` | `/docs/*` (catch-all) |
|
|
31
|
+
| `app/(auth)/login/page.tsx` | `/login` (grouped) |
|
|
32
|
+
| `app/api/users/route.ts` | `/api/users` |
|
|
33
|
+
| `app/api/users/[id]/route.ts` | `/api/users/:id` |
|
|
34
|
+
|
|
35
|
+
## Layout Rules (CRITICAL)
|
|
36
|
+
|
|
37
|
+
Layout MUST NOT include `<html>`, `<head>`, or `<body>` tags.
|
|
38
|
+
Mandu SSR generates these automatically.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// app/layout.tsx - CORRECT
|
|
42
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="min-h-screen bg-background font-sans antialiased">
|
|
45
|
+
{children}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// app/layout.tsx - WRONG (causes double-wrapping)
|
|
53
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
54
|
+
return (
|
|
55
|
+
<html> // <-- DO NOT include
|
|
56
|
+
<head /> // <-- DO NOT include
|
|
57
|
+
<body> // <-- DO NOT include
|
|
58
|
+
{children}
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Nested layouts wrap child pages:
|
|
66
|
+
```
|
|
67
|
+
app/layout.tsx -> wraps ALL pages
|
|
68
|
+
app/dashboard/layout.tsx -> wraps /dashboard/* pages
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Page Components
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// app/dashboard/page.tsx
|
|
75
|
+
export default function DashboardPage() {
|
|
76
|
+
return (
|
|
77
|
+
<main>
|
|
78
|
+
<h1>Dashboard</h1>
|
|
79
|
+
</main>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Pages are server-rendered by default. No `"use client"` needed unless using Islands.
|
|
85
|
+
|
|
86
|
+
## API Routes
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// app/api/users/route.ts
|
|
90
|
+
import { Mandu } from "@mandujs/core";
|
|
91
|
+
|
|
92
|
+
export default Mandu.filling()
|
|
93
|
+
.get((ctx) => ctx.ok({ users: [] }))
|
|
94
|
+
.post(async (ctx) => {
|
|
95
|
+
const body = await ctx.body<{ name: string }>();
|
|
96
|
+
return ctx.created({ user: body });
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
API routes use `.ts` extension (NOT `.tsx`).
|
|
101
|
+
|
|
102
|
+
## Dynamic Routes
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// app/users/[id]/page.tsx
|
|
106
|
+
export default function UserPage({ params }: { params: { id: string } }) {
|
|
107
|
+
return <h1>User {params.id}</h1>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// app/docs/[...path]/page.tsx
|
|
111
|
+
export default function DocsPage({ params }: { params: { path: string[] } }) {
|
|
112
|
+
return <h1>Doc: {params.path.join("/")}</h1>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Route Groups
|
|
117
|
+
|
|
118
|
+
Parenthesized folders create logical groups without affecting the URL:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
app/(marketing)/about/page.tsx -> /about
|
|
122
|
+
app/(marketing)/pricing/page.tsx -> /pricing
|
|
123
|
+
app/(app)/dashboard/page.tsx -> /dashboard
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Common Mistakes
|
|
127
|
+
|
|
128
|
+
- Adding `<html>/<head>/<body>` in layout.tsx (SSR does this)
|
|
129
|
+
- Using `route.tsx` instead of `route.ts` for API handlers
|
|
130
|
+
- Forgetting to export default from page/layout files
|
|
131
|
+
- Mixing page.tsx and route.ts in the same directory
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-guard-guide
|
|
3
|
+
description: |
|
|
4
|
+
Guard 아키텍처 가이드. 위반 수정/프리셋 선택/레이어 규칙. guard violation 시 자동 호출
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Mandu Guard Guide
|
|
8
|
+
|
|
9
|
+
Mandu Guard 아키텍처 강제 시스템의 상세 가이드.
|
|
10
|
+
6개 프리셋, 위반 유형별 수정 방법, 레이어 규칙을 다룹니다.
|
|
11
|
+
|
|
12
|
+
## 6 Presets
|
|
13
|
+
|
|
14
|
+
### mandu (default)
|
|
15
|
+
FSD + Clean Architecture 하이브리드. 프론트엔드와 백엔드 모두 커버.
|
|
16
|
+
```typescript
|
|
17
|
+
// mandu.config.ts
|
|
18
|
+
export default { guard: { preset: "mandu" } };
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### fsd (Feature-Sliced Design)
|
|
22
|
+
프론트엔드 전용. 7계층 레이어 구조.
|
|
23
|
+
```
|
|
24
|
+
app > pages > widgets > features > entities > shared
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### clean (Clean Architecture)
|
|
28
|
+
백엔드 전용. 의존성 역전 원칙.
|
|
29
|
+
```
|
|
30
|
+
api > application > domain > infra > core > shared
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### hexagonal (Ports & Adapters)
|
|
34
|
+
포트와 어댑터 패턴. domain이 중심, adapters가 외부 의존성 처리.
|
|
35
|
+
```
|
|
36
|
+
adapters > ports > application > domain
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### atomic (Atomic Design)
|
|
40
|
+
UI 컴포넌트 전용. atoms -> molecules -> organisms -> templates -> pages.
|
|
41
|
+
|
|
42
|
+
### cqrs (Command Query Responsibility Segregation)
|
|
43
|
+
명령과 조회를 분리. command, query, event, dto 전용 구조.
|
|
44
|
+
|
|
45
|
+
## Violation Types and Fixes
|
|
46
|
+
|
|
47
|
+
### LAYER_VIOLATION (error)
|
|
48
|
+
상위 레이어가 하위 레이어를 import하는 것만 허용.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// BAD: shared가 features를 import (하위 -> 상위)
|
|
52
|
+
// src/shared/utils/helper.ts
|
|
53
|
+
import { useAuth } from "@/features/auth"; // VIOLATION
|
|
54
|
+
|
|
55
|
+
// FIX: 의존 방향 반전, 또는 shared에 인터페이스 정의
|
|
56
|
+
// src/shared/types/auth.ts
|
|
57
|
+
export interface AuthContext { userId: string; }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### FORBIDDEN_IMPORT (error)
|
|
61
|
+
클라이언트 코드에서 서버 전용 모듈 import 금지.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// BAD: island에서 fs import
|
|
65
|
+
// app/editor.island.tsx
|
|
66
|
+
import fs from "fs"; // VIOLATION
|
|
67
|
+
|
|
68
|
+
// FIX: 서버 로직은 slot으로 분리
|
|
69
|
+
// spec/slots/editor.slot.ts
|
|
70
|
+
import fs from "fs"; // OK (server-only)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### WRONG_SLOT_LOCATION (warning)
|
|
74
|
+
Slot 파일이 잘못된 위치에 있음.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
BAD: src/server/slots/user.slot.ts
|
|
78
|
+
FIX: spec/slots/user.slot.ts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### GENERATED_DIRECT_EDIT (error)
|
|
82
|
+
자동 생성된 파일을 직접 수정하면 안 됨.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
BAD: .mandu/generated/server/routes.ts 직접 편집
|
|
86
|
+
FIX: 소스 파일을 수정하고 regenerate
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CONTRACT_MISSING (warning)
|
|
90
|
+
API route에 대응하는 contract가 없음.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Missing: src/shared/contracts/user.contract.ts
|
|
94
|
+
For: app/api/users/route.ts
|
|
95
|
+
FIX: contract 파일 생성
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### SLOT_NAMING (warning)
|
|
99
|
+
Slot 파일명이 규칙에 맞지 않음.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
BAD: spec/slots/userData.ts
|
|
103
|
+
FIX: spec/slots/user-data.slot.ts
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## CLI Commands
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 전체 아키텍처 검사
|
|
110
|
+
bunx mandu guard arch
|
|
111
|
+
|
|
112
|
+
# 특정 프리셋으로 검사
|
|
113
|
+
bunx mandu guard arch --preset fsd
|
|
114
|
+
|
|
115
|
+
# CI 모드 (위반 시 exit 1)
|
|
116
|
+
bunx mandu guard arch --ci
|
|
117
|
+
|
|
118
|
+
# Watch 모드
|
|
119
|
+
bunx mandu guard arch --watch
|
|
120
|
+
|
|
121
|
+
# 단일 파일 검사
|
|
122
|
+
bunx mandu guard check-file src/client/features/auth.ts
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// mandu.config.ts
|
|
129
|
+
export default {
|
|
130
|
+
guard: {
|
|
131
|
+
preset: "mandu",
|
|
132
|
+
rules: {
|
|
133
|
+
LAYER_VIOLATION: "error", // "error" | "warning" | "off"
|
|
134
|
+
FORBIDDEN_IMPORT: "error",
|
|
135
|
+
WRONG_SLOT_LOCATION: "warning",
|
|
136
|
+
CONTRACT_MISSING: "off",
|
|
137
|
+
GENERATED_DIRECT_EDIT: "error",
|
|
138
|
+
SLOT_NAMING: "warning",
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## MCP Tools
|
|
145
|
+
|
|
146
|
+
| Tool | Purpose |
|
|
147
|
+
|------|---------|
|
|
148
|
+
| `mandu_guard` | Run architecture check |
|
|
149
|
+
| `mandu_guard_heal` | Auto-fix violations |
|
|
150
|
+
| `mandu_negotiate` | Analyze with preset awareness |
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-hydration
|
|
3
|
+
description: |
|
|
4
|
+
Island import 규칙. 반드시 @mandujs/core/client에서 import.
|
|
5
|
+
"use client", .island.tsx, useState, hydration 작업 시 자동 호출.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mandu Island Hydration
|
|
9
|
+
|
|
10
|
+
Island Hydration은 페이지의 일부분만 클라이언트에서 인터랙티브하게 만드는 기술입니다.
|
|
11
|
+
|
|
12
|
+
## Import Rule (CRITICAL)
|
|
13
|
+
|
|
14
|
+
Client islands MUST import from `@mandujs/core/client`, NOT `@mandujs/core`.
|
|
15
|
+
The main module includes server-side dependencies that break client bundles.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// CORRECT
|
|
19
|
+
import { island } from "@mandujs/core/client";
|
|
20
|
+
import { useServerData, useHydrated, useIslandEvent } from "@mandujs/core/client";
|
|
21
|
+
|
|
22
|
+
// WRONG - will cause build errors or bloated bundles
|
|
23
|
+
import { island } from "@mandujs/core";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Island File Conventions
|
|
27
|
+
|
|
28
|
+
| File | Purpose |
|
|
29
|
+
|------|---------|
|
|
30
|
+
| `*.island.tsx` | Island component (auto-detected for hydration) |
|
|
31
|
+
| `*.client.tsx` | Client-only logic file |
|
|
32
|
+
| `*.slot.ts` / `*.slot.tsx` | Server-side data loader |
|
|
33
|
+
|
|
34
|
+
All island files MUST have `"use client"` directive at the very top.
|
|
35
|
+
|
|
36
|
+
## island() API
|
|
37
|
+
|
|
38
|
+
### Declarative Style (simple)
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// app/counter.island.tsx
|
|
42
|
+
"use client";
|
|
43
|
+
|
|
44
|
+
import { island } from "@mandujs/core/client";
|
|
45
|
+
import { useState } from "react";
|
|
46
|
+
|
|
47
|
+
function Counter() {
|
|
48
|
+
const [count, setCount] = useState(0);
|
|
49
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default island("visible", Counter);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Client Island with Setup (advanced)
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// app/chat.island.tsx
|
|
59
|
+
"use client";
|
|
60
|
+
|
|
61
|
+
import { island } from "@mandujs/core/client";
|
|
62
|
+
import { useState } from "react";
|
|
63
|
+
|
|
64
|
+
export default island<ServerData>({
|
|
65
|
+
setup(data) {
|
|
66
|
+
const [messages, setMessages] = useState(data.initialMessages);
|
|
67
|
+
return { messages, setMessages };
|
|
68
|
+
},
|
|
69
|
+
render({ messages, setMessages }) {
|
|
70
|
+
return <div>{messages.map(m => <p key={m.id}>{m.text}</p>)}</div>;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Hydration Priorities
|
|
76
|
+
|
|
77
|
+
| Priority | When loaded | Use case |
|
|
78
|
+
|----------|------------|----------|
|
|
79
|
+
| `"immediate"` | Page load | Critical interactions (nav, auth) |
|
|
80
|
+
| `"visible"` | In viewport | Default - most components |
|
|
81
|
+
| `"idle"` | Browser idle | Below-fold content |
|
|
82
|
+
| `"interaction"` | User interacts | Heavy widgets (editor, map) |
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
island("immediate", NavigationIsland); // Load right away
|
|
86
|
+
island("visible", CommentSection); // Load when scrolled into view
|
|
87
|
+
island("idle", AnalyticsDashboard); // Load when browser is idle
|
|
88
|
+
island("interaction", RichTextEditor); // Load on first click/focus
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Client Hooks
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { useServerData, useHydrated, useIslandEvent } from "@mandujs/core/client";
|
|
95
|
+
|
|
96
|
+
// Access data from server slot
|
|
97
|
+
const data = useServerData<UserData>("user", defaultValue);
|
|
98
|
+
|
|
99
|
+
// Check if component has hydrated (SSR-safe code branching)
|
|
100
|
+
const isHydrated = useHydrated();
|
|
101
|
+
|
|
102
|
+
// Cross-island communication
|
|
103
|
+
const { emit, on } = useIslandEvent();
|
|
104
|
+
emit("cart:updated", { items: newItems });
|
|
105
|
+
on("cart:updated", (data) => setCartItems(data.items));
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Server Data Flow
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// spec/slots/user-profile.slot.ts (server)
|
|
112
|
+
import { Mandu } from "@mandujs/core";
|
|
113
|
+
export default Mandu.filling()
|
|
114
|
+
.get(async (ctx) => {
|
|
115
|
+
const user = await db.getUser(ctx.params.id);
|
|
116
|
+
return ctx.ok({ user }); // Data available to Islands
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// app/user-profile.island.tsx (client)
|
|
120
|
+
"use client";
|
|
121
|
+
import { useServerData } from "@mandujs/core/client";
|
|
122
|
+
export default function UserProfile() {
|
|
123
|
+
const { user } = useServerData("user-profile");
|
|
124
|
+
return <div>{user.name}</div>;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Common Mistakes
|
|
129
|
+
|
|
130
|
+
- Importing from `@mandujs/core` instead of `@mandujs/core/client` in Islands
|
|
131
|
+
- Forgetting `"use client"` directive in island files
|
|
132
|
+
- Using `useState`/`useEffect` in server components (non-island files)
|
|
133
|
+
- Setting all Islands to `"immediate"` priority (defeats partial hydration)
|
|
134
|
+
- Putting heavy server imports in `.island.tsx` files (increases bundle size)
|