@blastlabs/utils 1.21.0 → 2.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/README.md +102 -8
- package/bin/Makefile +23 -0
- package/bin/init-routes.cjs +422 -0
- package/dist/components/dev/DevPanel.d.ts +16 -11
- package/dist/components/dev/DevPanel.d.ts.map +1 -1
- package/dist/components/dev/DevPanel.js +71 -77
- package/dist/components/dev/DevPanel.test.d.ts +2 -0
- package/dist/components/dev/DevPanel.test.d.ts.map +1 -0
- package/dist/components/dev/DevPanel.test.js +194 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.d.ts +97 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.d.ts.map +1 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.js +122 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.d.ts +2 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.d.ts.map +1 -0
- package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.js +104 -0
- package/dist/components/dev/DevToolsProvider/index.d.ts +3 -0
- package/dist/components/dev/DevToolsProvider/index.d.ts.map +1 -0
- package/dist/components/dev/DevToolsProvider/index.js +1 -0
- package/dist/components/dev/FormDevTools/FormDevTools.d.ts +5 -70
- package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -1
- package/dist/components/dev/FormDevTools/FormDevTools.js +163 -236
- package/dist/components/dev/FormDevTools/FormDevToolsContent.d.ts +27 -0
- package/dist/components/dev/FormDevTools/FormDevToolsContent.d.ts.map +1 -0
- package/dist/components/dev/FormDevTools/FormDevToolsContent.js +298 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.d.ts +29 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.d.ts.map +1 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.js +122 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.d.ts +4 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.d.ts.map +1 -0
- package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.js +121 -0
- package/dist/components/dev/TimezoneDevTools/index.d.ts +3 -0
- package/dist/components/dev/TimezoneDevTools/index.d.ts.map +1 -0
- package/dist/components/dev/TimezoneDevTools/index.js +1 -0
- package/dist/components/dev/TimezoneDevTools/styles.d.ts +12 -0
- package/dist/components/dev/TimezoneDevTools/styles.d.ts.map +1 -0
- package/dist/components/dev/TimezoneDevTools/styles.js +65 -0
- package/dist/components/dev/ZIndexDebugger.js +1 -1
- package/dist/components/dev/index.d.ts +4 -2
- package/dist/components/dev/index.d.ts.map +1 -1
- package/dist/components/dev/index.js +6 -1
- package/dist/date/index.d.ts +11 -0
- package/dist/date/index.d.ts.map +1 -1
- package/dist/date/index.js +43 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -74,25 +74,64 @@ function App() {
|
|
|
74
74
|
#### FormDevTools
|
|
75
75
|
|
|
76
76
|
react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트입니다.
|
|
77
|
+
DevPanel과 통합하거나 별도로 사용할 수 있습니다.
|
|
77
78
|
|
|
79
|
+
**DevPanel과 함께 사용 (권장):**
|
|
78
80
|
```tsx
|
|
81
|
+
import { DevToolsProvider, DevPanel, useRegisterForm } from '@blastlabs/utils/components/dev';
|
|
79
82
|
import { useForm } from 'react-hook-form';
|
|
83
|
+
|
|
84
|
+
// Layout.tsx - Provider로 감싸기
|
|
85
|
+
function Layout() {
|
|
86
|
+
return (
|
|
87
|
+
<DevToolsProvider>
|
|
88
|
+
<YourApp />
|
|
89
|
+
</DevToolsProvider>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 페이지에서 form 등록
|
|
94
|
+
function MyFormPage() {
|
|
95
|
+
const form = useForm({
|
|
96
|
+
defaultValues: { username: '', email: '' }
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// form을 DevPanel에 등록
|
|
100
|
+
useRegisterForm(form);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<form>
|
|
104
|
+
<input {...form.register('username')} />
|
|
105
|
+
<input {...form.register('email')} />
|
|
106
|
+
<button>Submit</button>
|
|
107
|
+
</form>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// App.tsx - DevPanel 렌더링
|
|
112
|
+
function App() {
|
|
113
|
+
return (
|
|
114
|
+
<div>
|
|
115
|
+
{import.meta.env.DEV && <DevPanel />}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**별도 사용:**
|
|
122
|
+
```tsx
|
|
80
123
|
import { FormDevTools } from '@blastlabs/utils/components/dev';
|
|
81
124
|
|
|
82
125
|
function MyForm() {
|
|
83
126
|
const form = useForm({
|
|
84
|
-
defaultValues: {
|
|
85
|
-
username: '',
|
|
86
|
-
email: '',
|
|
87
|
-
age: 0,
|
|
88
|
-
}
|
|
127
|
+
defaultValues: { username: '', email: '' }
|
|
89
128
|
});
|
|
90
129
|
|
|
91
130
|
return (
|
|
92
|
-
<form
|
|
131
|
+
<form>
|
|
93
132
|
<input {...form.register('username')} />
|
|
94
133
|
<input {...form.register('email')} />
|
|
95
|
-
<button
|
|
134
|
+
<button>Submit</button>
|
|
96
135
|
|
|
97
136
|
{import.meta.env.DEV && <FormDevTools form={form} />}
|
|
98
137
|
</form>
|
|
@@ -105,9 +144,64 @@ function MyForm() {
|
|
|
105
144
|
- 에러(errors) 상태 확인
|
|
106
145
|
- 변경된 필드(dirtyFields) 추적
|
|
107
146
|
- 터치된 필드(touchedFields) 확인
|
|
108
|
-
- Mock 데이터 생성 기능
|
|
109
147
|
- 드래그 & 리사이즈 가능한 패널
|
|
110
148
|
|
|
149
|
+
#### DevPanel
|
|
150
|
+
|
|
151
|
+
여러 개발 도구를 하나의 패널에서 통합 관리하는 컴포넌트입니다.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { DevToolsProvider, DevPanel, useRegisterForm } from '@blastlabs/utils/components/dev';
|
|
155
|
+
|
|
156
|
+
// Layout.tsx - 앱 상단에서 한 번만 감싸기
|
|
157
|
+
function Layout() {
|
|
158
|
+
return (
|
|
159
|
+
<DevToolsProvider>
|
|
160
|
+
<YourApp />
|
|
161
|
+
</DevToolsProvider>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 페이지에서 form 등록
|
|
166
|
+
function MyFormPage() {
|
|
167
|
+
const form = useForm({
|
|
168
|
+
defaultValues: { username: '', email: '' }
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Form DevTools 사용을 위해 form 등록
|
|
172
|
+
useRegisterForm(form);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<form>
|
|
176
|
+
<input {...form.register('username')} />
|
|
177
|
+
<input {...form.register('email')} />
|
|
178
|
+
<button>Submit</button>
|
|
179
|
+
</form>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// App.tsx - DevPanel 렌더링
|
|
184
|
+
function App() {
|
|
185
|
+
return (
|
|
186
|
+
<div>
|
|
187
|
+
{import.meta.env.DEV && <DevPanel />}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**주요 기능:**
|
|
194
|
+
- 📝 Form DevTools - react-hook-form 상태 시각화 (form 등록 필요)
|
|
195
|
+
- 🌍 Timezone DevTools - 타임존 정보 확인
|
|
196
|
+
- 📐 Window Size - 윈도우 크기 표시 (토글 가능)
|
|
197
|
+
- 🔍 Z-Index Debugger - z-index 값 시각화 (토글 가능)
|
|
198
|
+
- 각 도구 독립적 토글
|
|
199
|
+
- 드래그 & 리사이즈 가능한 패널
|
|
200
|
+
|
|
201
|
+
**주의사항:**
|
|
202
|
+
- DevPanel을 사용하려면 앱 상단에서 DevToolsProvider로 감싸야 합니다
|
|
203
|
+
- Form DevTools를 사용하려면 페이지에서 useRegisterForm로 form을 등록해야 합니다
|
|
204
|
+
|
|
111
205
|
#### ApiLogger
|
|
112
206
|
|
|
113
207
|
API 요청/응답을 로깅하고 모니터링하는 컴포넌트입니다.
|
package/bin/Makefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.PHONY: help all init-routes init-ai-rules generate-entity
|
|
2
|
+
|
|
3
|
+
help:
|
|
4
|
+
@echo "blastlabs CLI Tools"
|
|
5
|
+
@echo ""
|
|
6
|
+
@echo "Usage: make <command>"
|
|
7
|
+
@echo ""
|
|
8
|
+
@echo "Available commands:"
|
|
9
|
+
@echo " make all - Run all CLI commands"
|
|
10
|
+
@echo " make init-routes - Initialize routes folder structure"
|
|
11
|
+
@echo " make init-ai-rules - Initialize AI rules files"
|
|
12
|
+
@echo " make generate-entity - Generate entity scaffold"
|
|
13
|
+
|
|
14
|
+
all: init-routes init-ai-rules generate-entity
|
|
15
|
+
|
|
16
|
+
init-routes:
|
|
17
|
+
node init-routes.cjs
|
|
18
|
+
|
|
19
|
+
init-ai-rules:
|
|
20
|
+
node init-ai-rules.cjs
|
|
21
|
+
|
|
22
|
+
generate-entity:
|
|
23
|
+
node generate-entity.cjs
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
// routes 폴더 구조와 내용을 정의
|
|
7
|
+
const routesStructure = {
|
|
8
|
+
"base.ts": `/**
|
|
9
|
+
* 기본 라우트 경로
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const BASE_ROUTES = {
|
|
13
|
+
HOME: '/',
|
|
14
|
+
} as const;
|
|
15
|
+
`,
|
|
16
|
+
"types.ts": `/**
|
|
17
|
+
* 라우트 경로 관련 타입 정의
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ROUTE_PATH } from './index';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 라우트 경로 타입
|
|
24
|
+
*/
|
|
25
|
+
export type RoutePath = typeof ROUTE_PATH;
|
|
26
|
+
`,
|
|
27
|
+
"auth.ts": `/**
|
|
28
|
+
* 인증 관련 라우트 경로
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { createRoute } from './utils/helpers';
|
|
32
|
+
|
|
33
|
+
const AUTH_BASE = '/auth';
|
|
34
|
+
|
|
35
|
+
export const AUTH_ROUTES = {
|
|
36
|
+
ROOT: AUTH_BASE,
|
|
37
|
+
LOGIN: createRoute(AUTH_BASE, '/login'),
|
|
38
|
+
REGISTER: createRoute(AUTH_BASE, '/register'),
|
|
39
|
+
FORGOT_PASSWORD: createRoute(AUTH_BASE, '/forgot-password'),
|
|
40
|
+
RESET_PASSWORD: createRoute(AUTH_BASE, '/reset-password'),
|
|
41
|
+
VERIFY_EMAIL: createRoute(AUTH_BASE, '/verify-email'),
|
|
42
|
+
VERIFY_OTP: createRoute(AUTH_BASE, '/verify-otp'),
|
|
43
|
+
} as const;
|
|
44
|
+
`,
|
|
45
|
+
"post.ts": `/**
|
|
46
|
+
* 게시글 관련 라우트 경로
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import { createRoute } from './utils/helpers';
|
|
50
|
+
|
|
51
|
+
const POST_BASE = '/post';
|
|
52
|
+
|
|
53
|
+
export const POST_ROUTES = {
|
|
54
|
+
ROOT: POST_BASE,
|
|
55
|
+
LIST: POST_BASE,
|
|
56
|
+
CREATE: createRoute(POST_BASE, '/create'),
|
|
57
|
+
DETAIL: createRoute(POST_BASE, '/[id]'),
|
|
58
|
+
} as const;
|
|
59
|
+
`,
|
|
60
|
+
"users.example.ts": `/**
|
|
61
|
+
* 사용자 관련 라우트 경로 예시
|
|
62
|
+
* 복사해서 사용하세요
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
import { createRoute } from './utils/helpers';
|
|
66
|
+
|
|
67
|
+
const USERS_BASE = '/users';
|
|
68
|
+
|
|
69
|
+
export const USERS_ROUTES = {
|
|
70
|
+
ROOT: USERS_BASE,
|
|
71
|
+
LIST: USERS_BASE,
|
|
72
|
+
DETAIL: createRoute(USERS_BASE, '/[id]'),
|
|
73
|
+
CREATE: createRoute(USERS_BASE, '/create'),
|
|
74
|
+
EDIT: createRoute(USERS_BASE, '/[id]/edit'),
|
|
75
|
+
} as const;
|
|
76
|
+
`,
|
|
77
|
+
"index.ts": `/**
|
|
78
|
+
* 애플리케이션 라우트 경로 상수
|
|
79
|
+
* Next.js App Router 기준으로 작성되었습니다.
|
|
80
|
+
*
|
|
81
|
+
* 사용 예시:
|
|
82
|
+
* - ROUTE_PATH.HOME
|
|
83
|
+
* - ROUTE_PATH.AUTH.LOGIN
|
|
84
|
+
* - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
import { BASE_ROUTES } from './base';
|
|
88
|
+
import { AUTH_ROUTES } from './auth';
|
|
89
|
+
import { POST_ROUTES } from './post';
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 라우트 경로 상수 객체
|
|
93
|
+
*/
|
|
94
|
+
export const ROUTE_PATH = {
|
|
95
|
+
...BASE_ROUTES,
|
|
96
|
+
AUTH: AUTH_ROUTES,
|
|
97
|
+
POST: POST_ROUTES,
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
// 유틸리티 함수 재내보내기
|
|
101
|
+
export { withParam, withQuery } from './utils/helpers';
|
|
102
|
+
|
|
103
|
+
// 타입 내보내기
|
|
104
|
+
export type { RoutePath } from './types';
|
|
105
|
+
`,
|
|
106
|
+
"utils": {
|
|
107
|
+
"helpers.ts": `/**
|
|
108
|
+
* 라우트 경로 관련 유틸리티 함수
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
|
|
113
|
+
* @param path - 기본 경로 (예: '/users/[id]')
|
|
114
|
+
* @param params - 파라미터 객체 (예: { id: '123' })
|
|
115
|
+
* @returns 실제 경로 (예: '/users/123')
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* \`\`\`ts
|
|
119
|
+
* const userPath = withParam('/users/[id]', { id: '123' });
|
|
120
|
+
* // '/users/123'
|
|
121
|
+
* \`\`\`
|
|
122
|
+
*/
|
|
123
|
+
export function withParam<T extends string>(
|
|
124
|
+
path: T,
|
|
125
|
+
params: Record<string, string | number>,
|
|
126
|
+
): string {
|
|
127
|
+
let result: string = path;
|
|
128
|
+
for (const [key, value] of Object.entries(params)) {
|
|
129
|
+
result = result.replace(\`[\${key}]\`, String(value));
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
|
|
136
|
+
* @param path - 기본 경로
|
|
137
|
+
* @param query - 쿼리 파라미터 객체
|
|
138
|
+
* @returns 쿼리 파라미터가 포함된 경로
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* \`\`\`ts
|
|
142
|
+
* const searchPath = withQuery('/search', { q: 'test', page: 1 });
|
|
143
|
+
* // '/search?q=test&page=1'
|
|
144
|
+
* \`\`\`
|
|
145
|
+
*/
|
|
146
|
+
export function withQuery(
|
|
147
|
+
path: string,
|
|
148
|
+
query: Record<string, string | number | boolean | null | undefined>,
|
|
149
|
+
): string {
|
|
150
|
+
const searchParams = new URLSearchParams();
|
|
151
|
+
for (const [key, value] of Object.entries(query)) {
|
|
152
|
+
if (value !== null && value !== undefined) {
|
|
153
|
+
searchParams.append(key, String(value));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const queryString = searchParams.toString();
|
|
157
|
+
return queryString ? \`\${path}?\${queryString}\` : path;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 라우트 경로를 생성하는 헬퍼 함수
|
|
162
|
+
* @param base - 기본 경로
|
|
163
|
+
* @param path - 추가 경로
|
|
164
|
+
* @returns 합쳐진 경로
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* \`\`\`ts
|
|
168
|
+
* const route = createRoute('/auth', '/login');
|
|
169
|
+
* // '/auth/login'
|
|
170
|
+
* \`\`\`
|
|
171
|
+
*/
|
|
172
|
+
export function createRoute(base: string, path: string): string {
|
|
173
|
+
return \`\${base}\${path.startsWith('/') ? path : \`/\${path}\`}\`;
|
|
174
|
+
}
|
|
175
|
+
`,
|
|
176
|
+
"helpers.test.ts": `import { describe, it, expect } from 'vitest';
|
|
177
|
+
import { withParam, withQuery, createRoute } from './helpers';
|
|
178
|
+
|
|
179
|
+
describe('withParam', () => {
|
|
180
|
+
it('단일 파라미터를 변환한다', () => {
|
|
181
|
+
const result = withParam('/users/[id]', { id: '123' });
|
|
182
|
+
expect(result).toBe('/users/123');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('여러 파라미터를 변환한다', () => {
|
|
186
|
+
const result = withParam('/posts/[postId]/comments/[commentId]', {
|
|
187
|
+
postId: '456',
|
|
188
|
+
commentId: '789'
|
|
189
|
+
});
|
|
190
|
+
expect(result).toBe('/posts/456/comments/789');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('숫자 파라미터를 처리한다', () => {
|
|
194
|
+
const result = withParam('/users/[id]', { id: 123 });
|
|
195
|
+
expect(result).toBe('/users/123');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('withQuery', () => {
|
|
200
|
+
it('단일 쿼리 파라미터를 추가한다', () => {
|
|
201
|
+
const result = withQuery('/search', { q: 'test' });
|
|
202
|
+
expect(result).toBe('/search?q=test');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('여러 쿼리 파라미터를 추가한다', () => {
|
|
206
|
+
const result = withQuery('/search', { q: 'test', page: 1 });
|
|
207
|
+
expect(result).toBe('/search?q=test&page=1');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('null과 undefined을 무시한다', () => {
|
|
211
|
+
const result = withQuery('/search', { q: 'test', page: null, limit: undefined });
|
|
212
|
+
expect(result).toBe('/search?q=test');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('빈 쿼리 객체는 경로를 변경하지 않는다', () => {
|
|
216
|
+
const result = withQuery('/search', {});
|
|
217
|
+
expect(result).toBe('/search');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('boolean 파라미터를 처리한다', () => {
|
|
221
|
+
const result = withQuery('/users', { expanded: true });
|
|
222
|
+
expect(result).toBe('/users?expanded=true');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('createRoute', () => {
|
|
227
|
+
it('기본 경로와 하위 경로를 합친다', () => {
|
|
228
|
+
const result = createRoute('/auth', '/login');
|
|
229
|
+
expect(result).toBe('/auth/login');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('슬래시 없는 하위 경로도 처리한다', () => {
|
|
233
|
+
const result = createRoute('/auth', 'login');
|
|
234
|
+
expect(result).toBe('/auth/login');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
`
|
|
238
|
+
},
|
|
239
|
+
"README.md": `# 라우터 구조
|
|
240
|
+
|
|
241
|
+
라우트 경로를 도메인별로 분리하여 관리하는 구조입니다.
|
|
242
|
+
|
|
243
|
+
## 폴더 구조
|
|
244
|
+
|
|
245
|
+
\`\`\`
|
|
246
|
+
routes/
|
|
247
|
+
├── utils/ # 유틸리티 함수
|
|
248
|
+
│ └── helpers.ts # withParam, withQuery, createRoute 등
|
|
249
|
+
├── types.ts # 타입 정의
|
|
250
|
+
├── base.ts # 기본 라우트 (HOME 등)
|
|
251
|
+
├── auth.ts # 인증 관련 라우트
|
|
252
|
+
├── post.ts # 게시글 관련 라우트
|
|
253
|
+
├── users.example.ts # 확장 예시 파일
|
|
254
|
+
└── index.ts # 모든 라우트 통합 및 내보내기
|
|
255
|
+
\`\`\`
|
|
256
|
+
|
|
257
|
+
## 사용 방법
|
|
258
|
+
|
|
259
|
+
### 기본 사용
|
|
260
|
+
|
|
261
|
+
\`\`\`typescript
|
|
262
|
+
import { ROUTE_PATH } from '@/app/routes';
|
|
263
|
+
|
|
264
|
+
// 기본 라우트
|
|
265
|
+
ROUTE_PATH.HOME // '/'
|
|
266
|
+
|
|
267
|
+
// 인증 라우트
|
|
268
|
+
ROUTE_PATH.AUTH.LOGIN // '/auth/login'
|
|
269
|
+
ROUTE_PATH.AUTH.REGISTER // '/auth/register'
|
|
270
|
+
|
|
271
|
+
// 게시글 라우트
|
|
272
|
+
ROUTE_PATH.POST.CREATE // '/post/create'
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
### 동적 라우트 파라미터
|
|
276
|
+
|
|
277
|
+
\`\`\`typescript
|
|
278
|
+
import { ROUTE_PATH, withParam } from '@/app/routes';
|
|
279
|
+
|
|
280
|
+
// 단일 파라미터
|
|
281
|
+
const postPath = withParam(ROUTE_PATH.POST.DETAIL, { id: '123' });
|
|
282
|
+
// 결과: '/post/123'
|
|
283
|
+
|
|
284
|
+
// 여러 파라미터 (파라미터가 여러 개인 경우)
|
|
285
|
+
const customPath = withParam('/posts/[postId]/comments/[commentId]', {
|
|
286
|
+
postId: '456',
|
|
287
|
+
commentId: '789'
|
|
288
|
+
});
|
|
289
|
+
// 결과: '/posts/456/comments/789'
|
|
290
|
+
\`\`\`
|
|
291
|
+
|
|
292
|
+
### 파라미터와 쿼리 조합
|
|
293
|
+
|
|
294
|
+
\`\`\`typescript
|
|
295
|
+
import { ROUTE_PATH, withParam, withQuery } from '@/app/routes';
|
|
296
|
+
|
|
297
|
+
// withParam으로 파라미터 변환 후 withQuery로 쿼리 추가
|
|
298
|
+
const detailPath = withQuery(
|
|
299
|
+
withParam(ROUTE_PATH.POST.DETAIL, { id: '123' }),
|
|
300
|
+
{ tab: 'edit', expanded: true }
|
|
301
|
+
);
|
|
302
|
+
// 결과: '/post/123?tab=edit&expanded=true'
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
## 새로운 라우트 그룹 추가하기
|
|
306
|
+
|
|
307
|
+
### 1. 새로운 라우트 파일 생성
|
|
308
|
+
|
|
309
|
+
예: \`products.ts\`
|
|
310
|
+
|
|
311
|
+
\`\`\`typescript
|
|
312
|
+
import { createRoute } from './utils/helpers';
|
|
313
|
+
|
|
314
|
+
const PRODUCTS_BASE = '/products';
|
|
315
|
+
|
|
316
|
+
export const PRODUCTS_ROUTES = {
|
|
317
|
+
ROOT: PRODUCTS_BASE,
|
|
318
|
+
LIST: PRODUCTS_BASE,
|
|
319
|
+
DETAIL: createRoute(PRODUCTS_BASE, '/[id]'),
|
|
320
|
+
CREATE: createRoute(PRODUCTS_BASE, '/create'),
|
|
321
|
+
} as const;
|
|
322
|
+
\`\`\`
|
|
323
|
+
|
|
324
|
+
### 2. index.ts에 추가
|
|
325
|
+
|
|
326
|
+
\`\`\`typescript
|
|
327
|
+
import { PRODUCTS_ROUTES } from './products'; // 추가
|
|
328
|
+
|
|
329
|
+
export const ROUTE_PATH = {
|
|
330
|
+
...BASE_ROUTES,
|
|
331
|
+
AUTH: AUTH_ROUTES,
|
|
332
|
+
POST: POST_ROUTES,
|
|
333
|
+
PRODUCTS: PRODUCTS_ROUTES, // 추가
|
|
334
|
+
} as const;
|
|
335
|
+
\`\`\`
|
|
336
|
+
|
|
337
|
+
### 3. 사용하기
|
|
338
|
+
|
|
339
|
+
\`\`\`typescript
|
|
340
|
+
import { ROUTE_PATH } from '@/app/routes';
|
|
341
|
+
|
|
342
|
+
ROUTE_PATH.PRODUCTS.LIST // '/products'
|
|
343
|
+
ROUTE_PATH.PRODUCTS.DETAIL // '/products/[id]'
|
|
344
|
+
\`\`\`
|
|
345
|
+
|
|
346
|
+
## 파일 역할
|
|
347
|
+
|
|
348
|
+
- **base.ts**: 애플리케이션 전체에서 공통으로 사용되는 기본 라우트 (HOME 등)
|
|
349
|
+
- **auth.ts**: 인증 관련 라우트
|
|
350
|
+
- **post.ts**: 게시글 관련 라우트
|
|
351
|
+
- **[domain].ts**: 특정 도메인(예: users, products, orders)의 라우트
|
|
352
|
+
- **utils/helpers.ts**: 라우트 생성 및 조작을 위한 유틸리티 함수
|
|
353
|
+
- **types.ts**: 라우트 관련 타입 정의
|
|
354
|
+
- **index.ts**: 모든 라우트를 통합하여 내보내는 진입점
|
|
355
|
+
|
|
356
|
+
## 원칙
|
|
357
|
+
|
|
358
|
+
1. **도메인별 분리**: 각 도메인은 별도 파일로 관리
|
|
359
|
+
2. **일관된 네이밍**: 모든 라우트 그룹은 \`[DOMAIN]_ROUTES\` 형태로 명명
|
|
360
|
+
3. **타입 안전성**: \`as const\`를 사용하여 타입 추론 최적화
|
|
361
|
+
4. **재사용 가능한 유틸리티**: \`createRoute\`, \`withParam\`, \`withQuery\` 등을 활용
|
|
362
|
+
`
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// 디렉토리와 파일을 재귀적으로 생성하는 함수
|
|
366
|
+
function createDirectoryStructure(basePath, structure) {
|
|
367
|
+
// basePath가 존재하지 않으면 생성
|
|
368
|
+
if (!fs.existsSync(basePath)) {
|
|
369
|
+
fs.mkdirSync(basePath, { recursive: true });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const [name, content] of Object.entries(structure)) {
|
|
373
|
+
const fullPath = path.join(basePath, name);
|
|
374
|
+
|
|
375
|
+
if (typeof content === 'object') {
|
|
376
|
+
// 디렉토리인 경우
|
|
377
|
+
if (!fs.existsSync(fullPath)) {
|
|
378
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
379
|
+
}
|
|
380
|
+
createDirectoryStructure(fullPath, content);
|
|
381
|
+
} else {
|
|
382
|
+
// 파일인 경우
|
|
383
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// CLI 실행
|
|
389
|
+
function main() {
|
|
390
|
+
const targetDir = process.cwd();
|
|
391
|
+
const routesPath = path.join(targetDir, 'src', 'app', 'routes');
|
|
392
|
+
|
|
393
|
+
// 이미 routes 폴더가 있는지 확인
|
|
394
|
+
if (fs.existsSync(routesPath)) {
|
|
395
|
+
console.log('⚠️ src/app/routes 폴더가 이미 존재합니다.');
|
|
396
|
+
console.log('📂 기존 폴더를 삭제하고 다시 생성하려면 수동으로 삭제 후 다시 실행하세요.');
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// routes 폴더 생성
|
|
401
|
+
createDirectoryStructure(routesPath, routesStructure);
|
|
402
|
+
|
|
403
|
+
console.log('✅ routes 폴더가 성공적으로 생성되었습니다!\\n');
|
|
404
|
+
console.log('📍 위치: src/app/routes\\n');
|
|
405
|
+
console.log('생성된 파일들:');
|
|
406
|
+
console.log('📁 routes/');
|
|
407
|
+
console.log('├── utils/');
|
|
408
|
+
console.log('│ ├── helpers.ts');
|
|
409
|
+
console.log('│ └── helpers.test.ts');
|
|
410
|
+
console.log('├── base.ts');
|
|
411
|
+
console.log('├── types.ts');
|
|
412
|
+
console.log('├── auth.ts');
|
|
413
|
+
console.log('├── post.ts');
|
|
414
|
+
console.log('├── users.example.ts');
|
|
415
|
+
console.log('├── index.ts');
|
|
416
|
+
console.log('└── README.md\\n');
|
|
417
|
+
console.log('사용 방법:');
|
|
418
|
+
console.log(" import { ROUTE_PATH, withParam, withQuery } from '@/app/routes';");
|
|
419
|
+
console.log(" ROUTE_PATH.AUTH.LOGIN // '/auth/login'");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
main();
|
|
@@ -5,26 +5,31 @@ type Props = {
|
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
7
|
* 개발자 도구 패널
|
|
8
|
-
*
|
|
8
|
+
* Layout에 한 번만 배치하고, 페이지에서 hook으로 form을 주입받습니다.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```tsx
|
|
12
|
-
* //
|
|
12
|
+
* // Layout.tsx (한 번만)
|
|
13
13
|
* import { DevPanel } from '@blastlabs/utils/components/dev';
|
|
14
14
|
*
|
|
15
|
-
* function
|
|
15
|
+
* function Layout() {
|
|
16
16
|
* return (
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* <>
|
|
18
|
+
* <Outlet />
|
|
19
|
+
* <DevPanel />
|
|
20
|
+
* </>
|
|
20
21
|
* );
|
|
21
22
|
* }
|
|
22
|
-
* ```
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* // 페이지에서 form 주입
|
|
25
|
+
* import { useRegisterForm } from '@blastlabs/utils/components/dev';
|
|
26
|
+
*
|
|
27
|
+
* function MyPage() {
|
|
28
|
+
* const form = useForm();
|
|
29
|
+
* useRegisterForm(form);
|
|
30
|
+
*
|
|
31
|
+
* return <form>...</form>;
|
|
32
|
+
* }
|
|
28
33
|
* ```
|
|
29
34
|
*/
|
|
30
35
|
export default function DevPanel({ position }: Props): React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAOlE,KAAK,KAAK,GAAG;IACX,qCAAqC;IACrC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;CACtE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,QAAyB,EAAE,EAAE,KAAK,qBAkPpE"}
|