From ae71591a277b63a381c588e0ac513dba36a2516f Mon Sep 17 00:00:00 2001 From: Cristian Pufu Date: Fri, 27 Feb 2026 16:30:55 +0200 Subject: [PATCH 1/2] fix: read files in binary mode during pack to prevent content trimming on Windows Text files were opened in text mode during packing, causing Python's universal newline translation to convert CRLF to LF on Windows. This silently trimmed file content when git's autocrlf converted LF to CRLF on checkout. Also normalizes zip entry paths to use forward slashes on Windows. Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 2 +- src/uipath/_cli/cli_pack.py | 23 +++++------------------ uv.lock | 2 +- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4fd106947..16a6ac5c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.1" +version = "2.10.2" 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/src/uipath/_cli/cli_pack.py b/src/uipath/_cli/cli_pack.py index 974948902..84525d8be 100644 --- a/src/uipath/_cli/cli_pack.py +++ b/src/uipath/_cli/cli_pack.py @@ -292,24 +292,11 @@ def pack_fn( ) for file in files: - if file.is_binary: - # Read binary files in binary mode - with open(file.file_path, "rb") as f: - z.writestr(f"content/{file.relative_path}", f.read()) - else: - try: - # Try UTF-8 first - with open(file.file_path, "r", encoding="utf-8") as f: - z.writestr(f"content/{file.relative_path}", f.read()) - except UnicodeDecodeError: - # If UTF-8 fails, try with utf-8-sig (for files with BOM) - try: - with open(file.file_path, "r", encoding="utf-8-sig") as f: - z.writestr(f"content/{file.relative_path}", f.read()) - except UnicodeDecodeError: - # If that also fails, try with latin-1 as a fallback - with open(file.file_path, "r", encoding="latin-1") as f: - z.writestr(f"content/{file.relative_path}", f.read()) + archive_path = f"content/{file.relative_path}" + if os.sep != "/": + archive_path = archive_path.replace(os.sep, "/") + with open(file.file_path, "rb") as f: + z.writestr(archive_path, f.read()) def display_project_info(config): diff --git a/uv.lock b/uv.lock index d5b170efa..0757c5b0f 100644 --- a/uv.lock +++ b/uv.lock @@ -2531,7 +2531,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.1" +version = "2.10.2" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From 2e6d0b9c10637b59c18891c217031c29659651a8 Mon Sep 17 00:00:00 2001 From: Cristian Pufu Date: Fri, 27 Feb 2026 16:42:56 +0200 Subject: [PATCH 2/2] test: add regression test for CRLF byte-for-byte preservation during pack Writes files with explicit CRLF and LF line endings in binary mode, then asserts the zip entry bytes match exactly after packing. Prevents the text-mode read regression from reappearing. Co-Authored-By: Claude Opus 4.6 --- tests/cli/test_pack.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/cli/test_pack.py b/tests/cli/test_pack.py index 0c34ab2ee..44809c8cb 100644 --- a/tests/cli/test_pack.py +++ b/tests/cli/test_pack.py @@ -326,6 +326,55 @@ def test_include_file_extensions( "Binary file content was corrupted during packing" ) + def test_pack_preserves_crlf_line_endings( + self, + runner: CliRunner, + temp_dir: str, + project_details: ProjectDetails, + ) -> None: + """Test that packing preserves CRLF line endings byte-for-byte. + + Regression test: text-mode reads on Windows silently convert CRLF to LF, + causing files to be trimmed. The pack must use binary-mode reads so the + zip entry is byte-identical to the file on disk. + """ + crlf_content = b"line1\r\nline2\r\nline3\r\n" + lf_content = b"line1\nline2\nline3\n" + + with runner.isolated_filesystem(temp_dir=temp_dir): + with open("uipath.json", "w") as f: + json.dump(create_uipath_json(), f) + with open("pyproject.toml", "w") as f: + f.write(project_details.to_toml()) + # Write main.py with CRLF endings using binary mode to avoid any conversion + with open("main.py", "wb") as f: + f.write(b"def main(input):\r\n return input\r\n") + # Write a helper file with CRLF endings + with open("crlf_file.py", "wb") as f: + f.write(crlf_content) + # Write a helper file with LF endings + with open("lf_file.py", "wb") as f: + f.write(lf_content) + create_bindings_file() + + with patch("uipath._cli.cli_init.Middlewares.next") as mock_middleware: + mock_middleware.return_value = MiddlewareResult(should_continue=True) + init_result = runner.invoke(cli, ["init"], env={}) + assert init_result.exit_code == 0 + + result = runner.invoke(cli, ["pack", "./"], env={}) + assert result.exit_code == 0 + + with zipfile.ZipFile( + f".uipath/{project_details.name}.{project_details.version}.nupkg", "r" + ) as z: + assert z.read("content/crlf_file.py") == crlf_content, ( + "CRLF line endings were not preserved during packing" + ) + assert z.read("content/lf_file.py") == lf_content, ( + "LF line endings were not preserved during packing" + ) + def test_include_files( self, runner: CliRunner,