feat: YAML LSP (/yaml endpoint) + IAC source catalog enrichment
- Add yaml-language-server (Node.js) to Dockerfile stage 3 - Add YAML_LSP_PORT=2090 env var (Dockerfile + ilsp.nomad) - Start yaml-language-server in background thread (_serve_yaml_lsp) - Expose /yaml WebSocket endpoint (same WS→TCP proxy as /python and /bicep) - Load iac_source_catalog.json alongside bicep_modules_catalog.json - Enrich param_completion_items() with descriptions + required flag from IAC source - Required params sorted first (sortText 0_lru_param_0_...) and marked with * - detail field shows * prefix for required params - Update /health to report iac_source_modules + yaml_lsp fields - Rewrite EDITOR_SETUP.md: WebSocket URLs, YAML schemas config for all editors (Helix, Neovim, PyCharm, VS Code) with azure-pipelines + gitea actions schemas - All 35 tests pass
This commit is contained in:
@@ -29,6 +29,31 @@ _CATALOG_PATHS = [
|
||||
pathlib.Path(__file__).parent.parent.parent / "bicep_modules_catalog.json", # dev
|
||||
]
|
||||
|
||||
# IAC source catalog — richer param descriptions from Bicep source code
|
||||
_IAC_SOURCE_PATHS = [
|
||||
pathlib.Path("/data/iac_source_catalog.json"), # volume-mount
|
||||
pathlib.Path(__file__).parent.parent.parent / "iac_source_catalog.json", # dev
|
||||
]
|
||||
|
||||
|
||||
def _load_iac_source_catalog() -> dict[str, dict[str, Any]]:
|
||||
"""Load IAC source catalog for enriched param descriptions.
|
||||
|
||||
Returns dict keyed by module name (e.g. 'roleassignments') → module info
|
||||
with 'params' list containing name/description/required/type.
|
||||
"""
|
||||
for path in _IAC_SOURCE_PATHS:
|
||||
if path.exists():
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
modules = data.get("modules", {})
|
||||
logger.info("IAC source catalog loaded from %s: %d modules", path, len(modules))
|
||||
return modules
|
||||
except Exception:
|
||||
logger.exception("Failed to parse IAC source catalog at %s", path)
|
||||
logger.info("No iac_source_catalog.json found — param descriptions unavailable")
|
||||
return {}
|
||||
|
||||
|
||||
def _load_catalog() -> list[dict[str, Any]]:
|
||||
"""Load modules from the bundled catalog file, preserving per-version schema."""
|
||||
@@ -62,11 +87,19 @@ class BicepModuleCatalog:
|
||||
"""In-memory catalog of LRU Bicep modules, loaded once at startup."""
|
||||
|
||||
_modules: list[dict[str, Any]] = []
|
||||
_iac: dict[str, dict[str, Any]] = {} # module name → IAC source info
|
||||
|
||||
@classmethod
|
||||
def load(cls) -> None:
|
||||
"""Load catalog from disk. Call once at startup."""
|
||||
"""Load both catalogs from disk. Call once at startup."""
|
||||
cls._modules = _load_catalog()
|
||||
cls._iac = _load_iac_source_catalog()
|
||||
|
||||
@classmethod
|
||||
def _iac_param_map(cls, module_name: str) -> dict[str, dict[str, Any]]:
|
||||
"""Return {param_name: {description, required, type}} from IAC source catalog."""
|
||||
iac_mod = cls._iac.get(module_name, {})
|
||||
return {p["name"]: p for p in iac_mod.get("params", [])}
|
||||
|
||||
@classmethod
|
||||
def get_modules(cls) -> list[dict[str, Any]]:
|
||||
@@ -206,12 +239,23 @@ class BicepModuleCatalog:
|
||||
logger.debug("Param fallback: %s %s→%s", module_name, version, v)
|
||||
break
|
||||
|
||||
# IAC source catalog: richer descriptions + required flag
|
||||
iac_params = cls._iac_param_map(module_name)
|
||||
|
||||
items = []
|
||||
for param_name, param_info in ver_params.items():
|
||||
ptype = param_info.get("type", "any")
|
||||
description = param_info.get("description", "").strip()
|
||||
allowed = param_info.get("allowed", [])
|
||||
|
||||
# Prefer IAC source description (has human-readable text from Bicep source)
|
||||
iac = iac_params.get(param_name, {})
|
||||
description = iac.get("description", "") or param_info.get("description", "")
|
||||
description = description.strip()
|
||||
required = iac.get("required", False)
|
||||
|
||||
doc_lines = [f"**{param_name}** (`{ptype}`)"]
|
||||
if required:
|
||||
doc_lines[0] += " ⚠️ required"
|
||||
if description:
|
||||
doc_lines.append(f"\n{description}")
|
||||
if allowed:
|
||||
@@ -221,9 +265,9 @@ class BicepModuleCatalog:
|
||||
items.append({
|
||||
"label": param_name,
|
||||
"kind": 5, # Field
|
||||
"detail": ptype,
|
||||
"detail": f"{'*' if required else ''}{ptype}",
|
||||
"insertText": f"{param_name}: ",
|
||||
"sortText": f"0_lru_param_{param_name}",
|
||||
"sortText": f"0_lru_param_{'0' if required else '1'}_{param_name}",
|
||||
"documentation": {
|
||||
"kind": "markdown",
|
||||
"value": "\n".join(doc_lines),
|
||||
|
||||
Reference in New Issue
Block a user