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
« 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."""
3import json
4from typing import Any, Dict, List
6from ..core.models import InstallationConfig, TeamType
9class VSCodeConfigurator:
10 """Configura automaticamente o VS Code para o framework JTECH™ Core."""
12 def __init__(self, config: InstallationConfig, dry_run: bool = False):
13 """
14 Inicializa o configurador do VS Code.
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"
24 def configure_all(self) -> Dict[str, bool]:
25 """
26 Executa todas as configurações do VS Code.
28 Returns:
29 Dict com status de cada configuração aplicada
30 """
31 results = {}
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)
37 # Configurar settings.json
38 results["settings"] = self._configure_settings()
40 # Configurar extensões recomendadas
41 results["extensions"] = self._configure_extensions()
43 # Configurar tasks.json para framework
44 results["tasks"] = self._configure_tasks()
46 # Configurar launch.json se aplicável
47 results["launch"] = self._configure_launch()
49 return results
51 def _configure_settings(self) -> bool:
52 """
53 Configura o arquivo settings.json do VS Code.
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)
63 settings_file = self.vscode_dir / "settings.json"
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 }
103 # Configurações específicas por tipo de equipe
104 team_settings = self._get_team_specific_settings()
105 base_settings.update(team_settings)
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
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)
122 return True
124 except Exception as e:
125 print(f"Erro ao configurar settings.json: {e}")
126 return False
128 def _configure_extensions(self) -> bool:
129 """
130 Configura extensões recomendadas no extensions.json.
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)
140 extensions_file = self.vscode_dir / "extensions.json"
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 ]
163 # Extensões específicas por tipo de equipe
164 team_extensions = self._get_team_specific_extensions()
165 all_extensions = base_extensions + team_extensions
167 extensions_config = {
168 "recommendations": all_extensions,
169 "unwantedRecommendations": [
170 "ms-vscode.vscode-typescript-next",
171 "hookyqr.beautify",
172 ],
173 }
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
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 )
197 return True
199 except Exception as e:
200 print(f"Erro ao configurar extensions.json: {e}")
201 return False
203 def _configure_tasks(self) -> bool:
204 """
205 Configura tarefas do VS Code no tasks.json.
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)
215 tasks_file = self.vscode_dir / "tasks.json"
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 }
250 # Adicionar tarefas específicas por tipo de equipe
251 team_tasks = self._get_team_specific_tasks()
252 tasks_config["tasks"].extend(team_tasks)
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)
258 return True
260 except Exception as e:
261 print(f"Erro ao configurar tasks.json: {e}")
262 return False
264 def _configure_launch(self) -> bool:
265 """
266 Configura launch.json se aplicável.
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)
276 # Por enquanto, apenas criar estrutura básica
277 launch_file = self.vscode_dir / "launch.json"
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 }
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)
298 return True
300 except Exception as e:
301 print(f"Erro ao configurar launch.json: {e}")
302 return False
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 }
339 return team_settings.get(self.config.team_type, {})
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 }
374 return team_extensions.get(self.config.team_type, [])
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 ]
430 return []
432 def validate_configuration(self) -> Dict[str, bool]:
433 """
434 Valida se as configurações foram aplicadas corretamente.
436 Returns:
437 Dict com status de validação de cada arquivo
438 """
439 validation_results = {}
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 ]
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
459 return validation_results