@blastlabs/utils 1.18.0 → 1.19.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/bin/init-ai-rules.cjs +17 -8
- package/package.json +1 -1
- package/rules/development-workflow.md +96 -0
- package/rules/documentation.md +4 -2
- package/rules/entities-layer.md +15 -10
- package/rules/naming-convention.md +212 -0
- package/rules/shared-layer.md +12 -9
- package/rules/views-layer.md +6 -44
package/bin/init-ai-rules.cjs
CHANGED
|
@@ -7,7 +7,9 @@ const path = require("path");
|
|
|
7
7
|
const RULE_CATEGORIES = {
|
|
8
8
|
// 기본 규칙 (항상 포함)
|
|
9
9
|
base: [
|
|
10
|
+
"development-workflow.md",
|
|
10
11
|
"typescript-standards.md",
|
|
12
|
+
"naming-convention.md",
|
|
11
13
|
"react-hooks.md",
|
|
12
14
|
"testing.md",
|
|
13
15
|
"documentation.md",
|
|
@@ -29,6 +31,7 @@ function parseArgs(args) {
|
|
|
29
31
|
all: false,
|
|
30
32
|
list: false,
|
|
31
33
|
help: false,
|
|
34
|
+
force: false,
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
for (const arg of args) {
|
|
@@ -40,6 +43,8 @@ function parseArgs(args) {
|
|
|
40
43
|
options.all = true;
|
|
41
44
|
} else if (arg === "--list" || arg === "-l") {
|
|
42
45
|
options.list = true;
|
|
46
|
+
} else if (arg === "--force" || arg === "-f") {
|
|
47
|
+
options.force = true;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -53,12 +58,14 @@ function showHelp() {
|
|
|
53
58
|
console.log("Options:");
|
|
54
59
|
console.log(" --fsd FSD 아키텍처 규칙 포함");
|
|
55
60
|
console.log(" --all 모든 규칙 설치 (base + fsd)");
|
|
61
|
+
console.log(" --force, -f 기존 파일 덮어쓰기");
|
|
56
62
|
console.log(" --list, -l 설치 가능한 규칙 목록 보기");
|
|
57
63
|
console.log(" --help, -h 도움말 보기");
|
|
58
64
|
console.log("\n예시:");
|
|
59
65
|
console.log(" npx blastlabs-init-ai-rules # 기본 규칙만");
|
|
60
66
|
console.log(" npx blastlabs-init-ai-rules --fsd # 기본 + FSD 규칙");
|
|
61
67
|
console.log(" npx blastlabs-init-ai-rules --all # 모든 규칙");
|
|
68
|
+
console.log(" npx blastlabs-init-ai-rules --force # 기존 파일 덮어쓰기");
|
|
62
69
|
console.log("\n생성되는 파일:");
|
|
63
70
|
console.log(" .cursor/rules/*.mdc - Cursor용 규칙 파일들");
|
|
64
71
|
console.log(" .claude/rules/*.md - Claude Code용 규칙 파일들");
|
|
@@ -176,7 +183,7 @@ function serializeClaudeFrontmatter(frontmatter) {
|
|
|
176
183
|
return yaml;
|
|
177
184
|
}
|
|
178
185
|
|
|
179
|
-
function generateCursorRules(ruleFiles, sourceDir, targetDir) {
|
|
186
|
+
function generateCursorRules(ruleFiles, sourceDir, targetDir, force = false) {
|
|
180
187
|
const cursorDir = path.join(targetDir, ".cursor", "rules");
|
|
181
188
|
if (!fs.existsSync(cursorDir)) {
|
|
182
189
|
fs.mkdirSync(cursorDir, { recursive: true });
|
|
@@ -197,7 +204,7 @@ function generateCursorRules(ruleFiles, sourceDir, targetDir) {
|
|
|
197
204
|
continue;
|
|
198
205
|
}
|
|
199
206
|
|
|
200
|
-
if (fs.existsSync(targetPath)) {
|
|
207
|
+
if (fs.existsSync(targetPath) && !force) {
|
|
201
208
|
console.log(` ⏭️ ${targetName} - 이미 존재함, 건너뜀`);
|
|
202
209
|
skipped++;
|
|
203
210
|
continue;
|
|
@@ -208,14 +215,15 @@ function generateCursorRules(ruleFiles, sourceDir, targetDir) {
|
|
|
208
215
|
const output = serializeCursorFrontmatter(frontmatter) + body;
|
|
209
216
|
|
|
210
217
|
fs.writeFileSync(targetPath, output);
|
|
211
|
-
|
|
218
|
+
const action = force && fs.existsSync(targetPath) ? "🔄" : "✅";
|
|
219
|
+
console.log(` ${action} ${targetName}`);
|
|
212
220
|
installed++;
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
return { installed, skipped };
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
|
|
226
|
+
function generateClaudeRules(ruleFiles, sourceDir, targetDir, force = false) {
|
|
219
227
|
const claudeDir = path.join(targetDir, ".claude", "rules");
|
|
220
228
|
if (!fs.existsSync(claudeDir)) {
|
|
221
229
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -235,7 +243,7 @@ function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
|
|
|
235
243
|
continue;
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
if (fs.existsSync(targetPath)) {
|
|
246
|
+
if (fs.existsSync(targetPath) && !force) {
|
|
239
247
|
console.log(` ⏭️ ${ruleName} - 이미 존재함, 건너뜀`);
|
|
240
248
|
skipped++;
|
|
241
249
|
continue;
|
|
@@ -246,7 +254,8 @@ function generateClaudeRules(ruleFiles, sourceDir, targetDir) {
|
|
|
246
254
|
const output = serializeClaudeFrontmatter(frontmatter) + body;
|
|
247
255
|
|
|
248
256
|
fs.writeFileSync(targetPath, output);
|
|
249
|
-
|
|
257
|
+
const action = force && fs.existsSync(targetPath) ? "🔄" : "✅";
|
|
258
|
+
console.log(` ${action} ${ruleName}`);
|
|
250
259
|
installed++;
|
|
251
260
|
}
|
|
252
261
|
|
|
@@ -291,11 +300,11 @@ function main() {
|
|
|
291
300
|
|
|
292
301
|
// Cursor 규칙 생성
|
|
293
302
|
console.log("🔧 Cursor 규칙 생성:\n");
|
|
294
|
-
const cursor = generateCursorRules(rulesToInstall, rulesSourceDir, targetDir);
|
|
303
|
+
const cursor = generateCursorRules(rulesToInstall, rulesSourceDir, targetDir, options.force);
|
|
295
304
|
|
|
296
305
|
// Claude 규칙 생성
|
|
297
306
|
console.log("\n🤖 Claude 규칙 생성:\n");
|
|
298
|
-
const claude = generateClaudeRules(rulesToInstall, rulesSourceDir, targetDir);
|
|
307
|
+
const claude = generateClaudeRules(rulesToInstall, rulesSourceDir, targetDir, options.force);
|
|
299
308
|
|
|
300
309
|
const totalInstalled = cursor.installed + claude.installed;
|
|
301
310
|
const totalSkipped = cursor.skipped + claude.skipped;
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "개발 워크플로우: PRD 참고 및 테스트 기본 진행"
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Development Workflow
|
|
7
|
+
|
|
8
|
+
## 필수 원칙
|
|
9
|
+
|
|
10
|
+
### 1. PRD 먼저 확인
|
|
11
|
+
|
|
12
|
+
모든 기능 개발 전에 **반드시** PRD(Product Requirement Document)를 먼저 확인하세요:
|
|
13
|
+
|
|
14
|
+
- **PRD가 있다면** 내용을 확인하고 스펙 파악
|
|
15
|
+
- **PRD가 없다면** 사용자와 함께 PRD를 작성
|
|
16
|
+
- 사용자로부터 요구사항 수집
|
|
17
|
+
- AI가 PRD 초안 작성
|
|
18
|
+
- 사용자 확인 후 개발 진행
|
|
19
|
+
- PRD에서 요구사항, UI/UX 스펙, 비즈니스 로직을 명확히 파악
|
|
20
|
+
- 불확실한 부분은 PRD를 통해 명확화 후 개발 진행
|
|
21
|
+
|
|
22
|
+
**PRD 확인 체크리스트:**
|
|
23
|
+
- [ ] 기능 목적과 목표가 명확한가?
|
|
24
|
+
- [ ] UI/UX 스펙이 정의되어 있는가?
|
|
25
|
+
- [ ] API 스펙이 정의되어 있는가?
|
|
26
|
+
- [ ] 예외 상황과 에러 처리가 정의되어 있는가?
|
|
27
|
+
|
|
28
|
+
### 2. 테스트 기본 진행
|
|
29
|
+
|
|
30
|
+
**모든 기능 구현은 테스트 코드와 함께 진행합니다.**
|
|
31
|
+
|
|
32
|
+
#### 테스트 우선 원칙
|
|
33
|
+
|
|
34
|
+
1. **테스트 없이 구현하지 않기**
|
|
35
|
+
- 기능 구현 전 테스트 케이스 먼저 작성
|
|
36
|
+
- 테스트가 실패하면 구현으로 간주하지 않음
|
|
37
|
+
|
|
38
|
+
2. **최소 테스트 커버리지 준수**
|
|
39
|
+
- 새로운 hook: 100% 커버리지 권장
|
|
40
|
+
- 컴포넌트: 주요 사용자 시나리오 테스트
|
|
41
|
+
- 유틸리티 함수: 100% 커버리지
|
|
42
|
+
|
|
43
|
+
3. **테스트 실행 후 PR**
|
|
44
|
+
- `npm test` 통과 확인
|
|
45
|
+
- 빌드 성공 확인
|
|
46
|
+
- 테스트 실패 시 PR 불가
|
|
47
|
+
|
|
48
|
+
## 개발 순서
|
|
49
|
+
|
|
50
|
+
### Step 1: PRD 확인/작성
|
|
51
|
+
```
|
|
52
|
+
사용자: "로그인 기능 추가해줘"
|
|
53
|
+
AI: "PRD가 있나요? 없다면 먼저 함께 작성해요.
|
|
54
|
+
|
|
55
|
+
먼저 몇 가지 질문드릴게요:
|
|
56
|
+
1. 로그인 방법 (이메일/소셜)
|
|
57
|
+
2. 필요한 UI 컴포넌트
|
|
58
|
+
3. 연동할 API 엔드포인트
|
|
59
|
+
|
|
60
|
+
이를 바탕으로 PRD 초안을 작성할게요."
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Step 2: 테스트 코드 작성
|
|
64
|
+
```
|
|
65
|
+
AI: "테스트 코드부터 작성할게요."
|
|
66
|
+
→ 로그인 테스트 작성
|
|
67
|
+
→ 실패하는 것 확인
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Step 3: 기능 구현
|
|
71
|
+
```
|
|
72
|
+
AI: "이제 로그인 기능을 구현할게요."
|
|
73
|
+
→ 최소한의 구현으로 테스트 통과
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 4: 리팩토링
|
|
77
|
+
```
|
|
78
|
+
AI: "코드를 정리할게요."
|
|
79
|
+
→ 리팩토링 후 테스트 재실행
|
|
80
|
+
→ 모든 테스트 통과 확인
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 예외 사항
|
|
84
|
+
|
|
85
|
+
**테스트를 생략할 수 있는 경우:**
|
|
86
|
+
- 프로토타이핑 (코드 폐기 예정)
|
|
87
|
+
- 스타일만 변경하는 경우
|
|
88
|
+
- 명시적으로 테스트 제외 요청 시
|
|
89
|
+
|
|
90
|
+
**그 외에는 무조건 테스트 작성**
|
|
91
|
+
|
|
92
|
+
## 중요
|
|
93
|
+
|
|
94
|
+
- **Always** check PRD first before implementation
|
|
95
|
+
- **Always** write tests before or with implementation
|
|
96
|
+
- **Never** skip tests without explicit reason
|
package/rules/documentation.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: '문서화 가이드라인'
|
|
3
3
|
alwaysApply: false
|
|
4
|
+
paths:
|
|
5
|
+
- '**/docs/**'
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Documentation Guidelines
|
|
@@ -14,7 +16,7 @@ alwaysApply: false
|
|
|
14
16
|
## 문서 업데이트
|
|
15
17
|
|
|
16
18
|
- 기능 추가 시 README.md 업데이트
|
|
17
|
-
- 새 hooks 추가 시 관련 docs
|
|
19
|
+
- 새 hooks 추가 시 관련 docs/\*.md 업데이트
|
|
18
20
|
- 변경사항은 반드시 CHANGELOG.md에 기록
|
|
19
21
|
|
|
20
22
|
## CHANGELOG 작성
|
package/rules/entities-layer.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 'Entities 레이어 구조 및 API 패턴'
|
|
3
3
|
paths:
|
|
4
|
-
-
|
|
4
|
+
- '**/entities/**'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Entities Layer (엔티티)
|
|
8
8
|
|
|
9
9
|
엔티티는 도메인 모델과 API 로직을 관리합니다.
|
|
10
|
+
mapper는 받은데이터를 model에 설계한 방식으로 map을 합니다.
|
|
11
|
+
query는 params로 보통 get에서 사용합니다.
|
|
10
12
|
|
|
11
13
|
## 구조
|
|
14
|
+
|
|
12
15
|
```
|
|
13
16
|
entities/
|
|
14
17
|
└── (엔티티 이름)/ 예: inquiry, popular-product
|
|
@@ -22,29 +25,27 @@ entities/
|
|
|
22
25
|
│ ├── get-(엔티티 이름)-list.ts
|
|
23
26
|
│ ├── get-(엔티티 이름)-detail.ts
|
|
24
27
|
│ ├── (엔티티 이름)-queries.ts
|
|
25
|
-
│ ├── mutate.ts
|
|
26
|
-
│ ├── query.ts
|
|
27
28
|
│ └── index.ts
|
|
28
29
|
├── model/
|
|
29
30
|
│ ├── (엔티티 이름).ts
|
|
30
31
|
│ ├── (엔티티 이름)-detail.ts
|
|
31
32
|
│ └── (엔티티 이름)-update.ts
|
|
32
|
-
├── schema.ts
|
|
33
33
|
└── index.ts
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## 각 파일의 역할
|
|
37
|
+
|
|
37
38
|
- **mapper/**: API 응답을 도메인 모델로 변환
|
|
38
39
|
- **query/**: 쿼리 파라미터 타입 정의
|
|
39
|
-
- **get
|
|
40
|
-
- **get
|
|
40
|
+
- **get-\*-list.ts**: 리스트 조회 API 함수
|
|
41
|
+
- **get-\*-detail.ts**: 상세 조회 API 함수
|
|
42
|
+
- **post-\*.ts** : post API 함수 (delete, put같은것도 앞에 접두사가 변함)
|
|
41
43
|
- **\*-queries.ts**: TanStack Query queryOptions 정의 (all, lists, list, details, detail 등)
|
|
42
|
-
- **mutate.ts**: mutation 함수들
|
|
43
44
|
- **model/**: 도메인 모델 타입 정의
|
|
44
|
-
- **schema.ts**: zod 스키마 정의 및 변환 함수
|
|
45
45
|
- **index.ts**: `export * as (엔티티 이름)Api from "./api"`
|
|
46
46
|
|
|
47
47
|
## 예시
|
|
48
|
+
|
|
48
49
|
```
|
|
49
50
|
entities/inquiry/
|
|
50
51
|
├── api/
|
|
@@ -60,8 +61,12 @@ entities/inquiry/
|
|
|
60
61
|
├── model/
|
|
61
62
|
│ ├── inquiry.ts
|
|
62
63
|
│ └── inquiry-detail.ts
|
|
63
|
-
└── schema.ts
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
## TanStack Query 패턴
|
|
67
|
+
|
|
67
68
|
서버 상태 관리는 TanStack Query의 queryOptions 패턴을 사용합니다.
|
|
69
|
+
|
|
70
|
+
## model의 규칙
|
|
71
|
+
|
|
72
|
+
zod를 사용하며 export const (엔티티)Schema와 export type (엔티티)Type 의 네이밍 규칙을 활용한다
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "파일 및 변수 네이밍 컨벤션"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
paths:
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
- "**/*.tsx"
|
|
7
|
+
- "**/*.js"
|
|
8
|
+
- "**/*.jsx"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Naming Convention
|
|
12
|
+
|
|
13
|
+
## 파일 네이밍
|
|
14
|
+
|
|
15
|
+
### 기본 규칙
|
|
16
|
+
|
|
17
|
+
**모든 파일/폴더는 `kebab-case` 사용**
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
✅ good
|
|
21
|
+
user-profile/
|
|
22
|
+
use-user-data/
|
|
23
|
+
button-group.tsx
|
|
24
|
+
|
|
25
|
+
❌ bad
|
|
26
|
+
UserProfile/
|
|
27
|
+
useUserData/
|
|
28
|
+
buttonGroup.tsx
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 예외: React 컴포넌트
|
|
32
|
+
|
|
33
|
+
**컴포넌트 파일은 `PascalCase` 사용**
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
components/
|
|
37
|
+
✅ ButtonGroup.tsx
|
|
38
|
+
✅ UserProfile.tsx
|
|
39
|
+
✅ FormInput.tsx
|
|
40
|
+
|
|
41
|
+
❌ button-group.tsx
|
|
42
|
+
❌ user-profile.tsx
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**단, 폴더는 `kebab-case`**
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
components/
|
|
49
|
+
button-group/
|
|
50
|
+
ButtonGroup.tsx
|
|
51
|
+
ButtonGroup.test.tsx
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Hooks
|
|
55
|
+
|
|
56
|
+
**`use-` prefix + `camelCase`**
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
hooks/
|
|
60
|
+
✅ use-user-data.ts
|
|
61
|
+
✅ use-form-state.ts
|
|
62
|
+
✅ use-debounce.ts
|
|
63
|
+
|
|
64
|
+
❌ userData.ts
|
|
65
|
+
❌ useUserData.ts (파일명)
|
|
66
|
+
❌ form-state.ts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 타입 파일
|
|
70
|
+
|
|
71
|
+
**`.types.ts` 또는 `.type.ts` 접미사**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
model/
|
|
75
|
+
✅ user.types.ts
|
|
76
|
+
✅ product.type.ts
|
|
77
|
+
✅ api.types.ts
|
|
78
|
+
|
|
79
|
+
❌ UserTypes.ts
|
|
80
|
+
❷ user-type.ts (단수型)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 테스트 파일
|
|
84
|
+
|
|
85
|
+
**대상 파일명 + `.test.` + 확장자**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
ButtonGroup.tsx → ButtonGroup.test.tsx
|
|
89
|
+
useUserData.ts → useUserData.test.ts
|
|
90
|
+
api.ts → api.test.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 폴더 구조
|
|
94
|
+
|
|
95
|
+
**FSD 아키텍처 예시**
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
src/
|
|
99
|
+
├── entities/
|
|
100
|
+
│ └── user/ # kebab-case
|
|
101
|
+
│ ├── model/
|
|
102
|
+
│ │ ├── user.types.ts
|
|
103
|
+
│ │ └── user-detail.types.ts
|
|
104
|
+
│ ├── api/
|
|
105
|
+
│ │ ├── get-user-list.ts
|
|
106
|
+
│ │ ├── use-user-query.ts
|
|
107
|
+
│ │ └── user.test.ts
|
|
108
|
+
│ └── ui/
|
|
109
|
+
│ ├── UserCard.tsx # PascalCase
|
|
110
|
+
│ └── UserCard.test.tsx
|
|
111
|
+
├── features/
|
|
112
|
+
│ └── auth-login/ # kebab-case
|
|
113
|
+
│ ├── model/
|
|
114
|
+
│ ├── api/
|
|
115
|
+
│ └── ui/
|
|
116
|
+
│ └── LoginForm.tsx # PascalCase
|
|
117
|
+
├── shared/
|
|
118
|
+
│ └── ui/
|
|
119
|
+
│ └── button/ # kebab-case
|
|
120
|
+
│ ├── Button.tsx # PascalCase
|
|
121
|
+
│ └── Button.test.tsx
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 변수/함수 네이밍
|
|
125
|
+
|
|
126
|
+
### 변수
|
|
127
|
+
|
|
128
|
+
**`camelCase` 사용**
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
✅ good
|
|
132
|
+
const userName = '...';
|
|
133
|
+
const isActive = true;
|
|
134
|
+
const maxCount = 10;
|
|
135
|
+
|
|
136
|
+
❌ bad
|
|
137
|
+
const user_name = '...';
|
|
138
|
+
const IsActive = true;
|
|
139
|
+
const MAX_COUNT = 10; (const가 아니라면)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 상수
|
|
143
|
+
|
|
144
|
+
**`UPPER_SNAKE_CASE` 사용**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
✅ good
|
|
148
|
+
const MAX_RETRY_COUNT = 3;
|
|
149
|
+
const API_BASE_URL = 'https://...';
|
|
150
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
151
|
+
|
|
152
|
+
❌ bad
|
|
153
|
+
const maxRetryCount = 3;
|
|
154
|
+
const apiBaseUrl = 'https://...';
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 함수
|
|
158
|
+
|
|
159
|
+
**`camelCase` + 동사로 시작**
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
✅ good
|
|
163
|
+
function getUserData() { }
|
|
164
|
+
function handleSubmit() { }
|
|
165
|
+
function isValidEmail() { }
|
|
166
|
+
|
|
167
|
+
❌ bad
|
|
168
|
+
function userData() { }
|
|
169
|
+
function form_submit() { }
|
|
170
|
+
function ValidEmail() { }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 인터페이스/타입
|
|
174
|
+
|
|
175
|
+
**`PascalCase` 사용**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
✅ good
|
|
179
|
+
interface UserProfile { }
|
|
180
|
+
type LoginFormData = { };
|
|
181
|
+
interface ApiResponse<T> { }
|
|
182
|
+
|
|
183
|
+
❌ bad
|
|
184
|
+
interface userProfile { }
|
|
185
|
+
type loginFormData = { }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Enum
|
|
189
|
+
|
|
190
|
+
**`PascalCase` + 멤버는 `UPPER_SNAKE_CASE`**
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
✅ good
|
|
194
|
+
enum UserRole {
|
|
195
|
+
ADMIN = 'ADMIN',
|
|
196
|
+
USER = 'USER',
|
|
197
|
+
GUEST = 'GUEST',
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
❌ bad
|
|
201
|
+
enum userRole {
|
|
202
|
+
admin = 'admin',
|
|
203
|
+
USER = 'user',
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 중요
|
|
208
|
+
|
|
209
|
+
- **Always** use kebab-case for files and folders
|
|
210
|
+
- **Always** use PascalCase for component files
|
|
211
|
+
- **Always** use camelCase for variables and functions
|
|
212
|
+
- **Always** use UPPER_SNAKE_CASE for constants
|
package/rules/shared-layer.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 'Shared 레이어 구조 및 lib vs utils 구분'
|
|
3
3
|
paths:
|
|
4
|
-
-
|
|
4
|
+
- '**/shared/**'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Shared Layer (공유 레이어)
|
|
@@ -9,11 +9,10 @@ paths:
|
|
|
9
9
|
프로젝트 전체에서 공유되는 코드를 관리합니다.
|
|
10
10
|
|
|
11
11
|
## 구조
|
|
12
|
+
|
|
12
13
|
```
|
|
13
14
|
shared/
|
|
14
15
|
├── api/ API 인스턴스 설정 (instance_v2.ts 등)
|
|
15
|
-
├── constant/ 공통 상수 (path.ts 등)
|
|
16
|
-
├── hooks/ 공통 React 훅
|
|
17
16
|
├── mock-data/ 개발용 목 데이터
|
|
18
17
|
├── lib/ 라이브러리 래퍼 및 설정
|
|
19
18
|
│ └── (라이브러리명)/
|
|
@@ -28,14 +27,15 @@ shared/
|
|
|
28
27
|
│ │ └── (컴포넌트명).tsx
|
|
29
28
|
│ ├── theme/ 테마 관련 CSS 파일
|
|
30
29
|
│ └── utils/ UI 관련 유틸리티 함수
|
|
31
|
-
├── util/ 일반 유틸리티 함수
|
|
32
30
|
└── utils/ 추가 유틸리티 함수
|
|
33
31
|
```
|
|
34
32
|
|
|
35
33
|
## lib vs utils 구분 기준
|
|
36
34
|
|
|
37
35
|
### `shared/lib/`
|
|
36
|
+
|
|
38
37
|
외부 라이브러리를 프로젝트에 맞게 감싸거나 설정하는 코드
|
|
38
|
+
|
|
39
39
|
- 라이브러리 래퍼(wrapper) 컴포넌트
|
|
40
40
|
- 라이브러리 초기 설정 및 Provider
|
|
41
41
|
- 예시:
|
|
@@ -43,16 +43,19 @@ shared/
|
|
|
43
43
|
- `shared/lib/tanstack-query/query-client.tsx`
|
|
44
44
|
- `shared/lib/suspense/suspense-wrapper.tsx`
|
|
45
45
|
|
|
46
|
-
### `shared/utils/`
|
|
46
|
+
### `shared/utils/`
|
|
47
|
+
|
|
47
48
|
순수 유틸리티 함수 및 비즈니스 로직 헬퍼
|
|
49
|
+
|
|
48
50
|
- 라이브러리와 무관한 순수 함수
|
|
49
51
|
- 라이브러리 기능을 사용하는 유틸 함수
|
|
50
52
|
- 예시:
|
|
51
|
-
- `shared/
|
|
52
|
-
- `shared/
|
|
53
|
-
- `shared/
|
|
53
|
+
- `shared/utils/convert-price.ts` - 가격 변환 함수
|
|
54
|
+
- `shared/utils/export-excel.ts` - 엑셀 내보내기 함수
|
|
55
|
+
- `shared/utils/form-validation.ts` - 폼 검증 유틸
|
|
54
56
|
|
|
55
57
|
## 판단 기준
|
|
58
|
+
|
|
56
59
|
- 라이브러리를 감싸는가? → `lib/`
|
|
57
60
|
- 라이브러리와 무관한 로직인가? → `utils/`
|
|
58
61
|
- 라이브러리 기능을 사용하는 유틸인가? → `utils/`
|
package/rules/views-layer.md
CHANGED
|
@@ -1,72 +1,34 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 'Views 레이어 (페이지) 구조'
|
|
3
3
|
paths:
|
|
4
|
-
-
|
|
4
|
+
- '**/views/**'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Views Layer (페이지 레이어)
|
|
8
8
|
|
|
9
9
|
페이지 단위의 UI와 로직을 관리합니다.
|
|
10
|
+
기본적으로 fsd 방식을 따릅니다
|
|
10
11
|
|
|
11
12
|
## 구조
|
|
13
|
+
|
|
12
14
|
```
|
|
13
15
|
views/
|
|
14
16
|
└── (페이지명)/ 예: inquiry, product, seller
|
|
15
17
|
├── page.tsx 메인 페이지 컴포넌트 (필수)
|
|
16
|
-
├── (페이지명)-list-page.tsx
|
|
17
|
-
├── (페이지명)-detail-page.tsx
|
|
18
18
|
├── detail/
|
|
19
19
|
│ ├── page.tsx
|
|
20
20
|
│ ├── ui/
|
|
21
|
-
│ ├── hooks/ use-(페이지명)-detail-data.tsx 등
|
|
22
|
-
│ └── components/
|
|
23
|
-
├── list/
|
|
24
|
-
│ ├── (컬럼명)-column.tsx
|
|
25
|
-
│ ├── search.tsx
|
|
26
|
-
│ └── (모달명)-modal.tsx
|
|
27
21
|
├── ui/
|
|
28
|
-
├── hooks/ use-(페이지명)-data.tsx, use-(페이지명)-actions.tsx 등
|
|
29
|
-
├── components/
|
|
30
22
|
├── utils/
|
|
31
|
-
├── schema/ zod 스키마
|
|
32
|
-
└── constants.ts
|
|
33
23
|
```
|
|
34
24
|
|
|
35
25
|
## 예시
|
|
36
26
|
|
|
37
27
|
### 기본 페이지 구조
|
|
28
|
+
|
|
38
29
|
```
|
|
39
30
|
views/inquiry/
|
|
40
31
|
├── detail/
|
|
41
32
|
│ └── page.tsx
|
|
42
|
-
|
|
43
|
-
└── list/
|
|
44
|
-
├── inquiry-column.tsx
|
|
45
|
-
├── inquiry-modal.tsx
|
|
46
|
-
└── search.tsx
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### 상세 페이지 구조
|
|
50
|
-
```
|
|
51
|
-
views/ai-management/agency/agency-detail/
|
|
52
|
-
├── page.tsx
|
|
53
|
-
├── hooks/
|
|
54
|
-
│ ├── use-agency-detail-data.tsx
|
|
55
|
-
│ └── use-agency-data-actions.tsx
|
|
56
|
-
├── components/
|
|
57
|
-
│ └── agency-basic-info-tab.tsx
|
|
58
|
-
└── schema/
|
|
59
|
-
└── agency-detail.ts
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### 관리 페이지 구조
|
|
63
|
-
```
|
|
64
|
-
views/product/category-management/
|
|
65
|
-
├── page.tsx
|
|
66
|
-
├── hooks/
|
|
67
|
-
│ ├── use-category-data.tsx
|
|
68
|
-
│ └── use-category-actions.tsx
|
|
69
|
-
└── ui/
|
|
70
|
-
├── category-management.tsx
|
|
71
|
-
└── category-modal.tsx
|
|
33
|
+
│ page.tsx
|
|
72
34
|
```
|