@uipath/packager-tool-functions 0.0.7 → 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 +88 -0
- package/dist/functions-tool-factory.d.ts +1 -1
- package/dist/functions-tool.d.ts +1 -7
- package/dist/index.js +345 -107
- package/dist/js/build-js-functions-package.d.ts +3 -0
- package/dist/js/index.d.ts +1 -0
- package/dist/py/build-py-functions-package.d.ts +3 -0
- package/dist/py/constants.d.ts +14 -0
- package/dist/py/index.d.ts +1 -0
- package/dist/types.d.ts +13 -0
- package/package.json +10 -8
- package/src/functions-tool-factory.ts +1 -1
- package/src/functions-tool.ts +47 -204
- package/src/index.ts +1 -1
- package/src/js/build-js-functions-package.ts +14 -0
- package/src/js/index.ts +1 -0
- package/src/py/build-py-functions-package.ts +287 -0
- package/src/py/constants.ts +40 -0
- package/src/py/index.ts +1 -0
- package/src/types.ts +24 -0
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
|
|
3
|
+
* Factory for creating Functions project tools.
|
|
4
4
|
* Handles projects with projectType "Function".
|
|
5
5
|
*/
|
|
6
6
|
export declare class FunctionsToolFactory implements IProjectToolFactory {
|
package/dist/functions-tool.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 =
|
|
45
|
-
const
|
|
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(
|
|
48
|
-
await this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
87
|
-
const
|
|
88
|
-
|
|
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(
|
|
92
|
-
const
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 @@
|
|
|
1
|
+
export { buildJsFunctionsPackage } from "./build-js-functions-package.js";
|
|
@@ -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";
|
package/dist/types.d.ts
ADDED
|
@@ -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>;
|