@uipath/packager-tool-webapp 1.0.4

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.
@@ -0,0 +1,67 @@
1
+ import type {
2
+ IFileSystem,
3
+ NugetPackageInfo,
4
+ } from "@uipath/solutionpackager-tool-core";
5
+ import type { WebAppManifest } from "../models/webapp-manifest.js";
6
+
7
+ /**
8
+ * Arguments for packaging operation
9
+ */
10
+ export interface PackageArgs {
11
+ /**
12
+ * File system instance
13
+ */
14
+ fileSystem: IFileSystem;
15
+
16
+ /**
17
+ * Project root path
18
+ */
19
+ projectPath: string;
20
+
21
+ /**
22
+ * Parsed webApp manifest
23
+ */
24
+ manifest: WebAppManifest;
25
+
26
+ /**
27
+ * Output directory path
28
+ */
29
+ outputPath: string;
30
+
31
+ /**
32
+ * NuGet package information
33
+ */
34
+ packageInfo: NugetPackageInfo;
35
+
36
+ /**
37
+ * Logger instance (optional, for strategy-specific logging)
38
+ */
39
+ logger?: {
40
+ info: (message: string) => void;
41
+ error: (message: string) => void;
42
+ progress: (message: string) => void;
43
+ warn?: (message: string) => void;
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Strategy interface for different WebApp variant packaging
49
+ */
50
+ export interface VariantStrategy {
51
+ /**
52
+ * Package the WebApp according to variant-specific logic
53
+ * @param args - Packaging arguments
54
+ * @returns Path to the created .nupkg file
55
+ */
56
+ packageAsync(args: PackageArgs): Promise<string>;
57
+ /**
58
+ * Optional variant-specific validation step invoked during tool validation.
59
+ * Implementations may warn or throw on invalid configuration.
60
+ */
61
+ validateAsync?(args: {
62
+ fileSystem: PackageArgs["fileSystem"];
63
+ projectPath: string;
64
+ manifest: PackageArgs["manifest"];
65
+ logger?: PackageArgs["logger"];
66
+ }): Promise<void>;
67
+ }
@@ -0,0 +1,58 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ import { Path } from "@uipath/solutionpackager-tool-core";
3
+
4
+ /**
5
+ * Copy directory recursively from source to destination
6
+ */
7
+ export async function copyDirectoryAsync(
8
+ fileSystem: IFileSystem,
9
+ sourcePath: string,
10
+ destinationPath: string,
11
+ ): Promise<void> {
12
+ await fileSystem.mkdir(destinationPath);
13
+
14
+ const entries = await fileSystem.readdir(sourcePath);
15
+
16
+ for (const entry of entries) {
17
+ const sourceEntry = Path.join(sourcePath, entry);
18
+ const destEntry = Path.join(destinationPath, entry);
19
+
20
+ const stat = await fileSystem.stat(sourceEntry);
21
+ if (stat?.isDirectory()) {
22
+ await copyDirectoryAsync(fileSystem, sourceEntry, destEntry);
23
+ } else if (stat?.isFile()) {
24
+ const content = await fileSystem.readFile(sourceEntry);
25
+ if (content) {
26
+ await fileSystem.writeFile(destEntry, content);
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Find .nupkg file in a directory
34
+ */
35
+ export async function findNupkgInFolder(
36
+ fileSystem: IFileSystem,
37
+ directoryPath: string,
38
+ ): Promise<string | null> {
39
+ const entries = await fileSystem.readdir(directoryPath);
40
+
41
+ for (const entry of entries) {
42
+ const entryPath = Path.join(directoryPath, entry);
43
+ const stat = await fileSystem.stat(entryPath);
44
+
45
+ if (stat?.isFile() && entry.endsWith(".nupkg")) {
46
+ return entryPath;
47
+ }
48
+
49
+ if (stat?.isDirectory()) {
50
+ const found = await findNupkgInFolder(fileSystem, entryPath);
51
+ if (found) {
52
+ return found;
53
+ }
54
+ }
55
+ }
56
+
57
+ return null;
58
+ }
@@ -0,0 +1,34 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ import { Path } from "@uipath/solutionpackager-tool-core";
3
+ import type { WebAppManifest } from "../models/webapp-manifest.js";
4
+ import { WEBAPP_MANIFEST_FILE_NAME } from "../models/webapp-manifest.js";
5
+
6
+ /**
7
+ * Load and parse webAppManifest.json from project path
8
+ */
9
+ export async function loadWebAppManifest(
10
+ fileSystem: IFileSystem,
11
+ projectPath: string,
12
+ ): Promise<WebAppManifest | null> {
13
+ const manifestPath = Path.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
14
+
15
+ const exists = await fileSystem.exists(manifestPath);
16
+ if (!exists) {
17
+ return null;
18
+ }
19
+
20
+ const content = await fileSystem.readFile(manifestPath);
21
+ if (!content) {
22
+ return null;
23
+ }
24
+
25
+ try {
26
+ const text = new TextDecoder().decode(content);
27
+ const manifest: WebAppManifest = JSON.parse(text);
28
+ return manifest;
29
+ } catch (error) {
30
+ throw new Error(
31
+ `Failed to parse ${WEBAPP_MANIFEST_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`,
32
+ );
33
+ }
34
+ }
@@ -0,0 +1,23 @@
1
+ import {
2
+ type IFileSystem,
3
+ type IProjectToolFactory,
4
+ type IToolLogger,
5
+ type ProjectTool,
6
+ type ProjectType,
7
+ ProjectTypes,
8
+ } from "@uipath/solutionpackager-tool-core";
9
+ import { WebAppTool } from "./webapp-tool.js";
10
+
11
+ /**
12
+ * Factory for creating WebApp project tools
13
+ */
14
+ export class WebAppToolFactory implements IProjectToolFactory {
15
+ readonly supportedTypes: readonly ProjectType[] = [ProjectTypes.AppV2];
16
+
17
+ async createAsync(
18
+ logger: IToolLogger,
19
+ fileSystem: IFileSystem,
20
+ ): Promise<ProjectTool> {
21
+ return new WebAppTool(fileSystem, logger);
22
+ }
23
+ }
@@ -0,0 +1,269 @@
1
+ import {
2
+ type IFileSystem,
3
+ type IProjectBuildOptions,
4
+ type IProjectPackOptions,
5
+ type IProjectRestoreOptions,
6
+ type IProjectValidateOptions,
7
+ type IToolLogger,
8
+ Path,
9
+ ProjectTool,
10
+ TemporaryStorageService,
11
+ ToolErrorCodes,
12
+ ToolResult,
13
+ } from "@uipath/solutionpackager-tool-core";
14
+ import { ERROR_MESSAGES, PROJECT_JSON_FILE } from "./constants.js";
15
+ import type { WebAppManifest } from "./models/webapp-manifest.js";
16
+ import { WEBAPP_MANIFEST_FILE_NAME } from "./models/webapp-manifest.js";
17
+ import type { VariantStrategy } from "./strategies/variant-strategy.js";
18
+ import { VariantStrategyFactory } from "./strategies/variant-strategy-factory.js";
19
+ import { loadWebAppManifest } from "./utils/manifest-loader.js";
20
+
21
+ /**
22
+ * WebApp project tool implementation
23
+ * Packages WebApp-type projects into .nupkg artifacts
24
+ */
25
+ export class WebAppTool extends ProjectTool {
26
+ private readonly _temporaryStorage: TemporaryStorageService;
27
+ private _tempBuildFolder: string | null = null;
28
+
29
+ constructor(fileSystem: IFileSystem, logger: IToolLogger) {
30
+ super(fileSystem, logger);
31
+ this._temporaryStorage = new TemporaryStorageService(fileSystem);
32
+ }
33
+
34
+ override async restoreAsync(
35
+ _options: IProjectRestoreOptions,
36
+ _cancellationToken?: AbortSignal,
37
+ ): Promise<ToolResult> {
38
+ this.logger.info(
39
+ "Restore operation is not required for WebApp projects",
40
+ );
41
+ return ToolResult.success();
42
+ }
43
+
44
+ override async validateAsync(
45
+ options: IProjectValidateOptions,
46
+ _cancellationToken?: AbortSignal,
47
+ ): Promise<ToolResult> {
48
+ // Input validation
49
+ if (!options.projectPath) {
50
+ return this.handleError(
51
+ new Error(ERROR_MESSAGES.PROJECT_PATH_REQUIRED),
52
+ "Validation",
53
+ );
54
+ }
55
+
56
+ try {
57
+ this.logger.info("Validating WebApp project...");
58
+
59
+ // Prepare manifest and strategy (common logic factored out)
60
+ let manifest: WebAppManifest | undefined;
61
+ let strategy: VariantStrategy;
62
+ try {
63
+ const prepared = await this.getManifestAndStrategy(
64
+ options.projectPath,
65
+ );
66
+ manifest = prepared.manifest;
67
+ strategy = prepared.strategy;
68
+ } catch (error) {
69
+ return this.handleError(error, "Validation");
70
+ }
71
+
72
+ // Delegate variant-specific validation (may warn or throw)
73
+ try {
74
+ if (strategy.validateAsync) {
75
+ await strategy.validateAsync({
76
+ fileSystem: this.fileSystem,
77
+ projectPath: options.projectPath,
78
+ manifest,
79
+ logger: this.createStrategyLogger(),
80
+ });
81
+ }
82
+ } catch (error) {
83
+ return this.handleError(error, "Validation");
84
+ }
85
+
86
+ this.logger.info("WebApp project validation completed");
87
+ return ToolResult.success();
88
+ } catch (error) {
89
+ return this.handleError(error, "Validation");
90
+ }
91
+ }
92
+
93
+ override async buildAsync(
94
+ _options: IProjectBuildOptions,
95
+ _cancellationToken?: AbortSignal,
96
+ ): Promise<ToolResult> {
97
+ // For v1, build is lightweight - just prepare metadata structure
98
+ // Actual build happens via isCompiled=false (future) or external build
99
+ this.logger.info(
100
+ "Build operation for WebApp is lightweight (metadata preparation only)",
101
+ );
102
+ return ToolResult.success();
103
+ }
104
+
105
+ override async packAsync(
106
+ options: IProjectPackOptions,
107
+ _cancellationToken?: AbortSignal,
108
+ ): Promise<ToolResult> {
109
+ // Input validation
110
+ if (!options.projectPath) {
111
+ return this.handleError(
112
+ new Error(ERROR_MESSAGES.PROJECT_PATH_REQUIRED),
113
+ "Packing",
114
+ );
115
+ }
116
+ if (!options.outputPath) {
117
+ return this.handleError(
118
+ new Error(ERROR_MESSAGES.OUTPUT_PATH_REQUIRED),
119
+ "Packing",
120
+ );
121
+ }
122
+ if (!options.package?.id) {
123
+ return this.handleError(
124
+ new Error(ERROR_MESSAGES.PACKAGE_NAME_REQUIRED),
125
+ "Packing",
126
+ );
127
+ }
128
+ if (!options.package?.version) {
129
+ return this.handleError(
130
+ new Error(ERROR_MESSAGES.PACKAGE_VERSION_REQUIRED),
131
+ "Packing",
132
+ );
133
+ }
134
+
135
+ try {
136
+ this.logger.info(
137
+ `Packing WebApp project: ${options.package.id}@${options.package.version}`,
138
+ );
139
+
140
+ // Prepare manifest and strategy (common logic factored out)
141
+ let manifest: WebAppManifest | undefined;
142
+ let strategy: VariantStrategy;
143
+ try {
144
+ const prepared = await this.getManifestAndStrategy(
145
+ options.projectPath,
146
+ );
147
+ manifest = prepared.manifest;
148
+ strategy = prepared.strategy;
149
+ } catch (error) {
150
+ return this.handleError(
151
+ error,
152
+ `Packing WebApp project '${options.package.id}'`,
153
+ );
154
+ }
155
+
156
+ // Package using strategy
157
+ const nupkgPath = await strategy.packageAsync({
158
+ fileSystem: this.fileSystem,
159
+ projectPath: options.projectPath,
160
+ manifest,
161
+ outputPath: options.outputPath,
162
+ packageInfo: options.package,
163
+ logger: this.createStrategyLogger(),
164
+ });
165
+
166
+ this.logger.info(
167
+ `WebApp package created successfully: ${nupkgPath}`,
168
+ );
169
+ return new ToolResult(ToolErrorCodes.Success, "done", [nupkgPath]);
170
+ } catch (error) {
171
+ return this.handleError(
172
+ error,
173
+ `Packing WebApp project '${options.package.id}'`,
174
+ );
175
+ }
176
+ }
177
+
178
+ override async dispose(): Promise<void> {
179
+ this.logger.info("Disposing WebApp Tool");
180
+ try {
181
+ if (this._tempBuildFolder) {
182
+ await this.fileSystem.rm(this._tempBuildFolder);
183
+ this._tempBuildFolder = null;
184
+ }
185
+ await this._temporaryStorage.cleanup();
186
+ } catch {
187
+ // Ignore errors during cleanup
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Common helper that validates manifest file presence, loads manifest
193
+ * and instantiates a variant strategy. Throws Error with user-friendly
194
+ * message when any step fails.
195
+ */
196
+ private async getManifestAndStrategy(
197
+ projectPath: string,
198
+ ): Promise<{ manifest: WebAppManifest; strategy: VariantStrategy }> {
199
+ // Check manifest existence
200
+ const manifestPath = Path.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
201
+ const manifestExists = await this.fileSystem.exists(manifestPath);
202
+
203
+ if (!manifestExists) {
204
+ throw new Error(
205
+ ERROR_MESSAGES.MANIFEST_NOT_FOUND(WEBAPP_MANIFEST_FILE_NAME),
206
+ );
207
+ }
208
+
209
+ // Ensure this is a WebApp project: project.json must NOT exist
210
+ const projectJsonPath = Path.join(projectPath, PROJECT_JSON_FILE);
211
+ if (await this.fileSystem.exists(projectJsonPath)) {
212
+ throw new Error(ERROR_MESSAGES.PROJECT_JSON_FOUND);
213
+ }
214
+
215
+ // Load manifest and ensure it's valid
216
+ const manifest = await loadWebAppManifest(this.fileSystem, projectPath);
217
+ if (!manifest) {
218
+ throw new Error(
219
+ ERROR_MESSAGES.MANIFEST_LOAD_FAILED(WEBAPP_MANIFEST_FILE_NAME),
220
+ );
221
+ }
222
+
223
+ // Create strategy (will throw if unsupported)
224
+ const strategy = VariantStrategyFactory.createStrategy(
225
+ manifest,
226
+ this.fileSystem,
227
+ );
228
+
229
+ return { manifest, strategy };
230
+ }
231
+
232
+ /**
233
+ * Standardized error handling helper
234
+ */
235
+ private handleError(error: unknown, context: string): ToolResult {
236
+ const errorMessage =
237
+ error instanceof Error ? error.message : String(error);
238
+ this.logger.error(`${context}: ${errorMessage}`);
239
+
240
+ // Use appropriate error message based on context
241
+ let userMessage: string;
242
+ if (context === "Validation") {
243
+ userMessage = ERROR_MESSAGES.VALIDATION_FAILED(errorMessage);
244
+ } else if (context.startsWith("Packing")) {
245
+ userMessage = ERROR_MESSAGES.PACKING_FAILED(errorMessage);
246
+ } else {
247
+ userMessage = errorMessage;
248
+ }
249
+
250
+ return ToolResult.error(ToolErrorCodes.InternalError, userMessage);
251
+ }
252
+
253
+ /**
254
+ * Create a logger wrapper for strategy methods
255
+ */
256
+ private createStrategyLogger(): {
257
+ info: (message: string) => void;
258
+ error: (message: string) => void;
259
+ progress: (message: string) => void;
260
+ warn: (message: string) => void;
261
+ } {
262
+ return {
263
+ info: (msg: string) => this.logger.info(msg),
264
+ error: (msg: string) => this.logger.error(msg),
265
+ progress: (msg: string) => this.logger.progress(msg),
266
+ warn: (msg: string) => this.logger.warn(msg),
267
+ };
268
+ }
269
+ }