Coverage for src/jtech_installer/installer/vscode_configurator.py: 92%

119 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-20 15:10 -0300

1"""Configurador automático do VS Code para JTECH™ Core.""" 

2 

3import json 

4from typing import Any, Dict, List 

5 

6from ..core.models import InstallationConfig, TeamType 

7 

8 

9class VSCodeConfigurator: 

10 """Configura automaticamente o VS Code para o framework JTECH™ Core.""" 

11 

12 def __init__(self, config: InstallationConfig, dry_run: bool = False): 

13 """ 

14 Inicializa o configurador do VS Code. 

15 

16 Args: 

17 config: Configuração de instalação 

18 dry_run: Se True, simula operações sem modificar arquivos 

19 """ 

20 self.config = config 

21 self.dry_run = dry_run 

22 self.vscode_dir = config.project_path / ".vscode" 

23 

24 def configure_all(self) -> Dict[str, bool]: 

25 """ 

26 Executa todas as configurações do VS Code. 

27 

28 Returns: 

29 Dict com status de cada configuração aplicada 

30 """ 

31 results = {} 

32 

33 # Criar diretório .vscode se não existir 

34 if not self.dry_run: 

35 self.vscode_dir.mkdir(parents=True, exist_ok=True) 

36 

37 # Configurar settings.json 

38 results["settings"] = self._configure_settings() 

39 

40 # Configurar extensões recomendadas 

41 results["extensions"] = self._configure_extensions() 

42 

43 # Configurar tasks.json para framework 

44 results["tasks"] = self._configure_tasks() 

45 

46 # Configurar launch.json se aplicável 

47 results["launch"] = self._configure_launch() 

48 

49 return results 

50 

51 def _configure_settings(self) -> bool: 

52 """ 

53 Configura o arquivo settings.json do VS Code. 

54 

55 Returns: 

56 True se configurado com sucesso 

57 """ 

58 try: 

59 # Garantir que o diretório existe 

60 if not self.dry_run: 

61 self.vscode_dir.mkdir(parents=True, exist_ok=True) 

62 

63 settings_file = self.vscode_dir / "settings.json" 

64 

65 # Configurações base para JTECH™ Core 

66 base_settings = { 

67 # GitHub Copilot Chat 

68 "github.copilot.enable": { 

69 "*": True, 

70 "yaml": True, 

71 "markdown": True, 

72 "python": True, 

73 "javascript": True, 

74 "typescript": True, 

75 }, 

76 "github.copilot.chat.enable": True, 

77 "github.copilot.chat.localeOverride": "pt-BR", 

78 # Markdown 

79 "markdown.preview.scrollEditorWithPreview": True, 

80 "markdown.preview.scrollPreviewWithEditor": True, 

81 "markdown.extension.toc.levels": "2..6", 

82 # Files 

83 "files.associations": { 

84 "*.chatmode.md": "markdown", 

85 "core-config.yml": "yaml", 

86 "*.jtech.yml": "yaml", 

87 }, 

88 # Explorer 

89 "explorer.fileNesting.enabled": True, 

90 "explorer.fileNesting.patterns": { 

91 "*.md": "${capture}.*.md", 

92 "core-config.yml": "core-*.yml,*.core.yml", 

93 }, 

94 # Terminal 

95 "terminal.integrated.defaultProfile.linux": "bash", 

96 "terminal.integrated.cwd": "${workspaceFolder}", 

97 # JTECH™ Core específico 

98 "jtech.framework.autoload": True, 

99 "jtech.chatmodes.enabled": True, 

100 "jtech.agents.autodetect": True, 

101 } 

102 

103 # Configurações específicas por tipo de equipe 

104 team_settings = self._get_team_specific_settings() 

105 base_settings.update(team_settings) 

106 

107 # Merge com configurações existentes se o arquivo já existir 

108 if settings_file.exists() and not self.dry_run: 

109 with open(settings_file, "r", encoding="utf-8") as f: 

110 try: 

