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

1""" 

2Validate 명령 - Buf lint 및 format check 실행. 

3""" 

4 

5from __future__ import annotations 

6 

7import argparse 

8import subprocess 

9 

10from ...utils import ask_choice, ask_confirm 

11from ..models import ProtoConfig 

12from ..utils import Color, LogLevel, colorize, log, log_header 

13 

14 

15def buf_lint(config: ProtoConfig) -> bool: 

16 """Buf lint 실행""" 

17 log("Buf lint 실행 중...", LogLevel.STEP) 

18 

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 

34 

35 

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 

74 

75 

76def buf_breaking(config: ProtoConfig, against: str = "main") -> bool: 

77 """Buf breaking change 검사""" 

78 log(f"Breaking change 검사 중 (vs {against})...", LogLevel.STEP) 

79 

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 

100 

101 

102def execute(args: argparse.Namespace, config: ProtoConfig) -> int: 

103 """Validate 명령 실행""" 

104 log_header("Proto 파일 검증") 

105 

106 results = [] 

107 

108 # 1. Lint 검사 

109 if not args.skip_lint: 

110 lint_pass = buf_lint(config) 

111 results.append(("Lint", lint_pass)) 

112 

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)) 

117 

118 # 3. Breaking change 검사 

119 if args.breaking: 

120 breaking_pass = buf_breaking(config, against=args.against) 

121 results.append(("Breaking", breaking_pass)) 

122 

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}") 

132 

133 all_passed = all(passed for _, passed in results) 

134 

135 if all_passed: 

136 log("\n🎉 모든 검증 통과!", LogLevel.SUCCESS) 

137 return 0 

138 else: 

139 log("\n⚠️ 일부 검증 실패", LogLevel.WARNING) 

140 return 1 

141 

142 

143def execute_interactive(config: ProtoConfig) -> int: 

144 """대화형 모드로 validate 명령 실행""" 

145 log_header("Proto 파일 검증") 

146 

147 # 검사 옵션 선택 

148 skip_lint = not ask_confirm("Lint 검사를 수행하시겠습니까?", default=True) 

149 skip_format = not ask_confirm("Format 검사를 수행하시겠습니까?", default=True) 

150 

151 fix = False 

152 if not skip_format: 

153 fix = ask_confirm("Format 오류를 자동으로 수정하시겠습니까?", default=False) 

154 

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 

165 

166 against = ask_text("브랜치 이름을 입력하세요", default="main") 

167 

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) 

176 

177 

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 )