@gracefullight/validate-branch 0.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.en.md ADDED
@@ -0,0 +1,423 @@
1
+ # @gracefullight/validate-branch
2
+
3
+ > Git branch name validation tool with custom regexp support
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@gracefullight/validate-branch.svg)](https://www.npmjs.com/package/@gracefullight/validate-branch)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ [한국어](./README.md) | **English**
9
+
10
+ ## Features
11
+
12
+ - **Flexible Regular Expression Pattern** - Use default pattern or custom regular expression
13
+ - **Current Branch Detection** - Automatically detect current branch name from Git repository
14
+ - **Config File Support** - Load patterns from `branch.config.{ts,js,mjs}` files
15
+ - **CLI Tool** - Use directly from command line
16
+ - **Detailed Error Messages** - Clear error descriptions on validation failure
17
+ - **Full TypeScript Support** - Complete TypeScript type definitions
18
+
19
+ ## Default Pattern
20
+
21
+ By default, the following branch names are allowed:
22
+
23
+ - `main` / `master` - Main branches
24
+ - `develop` / `stage` - Development/staging branches
25
+ - `feature/.*` - Feature branches (e.g., `feature/login`, `feature/user-auth`)
26
+ - `fix/.*` - Bug fix branches (e.g., `fix/memory-leak`, `fix/typo`)
27
+ - `hotfix/.*` - Hotfix branches (e.g., `hotfix/critical-bug`)
28
+ - `release/.*` - Release branches (e.g., `release/v1.0.0`)
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ # Using pnpm
34
+ pnpm add @gracefullight/validate-branch
35
+
36
+ # Using npm
37
+ npm install @gracefullight/validate-branch
38
+
39
+ # Using yarn
40
+ yarn add @gracefullight/validate-branch
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### CLI Usage
46
+
47
+ ```bash
48
+ # Validate current branch
49
+ npx validate-branch
50
+
51
+ # Validate specific branch
52
+ npx validate-branch --test feature/new-feature
53
+
54
+ # Use custom regular expression
55
+ npx validate-branch --regexp "^(main|develop|feature/.+)$"
56
+ ```
57
+
58
+ ### Programmatic Usage
59
+
60
+ #### Validate with Default Pattern
61
+
62
+ ```typescript
63
+ import { validateBranchName, validateWithDetails } from "@gracefullight/validate-branch";
64
+
65
+ // Simple boolean return
66
+ const isValid = validateBranchName("feature/new-component");
67
+ console.log(isValid); // true
68
+
69
+ // Detailed result return
70
+ const result = validateWithDetails("feature/new-component");
71
+ console.log(result);
72
+ // { valid: true, branchName: "feature/new-component" }
73
+ ```
74
+
75
+ #### Validate with Custom Regexp
76
+
77
+ ```typescript
78
+ import { validateBranchName } from "@gracefullight/validate-branch";
79
+
80
+ const customPattern = "^(main|develop|story/.+|bugfix/.+)$";
81
+ const isValid = validateBranchName("story/US-123-add-user", { customRegexp: customPattern });
82
+ console.log(isValid); // true
83
+ ```
84
+
85
+ #### Get Current Branch Name
86
+
87
+ ```typescript
88
+ import { getCurrentBranchName } from "@gracefullight/validate-branch";
89
+
90
+ const currentBranch = await getCurrentBranchName();
91
+ console.log(currentBranch); // "feature/new-component" or null
92
+ ```
93
+
94
+ #### Load Config File
95
+
96
+ ```typescript
97
+ import { loadConfig } from "@gracefullight/validate-branch";
98
+
99
+ const config = await loadConfig();
100
+ console.log(config);
101
+ // {
102
+ // pattern: "^(main|develop|feature/.+)$",
103
+ // description: "Project branch naming rules"
104
+ // }
105
+ ```
106
+
107
+ ## Configuration File
108
+
109
+ Create a `branch.config.ts` or `branch.config.js` file in your project root to define custom patterns.
110
+
111
+ ### TypeScript Config (`branch.config.ts`)
112
+
113
+ ```typescript
114
+ import type { Config } from "@gracefullight/validate-branch";
115
+
116
+ const config: Config = {
117
+ pattern: "^(main|develop|feature/.+|fix/.+|refactor/.+)$",
118
+ description: "Project branch naming rules",
119
+ };
120
+
121
+ export default config;
122
+ ```
123
+
124
+ ### JavaScript Config (`branch.config.js`)
125
+
126
+ ```javascript
127
+ module.exports = {
128
+ pattern: "^(main|develop|feature/.+|fix/.+)$",
129
+ description: "Project branch naming rules",
130
+ };
131
+ ```
132
+
133
+ ## API Reference
134
+
135
+ ### `validateBranchName(branchName, options?)`
136
+
137
+ Check if a branch name is valid.
138
+
139
+ ```typescript
140
+ interface ValidateBranchNameOptions {
141
+ customRegexp?: string;
142
+ preset?: "gitflow" | "jira";
143
+ }
144
+
145
+ function validateBranchName(
146
+ branchName: string,
147
+ options?: ValidateBranchNameOptions
148
+ ): boolean
149
+ ```
150
+
151
+ **Parameters:**
152
+ - `branchName`: Branch name to validate
153
+ - `options`: Optional, validation options object
154
+ - `customRegexp`: Custom regular expression string
155
+ - `preset`: Preset pattern (`gitflow` or `jira`, default: `gitflow`)
156
+
157
+ **Returns:** Validity status
158
+
159
+ ---
160
+
161
+ ### `validateWithDetails(branchName, options?)`
162
+
163
+ Return detailed validation result.
164
+
165
+ ```typescript
166
+ function validateWithDetails(
167
+ branchName: string,
168
+ options?: ValidateBranchNameOptions
169
+ ): ValidationResult
170
+ ```
171
+
172
+ **Returns:**
173
+ ```typescript
174
+ {
175
+ valid: boolean;
176
+ branchName: string;
177
+ error?: string; // Error message on failure
178
+ }
179
+ ```
180
+
181
+ **Example:**
182
+ ```typescript
183
+ const result = validateWithDetails("invalid-branch");
184
+ // {
185
+ // valid: false,
186
+ // branchName: "invalid-branch",
187
+ // error: 'Branch name "invalid-branch" does not match pattern: /^(main|master|...)$/'
188
+ // }
189
+ ```
190
+
191
+ ---
192
+
193
+ ### `getCurrentBranchName()`
194
+
195
+ Get current Git branch name.
196
+
197
+ ```typescript
198
+ function getCurrentBranchName(): Promise<string | null>
199
+ ```
200
+
201
+ **Returns:** Current branch name, `null` if not in a Git repository
202
+
203
+ ---
204
+
205
+ ### `loadConfig()`
206
+
207
+ Load project configuration file.
208
+
209
+ ```typescript
210
+ function loadConfig(): Promise<Config | null>
211
+ ```
212
+
213
+ **Config file search order:**
214
+ 1. `branch.config.ts`
215
+ 2. `branch.config.mts`
216
+ 3. `branch.config.js`
217
+ 4. `branch.config.cjs`
218
+ 5. `branch.config.mjs`
219
+
220
+ **Returns:** Configuration object, `null` if no config file exists
221
+
222
+ ---
223
+
224
+ ## Type Definitions
225
+
226
+ ### `Config`
227
+
228
+ ```typescript
229
+ interface Config {
230
+ pattern?: string; // Single regex pattern
231
+ patterns?: string[]; // Multiple regex patterns (planned)
232
+ description?: string; // Config description
233
+ }
234
+ ```
235
+
236
+ ### `ValidationResult`
237
+
238
+ ```typescript
239
+ interface ValidationResult {
240
+ valid: boolean;
241
+ branchName: string;
242
+ error?: string;
243
+ }
244
+ ```
245
+
246
+ ## Examples
247
+
248
+ ### Use with Git Hooks
249
+
250
+ #### With Husky in `package.json`
251
+
252
+ ```json
253
+ {
254
+ "husky": {
255
+ "hooks": {
256
+ "pre-commit": "validate-branch"
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ #### Direct Git Hooks
263
+
264
+ ```bash
265
+ #!/bin/sh
266
+ # .git/hooks/pre-commit
267
+
268
+ npx validate-branch
269
+ if [ $? -ne 0 ]; then
270
+ echo "Branch name does not follow naming conventions."
271
+ exit 1
272
+ fi
273
+ ```
274
+
275
+ ### Use with GitHub Actions
276
+
277
+ ```yaml
278
+ name: Branch Validation
279
+
280
+ on:
281
+ pull_request:
282
+ types: [opened, synchronize]
283
+
284
+ jobs:
285
+ validate-branch:
286
+ runs-on: ubuntu-latest
287
+ steps:
288
+ - uses: actions/checkout@v4
289
+
290
+ - name: Setup Node.js
291
+ uses: actions/setup-node@v4
292
+ with:
293
+ node-version: "20"
294
+
295
+ - name: Install validate-branch
296
+ run: npm install -g @gracefullight/validate-branch
297
+
298
+ - name: Validate branch name
299
+ run: validate-branch --test ${{ github.head_ref }}
300
+ ```
301
+
302
+ ### Use in CI/CD Pipeline
303
+
304
+ ```yaml
305
+ # .github/workflows/ci.yml
306
+
307
+ jobs:
308
+ test:
309
+ runs-on: ubuntu-latest
310
+ steps:
311
+ - name: Checkout
312
+ uses: actions/checkout@v4
313
+
314
+ - name: Validate branch name
315
+ run: npx validate-branch
316
+ ```
317
+
318
+ ### Use with ESLint Plugin
319
+
320
+ ```javascript
321
+ // .eslintrc.js
322
+ const { execSync } = require("child_process");
323
+
324
+ module.exports = {
325
+ // ... other config
326
+ rules: {
327
+ "custom/validate-branch": {
328
+ meta: {
329
+ type: "suggestion",
330
+ docs: {
331
+ description: "Validate git branch name",
332
+ },
333
+ },
334
+ create(context) {
335
+ return {
336
+ Program() {
337
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
338
+ encoding: "utf-8",
339
+ }).trim();
340
+
341
+ const { validateBranchName } = require("@gracefullight/validate-branch");
342
+
343
+ if (!validateBranchName(branch)) {
344
+ context.report({
345
+ node: {},
346
+ message: `Invalid branch name: ${branch}`,
347
+ });
348
+ }
349
+ },
350
+ };
351
+ },
352
+ },
353
+ },
354
+ };
355
+ ```
356
+
357
+ ## Development
358
+
359
+ ### Setup
360
+
361
+ ```bash
362
+ # Clone repository
363
+ git clone https://github.com/gracefullight/saju.git
364
+ cd packages/validate-branch
365
+
366
+ # Install dependencies
367
+ pnpm install
368
+
369
+ # Build
370
+ pnpm build
371
+
372
+ # Test
373
+ pnpm test
374
+
375
+ # Lint
376
+ pnpm lint
377
+
378
+ # Format
379
+ pnpm lint:fix
380
+ ```
381
+
382
+ ### Test CLI Locally
383
+
384
+ ```bash
385
+ # After building, test CLI locally
386
+ node bin/validate-branch.js --test feature/new-feature
387
+
388
+ # Test with custom pattern
389
+ node bin/validate-branch.js --test invalid-name --regexp "^(main|develop)$"
390
+ ```
391
+
392
+ ## Contributing
393
+
394
+ Contributions are welcome! Please feel free to submit a Pull Request.
395
+
396
+ 1. Fork repository
397
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
398
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
399
+ 4. Push to branch (`git push origin feature/amazing-feature`)
400
+ 5. Open a Pull Request
401
+
402
+ ### Guidelines
403
+
404
+ - Write tests for new features
405
+ - Maintain or improve code coverage
406
+ - Follow existing code style (enforced by Biome)
407
+ - Update documentation as needed
408
+
409
+ ## License
410
+
411
+ MIT © [gracefullight](https://github.com/gracefullight)
412
+
413
+ ## Related Projects
414
+
415
+ - [isomorphic-git](https://isomorphic-git.org/) - Git implementation library
416
+ - [Commander.js](https://commander.js/) - Node.js CLI framework
417
+ - [Chalk](https://chalk.js.org/) - Terminal string styling
418
+
419
+ ## Support
420
+
421
+ - [Documentation](https://github.com/gracefullight/saju#readme)
422
+ - [Issue Tracker](https://github.com/gracefullight/saju/issues)
423
+ - [Discussions](https://github.com/gracefullight/saju/discussions)
package/README.md ADDED
@@ -0,0 +1,423 @@
1
+ # @gracefullight/validate-branch
2
+
3
+ > 커스텀 정규식을 지원하는 Git 브랜치 이름 유효성 검사 도구
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@gracefullight/validate-branch.svg)](https://www.npmjs.com/package/@gracefullight/validate-branch)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **한국어** | [English](./README.en.md)
9
+
10
+ ## 주요 기능
11
+
12
+ - **유연한 정규식 패턴** - 기본 패턴 또는 커스텀 정규식 사용
13
+ - **현재 브랜치 감지** - Git 저장소에서 현재 브랜치 이름 자동 감지
14
+ - **설정 파일 지원** - `branch.config.{ts,js,mjs}` 파일에서 패턴 로드
15
+ - **CLI 도구** - 명령줄에서 직접 사용 가능
16
+ - **상세한 오류 메시지** - 실패 시 명확한 오류 설명 제공
17
+ - **타입스크립트 지원** - 완전한 TypeScript 타입 정의
18
+
19
+ ## 기본 패턴
20
+
21
+ 기본적으로 다음 브랜치 이름이 허용됩니다:
22
+
23
+ - `main` / `master` - 메인 브랜치
24
+ - `develop` / `stage` - 개발/스테이지 브랜치
25
+ - `feature/.*` - 기능 브랜치 (예: `feature/login`, `feature/user-auth`)
26
+ - `fix/.*` - 버그 수정 브랜치 (예: `fix/memory-leak`, `fix/typo`)
27
+ - `hotfix/.*` - 핫픽스 브랜치 (예: `hotfix/critical-bug`)
28
+ - `release/.*` - 릴리스 브랜치 (예: `release/v1.0.0`)
29
+
30
+ ## 설치
31
+
32
+ ```bash
33
+ # pnpm 사용
34
+ pnpm add @gracefullight/validate-branch
35
+
36
+ # npm 사용
37
+ npm install @gracefullight/validate-branch
38
+
39
+ # yarn 사용
40
+ yarn add @gracefullight/validate-branch
41
+ ```
42
+
43
+ ## 사용법
44
+
45
+ ### CLI 사용
46
+
47
+ ```bash
48
+ # 현재 브랜치 검증
49
+ npx validate-branch
50
+
51
+ # 특정 브랜치 검증
52
+ npx validate-branch --test feature/new-feature
53
+
54
+ # 커스텀 정규식 사용
55
+ npx validate-branch --regexp "^(main|develop|feature/.+)$"
56
+ ```
57
+
58
+ ### 프로그래밍 방식 사용
59
+
60
+ #### 기본 패턴으로 검증
61
+
62
+ ```typescript
63
+ import { validateBranchName, validateWithDetails } from "@gracefullight/validate-branch";
64
+
65
+ // 간단한 boolean 반환
66
+ const isValid = validateBranchName("feature/new-component");
67
+ console.log(isValid); // true
68
+
69
+ // 상세한 결과 반환
70
+ const result = validateWithDetails("feature/new-component");
71
+ console.log(result);
72
+ // { valid: true, branchName: "feature/new-component" }
73
+ ```
74
+
75
+ #### 커스텀 정규식 사용
76
+
77
+ ```typescript
78
+ import { validateBranchName } from "@gracefullight/validate-branch";
79
+
80
+ const customPattern = "^(main|develop|story/.+|bugfix/.+)$";
81
+ const isValid = validateBranchName("story/US-123-add-user", { customRegexp: customPattern });
82
+ console.log(isValid); // true
83
+ ```
84
+
85
+ #### 현재 브랜치 이름 가져오기
86
+
87
+ ```typescript
88
+ import { getCurrentBranchName } from "@gracefullight/validate-branch";
89
+
90
+ const currentBranch = await getCurrentBranchName();
91
+ console.log(currentBranch); // "feature/new-component" 또는 null
92
+ ```
93
+
94
+ #### 설정 파일 로드
95
+
96
+ ```typescript
97
+ import { loadConfig } from "@gracefullight/validate-branch";
98
+
99
+ const config = await loadConfig();
100
+ console.log(config);
101
+ // {
102
+ // pattern: "^(main|develop|feature/.+)$",
103
+ // description: "프로젝트 브랜치 네이밍 규칙"
104
+ // }
105
+ ```
106
+
107
+ ## 설정 파일
108
+
109
+ 프로젝트 루트에 `branch.config.ts` 또는 `branch.config.js` 파일을 생성하여 커스텀 패턴을 정의하세요.
110
+
111
+ ### TypeScript 설정 (`branch.config.ts`)
112
+
113
+ ```typescript
114
+ import type { Config } from "@gracefullight/validate-branch";
115
+
116
+ const config: Config = {
117
+ pattern: "^(main|develop|feature/.+|fix/.+|refactor/.+)$",
118
+ description: "프로젝트 브랜치 네이밍 규칙",
119
+ };
120
+
121
+ export default config;
122
+ ```
123
+
124
+ ### JavaScript 설정 (`branch.config.js`)
125
+
126
+ ```javascript
127
+ module.exports = {
128
+ pattern: "^(main|develop|feature/.+|fix/.+)$",
129
+ description: "프로젝트 브랜치 네이밍 규칙",
130
+ };
131
+ ```
132
+
133
+ ## API 레퍼런스
134
+
135
+ ### `validateBranchName(branchName, options?)`
136
+
137
+ 브랜치 이름이 유효한지 확인합니다.
138
+
139
+ ```typescript
140
+ interface ValidateBranchNameOptions {
141
+ customRegexp?: string;
142
+ preset?: "gitflow" | "jira";
143
+ }
144
+
145
+ function validateBranchName(
146
+ branchName: string,
147
+ options?: ValidateBranchNameOptions
148
+ ): boolean
149
+ ```
150
+
151
+ **매개변수:**
152
+ - `branchName`: 검증할 브랜치 이름
153
+ - `options`: 선택사항, 검증 옵션 객체
154
+ - `customRegexp`: 커스텀 정규식 문자열
155
+ - `preset`: 프리셋 패턴 (`gitflow` 또는 `jira`, 기본값: `gitflow`)
156
+
157
+ **반환값:** 유효 여부
158
+
159
+ ---
160
+
161
+ ### `validateWithDetails(branchName, options?)`
162
+
163
+ 상세한 검증 결과를 반환합니다.
164
+
165
+ ```typescript
166
+ function validateWithDetails(
167
+ branchName: string,
168
+ options?: ValidateBranchNameOptions
169
+ ): ValidationResult
170
+ ```
171
+
172
+ **반환값:**
173
+ ```typescript
174
+ {
175
+ valid: boolean;
176
+ branchName: string;
177
+ error?: string; // 실패 시 오류 메시지
178
+ }
179
+ ```
180
+
181
+ **예시:**
182
+ ```typescript
183
+ const result = validateWithDetails("invalid-branch");
184
+ // {
185
+ // valid: false,
186
+ // branchName: "invalid-branch",
187
+ // error: 'Branch name "invalid-branch" does not match pattern: /^(main|master|...)$/'
188
+ // }
189
+ ```
190
+
191
+ ---
192
+
193
+ ### `getCurrentBranchName()`
194
+
195
+ 현재 Git 브랜치 이름을 가져옵니다.
196
+
197
+ ```typescript
198
+ function getCurrentBranchName(): Promise<string | null>
199
+ ```
200
+
201
+ **반환값:** 현재 브랜치 이름, Git 저장소가 아닌 경우 `null`
202
+
203
+ ---
204
+
205
+ ### `loadConfig()`
206
+
207
+ 프로젝트 설정 파일을 로드합니다.
208
+
209
+ ```typescript
210
+ function loadConfig(): Promise<Config | null>
211
+ ```
212
+
213
+ **설정 파일 탐색 순서:**
214
+ 1. `branch.config.ts`
215
+ 2. `branch.config.mts`
216
+ 3. `branch.config.js`
217
+ 4. `branch.config.cjs`
218
+ 5. `branch.config.mjs`
219
+
220
+ **반환값:** 설정 객체 또는 설정 파일이 없는 경우 `null`
221
+
222
+ ---
223
+
224
+ ## 타입 정의
225
+
226
+ ### `Config`
227
+
228
+ ```typescript
229
+ interface Config {
230
+ pattern?: string; // 단일 정규식 패턴
231
+ patterns?: string[]; // 다중 정규식 패턴 (예정)
232
+ description?: string; // 설정 설명
233
+ }
234
+ ```
235
+
236
+ ### `ValidationResult`
237
+
238
+ ```typescript
239
+ interface ValidationResult {
240
+ valid: boolean;
241
+ branchName: string;
242
+ error?: string;
243
+ }
244
+ ```
245
+
246
+ ## 예제
247
+
248
+ ### Git Hooks와 함께 사용
249
+
250
+ #### `package.json`에서 husky 설정
251
+
252
+ ```json
253
+ {
254
+ "husky": {
255
+ "hooks": {
256
+ "pre-commit": "validate-branch"
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ #### 직접 Git Hooks 사용
263
+
264
+ ```bash
265
+ #!/bin/sh
266
+ # .git/hooks/pre-commit
267
+
268
+ npx validate-branch
269
+ if [ $? -ne 0 ]; then
270
+ echo "브랜치 이름이 규칙에 맞지 않습니다."
271
+ exit 1
272
+ fi
273
+ ```
274
+
275
+ ### GitHub Actions와 함께 사용
276
+
277
+ ```yaml
278
+ name: Branch Validation
279
+
280
+ on:
281
+ pull_request:
282
+ types: [opened, synchronize]
283
+
284
+ jobs:
285
+ validate-branch:
286
+ runs-on: ubuntu-latest
287
+ steps:
288
+ - uses: actions/checkout@v4
289
+
290
+ - name: Setup Node.js
291
+ uses: actions/setup-node@v4
292
+ with:
293
+ node-version: "20"
294
+
295
+ - name: Install validate-branch
296
+ run: npm install -g @gracefullight/validate-branch
297
+
298
+ - name: Validate branch name
299
+ run: validate-branch --test ${{ github.head_ref }}
300
+ ```
301
+
302
+ ### CI/CD 파이프라인에서 사용
303
+
304
+ ```yaml
305
+ # .github/workflows/ci.yml
306
+
307
+ jobs:
308
+ test:
309
+ runs-on: ubuntu-latest
310
+ steps:
311
+ - name: Checkout
312
+ uses: actions/checkout@v4
313
+
314
+ - name: Validate branch name
315
+ run: npx validate-branch
316
+ ```
317
+
318
+ ### ESLint 플러그인과 함께 사용
319
+
320
+ ```javascript
321
+ // .eslintrc.js
322
+ const { execSync } = require("child_process");
323
+
324
+ module.exports = {
325
+ // ... 기본 설정
326
+ rules: {
327
+ "custom/validate-branch": {
328
+ meta: {
329
+ type: "suggestion",
330
+ docs: {
331
+ description: "Validate git branch name",
332
+ },
333
+ },
334
+ create(context) {
335
+ return {
336
+ Program() {
337
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
338
+ encoding: "utf-8",
339
+ }).trim();
340
+
341
+ const { validateBranchName } = require("@gracefullight/validate-branch");
342
+
343
+ if (!validateBranchName(branch)) {
344
+ context.report({
345
+ node: {},
346
+ message: `Invalid branch name: ${branch}`,
347
+ });
348
+ }
349
+ },
350
+ };
351
+ },
352
+ },
353
+ },
354
+ };
355
+ ```
356
+
357
+ ## 개발
358
+
359
+ ### 설정
360
+
361
+ ```bash
362
+ # 저장소 클론
363
+ git clone https://github.com/gracefullight/saju.git
364
+ cd packages/validate-branch
365
+
366
+ # 의존성 설치
367
+ pnpm install
368
+
369
+ # 빌드
370
+ pnpm build
371
+
372
+ # 테스트
373
+ pnpm test
374
+
375
+ # 린트
376
+ pnpm lint
377
+
378
+ # 포맷
379
+ pnpm lint:fix
380
+ ```
381
+
382
+ ### 로컬에서 CLI 테스트
383
+
384
+ ```bash
385
+ # 빌드 후 로컬에서 CLI 테스트
386
+ node bin/validate-branch.js --test feature/new-feature
387
+
388
+ # 커스텀 패턴 테스트
389
+ node bin/validate-branch.js --test invalid-name --regexp "^(main|develop)$"
390
+ ```
391
+
392
+ ## 기여하기
393
+
394
+ 기여를 환영합니다! Pull Request를 자유롭게 제출해주세요.
395
+
396
+ 1. 저장소 포크
397
+ 2. feature 브랜치 생성 (`git checkout -b feature/amazing-feature`)
398
+ 3. 변경사항 커밋 (`git commit -m 'Add some amazing feature'`)
399
+ 4. 브랜치에 푸시 (`git push origin feature/amazing-feature`)
400
+ 5. Pull Request 오픈
401
+
402
+ ### 가이드라인
403
+
404
+ - 새 기능에 대한 테스트 작성
405
+ - 코드 커버리지 유지 또는 개선
406
+ - 기존 코드 스타일 따르기 (Biome으로 강제)
407
+ - 필요에 따라 문서 업데이트
408
+
409
+ ## 라이선스
410
+
411
+ MIT © [gracefullight](https://github.com/gracefullight)
412
+
413
+ ## 관련 프로젝트
414
+
415
+ - [isomorphic-git](https://isomorphic-git.org/) - Git 구현 라이브러리
416
+ - [Commander.js](https://commander.js/) - Node.js CLI 프레임워크
417
+ - [Chalk](https://chalk.js.org/) - 터미널 문자열 스타일링
418
+
419
+ ## 지원
420
+
421
+ - [문서](https://github.com/gracefullight/saju#readme)
422
+ - [이슈 트래커](https://github.com/gracefullight/saju/issues)
423
+ - [토론](https://github.com/gracefullight/saju/discussions)
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command, Option } from "commander";
5
+ import chalk from "chalk";
6
+
7
+ // src/validate-branch-name.ts
8
+ import * as git from "isomorphic-git";
9
+ import * as fs from "fs";
10
+ import { cwd } from "process";
11
+ var GITFLOW_PATTERN = /^(main|master|develop|stage|feature\/[A-Za-z0-9_-]+|fix\/[A-Za-z0-9_-]+|hotfix\/[A-Za-z0-9_-]+|release\/[A-Za-z0-9_.-]+)$/;
12
+ var JIRA_PATTERN = /^(main|master|develop|stage|[A-Z]+-[0-9]+)$/;
13
+ async function getCurrentBranchName() {
14
+ try {
15
+ const branch = await git.currentBranch({
16
+ fs,
17
+ dir: cwd(),
18
+ fullname: false
19
+ });
20
+ return branch ?? null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function validateWithDetails(branchName, options) {
26
+ const { customRegexp, preset = "gitflow" } = options ?? {};
27
+ if (customRegexp) {
28
+ const pattern2 = new RegExp(customRegexp);
29
+ if (pattern2.test(branchName)) {
30
+ return { valid: true, branchName };
31
+ }
32
+ return {
33
+ valid: false,
34
+ branchName,
35
+ error: `Branch name "${branchName}" does not match pattern: ${pattern2.source}`
36
+ };
37
+ }
38
+ const pattern = preset === "jira" ? JIRA_PATTERN : GITFLOW_PATTERN;
39
+ if (pattern.test(branchName)) {
40
+ return { valid: true, branchName };
41
+ }
42
+ return {
43
+ valid: false,
44
+ branchName,
45
+ error: `Branch name "${branchName}" does not match pattern: ${pattern.source}`
46
+ };
47
+ }
48
+
49
+ // src/load-config.ts
50
+ import { readFile } from "fs/promises";
51
+ import { resolve } from "path";
52
+ import { cwd as cwd2 } from "process";
53
+ var CONFIG_FILES = [
54
+ "branch.config.ts",
55
+ "branch.config.mts",
56
+ "branch.config.js",
57
+ "branch.config.cjs",
58
+ "branch.config.mjs"
59
+ ];
60
+ async function loadConfig() {
61
+ for (const file of CONFIG_FILES) {
62
+ const configPath = resolve(cwd2(), file);
63
+ try {
64
+ await readFile(configPath, "utf-8");
65
+ if (file.endsWith(".ts") || file.endsWith(".mts")) {
66
+ const { default: config } = await import(`file://${configPath}?ts=${Date.now()}`);
67
+ return config;
68
+ }
69
+ if (file.endsWith(".mjs")) {
70
+ const { default: config } = await import(`file://${configPath}?ts=${Date.now()}`);
71
+ return config;
72
+ }
73
+ if (file.endsWith(".js") || file.endsWith(".cjs")) {
74
+ const { default: config } = await import(configPath);
75
+ return config;
76
+ }
77
+ } catch (error) {
78
+ if (error.code !== "ENOENT") {
79
+ console.error(`Error loading config file ${file}:`, error);
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ // src/cli.ts
87
+ var SUCCESS_CODE = 0;
88
+ var FAILED_CODE = 1;
89
+ var program = new Command();
90
+ program.name("validate-branch").description("Git branch name validation tool").version("0.1.0", "-v, --version");
91
+ program.addOption(
92
+ new Option("-t, --test <branch>", "Test target branch name (uses current branch by default)")
93
+ );
94
+ program.addOption(
95
+ new Option("-r, --regexp <regexp>", "Custom regular expression to test branch name")
96
+ );
97
+ program.addOption(
98
+ new Option("-p, --preset <preset>", "Use preset pattern (gitflow or jira)").choices([
99
+ "gitflow",
100
+ "jira"
101
+ ])
102
+ );
103
+ async function main() {
104
+ program.parse(process.argv);
105
+ const options = program.opts();
106
+ const branch = options.test || await getCurrentBranchName();
107
+ if (!branch) {
108
+ console.error(chalk.red.bold("Error: Not a git repository\n"));
109
+ process.exit(FAILED_CODE);
110
+ }
111
+ const config = await loadConfig();
112
+ const customRegexp = options.regexp || config?.pattern;
113
+ const preset = options.preset || config?.preset || "gitflow";
114
+ console.log(chalk.cyan.bold(`
115
+ Validating branch: ${chalk.yellow(branch)}
116
+ `));
117
+ console.log(chalk.gray(`Preset: ${preset}
118
+ `));
119
+ if (config?.description) {
120
+ console.log(chalk.gray(`Config: ${config.description}
121
+ `));
122
+ }
123
+ try {
124
+ const result = validateWithDetails(branch, { customRegexp, preset });
125
+ if (result.valid) {
126
+ console.log(chalk.green.bold("\u2713 Valid branch name\n"));
127
+ process.exit(SUCCESS_CODE);
128
+ } else {
129
+ console.error(chalk.red.bold("\u2717 Invalid branch name\n"));
130
+ console.error(chalk.yellow(result.error));
131
+ process.exit(FAILED_CODE);
132
+ }
133
+ } catch (error) {
134
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
135
+ console.error(chalk.red.bold(`
136
+ Error: ${errorMessage}
137
+ `));
138
+ process.exit(FAILED_CODE);
139
+ }
140
+ }
141
+ main();
@@ -0,0 +1,23 @@
1
+ type Preset = "gitflow" | "jira";
2
+ interface ValidateBranchNameOptions {
3
+ customRegexp?: string;
4
+ preset?: Preset;
5
+ }
6
+ declare function validateBranchName(branchName: string, options?: ValidateBranchNameOptions): boolean;
7
+ declare function getCurrentBranchName(): Promise<string | null>;
8
+ interface ValidationResult {
9
+ valid: boolean;
10
+ branchName: string;
11
+ error?: string;
12
+ }
13
+ declare function validateWithDetails(branchName: string, options?: ValidateBranchNameOptions): ValidationResult;
14
+
15
+ interface Config {
16
+ pattern?: string;
17
+ patterns?: string[];
18
+ preset?: Preset;
19
+ description?: string;
20
+ }
21
+ declare function loadConfig(): Promise<Config | null>;
22
+
23
+ export { type Config, type Preset, type ValidateBranchNameOptions, type ValidationResult, getCurrentBranchName, loadConfig, validateBranchName, validateWithDetails };
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ // src/validate-branch-name.ts
2
+ import * as git from "isomorphic-git";
3
+ import * as fs from "fs";
4
+ import { cwd } from "process";
5
+ var GITFLOW_PATTERN = /^(main|master|develop|stage|feature\/[A-Za-z0-9_-]+|fix\/[A-Za-z0-9_-]+|hotfix\/[A-Za-z0-9_-]+|release\/[A-Za-z0-9_.-]+)$/;
6
+ var JIRA_PATTERN = /^(main|master|develop|stage|[A-Z]+-[0-9]+)$/;
7
+ function validateBranchName(branchName, options) {
8
+ const { customRegexp, preset = "gitflow" } = options ?? {};
9
+ if (customRegexp) {
10
+ return new RegExp(customRegexp).test(branchName);
11
+ }
12
+ const pattern = preset === "jira" ? JIRA_PATTERN : GITFLOW_PATTERN;
13
+ return pattern.test(branchName);
14
+ }
15
+ async function getCurrentBranchName() {
16
+ try {
17
+ const branch = await git.currentBranch({
18
+ fs,
19
+ dir: cwd(),
20
+ fullname: false
21
+ });
22
+ return branch ?? null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ function validateWithDetails(branchName, options) {
28
+ const { customRegexp, preset = "gitflow" } = options ?? {};
29
+ if (customRegexp) {
30
+ const pattern2 = new RegExp(customRegexp);
31
+ if (pattern2.test(branchName)) {
32
+ return { valid: true, branchName };
33
+ }
34
+ return {
35
+ valid: false,
36
+ branchName,
37
+ error: `Branch name "${branchName}" does not match pattern: ${pattern2.source}`
38
+ };
39
+ }
40
+ const pattern = preset === "jira" ? JIRA_PATTERN : GITFLOW_PATTERN;
41
+ if (pattern.test(branchName)) {
42
+ return { valid: true, branchName };
43
+ }
44
+ return {
45
+ valid: false,
46
+ branchName,
47
+ error: `Branch name "${branchName}" does not match pattern: ${pattern.source}`
48
+ };
49
+ }
50
+
51
+ // src/load-config.ts
52
+ import { readFile } from "fs/promises";
53
+ import { resolve } from "path";
54
+ import { cwd as cwd2 } from "process";
55
+ var CONFIG_FILES = [
56
+ "branch.config.ts",
57
+ "branch.config.mts",
58
+ "branch.config.js",
59
+ "branch.config.cjs",
60
+ "branch.config.mjs"
61
+ ];
62
+ async function loadConfig() {
63
+ for (const file of CONFIG_FILES) {
64
+ const configPath = resolve(cwd2(), file);
65
+ try {
66
+ await readFile(configPath, "utf-8");
67
+ if (file.endsWith(".ts") || file.endsWith(".mts")) {
68
+ const { default: config } = await import(`file://${configPath}?ts=${Date.now()}`);
69
+ return config;
70
+ }
71
+ if (file.endsWith(".mjs")) {
72
+ const { default: config } = await import(`file://${configPath}?ts=${Date.now()}`);
73
+ return config;
74
+ }
75
+ if (file.endsWith(".js") || file.endsWith(".cjs")) {
76
+ const { default: config } = await import(configPath);
77
+ return config;
78
+ }
79
+ } catch (error) {
80
+ if (error.code !== "ENOENT") {
81
+ console.error(`Error loading config file ${file}:`, error);
82
+ }
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ export {
88
+ getCurrentBranchName,
89
+ loadConfig,
90
+ validateBranchName,
91
+ validateWithDetails
92
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@gracefullight/validate-branch",
3
+ "version": "0.1.0",
4
+ "description": "Git branch name validation tool with custom regexp support",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "validate-branch": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "keywords": [
23
+ "git",
24
+ "branch",
25
+ "validation",
26
+ "cli"
27
+ ],
28
+ "author": {
29
+ "name": "Eunkwang Shin",
30
+ "email": "gracefullight.dev@gmail.com"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "http://github.com/gracefullight/workspace/packages/validate-branch"
35
+ },
36
+ "license": "MIT",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.10.4",
42
+ "tsup": "^8.5.1",
43
+ "tsx": "^4.19.2",
44
+ "typescript": "^5.7.3",
45
+ "vitest": "^4.0.7"
46
+ },
47
+ "dependencies": {
48
+ "chalk": "^5.4.1",
49
+ "commander": "^12.1.0",
50
+ "isomorphic-git": "^1.30.1"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "dev": "tsx src/cli.ts",
55
+ "test": "vitest",
56
+ "lint": "biome check src",
57
+ "lint:fix": "biome check --write src",
58
+ "format": "biome format --write src"
59
+ }
60
+ }