tylersong 1.0.9 → 1.0.11

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,173 @@
1
+ # 테스트 가이드
2
+
3
+ ## 개요
4
+
5
+ 이 프로젝트는 **Vitest**를 사용하여 TypeScript 코드를 테스트합니다. Vitest는 빠르고 현대적인 테스트 프레임워크로, Vite 기반의 빠른 실행 속도와 훌륭한 TypeScript 지원을 제공합니다.
6
+
7
+ ## 테스트 실행
8
+
9
+ ### Bun 사용 (권장)
10
+
11
+ ```bash
12
+ # 모든 테스트 실행
13
+ bun run test
14
+
15
+ # 테스트 감시 모드 (파일 변경 시 자동 재실행)
16
+ bun run test:watch
17
+
18
+ # 커버리지와 함께 테스트 실행
19
+ bun run test:coverage
20
+ ```
21
+
22
+ ### NPM 사용
23
+
24
+ ```bash
25
+ # 모든 테스트 실행
26
+ npm test
27
+
28
+ # 테스트 감시 모드
29
+ npm run test:watch
30
+
31
+ # 커버리지와 함께 테스트 실행
32
+ npm run test:coverage
33
+ ```
34
+
35
+ ## 테스트 구조
36
+
37
+ ### 테스트 파일 위치
38
+
39
+ ```
40
+ test/
41
+ ├── index.test.ts # 메인 CLI 기능 테스트
42
+ └── (추가 테스트 파일들)
43
+ ```
44
+
45
+ ### 테스트 커버리지
46
+
47
+ 테스트 커버리지는 다음 영역을 포함합니다:
48
+
49
+ 1. **단위 테스트**
50
+ - `openUrl` 함수 - URL 열기 기능
51
+ - `getPackageVersion` 함수 - 버전 정보 읽기
52
+ - 타입 정의 검증
53
+
54
+ 2. **통합 테스트**
55
+ - CLI 옵션 처리
56
+ - 사용자 인터랙션
57
+ - 에러 핸들링
58
+
59
+ 3. **플랫폼 테스트**
60
+ - Windows, macOS, Linux 명령어 검증
61
+
62
+ ## 테스트 작성 가이드
63
+
64
+ ### 기본 구조
65
+
66
+ ```typescript
67
+ import { describe, it, expect, vi } from 'vitest';
68
+
69
+ describe('테스트 그룹 이름', () => {
70
+ it('테스트 케이스 설명', () => {
71
+ // 준비 (Arrange)
72
+ const input = 'test';
73
+
74
+ // 실행 (Act)
75
+ const result = someFunction(input);
76
+
77
+ // 검증 (Assert)
78
+ expect(result).toBe('expected');
79
+ });
80
+ });
81
+ ```
82
+
83
+ ### 모킹 (Mocking)
84
+
85
+ 외부 의존성은 모킹하여 테스트합니다:
86
+
87
+ ```typescript
88
+ vi.mock('child_process', () => ({
89
+ exec: vi.fn(),
90
+ }));
91
+
92
+ vi.mock('chalk', () => ({
93
+ default: {
94
+ red: vi.fn((str) => str),
95
+ // 기타 chalk 메서드들
96
+ },
97
+ }));
98
+ ```
99
+
100
+ ## CI/CD 통합
101
+
102
+ GitHub Actions를 통해 자동으로 테스트가 실행됩니다:
103
+
104
+ - **Pull Request**: 모든 PR에서 테스트 실행
105
+ - **Main 브랜치 푸시**: 빌드 및 테스트 검증
106
+ - **테스트 실패**: 머지 방지
107
+
108
+ ## 커버리지 목표
109
+
110
+ - **라인 커버리지**: 80% 이상
111
+ - **함수 커버리지**: 80% 이상
112
+ - **브랜치 커버리지**: 70% 이상
113
+
114
+ ## 모범 사례
115
+
116
+ 1. **테스트 이름은 명확하게**
117
+ - ✅ `'macOS에서 open 명령어를 사용해야 함'`
118
+ - ❌ `'테스트 1'`
119
+
120
+ 2. **하나의 테스트는 하나의 것만 검증**
121
+ ```typescript
122
+ // ✅ 좋은 예
123
+ it('URL 형식이 올바른지 검증', () => {
124
+ expect(url).toMatch(/^https:\/\//);
125
+ });
126
+
127
+ // ❌ 나쁜 예
128
+ it('URL과 이메일 검증', () => {
129
+ expect(url).toMatch(/^https:\/\//);
130
+ expect(email).toMatch(/@/);
131
+ });
132
+ ```
133
+
134
+ 3. **테스트 독립성 유지**
135
+ - 각 테스트는 독립적으로 실행 가능해야 함
136
+ - `beforeEach`로 초기화, `afterEach`로 정리
137
+
138
+ 4. **에러 케이스도 테스트**
139
+ ```typescript
140
+ it('잘못된 입력에 대해 에러를 발생시켜야 함', () => {
141
+ expect(() => someFunction(null)).toThrow();
142
+ });
143
+ ```
144
+
145
+ ## 문제 해결
146
+
147
+ ### 테스트가 실행되지 않을 때
148
+
149
+ 1. 의존성 설치 확인
150
+ ```bash
151
+ bun install
152
+ ```
153
+
154
+ 2. TypeScript 컴파일 확인
155
+ ```bash
156
+ bun run typecheck
157
+ ```
158
+
159
+ 3. Vitest 설정 확인
160
+ - `vitest.config.ts` 파일 존재 여부
161
+ - 테스트 파일 경로 확인
162
+
163
+ ### 모킹이 작동하지 않을 때
164
+
165
+ 1. `vi.mock()`이 import 문 앞에 있는지 확인
166
+ 2. 모킹된 모듈의 경로가 정확한지 확인
167
+ 3. `vi.clearAllMocks()` 사용하여 초기화
168
+
169
+ ## 추가 리소스
170
+
171
+ - [Vitest 공식 문서](https://vitest.dev/)
172
+ - [Testing Best Practices](https://github.com/goldbergyoni/javascript-testing-best-practices)
173
+
package/docs/usage.md ADDED
@@ -0,0 +1,112 @@
1
+ # tylersong CLI 사용법
2
+
3
+ ## 설치
4
+
5
+ NPX를 통해 바로 실행할 수 있습니다:
6
+
7
+ ```bash
8
+ npx tylersong
9
+ ```
10
+
11
+ ## 옵션
12
+
13
+ ### 기본 사용법
14
+
15
+ ```bash
16
+ npx tylersong
17
+ ```
18
+
19
+ 인터랙티브 모드로 개발자 프로필 정보를 확인할 수 있습니다.
20
+
21
+ ### 명령어 옵션
22
+
23
+ - `-V, --version`: 버전 정보 출력
24
+ - `-g, --github`: GitHub 프로필을 브라우저에서 바로 열기
25
+ - `-h, --help`: 도움말 출력
26
+
27
+ ### 예시
28
+
29
+ ```bash
30
+ # 버전 확인
31
+ npx tylersong --version
32
+
33
+ # GitHub 프로필 열기
34
+ npx tylersong --github
35
+
36
+ # 도움말 보기
37
+ npx tylersong --help
38
+ ```
39
+
40
+ ## 개발
41
+
42
+ ### TypeScript로 개발
43
+
44
+ #### NPM 사용
45
+
46
+ ```bash
47
+ # 의존성 설치
48
+ npm install
49
+
50
+ # 개발 모드 실행
51
+ npm run dev
52
+
53
+ # 빌드
54
+ npm run build
55
+
56
+ # 빌드된 파일 실행
57
+ npm start
58
+ ```
59
+
60
+ #### Bun 사용 (더 빠른 성능!)
61
+
62
+ ```bash
63
+ # 의존성 설치
64
+ bun install
65
+
66
+ # 개발 모드 실행 (TypeScript 직접 실행)
67
+ bun run dev:bun
68
+
69
+ # 빌드 (bun 번들러 사용)
70
+ bun run build:bun
71
+
72
+ # 빌드된 파일 실행
73
+ bun run start:bun
74
+
75
+ # 또는 TypeScript 소스를 직접 실행
76
+ bun run src/index.ts
77
+ ```
78
+
79
+ ### 프로젝트 구조
80
+
81
+ ```
82
+ tylersong/
83
+ ├── src/
84
+ │ └── index.ts # 메인 TypeScript 소스 파일
85
+ ├── dist/ # 빌드된 JavaScript 파일들
86
+ ├── docs/ # 문서 파일들
87
+ ├── package.json # 패키지 설정
88
+ └── tsconfig.json # TypeScript 설정
89
+ ```
90
+
91
+ ## 기능
92
+
93
+ - 🚀 개발자 프로필 정보 표시
94
+ - 💻 GitHub 프로필 바로 열기
95
+ - ✨ 터미널에서 색상과 아이콘으로 꾸며진 출력
96
+ - 📧 이메일 연락처 정보 제공
97
+
98
+ ## 배포
99
+
100
+ 자세한 배포 가이드는 [docs/deployment.md](./deployment.md)를 참고해주세요.
101
+
102
+ ### 빠른 배포
103
+
104
+ ```bash
105
+ # 패치 버전 업데이트 후 자동 배포
106
+ npm version patch
107
+ git push origin main
108
+
109
+ # 또는 태그와 함께 즉시 배포
110
+ npm version patch
111
+ git push origin main --tags
112
+ ```
@@ -0,0 +1,176 @@
1
+ # GitHub Actions 워크플로우 가이드
2
+
3
+ 이 프로젝트는 모듈화된 GitHub Actions 워크플로우를 사용합니다.
4
+
5
+ ## 📁 워크플로우 구조
6
+
7
+ ```
8
+ .github/
9
+ ├── actions/
10
+ │ └── setup-runtime/ # 재사용 가능한 컴포지트 액션
11
+ │ └── action.yml
12
+ └── workflows/
13
+ ├── ci.yml # 지속적 통합 (테스트, 빌드, 린팅)
14
+ └── publish.yml # NPM 배포
15
+ ```
16
+
17
+ ## 🔄 워크플로우 설명
18
+
19
+ ### 1. Continuous Integration (ci.yml)
20
+
21
+ **트리거:**
22
+
23
+ - `main`, `develop` 브랜치로의 push
24
+ - `main`, `develop` 브랜치로의 pull request
25
+
26
+ **작업:**
27
+
28
+ - **test**: Node.js와 Bun 환경에서 테스트 실행
29
+ - **build**: TypeScript 빌드 및 아티팩트 업로드
30
+ - **lint**: 코드 품질 검사, 타입 체크, 보안 감사
31
+
32
+ ### 2. Publish to NPM (publish.yml)
33
+
34
+ **트리거:**
35
+
36
+ - `main` 브랜치로의 push
37
+ - `v*` 형태의 태그 push
38
+ - CI 워크플로우 완료 후 (workflow_run)
39
+
40
+ **작업:**
41
+
42
+ - **check-ci**: CI 워크플로우 성공 여부 확인
43
+ - **version-check**: 버전 변경 사항 확인
44
+ - **publish**: NPM에 패키지 배포
45
+ - **notify**: 배포 결과 알림
46
+
47
+ ## 🔧 재사용 가능한 액션
48
+
49
+ ### setup-runtime
50
+
51
+ Node.js 또는 Bun 런타임 환경을 설정하고 의존성을 설치합니다.
52
+
53
+ **입력:**
54
+
55
+ - `runtime`: 'node' 또는 'bun'
56
+ - `node-version`: Node.js 버전 (기본: '20.x')
57
+ - `bun-version`: Bun 버전 (기본: 'latest')
58
+
59
+ **사용 예:**
60
+
61
+ ```yaml
62
+ - name: Setup runtime environment
63
+ uses: ./.github/actions/setup-runtime
64
+ with:
65
+ runtime: node
66
+ ```
67
+
68
+ ### discord-notify
69
+
70
+ Discord 웹후크를 통해 알림을 전송합니다.
71
+
72
+ **입력:**
73
+
74
+ - `webhook-url`: Discord 웹후크 URL (필수)
75
+ - `status`: 워크플로우 상태 (success/failure/cancelled)
76
+ - `title`: 알림 제목 (필수)
77
+ - `description`: 알림 설명
78
+ - `color`: Embed 색상 (hex, # 제외)
79
+ - `fields`: 추가 필드 (JSON 배열)
80
+
81
+ **사용 예:**
82
+
83
+ ```yaml
84
+ - name: Send Discord notification
85
+ uses: ./.github/actions/discord-notify
86
+ with:
87
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
88
+ status: success
89
+ title: "배포 완료!"
90
+ description: "새 버전이 배포되었습니다."
91
+ ```
92
+
93
+ ## 🚀 배포 시나리오
94
+
95
+ ### 자동 배포 (main 브랜치)
96
+
97
+ 1. 코드를 `main` 브랜치에 push
98
+ 2. CI 워크플로우 실행 (테스트, 빌드, 린팅)
99
+ 3. CI 성공 시 배포 워크플로우 실행
100
+ 4. package.json 버전이 NPM과 다르면 자동 배포
101
+
102
+ ### 즉시 배포 (태그)
103
+
104
+ 1. 버전 태그 push (`v1.0.5` 등)
105
+ 2. CI와 배포 워크플로우 동시 실행
106
+ 3. CI 성공 시 무조건 NPM에 배포
107
+ 4. GitHub Release 자동 생성
108
+
109
+ ### Pull Request
110
+
111
+ 1. PR 생성 시 CI 워크플로우만 실행
112
+ 2. 테스트, 빌드, 린팅 검사
113
+ 3. 배포는 실행되지 않음 (dry-run만)
114
+
115
+ ## 📋 워크플로우 흐름도
116
+
117
+ ```mermaid
118
+ graph TD
119
+ A[코드 Push/PR] --> B[CI 워크플로우]
120
+ B --> C{테스트 통과?}
121
+ C -->|실패| D[❌ 빌드 실패]
122
+ C -->|성공| E[빌드 & 아티팩트 업로드]
123
+ E --> F{main 브랜치?}
124
+ F -->|No| G[✅ CI 완료]
125
+ F -->|Yes| H[배포 워크플로우]
126
+ H --> I{버전 변경?}
127
+ I -->|No| J[⏭️ 배포 스킵]
128
+ I -->|Yes| K[NPM 배포]
129
+ K --> L[✅ 배포 완료]
130
+ ```
131
+
132
+ ## ⚙️ 환경 설정
133
+
134
+ ### GitHub Secrets
135
+
136
+ Repository Settings → Secrets and variables → Actions에서 설정:
137
+
138
+ - `NPM_TOKEN`: NPM 액세스 토큰
139
+ - `DISCORD_WEBHOOK_URL`: Discord 웹후크 URL (선택사항)
140
+ - `GITHUB_TOKEN`: 자동으로 제공됨 (Release 생성용)
141
+
142
+ > 💬 **Discord 알림**: 자세한 설정은 [Discord 설정 가이드](./discord-setup.md)를 참고하세요.
143
+
144
+ ### GitHub Environment
145
+
146
+ `production` 환경 설정을 통해 배포 시 추가 보안 검토 가능
147
+
148
+ ## 🔍 모니터링
149
+
150
+ ### CI 상태 확인
151
+
152
+ - Actions 탭에서 각 워크플로우 실행 상태 확인
153
+ - 실패 시 로그를 통해 문제 진단
154
+
155
+ ### 배포 상태 확인
156
+
157
+ - NPM: https://www.npmjs.com/package/tylersong
158
+ - GitHub Releases: 태그 배포 시 자동 생성
159
+ - 알림 작업에서 성공/실패 상태 확인
160
+
161
+ ## 🛠️ 트러블슈팅
162
+
163
+ ### CI 실패 시
164
+
165
+ 1. 테스트 실패: 코드 수정 후 재푸시
166
+ 2. 빌드 실패: TypeScript 오류 확인
167
+ 3. 린팅 실패: 코드 품질 이슈 해결
168
+
169
+ ### 배포 실패 시
170
+
171
+ 1. NPM_TOKEN 확인
172
+ 2. package.json 버전 확인
173
+ 3. 빌드 아티팩트 존재 확인
174
+ 4. NPM 패키지명 중복 확인
175
+
176
+ 이 구조를 통해 각 워크플로우의 책임이 명확히 분리되어 유지보수가 쉬워집니다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tylersong",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "나의 CLI 자기소개 도구",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -19,7 +19,14 @@
19
19
  "dev:bun": "bun run src/index.ts",
20
20
  "start": "node dist/index.js",
21
21
  "start:bun": "bun run dist/index.js",
22
- "test": "echo \"No test specified\"",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage",
25
+ "lint": "eslint src test --ext .ts",
26
+ "lint:fix": "eslint src test --ext .ts --fix",
27
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
28
+ "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
29
+ "typecheck": "tsc --noEmit",
23
30
  "prepublishOnly": "npm run build"
24
31
  },
25
32
  "dependencies": {
@@ -31,8 +38,14 @@
31
38
  "@types/chalk": "^0.4.31",
32
39
  "@types/inquirer": "^9.0.8",
33
40
  "@types/node": "^24.1.0",
41
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
42
+ "@typescript-eslint/parser": "^6.21.0",
43
+ "@vitest/coverage-v8": "^1.3.1",
44
+ "eslint": "^8.56.0",
45
+ "prettier": "^3.2.5",
34
46
  "ts-node": "^10.9.2",
35
47
  "tsx": "^4.20.3",
36
- "typescript": "^5.9.2"
48
+ "typescript": "^5.9.2",
49
+ "vitest": "^1.3.1"
37
50
  }
38
51
  }
package/src/index.ts CHANGED
@@ -4,6 +4,9 @@ import inquirer from "inquirer";
4
4
  import chalk from "chalk";
5
5
  import { program } from "commander";
6
6
  import { exec } from "child_process";
7
+ import { readFileSync } from "fs";
8
+ import { fileURLToPath } from "url";
9
+ import { dirname, join } from "path";
7
10
 
8
11
  interface ProgramOptions {
9
12
  github?: boolean;
@@ -13,6 +16,28 @@ interface UserAnswer {
13
16
  showInfo: boolean;
14
17
  }
15
18
 
19
+ interface PackageJson {
20
+ version: string;
21
+ name: string;
22
+ description: string;
23
+ }
24
+
25
+ // package.json에서 버전 동적으로 읽기
26
+ const getPackageVersion = (): string => {
27
+ try {
28
+ const __filename = fileURLToPath(import.meta.url);
29
+ const __dirname = dirname(__filename);
30
+ const packagePath = join(__dirname, "..", "package.json");
31
+ const packageJson = JSON.parse(
32
+ readFileSync(packagePath, "utf-8")
33
+ ) as PackageJson;
34
+ return packageJson.version;
35
+ } catch (error) {
36
+ console.error(chalk.red("⚠️ 버전 정보를 읽을 수 없습니다."));
37
+ return "1.0.0";
38
+ }
39
+ };
40
+
16
41
  const openUrl = (url: string): void => {
17
42
  const command: string =
18
43
  process.platform === "win32"
@@ -20,10 +45,16 @@ const openUrl = (url: string): void => {
20
45
  : process.platform === "darwin"
21
46
  ? "open"
22
47
  : "xdg-open";
23
- exec(`${command} ${url}`);
48
+
49
+ exec(`${command} ${url}`, (error) => {
50
+ if (error) {
51
+ console.error(chalk.red("❌ 브라우저를 열 수 없습니다."));
52
+ console.error(chalk.yellow(`수동으로 방문해주세요: ${url}`));
53
+ }
54
+ });
24
55
  };
25
56
 
26
- program.version("1.0.9");
57
+ program.version(getPackageVersion());
27
58
 
28
59
  program.option("-g, --github", "GitHub 프로필 열기");
29
60
 
@@ -37,10 +68,11 @@ if (Object.keys(options).length > 0) {
37
68
  }
38
69
 
39
70
  const main = async (): Promise<void> => {
40
- // 도움말 옵션 체크
41
- if (process.argv.includes("--help")) {
42
- console.log(
43
- chalk.yellow(`
71
+ try {
72
+ // 도움말 옵션 체크
73
+ if (process.argv.includes("--help")) {
74
+ console.log(
75
+ chalk.yellow(`
44
76
  ╭───────────────────────────────╮
45
77
  │ 사용 방법 │
46
78
  ├───────────────────────────────┤
@@ -52,45 +84,58 @@ const main = async (): Promise<void> => {
52
84
  │ -h, --help 도움말 │
53
85
  ╰───────────────────────────────╯
54
86
  `)
55
- );
56
- process.exit(0);
57
- }
87
+ );
88
+ process.exit(0);
89
+ }
90
+
91
+ // 사용자 질문
92
+ const answer: UserAnswer = await inquirer.prompt([
93
+ {
94
+ type: "confirm",
95
+ name: "showInfo",
96
+ message: chalk.cyan("✨ 저에 대해 궁금하신가요?"),
97
+ default: true,
98
+ },
99
+ ]);
58
100
 
59
- // 사용자 질문
60
- const answer: UserAnswer = await inquirer.prompt([
61
- {
62
- type: "confirm",
63
- name: "showInfo",
64
- message: chalk.cyan("✨ 저에 대해 궁금하신가요?"),
65
- default: true,
66
- },
67
- ]);
68
-
69
- if (answer.showInfo) {
70
- console.log(
71
- chalk.bold.cyan(`
101
+ if (answer.showInfo) {
102
+ console.log(
103
+ chalk.bold.cyan(`
72
104
  ╭───────────────────────────────────────────╮
73
105
  │ 🚀 Developer Profile 🚀 │
74
106
  ╰───────────────────────────────────────────╯
75
107
 
76
108
  `) +
77
- chalk.green(`👋 안녕하세요!
109
+ chalk.green(`👋 안녕하세요!
78
110
  💻 개발자 ${chalk.bold.yellow("송민성")}입니다.
79
111
 
80
112
  `) +
81
- chalk.hex("#fef6e1")(`📌 Github: ${chalk.underline.whiteBright(
82
- `https://github.com/alstjd0051`
83
- )}
113
+ chalk.hex("#fef6e1")(`📌 Github: ${chalk.underline.whiteBright(
114
+ `https://github.com/alstjd0051`
115
+ )}
84
116
  `) +
85
- chalk.blue(`✉️ E-mail: ${chalk.underline.whiteBright(
86
- `wsc7202@gmail.com`
87
- )}
117
+ chalk.blue(`✉️ E-mail: ${chalk.underline.whiteBright(
118
+ `wsc7202@gmail.com`
119
+ )}
88
120
  `) +
89
- chalk.magenta(`
121
+ chalk.magenta(`
90
122
  💡 더 많은 정보는 ${chalk.bold("--help")}를 통해 확인해주세요.
91
123
  `)
92
- );
124
+ );
125
+ }
126
+ } catch (error) {
127
+ console.error(chalk.red("\n❌ 오류가 발생했습니다:"));
128
+ if (error instanceof Error) {
129
+ console.error(chalk.yellow(error.message));
130
+ } else {
131
+ console.error(chalk.yellow("알 수 없는 오류가 발생했습니다."));
132
+ }
133
+ process.exit(1);
93
134
  }
94
135
  };
95
136
 
96
- main().catch(console.error);
137
+ main().catch((error) => {
138
+ console.error(chalk.red("\n❌ 치명적인 오류가 발생했습니다:"));
139
+ console.error(error);
140
+ process.exit(1);
141
+ });