@uipath/packager-tool-workflowcompiler 0.0.14
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/dist/i18n/index.d.ts +7 -0
- package/dist/i18n/locales/en.d.ts +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1213 -0
- package/dist/output-entry.d.ts +42 -0
- package/dist/workflow-compiler-config.d.ts +23 -0
- package/dist/workflow-compiler-executor.d.ts +30 -0
- package/dist/workflow-compiler-path-resolver.d.ts +30 -0
- package/dist/workflow-compiler-tool-factory.d.ts +9 -0
- package/dist/workflow-compiler-tool.d.ts +17 -0
- package/package.json +54 -0
- package/src/i18n/index.ts +45 -0
- package/src/i18n/locales/de.json +40 -0
- package/src/i18n/locales/en.ts +60 -0
- package/src/i18n/locales/es-MX.json +40 -0
- package/src/i18n/locales/es.json +40 -0
- package/src/i18n/locales/fr.json +40 -0
- package/src/i18n/locales/ja.json +40 -0
- package/src/i18n/locales/ko.json +40 -0
- package/src/i18n/locales/pt-BR.json +40 -0
- package/src/i18n/locales/pt.json +40 -0
- package/src/i18n/locales/ro.json +40 -0
- package/src/i18n/locales/ru.json +40 -0
- package/src/i18n/locales/tr.json +40 -0
- package/src/i18n/locales/zh-CN.json +40 -0
- package/src/i18n/locales/zh-TW.json +40 -0
- package/src/i18n/locales/zu.json +40 -0
- package/src/index.ts +22 -0
- package/src/output-entry.ts +46 -0
- package/src/workflow-compiler-config.ts +32 -0
- package/src/workflow-compiler-executor.ts +386 -0
- package/src/workflow-compiler-path-resolver.ts +311 -0
- package/src/workflow-compiler-tool-factory.ts +29 -0
- package/src/workflow-compiler-tool.ts +187 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
type IFileSystem,
|
|
4
|
+
type IToolLogger,
|
|
5
|
+
ToolErrorCodes,
|
|
6
|
+
ToolResult,
|
|
7
|
+
translate,
|
|
8
|
+
} from "@uipath/solutionpackager-tool-core";
|
|
9
|
+
import type {
|
|
10
|
+
IOutputLog,
|
|
11
|
+
IOutputMessage,
|
|
12
|
+
IOutputProgress,
|
|
13
|
+
IOutputResult,
|
|
14
|
+
} from "./output-entry.js";
|
|
15
|
+
import {
|
|
16
|
+
type IWorkflowCompilerPathResolver,
|
|
17
|
+
WorkflowCompilerPathResolver,
|
|
18
|
+
} from "./workflow-compiler-path-resolver.js";
|
|
19
|
+
|
|
20
|
+
function camelCaseKeys(obj: unknown): unknown {
|
|
21
|
+
if (Array.isArray(obj)) {
|
|
22
|
+
return obj.map(camelCaseKeys);
|
|
23
|
+
}
|
|
24
|
+
if (obj !== null && typeof obj === "object") {
|
|
25
|
+
const result: Record<string, unknown> = {};
|
|
26
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
27
|
+
result[key.charAt(0).toLowerCase() + key.slice(1)] =
|
|
28
|
+
camelCaseKeys(value);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
return obj;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Executor for running workflow compiler commands as child processes.
|
|
37
|
+
* Handles process spawning, output parsing, and logging.
|
|
38
|
+
*/
|
|
39
|
+
export class WorkflowCompilerExecutor {
|
|
40
|
+
private static readonly DOTNET_EXECUTABLE = "dotnet";
|
|
41
|
+
private readonly pathResolver: IWorkflowCompilerPathResolver;
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly logger: IToolLogger,
|
|
45
|
+
fileSystem: IFileSystem,
|
|
46
|
+
) {
|
|
47
|
+
this.pathResolver = WorkflowCompilerPathResolver.getInstance(
|
|
48
|
+
logger,
|
|
49
|
+
fileSystem,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Execute a workflow compiler command
|
|
55
|
+
* @param operation - Operation name (e.g., 'restore', 'build', 'pack', 'validate')
|
|
56
|
+
* @param args - Additional command arguments
|
|
57
|
+
* @param cancellationToken - Optional cancellation token
|
|
58
|
+
* @returns Promise resolving to ToolResult
|
|
59
|
+
*/
|
|
60
|
+
async executeAsync(
|
|
61
|
+
operation: string,
|
|
62
|
+
args: string[],
|
|
63
|
+
cancellationToken?: AbortSignal,
|
|
64
|
+
): Promise<ToolResult> {
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
const compilerPath = await this.pathResolver.getCompilerPathAsync();
|
|
67
|
+
const isDll = compilerPath.endsWith(".dll");
|
|
68
|
+
const executable = isDll
|
|
69
|
+
? WorkflowCompilerExecutor.DOTNET_EXECUTABLE
|
|
70
|
+
: compilerPath;
|
|
71
|
+
const commandArgs = isDll
|
|
72
|
+
? [compilerPath, operation, ...args]
|
|
73
|
+
: [operation, ...args];
|
|
74
|
+
|
|
75
|
+
this.logCommandStart(operation, executable, commandArgs);
|
|
76
|
+
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
let result: IOutputResult | null = null;
|
|
79
|
+
const fallbackErrors: string[] = [];
|
|
80
|
+
|
|
81
|
+
const childProcess = this.spawnProcess(executable, commandArgs);
|
|
82
|
+
|
|
83
|
+
// Track stream completion
|
|
84
|
+
let stdoutEnded = false;
|
|
85
|
+
let stderrEnded = false;
|
|
86
|
+
let processExited = false;
|
|
87
|
+
|
|
88
|
+
const checkComplete = () => {
|
|
89
|
+
if (stdoutEnded && stderrEnded && processExited) {
|
|
90
|
+
const elapsed = Date.now() - startTime;
|
|
91
|
+
|
|
92
|
+
if (result) {
|
|
93
|
+
result.elapsed = elapsed;
|
|
94
|
+
this.logger.info(
|
|
95
|
+
translate.t(
|
|
96
|
+
"toolWorkflowcompiler.lifecycle.completed",
|
|
97
|
+
{
|
|
98
|
+
operation,
|
|
99
|
+
elapsed: elapsed.toString(),
|
|
100
|
+
errorCode: result.errorCode,
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
resolve(
|
|
105
|
+
new ToolResult(
|
|
106
|
+
result.errorCode,
|
|
107
|
+
result.message,
|
|
108
|
+
result.outputPackages,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
const exitCode = childProcess.exitCode ?? -1;
|
|
113
|
+
const errorMessage =
|
|
114
|
+
fallbackErrors.join("\n") ||
|
|
115
|
+
translate.t(
|
|
116
|
+
"toolWorkflowcompiler.errors.processExited",
|
|
117
|
+
{
|
|
118
|
+
exitCode: exitCode.toString(),
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
this.logger.error(
|
|
122
|
+
translate.t("toolWorkflowcompiler.errors.failed", {
|
|
123
|
+
operation,
|
|
124
|
+
errorMessage,
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
resolve(
|
|
128
|
+
new ToolResult(
|
|
129
|
+
ToolErrorCodes.InternalError,
|
|
130
|
+
errorMessage,
|
|
131
|
+
[],
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const streamCompletion = {
|
|
139
|
+
markStdoutEnded: () => {
|
|
140
|
+
stdoutEnded = true;
|
|
141
|
+
checkComplete();
|
|
142
|
+
},
|
|
143
|
+
markStderrEnded: () => {
|
|
144
|
+
stderrEnded = true;
|
|
145
|
+
checkComplete();
|
|
146
|
+
},
|
|
147
|
+
markProcessExited: () => {
|
|
148
|
+
processExited = true;
|
|
149
|
+
checkComplete();
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this.setupStdoutHandling(
|
|
154
|
+
childProcess,
|
|
155
|
+
streamCompletion,
|
|
156
|
+
(r) => (result = r),
|
|
157
|
+
);
|
|
158
|
+
this.setupStderrHandling(
|
|
159
|
+
childProcess,
|
|
160
|
+
streamCompletion,
|
|
161
|
+
fallbackErrors,
|
|
162
|
+
);
|
|
163
|
+
this.setupProcessEventHandlers(
|
|
164
|
+
childProcess,
|
|
165
|
+
streamCompletion,
|
|
166
|
+
resolve,
|
|
167
|
+
);
|
|
168
|
+
this.setupCancellationHandler(
|
|
169
|
+
childProcess,
|
|
170
|
+
cancellationToken,
|
|
171
|
+
resolve,
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected spawnProcess(command: string, args: string[]) {
|
|
177
|
+
return spawn(command, args, {
|
|
178
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
179
|
+
shell: false,
|
|
180
|
+
windowsHide: true,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private logCommandStart(
|
|
185
|
+
operation: string,
|
|
186
|
+
executable: string,
|
|
187
|
+
commandArgs: string[],
|
|
188
|
+
): void {
|
|
189
|
+
const fullCommand = [executable, ...commandArgs];
|
|
190
|
+
this.logger.info(
|
|
191
|
+
translate.t("toolWorkflowcompiler.lifecycle.started", {
|
|
192
|
+
operation,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
this.logger.info(
|
|
196
|
+
translate.t("toolWorkflowcompiler.lifecycle.command", {
|
|
197
|
+
command: fullCommand.join(" "),
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private setupStdoutHandling(
|
|
203
|
+
childProcess: any,
|
|
204
|
+
streamCompletion: any,
|
|
205
|
+
onResult: (result: IOutputResult) => void,
|
|
206
|
+
): void {
|
|
207
|
+
if (!childProcess.stdout) {
|
|
208
|
+
streamCompletion.markStdoutEnded();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let buffer = "";
|
|
213
|
+
childProcess.stdout.on("data", (data: Buffer) => {
|
|
214
|
+
buffer += data.toString();
|
|
215
|
+
const lines = buffer.split("\n");
|
|
216
|
+
// Keep the last element — it's either a partial line or ""
|
|
217
|
+
buffer = lines.pop()!;
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
if (!line.trim()) continue;
|
|
220
|
+
this.processOutputLine(line, onResult);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
childProcess.stdout.on("end", () => {
|
|
225
|
+
if (buffer.trim()) {
|
|
226
|
+
this.processOutputLine(buffer, onResult);
|
|
227
|
+
}
|
|
228
|
+
streamCompletion.markStdoutEnded();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private processOutputLine(
|
|
233
|
+
line: string,
|
|
234
|
+
onResult: (result: IOutputResult) => void,
|
|
235
|
+
): void {
|
|
236
|
+
const entry = this.parseOutputEntry(line);
|
|
237
|
+
if (!entry) return;
|
|
238
|
+
|
|
239
|
+
if (entry.type === "Result") {
|
|
240
|
+
onResult(entry as IOutputResult);
|
|
241
|
+
} else if (entry.type === "Log") {
|
|
242
|
+
this.logMessage(entry as IOutputLog);
|
|
243
|
+
} else if (entry.type === "Progress") {
|
|
244
|
+
this.logProgress(entry as IOutputProgress);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private logMessage(log: IOutputLog): void {
|
|
249
|
+
switch (log.logLevel) {
|
|
250
|
+
case "Information":
|
|
251
|
+
this.logger.info(log.message);
|
|
252
|
+
break;
|
|
253
|
+
case "Warning":
|
|
254
|
+
this.logger.warn(log.message);
|
|
255
|
+
break;
|
|
256
|
+
case "Error":
|
|
257
|
+
this.logger.error(log.message);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private logProgress(progress: IOutputProgress): void {
|
|
263
|
+
const percent = progress.percentage ? `${progress.percentage}%` : "";
|
|
264
|
+
const separator = percent && progress.message ? " - " : "";
|
|
265
|
+
this.logger.progress(`${percent}${separator}${progress.message ?? ""}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private setupStderrHandling(
|
|
269
|
+
childProcess: any,
|
|
270
|
+
streamCompletion: any,
|
|
271
|
+
fallbackErrors: string[],
|
|
272
|
+
): void {
|
|
273
|
+
if (!childProcess.stderr) {
|
|
274
|
+
streamCompletion.markStderrEnded();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let buffer = "";
|
|
279
|
+
childProcess.stderr.on("data", (data: Buffer) => {
|
|
280
|
+
buffer += data.toString();
|
|
281
|
+
const lines = buffer.split("\n");
|
|
282
|
+
buffer = lines.pop()!;
|
|
283
|
+
for (const line of lines) {
|
|
284
|
+
if (!line.trim()) continue;
|
|
285
|
+
this.processErrorLine(line, fallbackErrors);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
childProcess.stderr.on("end", () => {
|
|
290
|
+
if (buffer.trim()) {
|
|
291
|
+
this.processErrorLine(buffer, fallbackErrors);
|
|
292
|
+
}
|
|
293
|
+
streamCompletion.markStderrEnded();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private processErrorLine(line: string, fallbackErrors: string[]): void {
|
|
298
|
+
const entry = this.parseOutputEntry(line);
|
|
299
|
+
|
|
300
|
+
if (entry && entry.type === "Log") {
|
|
301
|
+
this.logger.error((entry as IOutputLog).message);
|
|
302
|
+
} else {
|
|
303
|
+
fallbackErrors.push(line);
|
|
304
|
+
this.logger.error(line);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private setupProcessEventHandlers(
|
|
309
|
+
childProcess: any,
|
|
310
|
+
streamCompletion: any,
|
|
311
|
+
resolve: (value: ToolResult) => void,
|
|
312
|
+
): void {
|
|
313
|
+
childProcess.on("exit", () => streamCompletion.markProcessExited());
|
|
314
|
+
|
|
315
|
+
childProcess.on("error", (error: Error) => {
|
|
316
|
+
this.logger.error(
|
|
317
|
+
translate.t("toolWorkflowcompiler.errors.processError", {
|
|
318
|
+
message: error.message,
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
resolve(
|
|
322
|
+
new ToolResult(
|
|
323
|
+
ToolErrorCodes.InternalError,
|
|
324
|
+
translate.t("toolWorkflowcompiler.errors.startFailed", {
|
|
325
|
+
message: error.message,
|
|
326
|
+
}),
|
|
327
|
+
[],
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private setupCancellationHandler(
|
|
334
|
+
childProcess: any,
|
|
335
|
+
cancellationToken: AbortSignal | undefined,
|
|
336
|
+
resolve: (value: ToolResult) => void,
|
|
337
|
+
): void {
|
|
338
|
+
if (!cancellationToken) return;
|
|
339
|
+
|
|
340
|
+
cancellationToken.addEventListener("abort", () => {
|
|
341
|
+
this.logger.warn(
|
|
342
|
+
translate.t("toolWorkflowcompiler.lifecycle.cancelled"),
|
|
343
|
+
);
|
|
344
|
+
childProcess.kill("SIGTERM");
|
|
345
|
+
resolve(
|
|
346
|
+
new ToolResult(
|
|
347
|
+
ToolErrorCodes.Canceled,
|
|
348
|
+
translate.t(
|
|
349
|
+
"toolWorkflowcompiler.errors.operationCancelled",
|
|
350
|
+
),
|
|
351
|
+
[],
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private parseOutputEntry(line: string): IOutputMessage | null {
|
|
358
|
+
try {
|
|
359
|
+
const parsed = JSON.parse(line);
|
|
360
|
+
|
|
361
|
+
// Convert PascalCase to camelCase
|
|
362
|
+
const camelCased = camelCaseKeys(parsed) as Record<string, any>;
|
|
363
|
+
|
|
364
|
+
// Convert numeric error codes to strings
|
|
365
|
+
if (camelCased.errorCode !== undefined) {
|
|
366
|
+
if (camelCased.errorCode === 0 || camelCased.success === true) {
|
|
367
|
+
camelCased.errorCode = ToolErrorCodes.Success;
|
|
368
|
+
} else {
|
|
369
|
+
camelCased.errorCode = String(camelCased.errorCode);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Extract paths from OutputPackages array of objects
|
|
374
|
+
if (camelCased.outputPackages) {
|
|
375
|
+
camelCased.outputPackages = camelCased.outputPackages.map(
|
|
376
|
+
(pkg: any) => pkg.path || pkg,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return camelCased as IOutputMessage;
|
|
381
|
+
} catch {
|
|
382
|
+
// Not JSON, return null
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import type {
|
|
4
|
+
IFileSystem,
|
|
5
|
+
IToolLogger,
|
|
6
|
+
} from "@uipath/solutionpackager-tool-core";
|
|
7
|
+
import {
|
|
8
|
+
Path,
|
|
9
|
+
TemporaryStorageService,
|
|
10
|
+
translate,
|
|
11
|
+
} from "@uipath/solutionpackager-tool-core";
|
|
12
|
+
import { workflowCompilerConfig } from "./workflow-compiler-config.js";
|
|
13
|
+
|
|
14
|
+
const NUGET_FEED_URL =
|
|
15
|
+
"https://uipath.pkgs.visualstudio.com/Public.Feeds/_packaging/UiPath-Internal/nuget/v3/index.json";
|
|
16
|
+
|
|
17
|
+
const PLATFORM_PACKAGES: Record<
|
|
18
|
+
string,
|
|
19
|
+
{ packageId: string; compilerFileName: string }
|
|
20
|
+
> = {
|
|
21
|
+
win32: {
|
|
22
|
+
packageId: "UiPath.WorkflowCompiler.Windows",
|
|
23
|
+
compilerFileName: "UiPath.WorkflowCompiler.exe",
|
|
24
|
+
},
|
|
25
|
+
darwin: {
|
|
26
|
+
packageId: "UiPath.WorkflowCompiler.macOS",
|
|
27
|
+
compilerFileName: "UiPath.WorkflowCompiler.dll",
|
|
28
|
+
},
|
|
29
|
+
linux: {
|
|
30
|
+
packageId: "UiPath.WorkflowCompiler.Linux",
|
|
31
|
+
compilerFileName: "UiPath.WorkflowCompiler.dll",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const RESTORE_CSPROJ_TEMPLATE = `<Project Sdk="Microsoft.NET.Sdk">
|
|
36
|
+
<PropertyGroup>
|
|
37
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
38
|
+
</PropertyGroup>
|
|
39
|
+
<ItemGroup>
|
|
40
|
+
<PackageReference Include="{packageId}" Version="{version}" />
|
|
41
|
+
</ItemGroup>
|
|
42
|
+
</Project>`;
|
|
43
|
+
|
|
44
|
+
const EMPTY_NUGET_CONFIG = `<?xml version="1.0" encoding="utf-8"?>
|
|
45
|
+
<configuration>
|
|
46
|
+
</configuration>`;
|
|
47
|
+
|
|
48
|
+
export interface IWorkflowCompilerPathResolver {
|
|
49
|
+
getCompilerPathAsync(): Promise<string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves and caches the workflow compiler executable path.
|
|
54
|
+
*
|
|
55
|
+
* Resolution chain:
|
|
56
|
+
* 1. In-memory cache (subsequent calls)
|
|
57
|
+
* 2. Explicit config path (`workflowCompilerConfig.workflowCompilerPath`)
|
|
58
|
+
* 3. `UIPATH_WORKFLOWCOMPILER_LOCATION` env var — full file path (pipelines / offline)
|
|
59
|
+
* 4. NuGet global packages cache
|
|
60
|
+
* 5. `dotnet restore` to populate NuGet cache, then (4) again
|
|
61
|
+
*/
|
|
62
|
+
export class WorkflowCompilerPathResolver
|
|
63
|
+
implements IWorkflowCompilerPathResolver
|
|
64
|
+
{
|
|
65
|
+
private static instance: IWorkflowCompilerPathResolver | null = null;
|
|
66
|
+
private cachedPath: string | null = null;
|
|
67
|
+
private pendingResolution: Promise<string> | null = null;
|
|
68
|
+
|
|
69
|
+
private readonly tempStorage: TemporaryStorageService;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly logger: IToolLogger,
|
|
73
|
+
private readonly fileSystem: IFileSystem,
|
|
74
|
+
) {
|
|
75
|
+
this.tempStorage = new TemporaryStorageService(fileSystem);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static getInstance(
|
|
79
|
+
logger: IToolLogger,
|
|
80
|
+
fileSystem: IFileSystem,
|
|
81
|
+
): IWorkflowCompilerPathResolver {
|
|
82
|
+
if (!WorkflowCompilerPathResolver.instance) {
|
|
83
|
+
WorkflowCompilerPathResolver.instance =
|
|
84
|
+
new WorkflowCompilerPathResolver(logger, fileSystem);
|
|
85
|
+
}
|
|
86
|
+
return WorkflowCompilerPathResolver.instance;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getCompilerPathAsync(): Promise<string> {
|
|
90
|
+
if (this.cachedPath) {
|
|
91
|
+
this.logger.info(
|
|
92
|
+
translate.t(
|
|
93
|
+
"toolWorkflowcompiler.pathResolver.info.usingCached",
|
|
94
|
+
{ path: this.cachedPath },
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
return this.cachedPath;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Coalesce concurrent callers into a single resolution to avoid
|
|
101
|
+
// parallel dotnet restore processes.
|
|
102
|
+
if (this.pendingResolution) {
|
|
103
|
+
return this.pendingResolution;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.pendingResolution = this.resolveCompilerPath();
|
|
107
|
+
this.cachedPath = await this.pendingResolution;
|
|
108
|
+
this.logger.info(
|
|
109
|
+
translate.t("toolWorkflowcompiler.pathResolver.info.resolved", {
|
|
110
|
+
path: this.cachedPath,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
return this.cachedPath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async resolveCompilerPath(): Promise<string> {
|
|
117
|
+
this.logger.info("[PathResolver] resolveCompilerPath started");
|
|
118
|
+
|
|
119
|
+
if (workflowCompilerConfig.workflowCompilerPath) {
|
|
120
|
+
this.logger.info(
|
|
121
|
+
translate.t(
|
|
122
|
+
"toolWorkflowcompiler.pathResolver.info.usingExplicitPath",
|
|
123
|
+
{ path: workflowCompilerConfig.workflowCompilerPath },
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
return workflowCompilerConfig.workflowCompilerPath;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.logger.info(
|
|
130
|
+
translate.t("toolWorkflowcompiler.pathResolver.info.resolving"),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const envPath = await this.findInEnvLocation();
|
|
134
|
+
if (envPath) return envPath;
|
|
135
|
+
|
|
136
|
+
const version = workflowCompilerConfig.workflowCompilerVersion;
|
|
137
|
+
const compilerPath = this.getNuGetCompilerPath(version);
|
|
138
|
+
|
|
139
|
+
this.logger.info(
|
|
140
|
+
translate.t(
|
|
141
|
+
"toolWorkflowcompiler.pathResolver.info.checkingNuGetCache",
|
|
142
|
+
{ path: compilerPath },
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
if (await this.fileSystem.exists(compilerPath)) {
|
|
146
|
+
this.logger.info(
|
|
147
|
+
translate.t(
|
|
148
|
+
"toolWorkflowcompiler.pathResolver.info.foundInNuGetCache",
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
return compilerPath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.logger.info(
|
|
155
|
+
translate.t(
|
|
156
|
+
"toolWorkflowcompiler.pathResolver.info.notFoundStartingInstall",
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
return this.restoreAndResolve(compilerPath, version);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async findInEnvLocation(): Promise<string | null> {
|
|
163
|
+
const location = process.env.UIPATH_WORKFLOWCOMPILER_LOCATION;
|
|
164
|
+
if (!location) return null;
|
|
165
|
+
|
|
166
|
+
this.logger.info(
|
|
167
|
+
translate.t(
|
|
168
|
+
"toolWorkflowcompiler.pathResolver.info.checkingEnvVar",
|
|
169
|
+
{ path: location },
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Env var must point to a full file path (.dll or .exe)
|
|
174
|
+
if (
|
|
175
|
+
(location.endsWith(".dll") || location.endsWith(".exe")) &&
|
|
176
|
+
(await this.fileSystem.exists(location))
|
|
177
|
+
) {
|
|
178
|
+
this.logger.info(
|
|
179
|
+
translate.t(
|
|
180
|
+
"toolWorkflowcompiler.pathResolver.info.foundViaEnv",
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
return location;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async restoreAndResolve(
|
|
190
|
+
compilerPath: string,
|
|
191
|
+
version: string,
|
|
192
|
+
): Promise<string> {
|
|
193
|
+
const { packageId } = this.getPlatformInfo();
|
|
194
|
+
const tempDir = await this.tempStorage.getTempFolderPath();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
this.logger.info(
|
|
198
|
+
translate.t(
|
|
199
|
+
"toolWorkflowcompiler.pathResolver.info.restoringPackage",
|
|
200
|
+
{ packageName: packageId, version, platform: platform() },
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const csproj = RESTORE_CSPROJ_TEMPLATE.replace(
|
|
205
|
+
"{packageId}",
|
|
206
|
+
packageId,
|
|
207
|
+
).replace("{version}", version);
|
|
208
|
+
await this.fileSystem.writeFile(
|
|
209
|
+
Path.join(tempDir, "restore.csproj"),
|
|
210
|
+
csproj,
|
|
211
|
+
);
|
|
212
|
+
await this.fileSystem.writeFile(
|
|
213
|
+
Path.join(tempDir, "nuget.config"),
|
|
214
|
+
EMPTY_NUGET_CONFIG,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
this.logger.info(
|
|
218
|
+
translate.t(
|
|
219
|
+
"toolWorkflowcompiler.pathResolver.info.runningDotnetRestore",
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
try {
|
|
223
|
+
execFileSync(
|
|
224
|
+
"dotnet",
|
|
225
|
+
[
|
|
226
|
+
"restore",
|
|
227
|
+
"--source",
|
|
228
|
+
NUGET_FEED_URL,
|
|
229
|
+
"--source",
|
|
230
|
+
"https://api.nuget.org/v3/index.json",
|
|
231
|
+
],
|
|
232
|
+
{
|
|
233
|
+
cwd: tempDir,
|
|
234
|
+
stdio: "pipe",
|
|
235
|
+
},
|
|
236
|
+
);
|
|
237
|
+
} catch (error: unknown) {
|
|
238
|
+
const err = error as Record<string, unknown>;
|
|
239
|
+
const stderr = err?.stderr ? String(err.stderr).trim() : "";
|
|
240
|
+
const stdout = err?.stdout ? String(err.stdout).trim() : "";
|
|
241
|
+
const details =
|
|
242
|
+
stderr ||
|
|
243
|
+
stdout ||
|
|
244
|
+
(error instanceof Error ? error.message : String(error));
|
|
245
|
+
throw new Error(
|
|
246
|
+
translate.t(
|
|
247
|
+
"toolWorkflowcompiler.pathResolver.errors.dotnetRestoreFailed",
|
|
248
|
+
{ details },
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
this.logger.info(
|
|
253
|
+
translate.t(
|
|
254
|
+
"toolWorkflowcompiler.pathResolver.info.restoreCompleted",
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if (!(await this.fileSystem.exists(compilerPath))) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
translate.t(
|
|
261
|
+
"toolWorkflowcompiler.pathResolver.errors.compilerNotFoundAfterRestore",
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.logger.info(
|
|
267
|
+
translate.t(
|
|
268
|
+
"toolWorkflowcompiler.pathResolver.info.installCompleted",
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
return compilerPath;
|
|
272
|
+
} finally {
|
|
273
|
+
this.logger.info(
|
|
274
|
+
translate.t(
|
|
275
|
+
"toolWorkflowcompiler.pathResolver.info.cleaningUp",
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
await this.tempStorage.cleanup();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private getPlatformInfo(): {
|
|
283
|
+
packageId: string;
|
|
284
|
+
compilerFileName: string;
|
|
285
|
+
} {
|
|
286
|
+
const info = PLATFORM_PACKAGES[platform()];
|
|
287
|
+
if (!info) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
translate.t(
|
|
290
|
+
"toolWorkflowcompiler.pathResolver.errors.unsupportedPlatform",
|
|
291
|
+
{ platform: platform() },
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return info;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private getNuGetCompilerPath(version: string): string {
|
|
299
|
+
const cacheRoot =
|
|
300
|
+
process.env.NUGET_PACKAGES ??
|
|
301
|
+
Path.join(homedir(), ".nuget", "packages");
|
|
302
|
+
const { packageId, compilerFileName } = this.getPlatformInfo();
|
|
303
|
+
return Path.join(
|
|
304
|
+
cacheRoot,
|
|
305
|
+
packageId.toLowerCase(),
|
|
306
|
+
version.toLowerCase(),
|
|
307
|
+
"WorkflowCompiler",
|
|
308
|
+
compilerFileName,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|