@uipath/packager-tool-workflowcompiler-browser 1.202.0-preview.141 → 1.202.0-preview.142
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/package.json +4 -8
- package/src/cleanup-result.ts +0 -7
- package/src/i18n/index.ts +0 -45
- package/src/i18n/locales/de.json +0 -36
- package/src/i18n/locales/en.ts +0 -69
- package/src/i18n/locales/es-MX.json +0 -36
- package/src/i18n/locales/es.json +0 -36
- package/src/i18n/locales/fr.json +0 -36
- package/src/i18n/locales/ja.json +0 -36
- package/src/i18n/locales/ko.json +0 -36
- package/src/i18n/locales/pt-BR.json +0 -36
- package/src/i18n/locales/pt.json +0 -36
- package/src/i18n/locales/ro.json +0 -36
- package/src/i18n/locales/ru.json +0 -36
- package/src/i18n/locales/tr.json +0 -36
- package/src/i18n/locales/zh-CN.json +0 -36
- package/src/i18n/locales/zh-TW.json +0 -36
- package/src/i18n/locales/zu.json +0 -36
- package/src/index.ts +0 -11
- package/src/pack-result-processor.ts +0 -307
- package/src/packager-hub-connection.ts +0 -241
- package/src/project-bundle-executor.ts +0 -783
- package/src/workflow-compiler-tool-factory.ts +0 -47
- package/src/workflow-compiler-tool.ts +0 -93
|
@@ -1,783 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ConnectionInfo,
|
|
3
|
-
type IFileSystem,
|
|
4
|
-
type IProjectCleanupOptions,
|
|
5
|
-
type IProjectPackOptions,
|
|
6
|
-
type IProjectToolFactory,
|
|
7
|
-
type IProjectValidateOptions,
|
|
8
|
-
type IToolLogger,
|
|
9
|
-
PackJobTargetKind,
|
|
10
|
-
type ProjectOperationContext,
|
|
11
|
-
ToolErrorCodes,
|
|
12
|
-
ToolResult,
|
|
13
|
-
toRelativeUrl,
|
|
14
|
-
translate,
|
|
15
|
-
} from "@uipath/solutionpackager-tool-core";
|
|
16
|
-
import { toProjectCleanupInfo } from "./cleanup-result.js";
|
|
17
|
-
import { PackResultProcessor } from "./pack-result-processor.js";
|
|
18
|
-
import {
|
|
19
|
-
CompletedStatus,
|
|
20
|
-
type PackAgentResult,
|
|
21
|
-
PackagerHubConnection,
|
|
22
|
-
} from "./packager-hub-connection.js";
|
|
23
|
-
|
|
24
|
-
type ResolveCallback = (result: ToolResult) => void;
|
|
25
|
-
|
|
26
|
-
export enum BundleOperation {
|
|
27
|
-
Pack = "Pack",
|
|
28
|
-
Validate = "Validate",
|
|
29
|
-
Cleanup = "Cleanup",
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface PackEntry {
|
|
33
|
-
operation: BundleOperation.Pack;
|
|
34
|
-
options: IProjectPackOptions;
|
|
35
|
-
resolve: ResolveCallback;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ValidateEntry {
|
|
39
|
-
operation: BundleOperation.Validate;
|
|
40
|
-
options: IProjectValidateOptions;
|
|
41
|
-
resolve: ResolveCallback;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface CleanupEntry {
|
|
45
|
-
operation: BundleOperation.Cleanup;
|
|
46
|
-
options: IProjectCleanupOptions;
|
|
47
|
-
resolve: ResolveCallback;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type ProjectEntry = PackEntry | ValidateEntry | CleanupEntry;
|
|
51
|
-
|
|
52
|
-
interface BundleSession {
|
|
53
|
-
operation: BundleOperation;
|
|
54
|
-
expectedCount: number;
|
|
55
|
-
projects: Map<string, ProjectEntry>;
|
|
56
|
-
timeout?: ReturnType<typeof setTimeout>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface IProjectBundleExecutor {
|
|
60
|
-
packAsync(
|
|
61
|
-
options: IProjectPackOptions,
|
|
62
|
-
context: ProjectOperationContext,
|
|
63
|
-
): Promise<ToolResult>;
|
|
64
|
-
|
|
65
|
-
validateAsync(
|
|
66
|
-
options: IProjectValidateOptions,
|
|
67
|
-
context: ProjectOperationContext,
|
|
68
|
-
): Promise<ToolResult>;
|
|
69
|
-
|
|
70
|
-
cleanupAsync(
|
|
71
|
-
options: IProjectCleanupOptions,
|
|
72
|
-
context: ProjectOperationContext,
|
|
73
|
-
): Promise<ToolResult>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Coordinates pack or validate of multiple projects within a single operation context.
|
|
78
|
-
* Waits until all compatible projects have submitted their options,
|
|
79
|
-
* then executes a single bundled remote-agent operation and resolves all pending promises.
|
|
80
|
-
*/
|
|
81
|
-
/**
|
|
82
|
-
* Shape of the `POST /odata/Attachments` response. Orchestrator returns
|
|
83
|
-
* PascalCase (`Id`, `BlobFileAccess.Uri`); the camelCase fallbacks mirror the
|
|
84
|
-
* defensive reads in `@uipath/orchestrator-sdk`'s `uploadJobAttachment`.
|
|
85
|
-
*/
|
|
86
|
-
interface AttachmentResponse {
|
|
87
|
-
Id?: string;
|
|
88
|
-
id?: string;
|
|
89
|
-
BlobFileAccess?: { Uri: string };
|
|
90
|
-
blobFileAccess?: { Uri: string };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class ProjectBundleExecutor implements IProjectBundleExecutor {
|
|
94
|
-
static readonly SESSION_TIMEOUT_MS = 1_800_000;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Maximum length (in characters) of the inline `inputArguments` payload
|
|
98
|
-
* accepted by the serverless jobs endpoint. Beyond this the arguments must
|
|
99
|
-
* be uploaded as a file attachment and referenced via `inputFile`.
|
|
100
|
-
*/
|
|
101
|
-
static readonly MAX_INLINE_INPUT_ARGUMENTS_LENGTH = 10_000;
|
|
102
|
-
|
|
103
|
-
private readonly sessions = new Map<string, BundleSession>();
|
|
104
|
-
private readonly pendingSessionCreations = new Map<
|
|
105
|
-
string,
|
|
106
|
-
Promise<BundleSession>
|
|
107
|
-
>();
|
|
108
|
-
|
|
109
|
-
constructor(
|
|
110
|
-
private readonly toolFactory: IProjectToolFactory,
|
|
111
|
-
private readonly fileSystem: IFileSystem,
|
|
112
|
-
) {}
|
|
113
|
-
|
|
114
|
-
async packAsync(
|
|
115
|
-
options: IProjectPackOptions,
|
|
116
|
-
context: ProjectOperationContext,
|
|
117
|
-
): Promise<ToolResult> {
|
|
118
|
-
return this.enqueueAsync(context, {
|
|
119
|
-
operation: BundleOperation.Pack,
|
|
120
|
-
options,
|
|
121
|
-
resolve: () => {},
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async validateAsync(
|
|
126
|
-
options: IProjectValidateOptions,
|
|
127
|
-
context: ProjectOperationContext,
|
|
128
|
-
): Promise<ToolResult> {
|
|
129
|
-
return this.enqueueAsync(context, {
|
|
130
|
-
operation: BundleOperation.Validate,
|
|
131
|
-
options,
|
|
132
|
-
resolve: () => {},
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async cleanupAsync(
|
|
137
|
-
options: IProjectCleanupOptions,
|
|
138
|
-
context: ProjectOperationContext,
|
|
139
|
-
): Promise<ToolResult> {
|
|
140
|
-
if (
|
|
141
|
-
context.downloadUrl &&
|
|
142
|
-
!options.dryRun &&
|
|
143
|
-
!context.solutionId &&
|
|
144
|
-
!context.projectId
|
|
145
|
-
) {
|
|
146
|
-
return ToolResult.error(
|
|
147
|
-
ToolErrorCodes.InternalError,
|
|
148
|
-
"Cleanup save-back requires a solutionId or projectId for downloaded workspaces.",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return this.enqueueAsync(context, {
|
|
153
|
-
operation: BundleOperation.Cleanup,
|
|
154
|
-
options,
|
|
155
|
-
resolve: () => {},
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private async enqueueAsync(
|
|
160
|
-
context: ProjectOperationContext,
|
|
161
|
-
entry: ProjectEntry,
|
|
162
|
-
): Promise<ToolResult> {
|
|
163
|
-
const projectPath = entry.options.projectPath;
|
|
164
|
-
context.logger?.info(
|
|
165
|
-
`[BundleExecutor] ${entry.operation}Async called for project: ${projectPath}`,
|
|
166
|
-
);
|
|
167
|
-
const session = await this.getSessionAsync(context, entry.operation);
|
|
168
|
-
|
|
169
|
-
if (session.operation !== entry.operation) {
|
|
170
|
-
return ToolResult.error(
|
|
171
|
-
"BundleOperationMismatch",
|
|
172
|
-
translate.t(
|
|
173
|
-
"toolWorkflowCompilerBrowser.errors.bundleOperationMismatch",
|
|
174
|
-
{
|
|
175
|
-
sessionOperation: session.operation,
|
|
176
|
-
entryOperation: entry.operation,
|
|
177
|
-
},
|
|
178
|
-
),
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const promise = new Promise<ToolResult>((resolve) => {
|
|
183
|
-
entry.resolve = resolve;
|
|
184
|
-
});
|
|
185
|
-
session.projects.set(projectPath, entry);
|
|
186
|
-
const allReceived = session.projects.size === session.expectedCount;
|
|
187
|
-
context.logger?.info(
|
|
188
|
-
`[BundleExecutor] session (${session.operation}): ${session.projects.size}/${session.expectedCount} projects received. allReceived=${allReceived}`,
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
if (allReceived) {
|
|
192
|
-
clearTimeout(session.timeout);
|
|
193
|
-
await this.runAgentAsync(session, context);
|
|
194
|
-
this.sessions.delete(context.id);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return promise;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private getSessionAsync(
|
|
201
|
-
context: ProjectOperationContext,
|
|
202
|
-
operation: BundleOperation,
|
|
203
|
-
): Promise<BundleSession> {
|
|
204
|
-
const existing = this.sessions.get(context.id);
|
|
205
|
-
if (existing) {
|
|
206
|
-
return Promise.resolve(existing);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let pending = this.pendingSessionCreations.get(context.id);
|
|
210
|
-
if (!pending) {
|
|
211
|
-
pending = this.createSessionAsync(context, operation);
|
|
212
|
-
this.pendingSessionCreations.set(context.id, pending);
|
|
213
|
-
}
|
|
214
|
-
return pending;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private async createSessionAsync(
|
|
218
|
-
context: ProjectOperationContext,
|
|
219
|
-
operation: BundleOperation,
|
|
220
|
-
): Promise<BundleSession> {
|
|
221
|
-
const expectedCount = this.countCompatibleProjects(context);
|
|
222
|
-
const session: BundleSession = {
|
|
223
|
-
operation,
|
|
224
|
-
expectedCount,
|
|
225
|
-
projects: new Map(),
|
|
226
|
-
};
|
|
227
|
-
session.timeout = setTimeout(() => {
|
|
228
|
-
if (session.projects.size < session.expectedCount) {
|
|
229
|
-
context.logger?.error(
|
|
230
|
-
`[BundleExecutor] Session timed out after ${ProjectBundleExecutor.SESSION_TIMEOUT_MS}ms: ` +
|
|
231
|
-
`received ${session.projects.size}/${session.expectedCount} projects`,
|
|
232
|
-
);
|
|
233
|
-
this.resolveAll(
|
|
234
|
-
session,
|
|
235
|
-
ToolResult.error(
|
|
236
|
-
"SessionTimeoutError",
|
|
237
|
-
translate.t(
|
|
238
|
-
"toolWorkflowCompilerBrowser.errors.sessionTimeout",
|
|
239
|
-
{
|
|
240
|
-
received: session.projects.size,
|
|
241
|
-
expected: session.expectedCount,
|
|
242
|
-
},
|
|
243
|
-
),
|
|
244
|
-
),
|
|
245
|
-
);
|
|
246
|
-
this.sessions.delete(context.id);
|
|
247
|
-
}
|
|
248
|
-
}, ProjectBundleExecutor.SESSION_TIMEOUT_MS);
|
|
249
|
-
this.sessions.set(context.id, session);
|
|
250
|
-
this.pendingSessionCreations.delete(context.id);
|
|
251
|
-
return session;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private async runAgentAsync(
|
|
255
|
-
session: BundleSession,
|
|
256
|
-
context: ProjectOperationContext,
|
|
257
|
-
): Promise<void> {
|
|
258
|
-
const projectPaths = [...session.projects.keys()];
|
|
259
|
-
context.logger?.info(
|
|
260
|
-
translate.t(
|
|
261
|
-
"toolWorkflowCompilerBrowser.info.startingRemoteAgent",
|
|
262
|
-
{
|
|
263
|
-
operation: session.operation,
|
|
264
|
-
count: session.expectedCount,
|
|
265
|
-
paths: projectPaths.join(", "),
|
|
266
|
-
},
|
|
267
|
-
),
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
const connection = context.connection;
|
|
271
|
-
context.logger?.info(
|
|
272
|
-
`[BundleExecutor] connection info: cloudUrl=${connection?.cloudUrl ?? "MISSING"}, hasToken=${!!connection?.accessToken}, folderId=${connection?.folderId ?? "MISSING"}`,
|
|
273
|
-
);
|
|
274
|
-
if (
|
|
275
|
-
!connection?.cloudUrl ||
|
|
276
|
-
!connection.accessToken ||
|
|
277
|
-
!connection.folderId
|
|
278
|
-
) {
|
|
279
|
-
this.resolveAll(
|
|
280
|
-
session,
|
|
281
|
-
ToolResult.error(
|
|
282
|
-
"ConnectionError",
|
|
283
|
-
translate.t(
|
|
284
|
-
"toolWorkflowCompilerBrowser.errors.missingConnectionInfo",
|
|
285
|
-
),
|
|
286
|
-
),
|
|
287
|
-
);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const sessionId = crypto.randomUUID();
|
|
292
|
-
const hubConnection = await this.connectToHubAsync(
|
|
293
|
-
connection,
|
|
294
|
-
sessionId,
|
|
295
|
-
context.logger,
|
|
296
|
-
);
|
|
297
|
-
if (!hubConnection) {
|
|
298
|
-
this.resolveAll(
|
|
299
|
-
session,
|
|
300
|
-
ToolResult.error(
|
|
301
|
-
"SignalRConnectionError",
|
|
302
|
-
translate.t(
|
|
303
|
-
"toolWorkflowCompilerBrowser.errors.signalRConnectionFailed",
|
|
304
|
-
),
|
|
305
|
-
),
|
|
306
|
-
);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
context.logger?.info(`[BundleExecutor] Sending BeginSession...`);
|
|
312
|
-
await this.sendBeginSessionAsync(
|
|
313
|
-
session,
|
|
314
|
-
context,
|
|
315
|
-
connection,
|
|
316
|
-
sessionId,
|
|
317
|
-
);
|
|
318
|
-
context.logger?.info(
|
|
319
|
-
`[BundleExecutor] BeginSession succeeded. Waiting for completion...`,
|
|
320
|
-
);
|
|
321
|
-
const result = await hubConnection.waitForCompletionAsync();
|
|
322
|
-
context.logger?.info(
|
|
323
|
-
`[BundleExecutor] Got result: errorCode=${result.errorCode}, status=${result.status}, message=${result.message}, bucketId=${result.bucketId}`,
|
|
324
|
-
);
|
|
325
|
-
await this.handleAgentResultAsync(result, session, context);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
const message =
|
|
328
|
-
error instanceof Error ? error.message : String(error);
|
|
329
|
-
context.logger?.error(
|
|
330
|
-
`[BundleExecutor] runAgentAsync caught error: ${message}`,
|
|
331
|
-
);
|
|
332
|
-
this.resolveAll(
|
|
333
|
-
session,
|
|
334
|
-
ToolResult.error(`${session.operation}AgentError`, message),
|
|
335
|
-
);
|
|
336
|
-
} finally {
|
|
337
|
-
await hubConnection.disconnectAsync();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
private async connectToHubAsync(
|
|
342
|
-
connection: ConnectionInfo,
|
|
343
|
-
sessionId: string,
|
|
344
|
-
logger?: IToolLogger,
|
|
345
|
-
): Promise<PackagerHubConnection | null> {
|
|
346
|
-
const relativeBase = toRelativeUrl(connection.cloudUrl!);
|
|
347
|
-
const hubUrl = `${relativeBase}/orchestrator_/signalr/robotdebug?sessionId=${sessionId}`;
|
|
348
|
-
logger?.info(
|
|
349
|
-
translate.t("toolWorkflowCompilerBrowser.info.connectingToHub", {
|
|
350
|
-
url: hubUrl,
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
const hubConnection = new PackagerHubConnection(logger);
|
|
355
|
-
try {
|
|
356
|
-
await hubConnection.connectAsync(hubUrl, connection.accessToken!);
|
|
357
|
-
return hubConnection;
|
|
358
|
-
} catch (error) {
|
|
359
|
-
const message =
|
|
360
|
-
error instanceof Error ? error.message : String(error);
|
|
361
|
-
logger?.error(
|
|
362
|
-
translate.t(
|
|
363
|
-
"toolWorkflowCompilerBrowser.errors.hubConnectionFailed",
|
|
364
|
-
{ url: hubUrl, message },
|
|
365
|
-
),
|
|
366
|
-
);
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async handleAgentResultAsync(
|
|
372
|
-
result: PackAgentResult,
|
|
373
|
-
session: BundleSession,
|
|
374
|
-
context: ProjectOperationContext,
|
|
375
|
-
): Promise<void> {
|
|
376
|
-
if (
|
|
377
|
-
result.errorCode !== 0 ||
|
|
378
|
-
result.status !== CompletedStatus.Succeeded
|
|
379
|
-
) {
|
|
380
|
-
this.resolveAll(
|
|
381
|
-
session,
|
|
382
|
-
ToolResult.error(
|
|
383
|
-
`${session.operation}AgentError`,
|
|
384
|
-
result.message ??
|
|
385
|
-
translate.t(
|
|
386
|
-
"toolWorkflowCompilerBrowser.errors.agentFailed",
|
|
387
|
-
{
|
|
388
|
-
operation: session.operation,
|
|
389
|
-
status: result.status,
|
|
390
|
-
errorCode: result.errorCode,
|
|
391
|
-
},
|
|
392
|
-
),
|
|
393
|
-
),
|
|
394
|
-
);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (session.operation === BundleOperation.Pack) {
|
|
399
|
-
await this.handlePackBucketAsync(result, session, context);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (session.operation === BundleOperation.Cleanup) {
|
|
404
|
-
this.resolveCleanup(result, session, context);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
this.resolveAll(session, ToolResult.success());
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* The agent removed unused items in-place and (for cloud solutions) saved the
|
|
413
|
-
* solution back to StudioWeb; there is no bucket to download. Resolve each
|
|
414
|
-
* project with its own result carrying the changed files (the full map, which
|
|
415
|
-
* solution-cleanup-service unions) and that project's cleanup payload mapped
|
|
416
|
-
* to the generic {@link toProjectCleanupInfo} shape.
|
|
417
|
-
*/
|
|
418
|
-
private resolveCleanup(
|
|
419
|
-
result: PackAgentResult,
|
|
420
|
-
session: BundleSession,
|
|
421
|
-
context: ProjectOperationContext,
|
|
422
|
-
): void {
|
|
423
|
-
for (const [projectPath, entry] of session.projects) {
|
|
424
|
-
const details: Record<string, unknown> = {};
|
|
425
|
-
if (result.changedFilesByProject) {
|
|
426
|
-
details.changedFilesByProject = result.changedFilesByProject;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const designId = context.projects.find(
|
|
430
|
-
(p) => p.ProjectPath === projectPath,
|
|
431
|
-
)?.Id;
|
|
432
|
-
const raw = designId
|
|
433
|
-
? result.unusedDependenciesByProject?.[designId]
|
|
434
|
-
: undefined;
|
|
435
|
-
if (raw) {
|
|
436
|
-
details.projectCleanup = toProjectCleanupInfo(raw);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const projectResult = ToolResult.success();
|
|
440
|
-
if (Object.keys(details).length > 0) {
|
|
441
|
-
projectResult.details = details;
|
|
442
|
-
}
|
|
443
|
-
entry.resolve(projectResult);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private async handlePackBucketAsync(
|
|
448
|
-
result: PackAgentResult,
|
|
449
|
-
session: BundleSession,
|
|
450
|
-
context: ProjectOperationContext,
|
|
451
|
-
): Promise<void> {
|
|
452
|
-
if (result.bucketId == null) {
|
|
453
|
-
this.resolveAll(
|
|
454
|
-
session,
|
|
455
|
-
ToolResult.error(
|
|
456
|
-
"PackAgentError",
|
|
457
|
-
translate.t(
|
|
458
|
-
"toolWorkflowCompilerBrowser.errors.packSucceededNoBucket",
|
|
459
|
-
),
|
|
460
|
-
),
|
|
461
|
-
);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const packEntries = new Map<string, { options: IProjectPackOptions }>();
|
|
466
|
-
for (const [projectPath, entry] of session.projects) {
|
|
467
|
-
if (entry.operation === BundleOperation.Pack) {
|
|
468
|
-
packEntries.set(projectPath, { options: entry.options });
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const resultProcessor = new PackResultProcessor(
|
|
473
|
-
this.fileSystem,
|
|
474
|
-
context.logger,
|
|
475
|
-
);
|
|
476
|
-
const perProjectResults = await resultProcessor.processAsync(
|
|
477
|
-
result.bucketId,
|
|
478
|
-
packEntries,
|
|
479
|
-
context,
|
|
480
|
-
);
|
|
481
|
-
for (const [projectPath, entry] of session.projects) {
|
|
482
|
-
const projectResult = perProjectResults.get(projectPath);
|
|
483
|
-
entry.resolve(
|
|
484
|
-
projectResult ??
|
|
485
|
-
ToolResult.error(
|
|
486
|
-
"PackResultError",
|
|
487
|
-
translate.t(
|
|
488
|
-
"toolWorkflowCompilerBrowser.errors.noResultForProject",
|
|
489
|
-
),
|
|
490
|
-
),
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
private async sendBeginSessionAsync(
|
|
496
|
-
session: BundleSession,
|
|
497
|
-
context: ProjectOperationContext,
|
|
498
|
-
connection: ConnectionInfo,
|
|
499
|
-
sessionId: string,
|
|
500
|
-
): Promise<void> {
|
|
501
|
-
const relativeBase = toRelativeUrl(connection.cloudUrl!);
|
|
502
|
-
const onLocalRobot =
|
|
503
|
-
context.jobTarget?.kind === PackJobTargetKind.LocalRobot;
|
|
504
|
-
// Two different job-creation APIs, not one endpoint with a flag: the serverless
|
|
505
|
-
// endpoint forces RuntimeType=Serverless and takes no machine targeting, while
|
|
506
|
-
// BeginSession takes the runtime/machine fields but no jobType. The agent behaves
|
|
507
|
-
// the same either way — it dispatches on Command in the input arguments.
|
|
508
|
-
const url = onLocalRobot
|
|
509
|
-
? `${relativeBase}/orchestrator_/api/RobotDebug/BeginSession`
|
|
510
|
-
: `${relativeBase}/orchestrator_/api/serverless/jobs`;
|
|
511
|
-
|
|
512
|
-
context.logger?.info(
|
|
513
|
-
`[BundleExecutor] BeginSession URL: ${url} (target=${context.jobTarget?.kind ?? PackJobTargetKind.Serverless})`,
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
const firstEntry = session.projects.values().next().value!;
|
|
517
|
-
const opts = firstEntry.options;
|
|
518
|
-
const skipAnalyze = "skipAnalyze" in opts ? opts.skipAnalyze : false;
|
|
519
|
-
const skipValidate = "skipValidate" in opts ? opts.skipValidate : false;
|
|
520
|
-
|
|
521
|
-
const rpaProjects = [...session.projects.entries()].map(
|
|
522
|
-
([projectPath, entry]) => {
|
|
523
|
-
const project = context.projects.find(
|
|
524
|
-
(p) => p.ProjectPath === projectPath,
|
|
525
|
-
);
|
|
526
|
-
return {
|
|
527
|
-
RelativePath: project?.ProjectRelativePath ?? "",
|
|
528
|
-
DesignId: project?.Id ?? "",
|
|
529
|
-
ProjectStorageId:
|
|
530
|
-
entry.operation === BundleOperation.Pack
|
|
531
|
-
? (entry.options.projectStorageId ?? "")
|
|
532
|
-
: "",
|
|
533
|
-
PackageId:
|
|
534
|
-
entry.operation === BundleOperation.Pack
|
|
535
|
-
? entry.options.package.id
|
|
536
|
-
: "",
|
|
537
|
-
};
|
|
538
|
-
},
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
const executorInfo: Record<string, unknown> = {
|
|
542
|
-
Command: session.operation,
|
|
543
|
-
SupportedProtocolVersions: ["2.0"],
|
|
544
|
-
DownloadUrl: context.downloadUrl ?? "",
|
|
545
|
-
SolutionId: context.solutionId ?? "",
|
|
546
|
-
ProjectId: context.projectId ?? "",
|
|
547
|
-
SessionId: sessionId,
|
|
548
|
-
ProfileKey: "StudioWeb",
|
|
549
|
-
LicenseType: "Development",
|
|
550
|
-
LogLevel: opts.logLevel ?? "Warning",
|
|
551
|
-
Culture: "en-US",
|
|
552
|
-
DownloadTimeoutInSeconds: 300,
|
|
553
|
-
SkipAnalyze: String(skipAnalyze),
|
|
554
|
-
SkipValidate: String(skipValidate),
|
|
555
|
-
TargetFramework: context.targetFramework,
|
|
556
|
-
RpaProjects: rpaProjects,
|
|
557
|
-
WorkflowCompilerVersion: context.workflowCompilerVersion ?? "",
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
if (firstEntry.operation === BundleOperation.Pack) {
|
|
561
|
-
executorInfo.Configuration = context.configuration;
|
|
562
|
-
const pkg = firstEntry.options.package;
|
|
563
|
-
executorInfo.Package = {
|
|
564
|
-
Name: pkg.id,
|
|
565
|
-
Version: pkg.version,
|
|
566
|
-
Author: pkg.author ?? "",
|
|
567
|
-
Description: pkg.description ?? "",
|
|
568
|
-
ReleaseNotes: pkg.releaseNotes ?? "",
|
|
569
|
-
Tags: pkg.tags ?? "",
|
|
570
|
-
IconUrl: pkg.iconUrl ?? "",
|
|
571
|
-
ProjectUrl: pkg.projectUrl ?? "",
|
|
572
|
-
RepositoryType: pkg.repositoryType ?? "",
|
|
573
|
-
RepositoryUrl: pkg.repositoryUrl ?? "",
|
|
574
|
-
RepositoryBranch: pkg.repositoryBranch ?? "",
|
|
575
|
-
RepositoryCommit: pkg.repositoryCommit ?? "",
|
|
576
|
-
};
|
|
577
|
-
// Flat fields retained for backward compatibility with agents that
|
|
578
|
-
// predate the nested Package structure.
|
|
579
|
-
executorInfo.Name = pkg.id;
|
|
580
|
-
executorInfo.Version = pkg.version;
|
|
581
|
-
executorInfo.Author = pkg.author ?? "";
|
|
582
|
-
executorInfo.Description = pkg.description ?? "";
|
|
583
|
-
executorInfo.ReleaseNotes = pkg.releaseNotes ?? "";
|
|
584
|
-
|
|
585
|
-
// Lets the agent pack the sources already on its disk instead of
|
|
586
|
-
// downloading the snapshot. Only sent when the job is pinned to
|
|
587
|
-
// specific machine sessions of a local robot: on any other machine
|
|
588
|
-
// the path does not exist and the agent fails setup rather than
|
|
589
|
-
// falling back to the download, so an unpinned job must not carry it.
|
|
590
|
-
if (context.localSourcePath) {
|
|
591
|
-
if (
|
|
592
|
-
onLocalRobot &&
|
|
593
|
-
context.jobTarget?.machineSessionIds?.length
|
|
594
|
-
) {
|
|
595
|
-
executorInfo.LocalSourcePath = context.localSourcePath;
|
|
596
|
-
} else {
|
|
597
|
-
context.logger?.warn(
|
|
598
|
-
"[BundleExecutor] Dropping localSourcePath: the pack job is not pinned to a local robot machine session.",
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (firstEntry.operation === BundleOperation.Cleanup) {
|
|
605
|
-
executorInfo.DryRun = firstEntry.options.dryRun;
|
|
606
|
-
executorInfo.SkipImports = firstEntry.options.skipImports;
|
|
607
|
-
// Forward the StudioWeb editing session's solution lock key so the agent's
|
|
608
|
-
// save-back overwrite is accepted while the solution is locked by that session.
|
|
609
|
-
if (context.lockKey) {
|
|
610
|
-
executorInfo.LockKey = context.lockKey;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const inputArguments = JSON.stringify(executorInfo);
|
|
615
|
-
|
|
616
|
-
const body: Record<string, unknown> = onLocalRobot
|
|
617
|
-
? {
|
|
618
|
-
source: "StudioWeb",
|
|
619
|
-
targetFramework: context.targetFramework,
|
|
620
|
-
// Development runtime + runAsMe is what confines the job to this
|
|
621
|
-
// user's own machines; without both, Orchestrator is free to
|
|
622
|
-
// schedule it anywhere. Mirrors StudioWeb debug's payload.
|
|
623
|
-
runtimeType: "Development",
|
|
624
|
-
runAsMe: true,
|
|
625
|
-
jobsCount: 1,
|
|
626
|
-
strategy: "ModernJobsCount",
|
|
627
|
-
...(context.jobTarget?.machineSessionIds?.length
|
|
628
|
-
? {
|
|
629
|
-
machineSessionIds:
|
|
630
|
-
context.jobTarget.machineSessionIds,
|
|
631
|
-
}
|
|
632
|
-
: {}),
|
|
633
|
-
}
|
|
634
|
-
: {
|
|
635
|
-
source: "StudioWeb",
|
|
636
|
-
targetFramework: context.targetFramework,
|
|
637
|
-
jobType: "PublishStudioProject",
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
// Both job endpoints cap inline inputArguments at
|
|
641
|
-
// MAX_INLINE_INPUT_ARGUMENTS_LENGTH characters. For larger payloads we
|
|
642
|
-
// upload the arguments as a file attachment and reference it via
|
|
643
|
-
// inputFile instead (the two are mutually exclusive on the server).
|
|
644
|
-
if (
|
|
645
|
-
inputArguments.length >
|
|
646
|
-
ProjectBundleExecutor.MAX_INLINE_INPUT_ARGUMENTS_LENGTH
|
|
647
|
-
) {
|
|
648
|
-
context.logger?.info(
|
|
649
|
-
translate.t(
|
|
650
|
-
"toolWorkflowCompilerBrowser.info.offloadingInputArguments",
|
|
651
|
-
{ length: inputArguments.length },
|
|
652
|
-
),
|
|
653
|
-
);
|
|
654
|
-
body.inputFile = await this.uploadInputArgumentsFileAsync(
|
|
655
|
-
inputArguments,
|
|
656
|
-
connection,
|
|
657
|
-
context.logger,
|
|
658
|
-
);
|
|
659
|
-
} else {
|
|
660
|
-
body.inputArguments = inputArguments;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const headers: Record<string, string> = {
|
|
664
|
-
"Content-Type": "application/json",
|
|
665
|
-
Authorization: `Bearer ${connection.accessToken}`,
|
|
666
|
-
"X-UIPATH-OrganizationUnitId": connection.folderId ?? "",
|
|
667
|
-
};
|
|
668
|
-
|
|
669
|
-
if (connection.tenantId) {
|
|
670
|
-
headers["x-uipath-tenantid"] = connection.tenantId;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const response = await fetch(url, {
|
|
674
|
-
method: "POST",
|
|
675
|
-
headers,
|
|
676
|
-
body: JSON.stringify(body),
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
if (!response.ok) {
|
|
680
|
-
const errorText = await response.text();
|
|
681
|
-
throw new Error(
|
|
682
|
-
translate.t(
|
|
683
|
-
"toolWorkflowCompilerBrowser.errors.beginSessionFailed",
|
|
684
|
-
{ status: response.status, errorText },
|
|
685
|
-
),
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Uploads the input arguments JSON as a file attachment and returns the
|
|
692
|
-
* attachment id, to be passed as `inputFile` on the serverless job request.
|
|
693
|
-
* Mirrors the two-step flow used by the Orchestrator UI: create the
|
|
694
|
-
* attachment (which returns a signed blob write URI) then PUT the contents
|
|
695
|
-
* to that URI using the storage-provided headers.
|
|
696
|
-
*/
|
|
697
|
-
private async uploadInputArgumentsFileAsync(
|
|
698
|
-
inputArguments: string,
|
|
699
|
-
connection: ConnectionInfo,
|
|
700
|
-
logger?: IToolLogger,
|
|
701
|
-
): Promise<string> {
|
|
702
|
-
const baseUrl = toRelativeUrl(connection.cloudUrl!);
|
|
703
|
-
const attachmentsUrl = `${baseUrl}/orchestrator_/odata/Attachments`;
|
|
704
|
-
|
|
705
|
-
const headers: Record<string, string> = {
|
|
706
|
-
"Content-Type": "application/json",
|
|
707
|
-
Authorization: `Bearer ${connection.accessToken}`,
|
|
708
|
-
"X-UIPATH-OrganizationUnitId": connection.folderId ?? "",
|
|
709
|
-
};
|
|
710
|
-
if (connection.tenantId) {
|
|
711
|
-
headers["x-uipath-tenantid"] = connection.tenantId;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const createResponse = await fetch(attachmentsUrl, {
|
|
715
|
-
method: "POST",
|
|
716
|
-
headers,
|
|
717
|
-
body: JSON.stringify({ name: "input.json" }),
|
|
718
|
-
});
|
|
719
|
-
if (!createResponse.ok) {
|
|
720
|
-
const errorText = await createResponse.text();
|
|
721
|
-
throw new Error(
|
|
722
|
-
translate.t(
|
|
723
|
-
"toolWorkflowCompilerBrowser.errors.createAttachmentFailed",
|
|
724
|
-
{ status: createResponse.status, errorText },
|
|
725
|
-
),
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
const attachment = (await createResponse.json()) as AttachmentResponse;
|
|
730
|
-
const attachmentId = attachment.Id ?? attachment.id ?? "";
|
|
731
|
-
const writeUri =
|
|
732
|
-
attachment.BlobFileAccess?.Uri ?? attachment.blobFileAccess?.Uri;
|
|
733
|
-
if (!attachmentId || !writeUri) {
|
|
734
|
-
throw new Error(
|
|
735
|
-
translate.t(
|
|
736
|
-
"toolWorkflowCompilerBrowser.errors.attachmentResponseMalformed",
|
|
737
|
-
),
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// PUT the raw bytes to the SAS blob URI. The Azure blob endpoint
|
|
742
|
-
// requires the BlockBlob type header; matches the proven contract in
|
|
743
|
-
// `@uipath/orchestrator-sdk`'s uploadJobAttachment.
|
|
744
|
-
const uploadResponse = await fetch(writeUri, {
|
|
745
|
-
method: "PUT",
|
|
746
|
-
headers: {
|
|
747
|
-
"Content-Type": "application/octet-stream",
|
|
748
|
-
"x-ms-blob-type": "BlockBlob",
|
|
749
|
-
},
|
|
750
|
-
body: inputArguments,
|
|
751
|
-
});
|
|
752
|
-
if (!uploadResponse.ok) {
|
|
753
|
-
throw new Error(
|
|
754
|
-
translate.t(
|
|
755
|
-
"toolWorkflowCompilerBrowser.errors.uploadInputArgumentsFailed",
|
|
756
|
-
{
|
|
757
|
-
status: uploadResponse.status,
|
|
758
|
-
statusText: uploadResponse.statusText,
|
|
759
|
-
},
|
|
760
|
-
),
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
logger?.info(
|
|
765
|
-
`[BundleExecutor] Uploaded input arguments as attachment ${attachmentId}`,
|
|
766
|
-
);
|
|
767
|
-
return attachmentId;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
private resolveAll(session: BundleSession, result: ToolResult): void {
|
|
771
|
-
for (const [, entry] of session.projects) {
|
|
772
|
-
entry.resolve(result);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
private countCompatibleProjects(context: ProjectOperationContext): number {
|
|
777
|
-
return context.projects.filter(
|
|
778
|
-
(p) =>
|
|
779
|
-
p.ProjectPath != null &&
|
|
780
|
-
this.toolFactory.supportedTypes.includes(p.Type),
|
|
781
|
-
).length;
|
|
782
|
-
}
|
|
783
|
-
}
|