@kood/claude-code 0.1.7 → 0.1.9
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.js +118 -3
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +53 -326
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +49 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +24 -122
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +64 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +58 -276
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +0 -4
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/design/index.md +119 -12
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +18 -2
- package/templates/tanstack-start/docs/library/zod/index.md +16 -1
- package/templates/tanstack-start/docs/design/accessibility.md +0 -163
- package/templates/tanstack-start/docs/design/color.md +0 -93
- package/templates/tanstack-start/docs/design/spacing.md +0 -122
- package/templates/tanstack-start/docs/design/typography.md +0 -80
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -48
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -74
package/dist/index.js
CHANGED
|
@@ -136,6 +136,60 @@ var listAvailableTemplates = async () => {
|
|
|
136
136
|
}
|
|
137
137
|
return templates;
|
|
138
138
|
};
|
|
139
|
+
var copySkills = async (templates, targetDir) => {
|
|
140
|
+
const counter = { files: 0, directories: 0 };
|
|
141
|
+
const targetSkillsDir = path.join(targetDir, ".claude", "skills");
|
|
142
|
+
for (const template of templates) {
|
|
143
|
+
const templatePath = getTemplatePath(template);
|
|
144
|
+
const skillsSrc = path.join(templatePath, "docs", "skills");
|
|
145
|
+
if (await fs.pathExists(skillsSrc)) {
|
|
146
|
+
await fs.ensureDir(targetSkillsDir);
|
|
147
|
+
await copyRecursive(skillsSrc, targetSkillsDir, counter);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return counter;
|
|
151
|
+
};
|
|
152
|
+
var copyCommands = async (templates, targetDir) => {
|
|
153
|
+
const counter = { files: 0, directories: 0 };
|
|
154
|
+
const targetCommandsDir = path.join(targetDir, ".claude", "commands");
|
|
155
|
+
for (const template of templates) {
|
|
156
|
+
const templatePath = getTemplatePath(template);
|
|
157
|
+
const commandsSrc = path.join(templatePath, "docs", "commands");
|
|
158
|
+
if (await fs.pathExists(commandsSrc)) {
|
|
159
|
+
await fs.ensureDir(targetCommandsDir);
|
|
160
|
+
await copyRecursive(commandsSrc, targetCommandsDir, counter);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return counter;
|
|
164
|
+
};
|
|
165
|
+
var checkSkillsAndCommandsExist = async (templates) => {
|
|
166
|
+
let hasSkills = false;
|
|
167
|
+
let hasCommands = false;
|
|
168
|
+
for (const template of templates) {
|
|
169
|
+
const templatePath = getTemplatePath(template);
|
|
170
|
+
const skillsSrc = path.join(templatePath, "docs", "skills");
|
|
171
|
+
const commandsSrc = path.join(templatePath, "docs", "commands");
|
|
172
|
+
if (await fs.pathExists(skillsSrc)) {
|
|
173
|
+
hasSkills = true;
|
|
174
|
+
}
|
|
175
|
+
if (await fs.pathExists(commandsSrc)) {
|
|
176
|
+
hasCommands = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { hasSkills, hasCommands };
|
|
180
|
+
};
|
|
181
|
+
var checkExistingClaudeFiles = async (targetDir) => {
|
|
182
|
+
const existingFiles = [];
|
|
183
|
+
const skillsDir = path.join(targetDir, ".claude", "skills");
|
|
184
|
+
const commandsDir = path.join(targetDir, ".claude", "commands");
|
|
185
|
+
if (await fs.pathExists(skillsDir)) {
|
|
186
|
+
existingFiles.push(".claude/skills/");
|
|
187
|
+
}
|
|
188
|
+
if (await fs.pathExists(commandsDir)) {
|
|
189
|
+
existingFiles.push(".claude/commands/");
|
|
190
|
+
}
|
|
191
|
+
return existingFiles;
|
|
192
|
+
};
|
|
139
193
|
|
|
140
194
|
// src/commands/init.ts
|
|
141
195
|
var TEMPLATE_DESCRIPTIONS = {
|
|
@@ -227,10 +281,69 @@ var init = async (options) => {
|
|
|
227
281
|
}
|
|
228
282
|
logger.blank();
|
|
229
283
|
logger.success(`Total: ${totalFiles} files, ${totalDirectories} directories`);
|
|
284
|
+
if (options.skills || options.commands) {
|
|
285
|
+
const { hasSkills, hasCommands } = await checkSkillsAndCommandsExist(templates);
|
|
286
|
+
const existingClaudeFiles = await checkExistingClaudeFiles(targetDir);
|
|
287
|
+
if (existingClaudeFiles.length > 0 && !options.force) {
|
|
288
|
+
logger.warn("The following .claude files/folders already exist:");
|
|
289
|
+
existingClaudeFiles.forEach((f) => logger.step(f));
|
|
290
|
+
logger.blank();
|
|
291
|
+
const response = await prompts({
|
|
292
|
+
type: "confirm",
|
|
293
|
+
name: "overwrite",
|
|
294
|
+
message: "Overwrite existing .claude files?",
|
|
295
|
+
initial: false
|
|
296
|
+
});
|
|
297
|
+
if (!response.overwrite) {
|
|
298
|
+
logger.info("Skipping skills/commands installation.");
|
|
299
|
+
} else {
|
|
300
|
+
await installSkillsAndCommands();
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
await installSkillsAndCommands();
|
|
304
|
+
}
|
|
305
|
+
async function installSkillsAndCommands() {
|
|
306
|
+
if (options.skills && hasSkills) {
|
|
307
|
+
logger.blank();
|
|
308
|
+
logger.info("Installing skills...");
|
|
309
|
+
const skillsResult = await copySkills(templates, targetDir);
|
|
310
|
+
totalFiles += skillsResult.files;
|
|
311
|
+
totalDirectories += skillsResult.directories;
|
|
312
|
+
logger.success(
|
|
313
|
+
`Skills: ${skillsResult.files} files, ${skillsResult.directories} directories`
|
|
314
|
+
);
|
|
315
|
+
} else if (options.skills && !hasSkills) {
|
|
316
|
+
logger.warn("No skills found in selected templates.");
|
|
317
|
+
}
|
|
318
|
+
if (options.commands && hasCommands) {
|
|
319
|
+
logger.blank();
|
|
320
|
+
logger.info("Installing commands...");
|
|
321
|
+
const commandsResult = await copyCommands(templates, targetDir);
|
|
322
|
+
totalFiles += commandsResult.files;
|
|
323
|
+
totalDirectories += commandsResult.directories;
|
|
324
|
+
logger.success(
|
|
325
|
+
`Commands: ${commandsResult.files} files, ${commandsResult.directories} directories`
|
|
326
|
+
);
|
|
327
|
+
} else if (options.commands && !hasCommands) {
|
|
328
|
+
logger.warn("No commands found in selected templates.");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
logger.blank();
|
|
230
333
|
logger.success("Claude Code documentation installed!");
|
|
231
334
|
logger.blank();
|
|
232
335
|
logger.info("Installed templates:");
|
|
233
336
|
templates.forEach((t) => logger.step(t));
|
|
337
|
+
if (options.skills || options.commands) {
|
|
338
|
+
logger.blank();
|
|
339
|
+
logger.info("Installed extras:");
|
|
340
|
+
if (options.skills) {
|
|
341
|
+
logger.step("Skills \u2192 .claude/skills/");
|
|
342
|
+
}
|
|
343
|
+
if (options.commands) {
|
|
344
|
+
logger.step("Commands \u2192 .claude/commands/");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
234
347
|
logger.blank();
|
|
235
348
|
logger.info("Next steps:");
|
|
236
349
|
logger.step("Read CLAUDE.md for project guidelines");
|
|
@@ -240,11 +353,11 @@ var init = async (options) => {
|
|
|
240
353
|
|
|
241
354
|
// src/index.ts
|
|
242
355
|
var program = new Command();
|
|
243
|
-
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.1.
|
|
356
|
+
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.1.9");
|
|
244
357
|
program.option(
|
|
245
358
|
"-t, --template <names>",
|
|
246
359
|
"template names (comma-separated: tanstack-start,hono)"
|
|
247
|
-
).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").action(async (options) => {
|
|
360
|
+
).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").action(async (options) => {
|
|
248
361
|
banner();
|
|
249
362
|
if (options.list) {
|
|
250
363
|
const templates = await listAvailableTemplates();
|
|
@@ -256,7 +369,9 @@ program.option(
|
|
|
256
369
|
await init({
|
|
257
370
|
templates: options.template?.split(",").map((t) => t.trim()),
|
|
258
371
|
force: options.force,
|
|
259
|
-
cwd: options.cwd
|
|
372
|
+
cwd: options.cwd,
|
|
373
|
+
skills: options.skills,
|
|
374
|
+
commands: options.commands
|
|
260
375
|
});
|
|
261
376
|
});
|
|
262
377
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kood/claude-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Claude Code documentation installer for projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/index.js",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"copy-templates": "node scripts/copy-templates.js",
|
|
13
13
|
"build": "tsup && npm run copy-templates",
|
|
14
14
|
"dev": "npm run build && node dist/index.js",
|
|
15
|
+
"lint": "eslint src/",
|
|
16
|
+
"lint:fix": "eslint src/ --fix",
|
|
15
17
|
"prepublishOnly": "npm run build"
|
|
16
18
|
},
|
|
17
19
|
"keywords": [
|
|
@@ -38,10 +40,14 @@
|
|
|
38
40
|
"@types/fs-extra": "^11.0.4",
|
|
39
41
|
"@types/node": "^22.10.0",
|
|
40
42
|
"@types/prompts": "^2.4.9",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
44
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
45
|
+
"eslint": "^9.39.2",
|
|
46
|
+
"globals": "^16.5.0",
|
|
41
47
|
"tsup": "^8.3.5",
|
|
42
48
|
"typescript": "^5.7.2"
|
|
43
49
|
},
|
|
44
50
|
"engines": {
|
|
45
51
|
"node": ">=18"
|
|
46
52
|
}
|
|
47
|
-
}
|
|
53
|
+
}
|
package/templates/hono/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# CLAUDE.md -
|
|
1
|
+
# CLAUDE.md - Hono Server Framework
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Web Standards 기반 초경량 서버 프레임워크
|
|
4
4
|
|
|
5
5
|
## Instructions
|
|
6
6
|
|
|
@@ -12,213 +12,53 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## STOP - 금지 사항
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
└─────────────────────────────────────────────────────────────┘
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## ⛔ NEVER DO (절대 금지 - 예외 없음)
|
|
29
|
-
|
|
30
|
-
### Git 커밋 금지 사항
|
|
31
|
-
```
|
|
32
|
-
❌ "Generated with Claude Code" 포함 금지
|
|
33
|
-
❌ "🤖" 또는 AI 관련 이모지 포함 금지
|
|
34
|
-
❌ "Co-Authored-By:" 헤더 포함 금지
|
|
35
|
-
❌ AI/봇이 작성했다는 어떤 표시도 금지
|
|
36
|
-
❌ 커밋 메시지 여러 줄 작성 금지
|
|
37
|
-
❌ 커밋 메시지에 이모지 사용 금지
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Prisma 금지 사항
|
|
41
|
-
```
|
|
42
|
-
❌ prisma db push 자동 실행 금지
|
|
43
|
-
❌ prisma migrate 자동 실행 금지
|
|
44
|
-
❌ prisma generate 자동 실행 금지
|
|
45
|
-
❌ schema.prisma 임의 변경 금지 (요청된 것만)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### API 구현 금지 사항
|
|
49
|
-
```
|
|
50
|
-
❌ handler 내부에서 수동 검증 금지 (zValidator 사용)
|
|
51
|
-
❌ handler 내부에서 수동 인증 체크 금지 (middleware 사용)
|
|
52
|
-
❌ app.onError 없이 에러 처리 금지
|
|
53
|
-
❌ HTTPException 없이 에러 throw 금지
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 코드 검색 금지 사항
|
|
57
|
-
```
|
|
58
|
-
❌ grep, rg 등 기본 검색 도구 사용 금지
|
|
59
|
-
❌ find 명령어로 코드 검색 금지
|
|
60
|
-
✅ 코드베이스 검색 시 sgrep 사용 필수
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 코드 작성 규칙
|
|
64
|
-
```
|
|
65
|
-
✅ 모든 한글 텍스트는 UTF-8 인코딩 유지
|
|
66
|
-
✅ 코드 묶음 단위로 한글 주석 작성 (너무 세세하게 X)
|
|
67
|
-
✅ Prisma Multi-File 모든 요소에 한글 주석 필수
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Prisma Multi-File 구조 (필수)
|
|
71
|
-
```
|
|
72
|
-
prisma/
|
|
73
|
-
├── schema/
|
|
74
|
-
│ ├── +base.prisma # datasource, generator 설정
|
|
75
|
-
│ ├── +enum.prisma # 모든 enum 정의
|
|
76
|
-
│ ├── user.prisma # User 모델
|
|
77
|
-
│ ├── post.prisma # Post 모델
|
|
78
|
-
│ └── ... # 기타 모델별 파일
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## ✅ ALWAYS DO (필수 실행)
|
|
84
|
-
|
|
85
|
-
### 1. 작업 전: 관련 문서 읽기
|
|
86
|
-
```
|
|
87
|
-
API 작업 → docs/library/hono/ 읽기
|
|
88
|
-
DB 작업 → docs/library/prisma/ 읽기
|
|
89
|
-
검증 작업 → docs/library/zod/ 읽기
|
|
90
|
-
배포 작업 → docs/deployment/ 읽기
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 2. MCP 도구 적극 활용
|
|
94
|
-
```
|
|
95
|
-
코드베이스 검색 → sgrep 사용 (grep/rg 금지)
|
|
96
|
-
복잡한 분석/디버깅 → Sequential Thinking 사용
|
|
97
|
-
라이브러리 문서 → Context7 사용
|
|
98
|
-
```
|
|
99
|
-
**상세**: `docs/mcp/` 참고
|
|
100
|
-
|
|
101
|
-
### 3. 복잡한 작업 시: Gemini Review 실행
|
|
102
|
-
```
|
|
103
|
-
아키텍처 설계/변경 → gemini-review (architecture)
|
|
104
|
-
구현 계획 검증 → gemini-review (plan)
|
|
105
|
-
복잡한 코드 리뷰 → gemini-review (code)
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**실행 조건**:
|
|
109
|
-
- 3개 이상 파일 수정하는 기능 구현
|
|
110
|
-
- 새로운 아키텍처 패턴 도입
|
|
111
|
-
- 보안 관련 코드 (인증, 권한, 암호화)
|
|
112
|
-
- 성능 크리티컬 코드
|
|
113
|
-
|
|
114
|
-
### 4. 작업 완료 후: Git 커밋
|
|
115
|
-
```bash
|
|
116
|
-
git add .
|
|
117
|
-
git commit -m "<prefix>: <설명>"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**커밋 형식**: `<prefix>: <설명>` (한 줄, 본문 없음)
|
|
121
|
-
|
|
122
|
-
**Prefix**: `feat` | `fix` | `refactor` | `style` | `docs` | `test` | `chore` | `perf` | `ci`
|
|
123
|
-
|
|
124
|
-
**예시**:
|
|
125
|
-
```bash
|
|
126
|
-
feat: 사용자 인증 API 추가
|
|
127
|
-
fix: JWT 토큰 검증 오류 수정
|
|
128
|
-
docs: API 문서 업데이트
|
|
129
|
-
```
|
|
17
|
+
| 분류 | 금지 항목 |
|
|
18
|
+
|------|----------|
|
|
19
|
+
| **Git** | `Generated with Claude Code`, `🤖`, `Co-Authored-By:`, 여러 줄 커밋, 이모지 |
|
|
20
|
+
| **Prisma** | `db push`, `migrate`, `generate` 자동 실행, schema 임의 변경 |
|
|
21
|
+
| **API** | handler 내부 수동 검증 (→ zValidator), 수동 인증 (→ middleware), 일반 Error throw (→ HTTPException) |
|
|
22
|
+
| **검색** | grep, rg, find (→ ast-grep 사용) |
|
|
130
23
|
|
|
131
24
|
---
|
|
132
25
|
|
|
133
|
-
##
|
|
26
|
+
## ALWAYS - 필수 사항
|
|
134
27
|
|
|
135
|
-
| 작업 |
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
28
|
+
| 작업 | 필수 |
|
|
29
|
+
|------|------|
|
|
30
|
+
| 작업 전 | 관련 docs 읽기 |
|
|
31
|
+
| 코드 검색 | ast-grep 사용 |
|
|
32
|
+
| 복잡한 분석 | Sequential Thinking MCP |
|
|
33
|
+
| 3+ 파일 수정 | gemini-review 실행 |
|
|
34
|
+
| 코드 작성 | UTF-8, 코드 묶음별 한글 주석 |
|
|
35
|
+
| Prisma | Multi-File 구조, 모든 요소 한글 주석 |
|
|
142
36
|
|
|
143
37
|
---
|
|
144
38
|
|
|
145
|
-
##
|
|
39
|
+
## Tech Stack
|
|
146
40
|
|
|
147
41
|
| 기술 | 버전 | 주의사항 |
|
|
148
42
|
|------|------|----------|
|
|
149
|
-
| Hono | 최신 | Web Standards 기반
|
|
43
|
+
| Hono | 최신 | Web Standards 기반 |
|
|
150
44
|
| TypeScript | 5.x | strict mode |
|
|
151
|
-
| Prisma | **7.x** | `prisma-client
|
|
152
|
-
| Zod | **4.x** | `z.email()`, `z.url()` (
|
|
153
|
-
| @hono/zod-validator | 최신 | Zod 검증 미들웨어 |
|
|
45
|
+
| Prisma | **7.x** | `prisma-client`, output 필수 |
|
|
46
|
+
| Zod | **4.x** | `z.email()`, `z.url()` (v4 문법) |
|
|
154
47
|
|
|
155
48
|
---
|
|
156
49
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
src/
|
|
161
|
-
├── index.ts # Entry point
|
|
162
|
-
├── routes/ # 라우트 모듈
|
|
163
|
-
│ ├── index.ts # 라우트 통합
|
|
164
|
-
│ ├── users.ts # /users 라우트
|
|
165
|
-
│ └── posts.ts # /posts 라우트
|
|
166
|
-
├── middleware/ # 미들웨어
|
|
167
|
-
│ ├── auth.ts # 인증 미들웨어
|
|
168
|
-
│ └── logger.ts # 로깅 미들웨어
|
|
169
|
-
├── validators/ # Zod 스키마
|
|
170
|
-
│ ├── user.ts # 사용자 스키마
|
|
171
|
-
│ └── post.ts # 게시글 스키마
|
|
172
|
-
├── services/ # 비즈니스 로직
|
|
173
|
-
│ └── user.service.ts # 사용자 서비스
|
|
174
|
-
├── database/ # DB 연결
|
|
175
|
-
│ └── prisma.ts # Prisma Client
|
|
176
|
-
├── types/ # 타입 정의
|
|
177
|
-
│ └── env.d.ts # 환경변수 타입
|
|
178
|
-
└── lib/ # 유틸리티
|
|
179
|
-
└── errors.ts # 커스텀 에러
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## 🔧 Code Conventions
|
|
185
|
-
|
|
186
|
-
### File Naming
|
|
187
|
-
- **kebab-case**: `user-service.ts`, `auth-middleware.ts`
|
|
188
|
-
- **라우트 파일**: `users.ts`, `posts.ts`
|
|
189
|
-
|
|
190
|
-
### TypeScript
|
|
191
|
-
- `const` 선언 사용 (function 대신)
|
|
192
|
-
- 명시적 return type
|
|
193
|
-
- `interface` (객체) / `type` (유니온)
|
|
194
|
-
- `any` 금지 → `unknown` 사용
|
|
195
|
-
|
|
196
|
-
### Import
|
|
197
|
-
```typescript
|
|
198
|
-
// @/ → ./src/
|
|
199
|
-
import { prisma } from '@/database/prisma'
|
|
200
|
-
import { userSchema } from '@/validators/user'
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**순서**: 외부 → 내부(@/) → 상대경로 → type imports
|
|
204
|
-
|
|
205
|
-
---
|
|
50
|
+
## Quick Patterns
|
|
206
51
|
|
|
207
|
-
|
|
52
|
+
### App + 에러 핸들러
|
|
208
53
|
|
|
209
|
-
### App 설정
|
|
210
54
|
```typescript
|
|
211
55
|
import { Hono } from 'hono'
|
|
212
56
|
import { HTTPException } from 'hono/http-exception'
|
|
213
57
|
|
|
214
|
-
type Bindings = {
|
|
215
|
-
DATABASE_URL: string
|
|
216
|
-
JWT_SECRET: string
|
|
217
|
-
}
|
|
58
|
+
type Bindings = { DATABASE_URL: string; JWT_SECRET: string }
|
|
218
59
|
|
|
219
60
|
const app = new Hono<{ Bindings: Bindings }>()
|
|
220
61
|
|
|
221
|
-
// 글로벌 에러 핸들러
|
|
222
62
|
app.onError((err, c) => {
|
|
223
63
|
if (err instanceof HTTPException) {
|
|
224
64
|
return c.json({ error: err.message }, err.status)
|
|
@@ -227,172 +67,59 @@ app.onError((err, c) => {
|
|
|
227
67
|
return c.json({ error: 'Internal Server Error' }, 500)
|
|
228
68
|
})
|
|
229
69
|
|
|
230
|
-
|
|
231
|
-
app.notFound((c) => {
|
|
232
|
-
return c.json({ error: 'Not Found', path: c.req.path }, 404)
|
|
233
|
-
})
|
|
70
|
+
app.notFound((c) => c.json({ error: 'Not Found' }, 404))
|
|
234
71
|
|
|
235
72
|
export default app
|
|
236
73
|
```
|
|
237
74
|
|
|
238
|
-
###
|
|
239
|
-
```typescript
|
|
240
|
-
import { Hono } from 'hono'
|
|
241
|
-
import { zValidator } from '@hono/zod-validator'
|
|
242
|
-
import { z } from 'zod'
|
|
243
|
-
|
|
244
|
-
const app = new Hono()
|
|
75
|
+
### Zod 검증 (v4 문법)
|
|
245
76
|
|
|
246
|
-
// ✅ 올바른 패턴: zValidator 사용
|
|
247
|
-
const querySchema = z.object({
|
|
248
|
-
page: z.coerce.number().positive().optional(),
|
|
249
|
-
limit: z.coerce.number().max(100).optional(),
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
app.get('/users', zValidator('query', querySchema), (c) => {
|
|
253
|
-
const { page = 1, limit = 10 } = c.req.valid('query')
|
|
254
|
-
return c.json({ page, limit, users: [] })
|
|
255
|
-
})
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### 라우트 + Zod 검증 (POST)
|
|
259
77
|
```typescript
|
|
260
78
|
import { zValidator } from '@hono/zod-validator'
|
|
261
79
|
import { z } from 'zod'
|
|
262
80
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
email: z.email(), // ✅ v4
|
|
81
|
+
const schema = z.object({
|
|
82
|
+
email: z.email(), // ✅ v4
|
|
266
83
|
name: z.string().min(1).trim(),
|
|
267
|
-
website: z.url().optional()
|
|
84
|
+
website: z.url().optional() // ✅ v4
|
|
268
85
|
})
|
|
269
86
|
|
|
270
|
-
app.post('/users', zValidator('json',
|
|
87
|
+
app.post('/users', zValidator('json', schema), (c) => {
|
|
271
88
|
const data = c.req.valid('json')
|
|
272
|
-
// prisma.user.create({ data })
|
|
273
89
|
return c.json({ user: data }, 201)
|
|
274
90
|
})
|
|
275
91
|
```
|
|
276
92
|
|
|
277
|
-
###
|
|
278
|
-
```typescript
|
|
279
|
-
// ❌ handler 내부에서 수동 검증 금지
|
|
280
|
-
app.post('/users', async (c) => {
|
|
281
|
-
const body = await c.req.json()
|
|
282
|
-
// ❌ 이렇게 하지 마세요!
|
|
283
|
-
if (!body.email) {
|
|
284
|
-
return c.json({ error: 'Email required' }, 400)
|
|
285
|
-
}
|
|
286
|
-
})
|
|
93
|
+
### Prisma Multi-File 구조
|
|
287
94
|
|
|
288
|
-
// ❌ 일반 Error throw 금지
|
|
289
|
-
app.get('/user/:id', async (c) => {
|
|
290
|
-
throw new Error('Not found') // ❌ HTTPException 사용해야 함
|
|
291
|
-
})
|
|
292
95
|
```
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
app.get('/users/:id', async (c) => {
|
|
299
|
-
const id = c.req.param('id')
|
|
300
|
-
const user = await prisma.user.findUnique({ where: { id } })
|
|
301
|
-
|
|
302
|
-
if (!user) {
|
|
303
|
-
throw new HTTPException(404, { message: 'User not found' })
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return c.json({ user })
|
|
307
|
-
})
|
|
96
|
+
prisma/schema/
|
|
97
|
+
├── +base.prisma # datasource, generator
|
|
98
|
+
├── +enum.prisma # enum 정의
|
|
99
|
+
├── user.prisma # User 모델 (한글 주석 필수)
|
|
100
|
+
└── post.prisma # Post 모델
|
|
308
101
|
```
|
|
309
102
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
type Env = {
|
|
316
|
-
Variables: {
|
|
317
|
-
userId: string
|
|
318
|
-
}
|
|
103
|
+
```prisma
|
|
104
|
+
// +base.prisma
|
|
105
|
+
generator client {
|
|
106
|
+
provider = "prisma-client" // ✅ v7
|
|
107
|
+
output = "./generated/client" // ✅ 필수
|
|
319
108
|
}
|
|
320
|
-
|
|
321
|
-
export const authMiddleware = createMiddleware<Env>(async (c, next) => {
|
|
322
|
-
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
|
323
|
-
|
|
324
|
-
if (!token) {
|
|
325
|
-
throw new HTTPException(401, { message: 'Unauthorized' })
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// JWT 검증 로직
|
|
329
|
-
const payload = verifyToken(token)
|
|
330
|
-
c.set('userId', payload.sub)
|
|
331
|
-
|
|
332
|
-
await next()
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
// 사용
|
|
336
|
-
app.get('/me', authMiddleware, (c) => {
|
|
337
|
-
const userId = c.get('userId')
|
|
338
|
-
return c.json({ userId })
|
|
339
|
-
})
|
|
340
109
|
```
|
|
341
110
|
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
import { z } from 'zod'
|
|
111
|
+
---
|
|
345
112
|
|
|
346
|
-
|
|
347
|
-
email: z.email(), // ✅ v4
|
|
348
|
-
name: z.string().min(1).trim(),
|
|
349
|
-
website: z.url().optional(), // ✅ v4
|
|
350
|
-
age: z.number().min(0),
|
|
351
|
-
})
|
|
113
|
+
## 문서 구조
|
|
352
114
|
|
|
353
|
-
// 커스텀 에러 핸들링
|
|
354
|
-
app.post(
|
|
355
|
-
'/users',
|
|
356
|
-
zValidator('json', schema, (result, c) => {
|
|
357
|
-
if (!result.success) {
|
|
358
|
-
return c.json({ errors: result.error.flatten() }, 400)
|
|
359
|
-
}
|
|
360
|
-
}),
|
|
361
|
-
(c) => {
|
|
362
|
-
const data = c.req.valid('json')
|
|
363
|
-
return c.json({ user: data }, 201)
|
|
364
|
-
}
|
|
365
|
-
)
|
|
366
115
|
```
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
export type AppType = typeof app
|
|
379
|
-
|
|
380
|
-
// client.ts
|
|
381
|
-
import { hc } from 'hono/client'
|
|
382
|
-
import type { AppType } from './server'
|
|
383
|
-
|
|
384
|
-
const client = hc<AppType>('http://localhost:8787/')
|
|
385
|
-
|
|
386
|
-
// Type-safe API 호출
|
|
387
|
-
const res = await client.users.$get()
|
|
388
|
-
const data = await res.json() // { users: [] }
|
|
116
|
+
docs/
|
|
117
|
+
├── library/
|
|
118
|
+
│ ├── hono/ # 라우팅, 미들웨어, 검증, RPC
|
|
119
|
+
│ ├── prisma/ # CRUD, 관계, D1 연동
|
|
120
|
+
│ ├── zod/ # v4 문법, 검증 패턴
|
|
121
|
+
│ ├── ai-sdk/ # LLM 통합 (streaming, tools)
|
|
122
|
+
│ └── pino/ # 로깅
|
|
123
|
+
├── deployment/ # Docker, Railway, Vercel, Cloudflare
|
|
124
|
+
└── architecture/ # 아키텍처 패턴
|
|
389
125
|
```
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## 🔗 Quick Links
|
|
394
|
-
|
|
395
|
-
- [Hono 가이드](./docs/library/hono/index.md)
|
|
396
|
-
- [Git 규칙](../../commands/git.md)
|
|
397
|
-
- [MCP 가이드](./docs/mcp/index.md)
|
|
398
|
-
- [배포 가이드](./docs/deployment/index.md)
|