@uipath/packager-tool-functions 0.0.8 → 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @uipath/packager-tool-functions
2
+
3
+ UiPath solution packager plugin for `ProjectType.Function`. Used by Studio Web to build and pack JS/TS and Python Functions projects.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Studio Web publish (JS/TS)
9
+ → FunctionsTool.buildAsync() ← this package
10
+ → buildJsFunctionsPackage() ← src/js (thin wrapper)
11
+ → buildFunctionsPackage() ← @uipath/coded-functions-js-packager
12
+
13
+ Studio Web publish (Python)
14
+ → FunctionsTool.buildAsync() ← this package
15
+ → buildPyFunctionsPackage() ← src/py (local implementation)
16
+
17
+ CLI: uip functions pack (JS/TS)
18
+ → uipath-functions pack ← @uipath/uipath-functions CLI
19
+ → buildFunctionsPackage() ← same shared library
20
+
21
+ CLI: uip functions pack (Python)
22
+ → uipath pack ← Python CLI subprocess
23
+ ```
24
+
25
+ ## How it works
26
+
27
+ `FunctionsTool` implements `ProjectTool` for `ProjectType.Function`. On `buildAsync`:
28
+
29
+ 1. Detects language from `uipath.json` functions map (`.ts`/`.js`/`.mjs` → javascript, `.py` → python); fallback: `pyproject.toml` presence → python.
30
+ 2. **JS/TS**: delegates to `buildFunctionsPackage(fs, options)` from `@uipath/coded-functions-js-packager` (externally-published library, shared with the `uipath-functions` CLI).
31
+ 3. **Python**: delegates to the local `buildPyFunctionsPackage(fs, options)` implementation in [`src/py/`](src/py/).
32
+
33
+ ### Language detection
34
+
35
+ | Signal | Result |
36
+ |---|---|
37
+ | `uipath.json` functions map extension `.ts`/`.js`/`.mjs` | javascript |
38
+ | `uipath.json` functions map extension `.py` | python |
39
+ | `pyproject.toml` present | python (fallback) |
40
+ | Neither | javascript (default) |
41
+
42
+ ## Python build pipeline
43
+
44
+ `buildPyFunctionsPackage` produces a fully-formed nupkg content folder from the Python project sources. Pipeline:
45
+
46
+ 1. **Validate inputs** — requires `uipath.json` and `entry-points.json` at the project root. Throws a readable error if either is missing.
47
+ 2. **Copy source files** to `content/`, filtered by [`EXCLUDED_NAMES`](src/py/constants.ts) (`agent.json`, `project.uiproj`, `bindings.json`, `entry-points.json`, OS metadata) and [`EXCLUDED_DIRECTORIES`](src/py/constants.ts) (virtualenvs, caches, editor dirs, VCS dirs). `bindings.json` and `entry-points.json` are excluded because the packager re-emits canonical versions — see steps 3 and 5.
48
+ 3. **Normalize `content/entry-points.json`** — reads the source file and rewrites it with:
49
+ - Canonical `$schema` (`https://cloud.uipath.com/draft/2024-12/entry-point`) and `$id` (`entry-points.json`).
50
+ - Per-entry `uniqueId` — backfilled with `crypto.randomUUID()` when the source is missing it.
51
+ - Per-entry `type` — defaults to `"function"` when missing.
52
+ - All other fields (`filePath`, `input`, `output`, etc.) are passed through.
53
+ 4. **Write `content/operate.json`** — `$schema` = `operate`, `contentType` = `"function"`, `targetRuntime` = `"python"`, `main` = first entry-point's `filePath`, `projectId` = the platform-assigned storage id.
54
+ 5. **Write `content/bindings_v2.json`** — uses the source `bindings.json` if present and parseable; otherwise emits the default `{ $schema, version: "2.0", resources: [] }`.
55
+ 6. **Write `content/package-descriptor.json`** — registers `operate.json`, `entry-points.json`, and `bindings.json` (the latter pointing at `content/bindings_v2.json`).
56
+
57
+ ### Expected source-project shape
58
+
59
+ The upstream tool (`uipath init` / Function Builder MFE) is responsible for producing a valid source layout before this packager runs. In particular:
60
+
61
+ - `uipath.json.functions` — map of entrypoint key → `"<file>.py:<python-function>"` (the runtime uses this to resolve the function body).
62
+ - `entry-points.json` — each entry's `filePath` must equal the functions-map key. The packager will fill in `uniqueId` and `type: "function"` if they're missing, but nothing can recover the real function-name mapping — that lives in `uipath.json.functions`.
63
+ - The Python function must take exactly one parameter typed as a dataclass, Pydantic model, or annotated class; the UiPath runtime calls `func(input)` with a single argument and converts the input dict into that type.
64
+
65
+
66
+ ## Project structure
67
+
68
+ ```
69
+ src/
70
+ ├── functions-tool.ts # ProjectTool implementation (language detection, build/pack lifecycle)
71
+ ├── functions-tool-factory.ts # Factory for the tool loader
72
+ ├── js/ # JS/TS build path (delegates to @uipath/coded-functions-js-packager)
73
+ ├── py/ # Python build path
74
+ │ ├── build-py-functions-package.ts
75
+ │ ├── constants.ts # All Python-packager constants (schemas, file names, exclusions)
76
+ │ └── index.ts
77
+ ├── types.ts # BuildPackageOptions, Language enum, labels
78
+ └── index.ts
79
+ ```
80
+
81
+ ## Development
82
+
83
+ ```bash
84
+ bun install
85
+ bun run build # bun build → dist/ (ESM, browser target)
86
+ bun run test # vitest
87
+ bun run lint # biome check
88
+ ```
@@ -1,6 +1,6 @@
1
1
  import { type IFileSystem, type IProjectToolFactory, type IToolLogger, type ProjectTool, type ProjectType } from "@uipath/solutionpackager-tool-core";
