""" 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 import threading 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", ) logging.getLogger("ilsp.bicep_lsp.proxy").setLevel(logging.DEBUG) # Pre-warm caches logger.info("Pre-warming catalogs…") from .bicep_lsp.modules import BicepModuleCatalog await asyncio.gather( PypiCatalog.start_background_refresh(), BicepModuleCatalog.start_background_refresh(), ) # Start Bicep LSP proxy in a daemon thread (blocking socket server) threading.Thread( target=serve_bicep, args=(BICEP_LSP_PORT,), daemon=True, ).start() # 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 remaining asyncio services await _serve_python_lsp(PYTHON_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())