@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.
- package/dist/constants.d.ts +36 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +466 -0
- package/dist/models/webapp-manifest.d.ts +39 -0
- package/dist/strategies/coded-app-strategy.d.ts +37 -0
- package/dist/strategies/variant-strategy-factory.d.ts +16 -0
- package/dist/strategies/variant-strategy.d.ts +57 -0
- package/dist/utils/fs-helpers.d.ts +9 -0
- package/dist/utils/manifest-loader.d.ts +6 -0
- package/dist/webapp-tool-factory.d.ts +8 -0
- package/dist/webapp-tool.d.ts +29 -0
- package/package.json +58 -0
- package/src/constants.ts +60 -0
- package/src/index.ts +7 -0
- package/src/models/webapp-manifest.ts +45 -0
- package/src/strategies/coded-app-strategy.ts +413 -0
- package/src/strategies/variant-strategy-factory.ts +39 -0
- package/src/strategies/variant-strategy.ts +67 -0
- package/src/utils/fs-helpers.ts +58 -0
- package/src/utils/manifest-loader.ts +34 -0
- package/src/webapp-tool-factory.ts +23 -0
- package/src/webapp-tool.ts +269 -0
|
@@ -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
|
+
}
|