2
2
  /**
3
- * Factory for creating JS Functions project tools.
3
+ * Factory for creating Functions project tools.
4
4
  * Handles projects with projectType "Function".
5
5
  */
6
6
  export declare class FunctionsToolFactory implements IProjectToolFactory {
@@ -8,11 +8,5 @@ export declare class FunctionsTool extends ProjectTool {
8
8
  buildAsync(options: IProjectBuildOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
9
9
  packAsync(options: IProjectPackOptions, cancellationToken?: AbortSignal): Promise<ToolResult>;
10
10
  dispose(): Promise<void>;
11
- private detectRuntime;
12
- private copyFiles;
13
- private isExcluded;
14
- private createOperateFile;
15
- private createOperateJsonFile;
16
- private createPackageDescriptor;
17
- private addFileToDescriptor;
11
+ private detectLanguage;
18
12
  }
package/dist/index.js CHANGED
@@ -8,23 +8,334 @@ import {
8
8
 
9
9
  // src/functions-tool.ts
10
10
  import {
11
- NugetConstants,
11
+ NugetConstants as NugetConstants2,
12
12
  NugetPackager,
13
- Path,
13
+ Path as Path2,
14
14
  ProjectTool,
15
15
  TemporaryStorageService,
16
16
  ToolErrorCodes,
17
17
  ToolResult
18
18
  } from "@uipath/solutionpackager-tool-core";
19
- var EXCLUDED_NAMES = new Set([".deno", ".env"]);
19
+
20
+ // ../../../node_modules/@uipath/coded-functions-js-packager/dist/index.js
21
+ var EXCLUDED_NAMES = new Set([
22
+ "node_modules",
23
+ ".git",
24
+ ".env",
25
+ ".deno",
26
+ "dist",
27
+ ".uip-dbg",
28
+ ".uipath"
29
+ ]);
20
30
  var EXCLUDED_EXTENSIONS = [".nupkg"];
21
- var EXTENSION_TO_RUNTIME = {
22
- ".ts": "javascript",
23
- ".js": "javascript",
24
- ".py": "python"
31
+ var LOCK_FILES = new Set(["bun.lock", "package-lock.json", "deno.lock", "yarn.lock"]);
32
+ async function buildFunctionsPackage(fs, options) {
33
+ const { projectPath, outputPath, projectStorageId } = options;
34
+ const contentPath = fs.path.join(outputPath, "content");
35
+ await fs.mkdir(outputPath);
36
+ await fs.mkdir(contentPath);
37
+ const uipathJsonRaw = await fs.readFile(fs.path.join(projectPath, "uipath.json"), "utf-8");
38
+ if (!uipathJsonRaw) {
39
+ throw new Error("uipath.json not found. Run 'uipath-functions init' first.");
40
+ }
41
+ const uipathJson = JSON.parse(uipathJsonRaw);
42
+ const entryPointsRaw = await fs.readFile(fs.path.join(projectPath, "entry-points.json"), "utf-8");
43
+ if (!entryPointsRaw) {
44
+ throw new Error("entry-points.json not found. Run 'uipath-functions pack' to generate it.");
45
+ }
46
+ const entryPoints = JSON.parse(entryPointsRaw);
47
+ const bindingsRaw = await fs.readFile(fs.path.join(projectPath, "bindings.json"), "utf-8");
48
+ const bindings = bindingsRaw ? JSON.parse(bindingsRaw) : {
49
+ $schema: "https://cloud.uipath.com/draft/2024-12/bindings",
50
+ version: "2.0",
51
+ resources: []
52
+ };
53
+ const packageJsonRaw = await fs.readFile(fs.path.join(projectPath, "package.json"), "utf-8");
54
+ const packageDependencies = packageJsonRaw ? JSON.parse(packageJsonRaw).dependencies ?? {} : {};
55
+ const targetRuntime = detectRuntime(uipathJson.functions);
56
+ const includeLockFile = options.excludeLockFile ? false : uipathJson.packOptions?.includeLockFile ?? true;
57
+ await copyProjectFiles(fs, projectPath, contentPath, includeLockFile, uipathJson.packOptions);
58
+ const mainPath = entryPoints.entryPoints[0]?.filePath ?? "";
59
+ const operateJson = {
60
+ $schema: "https://cloud.uipath.com/draft/2024-12/operate",
61
+ projectId: projectStorageId,
62
+ main: mainPath,
63
+ contentType: "function",
64
+ targetFramework: "Portable",
65
+ targetRuntime,
66
+ runtimeOptions: {
67
+ requiresUserInteraction: false,
68
+ isAttended: false
69
+ },
70
+ dependencies: packageDependencies
71
+ };
72
+ await fs.writeFile(fs.path.join(contentPath, "operate.json"), JSON.stringify(operateJson, null, 2));
73
+ await fs.writeFile(fs.path.join(contentPath, "entry-points.json"), JSON.stringify(entryPoints, null, 2));
74
+ await fs.writeFile(fs.path.join(contentPath, "bindings_v2.json"), JSON.stringify(bindings, null, 2));
75
+ const packageDescriptor = {
76
+ $schema: "https://cloud.uipath.com/draft/2024-12/package-descriptor",
77
+ files: {
78
+ "operate.json": "content/operate.json",
79
+ "entry-points.json": "content/entry-points.json",
80
+ "bindings.json": "content/bindings_v2.json"
81
+ }
82
+ };
83
+ for (const ep of entryPoints.entryPoints) {
84
+ const fnEntry = uipathJson.functions?.[ep.filePath];
85
+ const filePath = fnEntry ? fnEntry.split(":")[0] ?? ep.filePath : ep.filePath;
86
+ packageDescriptor.files[ep.filePath] = `content/${filePath}`;
87
+ }
88
+ await fs.writeFile(fs.path.join(contentPath, "package-descriptor.json"), JSON.stringify(packageDescriptor, null, 2));
89
+ }
90
+ function detectRuntime(functions) {
91
+ if (!functions)
92
+ return "javascript";
93
+ const first = Object.values(functions)[0];
94
+ if (!first)
95
+ return "javascript";
96
+ const filePath = first.split(":")[0] ?? "";
97
+ const ext = filePath.slice(filePath.lastIndexOf("."));
98
+ if (ext === ".py")
99
+ return "python";
100
+ return "javascript";
101
+ }
102
+ async function copyProjectFiles(fs, sourcePath, destPath, includeLockFile, packOptions) {
103
+ const extraExcludedDirs = new Set(packOptions?.directoriesExcluded ?? []);
104
+ const extraExcludedFiles = new Set(packOptions?.filesExcluded ?? []);
105
+ async function walk(src, dest) {
106
+ await fs.mkdir(dest);
107
+ const entries = await fs.readdir(src);
108
+ for (const name of entries) {
109
+ if (EXCLUDED_NAMES.has(name))
110
+ continue;
111
+ if (extraExcludedDirs.has(name))
112
+ continue;
113
+ if (extraExcludedFiles.has(name))
114
+ continue;
115
+ if (EXCLUDED_EXTENSIONS.some((ext) => name.endsWith(ext)))
116
+ continue;
117
+ if (!includeLockFile && LOCK_FILES.has(name))
118
+ continue;
119
+ const srcEntry = fs.path.join(src, name);
120
+ const destEntry = fs.path.join(dest, name);
121
+ const stat = await fs.stat(srcEntry);
122
+ if (stat?.isDirectory()) {
123
+ await walk(srcEntry, destEntry);
124
+ } else if (stat?.isFile()) {
125
+ const content = await fs.readFile(srcEntry);
126
+ if (content)
127
+ await fs.writeFile(destEntry, content);
128
+ }
129
+ }
130
+ }
131
+ await walk(sourcePath, destPath);
132
+ }
133
+
134
+ // src/js/build-js-functions-package.ts
135
+ async function buildJsFunctionsPackage(fileSystem, options) {
136
+ await buildFunctionsPackage(fileSystem, {
137
+ projectPath: options.projectPath,
138
+ outputPath: options.outputPath,
139
+ projectStorageId: options.projectStorageId
140
+ });
141
+ }
142
+ // src/py/build-py-functions-package.ts
143
+ import { NugetConstants, Path } from "@uipath/solutionpackager-tool-core";
144
+
145
+ // src/py/constants.ts
146
+ var EXCLUDED_NAMES2 = new Set([
147
+ "agent.json",
148
+ "project.uiproj",
149
+ "bindings.json",
150
+ "entry-points.json",
151
+ ".DS_Store",
152
+ "Thumbs.db"
153
+ ]);
154
+ var EXCLUDED_DIRECTORIES = new Set([
155
+ "evaluations",
156
+ "evals",
157
+ ".function-builder",
158
+ "node_modules",
159
+ "__pycache__",
160
+ ".pytest_cache",
161
+ ".venv",
162
+ "venv",
163
+ "env",
164
+ ".git",
165
+ ".vscode",
166
+ ".idea",
167
+ ".local"
168
+ ]);
169
+ var CONTENT_TYPE = "function";
170
+ var TARGET_RUNTIME = "python";
171
+ var UIPATH_JSON_FILE_NAME = "uipath.json";
172
+ var BINDINGS_SOURCE_FILE_NAME = "bindings.json";
173
+ var BINDINGS_VERSION = "2.0";
174
+ var ENTRY_POINT_TYPE = "function";
175
+ var SCHEMA_BASE_URL = "https://cloud.uipath.com/draft/2024-12";
176
+ var SCHEMAS = {
177
+ Bindings: `${SCHEMA_BASE_URL}/bindings`,
178
+ EntryPoint: `${SCHEMA_BASE_URL}/entry-point`,
179
+ Operate: `${SCHEMA_BASE_URL}/operate`,
180
+ PackageDescriptor: `${SCHEMA_BASE_URL}/package-descriptor`
25
181
  };
26
- var DEFAULT_RUNTIME = "javascript";
27
182
 
183
+ // src/py/build-py-functions-package.ts
184
+ async function buildPyFunctionsPackage(fileSystem, options) {
185
+ const contentFolder = Path.join(options.outputPath, NugetConstants.ContentFolderName);
186
+ await validateProjectFiles(fileSystem, options.projectPath);
187
+ await copyFiles(fileSystem, options.projectPath, contentFolder);
188
+ await writeEntryPointsFile(fileSystem, options.projectPath, contentFolder);
189
+ await createOperateFile(fileSystem, contentFolder, options.projectStorageId);
190
+ await createBindingsFile(fileSystem, options.projectPath, contentFolder);
191
+ await createPackageDescriptor(fileSystem, options.outputPath);
192
+ }
193
+ async function validateProjectFiles(fileSystem, projectPath) {
194
+ const uipathJsonPath = Path.join(projectPath, UIPATH_JSON_FILE_NAME);
195
+ const entryPointsPath = Path.join(projectPath, NugetConstants.EntryPointsFileName);
196
+ if (!await fileSystem.exists(uipathJsonPath)) {
197
+ throw new Error("uipath.json is missing from the project.");
198
+ }
199
+ if (!await fileSystem.exists(entryPointsPath)) {
200
+ throw new Error("entry-points.json is missing from the project.");
201
+ }
202
+ }
203
+ async function copyFiles(fileSystem, sourcePath, destinationPath) {
204
+ await fileSystem.mkdir(destinationPath);
205
+ const entries = await fileSystem.readdir(sourcePath);
206
+ for (const entry of entries) {
207
+ const sourceFile = Path.join(sourcePath, entry);
208
+ const destinationFile = Path.join(destinationPath, entry);
209
+ const stat = await fileSystem.stat(sourceFile);
210
+ if (stat?.isFile()) {
211
+ if (EXCLUDED_NAMES2.has(entry)) {
212
+ continue;
213
+ }
214
+ const content = await fileSystem.readFile(sourceFile);
215
+ if (content) {
216
+ await fileSystem.writeFile(destinationFile, content);
217
+ }
218
+ } else if (stat?.isDirectory()) {
219
+ if (EXCLUDED_DIRECTORIES.has(entry)) {
220
+ continue;
221
+ }
222
+ await copyFiles(fileSystem, sourceFile, destinationFile);
223
+ }
224
+ }
225
+ }
226
+ async function createOperateFile(fileSystem, contentFolder, projectStorageId) {
227
+ const operateJsonFilePath = Path.join(contentFolder, NugetConstants.OperateFileName);
228
+ const exists = await fileSystem.exists(operateJsonFilePath);
229
+ if (exists) {
230
+ return;
231
+ }
232
+ const entryPointsFilePath = Path.join(contentFolder, NugetConstants.EntryPointsFileName);
233
+ let mainPath = "";
234
+ const entryPointsExists = await fileSystem.exists(entryPointsFilePath);
235
+ if (entryPointsExists) {
236
+ const entryPointsContent = await fileSystem.readFile(entryPointsFilePath);
237
+ if (entryPointsContent) {
238
+ try {
239
+ const entryPointsText = new TextDecoder().decode(entryPointsContent);
240
+ const entryPoints = JSON.parse(entryPointsText);
241
+ mainPath = entryPoints.entryPoints?.[0]?.filePath ?? "";
242
+ } catch {}
243
+ }
244
+ }
245
+ const operateFileModel = {
246
+ $schema: SCHEMAS.Operate,
247
+ contentType: CONTENT_TYPE,
248
+ projectId: projectStorageId,
249
+ main: mainPath,
250
+ targetFramework: "Portable",
251
+ targetRuntime: TARGET_RUNTIME,
252
+ runtimeOptions: {
253
+ isAttended: false,
254
+ requiresUserInteraction: false,
255
+ isConversational: false
256
+ }
257
+ };
258
+ const operateJsonString = JSON.stringify(operateFileModel, null, 2);
259
+ await fileSystem.writeFile(operateJsonFilePath, operateJsonString);
260
+ }
261
+ async function writeEntryPointsFile(fileSystem, projectPath, contentFolder) {
262
+ const sourcePath = Path.join(projectPath, NugetConstants.EntryPointsFileName);
263
+ const destinationPath = Path.join(contentFolder, NugetConstants.EntryPointsFileName);
264
+ const raw = await fileSystem.readFile(sourcePath);
265
+ if (!raw) {
266
+ throw new Error("entry-points.json is missing from the project.");
267
+ }
268
+ let parsed;
269
+ try {
270
+ parsed = JSON.parse(new TextDecoder().decode(raw));
271
+ } catch {
272
+ throw new Error("entry-points.json is not valid JSON.");
273
+ }
274
+ const sourceEntries = Array.isArray(parsed.entryPoints) ? parsed.entryPoints : [];
275
+ const normalizedEntries = sourceEntries.map((entry) => ({
276
+ ...entry,
277
+ uniqueId: typeof entry.uniqueId === "string" && entry.uniqueId.length > 0 ? entry.uniqueId : crypto.randomUUID(),
278
+ type: typeof entry.type === "string" && entry.type.length > 0 ? entry.type : ENTRY_POINT_TYPE
279
+ }));
280
+ const normalized = {
281
+ $schema: SCHEMAS.EntryPoint,
282
+ $id: NugetConstants.EntryPointsFileName,
283
+ entryPoints: normalizedEntries
284
+ };
285
+ await fileSystem.writeFile(destinationPath, JSON.stringify(normalized, null, 2));
286
+ }
287
+ async function createBindingsFile(fileSystem, projectPath, contentFolder) {
288
+ const bindingsOutputPath = Path.join(contentFolder, NugetConstants.BindingsV2FileName);
289
+ const sourcePath = Path.join(projectPath, BINDINGS_SOURCE_FILE_NAME);
290
+ let bindings = null;
291
+ if (await fileSystem.exists(sourcePath)) {
292
+ const raw = await fileSystem.readFile(sourcePath);
293
+ if (raw) {
294
+ try {
295
+ bindings = JSON.parse(new TextDecoder().decode(raw));
296
+ } catch {
297
+ bindings = null;
298
+ }
299
+ }
300
+ }
301
+ if (bindings === null) {
302
+ bindings = {
303
+ $schema: SCHEMAS.Bindings,
304
+ version: BINDINGS_VERSION,
305
+ resources: []
306
+ };
307
+ }
308
+ await fileSystem.writeFile(bindingsOutputPath, JSON.stringify(bindings, null, 2));
309
+ }
310
+ async function createPackageDescriptor(fileSystem, localBuildFolder) {
311
+ const packageDescriptor = {
312
+ files: {}
313
+ };
314
+ addFileToDescriptor(packageDescriptor, NugetConstants.OperateFileName, NugetConstants.OperateFileName);
315
+ addFileToDescriptor(packageDescriptor, NugetConstants.EntryPointsFileName, NugetConstants.EntryPointsFileName);
316
+ addFileToDescriptor(packageDescriptor, NugetConstants.BindingsFileId, NugetConstants.BindingsV2FileName);
317
+ const packageDescriptorPath = Path.join(localBuildFolder, NugetConstants.ContentFolderName, NugetConstants.PackageDescriptorFileName);
318
+ const packageDescriptorJson = JSON.stringify({
319
+ $schema: SCHEMAS.PackageDescriptor,
320
+ ...packageDescriptor
321
+ }, null, 2);
322
+ await fileSystem.writeFile(packageDescriptorPath, packageDescriptorJson);
323
+ }
324
+ function addFileToDescriptor(packageDescriptor, key, fileName) {
325
+ const relativePath = Path.join(NugetConstants.ContentFolderName, fileName);
326
+ packageDescriptor.files[key] = relativePath;
327
+ }
328
+ // src/types.ts
329
+ var Languages = {
330
+ JavaScript: "javascript",
331
+ Python: "python"
332
+ };
333
+ var LanguageLabels = {
334
+ [Languages.JavaScript]: "JS/TS",
335
+ [Languages.Python]: "Python"
336
+ };
337
+
338
+ // src/functions-tool.ts
28
339
  class FunctionsTool extends ProjectTool {
29
340
  _temporaryStorage;
30
341
  constructor(fileSystem, logger) {
@@ -40,21 +351,23 @@ class FunctionsTool extends ProjectTool {
40
351
  return ToolResult.success();
41
352
  }
42
353
  async buildAsync(options, _cancellationToken) {
354
+ const language = await this.detectLanguage(options.projectPath);
43
355
  const tempFolder = await this._temporaryStorage.getTempFolderPath();
44
- const localBuildFolder = Path.join(tempFolder, NugetConstants.OutputFolderName);
45
- const contentFolder = Path.join(localBuildFolder, NugetConstants.ContentFolderName);
356
+ const localBuildFolder = Path2.join(tempFolder, NugetConstants2.OutputFolderName);
357
+ const build = language === Languages.Python ? buildPyFunctionsPackage : buildJsFunctionsPackage;
358
+ const languageLabel = LanguageLabels[language];
46
359
  try {
47
- this.logger.progress("Copying project files...");
48
- await this.copyFiles(options.projectPath, contentFolder);
49
- this.logger.progress("Creating operate.json...");
50
- await this.createOperateFile(options, contentFolder);
51
- this.logger.progress("Creating package-descriptor.json...");
52
- await this.createPackageDescriptor(localBuildFolder);
360
+ this.logger.progress(`Building ${languageLabel} Functions package...`);
361
+ await build(this.fileSystem, {
362
+ projectPath: options.projectPath,
363
+ outputPath: localBuildFolder,
364
+ projectStorageId: options.projectStorageId ?? ""
365
+ });
53
366
  return new ToolResult(ToolErrorCodes.Success, "Build completed successfully.", [localBuildFolder]);
54
367
  } catch (error) {
55
368
  const errorMessage = error instanceof Error ? error.toString() : String(error);
56
369
  this.logger.error(errorMessage);
57
- return ToolResult.error(ToolErrorCodes.InternalError, "Build failed for Functions project.");
370
+ return ToolResult.error(ToolErrorCodes.InternalError, `Build failed for ${languageLabel} Functions project.`);
58
371
  }
59
372
  }
60
373
  async packAsync(options, cancellationToken) {
@@ -66,7 +379,7 @@ class FunctionsTool extends ProjectTool {
66
379
  try {
67
380
  this.logger.progress("Creating NuGet package...");
68
381
  const nupkgFileName = `${options.package.id}.${options.package.version}.nupkg`;
69
- const nupkgPath = Path.join(options.outputPath, nupkgFileName);
382
+ const nupkgPath = Path2.join(options.outputPath, nupkgFileName);
70
383
  const packager = new NugetPackager(this.fileSystem);
71
384
  const result = await packager.packAsync(localBuildFolder, options.package, nupkgPath);
72
385
  this.logger.progress("Package created successfully.");
@@ -83,101 +396,26 @@ class FunctionsTool extends ProjectTool {
83
396
  await this._temporaryStorage.cleanup();
84
397
  } catch {}
85
398
  }
86
- async detectRuntime(projectPath) {
87
- const uipathJsonPath = Path.join(projectPath, "uipath.json");
88
- const content = await this.fileSystem.readFile(uipathJsonPath);
89
- if (content) {
399
+ async detectLanguage(projectPath) {
400
+ const uipathJsonContent = await this.fileSystem.readFile(Path2.join(projectPath, "uipath.json"), "utf-8");
401
+ if (uipathJsonContent) {
90
402
  try {
91
- const uipathJson = JSON.parse(new TextDecoder().decode(content));
92
- const functions = uipathJson?.functions;
93
- const firstEntry = functions ? Object.values(functions)[0] : undefined;
403
+ const uipathJson = JSON.parse(uipathJsonContent);
404
+ const firstEntry = Object.values(uipathJson.functions ?? {})[0];
94
405
  if (firstEntry) {
95
- const filePath = firstEntry.split(":")[0];
406
+ const filePath = firstEntry.split(":")[0] ?? "";
96
407
  const ext = filePath.slice(filePath.lastIndexOf("."));
97
- const runtime = EXTENSION_TO_RUNTIME[ext];
98
- if (runtime) {
99
- return runtime;
100
- }
408
+ if (ext === ".ts" || ext === ".js" || ext === ".mjs")
409
+ return Languages.JavaScript;
410
+ if (ext === ".py")
411
+ return Languages.Python;
101
412
  }
102
413
  } catch {}
103
414
  }
104
- return DEFAULT_RUNTIME;
105
- }
106
- async copyFiles(sourcePath, destinationPath) {
107
- await this.fileSystem.mkdir(destinationPath);
108
- const entries = await this.fileSystem.readdir(sourcePath);
109
- for (const entry of entries) {
110
- if (this.isExcluded(entry)) {
111
- continue;
112
- }
113
- const sourceFile = Path.join(sourcePath, entry);
114
- const destinationFile = Path.join(destinationPath, entry);
115
- const stat = await this.fileSystem.stat(sourceFile);
116
- if (stat?.isFile()) {
117
- const content = await this.fileSystem.readFile(sourceFile);
118
- if (content) {
119
- await this.fileSystem.writeFile(destinationFile, content);
120
- }
121
- } else if (stat?.isDirectory()) {
122
- await this.copyFiles(sourceFile, destinationFile);
123
- }
124
- }
125
- }
126
- isExcluded(name) {
127
- if (EXCLUDED_NAMES.has(name)) {
128
- return true;
129
- }
130
- return EXCLUDED_EXTENSIONS.some((ext) => name.endsWith(ext));
131
- }
132
- async createOperateFile(options, contentFolder) {
133
- const operateJsonFilePath = Path.join(contentFolder, NugetConstants.OperateFileName);
134
- const exists = await this.fileSystem.exists(operateJsonFilePath);
135
- if (!exists) {
136
- const targetRuntime = await this.detectRuntime(options.projectPath);
137
- await this.createOperateJsonFile(contentFolder, options.projectStorageId ?? "", operateJsonFilePath, targetRuntime);
138
- }
139
- }
140
- async createOperateJsonFile(contentFolder, projectId, filePath, targetRuntime) {
141
- const entryPointsFilePath = Path.join(contentFolder, NugetConstants.EntryPointsFileName);
142
- let mainPath = "";
143
- const entryPointsExists = await this.fileSystem.exists(entryPointsFilePath);
144
- if (entryPointsExists) {
145
- const entryPointsContent = await this.fileSystem.readFile(entryPointsFilePath);
146
- if (entryPointsContent) {
147
- try {
148
- const entryPointsText = new TextDecoder().decode(entryPointsContent);
149
- const entryPoints = JSON.parse(entryPointsText);
150
- mainPath = entryPoints.entryPoints?.[0]?.filePath ?? "";
151
- } catch {}
152
- }
153
- }
154
- const operateFileModel = {
155
- $schema: "https://cloud.uipath.com/draft/2024-12/operate",
156
- contentType: "function",
157
- projectId,
158
- main: mainPath,
159
- targetRuntime
160
- };
161
- const operateJsonString = JSON.stringify(operateFileModel, null, 2);
162
- await this.fileSystem.writeFile(filePath, operateJsonString);
163
- }
164
- async createPackageDescriptor(localBuildFolder) {
165
- const packageDescriptor = {
166
- files: {}
167
- };
168
- this.addFileToDescriptor(packageDescriptor, NugetConstants.OperateFileName, NugetConstants.OperateFileName);
169
- this.addFileToDescriptor(packageDescriptor, NugetConstants.EntryPointsFileName, NugetConstants.EntryPointsFileName);
170
- this.addFileToDescriptor(packageDescriptor, NugetConstants.BindingsFileId, NugetConstants.BindingsV2FileName);
171
- const packageDescriptorPath = Path.join(localBuildFolder, NugetConstants.ContentFolderName, NugetConstants.PackageDescriptorFileName);
172
- const packageDescriptorJson = JSON.stringify({
173
- $schema: "https://cloud.uipath.com/draft/2024-12/package-descriptor",
174
- ...packageDescriptor
175
- }, null, 2);
176
- await this.fileSystem.writeFile(packageDescriptorPath, packageDescriptorJson);
177
- }
178
- addFileToDescriptor(packageDescriptor, key, fileName) {
179
- const relativePath = Path.join(NugetConstants.ContentFolderName, fileName);
180
- packageDescriptor.files[key] = relativePath;
415
+ const pyprojectStat = await this.fileSystem.stat(Path2.join(projectPath, "pyproject.toml"));
416
+ if (pyprojectStat?.isFile())
417
+ return Languages.Python;
418
+ return Languages.JavaScript;
181
419
  }
182
420
  }
183
421
 
@@ -0,0 +1,3 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ import type { BuildPackageOptions } from "../types.js";
3
+ export declare function buildJsFunctionsPackage(fileSystem: IFileSystem, options: BuildPackageOptions): Promise<void>;
@@ -0,0 +1 @@
1
+ export { buildJsFunctionsPackage } from "./build-js-functions-package.js";
@@ -0,0 +1,3 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ import type { BuildPackageOptions } from "../types.js";
3
+ export declare function buildPyFunctionsPackage(fileSystem: IFileSystem, options: BuildPackageOptions): Promise<void>;
@@ -0,0 +1,14 @@
1
+ export declare const EXCLUDED_NAMES: Set<string>;
2
+ export declare const EXCLUDED_DIRECTORIES: Set<string>;
3
+ export declare const CONTENT_TYPE = "function";
4
+ export declare const TARGET_RUNTIME = "python";
5
+ export declare const UIPATH_JSON_FILE_NAME = "uipath.json";
6
+ export declare const BINDINGS_SOURCE_FILE_NAME = "bindings.json";
7
+ export declare const BINDINGS_VERSION = "2.0";
8
+ export declare const ENTRY_POINT_TYPE = "function";
9
+ export declare const SCHEMAS: {
10
+ readonly Bindings: "https://cloud.uipath.com/draft/2024-12/bindings";
11
+ readonly EntryPoint: "https://cloud.uipath.com/draft/2024-12/entry-point";
12
+ readonly Operate: "https://cloud.uipath.com/draft/2024-12/operate";
13
+ readonly PackageDescriptor: "https://cloud.uipath.com/draft/2024-12/package-descriptor";
14
+ };
@@ -0,0 +1 @@
1
+ export { buildPyFunctionsPackage } from "./build-py-functions-package.js";
@@ -0,0 +1,13 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ export declare const Languages: {
3
+ readonly JavaScript: "javascript";
4
+ readonly Python: "python";
5
+ };
6
+ export type Language = (typeof Languages)[keyof typeof Languages];
7
+ export declare const LanguageLabels: Record<Language, string>;
8
+ export interface BuildPackageOptions {
9
+ projectPath: string;
10
+ outputPath: string;
11
+ projectStorageId: string;
12
+ }
13
+ export type BuildPackageFn = (fileSystem: IFileSystem, options: BuildPackageOptions) => Promise<void>;