fix: yaml stdio per-connection, iac catalog path, makefile port
All checks were successful
Build and Deploy iLSP / test (push) Successful in 25s
Build and Deploy iLSP / build-and-deploy (push) Successful in 1m37s

- yaml-language-server: rewrite to stdio per WebSocket (fixes crash loop)
  vscode-jsonrpc v9 createServerSocketTransport is a TCP client, not server
  now spawns yaml-language-server --stdio per connection via asyncio subprocess

- bicep/modules.py: add /iac_source_catalog.json as first path in _IAC_SOURCE_PATHS
  Dockerfile copies to /iac_source_catalog.json but path wasn't listed

- server.py: remove YAML_LSP_PORT daemon (no longer needed with stdio mode)

- Makefile: add -e HTTP_PORT=$(HEALTH_PORT) to all docker run commands
  server defaulted to :8000 but Makefile exposed :2089 with no override
This commit is contained in:
Henrik Jess Nielsen
2026-05-10 16:37:42 +02:00
parent 2a1717ff81
commit ae751f944c
5 changed files with 52 additions and 38 deletions

View File

@@ -273,31 +273,48 @@ class _YamlSession:
# ── Main WS handler ───────────────────────────────────────────────────────────
async def yaml_ws_handler(request: web.Request, yaml_lsp_port: int) -> web.WebSocketResponse:
async def yaml_ws_handler(request: web.Request, yaml_lsp_port: int = 0) -> web.WebSocketResponse:
"""
WebSocket handler for the /yaml endpoint.
Bridges the editor WS connection to yaml-language-server TCP, intercepting
completion messages to inject pipeline template completions.
Spawns yaml-language-server --stdio per editor connection (one process per
session). Bridges WS ↔ process stdin/stdout, intercepting completion messages
to inject pipeline template completions.
Note: yaml_lsp_port is unused — kept for API compatibility.
yaml-language-server uses --stdio so no TCP port is needed.
"""
import shutil
ws = web.WebSocketResponse()
await ws.prepare(request)
try:
tcp_reader, tcp_writer = await asyncio.wait_for(
asyncio.open_connection("127.0.0.1", yaml_lsp_port), timeout=3.0
)
except (OSError, asyncio.TimeoutError) as exc:
logger.error("Cannot connect to yaml-language-server on port %d: %s", yaml_lsp_port, exc)
await ws.close(code=1011, message=b"YAML LSP backend unavailable", timeout=2.0)
yaml_ls = shutil.which("yaml-language-server")
if not yaml_ls:
logger.error("yaml-language-server not found in PATH")
await ws.close(code=1011, message=b"yaml-language-server not installed", timeout=2.0)
return ws
try:
proc = await asyncio.create_subprocess_exec(
yaml_ls, "--stdio",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL,
)
except Exception as exc:
logger.error("Failed to start yaml-language-server: %s", exc)
await ws.close(code=1011, message=b"YAML LSP failed to start", timeout=2.0)
return ws
logger.info("yaml-language-server started (--stdio) PID=%d", proc.pid)
session = _YamlSession()
ws_buf = _LspFrameBuffer()
tcp_buf = _LspFrameBuffer()
proc_buf = _LspFrameBuffer()
async def client_to_server() -> None:
"""WS → TCP: track document content and completion requests."""
"""WS → stdin: track document content and completion requests."""
try:
async for msg in ws:
if msg.type not in (WSMsgType.BINARY, WSMsgType.TEXT):
@@ -313,7 +330,6 @@ async def yaml_ws_handler(request: web.Request, yaml_lsp_port: int) -> web.WebSo
uri = params.get("textDocument", {}).get("uri", "")
text = params.get("textDocument", {}).get("text") or ""
if not text:
# didChange has contentChanges
changes = params.get("contentChanges", [])
if changes:
text = changes[-1].get("text", "")
@@ -323,21 +339,25 @@ async def yaml_ws_handler(request: web.Request, yaml_lsp_port: int) -> web.WebSo
session.record_request(parsed)
except Exception:
pass
tcp_writer.write(_lsp_frame(frame))
await tcp_writer.drain()
proc.stdin.write(_lsp_frame(frame))
await proc.stdin.drain()
except Exception:
pass
finally:
tcp_writer.close()
try:
proc.stdin.close()
except Exception:
pass
proc.kill()
async def server_to_client() -> None:
"""TCP → WS: inject completions into completion responses."""
"""stdout → WS: inject completions into completion responses."""
try:
while True:
data = await tcp_reader.read(_CHUNK)
data = await proc.stdout.read(_CHUNK)
if not data:
break
frames = tcp_buf.feed(data)
frames = proc_buf.feed(data)
for frame in frames:
try:
parsed = json.loads(frame)
@@ -356,4 +376,9 @@ async def yaml_ws_handler(request: web.Request, yaml_lsp_port: int) -> web.WebSo
await ws.close()
await asyncio.gather(client_to_server(), server_to_client(), return_exceptions=True)
try:
proc.kill()
except Exception:
pass
logger.info("yaml-language-server session ended PID=%d", proc.pid)
return ws