@uipath/packager-tool-workflowcompiler 1.202.0-preview.140 → 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.
@@ -1,519 +0,0 @@
1
- import { OrchestratorFeedComposer } from "@uipath/packager-orchestrator-client";
2
- import {
3
- type FileSystemStats,
4
- type IFileSystem,
5
- type IProjectBuildOptions,
6
- type IProjectCleanupOptions,
7
- type IProjectOptions,
8
- type IProjectPackOptions,
9
- type IProjectRestoreOptions,
10
- type IProjectValidateOptions,
11
- type IToolLogger,
12
- LogLevel,
13
- type ProjectOperationContext,
14
- ProjectTool,
15
- type ToolResult,
16
- toProjectCleanupInfo,
17
- type WorkflowCompilerCleanupPayload,
18
- } from "@uipath/solutionpackager-tool-core";
19
- import { WorkflowCompilerExecutor } from "./workflow-compiler-executor.js";
20
-
21
- /**
22
- * Raw `remove-unused-dependencies` payload the WorkflowCompiler emits on
23
- * `details.removeUnusedDependencies` (PascalCase on the wire, camelCased by the
24
- * executor). Owned by this tool; mapped to the generic {@link ProjectCleanupInfo}
25
- * before it leaves `cleanupAsync` so nothing downstream depends on this shape.
26
- */
27
- type RawRemoveUnusedDependenciesResult = WorkflowCompilerCleanupPayload;
28
-
29
- const NO_CLEANUP_SOURCE_FILES_MESSAGE =
30
- "no workflow xamls or coded source files";
31
- const CLEANUP_SOURCE_FILE_EXTENSIONS = [".xaml", ".cs", ".vb"];
32
- const CLEANUP_SOURCE_SCAN_SKIP_DIRS = new Set([
33
- ".git",
34
- ".local",
35
- ".project",
36
- "bin",
37
- "dist",
38
- "node_modules",
39
- "obj",
40
- ]);
41
-
42
- /**
43
- * The two NuGet-source channels the WorkflowCompiler accepts, kept apart on
44
- * purpose:
45
- * - `orchestratorFeedsJsonPath` — Orchestrator tenant feeds composed internally into a
46
- * JSON file, passed on the hidden `--nuget-config-file` option. Never
47
- * user-supplied.
48
- * - `userNugetConfigPath` — the user's own NuGet.config (XML), passed verbatim on
49
- * `--nuget-config`.
50
- */
51
- interface FeedPaths {
52
- orchestratorFeedsJsonPath?: string;
53
- userNugetConfigPath?: string;
54
- }
55
-
56
- /**
57
- * Workflow compiler tool implementation.
58
- * Builds commands and delegates execution to WorkflowCompilerExecutor.
59
- */
60
- export class WorkflowCompilerTool extends ProjectTool {
61
- private readonly executor: WorkflowCompilerExecutor;
62
-
63
- constructor(
64
- fileSystem: IFileSystem,
65
- logger: IToolLogger,
66
- private readonly context?: ProjectOperationContext,
67
- ) {
68
- super(fileSystem, logger);
69
- this.executor = new WorkflowCompilerExecutor(logger, fileSystem);
70
- }
71
-
72
- private async withFeedPaths(
73
- options: IProjectOptions,
74
- run: (paths: FeedPaths) => Promise<ToolResult>,
75
- ): Promise<ToolResult> {
76
- // The user's own NuGet.config (XML) always rides the --nuget-config
77
- // channel; it is never mixed into the internal feeds file.
78
- const userNugetConfigPath = options.nuGetSourcesConfigPath;
79
- const connection = this.context?.connection;
80
- // A named folder must fail closed. Skipping composition here would let
81
- // the build fall back to whatever other sources are configured, which is
82
- // precisely what --feed-folder exists to prevent — so hand it to the
83
- // composer even without a session and let it raise.
84
- if (!connection && !options.feedFolder) {
85
- return run({ userNugetConfigPath });
86
- }
87
- // Orchestrator tenant feeds are composed into a JSON file that only ever
88
- // goes on the hidden --nuget-config-file option.
89
- const composed = await new OrchestratorFeedComposer(
90
- this.fileSystem,
91
- this.logger,
92
- ).composeAsync({ connection, feedFolder: options.feedFolder });
93
- try {
94
- return await run({
95
- orchestratorFeedsJsonPath: composed.configPath,
96
- userNugetConfigPath,
97
- });
98
- } finally {
99
- await composed.dispose();
100
- }
101
- }
102
-
103
- async restoreAsync(
104
- options: IProjectRestoreOptions,
105
- cancellationToken?: AbortSignal,
106
- ): Promise<ToolResult> {
107
- return this.withFeedPaths(options, (paths) => {
108
- const args = [
109
- `-p`,
110
- options.projectPath,
111
- `--log-level`,
112
- String(options.logLevel ?? LogLevel.Info),
113
- `--destination`,
114
- options.outputPath ?? "",
115
- `--format-logs`,
116
- `true`,
117
- `--exclude-configured-sources`,
118
- `${options.excludeConfiguredSources}`,
119
- ];
120
-
121
- if (paths.orchestratorFeedsJsonPath) {
122
- args.push(
123
- `--nuget-config-file`,
124
- paths.orchestratorFeedsJsonPath,
125
- );
126
- }
127
- if (paths.userNugetConfigPath) {
128
- args.push(`--nuget-config`, paths.userNugetConfigPath);
129
- }
130
-
131
- return this.executor.executeAsync(
132
- "restore",
133
- args,
134
- cancellationToken,
135
- );
136
- });
137
- }
138
-
139
- async validateAsync(
140
- options: IProjectValidateOptions,
141
- cancellationToken?: AbortSignal,
142
- ): Promise<ToolResult> {
143
- return this.withFeedPaths(options, (paths) => {
144
- const args = [
145
- `-p`,
146
- options.projectPath,
147
- `--log-level`,
148
- String(options.logLevel ?? LogLevel.Info),
149
- `--format-logs`,
150
- `true`,
151
- `--skip-analyze`,
152
- `${options.skipAnalyze}`,
153
- `--skip-validate`,
154
- `${options.skipValidate}`,
155
- `--exclude-configured-sources`,
156
- `${options.excludeConfiguredSources}`,
157
- ];
158
-
159
- if (options.detailedLogPath) {
160
- args.push(`--detailed`, options.detailedLogPath);
161
- }
162
- if (options.defaultSeverity) {
163
- args.push(`--default-severity`, options.defaultSeverity);
164
- }
165
- if (options.policyFilePath) {
166
- args.push(`--policy-file`, options.policyFilePath);
167
- }
168
- if (options.policyFileType) {
169
- args.push(`--policy-file-type`, `${options.policyFileType}`);
170
- }
171
- if (paths.orchestratorFeedsJsonPath) {
172
- args.push(
173
- `--nuget-config-file`,
174
- paths.orchestratorFeedsJsonPath,
175
- );
176
- }
177
- if (paths.userNugetConfigPath) {
178
- args.push(`--nuget-config`, paths.userNugetConfigPath);
179
- }
180
-
181
- return this.executor.executeAsync(
182
- "validate",
183
- args,
184
- cancellationToken,
185
- );
186
- });
187
- }
188
-
189
- async buildAsync(
190
- options: IProjectBuildOptions,
191
- cancellationToken?: AbortSignal,
192
- ): Promise<ToolResult> {
193
- return this.withFeedPaths(options, (paths) => {
194
- const args = [
195
- `-p`,
196
- options.projectPath,
197
- `-o`,
198
- options.outputPath,
199
- `--log-level`,
200
- String(options.logLevel ?? LogLevel.Info),
201
- `--format-logs`,
202
- `true`,
203
- `--skip-analyze`,
204
- `${options.skipAnalyze}`,
205
- `--skip-validate`,
206
- `${options.skipValidate}`,
207
- `--exclude-configured-sources`,
208
- `${options.excludeConfiguredSources}`,
209
- ];
210
-
211
- if (options.detailedLogPath) {
212
- args.push(`--detailed`, options.detailedLogPath);
213
- }
214
- if (options.policyFilePath) {
215
- args.push(`--policy-file`, options.policyFilePath);
216
- }
217
- if (options.policyFileType) {
218
- args.push(`--policy-file-type`, `${options.policyFileType}`);
219
- }
220
- if (options.outputType) {
221
- const outputType = this.mapOutputType(options.outputType);
222
- args.push(`--output-type`, outputType);
223
- }
224
- if (paths.orchestratorFeedsJsonPath) {
225
- args.push(
226
- `--nuget-config-file`,
227
- paths.orchestratorFeedsJsonPath,
228
- );
229
- }
230
- if (paths.userNugetConfigPath) {
231
- args.push(`--nuget-config`, paths.userNugetConfigPath);
232
- }
233
-
234
- return this.executor.executeAsync("build", args, cancellationToken);
235
- });
236
- }
237
-
238
- async packAsync(
239
- options: IProjectPackOptions,
240
- cancellationToken?: AbortSignal,
241
- ): Promise<ToolResult> {
242
- return this.withFeedPaths(options, async (paths) => {
243
- const args: string[] = [
244
- `-p`,
245
- options.projectPath,
246
- `-o`,
247
- options.outputPath,
248
- `--build-version`,
249
- options.package.version,
250
- `--log-level`,
251
- String(options.logLevel ?? LogLevel.Info),
252
- `--format-logs`,
253
- `true`,
254
- `--skip-analyze`,
255
- `${options.skipAnalyze}`,
256
- `--skip-validate`,
257
- `${options.skipValidate}`,
258
- `--no-optimize-deps`,
259
- `${options.skipDependencyOptimization}`,
260
- `--include-sources`,
261
- `${options.includeSources}`,
262
- `--split-packages`,
263
- `${options.splitPackages}`,
264
- `--exclude-configured-sources`,
265
- `${options.excludeConfiguredSources}`,
266
- `--pack-options`,
267
- JSON.stringify(options.package),
268
- ];
269
-
270
- if (options.package.id) {
271
- args.push(`--package-name`, options.package.id);
272
- }
273
- if (options.detailedLogPath) {
274
- args.push(`--detailed`, options.detailedLogPath);
275
- }
276
- if (options.policyFilePath) {
277
- args.push(`--policy-file`, options.policyFilePath);
278
- }
279
- if (options.policyFileType) {
280
- args.push(`--policy-file-type`, options.policyFileType);
281
- }
282
- if (options.outputType) {
283
- const outputType = this.mapOutputType(options.outputType);
284
- args.push(`--output-type`, outputType);
285
- }
286
- if (paths.orchestratorFeedsJsonPath) {
287
- args.push(
288
- `--nuget-config-file`,
289
- paths.orchestratorFeedsJsonPath,
290
- );
291
- }
292
- if (paths.userNugetConfigPath) {
293
- args.push(`--nuget-config`, paths.userNugetConfigPath);
294
- }
295
-
296
- const result = await this.executor.executeAsync(
297
- "build",
298
- args,
299
- cancellationToken,
300
- );
301
- return this.recoverPackagePathsAsync(result, options.outputPath);
302
- });
303
- }
304
-
305
- async cleanupAsync(
306
- options: IProjectCleanupOptions,
307
- cancellationToken?: AbortSignal,
308
- ): Promise<ToolResult> {
309
- return this.withFeedPaths(options, async (paths) => {
310
- const args = [
311
- `-p`,
312
- options.projectPath,
313
- `--log-level`,
314
- String(options.logLevel ?? LogLevel.Info),
315
- `--format-logs`,
316
- `true`,
317
- `--exclude-configured-sources`,
318
- `${options.excludeConfiguredSources}`,
319
- ];
320
-
321
- // --dry-run and --skip-imports are boolean switches on the compiler CLI:
322
- // present means true, absent means false. They take no value — passing
323
- // one (`--dry-run false`) leaves a stray token and still enables the flag.
324
- if (options.dryRun) {
325
- args.push(`--dry-run`);
326
- }
327
- if (options.skipImports) {
328
- args.push(`--skip-imports`);
329
- }
330
-
331
- if (paths.orchestratorFeedsJsonPath) {
332
- args.push(
333
- `--nuget-config-file`,
334
- paths.orchestratorFeedsJsonPath,
335
- );
336
- }
337
- if (paths.userNugetConfigPath) {
338
- args.push(`--nuget-config`, paths.userNugetConfigPath);
339
- }
340
-
341
- const result = await this.executor.executeAsync(
342
- "remove-unused-dependencies",
343
- args,
344
- cancellationToken,
345
- );
346
- if (
347
- await this.shouldRetryCleanupNoSourceFalseNegativeAsync(
348
- result,
349
- options.projectPath,
350
- )
351
- ) {
352
- this.logger.warn(
353
- "[WorkflowCompiler] remove-unused-dependencies reported no workflow/source files even though the project contains source files; retrying once.",
354
- );
355
- const retryResult = await this.executor.executeAsync(
356
- "remove-unused-dependencies",
357
- args,
358
- cancellationToken,
359
- );
360
- return this.toProjectCleanupResult(retryResult);
361
- }
362
- return this.toProjectCleanupResult(result);
363
- });
364
- }
365
-
366
- private async shouldRetryCleanupNoSourceFalseNegativeAsync(
367
- result: ToolResult,
368
- projectPath: string,
369
- ): Promise<boolean> {
370
- if (result.isSuccess) {
371
- return false;
372
- }
373
-
374
- const message = result.message?.toLowerCase();
375
- if (!message?.includes(NO_CLEANUP_SOURCE_FILES_MESSAGE)) {
376
- return false;
377
- }
378
-
379
- return await this.hasCleanupSourceFilesAsync(projectPath);
380
- }
381
-
382
- private async hasCleanupSourceFilesAsync(
383
- projectPath: string,
384
- ): Promise<boolean> {
385
- let entries: string[];
386
- try {
387
- entries = await this.fileSystem.readdir(projectPath);
388
- } catch {
389
- return false;
390
- }
391
-
392
- for (const entry of entries) {
393
- const lowerEntry = entry.toLowerCase();
394
- const fullPath = this.fileSystem.path.join(projectPath, entry);
395
- let stats: FileSystemStats | null;
396
- try {
397
- stats = await this.fileSystem.stat(fullPath);
398
- } catch {
399
- continue;
400
- }
401
- if (!stats) {
402
- continue;
403
- }
404
-
405
- if (
406
- stats.isFile() &&
407
- CLEANUP_SOURCE_FILE_EXTENSIONS.some((extension) =>
408
- lowerEntry.endsWith(extension),
409
- )
410
- ) {
411
- return true;
412
- }
413
-
414
- if (
415
- stats.isDirectory() &&
416
- !CLEANUP_SOURCE_SCAN_SKIP_DIRS.has(lowerEntry) &&
417
- (await this.hasCleanupSourceFilesAsync(fullPath))
418
- ) {
419
- return true;
420
- }
421
- }
422
-
423
- return false;
424
- }
425
-
426
- /**
427
- * Convert the WorkflowCompiler's raw `remove-unused-dependencies` payload
428
- * into the generic {@link ProjectCleanupInfo} the cleanup pipeline expects,
429
- * exposed under the generic `details.projectCleanup` key. The
430
- * workflow-specific bits (per-file removed namespaces) are preserved under
431
- * `toolDetails` with this tool's own type; callers that only want the general
432
- * result ignore it. Leaves the result untouched when the tool reported no
433
- * cleanup payload (e.g. a failure, or an older compiler build).
434
- */
435
- private toProjectCleanupResult(result: ToolResult): ToolResult {
436
- const raw = result.details?.removeUnusedDependencies as
437
- | RawRemoveUnusedDependenciesResult
438
- | undefined;
439
- if (!raw) {
440
- return result;
441
- }
442
-
443
- // Re-key the wire-named payload under the generic `projectCleanup` so
444
- // nothing downstream sees this tool's domain naming.
445
- const details = { ...result.details };
446
- delete details.removeUnusedDependencies;
447
- details.projectCleanup = toProjectCleanupInfo(raw);
448
- result.details = details;
449
- return result;
450
- }
451
-
452
- /**
453
- * Maps output type for workflow compiler.
454
- */
455
- private mapOutputType(outputType: string): string {
456
- return outputType === "WebApp" ? "Process" : outputType;
457
- }
458
-
459
- /**
460
- * WORKAROUND (UV-15007): older WorkflowCompiler builds report the staging
461
- * content folder in their pack result instead of the produced `.nupkg` path,
462
- * so downstream consumers (package signing, solution compression) cannot rely
463
- * on `packages`. When the result does not already carry `.nupkg` paths, recover
464
- * them by scanning the output folder.
465
- *
466
- * Remove this fallback after UV-15007 lands and the bundled WorkflowCompiler
467
- * surfaces the produced `.nupkg` path in its result
468
- * (see scripts/Download-WorkflowCompiler.ps1).
469
- */
470
- private async recoverPackagePathsAsync(
471
- result: ToolResult,
472
- outputPath: string,
473
- ): Promise<ToolResult> {
474
- if (!result.isSuccess) {
475
- return result;
476
- }
477
-
478
- const alreadyHasNupkgPaths =
479
- result.packages.length > 0 &&
480
- result.packages.every((p) => p.toLowerCase().endsWith(".nupkg"));
481
- if (alreadyHasNupkgPaths) {
482
- return result;
483
- }
484
-
485
- const recovered = await this.findNupkgFilesAsync(outputPath);
486
- if (recovered.length > 0) {
487
- this.logger.warn(
488
- `[WorkflowCompiler] pack result did not include .nupkg paths; recovered ${recovered.length} package(s) by scanning the output folder (UV-15007 workaround).`,
489
- );
490
- result.packages = recovered;
491
- }
492
- return result;
493
- }
494
-
495
- /**
496
- * Recursively collect `.nupkg` files under `dir` (cross-platform). Returns an
497
- * empty list if the folder cannot be read.
498
- */
499
- private async findNupkgFilesAsync(dir: string): Promise<string[]> {
500
- const results: string[] = [];
501
- let entries: string[];
502
- try {
503
- entries = await this.fileSystem.readdir(dir);
504
- } catch {
505
- return results;
506
- }
507
-
508
- for (const entry of entries) {
509
- const fullPath = this.fileSystem.path.join(dir, entry);
510
- const stats = await this.fileSystem.stat(fullPath);
511
- if (stats?.isDirectory()) {
512
- results.push(...(await this.findNupkgFilesAsync(fullPath)));
513
- } else if (entry.toLowerCase().endsWith(".nupkg")) {
514
- results.push(fullPath);
515
- }
516
- }
517
- return results;
518
- }
519
- }