@blastlabs/utils 1.14.0 → 1.15.1

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.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: 문서화 가이드라인
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Documentation Guidelines
8
+
9
+ ## JSDoc
10
+
11
+ - 모든 public API에 JSDoc 주석 사용
12
+ - 예제 코드 포함
13
+ - edge cases와 gotchas 문서화
14
+
15
+ ## 문서 업데이트
16
+
17
+ - 기능 추가 시 README.md 업데이트
18
+ - 새 hooks 추가 시 관련 docs/*.md 업데이트
19
+
20
+ ## 필수 사항
21
+
22
+ - **Always** update documentation when adding features
@@ -0,0 +1,66 @@
1
+ ---
2
+ description: "Entities 레이어 구조 및 API 패턴"
3
+ globs: ["**/entities/**"]
4
+ ---
5
+
6
+ # Entities Layer (엔티티)
7
+
8
+ 엔티티는 도메인 모델과 API 로직을 관리합니다.
9
+
10
+ ## 구조
11
+ ```
12
+ entities/
13
+ └── (엔티티 이름)/ 예: inquiry, popular-product
14
+ ├── api/
15
+ │ ├── mapper/
16
+ │ │ ├── map-(엔티티 이름).ts
17
+ │ │ └── map-(엔티티 이름)-detail.ts
18
+ │ ├── query/
19
+ │ │ ├── (엔티티 이름)-list-query.ts
20
+ │ │ └── (엔티티 이름)-detail-query.ts
21
+ │ ├── get-(엔티티 이름)-list.ts
22
+ │ ├── get-(엔티티 이름)-detail.ts
23
+ │ ├── (엔티티 이름)-queries.ts
24
+ │ ├── mutate.ts
25
+ │ ├── query.ts
26
+ │ └── index.ts
27
+ ├── model/
28
+ │ ├── (엔티티 이름).ts
29
+ │ ├── (엔티티 이름)-detail.ts
30
+ │ └── (엔티티 이름)-update.ts
31
+ ├── schema.ts
32
+ └── index.ts
33
+ ```
34
+
35
+ ## 각 파일의 역할
36
+ - **mapper/**: API 응답을 도메인 모델로 변환
37
+ - **query/**: 쿼리 파라미터 타입 정의
38
+ - **get-*-list.ts**: 리스트 조회 API 함수
39
+ - **get-*-detail.ts**: 상세 조회 API 함수
40
+ - **\*-queries.ts**: TanStack Query queryOptions 정의 (all, lists, list, details, detail 등)
41
+ - **mutate.ts**: mutation 함수들
42
+ - **model/**: 도메인 모델 타입 정의
43
+ - **schema.ts**: zod 스키마 정의 및 변환 함수
44
+ - **index.ts**: `export * as (엔티티 이름)Api from "./api"`
45
+
46
+ ## 예시
47
+ ```
48
+ entities/inquiry/
49
+ ├── api/
50
+ │ ├── mapper/
51
+ │ │ ├── map-inquiry.ts
52
+ │ │ └── map-inquiry-detail.ts
53
+ │ ├── query/
54
+ │ │ ├── inquiry-list-query.ts
55
+ │ │ └── inquiry-detail-query.ts
56
+ │ ├── get-inquiry-list.ts
57
+ │ ├── get-inquiry-detail.ts
58
+ │ └── inquiry-queries.ts
59
+ ├── model/
60
+ │ ├── inquiry.ts
61
+ │ └── inquiry-detail.ts
62
+ └── schema.ts
63
+ ```
64
+
65
+ ## TanStack Query 패턴
66
+ 서버 상태 관리는 TanStack Query의 queryOptions 패턴을 사용합니다.
@@ -0,0 +1,32 @@
1
+ ---
2
+ description: "Feature-Sliced Design 아키텍처 가이드"
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Feature-Sliced Design (FSD) 아키텍처
7
+
8
+ 프로젝트는 [FSD 공식문서](https://feature-sliced.github.io/documentation/kr/docs/get-started/overview)를 참고하여 구조화되어 있습니다.
9
+
10
+ ## apps 내 폴더 구조
11
+ ```
12
+ src/
13
+ ├── app/ # 앱 초기화, 프로바이더, 라우팅
14
+ ├── entities/ # 도메인 모델, API
15
+ ├── feature/ # 비즈니스 기능
16
+ ├── shared/ # 공통 유틸, UI
17
+ ├── views/ # 페이지 컴포넌트
18
+ └── widget/ # 독립적 UI 블록
19
+ ```
20
+
21
+ ## 레이어 의존성 규칙
22
+ - `views` → `widget`, `feature`, `entities`, `shared`
23
+ - `widget` → `feature`, `entities`, `shared`
24
+ - `feature` → `entities`, `shared`
25
+ - `entities` → `shared`
26
+ - `shared` → 외부 라이브러리만
27
+
28
+ ## 개발 가이드라인
29
+ 1. **FSD 아키텍처 준수**: 레이어 구조를 유지합니다
30
+ 2. **명명 규칙**: 파일명과 폴더명은 케밥 케이스 사용
31
+ 3. **타입 안정성**: TypeScript로 타입을 명확하게 정의
32
+ 4. **코드 재사용**: 공통 로직은 shared, 도메인 로직은 entities에 배치
@@ -0,0 +1,30 @@
1
+ ---
2
+ description: Git 커밋 컨벤션
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Git Commit Convention
8
+
9
+ ## Conventional Commits Format
10
+
11
+ - `feat(scope): description` - New features
12
+ - `fix(scope): description` - Bug fixes
13
+ - `refactor(scope): description` - Code refactoring
14
+ - `docs(scope): description` - Documentation changes
15
+ - `test(scope): description` - Test changes
16
+ - `chore(scope): description` - Build/tooling changes
17
+
18
+ ## 예시
19
+
20
+ - `feat(hooks): add useAuth hook for authentication management`
21
+ - `fix(hooks): resolve SSR localStorage error in useAuth`
22
+ - `docs(auth): update useAuth documentation with examples`
23
+
24
+ ## 커밋 전 체크리스트
25
+
26
+ 1. Run tests: `npm test`
27
+ 2. Build successfully: `npm run build`
28
+ 3. Update documentation if needed
29
+ 4. Follow conventional commit format
30
+ 5. Add Co-Authored-By line for AI assistance
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: @blastlabs/utils 프로젝트 개요 및 기본 가이드라인
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+
7
+ # @blastlabs/utils Development Rules
8
+
9
+ React 애플리케이션을 위한 종합 TypeScript 유틸리티 라이브러리입니다.
10
+
11
+ ## 제공 기능
12
+
13
+ - Authentication management (useAuth, AuthGuard)
14
+ - UI/UX hooks (useTabs, useMediaQuery, useWindowSize)
15
+ - Form management (useCRUDForm)
16
+ - Time utilities (useCountdown, useStopwatch, useInterval)
17
+ - Storage hooks (useLocalStorage, useSessionStorage, useCopyToClipboard)
18
+ - State management hooks (useToggle, usePrevious)
19
+ - Performance hooks (useDebounce, useThrottle, useIntersectionObserver)
20
+ - Event hooks (useEventListener, useClickOutside)
21
+
22
+ ## 문서
23
+
24
+ - [Development Guide](docs/development-guide.md) - 개발 가이드 통합 문서
25
+ - [Authentication Hooks](docs/auth-hooks.md) - useAuth, AuthGuard
26
+ - [UI Hooks](docs/ui-hooks.md) - useTabs, useMediaQuery, useWindowSize
27
+
28
+ ## 파일 구조
29
+
30
+ ```
31
+ src/
32
+ ├── hooks/
33
+ │ ├── auth/ # Authentication hooks
34
+ │ ├── ui/ # UI/UX hooks
35
+ │ ├── time/ # Time-related hooks
36
+ │ ├── form/ # Form management hooks
37
+ │ ├── storage/ # Storage hooks
38
+ │ ├── state/ # State management hooks
39
+ │ ├── performance/ # Performance hooks
40
+ │ └── event/ # Event hooks
41
+ └── components/
42
+ ├── auth/ # Auth components (AuthGuard)
43
+ └── dev/ # Development components
44
+ ```
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: React Hooks 개발 규칙 및 SSR 안전성
3
+ globs: "**/use*.ts,**/use*.tsx"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # React Hooks Rules
8
+
9
+ ## 필수 규칙
10
+
11
+ - 모든 hooks는 SSR-safe 해야 함 (browser APIs 접근 시 `typeof window !== 'undefined'` 체크)
12
+ - `useCallback`, `useMemo`를 적절히 사용하여 성능 최적화
13
+ - `useEffect`에서 side effects 정리 필수
14
+ - React Hooks 규칙 준수 (조건부 호출 금지 등)
15
+
16
+ ## SSR-Safe Hook Pattern
17
+
18
+ ```typescript
19
+ const [value, setValue] = useState(() => {
20
+ if (typeof window === 'undefined') return defaultValue;
21
+ return window.localStorage.getItem(key);
22
+ });
23
+
24
+ useEffect(() => {
25
+ if (typeof window === 'undefined') return;
26
+ // Browser-only code
27
+ }, []);
28
+ ```
29
+
30
+ ## Storage Pattern
31
+
32
+ ```typescript
33
+ const setItem = useCallback((value: T) => {
34
+ if (typeof window === 'undefined') return;
35
+
36
+ const storage = rememberMe ? localStorage : sessionStorage;
37
+ storage.setItem(key, JSON.stringify(value));
38
+ }, [key, rememberMe]);
39
+ ```
40
+
41
+ ## 중요
42
+
43
+ - **Always** check SSR compatibility for browser APIs
@@ -0,0 +1,57 @@
1
+ ---
2
+ description: "Shared 레이어 구조 및 lib vs utils 구분"
3
+ globs: ["**/shared/**"]
4
+ ---
5
+
6
+ # Shared Layer (공유 레이어)
7
+
8
+ 프로젝트 전체에서 공유되는 코드를 관리합니다.
9
+
10
+ ## 구조
11
+ ```
12
+ shared/
13
+ ├── api/ API 인스턴스 설정 (instance_v2.ts 등)
14
+ ├── constant/ 공통 상수 (path.ts 등)
15
+ ├── hooks/ 공통 React 훅
16
+ ├── mock-data/ 개발용 목 데이터
17
+ ├── lib/ 라이브러리 래퍼 및 설정
18
+ │ └── (라이브러리명)/
19
+ │ ├── provider.tsx 라이브러리 Provider 래퍼
20
+ │ └── config.ts 라이브러리 기본 설정
21
+ ├── ui/
22
+ │ ├── component/ 재사용 가능한 UI 컴포넌트
23
+ │ │ └── (컴포넌트명)/
24
+ │ │ ├── index.tsx
25
+ │ │ ├── types.ts
26
+ │ │ ├── styles.ts
27
+ │ │ └── (컴포넌트명).tsx
28
+ │ ├── theme/ 테마 관련 CSS 파일
29
+ │ └── utils/ UI 관련 유틸리티 함수
30
+ ├── util/ 일반 유틸리티 함수
31
+ └── utils/ 추가 유틸리티 함수
32
+ ```
33
+
34
+ ## lib vs utils 구분 기준
35
+
36
+ ### `shared/lib/`
37
+ 외부 라이브러리를 프로젝트에 맞게 감싸거나 설정하는 코드
38
+ - 라이브러리 래퍼(wrapper) 컴포넌트
39
+ - 라이브러리 초기 설정 및 Provider
40
+ - 예시:
41
+ - `shared/lib/react-hook-form/form-provider.tsx`
42
+ - `shared/lib/tanstack-query/query-client.tsx`
43
+ - `shared/lib/suspense/suspense-wrapper.tsx`
44
+
45
+ ### `shared/utils/` (또는 `shared/util/`)
46
+ 순수 유틸리티 함수 및 비즈니스 로직 헬퍼
47
+ - 라이브러리와 무관한 순수 함수
48
+ - 라이브러리 기능을 사용하는 유틸 함수
49
+ - 예시:
50
+ - `shared/util/convert-price.ts` - 가격 변환 함수
51
+ - `shared/util/export-excel.ts` - 엑셀 내보내기 함수
52
+ - `shared/util/form-validation.ts` - 폼 검증 유틸
53
+
54
+ ## 판단 기준
55
+ - 라이브러리를 감싸는가? → `lib/`
56
+ - 라이브러리와 무관한 로직인가? → `utils/`
57
+ - 라이브러리 기능을 사용하는 유틸인가? → `utils/`
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: 테스트 작성 가이드라인
3
+ globs: "**/*.test.ts,**/*.test.tsx,**/__tests__/**"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Testing Guidelines
8
+
9
+ ## 테스트 도구
10
+
11
+ - Vitest 사용
12
+ - @testing-library/react 사용
13
+
14
+ ## 테스트 원칙
15
+
16
+ - 모든 hooks에 대해 포괄적인 테스트 작성
17
+ - SSR 시나리오 테스트 포함
18
+ - 높은 테스트 커버리지 목표
19
+
20
+ ## 테스트 실행
21
+
22
+ ```bash
23
+ npm test
24
+ ```
25
+
26
+ ## 필수 사항
27
+
28
+ - **Always** write tests for new features
29
+ - 새 기능 추가 시 반드시 테스트 포함
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: TypeScript 코딩 표준 및 타입 규칙
3
+ globs: "**/*.ts,**/*.tsx"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # TypeScript Standards
8
+
9
+ ## 타입 규칙
10
+
11
+ - strict type checking 사용
12
+ - `any` 타입 대신 명시적 타입 사용
13
+ - public types와 interfaces는 모두 export
14
+ - 재사용 가능한 컴포넌트에는 generics 사용
15
+
16
+ ## 예시
17
+
18
+ ### Hook Return Pattern
19
+
20
+ ```typescript
21
+ export interface UseHookReturn {
22
+ // State
23
+ value: T;
24
+ isLoading: boolean;
25
+ error: Error | null;
26
+
27
+ // Actions
28
+ action: () => void;
29
+ reset: () => void;
30
+ }
31
+ ```
32
+
33
+ ## 금지 사항
34
+
35
+ - **Never** use `any` type without justification
36
+ - **Always** provide TypeScript types for public APIs
@@ -0,0 +1,71 @@
1
+ ---
2
+ description: "Views 레이어 (페이지) 구조"
3
+ globs: ["**/views/**"]
4
+ ---
5
+
6
+ # Views Layer (페이지 레이어)
7
+
8
+ 페이지 단위의 UI와 로직을 관리합니다.
9
+
10
+ ## 구조
11
+ ```
12
+ views/
13
+ └── (페이지명)/ 예: inquiry, product, seller
14
+ ├── page.tsx 메인 페이지 컴포넌트 (필수)
15
+ ├── (페이지명)-list-page.tsx
16
+ ├── (페이지명)-detail-page.tsx
17
+ ├── detail/
18
+ │ ├── page.tsx
19
+ │ ├── ui/
20
+ │ ├── hooks/ use-(페이지명)-detail-data.tsx 등
21
+ │ └── components/
22
+ ├── list/
23
+ │ ├── (컬럼명)-column.tsx
24
+ │ ├── search.tsx
25
+ │ └── (모달명)-modal.tsx
26
+ ├── ui/
27
+ ├── hooks/ use-(페이지명)-data.tsx, use-(페이지명)-actions.tsx 등
28
+ ├── components/
29
+ ├── utils/
30
+ ├── schema/ zod 스키마
31
+ └── constants.ts
32
+ ```
33
+
34
+ ## 예시
35
+
36
+ ### 기본 페이지 구조
37
+ ```
38
+ views/inquiry/
39
+ ├── detail/
40
+ │ └── page.tsx
41
+ ├── inquiry-list-page.tsx
42
+ └── list/
43
+ ├── inquiry-column.tsx
44
+ ├── inquiry-modal.tsx
45
+ └── search.tsx
46
+ ```
47
+
48
+ ### 상세 페이지 구조
49
+ ```
50
+ views/ai-management/agency/agency-detail/
51
+ ├── page.tsx
52
+ ├── hooks/
53
+ │ ├── use-agency-detail-data.tsx
54
+ │ └── use-agency-data-actions.tsx
55
+ ├── components/
56
+ │ └── agency-basic-info-tab.tsx
57
+ └── schema/
58
+ └── agency-detail.ts
59
+ ```
60
+
61
+ ### 관리 페이지 구조
62
+ ```
63
+ views/product/category-management/
64
+ ├── page.tsx
65
+ ├── hooks/
66
+ │ ├── use-category-data.tsx
67
+ │ └── use-category-actions.tsx
68
+ └── ui/
69
+ ├── category-management.tsx
70
+ └── category-modal.tsx
71
+ ```
@@ -3,53 +3,42 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
- const TEMPLATES = {
7
- base: "base.md",
8
- fsd: "fsd.md",
9
- testing: "testing.md",
10
- nextjs: "nextjs.md",
11
- vite: "vite.md",
12
- monorepo: "monorepo.md",
13
- project: "project-specific.md",
6
+ // 규칙 카테고리별 정의
7
+ const RULE_CATEGORIES = {
8
+ // 기본 규칙 (항상 포함)
9
+ base: [
10
+ "typescript-standards.mdc",
11
+ "react-hooks.mdc",
12
+ "testing.mdc",
13
+ "documentation.mdc",
14
+ "git-commit.mdc",
15
+ ],
16
+ // FSD 아키텍처 규칙
17
+ fsd: [
18
+ "fsd-architecture.mdc",
19
+ "entities-layer.mdc",
20
+ "shared-layer.mdc",
21
+ "views-layer.mdc",
22
+ ],
14
23
  };
15
24
 
16
25
  function parseArgs(args) {
17
26
  const options = {
18
- projectName: null,
19
- projectDescription: "프로젝트 설명을 작성해주세요.",
20
- framework: null, // 'nextjs' or 'vite'
21
27
  fsd: false,
22
- testing: false,
23
- monorepo: false,
24
- packageManager: "pnpm",
28
+ all: false,
29
+ list: false,
25
30
  help: false,
26
31
  };
27
32
 
28
- for (let i = 0; i < args.length; i++) {
29
- const arg = args[i];
30
-
33
+ for (const arg of args) {
31
34
  if (arg === "--help" || arg === "-h") {
32
35
  options.help = true;
33
- } else if (arg === "--next" || arg === "--nextjs") {
34
- options.framework = "nextjs";
35
- } else if (arg === "--vite") {
36
- options.framework = "vite";
37
36
  } else if (arg === "--fsd") {
38
37
  options.fsd = true;
39
- } else if (arg === "--testing" || arg === "--test") {
40
- options.testing = true;
41
- } else if (arg === "--monorepo") {
42
- options.monorepo = true;
43
- } else if (arg === "--npm") {
44
- options.packageManager = "npm";
45
- } else if (arg === "--yarn") {
46
- options.packageManager = "yarn";
47
- } else if (arg === "--pnpm") {
48
- options.packageManager = "pnpm";
49
- } else if (!arg.startsWith("--") && !options.projectName) {
50
- options.projectName = arg;
51
- } else if (!arg.startsWith("--") && options.projectName && !options.projectDescription) {
52
- options.projectDescription = arg;
38
+ } else if (arg === "--all") {
39
+ options.all = true;
40
+ } else if (arg === "--list" || arg === "-l") {
41
+ options.list = true;
53
42
  }
54
43
  }
55
44
 
@@ -57,111 +46,129 @@ function parseArgs(args) {
57
46
  }
58
47
 
59
48
  function showHelp() {
60
- console.log("@blastlabs/utils - AI Rules Generator\n");
61
- console.log("프로젝트에 .cursorrules와 CLAUDE.md 파일을 생성합니다.\n");
62
- console.log("사용법: npx blastlabs-init-ai-rules <project-name> [options]\n");
49
+ console.log("@blastlabs/utils - Cursor Rules Installer\n");
50
+ console.log("프로젝트에 .cursor/rules/ 규칙 파일들을 설치합니다.\n");
51
+ console.log("사용법: npx blastlabs-init-ai-rules [options]\n");
63
52
  console.log("Options:");
64
- console.log(" --next, --nextjs Next.js 프로젝트 규칙 포함");
65
- console.log(" --vite Vite 프로젝트 규칙 포함");
66
- console.log(" --fsd Feature-Sliced Design 규칙 포함");
67
- console.log(" --testing, --test 테스트 전략 규칙 포함");
68
- console.log(" --monorepo 모노레포 규칙 포함");
69
- console.log(" --npm npm 사용 (기본값: pnpm)");
70
- console.log(" --yarn yarn 사용");
71
- console.log(" --pnpm pnpm 사용 (기본값)");
53
+ console.log(" --fsd FSD 아키텍처 규칙 포함");
54
+ console.log(" --all 모든 규칙 설치 (base + fsd)");
55
+ console.log(" --list, -l 설치 가능한 규칙 목록 보기");
56
+ console.log(" --help, -h 도움말 보기");
72
57
  console.log("\n예시:");
73
- console.log(' npx blastlabs-init-ai-rules "My Project" --vite --fsd');
74
- console.log(' npx blastlabs-init-ai-rules "Admin" --next --fsd --testing --monorepo');
75
- console.log(' npx blastlabs-init-ai-rules "Web App" "React 웹 애플리케이션" --vite');
58
+ console.log(" npx blastlabs-init-ai-rules # 기본 규칙만");
59
+ console.log(" npx blastlabs-init-ai-rules --fsd # 기본 + FSD 규칙");
60
+ console.log(" npx blastlabs-init-ai-rules --all # 모든 규칙");
76
61
  console.log("\n⚠️ 프로젝트 루트 디렉토리에서 실행해주세요!");
77
62
  }
78
63
 
79
- function loadTemplate(name) {
80
- const templatePath = path.join(__dirname, "..", "templates", name);
81
- try {
82
- return fs.readFileSync(templatePath, "utf-8");
83
- } catch (error) {
84
- console.error(`❌ 템플릿 파일을 찾을 수 없습니다: ${name}`);
85
- return "";
64
+ function showList() {
65
+ console.log("📋 설치 가능한 규칙 목록\n");
66
+
67
+ console.log("📦 기본 규칙 (base) - 항상 포함:");
68
+ for (const rule of RULE_CATEGORIES.base) {
69
+ console.log(` ├── ${rule}`);
70
+ }
71
+
72
+ console.log("\n🏗️ FSD 아키텍처 규칙 (--fsd):");
73
+ for (const rule of RULE_CATEGORIES.fsd) {
74
+ console.log(` ├── ${rule}`);
75
+ }
76
+
77
+ console.log("\n💡 각 규칙은 globs 패턴으로 자동 적용됩니다.");
78
+ }
79
+
80
+ function getRulesSourceDir() {
81
+ // 패키지 내 .cursor/rules 경로
82
+ return path.join(__dirname, "..", ".cursor", "rules");
83
+ }
84
+
85
+ function copyRule(ruleName, sourceDir, targetDir) {
86
+ const sourcePath = path.join(sourceDir, ruleName);
87
+ const targetPath = path.join(targetDir, ruleName);
88
+
89
+ if (!fs.existsSync(sourcePath)) {
90
+ console.log(` ⚠️ ${ruleName} - 소스 파일 없음, 건너뜀`);
91
+ return false;
92
+ }
93
+
94
+ if (fs.existsSync(targetPath)) {
95
+ console.log(` ⏭️ ${ruleName} - 이미 존재함, 건너뜀`);
96
+ return false;
86
97
  }
98
+
99
+ fs.copyFileSync(sourcePath, targetPath);
100
+ console.log(` ✅ ${ruleName}`);
101
+ return true;
87
102
  }
88
103
 
89
104
  function main() {
90
105
  const args = process.argv.slice(2);
91
106
  const options = parseArgs(args);
92
107
 
93
- if (options.help || !options.projectName) {
108
+ if (options.help) {
94
109
  showHelp();
95
110
  process.exit(0);
96
111
  }
97
112
 
98
- const targetDir = process.cwd();
99
-
100
- // 템플릿 조합
101
- let content = loadTemplate(TEMPLATES.base);
102
-
103
- if (options.fsd) {
104
- content += "\n" + loadTemplate(TEMPLATES.fsd);
113
+ if (options.list) {
114
+ showList();
115
+ process.exit(0);
105
116
  }
106
117
 
107
- if (options.testing) {
108
- content += "\n" + loadTemplate(TEMPLATES.testing);
118
+ const targetDir = process.cwd();
119
+ const rulesTargetDir = path.join(targetDir, ".cursor", "rules");
120
+ const rulesSourceDir = getRulesSourceDir();
121
+
122
+ // 소스 디렉토리 확인
123
+ if (!fs.existsSync(rulesSourceDir)) {
124
+ console.error("❌ 규칙 소스 디렉토리를 찾을 수 없습니다.");
125
+ console.error(` 경로: ${rulesSourceDir}`);
126
+ process.exit(1);
109
127
  }
110
128
 
111
- if (options.framework === "nextjs") {
112
- content += "\n" + loadTemplate(TEMPLATES.nextjs);
113
- } else if (options.framework === "vite") {
114
- content += "\n" + loadTemplate(TEMPLATES.vite);
129
+ // .cursor/rules 디렉토리 생성
130
+ if (!fs.existsSync(rulesTargetDir)) {
131
+ fs.mkdirSync(rulesTargetDir, { recursive: true });
132
+ console.log("📁 .cursor/rules/ 디렉토리 생성됨\n");
115
133
  }
116
134
 
117
- if (options.monorepo) {
118
- content += "\n" + loadTemplate(TEMPLATES.monorepo);
135
+ // 설치할 규칙 목록 결정
136
+ let rulesToInstall = [...RULE_CATEGORIES.base];
137
+
138
+ if (options.all || options.fsd) {
139
+ rulesToInstall = [...rulesToInstall, ...RULE_CATEGORIES.fsd];
119
140
  }
120
141
 
121
- // 항상 project-specific 섹션 추가
122
- content += "\n" + loadTemplate(TEMPLATES.project);
142
+ // 중복 제거
143
+ rulesToInstall = [...new Set(rulesToInstall)];
123
144
 
124
- // 플레이스홀더 치환
125
- const frameworkName = options.framework === "nextjs" ? "Next.js" :
126
- options.framework === "vite" ? "Vite" : "React";
127
-
128
- content = content
129
- .replace(/\{\{PROJECT_NAME\}\}/g, options.projectName)
130
- .replace(/\{\{PROJECT_DESCRIPTION\}\}/g, options.projectDescription)
131
- .replace(/\{\{PACKAGE_MANAGER\}\}/g, options.packageManager)
132
- .replace(/\{\{FRAMEWORK\}\}/g, frameworkName);
133
-
134
- // .cursorrules 파일 생성
135
- const cursorrulesPath = path.join(targetDir, ".cursorrules");
136
- if (fs.existsSync(cursorrulesPath)) {
137
- console.log("⚠️ .cursorrules 파일이 이미 존재합니다. 덮어쓰기를 건너뜁니다.");
138
- } else {
139
- fs.writeFileSync(cursorrulesPath, content);
140
- console.log("✅ .cursorrules 생성 완료");
141
- }
145
+ console.log("📥 규칙 설치 중...\n");
142
146
 
143
- // CLAUDE.md 파일 생성 (루트)
144
- const claudePath = path.join(targetDir, "CLAUDE.md");
145
- if (fs.existsSync(claudePath)) {
146
- console.log("⚠️ CLAUDE.md 파일이 이미 존재합니다. 덮어쓰기를 건너뜁니다.");
147
- } else {
148
- fs.writeFileSync(claudePath, content);
149
- console.log("✅ CLAUDE.md 생성 완료");
147
+ let installedCount = 0;
148
+ let skippedCount = 0;
149
+
150
+ for (const rule of rulesToInstall) {
151
+ const installed = copyRule(rule, rulesSourceDir, rulesTargetDir);
152
+ if (installed) {
153
+ installedCount++;
154
+ } else {
155
+ skippedCount++;
156
+ }
150
157
  }
151
158
 
152
- console.log("\n📁 생성된 파일들:");
153
- console.log("├── .cursorrules");
154
- console.log("└── CLAUDE.md");
159
+ console.log("\n" + "─".repeat(40));
160
+ console.log(`\n✨ 완료! ${installedCount}개 설치, ${skippedCount}개 건너뜀`);
155
161
 
156
- console.log("\n📦 포함된 규칙:");
157
- console.log("├── base (공통 규칙)");
158
- if (options.fsd) console.log("├── fsd (Feature-Sliced Design)");
159
- if (options.testing) console.log("├── testing (테스트 전략)");
160
- if (options.framework) console.log(`├── ${options.framework}`);
161
- if (options.monorepo) console.log("├── monorepo");
162
- console.log("└── project-specific");
162
+ console.log("\n📦 설치된 규칙 카테고리:");
163
+ console.log(" ├── base (기본 규칙)");
164
+ if (options.all || options.fsd) {
165
+ console.log(" └── fsd (Feature-Sliced Design)");
166
+ }
163
167
 
164
- console.log("\n💡 Tip: 생성된 파일을 프로젝트에 맞게 수정하세요!");
168
+ console.log("\n💡 Tip:");
169
+ console.log(" - 각 .mdc 파일의 globs 패턴에 따라 자동 적용됩니다");
170
+ console.log(" - alwaysApply: true 규칙은 항상 적용됩니다");
171
+ console.log(" - 프로젝트에 맞게 규칙을 수정하세요!");
165
172
  }
166
173
 
167
174
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "bin",
14
- "templates"
14
+ ".cursor/rules"
15
15
  ],
16
16
  "scripts": {
17
17
  "prepare": "npm run build:tsc",
package/templates/base.md DELETED
@@ -1,111 +0,0 @@
1
- # {{PROJECT_NAME}} Development Rules
2
-
3
- This file contains development guidelines for AI assistants (Cursor, Claude Code, etc.)
4
-
5
- ## Project Overview
6
-
7
- {{PROJECT_DESCRIPTION}}
8
-
9
- ---
10
-
11
- # 프로젝트 기본 정보
12
-
13
- - **패키지 매니저**: {{PACKAGE_MANAGER}}
14
- - **프레임워크**: {{FRAMEWORK}}
15
-
16
- ## 기술 스택
17
-
18
- - TypeScript
19
- - React
20
- - TanStack Query
21
- - Tailwind CSS
22
- - Zod
23
-
24
- ---
25
-
26
- # Common Rules (공통 규칙)
27
-
28
- ## Code Style & Standards
29
-
30
- ### TypeScript
31
- - Use strict type checking (`strict: true` in tsconfig)
32
- - Prefer explicit types over `any`
33
- - Export all public types and interfaces
34
- - Use generics for reusable components
35
- - Use `type` for unions/intersections, `interface` for object shapes
36
-
37
- ### Naming Conventions
38
- - **Files/Folders**: kebab-case (`user-profile.tsx`, `use-auth.ts`)
39
- - **Components**: PascalCase (`UserProfile`, `AuthGuard`)
40
- - **Hooks**: camelCase with `use` prefix (`useAuth`, `useLocalStorage`)
41
- - **Constants**: UPPER_SNAKE_CASE (`API_BASE_URL`, `MAX_RETRY_COUNT`)
42
- - **Types/Interfaces**: PascalCase (`UserProfile`, `AuthState`)
43
-
44
- ### Import Order
45
- 1. React/Framework imports
46
- 2. Third-party libraries
47
- 3. Internal modules (absolute paths)
48
- 4. Relative imports
49
- 5. Types (with `type` keyword)
50
-
51
- ```typescript
52
- import React, { useState, useEffect } from 'react';
53
- import { useQuery } from '@tanstack/react-query';
54
- import { Button } from '@/components/ui';
55
- import { formatDate } from '../utils';
56
- import type { User } from '@/types';
57
- ```
58
-
59
- ## React Best Practices
60
-
61
- ### Component Patterns
62
- - Use functional components with hooks
63
- - Keep components small and focused (single responsibility)
64
- - Extract reusable logic into custom hooks
65
- - Use composition over inheritance
66
-
67
- ### Hooks Rules
68
- - Only call hooks at the top level
69
- - Only call hooks from React functions
70
- - Use `useCallback` for event handlers passed to children
71
- - Use `useMemo` for expensive calculations
72
-
73
- ### State Management
74
- - Keep state as local as possible
75
- - Lift state up only when necessary
76
- - Use TanStack Query for server state
77
- - Use context or Zustand for client global state
78
-
79
- ## Error Handling
80
-
81
- ```typescript
82
- try {
83
- const result = await someAsyncOperation();
84
- return { data: result, error: null };
85
- } catch (error) {
86
- console.error('Operation failed:', error);
87
- return { data: null, error: error instanceof Error ? error : new Error('Unknown error') };
88
- }
89
- ```
90
-
91
- ## Git Commit Convention
92
-
93
- Use conventional commits format:
94
- - `feat(scope): description` - New features
95
- - `fix(scope): description` - Bug fixes
96
- - `refactor(scope): description` - Code refactoring
97
- - `docs(scope): description` - Documentation changes
98
- - `test(scope): description` - Test changes
99
- - `chore(scope): description` - Build/tooling changes
100
-
101
- Examples:
102
- - `feat(auth): add login functionality`
103
- - `fix(api): resolve timeout error`
104
- - `docs(readme): update installation guide`
105
-
106
- ## Before Committing
107
-
108
- 1. Run tests: `{{PACKAGE_MANAGER}} test`
109
- 2. Build successfully: `{{PACKAGE_MANAGER}} build`
110
- 3. Lint check: `{{PACKAGE_MANAGER}} lint`
111
- 4. Format code: `{{PACKAGE_MANAGER}} format`
package/templates/fsd.md DELETED
@@ -1,150 +0,0 @@
1
-
2
- ---
3
-
4
- # Feature-Sliced Design (FSD)
5
-
6
- 프로젝트는 [FSD 공식문서](https://feature-sliced.github.io/documentation/kr/docs/get-started/overview)를 참고하여 구조화되어 있습니다.
7
-
8
- ## 폴더 구조
9
-
10
- ```
11
- src/
12
- ├── app/ # 앱 설정, providers, 라우팅
13
- ├── entities/ # 도메인 모델, API
14
- ├── features/ # 사용자 기능 단위
15
- ├── shared/ # 공유 컴포넌트, 유틸
16
- ├── views/ # 페이지 컴포넌트
17
- └── widgets/ # 복합 UI 블록
18
- ```
19
-
20
- ## Entities Layer (엔티티)
21
-
22
- 엔티티는 도메인 모델과 API 로직을 관리합니다.
23
-
24
- ### 구조
25
-
26
- ```
27
- entities/
28
- └── (엔티티 이름)/ 예: inquiry, popular-product
29
- ├── api/
30
- │ ├── mapper/
31
- │ │ ├── map-(엔티티 이름).ts
32
- │ │ └── map-(엔티티 이름)-detail.ts
33
- │ ├── query/
34
- │ │ ├── (엔티티 이름)-list-query.ts
35
- │ │ └── (엔티티 이름)-detail-query.ts
36
- │ ├── get-(엔티티 이름)-list.ts
37
- │ ├── get-(엔티티 이름)-detail.ts
38
- │ ├── (엔티티 이름)-queries.ts
39
- │ ├── mutate.ts
40
- │ └── index.ts
41
- ├── model/
42
- │ ├── (엔티티 이름).ts
43
- │ ├── (엔티티 이름)-detail.ts
44
- │ └── (엔티티 이름)-update.ts
45
- ├── schema.ts
46
- └── index.ts
47
- ```
48
-
49
- ### 각 파일의 역할
50
-
51
- - **mapper/**: API 응답을 도메인 모델로 변환
52
- - **query/**: 쿼리 파라미터 타입 정의
53
- - **get-\*-list.ts**: 리스트 조회 API 함수
54
- - **get-\*-detail.ts**: 상세 조회 API 함수
55
- - **\*-queries.ts**: TanStack Query queryOptions 정의 (all, lists, list, details, detail 등)
56
- - **mutate.ts**: mutation 함수들
57
- - **model/**: 도메인 모델 타입 정의
58
- - **schema.ts**: zod 스키마 정의 및 변환 함수
59
- - **index.ts**: `export * as (엔티티 이름)Api from "./api"`
60
-
61
- ### 예시
62
-
63
- ```
64
- entities/inquiry/
65
- ├── api/
66
- │ ├── mapper/
67
- │ │ ├── map-inquiry.ts
68
- │ │ └── map-inquiry-detail.ts
69
- │ ├── query/
70
- │ │ ├── inquiry-list-query.ts
71
- │ │ └── inquiry-detail-query.ts
72
- │ ├── get-inquiry-list.ts
73
- │ ├── get-inquiry-detail.ts
74
- │ └── inquiry-queries.ts
75
- ├── model/
76
- │ ├── inquiry.ts
77
- │ └── inquiry-detail.ts
78
- └── schema.ts
79
- ```
80
-
81
- ## Shared Layer (공유 레이어)
82
-
83
- 프로젝트 전체에서 공유되는 코드를 관리합니다.
84
-
85
- ### 구조
86
-
87
- ```
88
- shared/
89
- ├── api/ API 인스턴스 설정
90
- ├── constant/ 공통 상수 (path.ts 등)
91
- ├── hooks/ 공통 React 훅
92
- ├── lib/ 라이브러리 래퍼 및 설정
93
- │ └── (라이브러리명)/
94
- │ ├── provider.tsx
95
- │ └── config.ts
96
- ├── ui/
97
- │ ├── component/ 재사용 가능한 UI 컴포넌트
98
- │ │ └── (컴포넌트명)/
99
- │ │ ├── index.tsx
100
- │ │ ├── types.ts
101
- │ │ └── (컴포넌트명).tsx
102
- │ ├── theme/ 테마 관련 CSS 파일
103
- │ └── utils/ UI 관련 유틸리티 함수
104
- └── utils/ 일반 유틸리티 함수
105
- ```
106
-
107
- ### lib vs utils 구분 기준
108
-
109
- #### `shared/lib/`
110
- 외부 라이브러리를 프로젝트에 맞게 감싸거나 설정하는 코드
111
- - 라이브러리 래퍼(wrapper) 컴포넌트
112
- - 라이브러리 초기 설정 및 Provider
113
- - 예시: `react-hook-form/form-provider.tsx`, `tanstack-query/query-client.tsx`
114
-
115
- #### `shared/utils/`
116
- 순수 유틸리티 함수 및 비즈니스 로직 헬퍼
117
- - 라이브러리와 무관한 순수 함수
118
- - 예시: `convert-price.ts`, `export-excel.ts`, `format-date.ts`
119
-
120
- **판단 기준:**
121
- - 라이브러리를 감싸는가? → `lib/`
122
- - 순수 함수 또는 유틸인가? → `utils/`
123
-
124
- ## Views Layer (페이지 레이어)
125
-
126
- 페이지 단위의 UI와 로직을 관리합니다.
127
-
128
- ### 구조
129
-
130
- ```
131
- views/
132
- └── (페이지명)/
133
- ├── page.tsx 메인 페이지 컴포넌트 (필수)
134
- ├── (페이지명)-list-page.tsx
135
- ├── (페이지명)-detail-page.tsx
136
- ├── detail/
137
- │ ├── page.tsx
138
- │ ├── ui/
139
- │ ├── hooks/ use-(페이지명)-detail-data.tsx 등
140
- │ └── components/
141
- ├── list/
142
- │ ├── (컬럼명)-column.tsx
143
- │ ├── search.tsx
144
- │ └── (모달명)-modal.tsx
145
- ├── hooks/
146
- ├── components/
147
- ├── utils/
148
- ├── schema/
149
- └── constants.ts
150
- ```
@@ -1,61 +0,0 @@
1
-
2
- ---
3
-
4
- # Monorepo Rules
5
-
6
- ## Structure
7
-
8
- ```
9
- root/
10
- ├── apps/
11
- │ ├── web/ # 웹 앱
12
- │ ├── admin/ # 어드민
13
- │ └── mobile/ # 모바일 앱
14
- ├── packages/
15
- │ ├── ui/ # 공유 UI 컴포넌트
16
- │ ├── utils/ # 공유 유틸리티
17
- │ ├── config/ # 공유 설정 (eslint, tsconfig)
18
- │ └── types/ # 공유 TypeScript 타입
19
- ├── package.json
20
- └── pnpm-workspace.yaml
21
- ```
22
-
23
- ## Package Management
24
- - Use workspace protocol for internal packages (`workspace:*`)
25
- - Keep shared dependencies in root `package.json`
26
- - App-specific dependencies in app's `package.json`
27
-
28
- ```json
29
- {
30
- "dependencies": {
31
- "@repo/ui": "workspace:*",
32
- "@repo/utils": "workspace:*"
33
- }
34
- }
35
- ```
36
-
37
- ## Build Order
38
- - Build packages before apps
39
- - Use Turborepo for build orchestration
40
- - Configure proper `dependsOn` in turbo.json
41
-
42
- ## Shared Configurations
43
- - Extend base tsconfig in all packages
44
- - Share ESLint config from `packages/config`
45
- - Maintain consistent formatting across packages
46
-
47
- ## Commands
48
-
49
- ```bash
50
- # 전체 빌드
51
- pnpm build
52
-
53
- # 특정 앱만 빌드
54
- pnpm --filter @repo/admin build
55
-
56
- # 특정 앱 dev 서버
57
- pnpm --filter @repo/web dev
58
-
59
- # 전체 린트
60
- pnpm lint
61
- ```
@@ -1,67 +0,0 @@
1
-
2
- ---
3
-
4
- # Next.js Rules
5
-
6
- ## App Router
7
- - Use Server Components by default
8
- - Add `'use client'` only when needed (hooks, browser APIs, interactivity)
9
- - Colocate loading.tsx, error.tsx, not-found.tsx with pages
10
- - Use route groups `(group)` for organization without affecting URL
11
-
12
- ## File Structure
13
- ```
14
- app/
15
- ├── (auth)/
16
- │ ├── login/page.tsx
17
- │ └── register/page.tsx
18
- ├── (main)/
19
- │ ├── dashboard/page.tsx
20
- │ └── settings/page.tsx
21
- ├── api/
22
- │ └── [...route]/route.ts
23
- ├── layout.tsx
24
- └── page.tsx
25
- ```
26
-
27
- ## Data Fetching
28
- - Prefer Server Components for data fetching
29
- - Use `fetch` with caching options in Server Components
30
- - Use React Query/SWR for client-side data fetching
31
- - Implement proper loading and error states
32
-
33
- ## Environment Variables
34
- - `NEXT_PUBLIC_*` for client-side variables
35
- - Server-only variables without prefix
36
- - Use `.env.local` for local development
37
-
38
- ## SSR Safety
39
-
40
- ```typescript
41
- // ❌ Wrong - breaks SSR
42
- const token = localStorage.getItem('token');
43
-
44
- // ✅ Correct - SSR safe
45
- const token = typeof window !== 'undefined'
46
- ? localStorage.getItem('token')
47
- : null;
48
- ```
49
-
50
- ## SSR-Safe Hook Pattern
51
-
52
- ```typescript
53
- const [value, setValue] = useState(() => {
54
- if (typeof window === 'undefined') return defaultValue;
55
- return window.localStorage.getItem(key);
56
- });
57
-
58
- useEffect(() => {
59
- if (typeof window === 'undefined') return;
60
- // Browser-only code
61
- }, []);
62
- ```
63
-
64
- ## Image Optimization
65
- - Always use `next/image` for images
66
- - Provide width and height or use `fill`
67
- - Use appropriate `sizes` prop for responsive images
@@ -1,21 +0,0 @@
1
-
2
- ---
3
-
4
- # Project Specific Rules (프로젝트별 추가 규칙)
5
-
6
- <!-- 여기에 프로젝트 고유의 규칙을 추가하세요 -->
7
-
8
- ## Custom Rules
9
- - Add your project-specific rules here
10
- - Document any exceptions to common rules
11
- - Include team agreements and decisions
12
-
13
- ## API Conventions
14
- - Document API patterns used in this project
15
- - Specify error handling strategies
16
- - Define response/request formats
17
-
18
- ## State Management
19
- - Document which state library is used
20
- - Define when to use local vs global state
21
- - Specify state structure conventions
@@ -1,66 +0,0 @@
1
-
2
- ---
3
-
4
- # 테스트 전략
5
-
6
- ## 테스트 작성 원칙
7
-
8
- ### 컴포넌트 (Component) - 단위 테스트
9
-
10
- - **대상**: `shared/ui/component/`, 재사용 가능한 컴포넌트
11
- - **목적**: 컴포넌트의 독립적인 동작 검증
12
- - **방법**:
13
- - Props와 사용자 상호작용에 집중
14
- - 외부 의존성(API, 라우팅)은 mock 처리
15
- - 렌더링, 이벤트 핸들링, 상태 변경 검증
16
-
17
- ### 페이지 (Page) - 기능 테스트
18
-
19
- - **대상**: `views/*/page.tsx` 페이지 컴포넌트
20
- - **목적**: 사용자 시나리오와 비즈니스 로직 검증
21
- - **Mock 최소화 원칙**:
22
- - 외부 의존성(API 호출, toast)만 mock
23
- - 내부 컴포넌트는 실제로 렌더링하여 통합 테스트
24
- - `data-testid` 대신 접근성 기반 쿼리 사용
25
-
26
- ## Testing Library 쿼리 우선순위
27
-
28
- **1순위 (가장 권장):**
29
- - `getByRole` - 접근성 역할 기반 (button, textbox, checkbox 등)
30
- - `getByLabelText` - label과 연결된 폼 요소
31
- - `getByPlaceholderText` - placeholder가 있는 입력 필드
32
- - `getByText` - 텍스트 내용으로 찾기
33
- - `getByDisplayValue` - 입력 필드의 현재 값
34
-
35
- **2순위:**
36
- - `getByAltText` - 이미지의 alt 텍스트
37
- - `getByTitle` - title 속성
38
-
39
- **3순위 (최후의 수단):**
40
- - `getByTestId` - `data-testid` (가능한 한 피해야 함)
41
-
42
- **예시:**
43
- ```typescript
44
- // ✅ 좋은 예
45
- screen.getByRole('button', { name: '저장' });
46
- screen.getByRole('checkbox');
47
- screen.getByText('셀러 정보를 찾을 수 없습니다.');
48
- screen.getByLabelText(/대표자명/);
49
-
50
- // ❌ 나쁜 예
51
- screen.getByTestId('save-button');
52
- ```
53
-
54
- ## 테스트 파일 위치
55
-
56
- - 컴포넌트와 같은 디렉토리에 `*.test.tsx`
57
-
58
- ## 테스트 구조
59
-
60
- ```typescript
61
- describe('ComponentName', () => {
62
- it('should render correctly', () => {});
63
- it('should handle user interaction', () => {});
64
- it('should show error state', () => {});
65
- });
66
- ```
package/templates/vite.md DELETED
@@ -1,53 +0,0 @@
1
-
2
- ---
3
-
4
- # Vite + React Rules
5
-
6
- ## Environment Variables
7
- - Use `import.meta.env.VITE_*` for environment variables
8
- - Only `VITE_` prefixed variables are exposed to client
9
-
10
- ```typescript
11
- // ✅ Correct
12
- const apiUrl = import.meta.env.VITE_API_URL;
13
-
14
- // ❌ Wrong - won't work
15
- const secret = import.meta.env.SECRET_KEY;
16
- ```
17
-
18
- ## File Structure
19
- ```
20
- src/
21
- ├── assets/ # Static assets
22
- ├── components/ # React components
23
- ├── hooks/ # Custom hooks
24
- ├── pages/ # Page components
25
- ├── routes/ # Routing configuration
26
- ├── services/ # API services
27
- ├── stores/ # State management
28
- ├── utils/ # Utility functions
29
- └── main.tsx # Entry point
30
- ```
31
-
32
- ## Path Aliases
33
- - Use `@/` alias for `src/` directory
34
- - Configure in `vite.config.ts` and `tsconfig.json`
35
-
36
- ```typescript
37
- // vite.config.ts
38
- resolve: {
39
- alias: {
40
- '@': path.resolve(__dirname, './src'),
41
- },
42
- }
43
- ```
44
-
45
- ## Build Optimization
46
- - Use dynamic imports for code splitting
47
- - Lazy load routes and heavy components
48
- - Analyze bundle size with `rollup-plugin-visualizer`
49
-
50
- ```typescript
51
- // Lazy loading example
52
- const Dashboard = lazy(() => import('./pages/Dashboard'));
53
- ```