111 existing_settings = json.load(f) 

112 # Merge inteligente - preserva configurações existentes 

113 base_settings = {**existing_settings, **base_settings} 

114 except json.JSONDecodeError: 

115 # Se arquivo está corrompido, usar apenas nossas configurações 

116 pass 

117 

118 if not self.dry_run: 

119 with open(settings_file, "w", encoding="utf-8") as f: 

120 json.dump(base_settings, f, indent=2, ensure_ascii=False) 

121 

122 return True 

123 

124 except Exception as e: 

125 print(f"Erro ao configurar settings.json: {e}") 

126 return False 

127 

128 def _configure_extensions(self) -> bool: 

129 """ 

130 Configura extensões recomendadas no extensions.json. 

131 

132 Returns: 

133 True se configurado com sucesso 

134 """ 

135 try: 

136 # Garantir que o diretório existe 

137 if not self.dry_run: 

138 self.vscode_dir.mkdir(parents=True, exist_ok=True) 

139 

140 extensions_file = self.vscode_dir / "extensions.json" 

141 

142 # Extensões base para JTECH™ Core 

143 base_extensions = [ 

144 # GitHub Copilot 

145 "github.copilot", 

146 "github.copilot-chat", 

147 # Markdown 

148 "yzhang.markdown-all-in-one", 

149 "davidanson.vscode-markdownlint", 

150 "bierner.markdown-mermaid", 

151 # YAML 

152 "redhat.vscode-yaml", 

153 # Git 

154 "eamodio.gitlens", 

155 # Formatação 

156 "esbenp.prettier-vscode", 

157 "ms-vscode.vscode-json", 

158 # Úteis gerais 

159 "ms-vscode.remote-containers", 

160 "ms-vscode-remote.remote-ssh", 

161 ] 

162 

163 # Extensões específicas por tipo de equipe 

164 team_extensions = self._get_team_specific_extensions() 

165 all_extensions = base_extensions + team_extensions 

166 

167 extensions_config = { 

168 "recommendations": all_extensions, 

169 "unwantedRecommendations": [ 

170 "ms-vscode.vscode-typescript-next", 

171 "hookyqr.beautify", 

172 ], 

173 } 

174 

175 # Merge com recomendações existentes 

176 if extensions_file.exists() and not self.dry_run: 

177 with open(extensions_file, "r", encoding="utf-8") as f: 

178 try: 

179 existing_config = json.load(f) 

180 existing_recs = existing_config.get( 

181 "recommendations", [] 

182 ) 

183 # Combinar sem duplicatas 

184 combined_recs = list( 

185 set(existing_recs + all_extensions) 

186 ) 

187 extensions_config["recommendations"] = combined_recs 

188 except json.JSONDecodeError: 

189 pass 

190 

191 if not self.dry_run: 

192 with open(extensions_file, "w", encoding="utf-8") as f: 

193 json.dump( 

194 extensions_config, f, indent=2, ensure_ascii=False 

195 ) 

196 

197 return True 

198 

199 except Exception as e: 

200 print(f"Erro ao configurar extensions.json: {e}") 

201 return False 

202 

203 def _configure_tasks(self) -> bool: 

204 """ 

205 Configura tarefas do VS Code no tasks.json. 

206 

207 Returns: 

208 True se configurado com sucesso 

209 """ 

210 try: 

211 # Garantir que o diretório existe 

212 if not self.dry_run: 

213 self.vscode_dir.mkdir(parents=True, exist_ok=True) 

214 

215 tasks_file = self.vscode_dir / "tasks.json" 

216 

