feat: initial iLSP project scaffolding
- Python LSP (pylsp + pylsp_i80 plugin): i80 pypi package completions - Bicep LSP (asyncio TCP proxy → Bicep.LangServer.dll): LRU module injection - Health HTTP endpoint (:2089) for Consul/Nomad checks - Startup catalog fetch from pypi-server.i80.dk + DevOpsMCP (no volume needed) - Multi-stage Dockerfile: downloads Bicep LS at build time, dotnet-runtime-8.0 + python3.12 - Nomad job: static TCP ports 2087/2088, health check on 2089 - Gitea Actions CI: build + push + deploy pipeline - Editor configs: Helix / nvim / LSP4IJ / VS Code
This commit is contained in:
87
ilsp/server.py
Normal file
87
ilsp/server.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user