refactor: extract CSS modules for terminal and git components (Task 2.2)

- Create TerminalComponent.module.css with terminal-* styles
- Create GitToolbar.module.css with git toolbar styles
- Create CommitDialog.module.css with commit dialog styles
- Create MergeDialog.module.css with merge dialog styles
- Create FileEditor.module.css with file editor styles
- Update all components to import their CSS modules
- Remove extracted rules from styles.css (~441 lines removed)

Quality gates: tsc (pass), eslint (pass), build (pass)
Refs: repo-restructure Task 2.2
This commit is contained in:
Developer
2026-06-02 20:39:32 +00:00
parent 58a9728d5e
commit dd7696b5a4
15 changed files with 1263 additions and 1845 deletions
+125
View File
@@ -1,9 +1,134 @@
"""Docker Compose file generation and command execution."""
import re
import subprocess
import uuid
from pathlib import Path
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.config_profile import ConfigProfile
from src.models.tool_instance import ToolInstance
from src.services.profile_resolver import resolve_profile
def _sanitize_name(name: str) -> str:
"""Sanitize a string for use in Docker/container names."""
sanitized = re.sub(r"[^a-z0-9-]", "-", name.lower())
sanitized = re.sub(r"-+", "-", sanitized)
return sanitized.strip("-")
async def _generate_instance_name(
session: AsyncSession,
project_name: str,
tool_type_name: str,
) -> str:
"""Generate a unique instance name: project-tool-NUM."""
base = f"{_sanitize_name(project_name)}-{_sanitize_name(tool_type_name)}"
base = base.strip("-") or "instance"
result = await session.execute(
select(ToolInstance.name).where(ToolInstance.name.like(f"{base}-%"))
)
names = result.scalars().all()
max_num = 0
for name in names:
parts = name.rsplit("-", 1)
if len(parts) == 2 and parts[0] == base and parts[1].isdigit():
max_num = max(max_num, int(parts[1]))
return f"{base}-{max_num + 1:03d}"
def _modify_compose_file(
compose_path: str,
port_override: int | None = None,
start_command: str | None = None,
working_directory: str | None = None,
extra_volumes: list[dict] | None = None,
) -> None:
"""Modify compose file with runtime overrides."""
import yaml
compose_file = Path(compose_path)
content = compose_file.read_text()
compose_data = yaml.safe_load(content)
if not compose_data or "services" not in compose_data:
return
for service_name, service_config in compose_data["services"].items():
if port_override and "ports" in service_config:
for i, port_mapping in enumerate(service_config["ports"]):
if isinstance(port_mapping, str) and ":" in port_mapping:
_host_port, container_port = port_mapping.split(":", 1)
service_config["ports"][i] = f"{port_override}:{container_port}"
break
if start_command:
service_config["command"] = start_command
if working_directory:
service_config["working_dir"] = working_directory
if extra_volumes:
if "volumes" not in service_config:
service_config["volumes"] = []
for vol in extra_volumes:
source = vol.get("source", "")
target = vol.get("target", "")
vol_type = vol.get("type", "bind")
if vol_type == "bind":
service_config["volumes"].append(f"{source}:{target}")
else:
service_config["volumes"].append(f"{source}:{target}:{vol_type}")
break
compose_file.write_text(yaml.dump(compose_data, default_flow_style=False))
async def _apply_resolved_profile(
profile: ConfigProfile,
instance_dir: str,
env_vars: dict[str, str],
port_override: int | None,
start_command: str | None,
working_directory: str | None,
extra_volumes: list[dict],
) -> tuple[dict[str, str], int | None, str | None, str | None, list[dict]]:
"""Resolve a profile and apply its output to instance configuration."""
resolved = resolve_profile(profile)
if resolved.environment_variables:
env_vars.update(resolved.environment_variables)
if resolved.runtime_hints.start_command is not None:
start_command = resolved.runtime_hints.start_command
if resolved.runtime_hints.working_directory is not None:
working_directory = resolved.runtime_hints.working_directory
if resolved.runtime_hints.port is not None:
port_override = resolved.runtime_hints.port
for target_path, mount in resolved.mounts.items():
safe_name = target_path.strip("/").replace("/", "_")
mount_dir = Path(instance_dir) / "mounts" / safe_name
mount_dir.mkdir(parents=True, exist_ok=True)
for rel_path, content in mount.files.items():
file_path = mount_dir / rel_path
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content)
extra_volumes.append({
"source": str(mount_dir),
"target": target_path,
"type": mount.mode,
})
return env_vars, port_override, start_command, working_directory, extra_volumes
def render_compose_template(template: str, variables: dict[str, Any]) -> str:
"""Render a Docker Compose template with variable substitution.