217 tasks_config = { 

218 "version": "2.0.0", 

219 "tasks": [ 

220 { 

221 "label": "JTECH: Validate Framework", 

222 "type": "shell", 

223 "command": "echo", 

224 "args": ["Framework validation would run here"], 

225 "group": "build", 

226 "presentation": { 

227 "echo": True, 

228 "reveal": "always", 

229 "focus": False, 

230 "panel": "shared", 

231 }, 

232 "problemMatcher": [], 

233 }, 

234 { 

235 "label": "JTECH: Generate Documentation", 

236 "type": "shell", 

237 "command": "echo", 

238 "args": ["Documentation generation would run here"], 

239 "group": "build", 

240 "presentation": { 

241 "echo": True, 

242 "reveal": "always", 

243 "focus": False, 

244 "panel": "shared", 

245 }, 

246 }, 

247 ], 

248 } 

249 

250 # Adicionar tarefas específicas por tipo de equipe 

251 team_tasks = self._get_team_specific_tasks() 

252 tasks_config["tasks"].extend(team_tasks) 

253 

254 if not self.dry_run: 

255 with open(tasks_file, "w", encoding="utf-8") as f: 

256 json.dump(tasks_config, f, indent=2, ensure_ascii=False) 

257 

258 return True 

259 

260 except Exception as e: 

261 print(f"Erro ao configurar tasks.json: {e}") 

262 return False 

263 

264 def _configure_launch(self) -> bool: 

265 """ 

266 Configura launch.json se aplicável. 

267 

268 Returns: 

269 True se configurado com sucesso 

270 """ 

271 try: 

272 # Garantir que o diretório existe 

273 if not self.dry_run: 

274 self.vscode_dir.mkdir(parents=True, exist_ok=True) 

275 

276 # Por enquanto, apenas criar estrutura básica 

277 launch_file = self.vscode_dir / "launch.json" 

278 

279 launch_config = { 

280 "version": "0.2.0", 

281 "configurations": [ 

282 { 

283 "name": "JTECH Debug Mode", 

284 "type": "node", 

285 "request": "launch", 

286 "program": "${workspaceFolder}/debug.js", 

287 "console": "integratedTerminal", 

288 "skipFiles": ["<node_internals>/**"], 

289 } 

290 ], 

291 } 

292 

293 # Só criar se não existir (não sobrescrever configurações de debug) 

294 if not launch_file.exists() and not self.dry_run: 

295 with open(launch_file, "w", encoding="utf-8") as f: 

296 json.dump(launch_config, f, indent=2, ensure_ascii=False) 

297 

298 return True 

299 

300 except Exception as e: 

301 print(f"Erro ao configurar launch.json: {e}") 

302 return False 

303 

304 def _get_team_specific_settings(self) -> Dict[str, Any]: 

305 """Retorna configurações específicas por tipo de equipe.""" 

306 team_settings = { 

307 TeamType.ALL: { 

308 "python.defaultInterpreterPath": "./venv/bin/python", 

309 "python.formatting.provider": "black", 

310 "python.linting.enabled": True, 

311 "python.linting.pylintEnabled": True, 

312 "javascript.preferences.includePackageJsonAutoImports": "auto", 

313 "typescript.preferences.includePackageJsonAutoImports": "auto", 

314 }, 

315 TeamType.FULLSTACK: { 

316 "python.defaultInterpreterPath": "./venv/bin/python", 

317 "python.formatting.provider": "black", 

318 "javascript.preferences.includePackageJsonAutoImports": "auto", 

319 "typescript.preferences.includePackageJsonAutoImports": "auto", 

320 "emmet.includeLanguages": { 

321 "javascript": "javascriptreact", 

322 "typescript": "typescriptreact", 

323 }, 

324 }, 

325 TeamType.NO_UI: { 

326 "python.defaultInterpreterPath": "./venv/bin/python", 

327 "python.formatting.provider": "black", 

328 "python.linting.enabled": True, 

329 "python.linting.pylintEnabled": True, 

330 "rest-client.requestTimeout": 30000, 

331 }, 

332 TeamType.IDE_MINIMAL: { 

333 "editor.minimap.enabled": False, 

334 "workbench.activityBar.visible": True, 

335 "editor.wordWrap": "on", 

336 }, 

337 } 

338 

339 return team_settings.get(self.config.team_type, {}) 

340 

