@kood/claude-code 0.1.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/dist/index.d.ts +2 -0
- package/dist/index.js +297 -0
- package/package.json +47 -0
- package/templates/hono/CLAUDE.md +376 -0
- package/templates/hono/docs/deployment/cloudflare.md +328 -0
- package/templates/hono/docs/deployment/index.md +291 -0
- package/templates/hono/docs/git/index.md +180 -0
- package/templates/hono/docs/library/hono/error-handling.md +400 -0
- package/templates/hono/docs/library/hono/index.md +241 -0
- package/templates/hono/docs/library/hono/middleware.md +334 -0
- package/templates/hono/docs/library/hono/rpc.md +454 -0
- package/templates/hono/docs/library/hono/validation.md +328 -0
- package/templates/hono/docs/library/prisma/index.md +427 -0
- package/templates/hono/docs/library/zod/index.md +413 -0
- package/templates/hono/docs/mcp/context7.md +106 -0
- package/templates/hono/docs/mcp/index.md +94 -0
- package/templates/hono/docs/mcp/sequential-thinking.md +101 -0
- package/templates/hono/docs/mcp/sgrep.md +105 -0
- package/templates/hono/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +136 -0
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +303 -0
- package/templates/tanstack-start/CLAUDE.md +279 -0
- package/templates/tanstack-start/docs/architecture/architecture.md +547 -0
- package/templates/tanstack-start/docs/deployment/cloudflare.md +346 -0
- package/templates/tanstack-start/docs/deployment/index.md +102 -0
- package/templates/tanstack-start/docs/deployment/nitro.md +211 -0
- package/templates/tanstack-start/docs/deployment/railway.md +364 -0
- package/templates/tanstack-start/docs/deployment/vercel.md +287 -0
- package/templates/tanstack-start/docs/design/accessibility.md +433 -0
- package/templates/tanstack-start/docs/design/color.md +235 -0
- package/templates/tanstack-start/docs/design/components.md +409 -0
- package/templates/tanstack-start/docs/design/index.md +107 -0
- package/templates/tanstack-start/docs/design/safe-area.md +317 -0
- package/templates/tanstack-start/docs/design/spacing.md +341 -0
- package/templates/tanstack-start/docs/design/tailwind-setup.md +470 -0
- package/templates/tanstack-start/docs/design/typography.md +324 -0
- package/templates/tanstack-start/docs/git/index.md +203 -0
- package/templates/tanstack-start/docs/guides/best-practices.md +753 -0
- package/templates/tanstack-start/docs/guides/getting-started.md +304 -0
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +303 -0
- package/templates/tanstack-start/docs/guides/prettier.md +189 -0
- package/templates/tanstack-start/docs/guides/project-templates.md +710 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +136 -0
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +138 -0
- package/templates/tanstack-start/docs/library/better-auth/index.md +83 -0
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +111 -0
- package/templates/tanstack-start/docs/library/better-auth/session.md +127 -0
- package/templates/tanstack-start/docs/library/better-auth/setup.md +123 -0
- package/templates/tanstack-start/docs/library/prisma/crud.md +218 -0
- package/templates/tanstack-start/docs/library/prisma/index.md +165 -0
- package/templates/tanstack-start/docs/library/prisma/relations.md +191 -0
- package/templates/tanstack-start/docs/library/prisma/schema.md +177 -0
- package/templates/tanstack-start/docs/library/prisma/setup.md +156 -0
- package/templates/tanstack-start/docs/library/prisma/transactions.md +140 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +196 -0
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +110 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +170 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +173 -0
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +171 -0
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +114 -0
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +142 -0
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +163 -0
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +128 -0
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +85 -0
- package/templates/tanstack-start/docs/library/zod/basic-types.md +186 -0
- package/templates/tanstack-start/docs/library/zod/complex-types.md +204 -0
- package/templates/tanstack-start/docs/library/zod/index.md +186 -0
- package/templates/tanstack-start/docs/library/zod/transforms.md +174 -0
- package/templates/tanstack-start/docs/library/zod/validation.md +208 -0
- package/templates/tanstack-start/docs/mcp/context7.md +204 -0
- package/templates/tanstack-start/docs/mcp/index.md +116 -0
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +180 -0
- package/templates/tanstack-start/docs/mcp/sgrep.md +174 -0
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +150 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +293 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# CLAUDE.md - Claude Code Instructions
|
|
2
|
+
|
|
3
|
+
> Hono 서버 프레임워크 프로젝트 작업 지침
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚨 STOP - 작업 전 필수 확인
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ 이 프로젝트에서 작업하기 전에 이 문서를 끝까지 읽으세요. │
|
|
12
|
+
│ 특히 ⛔ NEVER DO 섹션의 규칙은 절대 위반하지 마세요. │
|
|
13
|
+
│ │
|
|
14
|
+
│ 📖 작업 유형별 상세 문서: docs/ 폴더 참조 │
|
|
15
|
+
└─────────────────────────────────────────────────────────────┘
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ⛔ NEVER DO (절대 금지 - 예외 없음)
|
|
21
|
+
|
|
22
|
+
### Git 커밋 금지 사항
|
|
23
|
+
```
|
|
24
|
+
❌ "Generated with Claude Code" 포함 금지
|
|
25
|
+
❌ "🤖" 또는 AI 관련 이모지 포함 금지
|
|
26
|
+
❌ "Co-Authored-By:" 헤더 포함 금지
|
|
27
|
+
❌ AI/봇이 작성했다는 어떤 표시도 금지
|
|
28
|
+
❌ 커밋 메시지 여러 줄 작성 금지
|
|
29
|
+
❌ 커밋 메시지에 이모지 사용 금지
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Prisma 금지 사항
|
|
33
|
+
```
|
|
34
|
+
❌ prisma db push 자동 실행 금지
|
|
35
|
+
❌ prisma migrate 자동 실행 금지
|
|
36
|
+
❌ prisma generate 자동 실행 금지
|
|
37
|
+
❌ schema.prisma 임의 변경 금지 (요청된 것만)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### API 구현 금지 사항
|
|
41
|
+
```
|
|
42
|
+
❌ handler 내부에서 수동 검증 금지 (zValidator 사용)
|
|
43
|
+
❌ handler 내부에서 수동 인증 체크 금지 (middleware 사용)
|
|
44
|
+
❌ app.onError 없이 에러 처리 금지
|
|
45
|
+
❌ HTTPException 없이 에러 throw 금지
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 코드 검색 금지 사항
|
|
49
|
+
```
|
|
50
|
+
❌ grep, rg 등 기본 검색 도구 사용 금지
|
|
51
|
+
❌ find 명령어로 코드 검색 금지
|
|
52
|
+
✅ 코드베이스 검색 시 sgrep 사용 필수
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## ✅ ALWAYS DO (필수 실행)
|
|
58
|
+
|
|
59
|
+
### 1. 작업 전: 관련 문서 읽기
|
|
60
|
+
```
|
|
61
|
+
API 작업 → docs/library/hono/ 읽기
|
|
62
|
+
DB 작업 → docs/library/prisma/ 읽기
|
|
63
|
+
검증 작업 → docs/library/zod/ 읽기
|
|
64
|
+
배포 작업 → docs/deployment/ 읽기
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. MCP 도구 적극 활용
|
|
68
|
+
```
|
|
69
|
+
코드베이스 검색 → sgrep 사용 (grep/rg 금지)
|
|
70
|
+
복잡한 분석/디버깅 → Sequential Thinking 사용
|
|
71
|
+
라이브러리 문서 → Context7 사용
|
|
72
|
+
```
|
|
73
|
+
**상세**: `docs/mcp/` 참고
|
|
74
|
+
|
|
75
|
+
### 3. 복잡한 작업 시: Gemini Review 실행
|
|
76
|
+
```
|
|
77
|
+
아키텍처 설계/변경 → gemini-review (architecture)
|
|
78
|
+
구현 계획 검증 → gemini-review (plan)
|
|
79
|
+
복잡한 코드 리뷰 → gemini-review (code)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**실행 조건**:
|
|
83
|
+
- 3개 이상 파일 수정하는 기능 구현
|
|
84
|
+
- 새로운 아키텍처 패턴 도입
|
|
85
|
+
- 보안 관련 코드 (인증, 권한, 암호화)
|
|
86
|
+
- 성능 크리티컬 코드
|
|
87
|
+
|
|
88
|
+
**상세**: `docs/skills/gemini-review/SKILL.md` 참고
|
|
89
|
+
|
|
90
|
+
### 4. 작업 완료 후: Git 커밋
|
|
91
|
+
```bash
|
|
92
|
+
git add .
|
|
93
|
+
git commit -m "<prefix>: <설명>"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**커밋 형식**: `<prefix>: <설명>` (한 줄, 본문 없음)
|
|
97
|
+
|
|
98
|
+
**Prefix**: `feat` | `fix` | `refactor` | `style` | `docs` | `test` | `chore` | `perf` | `ci`
|
|
99
|
+
|
|
100
|
+
**예시**:
|
|
101
|
+
```bash
|
|
102
|
+
feat: 사용자 인증 API 추가
|
|
103
|
+
fix: JWT 토큰 검증 오류 수정
|
|
104
|
+
docs: API 문서 업데이트
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 📚 문서 참조 테이블
|
|
110
|
+
|
|
111
|
+
| 작업 | 문서 경로 | 필독 여부 |
|
|
112
|
+
|------|----------|----------|
|
|
113
|
+
| **Git 규칙** | `docs/git/index.md` | 🔴 필수 |
|
|
114
|
+
| **MCP 도구** | `docs/mcp/` | 🔴 필수 |
|
|
115
|
+
| **Gemini Review** | `docs/skills/gemini-review/` | 🟡 복잡한 작업 시 |
|
|
116
|
+
| **API 개발** | `docs/library/hono/` | 🔴 필수 |
|
|
117
|
+
| **DB** | `docs/library/prisma/` | 🟡 해당 시 |
|
|
118
|
+
| **검증** | `docs/library/zod/` | 🟡 해당 시 |
|
|
119
|
+
| **배포** | `docs/deployment/` | 🟡 해당 시 |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 🛠 Tech Stack (버전 주의)
|
|
124
|
+
|
|
125
|
+
| 기술 | 버전 | 주의사항 |
|
|
126
|
+
|------|------|----------|
|
|
127
|
+
| Hono | 최신 | Web Standards 기반 서버 프레임워크 |
|
|
128
|
+
| TypeScript | 5.x | strict mode |
|
|
129
|
+
| Prisma | **7.x** | `prisma-client` (js 아님), output 필수 |
|
|
130
|
+
| Zod | **4.x** | `z.email()`, `z.url()` (string().email() 아님) |
|
|
131
|
+
| @hono/zod-validator | 최신 | Zod 검증 미들웨어 |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 📁 Directory Structure
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
src/
|
|
139
|
+
├── index.ts # Entry point
|
|
140
|
+
├── routes/ # 라우트 모듈
|
|
141
|
+
│ ├── index.ts # 라우트 통합
|
|
142
|
+
│ ├── users.ts # /users 라우트
|
|
143
|
+
│ └── posts.ts # /posts 라우트
|
|
144
|
+
├── middleware/ # 미들웨어
|
|
145
|
+
│ ├── auth.ts # 인증 미들웨어
|
|
146
|
+
│ └── logger.ts # 로깅 미들웨어
|
|
147
|
+
├── validators/ # Zod 스키마
|
|
148
|
+
│ ├── user.ts # 사용자 스키마
|
|
149
|
+
│ └── post.ts # 게시글 스키마
|
|
150
|
+
├── services/ # 비즈니스 로직
|
|
151
|
+
│ └── user.service.ts # 사용자 서비스
|
|
152
|
+
├── database/ # DB 연결
|
|
153
|
+
│ └── prisma.ts # Prisma Client
|
|
154
|
+
├── types/ # 타입 정의
|
|
155
|
+
│ └── env.d.ts # 환경변수 타입
|
|
156
|
+
└── lib/ # 유틸리티
|
|
157
|
+
└── errors.ts # 커스텀 에러
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🔧 Code Conventions
|
|
163
|
+
|
|
164
|
+
### File Naming
|
|
165
|
+
- **kebab-case**: `user-service.ts`, `auth-middleware.ts`
|
|
166
|
+
- **라우트 파일**: `users.ts`, `posts.ts`
|
|
167
|
+
|
|
168
|
+
### TypeScript
|
|
169
|
+
- `const` 선언 사용 (function 대신)
|
|
170
|
+
- 명시적 return type
|
|
171
|
+
- `interface` (객체) / `type` (유니온)
|
|
172
|
+
- `any` 금지 → `unknown` 사용
|
|
173
|
+
|
|
174
|
+
### Import
|
|
175
|
+
```typescript
|
|
176
|
+
// @/ → ./src/
|
|
177
|
+
import { prisma } from '@/database/prisma'
|
|
178
|
+
import { userSchema } from '@/validators/user'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**순서**: 외부 → 내부(@/) → 상대경로 → type imports
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 📝 Quick Patterns (복사용)
|
|
186
|
+
|
|
187
|
+
### App 설정
|
|
188
|
+
```typescript
|
|
189
|
+
import { Hono } from 'hono'
|
|
190
|
+
import { HTTPException } from 'hono/http-exception'
|
|
191
|
+
|
|
192
|
+
type Bindings = {
|
|
193
|
+
DATABASE_URL: string
|
|
194
|
+
JWT_SECRET: string
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const app = new Hono<{ Bindings: Bindings }>()
|
|
198
|
+
|
|
199
|
+
// 글로벌 에러 핸들러
|
|
200
|
+
app.onError((err, c) => {
|
|
201
|
+
if (err instanceof HTTPException) {
|
|
202
|
+
return c.json({ error: err.message }, err.status)
|
|
203
|
+
}
|
|
204
|
+
console.error(err)
|
|
205
|
+
return c.json({ error: 'Internal Server Error' }, 500)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// 404 핸들러
|
|
209
|
+
app.notFound((c) => {
|
|
210
|
+
return c.json({ error: 'Not Found', path: c.req.path }, 404)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
export default app
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 라우트 + Zod 검증 (GET)
|
|
217
|
+
```typescript
|
|
218
|
+
import { Hono } from 'hono'
|
|
219
|
+
import { zValidator } from '@hono/zod-validator'
|
|
220
|
+
import { z } from 'zod'
|
|
221
|
+
|
|
222
|
+
const app = new Hono()
|
|
223
|
+
|
|
224
|
+
// ✅ 올바른 패턴: zValidator 사용
|
|
225
|
+
const querySchema = z.object({
|
|
226
|
+
page: z.coerce.number().positive().optional(),
|
|
227
|
+
limit: z.coerce.number().max(100).optional(),
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
app.get('/users', zValidator('query', querySchema), (c) => {
|
|
231
|
+
const { page = 1, limit = 10 } = c.req.valid('query')
|
|
232
|
+
return c.json({ page, limit, users: [] })
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 라우트 + Zod 검증 (POST)
|
|
237
|
+
```typescript
|
|
238
|
+
import { zValidator } from '@hono/zod-validator'
|
|
239
|
+
import { z } from 'zod'
|
|
240
|
+
|
|
241
|
+
// ✅ Zod v4 문법
|
|
242
|
+
const createUserSchema = z.object({
|
|
243
|
+
email: z.email(), // ✅ v4
|
|
244
|
+
name: z.string().min(1).trim(),
|
|
245
|
+
website: z.url().optional(), // ✅ v4
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
app.post('/users', zValidator('json', createUserSchema), async (c) => {
|
|
249
|
+
const data = c.req.valid('json')
|
|
250
|
+
// prisma.user.create({ data })
|
|
251
|
+
return c.json({ user: data }, 201)
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### ❌ 잘못된 패턴 (금지)
|
|
256
|
+
```typescript
|
|
257
|
+
// ❌ handler 내부에서 수동 검증 금지
|
|
258
|
+
app.post('/users', async (c) => {
|
|
259
|
+
const body = await c.req.json()
|
|
260
|
+
// ❌ 이렇게 하지 마세요!
|
|
261
|
+
if (!body.email) {
|
|
262
|
+
return c.json({ error: 'Email required' }, 400)
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// ❌ 일반 Error throw 금지
|
|
267
|
+
app.get('/user/:id', async (c) => {
|
|
268
|
+
throw new Error('Not found') // ❌ HTTPException 사용해야 함
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### HTTPException 사용
|
|
273
|
+
```typescript
|
|
274
|
+
import { HTTPException } from 'hono/http-exception'
|
|
275
|
+
|
|
276
|
+
app.get('/users/:id', async (c) => {
|
|
277
|
+
const id = c.req.param('id')
|
|
278
|
+
const user = await prisma.user.findUnique({ where: { id } })
|
|
279
|
+
|
|
280
|
+
if (!user) {
|
|
281
|
+
throw new HTTPException(404, { message: 'User not found' })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return c.json({ user })
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 인증 미들웨어
|
|
289
|
+
```typescript
|
|
290
|
+
import { createMiddleware } from 'hono/factory'
|
|
291
|
+
import { HTTPException } from 'hono/http-exception'
|
|
292
|
+
|
|
293
|
+
type Env = {
|
|
294
|
+
Variables: {
|
|
295
|
+
userId: string
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const authMiddleware = createMiddleware<Env>(async (c, next) => {
|
|
300
|
+
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
|
301
|
+
|
|
302
|
+
if (!token) {
|
|
303
|
+
throw new HTTPException(401, { message: 'Unauthorized' })
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// JWT 검증 로직
|
|
307
|
+
const payload = verifyToken(token)
|
|
308
|
+
c.set('userId', payload.sub)
|
|
309
|
+
|
|
310
|
+
await next()
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// 사용
|
|
314
|
+
app.get('/me', authMiddleware, (c) => {
|
|
315
|
+
const userId = c.get('userId')
|
|
316
|
+
return c.json({ userId })
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Zod Schema (v4 문법!)
|
|
321
|
+
```typescript
|
|
322
|
+
import { z } from 'zod'
|
|
323
|
+
|
|
324
|
+
const schema = z.object({
|
|
325
|
+
email: z.email(), // ✅ v4
|
|
326
|
+
name: z.string().min(1).trim(),
|
|
327
|
+
website: z.url().optional(), // ✅ v4
|
|
328
|
+
age: z.number().min(0),
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// 커스텀 에러 핸들링
|
|
332
|
+
app.post(
|
|
333
|
+
'/users',
|
|
334
|
+
zValidator('json', schema, (result, c) => {
|
|
335
|
+
if (!result.success) {
|
|
336
|
+
return c.json({ errors: result.error.flatten() }, 400)
|
|
337
|
+
}
|
|
338
|
+
}),
|
|
339
|
+
(c) => {
|
|
340
|
+
const data = c.req.valid('json')
|
|
341
|
+
return c.json({ user: data }, 201)
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### RPC Client (Type-safe)
|
|
347
|
+
```typescript
|
|
348
|
+
// server.ts
|
|
349
|
+
const app = new Hono()
|
|
350
|
+
.get('/users', (c) => c.json({ users: [] }))
|
|
351
|
+
.post('/users', zValidator('json', createUserSchema), (c) => {
|
|
352
|
+
const data = c.req.valid('json')
|
|
353
|
+
return c.json({ user: data }, 201)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
export type AppType = typeof app
|
|
357
|
+
|
|
358
|
+
// client.ts
|
|
359
|
+
import { hc } from 'hono/client'
|
|
360
|
+
import type { AppType } from './server'
|
|
361
|
+
|
|
362
|
+
const client = hc<AppType>('http://localhost:8787/')
|
|
363
|
+
|
|
364
|
+
// Type-safe API 호출
|
|
365
|
+
const res = await client.users.$get()
|
|
366
|
+
const data = await res.json() // { users: [] }
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 🔗 Quick Links
|
|
372
|
+
|
|
373
|
+
- [Hono 가이드](./docs/library/hono/index.md)
|
|
374
|
+
- [Git 규칙](./docs/git/index.md)
|
|
375
|
+
- [MCP 가이드](./docs/mcp/index.md)
|
|
376
|
+
- [배포 가이드](./docs/deployment/index.md)
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Cloudflare Workers 배포
|
|
2
|
+
|
|
3
|
+
> Hono + Cloudflare Workers 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 프로젝트 생성
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create hono@latest my-app
|
|
11
|
+
# cloudflare-workers 템플릿 선택
|
|
12
|
+
|
|
13
|
+
cd my-app
|
|
14
|
+
npm install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 프로젝트 구조
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
my-app/
|
|
23
|
+
├── src/
|
|
24
|
+
│ └── index.ts
|
|
25
|
+
├── wrangler.toml
|
|
26
|
+
├── package.json
|
|
27
|
+
└── tsconfig.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 기본 설정
|
|
33
|
+
|
|
34
|
+
### wrangler.toml
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
name = "my-hono-app"
|
|
38
|
+
main = "src/index.ts"
|
|
39
|
+
compatibility_date = "2024-01-01"
|
|
40
|
+
compatibility_flags = ["nodejs_compat"]
|
|
41
|
+
|
|
42
|
+
[vars]
|
|
43
|
+
NODE_ENV = "production"
|
|
44
|
+
|
|
45
|
+
# KV Namespace
|
|
46
|
+
[[kv_namespaces]]
|
|
47
|
+
binding = "MY_KV"
|
|
48
|
+
id = "your-kv-namespace-id"
|
|
49
|
+
|
|
50
|
+
# D1 Database
|
|
51
|
+
[[d1_databases]]
|
|
52
|
+
binding = "DB"
|
|
53
|
+
database_name = "my-database"
|
|
54
|
+
database_id = "your-database-id"
|
|
55
|
+
|
|
56
|
+
# R2 Bucket
|
|
57
|
+
[[r2_buckets]]
|
|
58
|
+
binding = "MY_BUCKET"
|
|
59
|
+
bucket_name = "my-bucket"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### src/index.ts
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Hono } from 'hono'
|
|
66
|
+
|
|
67
|
+
type Bindings = {
|
|
68
|
+
NODE_ENV: string
|
|
69
|
+
MY_KV: KVNamespace
|
|
70
|
+
DB: D1Database
|
|
71
|
+
MY_BUCKET: R2Bucket
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const app = new Hono<{ Bindings: Bindings }>()
|
|
75
|
+
|
|
76
|
+
app.get('/', (c) => {
|
|
77
|
+
return c.json({ message: 'Hello Cloudflare Workers!' })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
export default app
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Bindings 사용
|
|
86
|
+
|
|
87
|
+
### KV Namespace
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
app.get('/kv/:key', async (c) => {
|
|
91
|
+
const key = c.req.param('key')
|
|
92
|
+
const value = await c.env.MY_KV.get(key)
|
|
93
|
+
return c.json({ key, value })
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
app.put('/kv/:key', async (c) => {
|
|
97
|
+
const key = c.req.param('key')
|
|
98
|
+
const { value } = await c.req.json()
|
|
99
|
+
await c.env.MY_KV.put(key, value)
|
|
100
|
+
return c.json({ success: true })
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### D1 Database
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
app.get('/users', async (c) => {
|
|
108
|
+
const { results } = await c.env.DB.prepare(
|
|
109
|
+
'SELECT * FROM users'
|
|
110
|
+
).all()
|
|
111
|
+
return c.json({ users: results })
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
app.post('/users', async (c) => {
|
|
115
|
+
const { name, email } = await c.req.json()
|
|
116
|
+
const { success } = await c.env.DB.prepare(
|
|
117
|
+
'INSERT INTO users (name, email) VALUES (?, ?)'
|
|
118
|
+
).bind(name, email).run()
|
|
119
|
+
return c.json({ success }, 201)
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### R2 Bucket
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
app.put('/upload/:key', async (c) => {
|
|
127
|
+
const key = c.req.param('key')
|
|
128
|
+
await c.env.MY_BUCKET.put(key, c.req.body)
|
|
129
|
+
return c.json({ success: true })
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
app.get('/download/:key', async (c) => {
|
|
133
|
+
const key = c.req.param('key')
|
|
134
|
+
const object = await c.env.MY_BUCKET.get(key)
|
|
135
|
+
|
|
136
|
+
if (!object) {
|
|
137
|
+
return c.notFound()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new Response(object.body, {
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 환경 변수
|
|
151
|
+
|
|
152
|
+
### 로컬 개발 (.dev.vars)
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
DATABASE_URL=postgresql://...
|
|
156
|
+
JWT_SECRET=dev-secret
|
|
157
|
+
API_KEY=dev-api-key
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 시크릿 설정
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# 시크릿 추가
|
|
164
|
+
npx wrangler secret put JWT_SECRET
|
|
165
|
+
npx wrangler secret put DATABASE_URL
|
|
166
|
+
|
|
167
|
+
# 시크릿 목록
|
|
168
|
+
npx wrangler secret list
|
|
169
|
+
|
|
170
|
+
# 시크릿 삭제
|
|
171
|
+
npx wrangler secret delete JWT_SECRET
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 타입 생성
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# wrangler.toml 기반 타입 생성
|
|
180
|
+
npx wrangler types
|
|
181
|
+
|
|
182
|
+
# 또는 환경별 타입 생성
|
|
183
|
+
npx wrangler types --env-interface CloudflareBindings
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### worker-configuration.d.ts
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface CloudflareBindings {
|
|
190
|
+
NODE_ENV: string
|
|
191
|
+
MY_KV: KVNamespace
|
|
192
|
+
DB: D1Database
|
|
193
|
+
MY_BUCKET: R2Bucket
|
|
194
|
+
JWT_SECRET: string
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### tsconfig.json
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"compilerOptions": {
|
|
203
|
+
"types": [
|
|
204
|
+
"@cloudflare/workers-types",
|
|
205
|
+
"./worker-configuration.d.ts"
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 배포
|
|
214
|
+
|
|
215
|
+
### 개발 서버
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npm run dev
|
|
219
|
+
# 또는
|
|
220
|
+
npx wrangler dev
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 프로덕션 배포
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm run deploy
|
|
227
|
+
# 또는
|
|
228
|
+
npx wrangler deploy
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 환경별 배포
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# staging
|
|
235
|
+
npx wrangler deploy --env staging
|
|
236
|
+
|
|
237
|
+
# production
|
|
238
|
+
npx wrangler deploy --env production
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### wrangler.toml (환경별)
|
|
242
|
+
|
|
243
|
+
```toml
|
|
244
|
+
name = "my-app"
|
|
245
|
+
main = "src/index.ts"
|
|
246
|
+
|
|
247
|
+
[env.staging]
|
|
248
|
+
name = "my-app-staging"
|
|
249
|
+
vars = { NODE_ENV = "staging" }
|
|
250
|
+
|
|
251
|
+
[env.production]
|
|
252
|
+
name = "my-app-production"
|
|
253
|
+
vars = { NODE_ENV = "production" }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## GitHub Actions
|
|
259
|
+
|
|
260
|
+
```yaml
|
|
261
|
+
name: Deploy to Cloudflare Workers
|
|
262
|
+
|
|
263
|
+
on:
|
|
264
|
+
push:
|
|
265
|
+
branches:
|
|
266
|
+
- main
|
|
267
|
+
|
|
268
|
+
jobs:
|
|
269
|
+
deploy:
|
|
270
|
+
runs-on: ubuntu-latest
|
|
271
|
+
steps:
|
|
272
|
+
- uses: actions/checkout@v4
|
|
273
|
+
|
|
274
|
+
- name: Setup Node.js
|
|
275
|
+
uses: actions/setup-node@v4
|
|
276
|
+
with:
|
|
277
|
+
node-version: '20'
|
|
278
|
+
|
|
279
|
+
- name: Install dependencies
|
|
280
|
+
run: npm ci
|
|
281
|
+
|
|
282
|
+
- name: Deploy
|
|
283
|
+
uses: cloudflare/wrangler-action@v3
|
|
284
|
+
with:
|
|
285
|
+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
286
|
+
# 환경 지정 시
|
|
287
|
+
# command: deploy --env production
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### GitHub Secrets 설정
|
|
291
|
+
|
|
292
|
+
1. Cloudflare Dashboard → My Profile → API Tokens
|
|
293
|
+
2. "Create Token" → "Edit Cloudflare Workers" 템플릿 사용
|
|
294
|
+
3. GitHub Repository → Settings → Secrets → `CLOUDFLARE_API_TOKEN` 추가
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 로깅 및 모니터링
|
|
299
|
+
|
|
300
|
+
### 실시간 로그
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
npx wrangler tail
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 커스텀 로깅
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
app.use(async (c, next) => {
|
|
310
|
+
const start = Date.now()
|
|
311
|
+
await next()
|
|
312
|
+
const duration = Date.now() - start
|
|
313
|
+
|
|
314
|
+
console.log({
|
|
315
|
+
method: c.req.method,
|
|
316
|
+
path: c.req.path,
|
|
317
|
+
status: c.res.status,
|
|
318
|
+
duration: `${duration}ms`,
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 관련 문서
|
|
326
|
+
|
|
327
|
+
- [배포 개요](./index.md)
|
|
328
|
+
- [Hono 기본](../library/hono/index.md)
|