uipath 2.0.0.dev3__py3-none-any.whl → 2.0.1.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

Files changed (76) hide show
  1. uipath/__init__.py +24 -0
  2. uipath/_cli/README.md +11 -0
  3. uipath/_cli/__init__.py +54 -0
  4. uipath/_cli/_auth/_auth_server.py +165 -0
  5. uipath/_cli/_auth/_models.py +51 -0
  6. uipath/_cli/_auth/_oidc_utils.py +69 -0
  7. uipath/_cli/_auth/_portal_service.py +163 -0
  8. uipath/_cli/_auth/_utils.py +51 -0
  9. uipath/_cli/_auth/auth_config.json +6 -0
  10. uipath/_cli/_auth/index.html +167 -0
  11. uipath/_cli/_auth/localhost.crt +25 -0
  12. uipath/_cli/_auth/localhost.key +27 -0
  13. uipath/_cli/_runtime/_contracts.py +429 -0
  14. uipath/_cli/_runtime/_logging.py +193 -0
  15. uipath/_cli/_runtime/_runtime.py +264 -0
  16. uipath/_cli/_templates/.psmdcp.template +9 -0
  17. uipath/_cli/_templates/.rels.template +5 -0
  18. uipath/_cli/_templates/[Content_Types].xml.template +9 -0
  19. uipath/_cli/_templates/main.py.template +25 -0
  20. uipath/_cli/_templates/package.nuspec.template +10 -0
  21. uipath/_cli/_utils/_common.py +24 -0
  22. uipath/_cli/_utils/_input_args.py +126 -0
  23. uipath/_cli/_utils/_parse_ast.py +542 -0
  24. uipath/_cli/cli_auth.py +97 -0
  25. uipath/_cli/cli_deploy.py +13 -0
  26. uipath/_cli/cli_init.py +113 -0
  27. uipath/_cli/cli_new.py +76 -0
  28. uipath/_cli/cli_pack.py +337 -0
  29. uipath/_cli/cli_publish.py +113 -0
  30. uipath/_cli/cli_run.py +133 -0
  31. uipath/_cli/middlewares.py +113 -0
  32. uipath/_config.py +6 -0
  33. uipath/_execution_context.py +83 -0
  34. uipath/_folder_context.py +62 -0
  35. uipath/_models/__init__.py +37 -0
  36. uipath/_models/action_schema.py +26 -0
  37. uipath/_models/actions.py +64 -0
  38. uipath/_models/assets.py +48 -0
  39. uipath/_models/connections.py +51 -0
  40. uipath/_models/context_grounding.py +18 -0
  41. uipath/_models/context_grounding_index.py +60 -0
  42. uipath/_models/exceptions.py +6 -0
  43. uipath/_models/interrupt_models.py +28 -0
  44. uipath/_models/job.py +66 -0
  45. uipath/_models/llm_gateway.py +101 -0
  46. uipath/_models/processes.py +48 -0
  47. uipath/_models/queues.py +167 -0
  48. uipath/_services/__init__.py +26 -0
  49. uipath/_services/_base_service.py +250 -0
  50. uipath/_services/actions_service.py +271 -0
  51. uipath/_services/api_client.py +89 -0
  52. uipath/_services/assets_service.py +257 -0
  53. uipath/_services/buckets_service.py +268 -0
  54. uipath/_services/connections_service.py +185 -0
  55. uipath/_services/connections_service.pyi +50 -0
  56. uipath/_services/context_grounding_service.py +402 -0
  57. uipath/_services/folder_service.py +49 -0
  58. uipath/_services/jobs_service.py +265 -0
  59. uipath/_services/llm_gateway_service.py +311 -0
  60. uipath/_services/processes_service.py +168 -0
  61. uipath/_services/queues_service.py +314 -0
  62. uipath/_uipath.py +98 -0
  63. uipath/_utils/__init__.py +17 -0
  64. uipath/_utils/_endpoint.py +79 -0
  65. uipath/_utils/_infer_bindings.py +30 -0
  66. uipath/_utils/_logs.py +15 -0
  67. uipath/_utils/_request_override.py +18 -0
  68. uipath/_utils/_request_spec.py +23 -0
  69. uipath/_utils/_user_agent.py +16 -0
  70. uipath/_utils/constants.py +25 -0
  71. uipath/py.typed +0 -0
  72. {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/METADATA +2 -3
  73. uipath-2.0.1.dev1.dist-info/RECORD +75 -0
  74. uipath-2.0.0.dev3.dist-info/RECORD +0 -4
  75. {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/WHEEL +0 -0
  76. {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,113 @@
1
+ # type: ignore
2
+ import json
3
+ import os
4
+ import traceback
5
+ import uuid
6
+ from typing import Optional
7
+
8
+ import click
9
+
10
+ from ._utils._input_args import generate_args
11
+ from ._utils._parse_ast import generate_bindings_json
12
+ from .middlewares import Middlewares
13
+
14
+
15
+ def generate_env_file(target_directory):
16
+ env_path = os.path.join(target_directory, ".env")
17
+
18
+ if not os.path.exists(env_path):
19
+ relative_path = os.path.relpath(env_path, target_directory)
20
+ click.echo(f"Created {relative_path} file.")
21
+ with open(env_path, "w") as f:
22
+ f.write("UIPATH_ACCESS_TOKEN=YOUR_TOKEN_HERE\n")
23
+ f.write("UIPATH_URL=https://alpha.uipath.com/ACCOUNT_NAME/TENANT_NAME\n")
24
+
25
+
26
+ def get_user_script(directory: str, entrypoint: Optional[str] = None) -> Optional[str]:
27
+ """Find the Python script to process."""
28
+ if entrypoint:
29
+ script_path = os.path.join(directory, entrypoint)
30
+ if not os.path.isfile(script_path):
31
+ click.echo(f"The {entrypoint} file does not exist in the current directory")
32
+ return None
33
+ return script_path
34
+
35
+ python_files = [f for f in os.listdir(directory) if f.endswith(".py")]
36
+
37
+ if not python_files:
38
+ click.echo("No Python files found in the directory")
39
+ return None
40
+ elif len(python_files) == 1:
41
+ return os.path.join(directory, python_files[0])
42
+ else:
43
+ click.echo(
44
+ "Multiple Python files found in the current directory.\nPlease specify the entrypoint: `uipath init <entrypoint_path>`"
45
+ )
46
+ return None
47
+
48
+
49
+ @click.command()
50
+ @click.argument("entrypoint", required=False, default=None)
51
+ def init(entrypoint: str) -> None:
52
+ """Initialize a uipath.json configuration file for the script."""
53
+ current_directory = os.getcwd()
54
+ generate_env_file(current_directory)
55
+
56
+ result = Middlewares.next("init", entrypoint)
57
+
58
+ if result.error_message:
59
+ click.echo(result.error_message)
60
+ if result.should_include_stacktrace:
61
+ click.echo(traceback.format_exc())
62
+ click.get_current_context().exit(1)
63
+
64
+ if result.info_message:
65
+ click.echo(result.info_message)
66
+
67
+ if not result.should_continue:
68
+ return
69
+
70
+ script_path = get_user_script(current_directory, entrypoint=entrypoint)
71
+
72
+ if not script_path:
73
+ click.get_current_context().exit(1)
74
+
75
+ try:
76
+ args = generate_args(script_path)
77
+
78
+ relative_path = os.path.relpath(script_path, current_directory)
79
+
80
+ config_data = {
81
+ "entryPoints": [
82
+ {
83
+ "filePath": relative_path,
84
+ "uniqueId": str(uuid.uuid4()),
85
+ # "type": "process", OR BE doesn't offer json schema support for type: Process
86
+ "type": "agent",
87
+ "input": args["input"],
88
+ "output": args["output"],
89
+ }
90
+ ]
91
+ }
92
+
93
+ # Generate bindings JSON based on the script path
94
+ try:
95
+ bindings_data = generate_bindings_json(script_path)
96
+
97
+ # Add bindings to the config data
98
+ config_data["bindings"] = bindings_data
99
+
100
+ click.echo("Bindings generated successfully.")
101
+ except Exception as e:
102
+ click.echo(f"Warning: Could not generate bindings: {str(e)}")
103
+
104
+ config_path = "uipath.json"
105
+ with open(config_path, "w") as config_file:
106
+ json.dump(config_data, config_file, indent=4)
107
+
108
+ click.echo(f"Configuration file {config_path} created successfully.")
109
+
110
+ except Exception as e:
111
+ click.echo(f"Error generating configuration: {str(e)}")
112
+ click.echo(traceback.format_exc())
113
+ click.get_current_context().exit(1)
uipath/_cli/cli_new.py ADDED
@@ -0,0 +1,76 @@
1
+ # type: ignore
2
+ import os
3
+ import shutil
4
+ import traceback
5
+
6
+ import click
7
+
8
+ from .middlewares import Middlewares
9
+
10
+
11
+ def generate_script(target_directory):
12
+ template_path = os.path.join(
13
+ os.path.dirname(__file__), "_templates/main.py.template"
14
+ )
15
+ target_path = os.path.join(target_directory, "main.py")
16
+
17
+ shutil.copyfile(template_path, target_path)
18
+
19
+
20
+ def generate_pyproject(target_directory, project_name):
21
+ project_toml_path = os.path.join(target_directory, "pyproject.toml")
22
+ toml_content = f"""[project]
23
+ name = "{project_name}"
24
+ version = "0.0.1"
25
+ description = "{project_name}"
26
+ authors = [{{ name = "John Doe", email = "john.doe@myemail.com" }}]
27
+ dependencies = [
28
+ "uipath>=2.0.0"
29
+ ]
30
+ requires-python = ">=3.9"
31
+ """
32
+
33
+ with open(project_toml_path, "w") as f:
34
+ f.write(toml_content)
35
+
36
+
37
+ @click.command()
38
+ @click.argument("name", type=str, default="")
39
+ def new(name: str):
40
+ directory = os.getcwd()
41
+
42
+ if not name:
43
+ raise click.UsageError(
44
+ "Please specify a name for your project\n`uipath new hello-world`"
45
+ )
46
+
47
+ click.echo(f"Initializing project {name} in current directory..")
48
+
49
+ result = Middlewares.next("new", name)
50
+
51
+ if result.error_message:
52
+ click.echo(result.error_message)
53
+ if result.should_include_stacktrace:
54
+ click.echo(traceback.format_exc())
55
+ click.get_current_context().exit(1)
56
+
57
+ if result.info_message:
58
+ click.echo(result.info_message)
59
+
60
+ if not result.should_continue:
61
+ return
62
+
63
+ generate_script(directory)
64
+ click.echo("Created main.py file.")
65
+ generate_pyproject(directory, name)
66
+ click.echo("Created pyproject.toml file.")
67
+
68
+ ctx = click.get_current_context()
69
+ init_cmd = ctx.parent.command.get_command(ctx, "init")
70
+ ctx.invoke(init_cmd)
71
+
72
+ click.echo("""` uipath run main.py '{"message": "Hello World!"}' `""")
73
+
74
+
75
+ if __name__ == "__main__":
76
+ new()
@@ -0,0 +1,337 @@
1
+ # type: ignore
2
+ import json
3
+ import os
4
+ import uuid
5
+ import zipfile
6
+ from string import Template
7
+
8
+ import click
9
+
10
+ try:
11
+ import tomllib
12
+ except ImportError:
13
+ import tomli as tomllib
14
+
15
+ from ._utils._parse_ast import generate_bindings_json
16
+
17
+ schema = "https://cloud.uipath.com/draft/2024-12/entry-point"
18
+
19
+
20
+ def validate_config_structure(config_data):
21
+ required_fields = ["entryPoints"]
22
+ for field in required_fields:
23
+ if field not in config_data:
24
+ raise Exception(f"uipath.json is missing the required field: {field}")
25
+
26
+
27
+ def check_config(directory):
28
+ config_path = os.path.join(directory, "uipath.json")
29
+ toml_path = os.path.join(directory, "pyproject.toml")
30
+
31
+ if not os.path.isfile(config_path):
32
+ raise Exception("uipath.json not found, please run `uipath init`")
33
+ if not os.path.isfile(toml_path):
34
+ raise Exception("pyproject.toml not found")
35
+
36
+ with open(config_path, "r") as config_file:
37
+ config_data = json.load(config_file)
38
+
39
+ validate_config_structure(config_data)
40
+
41
+ toml_data = read_toml_project(toml_path)
42
+
43
+ return {
44
+ "project_name": toml_data["name"],
45
+ "description": toml_data["description"],
46
+ "entryPoints": config_data["entryPoints"],
47
+ "version": toml_data["version"],
48
+ "authors": toml_data["authors"],
49
+ }
50
+
51
+
52
+ def generate_operate_file(entryPoints):
53
+ project_id = str(uuid.uuid4())
54
+
55
+ first_entry = entryPoints[0]
56
+ file_path = first_entry["filePath"]
57
+ type = first_entry["type"]
58
+
59
+ operate_json_data = {
60
+ "$schema": schema,
61
+ "projectId": project_id,
62
+ "main": file_path,
63
+ "contentType": type,
64
+ "targetFramework": "Portable",
65
+ "targetRuntime": "python",
66
+ "runtimeOptions": {"requiresUserInteraction": False, "isAttended": False},
67
+ }
68
+
69
+ return operate_json_data
70
+
71
+
72
+ def generate_entrypoints_file(entryPoints):
73
+ entrypoint_json_data = {
74
+ "$schema": schema,
75
+ "$id": "entry-points.json",
76
+ "entryPoints": entryPoints,
77
+ }
78
+
79
+ return entrypoint_json_data
80
+
81
+
82
+ def generate_bindings_content():
83
+ bindings_content = {"version": "2.0", "resources": []}
84
+
85
+ return bindings_content
86
+
87
+
88
+ def get_proposed_version(directory):
89
+ output_dir = os.path.join(directory, ".uipath")
90
+ if not os.path.exists(output_dir):
91
+ return None
92
+
93
+ # Get all .nupkg files
94
+ nupkg_files = [f for f in os.listdir(output_dir) if f.endswith(".nupkg")]
95
+ if not nupkg_files:
96
+ return None
97
+
98
+ # Sort by modification time to get most recent
99
+ latest_file = max(
100
+ nupkg_files, key=lambda f: os.path.getmtime(os.path.join(output_dir, f))
101
+ )
102
+
103
+ # Extract version from filename
104
+ # Remove .nupkg extension first
105
+ name_version = latest_file[:-6]
106
+ # Find 3rd last occurrence of . by splitting and joining parts
107
+ parts = name_version.split(".")
108
+ if len(parts) >= 3:
109
+ version = ".".join(parts[-3:])
110
+ else:
111
+ version = name_version
112
+
113
+ # Increment patch version by 1
114
+ try:
115
+ major, minor, patch = version.split(".")
116
+ new_version = f"{major}.{minor}.{int(patch) + 1}"
117
+ return new_version
118
+ except Exception:
119
+ return "0.0.1"
120
+
121
+
122
+ def generate_content_types_content():
123
+ templates_path = os.path.join(
124
+ os.path.dirname(__file__), "_templates", "[Content_Types].xml.template"
125
+ )
126
+ with open(templates_path, "r") as file:
127
+ content_types_content = file.read()
128
+ return content_types_content
129
+
130
+
131
+ def generate_nuspec_content(projectName, packageVersion, description, authors):
132
+ variables = {
133
+ "packageName": projectName,
134
+ "packageVersion": packageVersion,
135
+ "description": description,
136
+ "authors": authors,
137
+ }
138
+ templates_path = os.path.join(
139
+ os.path.dirname(__file__), "_templates", "package.nuspec.template"
140
+ )
141
+ with open(templates_path, "r", encoding="utf-8-sig") as f:
142
+ content = f.read()
143
+ return Template(content).substitute(variables)
144
+
145
+
146
+ def generate_rels_content(nuspecPath, psmdcpPath):
147
+ # /package/services/metadata/core-properties/254324ccede240e093a925f0231429a0.psmdcp
148
+ templates_path = os.path.join(
149
+ os.path.dirname(__file__), "_templates", ".rels.template"
150
+ )
151
+ nuspecId = "R" + str(uuid.uuid4()).replace("-", "")[:16]
152
+ psmdcpId = "R" + str(uuid.uuid4()).replace("-", "")[:16]
153
+ variables = {
154
+ "nuspecPath": nuspecPath,
155
+ "nuspecId": nuspecId,
156
+ "psmdcpPath": psmdcpPath,
157
+ "psmdcpId": psmdcpId,
158
+ }
159
+ with open(templates_path, "r", encoding="utf-8-sig") as f:
160
+ content = f.read()
161
+ return Template(content).substitute(variables)
162
+
163
+
164
+ def generate_psmdcp_content(projectName, version, description, authors):
165
+ templates_path = os.path.join(
166
+ os.path.dirname(__file__), "_templates", ".psmdcp.template"
167
+ )
168
+
169
+ token = str(uuid.uuid4()).replace("-", "")[:32]
170
+ random_file_name = f"{uuid.uuid4().hex[:16]}.psmdcp"
171
+ variables = {
172
+ "creator": authors,
173
+ "description": description,
174
+ "packageVersion": version,
175
+ "projectName": projectName,
176
+ "publicKeyToken": token,
177
+ }
178
+ with open(templates_path, "r", encoding="utf-8-sig") as f:
179
+ content = f.read()
180
+
181
+ return [random_file_name, Template(content).substitute(variables)]
182
+
183
+
184
+ def generate_package_descriptor_content(entryPoints):
185
+ files = {
186
+ "operate.json": "content/operate.json",
187
+ "entry-points.json": "content/entry-points.json",
188
+ "bindings.json": "content/bindings_v2.json",
189
+ }
190
+
191
+ for entry in entryPoints:
192
+ files[entry["filePath"]] = entry["filePath"]
193
+
194
+ package_descriptor_content = {
195
+ "$schema": "https://cloud.uipath.com/draft/2024-12/package-descriptor",
196
+ "files": files,
197
+ }
198
+
199
+ return package_descriptor_content
200
+
201
+
202
+ def pack_fn(projectName, description, entryPoints, version, authors, directory):
203
+ operate_file = generate_operate_file(entryPoints)
204
+ entrypoints_file = generate_entrypoints_file(entryPoints)
205
+
206
+ # Get bindings from uipath.json if available
207
+ config_path = os.path.join(directory, "uipath.json")
208
+ if os.path.exists(config_path):
209
+ with open(config_path, "r") as f:
210
+ config_data = json.load(f)
211
+ if "bindings" in config_data:
212
+ bindings_content = config_data["bindings"]
213
+ else:
214
+ # Fall back to generating bindings from the first entry point
215
+ bindings_content = generate_bindings_json(entryPoints[0]["filePath"])
216
+ else:
217
+ # Fall back to generating bindings from the first entry point
218
+ bindings_content = generate_bindings_json(entryPoints[0]["filePath"])
219
+
220
+ content_types_content = generate_content_types_content()
221
+ [psmdcp_file_name, psmdcp_content] = generate_psmdcp_content(
222
+ projectName, version, description, authors
223
+ )
224
+ nuspec_content = generate_nuspec_content(projectName, version, description, authors)
225
+ rels_content = generate_rels_content(
226
+ f"/{projectName}.nuspec",
227
+ f"/package/services/metadata/core-properties/{psmdcp_file_name}",
228
+ )
229
+ package_descriptor_content = generate_package_descriptor_content(entryPoints)
230
+
231
+ # Create .uipath directory if it doesn't exist
232
+ os.makedirs(".uipath", exist_ok=True)
233
+
234
+ # Define the allowlist of file extensions to include
235
+ file_extensions_allowlist = [".py", ".mermaid", ".json"]
236
+
237
+ with zipfile.ZipFile(
238
+ f".uipath/{projectName}.{version}.nupkg", "w", zipfile.ZIP_DEFLATED
239
+ ) as z:
240
+ # Add metadata files
241
+ z.writestr(
242
+ f"./package/services/metadata/core-properties/{psmdcp_file_name}",
243
+ psmdcp_content,
244
+ )
245
+ z.writestr("[Content_Types].xml", content_types_content)
246
+ z.writestr(
247
+ "content/package-descriptor.json",
248
+ json.dumps(package_descriptor_content, indent=4),
249
+ )
250
+ z.writestr("content/operate.json", json.dumps(operate_file, indent=4))
251
+ z.writestr("content/entry-points.json", json.dumps(entrypoints_file, indent=4))
252
+ z.writestr("content/bindings_v2.json", json.dumps(bindings_content, indent=4))
253
+ z.writestr(f"{projectName}.nuspec", nuspec_content)
254
+ z.writestr("_rels/.rels", rels_content)
255
+
256
+ # Walk through directory and add all files with extensions in the allowlist
257
+ for root, dirs, files in os.walk(directory):
258
+ # Skip all directories that start with .
259
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
260
+
261
+ for file in files:
262
+ file_extension = os.path.splitext(file)[1].lower()
263
+ if file_extension in file_extensions_allowlist:
264
+ file_path = os.path.join(root, file)
265
+ rel_path = os.path.relpath(file_path, directory)
266
+ try:
267
+ # Try UTF-8 first
268
+ with open(file_path, "r", encoding="utf-8") as f:
269
+ z.writestr(f"content/{rel_path}", f.read())
270
+ except UnicodeDecodeError:
271
+ # If UTF-8 fails, try with utf-8-sig (for files with BOM)
272
+ try:
273
+ with open(file_path, "r", encoding="utf-8-sig") as f:
274
+ z.writestr(f"content/{rel_path}", f.read())
275
+ except UnicodeDecodeError:
276
+ # If that also fails, try with latin-1 as a fallback
277
+ with open(file_path, "r", encoding="latin-1") as f:
278
+ z.writestr(f"content/{rel_path}", f.read())
279
+
280
+ optional_files = ["pyproject.toml", "README.md"]
281
+ for file in optional_files:
282
+ file_path = os.path.join(directory, file)
283
+ if os.path.exists(file_path):
284
+ try:
285
+ with open(file_path, "r", encoding="utf-8") as f:
286
+ z.writestr(f"content/{file}", f.read())
287
+ except UnicodeDecodeError:
288
+ with open(file_path, "r", encoding="latin-1") as f:
289
+ z.writestr(f"content/{file}", f.read())
290
+
291
+
292
+ def read_toml_project(file_path: str) -> dict[str, any]:
293
+ with open(file_path, "rb") as f:
294
+ content = tomllib.load(f)
295
+ return {
296
+ "name": content["project"]["name"],
297
+ "description": content["project"]["description"],
298
+ "version": content["project"]["version"],
299
+ "authors": content["project"].get("authors", [{"name": ""}])[0]["name"],
300
+ }
301
+
302
+
303
+ def get_project_version(directory):
304
+ toml_path = os.path.join(directory, "pyproject.toml")
305
+ if not os.path.exists(toml_path):
306
+ click.echo("Warning: No pyproject.toml found. Using default version 0.0.1")
307
+ return "0.0.1"
308
+ toml_data = read_toml_project(toml_path)
309
+ return toml_data["version"]
310
+
311
+
312
+ @click.command()
313
+ @click.argument("root", type=str, default="./")
314
+ def pack(root):
315
+ version = get_project_version(root)
316
+
317
+ while not os.path.isfile(os.path.join(root, "uipath.json")):
318
+ click.echo(
319
+ "uipath.json not found. Please run `uipath init` in the project directory."
320
+ )
321
+ return
322
+ config = check_config(root)
323
+ click.echo(
324
+ f"Packaging project {config['project_name']}:{version or config['version']} description {config['description']} authored by {config['authors']}"
325
+ )
326
+ pack_fn(
327
+ config["project_name"],
328
+ config["description"],
329
+ config["entryPoints"],
330
+ version or config["version"],
331
+ config["authors"],
332
+ root,
333
+ )
334
+
335
+
336
+ if __name__ == "__main__":
337
+ pack()
@@ -0,0 +1,113 @@
1
+ # type: ignore
2
+ import os
3
+
4
+ import click
5
+ import requests
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+
11
+ def get_most_recent_package():
12
+ nupkg_files = [f for f in os.listdir(".uipath") if f.endswith(".nupkg")]
13
+ if not nupkg_files:
14
+ click.echo("No .nupkg file found in .uipath directory")
15
+ return
16
+
17
+ # Get full path and modification time for each file
18
+ nupkg_files_with_time = [
19
+ (f, os.path.getmtime(os.path.join(".uipath", f))) for f in nupkg_files
20
+ ]
21
+
22
+ # Sort by modification time (most recent first)
23
+ nupkg_files_with_time.sort(key=lambda x: x[1], reverse=True)
24
+
25
+ # Get most recent file
26
+ return nupkg_files_with_time[0][0]
27
+
28
+
29
+ def get_env_vars():
30
+ base_url = os.environ.get("UIPATH_URL")
31
+ token = os.environ.get("UIPATH_ACCESS_TOKEN")
32
+
33
+ if not all([base_url, token]):
34
+ click.echo(
35
+ "Missing required environment variables. Please check your .env file contains:"
36
+ )
37
+ click.echo("UIPATH_URL, UIPATH_ACCESS_TOKEN")
38
+ raise click.Abort("Missing environment variables")
39
+
40
+ return [base_url, token]
41
+
42
+
43
+ @click.command()
44
+ @click.option(
45
+ "--tenant",
46
+ "-t",
47
+ "feed",
48
+ flag_value="tenant",
49
+ help="Whether to publish to the tenant package feed",
50
+ )
51
+ @click.option(
52
+ "--personal-workspace",
53
+ "-p",
54
+ "feed",
55
+ flag_value="personal",
56
+ help="Whether to publish to the personal workspace",
57
+ )
58
+ def publish(feed):
59
+ if feed is None:
60
+ click.echo("Select feed type:")
61
+ click.echo(" 0: Tenant package feed")
62
+ click.echo(" 1: Personal workspace")
63
+ feed_idx = click.prompt("Select feed", type=int)
64
+ feed = "tenant" if feed_idx == 0 else "personal"
65
+ click.echo(f"Selected feed: {feed}")
66
+ os.makedirs(".uipath", exist_ok=True)
67
+
68
+ # Find most recent .nupkg file in .uipath directory
69
+ most_recent = get_most_recent_package()
70
+
71
+ if not most_recent:
72
+ click.echo("Error: No package files found in .uipath directory")
73
+ raise click.Abort()
74
+ click.echo(f"Publishing most recent package: {most_recent}")
75
+
76
+ package_to_publish_path = os.path.join(".uipath", most_recent)
77
+
78
+ [base_url, token] = get_env_vars()
79
+
80
+ url = f"{base_url}/orchestrator_/odata/Processes/UiPath.Server.Configuration.OData.UploadPackage()"
81
+
82
+ if feed == "personal":
83
+ # Get current user extended info to get personal workspace ID
84
+ user_url = f"{base_url}/orchestrator_/odata/Users/UiPath.Server.Configuration.OData.GetCurrentUserExtended"
85
+ user_response = requests.get(
86
+ user_url, headers={"Authorization": f"Bearer {token}"}
87
+ )
88
+
89
+ if user_response.status_code != 200:
90
+ click.echo("Failed to get user info")
91
+ click.echo(f"Response: {user_response.text}")
92
+ raise click.Abort()
93
+
94
+ user_data = user_response.json()
95
+ personal_workspace_id = user_data.get("PersonalWorskpaceFeedId")
96
+
97
+ if not personal_workspace_id:
98
+ click.echo("No personal workspace found for user")
99
+ raise click.Abort()
100
+
101
+ url = url + "?feedId=" + personal_workspace_id
102
+
103
+ headers = {"Authorization": f"Bearer {token}"}
104
+
105
+ with open(package_to_publish_path, "rb") as f:
106
+ files = {"file": (package_to_publish_path, f, "application/octet-stream")}
107
+ response = requests.post(url, headers=headers, files=files)
108
+
109
+ if response.status_code == 200:
110
+ click.echo("Package published successfully!")
111
+ else:
112
+ click.echo(f"Failed to publish package. Status code: {response.status_code}")
113
+ click.echo(f"Response: {response.text}")