341 def _get_team_specific_extensions(self) -> List[str]: 

342 """Retorna extensões específicas por tipo de equipe.""" 

343 team_extensions = { 

344 TeamType.ALL: [ 

345 "ms-python.python", 

346 "ms-python.black-formatter", 

347 "ms-python.pylint", 

348 "ms-vscode.vscode-typescript-next", 

349 "bradlc.vscode-tailwindcss", 

350 "ms-vscode.vscode-docker", 

351 ], 

352 TeamType.FULLSTACK: [ 

353 "ms-python.python", 

354 "ms-python.black-formatter", 

355 "ms-vscode.vscode-typescript-next", 

356 "bradlc.vscode-tailwindcss", 

357 "ms-vscode.vscode-react-native", 

358 "formulahendry.auto-rename-tag", 

359 ], 

360 TeamType.NO_UI: [ 

361 "ms-python.python", 

362 "ms-python.black-formatter", 

363 "ms-python.pylint", 

364 "humao.rest-client", 

365 "ms-vscode.vscode-docker", 

366 "mongodb.mongodb-vscode", 

367 ], 

368 TeamType.IDE_MINIMAL: [ 

369 "ms-python.python", 

370 "ms-vscode.vscode-json", 

371 ], 

372 } 

373 

374 return team_extensions.get(self.config.team_type, []) 

375 

376 def _get_team_specific_tasks(self) -> List[Dict[str, Any]]: 

377 """Retorna tarefas específicas por tipo de equipe.""" 

378 if self.config.team_type == TeamType.ALL: 

379 return [ 

380 { 

381 "label": "Run Tests", 

382 "type": "shell", 

383 "command": "python", 

384 "args": ["-m", "pytest"], 

385 "group": "test", 

386 }, 

387 { 

388 "label": "Build Frontend", 

389 "type": "shell", 

390 "command": "npm", 

391 "args": ["run", "build"], 

392 "group": "build", 

393 }, 

394 ] 

395 elif self.config.team_type == TeamType.FULLSTACK: 

396 return [ 

397 { 

398 "label": "Run Tests", 

399 "type": "shell", 

400 "command": "python", 

401 "args": ["-m", "pytest"], 

402 "group": "test", 

403 }, 

404 { 

405 "label": "Start Dev Server", 

406 "type": "shell", 

407 "command": "npm", 

408 "args": ["run", "dev"], 

409 "group": "build", 

410 }, 

411 ] 

412 elif self.config.team_type == TeamType.NO_UI: 

413 return [ 

414 { 

415 "label": "Run API Tests", 

416 "type": "shell", 

417 "command": "python", 

418 "args": ["-m", "pytest", "tests/api/"], 

419 "group": "test", 

420 }, 

421 { 

422 "label": "Start API Server", 

423 "type": "shell", 

424 "command": "python", 

425 "args": ["-m", "uvicorn", "main:app", "--reload"], 

426 "group": "build", 

427 }, 

428 ] 

429 

430 return [] 

431 

432 def validate_configuration(self) -> Dict[str, bool]: 

433 """ 

434 Valida se as configurações foram aplicadas corretamente. 

435 

436 Returns: 

437 Dict com status de validação de cada arquivo 

438 """ 

439 validation_results = {} 

440 

441 files_to_check = [ 

442 ("settings.json", self.vscode_dir / "settings.json"), 

443 ("extensions.json", self.vscode_dir / "extensions.json"), 

444 ("tasks.json", self.vscode_dir / "tasks.json"), 

445 ("launch.json", self.vscode_dir / "launch.json"), 

446 ] 

447 

448 for name, file_path in files_to_check: 

449 if file_path.exists(): 

450 try: 

451 with open(file_path, "r", encoding="utf-8") as f: 

452 json.load(f) # Testa se é JSON válido 

453 validation_results[name] = True 

454 except json.JSONDecodeError: 

455 validation_results[name] = False 

456 else: 

457 validation_results[name] = False 

458 

459 return validation_results