Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
21 changes: 19 additions & 2 deletions packages/uipath/src/uipath/_cli/cli_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import json
import logging
import os
import re
import shutil
import uuid
from pathlib import Path
Expand Down Expand Up @@ -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.

Expand All @@ -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)

Expand Down
42 changes: 42 additions & 0 deletions packages/uipath/tests/cli/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading