""" iLSP main entrypoint. Starts three services concurrently: - pylsp server on TCP :2087 (Python LSP + i80 completions) - Bicep proxy on TCP :2088 (Bicep LS wrapper + LRU modules) - Health HTTP on TCP :2089 (for Consul/Nomad health checks) """ import asyncio import logging import os import signal from aiohttp import web from .python_lsp.catalog import PypiCatalog from .bicep_lsp.proxy import serve_bicep logger = logging.getLogger(__name__) PYTHON_LSP_PORT = int(os.getenv("PYTHON_LSP_PORT", "2087")) BICEP_LSP_PORT = int(os.getenv("BICEP_LSP_PORT", "2088")) HEALTH_PORT = int(os.getenv("HEALTH_PORT", "2089")) async def _health_app() -> web.Application: app = web.Application() async def health(_: web.Request) -> web.Response: pypi_count = len(PypiCatalog._packages) from .bicep_lsp.modules import BicepModuleCatalog bicep_count = len(BicepModuleCatalog._modules) return web.json_response({ "status": "ok", "pypi_packages": pypi_count, "bicep_modules": bicep_count, }) app.router.add_get("/health", health) return app async def _serve_python_lsp(port: int) -> None: """Start pylsp in TCP server mode.""" import subprocess, sys proc = await asyncio.create_subprocess_exec( sys.executable, "-m", "pylsp", "--tcp", "--host", "0.0.0.0", "--port", str(port), ) logger.info("Python LSP (pylsp) listening on TCP :%d PID=%d", port, proc.pid) await proc.wait() logger.warning("pylsp exited — restarting") async def main_async() -> None: logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) # Pre-warm caches logger.info("Pre-warming catalogs…") await asyncio.gather( PypiCatalog.start_background_refresh(), ) # Build health app health_app = await _health_app() runner = web.AppRunner(health_app) await runner.setup() site = web.TCPSite(runner, "0.0.0.0", HEALTH_PORT) await site.start() logger.info("Health endpoint on http://0.0.0.0:%d/health", HEALTH_PORT) # Run all services await asyncio.gather( _serve_python_lsp(PYTHON_LSP_PORT), serve_bicep(BICEP_LSP_PORT), ) def main() -> None: loop = asyncio.new_event_loop() for sig in (signal.SIGTERM, signal.SIGINT): loop.add_signal_handler(sig, loop.stop) loop.run_until_complete(main_async())