Coverage for src / mysingle / cli / protos / commands / validate.py: 0%
101 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-02 00:58 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-02 00:58 +0900
1"""
2Validate 명령 - Buf lint 및 format check 실행.
3"""
5from __future__ import annotations
7import argparse
8import subprocess
10from ...utils import ask_choice, ask_confirm
11from ..models import ProtoConfig
12from ..utils import Color, LogLevel, colorize, log, log_header
15def buf_lint(config: ProtoConfig) -> bool:
16 """Buf lint 실행"""
17 log("Buf lint 실행 중...", LogLevel.STEP)
19 try:
20 subprocess.run(
21 ["buf", "lint"],
22 cwd=config.repo_root,
23 check=True,
24 )
25 log("✅ Lint 통과", LogLevel.SUCCESS)
26 return True
27 except subprocess.CalledProcessError:
28 log("❌ Lint 실패", LogLevel.ERROR)
29 return False
30 except FileNotFoundError:
31 log("Buf가 설치되어 있지 않습니다.", LogLevel.ERROR)
32 log("설치 방법: https://buf.build/docs/installation", LogLevel.INFO)
33 return False
36def buf_format_check(config: ProtoConfig, fix: bool = False) -> bool:
37 """Buf format check 실행"""
38 if fix:
39 log("Buf format 자동 수정 중...", LogLevel.STEP)
40 try:
41 subprocess.run(
42 ["buf", "format", "-w"],
43 cwd=config.repo_root,
44 check=True,
45 )
46 log("✅ Format 수정 완료", LogLevel.SUCCESS)
47 return True
48 except subprocess.CalledProcessError:
49 log("❌ Format 수정 실패", LogLevel.ERROR)
50 return False
51 except FileNotFoundError:
52 log("Buf가 설치되어 있지 않습니다.", LogLevel.ERROR)
53 return False
54 else:
55 log("Buf format check 실행 중...", LogLevel.STEP)
56 try:
57 subprocess.run(
58 ["buf", "format", "-d", "--exit-code"],
59 cwd=config.repo_root,
60 check=True,
61 )
62 log("✅ Format 통과", LogLevel.SUCCESS)
63 return True
64 except subprocess.CalledProcessError:
65 log("❌ Format 검사 실패 (수정이 필요합니다)", LogLevel.ERROR)
66 log(
67 f"자동 수정: {colorize('proto-cli validate --fix', Color.BRIGHT_YELLOW)}",
68 LogLevel.INFO,
69 )
70 return False
71 except FileNotFoundError:
72 log("Buf가 설치되어 있지 않습니다.", LogLevel.ERROR)
73 return False
76def buf_breaking(config: ProtoConfig, against: str = "main") -> bool:
77 """Buf breaking change 검사"""
78 log(f"Breaking change 검사 중 (vs {against})...", LogLevel.STEP)
80 try:
81 subprocess.run(
82 [
83 "buf",
84 "breaking",
85 "--against",
86 f"https://github.com/Br0therDan/grpc-protos.git#branch={against}",
87 ],
88 cwd=config.repo_root,
89 check=True,
90 )
91 log("✅ Breaking change 없음", LogLevel.SUCCESS)
92 return True
93 except subprocess.CalledProcessError:
94 log("⚠️ Breaking change 감지됨", LogLevel.WARNING)
95 log("주의: Breaking change는 버전 메이저 업데이트가 필요합니다.", LogLevel.INFO)
96 return False
97 except FileNotFoundError:
98 log("Buf가 설치되어 있지 않습니다.", LogLevel.ERROR)
99 return False
102def execute(args: argparse.Namespace, config: ProtoConfig) -> int:
103 """Validate 명령 실행"""
104 log_header("Proto 파일 검증")
106 results = []
108 # 1. Lint 검사
109 if not args.skip_lint:
110 lint_pass = buf_lint(config)
111 results.append(("Lint", lint_pass))
113 # 2. Format 검사
114 if not args.skip_format:
115 format_pass = buf_format_check(config, fix=args.fix)
116 results.append(("Format", format_pass))
118 # 3. Breaking change 검사
119 if args.breaking:
120 breaking_pass = buf_breaking(config, against=args.against)
121 results.append(("Breaking", breaking_pass))
123 # 결과 요약
124 log_header("검증 결과")
125 for name, passed in results:
126 status = (
127 colorize("✅ 통과", Color.GREEN)
128 if passed
129 else colorize("❌ 실패", Color.RED)
130 )
131 print(f"{name:15} {status}")
133 all_passed = all(passed for _, passed in results)
135 if all_passed:
136 log("\n🎉 모든 검증 통과!", LogLevel.SUCCESS)
137 return 0
138 else:
139 log("\n⚠️ 일부 검증 실패", LogLevel.WARNING)
140 return 1
143def execute_interactive(config: ProtoConfig) -> int:
144 """대화형 모드로 validate 명령 실행"""
145 log_header("Proto 파일 검증")
147 # 검사 옵션 선택
148 skip_lint = not ask_confirm("Lint 검사를 수행하시겠습니까?", default=True)
149 skip_format = not ask_confirm("Format 검사를 수행하시겠습니까?", default=True)
151 fix = False
152 if not skip_format:
153 fix = ask_confirm("Format 오류를 자동으로 수정하시겠습니까?", default=False)
155 breaking = ask_confirm("Breaking change 검사를 수행하시겠습니까?", default=False)
156 against = "main"
157 if breaking:
158 against = ask_choice(
159 "비교 대상 브랜치를 선택하세요",
160 ["main", "develop", "custom"],
161 default="main",
162 )
163 if against == "custom":
164 from ...utils import ask_text
166 against = ask_text("브랜치 이름을 입력하세요", default="main")
168 args = argparse.Namespace(
169 skip_lint=skip_lint,
170 skip_format=skip_format,
171 fix=fix,
172 breaking=breaking,
173 against=against,
174 )
175 return execute(args, config)
178def setup_parser(parser: argparse.ArgumentParser) -> None:
179 """Validate 명령 파서 설정"""
180 parser.add_argument(
181 "--skip-lint",
182 action="store_true",
183 help="Lint 검사 건너뛰기",
184 )
185 parser.add_argument(
186 "--skip-format",
187 action="store_true",
188 help="Format 검사 건너뛰기",
189 )
190 parser.add_argument(
191 "--fix",
192 action="store_true",
193 help="Format 오류 자동 수정",
194 )
195 parser.add_argument(
196 "--breaking",
197 action="store_true",
198 help="Breaking change 검사 수행",
199 )
200 parser.add_argument(
201 "--against",
202 default="main",
203 help="Breaking change 비교 대상 브랜치 (기본값: main)",
204 )