From 22acd4e54534a1512f915fd4ce1430d4334c9b28 Mon Sep 17 00:00:00 2001 From: radu-mocanu Date: Tue, 28 Apr 2026 13:56:18 +0300 Subject: [PATCH] fix: sanitize mermaid node ids in uipath init output (#1505) --- packages/uipath/pyproject.toml | 2 +- packages/uipath/src/uipath/_cli/cli_init.py | 21 ++++++++++- packages/uipath/tests/cli/test_init.py | 42 +++++++++++++++++++++ packages/uipath/uv.lock | 2 +- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index c6a998262..3883b3d84 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.58" +version = "2.10.59" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/_cli/cli_init.py b/packages/uipath/src/uipath/_cli/cli_init.py index 80396d8ff..ce606699c 100644 --- a/packages/uipath/src/uipath/_cli/cli_init.py +++ b/packages/uipath/src/uipath/_cli/cli_init.py @@ -4,6 +4,7 @@ import json import logging import os +import re import shutil import uuid from pathlib import Path @@ -313,6 +314,19 @@ def write_mermaid_files(entry_points: list[UiPathRuntimeSchema]) -> list[Path]: return mermaid_paths +_MERMAID_ID_INVALID_CHARS = re.compile(r"[^a-zA-Z0-9_]+") + + +def _sanitize_mermaid_id(node_id: str) -> str: + """Replace characters invalid in Mermaid node IDs with underscores. + + `UiPathRuntimeNode.id` uses `file.py:line` so the value can double as a + breakpoint location. Mermaid treats `.` and `:` as syntax, so the raw ID + breaks the flowchart parser — sanitize before emitting. + """ + return _MERMAID_ID_INVALID_CHARS.sub("_", node_id) + + def _add_graph_to_chart(chart: Chart | Subgraph, graph: UiPathRuntimeGraph) -> None: """Recursively add nodes and edges from UiPathRuntimeGraph to mermaid chart. @@ -328,13 +342,16 @@ def _add_graph_to_chart(chart: Chart | Subgraph, graph: UiPathRuntimeGraph) -> N _add_graph_to_chart(subgraph, node.subgraph) chart.add_subgraph(subgraph) else: - mermaid_node = Node(title=node.name, id=node.id) + mermaid_id = _sanitize_mermaid_id(node.id) + mermaid_node = Node(title=node.name, id=mermaid_id) chart.add_node(mermaid_node) node_objects[node.id] = mermaid_node for edge in graph.edges: link = Link( - src=edge.source, dest=edge.target, text=edge.label if edge.label else None + src=_sanitize_mermaid_id(edge.source), + dest=_sanitize_mermaid_id(edge.target), + text=edge.label if edge.label else None, ) chart.add_link(link) diff --git a/packages/uipath/tests/cli/test_init.py b/packages/uipath/tests/cli/test_init.py index 59d4eaaa8..038ff3594 100644 --- a/packages/uipath/tests/cli/test_init.py +++ b/packages/uipath/tests/cli/test_init.py @@ -685,3 +685,45 @@ def test_mermaid_file_starts_with_header_comment( assert contents.startswith(MERMAID_FILE_HEADER) assert "AUTO-GENERATED" in contents assert "uipath init" in contents + + def test_mermaid_node_ids_are_sanitized( + self, runner: CliRunner, temp_dir: str + ) -> None: + """Node IDs containing `.` or `:` must be sanitized so Mermaid can parse them.""" + from uipath._cli.cli_init import write_mermaid_files + from uipath.runtime.schema import ( + UiPathRuntimeEdge, + UiPathRuntimeGraph, + UiPathRuntimeNode, + UiPathRuntimeSchema, + ) + + graph = UiPathRuntimeGraph( + nodes=[ + UiPathRuntimeNode( + id="vendor.py:52", name="check_vendor_risk", type="function" + ), + UiPathRuntimeNode( + id="vendor.py:110", name="_resolve_instance_url", type="function" + ), + ], + edges=[UiPathRuntimeEdge(source="vendor.py:52", target="vendor.py:110")], + ) + ep = UiPathRuntimeSchema( + filePath="main.py", + uniqueId="main", + type="function", + input={}, + output={}, + graph=graph, + ) + + with runner.isolated_filesystem(temp_dir=temp_dir): + paths = write_mermaid_files([ep]) + contents = paths[0].read_text() + + assert "vendor.py:52" not in contents + assert "vendor.py:110" not in contents + assert "vendor_py_52" in contents + assert "vendor_py_110" in contents + assert "vendor_py_52 --> vendor_py_110" in contents diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 87c9a3aa2..287ad78c9 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2543,7 +2543,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.58" +version = "2.10.59" source = { editable = "." } dependencies = [ { name = "applicationinsights" },