@uipath/packager-tool-webapp 1.0.5 → 1.197.0-preview.64

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.
@@ -9,10 +9,22 @@ export declare const PROJECT_JSON_FILE = "project.json";
9
9
  * Default bundle path if not specified in manifest
10
10
  */
11
11
  export declare const DEFAULT_BUNDLE_PATH = "source/dist";
12
+ /**
13
+ * Pre-built app folder for the JS variant (source)
14
+ */
15
+ export declare const APP_FOLDER_NAME = ".app";
16
+ /**
17
+ * Destination folder inside the NuGet content folder for the JS variant
18
+ */
19
+ export declare const CONTENT_APP_FOLDER_NAME = "app";
12
20
  /**
13
21
  * Target runtime for operate.json
14
22
  */
15
23
  export declare const TARGET_RUNTIME = "Coded";
24
+ /**
25
+ * Target runtime for operate.json
26
+ */
27
+ export declare const TARGET_JS_RUNTIME = "JS";
16
28
  /**
17
29
  * Default entry point type
18
30
  */
@@ -26,6 +38,8 @@ export declare const ERROR_MESSAGES: {
26
38
  readonly MANIFEST_LOAD_FAILED: (file: string) => string;
27
39
  readonly BUNDLE_NOT_FOUND: (path: string) => string;
28
40
  readonly BUNDLE_NOT_DIRECTORY: (path: string) => string;
41
+ readonly APP_FOLDER_NOT_FOUND: (path: string) => string;
42
+ readonly APP_FOLDER_NOT_DIRECTORY: (path: string) => string;
29
43
  readonly BUILD_NOT_SUPPORTED: "Build execution (isCompiled=false) is not yet supported. Please build the project manually and set isCompiled=true.";
30
44
  readonly VALIDATION_FAILED: (context: string) => string;
31
45
  readonly PACKING_FAILED: (context: string) => string;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export type { WebAppManifest } from "./models/webapp-manifest.js";
2
+ export { WEBAPP_MANIFEST_FILE_NAME, WebAppVariantType, } from "./models/webapp-manifest.js";
1
3
  export { WebAppTool } from "./webapp-tool.js";
2
4
  export { WebAppToolFactory } from "./webapp-tool-factory.js";
package/dist/index.js CHANGED
@@ -1,14 +1,13 @@
1
- // src/index.ts
2
- import { toolsFactoryRepository } from "@uipath/solutionpackager-tool-core";
3
-
4
- // src/webapp-tool-factory.ts
5
- import {
6
- ProjectTypes as ProjectTypes2
7
- } from "@uipath/solutionpackager-tool-core";
8
-
1
+ // src/models/webapp-manifest.ts
2
+ var WebAppVariantType;
3
+ ((WebAppVariantType2) => {
4
+ WebAppVariantType2["Coded"] = "Coded";
5
+ WebAppVariantType2["JS"] = "JS";
6
+ })(WebAppVariantType ||= {});
7
+ var WEBAPP_MANIFEST_FILE_NAME = "webAppManifest.json";
9
8
  // src/webapp-tool.ts
10
9
  import {
11
- Path as Path4,
10
+ Path as Path5,
12
11
  ProjectTool,
13
12
  TemporaryStorageService,
14
13
  ToolErrorCodes,
@@ -18,7 +17,10 @@ import {
18
17
  // src/constants.ts
19
18
  var PROJECT_JSON_FILE = "project.json";
20
19
  var DEFAULT_BUNDLE_PATH = "source/dist";
20
+ var APP_FOLDER_NAME = ".app";
21
+ var CONTENT_APP_FOLDER_NAME = "app";
21
22
  var TARGET_RUNTIME = "Coded";
23
+ var TARGET_JS_RUNTIME = "JS";
22
24
  var DEFAULT_ENTRY_POINT_TYPE = "api";
23
25
  var ERROR_MESSAGES = {
24
26
  MANIFEST_NOT_FOUND: (file) => `WebApp manifest not found: ${file}. This file is required for WebApp projects.`,
@@ -26,6 +28,8 @@ var ERROR_MESSAGES = {
26
28
  MANIFEST_LOAD_FAILED: (file) => `Failed to load or parse ${file}`,
27
29
  BUNDLE_NOT_FOUND: (path) => `Compiled bundle not found at ${path}. Ensure the project is built before packing.`,
28
30
  BUNDLE_NOT_DIRECTORY: (path) => `Bundle path ${path} exists but is not a directory.`,
31
+ APP_FOLDER_NOT_FOUND: (path) => `.app folder not found at ${path}. Ensure the project contains a .app folder.`,
32
+ APP_FOLDER_NOT_DIRECTORY: (path) => `.app path ${path} exists but is not a directory.`,
29
33
  BUILD_NOT_SUPPORTED: "Build execution (isCompiled=false) is not yet supported. Please build the project manually and set isCompiled=true.",
30
34
  VALIDATION_FAILED: (context) => `Validation failed: ${context}`,
31
35
  PACKING_FAILED: (context) => `An error occurred while packing WebApp project: ${context}`,
@@ -35,19 +39,11 @@ var ERROR_MESSAGES = {
35
39
  OUTPUT_PATH_REQUIRED: "Output path is required"
36
40
  };
37
41
 
38
- // src/models/webapp-manifest.ts
39
- var WebAppVariantType;
40
- ((WebAppVariantType2) => {
41
- WebAppVariantType2["Coded"] = "Coded";
42
- WebAppVariantType2["JS"] = "JS";
43
- })(WebAppVariantType ||= {});
44
- var WEBAPP_MANIFEST_FILE_NAME = "webAppManifest.json";
45
-
46
42
  // src/strategies/coded-app-strategy.ts
47
43
  import {
48
44
  NugetConstants,
49
45
  NugetPackager,
50
- Path as Path2,
46
+ Path as Path3,
51
47
  ProjectTypes,
52
48
  TargetFramework
53
49
  } from "@uipath/solutionpackager-tool-core";
@@ -72,6 +68,41 @@ async function copyDirectoryAsync(fileSystem, sourcePath, destinationPath) {
72
68
  }
73
69
  }
74
70
 
71
+ // src/utils/manifest-loader.ts
72
+ import { Path as Path2 } from "@uipath/solutionpackager-tool-core";
73
+ async function loadWebAppManifest(fileSystem, projectPath) {
74
+ const manifestPath = Path2.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
75
+ const exists = await fileSystem.exists(manifestPath);
76
+ if (!exists) {
77
+ return null;
78
+ }
79
+ const content = await fileSystem.readFile(manifestPath);
80
+ if (!content) {
81
+ return null;
82
+ }
83
+ try {
84
+ const text = new TextDecoder().decode(content);
85
+ const manifest = JSON.parse(text);
86
+ return manifest;
87
+ } catch (error) {
88
+ throw new Error(`Failed to parse ${WEBAPP_MANIFEST_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ async function ensureWebAppProjectId(fileSystem, projectPath) {
92
+ const manifest = await loadWebAppManifest(fileSystem, projectPath);
93
+ if (manifest && typeof manifest.projectId === "string" && manifest.projectId.length > 0) {
94
+ return manifest.projectId;
95
+ }
96
+ const id = crypto.randomUUID();
97
+ if (manifest) {
98
+ const updated = { ...manifest, projectId: id };
99
+ const manifestPath = Path2.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
100
+ await fileSystem.writeFile(manifestPath, `${JSON.stringify(updated, null, 2)}
101
+ `);
102
+ }
103
+ return id;
104
+ }
105
+
75
106
  // src/strategies/coded-app-strategy.ts
76
107
  class CodedAppStrategy {
77
108
  fileSystem;
@@ -87,7 +118,7 @@ class CodedAppStrategy {
87
118
  }
88
119
  const isCompiled = typed.config?.isCompiled ?? true;
89
120
  if (isCompiled) {
90
- const fullBundlePath = Path2.join(projectPath, bundlePath);
121
+ const fullBundlePath = Path3.join(projectPath, bundlePath);
91
122
  const exists = await this.fileSystem.exists(fullBundlePath);
92
123
  if (!exists) {
93
124
  const message = ERROR_MESSAGES.BUNDLE_NOT_FOUND(fullBundlePath);
@@ -107,7 +138,7 @@ class CodedAppStrategy {
107
138
  bundlePath = DEFAULT_BUNDLE_PATH;
108
139
  }
109
140
  const isCompiled = manifest.config?.isCompiled ?? true;
110
- const fullBundlePath = Path2.join(projectPath, bundlePath);
141
+ const fullBundlePath = Path3.join(projectPath, bundlePath);
111
142
  if (isCompiled) {
112
143
  logger?.progress("Validating compiled bundle...");
113
144
  const bundleExists = await this.fileSystem.exists(fullBundlePath);
@@ -122,8 +153,8 @@ class CodedAppStrategy {
122
153
  logger?.info("Build mode (isCompiled=false) is not yet implemented in v1.");
123
154
  throw new Error(ERROR_MESSAGES.BUILD_NOT_SUPPORTED);
124
155
  }
125
- const localBuildFolder = Path2.join(outputPath, NugetConstants.OutputFolderName);
126
- const contentFolder = Path2.join(localBuildFolder, NugetConstants.ContentFolderName);
156
+ const localBuildFolder = Path3.join(outputPath, NugetConstants.OutputFolderName);
157
+ const contentFolder = Path3.join(localBuildFolder, NugetConstants.ContentFolderName);
127
158
  await this.fileSystem.mkdir(contentFolder);
128
159
  try {
129
160
  logger?.progress("Copying bundle to content folder...");
@@ -132,7 +163,7 @@ class CodedAppStrategy {
132
163
  await this.prepareMetadataFiles(localBuildFolder, contentFolder, packageInfo, manifest, projectPath);
133
164
  logger?.progress("Creating NuGet package...");
134
165
  const nupkgFileName = `${packageInfo.id}.${packageInfo.version}.nupkg`;
135
- const nupkgPath = Path2.join(outputPath, nupkgFileName);
166
+ const nupkgPath = Path3.join(outputPath, nupkgFileName);
136
167
  const packager = new NugetPackager(this.fileSystem);
137
168
  const result = await packager.packAsync(localBuildFolder, packageInfo, nupkgPath);
138
169
  logger?.info(`Package created successfully: ${result.outputPath}`);
@@ -145,23 +176,23 @@ class CodedAppStrategy {
145
176
  }
146
177
  }
147
178
  }
148
- async prepareMetadataFiles(_localBuildFolder, contentFolder, packageInfo, _manifest, projectPath) {
179
+ async prepareMetadataFiles(_localBuildFolder, contentFolder, _packageInfo, _manifest, projectPath) {
149
180
  let mainFile = "index.html";
150
- const indexHtmlPath = Path2.join(contentFolder, "index.html");
181
+ const indexHtmlPath = Path3.join(contentFolder, "index.html");
151
182
  const indexHtmlExists = await this.fileSystem.exists(indexHtmlPath);
152
183
  if (!indexHtmlExists) {
153
184
  const possibleEntries = ["index.html", "main.html", "app.html"];
154
185
  for (const entry of possibleEntries) {
155
- const entryPath = Path2.join(contentFolder, entry);
186
+ const entryPath = Path3.join(contentFolder, entry);
156
187
  if (await this.fileSystem.exists(entryPath)) {
157
188
  mainFile = entry;
158
189
  break;
159
190
  }
160
191
  }
161
192
  }
162
- const operatePath = Path2.join(contentFolder, NugetConstants.OperateFileName);
193
+ const operatePath = Path3.join(contentFolder, NugetConstants.OperateFileName);
163
194
  const operateModel = {
164
- projectId: packageInfo.id,
195
+ projectId: await ensureWebAppProjectId(this.fileSystem, projectPath),
165
196
  main: mainFile,
166
197
  contentType: ProjectTypes.WebApp,
167
198
  targetFramework: TargetFramework.Portable,
@@ -173,25 +204,25 @@ class CodedAppStrategy {
173
204
  };
174
205
  const operateJson = JSON.stringify(operateModel, null, 2);
175
206
  await this.fileSystem.writeFile(operatePath, operateJson);
176
- const manifestSourcePath = Path2.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
177
- const manifestDestPath = Path2.join(contentFolder, WEBAPP_MANIFEST_FILE_NAME);
207
+ const manifestSourcePath = Path3.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
208
+ const manifestDestPath = Path3.join(contentFolder, WEBAPP_MANIFEST_FILE_NAME);
178
209
  const manifestExists = await this.fileSystem.exists(manifestSourcePath);
179
210
  if (manifestExists) {
180
- const manifestContent = await this.fileSystem.readFile(manifestSourcePath);
211
+ const manifestContent = await this.fileSystem.readFile(manifestSourcePath, "utf-8");
181
212
  if (manifestContent) {
182
213
  await this.fileSystem.writeFile(manifestDestPath, manifestContent);
183
214
  }
184
215
  }
185
- const uipathJsonSourcePath = Path2.join(projectPath, "uipath.json");
186
- const uipathJsonDestPath = Path2.join(contentFolder, "uipath.json");
216
+ const uipathJsonSourcePath = Path3.join(projectPath, "uipath.json");
217
+ const uipathJsonDestPath = Path3.join(contentFolder, "uipath.json");
187
218
  const uipathJsonExists = await this.fileSystem.exists(uipathJsonSourcePath);
188
219
  if (uipathJsonExists) {
189
- const uipathJsonContent = await this.fileSystem.readFile(uipathJsonSourcePath);
220
+ const uipathJsonContent = await this.fileSystem.readFile(uipathJsonSourcePath, "utf-8");
190
221
  if (uipathJsonContent) {
191
222
  await this.fileSystem.writeFile(uipathJsonDestPath, uipathJsonContent);
192
223
  }
193
224
  }
194
- const bindingsPath = Path2.join(contentFolder, "bindings.json");
225
+ const bindingsPath = Path3.join(contentFolder, "bindings.json");
195
226
  const bindingsExists = await this.fileSystem.exists(bindingsPath);
196
227
  if (!bindingsExists) {
197
228
  const bindingsJson = JSON.stringify({
@@ -200,23 +231,38 @@ class CodedAppStrategy {
200
231
  }, null, 2);
201
232
  await this.fileSystem.writeFile(bindingsPath, bindingsJson);
202
233
  }
203
- const bindingsV2Path = Path2.join(contentFolder, "bindings_v2.json");
204
- const bindingsV2Exists = await this.fileSystem.exists(bindingsV2Path);
205
- if (!bindingsV2Exists) {
206
- const bindingsV2Json = JSON.stringify({
234
+ const bindingsV2Path = Path3.join(contentFolder, "bindings_v2.json");
235
+ const bindingsCandidatePaths = [
236
+ Path3.join(projectPath, "source", "bindings.json"),
237
+ Path3.join(projectPath, "bindings.json"),
238
+ Path3.join(projectPath, "bindings_v2.json")
239
+ ];
240
+ let bindingsV2Json = null;
241
+ for (const candidate of bindingsCandidatePaths) {
242
+ if (await this.fileSystem.exists(candidate)) {
243
+ bindingsV2Json = await this.fileSystem.readFile(candidate, "utf-8");
244
+ if (bindingsV2Json)
245
+ break;
246
+ }
247
+ }
248
+ if (!bindingsV2Json && await this.fileSystem.exists(bindingsV2Path)) {
249
+ bindingsV2Json = await this.fileSystem.readFile(bindingsV2Path, "utf-8");
250
+ }
251
+ if (!bindingsV2Json) {
252
+ bindingsV2Json = JSON.stringify({
207
253
  version: "2.0",
208
254
  resources: []
209
255
  }, null, 2);
210
- await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
211
256
  }
212
- const entryPointsPath = Path2.join(contentFolder, "entry-points.json");
213
- const projectEntryPointsPath = Path2.join(projectPath, "entry-points.json");
257
+ await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
258
+ const entryPointsPath = Path3.join(contentFolder, "entry-points.json");
259
+ const projectEntryPointsPath = Path3.join(projectPath, "entry-points.json");
214
260
  let entryPointsContent;
215
261
  const projectEntryPointsExists = await this.fileSystem.exists(projectEntryPointsPath);
216
262
  if (projectEntryPointsExists) {
217
- const entryPointsData = await this.fileSystem.readFile(projectEntryPointsPath);
263
+ const entryPointsData = await this.fileSystem.readFile(projectEntryPointsPath, "utf-8");
218
264
  if (entryPointsData) {
219
- entryPointsContent = new TextDecoder().decode(entryPointsData);
265
+ entryPointsContent = entryPointsData;
220
266
  } else {
221
267
  entryPointsContent = this.createDefaultEntryPoints(mainFile);
222
268
  }
@@ -224,12 +270,13 @@ class CodedAppStrategy {
224
270
  entryPointsContent = this.createDefaultEntryPoints(mainFile);
225
271
  }
226
272
  await this.fileSystem.writeFile(entryPointsPath, entryPointsContent);
227
- const packageDescriptorPath = Path2.join(contentFolder, NugetConstants.PackageDescriptorFileName);
273
+ const packageDescriptorPath = Path3.join(contentFolder, NugetConstants.PackageDescriptorFileName);
228
274
  const packageDescriptor = {
275
+ $schema: "https://cloud.uipath.com/draft/2024-12/package-descriptor",
229
276
  files: {
230
- [NugetConstants.OperateFileName]: Path2.join(NugetConstants.ContentFolderName, NugetConstants.OperateFileName),
231
- "entry-points.json": Path2.join(NugetConstants.ContentFolderName, "entry-points.json"),
232
- "bindings.json": Path2.join(NugetConstants.ContentFolderName, "bindings_v2.json")
277
+ [NugetConstants.OperateFileName]: Path3.join(NugetConstants.ContentFolderName, NugetConstants.OperateFileName),
278
+ "entry-points.json": Path3.join(NugetConstants.ContentFolderName, "entry-points.json"),
279
+ "bindings.json": Path3.join(NugetConstants.ContentFolderName, "bindings_v2.json")
233
280
  }
234
281
  };
235
282
  const packageDescriptorJson = JSON.stringify(packageDescriptor, null, 2);
@@ -277,6 +324,194 @@ class CodedAppStrategy {
277
324
  }
278
325
  }
279
326
 
327
+ // src/strategies/js-apps-strategy.ts
328
+ import {
329
+ NugetConstants as NugetConstants2,
330
+ NugetPackager as NugetPackager2,
331
+ Path as Path4,
332
+ ProjectTypes as ProjectTypes2,
333
+ TargetFramework as TargetFramework2
334
+ } from "@uipath/solutionpackager-tool-core";
335
+ class JsAppsStrategy {
336
+ fileSystem;
337
+ constructor(fileSystem) {
338
+ this.fileSystem = fileSystem;
339
+ }
340
+ async validateAsync(args) {
341
+ const { projectPath, logger } = args;
342
+ const appFolder = Path4.join(projectPath, APP_FOLDER_NAME);
343
+ const exists = await this.fileSystem.exists(appFolder);
344
+ if (!exists) {
345
+ const message = ERROR_MESSAGES.APP_FOLDER_NOT_FOUND(appFolder);
346
+ if (logger?.warn) {
347
+ logger.warn(message);
348
+ } else {
349
+ logger?.info(`Warning: ${message}`);
350
+ }
351
+ return;
352
+ }
353
+ const stat = await this.fileSystem.stat(appFolder);
354
+ if (!stat?.isDirectory()) {
355
+ const message = ERROR_MESSAGES.APP_FOLDER_NOT_DIRECTORY(appFolder);
356
+ if (logger?.warn) {
357
+ logger.warn(message);
358
+ } else {
359
+ logger?.info(`Warning: ${message}`);
360
+ }
361
+ }
362
+ }
363
+ async packageAsync(args) {
364
+ const { projectPath, manifest, outputPath, packageInfo, logger } = args;
365
+ logger?.info(`Packaging JS variant: ${packageInfo.id}@${packageInfo.version}`);
366
+ const appFolder = Path4.join(projectPath, APP_FOLDER_NAME);
367
+ logger?.progress("Validating .app folder...");
368
+ const appFolderExists = await this.fileSystem.exists(appFolder);
369
+ if (!appFolderExists) {
370
+ throw new Error(ERROR_MESSAGES.APP_FOLDER_NOT_FOUND(appFolder));
371
+ }
372
+ const appFolderStat = await this.fileSystem.stat(appFolder);
373
+ if (!appFolderStat?.isDirectory()) {
374
+ throw new Error(ERROR_MESSAGES.APP_FOLDER_NOT_DIRECTORY(appFolder));
375
+ }
376
+ const localBuildFolder = Path4.join(outputPath, NugetConstants2.OutputFolderName);
377
+ const contentFolder = Path4.join(localBuildFolder, NugetConstants2.ContentFolderName);
378
+ const contentAppFolder = Path4.join(contentFolder, CONTENT_APP_FOLDER_NAME);
379
+ await this.fileSystem.mkdir(contentFolder);
380
+ try {
381
+ logger?.progress("Copying .app folder into content/app...");
382
+ await copyDirectoryAsync(this.fileSystem, appFolder, contentAppFolder);
383
+ logger?.progress("Preparing metadata files...");
384
+ await this.prepareMetadataFiles(contentFolder, contentAppFolder, packageInfo, manifest, projectPath);
385
+ logger?.progress("Creating NuGet package...");
386
+ const nupkgFileName = `${packageInfo.id}.${packageInfo.version}.nupkg`;
387
+ const nupkgPath = Path4.join(outputPath, nupkgFileName);
388
+ const packager = new NugetPackager2(this.fileSystem);
389
+ const result = await packager.packAsync(localBuildFolder, packageInfo, nupkgPath);
390
+ logger?.info(`Package created successfully: ${result.outputPath}`);
391
+ return result.outputPath;
392
+ } finally {
393
+ try {
394
+ await this.fileSystem.rm(localBuildFolder);
395
+ } catch (cleanupError) {
396
+ logger?.error(`Failed to cleanup build folder: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
397
+ }
398
+ }
399
+ }
400
+ async prepareMetadataFiles(contentFolder, _contentAppFolder, _packageInfo, _manifest, projectPath) {
401
+ const mainFile = "index.html";
402
+ const mainRelativeToContent = `${CONTENT_APP_FOLDER_NAME}/${mainFile}`;
403
+ const operatePath = Path4.join(contentFolder, NugetConstants2.OperateFileName);
404
+ const operateModel = {
405
+ projectId: await ensureWebAppProjectId(this.fileSystem, projectPath),
406
+ main: mainRelativeToContent,
407
+ contentType: ProjectTypes2.WebApp,
408
+ targetFramework: TargetFramework2.Portable,
409
+ targetRuntime: TARGET_JS_RUNTIME,
410
+ runtimeOptions: {
411
+ requiresUserInteraction: false,
412
+ isAttended: false
413
+ }
414
+ };
415
+ const operateJson = JSON.stringify(operateModel, null, 2);
416
+ await this.fileSystem.writeFile(operatePath, operateJson);
417
+ const manifestSourcePath = Path4.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
418
+ const manifestDestPath = Path4.join(contentFolder, WEBAPP_MANIFEST_FILE_NAME);
419
+ const manifestExists = await this.fileSystem.exists(manifestSourcePath);
420
+ if (manifestExists) {
421
+ const manifestContent = await this.fileSystem.readFile(manifestSourcePath);
422
+ if (manifestContent) {
423
+ await this.fileSystem.writeFile(manifestDestPath, manifestContent);
424
+ }
425
+ }
426
+ const uipathJsonSourcePath = Path4.join(projectPath, "uipath.json");
427
+ const uipathJsonDestPath = Path4.join(contentFolder, "uipath.json");
428
+ const uipathJsonExists = await this.fileSystem.exists(uipathJsonSourcePath);
429
+ if (uipathJsonExists) {
430
+ const uipathJsonContent = await this.fileSystem.readFile(uipathJsonSourcePath);
431
+ if (uipathJsonContent) {
432
+ await this.fileSystem.writeFile(uipathJsonDestPath, uipathJsonContent);
433
+ }
434
+ }
435
+ const bindingsPath = Path4.join(contentFolder, "bindings.json");
436
+ const bindingsExists = await this.fileSystem.exists(bindingsPath);
437
+ if (!bindingsExists) {
438
+ const bindingsJson = JSON.stringify({ version: "1.0", resources: [] }, null, 2);
439
+ await this.fileSystem.writeFile(bindingsPath, bindingsJson);
440
+ }
441
+ const bindingsV2Path = Path4.join(contentFolder, "bindings_v2.json");
442
+ const bindingsV2Exists = await this.fileSystem.exists(bindingsV2Path);
443
+ if (!bindingsV2Exists) {
444
+ const bindingsV2Json = JSON.stringify({ version: "2.0", resources: [] }, null, 2);
445
+ await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
446
+ }
447
+ const entryPointsPath = Path4.join(contentFolder, "entry-points.json");
448
+ const projectEntryPointsPath = Path4.join(projectPath, "entry-points.json");
449
+ let entryPointsContent;
450
+ const projectEntryPointsExists = await this.fileSystem.exists(projectEntryPointsPath);
451
+ if (projectEntryPointsExists) {
452
+ const entryPointsData = await this.fileSystem.readFile(projectEntryPointsPath);
453
+ if (entryPointsData) {
454
+ entryPointsContent = new TextDecoder().decode(entryPointsData);
455
+ } else {
456
+ entryPointsContent = this.createDefaultEntryPoints(mainRelativeToContent);
457
+ }
458
+ } else {
459
+ entryPointsContent = this.createDefaultEntryPoints(mainRelativeToContent);
460
+ }
461
+ await this.fileSystem.writeFile(entryPointsPath, entryPointsContent);
462
+ const packageDescriptorPath = Path4.join(contentFolder, NugetConstants2.PackageDescriptorFileName);
463
+ const packageDescriptor = {
464
+ files: {
465
+ [NugetConstants2.OperateFileName]: Path4.join(NugetConstants2.ContentFolderName, NugetConstants2.OperateFileName),
466
+ "entry-points.json": Path4.join(NugetConstants2.ContentFolderName, "entry-points.json"),
467
+ "bindings.json": Path4.join(NugetConstants2.ContentFolderName, "bindings_v2.json")
468
+ }
469
+ };
470
+ const packageDescriptorJson = JSON.stringify(packageDescriptor, null, 2);
471
+ await this.fileSystem.writeFile(packageDescriptorPath, packageDescriptorJson);
472
+ }
473
+ createDefaultEntryPoints(mainFilePath) {
474
+ const uniqueId = this.generateUniqueId();
475
+ const entryPoints = {
476
+ $schema: "https://cloud.uipath.com/draft/2024-12/entry-point",
477
+ $id: "entry-points-doc-001",
478
+ entryPoints: [
479
+ {
480
+ filePath: mainFilePath,
481
+ uniqueId,
482
+ type: DEFAULT_ENTRY_POINT_TYPE,
483
+ input: {
484
+ amount: { type: "integer" },
485
+ id: { type: "string" }
486
+ },
487
+ output: {
488
+ status: { type: "string" }
489
+ }
490
+ }
491
+ ]
492
+ };
493
+ return JSON.stringify(entryPoints, null, 2);
494
+ }
495
+ generateUniqueId() {
496
+ const randomBytes = new Uint8Array(16);
497
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
498
+ crypto.getRandomValues(randomBytes);
499
+ } else {
500
+ throw new Error("crypto.getRandomValues is not available");
501
+ }
502
+ randomBytes[6] = randomBytes[6] & 15 | 64;
503
+ randomBytes[8] = randomBytes[8] & 63 | 128;
504
+ const hex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
505
+ return [
506
+ hex.substring(0, 8),
507
+ hex.substring(8, 12),
508
+ hex.substring(12, 16),
509
+ hex.substring(16, 20),
510
+ hex.substring(20, 32)
511
+ ].join("-");
512
+ }
513
+ }
514
+
280
515
  // src/strategies/variant-strategy-factory.ts
281
516
  class VariantStrategyFactory {
282
517
  static createStrategy(manifest, fileSystem) {
@@ -285,34 +520,13 @@ class VariantStrategyFactory {
285
520
  case "Coded" /* Coded */:
286
521
  return new CodedAppStrategy(fileSystem);
287
522
  case "JS" /* JS */:
288
- throw new Error("JS variant is not yet implemented. Only Coded is supported in v1.");
523
+ return new JsAppsStrategy(fileSystem);
289
524
  default:
290
525
  throw new Error(`Unknown WebApp variant: ${manifest.type}. Supported variants: ${Object.values(WebAppVariantType).join(", ")}`);
291
526
  }
292
527
  }
293
528
  }
294
529
 
295
- // src/utils/manifest-loader.ts
296
- import { Path as Path3 } from "@uipath/solutionpackager-tool-core";
297
- async function loadWebAppManifest(fileSystem, projectPath) {
298
- const manifestPath = Path3.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
299
- const exists = await fileSystem.exists(manifestPath);
300
- if (!exists) {
301
- return null;
302
- }
303
- const content = await fileSystem.readFile(manifestPath);
304
- if (!content) {
305
- return null;
306
- }
307
- try {
308
- const text = new TextDecoder().decode(content);
309
- const manifest = JSON.parse(text);
310
- return manifest;
311
- } catch (error) {
312
- throw new Error(`Failed to parse ${WEBAPP_MANIFEST_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`);
313
- }
314
- }
315
-
316
530
  // src/webapp-tool.ts
317
531
  class WebAppTool extends ProjectTool {
318
532
  _temporaryStorage;
@@ -411,12 +625,12 @@ class WebAppTool extends ProjectTool {
411
625
  } catch {}
412
626
  }
413
627
  async getManifestAndStrategy(projectPath) {
414
- const manifestPath = Path4.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
628
+ const manifestPath = Path5.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
415
629
  const manifestExists = await this.fileSystem.exists(manifestPath);
416
630
  if (!manifestExists) {
417
631
  throw new Error(ERROR_MESSAGES.MANIFEST_NOT_FOUND(WEBAPP_MANIFEST_FILE_NAME));
418
632
  }
419
- const projectJsonPath = Path4.join(projectPath, PROJECT_JSON_FILE);
633
+ const projectJsonPath = Path5.join(projectPath, PROJECT_JSON_FILE);
420
634
  if (await this.fileSystem.exists(projectJsonPath)) {
421
635
  throw new Error(ERROR_MESSAGES.PROJECT_JSON_FOUND);
422
636
  }
@@ -449,18 +663,21 @@ class WebAppTool extends ProjectTool {
449
663
  };
450
664
  }
451
665
  }
452
-
453
666
  // src/webapp-tool-factory.ts
667
+ import {
668
+ ProjectTypes as ProjectTypes3
669
+ } from "@uipath/solutionpackager-tool-core";
454
670
  class WebAppToolFactory {
455
- supportedTypes = [ProjectTypes2.AppV2];
671
+ supportedTypes = [ProjectTypes3.AppV2];
456
672
  async createAsync(logger, fileSystem) {
457
673
  return new WebAppTool(fileSystem, logger);
458
674
  }
459
675
  }
460
-
461
- // src/index.ts
462
- toolsFactoryRepository.registerProjectToolFactory(new WebAppToolFactory);
463
676
  export {
677
+ WebAppVariantType,
464
678
  WebAppToolFactory,
465
- WebAppTool
679
+ WebAppTool,
680
+ WEBAPP_MANIFEST_FILE_NAME
466
681
  };
682
+
683
+ //# debugId=D42E6DA7B2DF063064756E2164756E21
@@ -34,6 +34,7 @@ export type WebAppManifest<T extends WebAppVariantType = WebAppVariantType> = {
34
34
  type: T;
35
35
  solutionResourceSubType: string;
36
36
  config?: ManifestConfigMap[T];
37
+ projectId?: string;
37
38
  };
38
39
  export declare const WEBAPP_MANIFEST_FILE_NAME = "webAppManifest.json";
39
40
  export {};
@@ -0,0 +1,40 @@
1
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
+ import type { WebAppManifest } from "../models/webapp-manifest.js";
3
+ import type { PackageArgs, VariantStrategy } from "./variant-strategy.js";
4
+ /**
5
+ * JS apps strategy for the JS variant.
6
+ * Packages a pre-built `.app` folder whose contents are placed under `content/app`
7
+ * in the resulting .nupkg.
8
+ */
9
+ export declare class JsAppsStrategy implements VariantStrategy {
10
+ private readonly fileSystem;
11
+ constructor(fileSystem: IFileSystem);
12
+ /**
13
+ * Variant-specific validation: warn if the `.app` folder is missing or not a directory.
14
+ * Bundle path from the manifest is intentionally ignored for this variant.
15
+ */
16
+ validateAsync(args: {
17
+ fileSystem: IFileSystem;
18
+ projectPath: string;
19
+ manifest: WebAppManifest;
20
+ logger?: {
21
+ info: (message: string) => void;
22
+ error: (message: string) => void;
23
+ progress: (message: string) => void;
24
+ warn?: (message: string) => void;
25
+ };
26
+ }): Promise<void>;
27
+ packageAsync(args: PackageArgs): Promise<string>;
28
+ /**
29
+ * Prepare metadata files (operate.json, package-descriptor.json, bindings.json, bindings_v2.json, entry-points.json)
30
+ */
31
+ private prepareMetadataFiles;
32
+ /**
33
+ * Create default entry-points.json structure
34
+ */
35
+ private createDefaultEntryPoints;
36
+ /**
37
+ * Generate a cryptographically secure unique ID (UUID v4-like format)
38
+ */
39
+ private generateUniqueId;
40
+ }
@@ -4,3 +4,8 @@ import type { WebAppManifest } from "../models/webapp-manifest.js";
4
4
  * Load and parse webAppManifest.json from project path
5
5
  */
6
6
  export declare function loadWebAppManifest(fileSystem: IFileSystem, projectPath: string): Promise<WebAppManifest | null>;
7
+ /**
8
+ * Resolve a stable projectId for a webapp, persisting it to
9
+ * webAppManifest.json so re-packs are stable.
10
+ */
11
+ export declare function ensureWebAppProjectId(fileSystem: IFileSystem, projectPath: string): Promise<string>;
package/package.json CHANGED
@@ -1,59 +1,31 @@
1
1
  {
2
- "name": "@uipath/packager-tool-webapp",
3
- "version": "1.0.5",
4
- "description": "UiPath WebApp tool implementation",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "source": "./src/index.ts",
9
- "default": "./dist/index.js"
10
- }
11
- },
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/UiPath/cli.git",
15
- "directory": "packages/packager/packager-tool-webapp"
16
- },
17
- "publishConfig": {
18
- "registry": "https://registry.npmjs.org/"
19
- },
20
- "types": "./dist/index.d.ts",
21
- "scripts": {
22
- "build": "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/solutionpackager-tool-core && tsc --emitDeclarationOnly --outDir dist",
23
- "dev": "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/solutionpackager-tool-core --watch",
24
- "test": "vitest run",
25
- "test:browser": "vitest run --config=vitest.browser.config.ts",
26
- "test:coverage": "vitest run --coverage",
27
- "test:browser:coverage": "vitest run --config=vitest.browser.config.ts --coverage",
28
- "test:all": "bun run test && bun run test:browser",
29
- "test:all:coverage": "bun run test:coverage && bun run test:browser:coverage",
30
- "prepack": "bun run build",
31
- "publish:dry": "bun publish --dry-run",
32
- "publish:gh": "bun publish",
33
- "version:patch": "bun version patch --no-git-tag-version",
34
- "version:minor": "bun version minor --no-git-tag-version",
35
- "version:major": "bun version major --no-git-tag-version",
36
- "lint": "biome check ."
37
- },
38
- "files": [
39
- "dist",
40
- "!dist/**/*.map",
41
- "src"
42
- ],
43
- "author": "",
44
- "license": "ISC",
45
- "peerDependencies": {
46
- "@uipath/solutionpackager-tool-core": "0.0.33"
47
- },
48
- "devDependencies": {
49
- "@types/node": "^25.5.0",
50
- "@uipath/solutionpackager-tool-core": "0.0.33",
51
- "@vitest/browser": "^4.0.14",
52
- "@vitest/browser-playwright": "^4.0.14",
53
- "@vitest/coverage-v8": "^4.0.14",
54
- "playwright": "^1.57.0",
55
- "typescript": "^5.9.3",
56
- "vitest": "^4.0.14"
57
- },
58
- "gitHead": "3f1b4d8e9f910be81e4cab956537f21dbd5d63ac"
2
+ "name": "@uipath/packager-tool-webapp",
3
+ "version": "1.197.0-preview.64",
4
+ "description": "UiPath WebApp tool implementation",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "source": "./src/index.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/UiPath/cli.git",
15
+ "directory": "packages/packager/packager-tool-webapp"
16
+ },
17
+ "publishConfig": {
18
+ "registry": "https://registry.npmjs.org/"
19
+ },
20
+ "types": "./dist/index.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "peerDependencies": {
28
+ "@uipath/solutionpackager-tool-core": "1.197.0"
29
+ },
30
+ "gitHead": "3977b977945bf519336258da69fd271433eaaef4"
59
31
  }
package/src/constants.ts CHANGED
@@ -12,11 +12,26 @@ export const PROJECT_JSON_FILE = "project.json";
12
12
  */
13
13
  export const DEFAULT_BUNDLE_PATH = "source/dist";
14
14
 
15
+ /**
16
+ * Pre-built app folder for the JS variant (source)
17
+ */
18
+ export const APP_FOLDER_NAME = ".app";
19
+
20
+ /**
21
+ * Destination folder inside the NuGet content folder for the JS variant
22
+ */
23
+ export const CONTENT_APP_FOLDER_NAME = "app";
24
+
15
25
  /**
16
26
  * Target runtime for operate.json
17
27
  */
18
28
  export const TARGET_RUNTIME = "Coded";
19
29
 
30
+ /**
31
+ * Target runtime for operate.json
32
+ */
33
+ export const TARGET_JS_RUNTIME = "JS";
34
+
20
35
  /**
21
36
  * Default entry point type
22
37
  */
@@ -41,6 +56,12 @@ export const ERROR_MESSAGES = {
41
56
  BUNDLE_NOT_DIRECTORY: (path: string): string =>
42
57
  `Bundle path ${path} exists but is not a directory.`,
43
58
 
59
+ APP_FOLDER_NOT_FOUND: (path: string): string =>
60
+ `.app folder not found at ${path}. Ensure the project contains a .app folder.`,
61
+
62
+ APP_FOLDER_NOT_DIRECTORY: (path: string): string =>
63
+ `.app path ${path} exists but is not a directory.`,
64
+
44
65
  BUILD_NOT_SUPPORTED:
45
66
  "Build execution (isCompiled=false) is not yet supported. Please build the project manually and set isCompiled=true.",
46
67
 
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { toolsFactoryRepository } from "@uipath/solutionpackager-tool-core";
2
- import { WebAppToolFactory } from "./webapp-tool-factory.js";
3
-
1
+ // WebApp tool factory. Consumers register it explicitly via
2
+ // `toolsFactoryRepository.registerProjectToolFactory(new WebAppToolFactory())`.
3
+ export type { WebAppManifest } from "./models/webapp-manifest.js";
4
+ export {
5
+ WEBAPP_MANIFEST_FILE_NAME,
6
+ WebAppVariantType,
7
+ } from "./models/webapp-manifest.js";
4
8
  export { WebAppTool } from "./webapp-tool.js";
5
9
  export { WebAppToolFactory } from "./webapp-tool-factory.js";
6
-
7
- toolsFactoryRepository.registerProjectToolFactory(new WebAppToolFactory());
@@ -40,6 +40,7 @@ export type WebAppManifest<T extends WebAppVariantType = WebAppVariantType> = {
40
40
  type: T;
41
41
  solutionResourceSubType: string;
42
42
  config?: ManifestConfigMap[T];
43
+ projectId?: string;
43
44
  };
44
45
 
45
46
  export const WEBAPP_MANIFEST_FILE_NAME = "webAppManifest.json";
@@ -22,6 +22,7 @@ import {
22
22
  type WebAppVariantType,
23
23
  } from "../models/webapp-manifest.js";
24
24
  import { copyDirectoryAsync } from "../utils/fs-helpers.js";
25
+ import { ensureWebAppProjectId } from "../utils/manifest-loader.js";
25
26
  import type { PackageArgs, VariantStrategy } from "./variant-strategy.js";
26
27
 
27
28
  /**
@@ -179,7 +180,7 @@ export class CodedAppStrategy implements VariantStrategy {
179
180
  private async prepareMetadataFiles(
180
181
  _localBuildFolder: string,
181
182
  contentFolder: string,
182
- packageInfo: {
183
+ _packageInfo: {
183
184
  id: string;
184
185
  version: string;
185
186
  author?: string;
@@ -210,7 +211,10 @@ export class CodedAppStrategy implements VariantStrategy {
210
211
  NugetConstants.OperateFileName,
211
212
  );
212
213
  const operateModel: OperateFileModel & { targetRuntime: string } = {
213
- projectId: packageInfo.id,
214
+ projectId: await ensureWebAppProjectId(
215
+ this.fileSystem,
216
+ projectPath,
217
+ ),
214
218
  main: mainFile,
215
219
  contentType: ProjectTypes.WebApp,
216
220
  targetFramework: TargetFramework.Portable,
@@ -235,8 +239,10 @@ export class CodedAppStrategy implements VariantStrategy {
235
239
  );
236
240
  const manifestExists = await this.fileSystem.exists(manifestSourcePath);
237
241
  if (manifestExists) {
238
- const manifestContent =
239
- await this.fileSystem.readFile(manifestSourcePath);
242
+ const manifestContent = await this.fileSystem.readFile(
243
+ manifestSourcePath,
244
+ "utf-8",
245
+ );
240
246
  if (manifestContent) {
241
247
  await this.fileSystem.writeFile(
242
248
  manifestDestPath,
@@ -251,8 +257,10 @@ export class CodedAppStrategy implements VariantStrategy {
251
257
  const uipathJsonExists =
252
258
  await this.fileSystem.exists(uipathJsonSourcePath);
253
259
  if (uipathJsonExists) {
254
- const uipathJsonContent =
255
- await this.fileSystem.readFile(uipathJsonSourcePath);
260
+ const uipathJsonContent = await this.fileSystem.readFile(
261
+ uipathJsonSourcePath,
262
+ "utf-8",
263
+ );
256
264
  if (uipathJsonContent) {
257
265
  await this.fileSystem.writeFile(
258
266
  uipathJsonDestPath,
@@ -279,10 +287,32 @@ export class CodedAppStrategy implements VariantStrategy {
279
287
 
280
288
  // Create or copy bindings_v2.json (version 2.0)
281
289
  const bindingsV2Path = Path.join(contentFolder, "bindings_v2.json");
282
- const bindingsV2Exists = await this.fileSystem.exists(bindingsV2Path);
290
+ const bindingsCandidatePaths = [
291
+ Path.join(projectPath, "source", "bindings.json"),
292
+ Path.join(projectPath, "bindings.json"),
293
+ Path.join(projectPath, "bindings_v2.json"),
294
+ ];
295
+
296
+ let bindingsV2Json: string | null = null;
297
+ for (const candidate of bindingsCandidatePaths) {
298
+ if (await this.fileSystem.exists(candidate)) {
299
+ bindingsV2Json = await this.fileSystem.readFile(
300
+ candidate,
301
+ "utf-8",
302
+ );
303
+ if (bindingsV2Json) break;
304
+ }
305
+ }
306
+
307
+ if (!bindingsV2Json && (await this.fileSystem.exists(bindingsV2Path))) {
308
+ bindingsV2Json = await this.fileSystem.readFile(
309
+ bindingsV2Path,
310
+ "utf-8",
311
+ );
312
+ }
283
313
 
284
- if (!bindingsV2Exists) {
285
- const bindingsV2Json = JSON.stringify(
314
+ if (!bindingsV2Json) {
315
+ bindingsV2Json = JSON.stringify(
286
316
  {
287
317
  version: "2.0",
288
318
  resources: [],
@@ -290,8 +320,8 @@ export class CodedAppStrategy implements VariantStrategy {
290
320
  null,
291
321
  2,
292
322
  );
293
- await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
294
323
  }
324
+ await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
295
325
 
296
326
  // Create or copy entry-points.json
297
327
  const entryPointsPath = Path.join(contentFolder, "entry-points.json");
@@ -308,9 +338,10 @@ export class CodedAppStrategy implements VariantStrategy {
308
338
  if (projectEntryPointsExists) {
309
339
  const entryPointsData = await this.fileSystem.readFile(
310
340
  projectEntryPointsPath,
341
+ "utf-8",
311
342
  );
312
343
  if (entryPointsData) {
313
- entryPointsContent = new TextDecoder().decode(entryPointsData);
344
+ entryPointsContent = entryPointsData;
314
345
  } else {
315
346
  entryPointsContent = this.createDefaultEntryPoints(mainFile);
316
347
  }
@@ -326,6 +357,8 @@ export class CodedAppStrategy implements VariantStrategy {
326
357
  NugetConstants.PackageDescriptorFileName,
327
358
  );
328
359
  const packageDescriptor: PackageDescriptorFileModel = {
360
+ $schema:
361
+ "https://cloud.uipath.com/draft/2024-12/package-descriptor",
329
362
  files: {
330
363
  [NugetConstants.OperateFileName]: Path.join(
331
364
  NugetConstants.ContentFolderName,
@@ -0,0 +1,378 @@
1
+ import type {
2
+ IFileSystem,
3
+ OperateFileModel,
4
+ PackageDescriptorFileModel,
5
+ } from "@uipath/solutionpackager-tool-core";
6
+ import {
7
+ NugetConstants,
8
+ NugetPackager,
9
+ Path,
10
+ ProjectTypes,
11
+ TargetFramework,
12
+ } from "@uipath/solutionpackager-tool-core";
13
+ import {
14
+ APP_FOLDER_NAME,
15
+ CONTENT_APP_FOLDER_NAME,
16
+ DEFAULT_ENTRY_POINT_TYPE,
17
+ ERROR_MESSAGES,
18
+ TARGET_JS_RUNTIME,
19
+ } from "../constants.js";
20
+ import type { WebAppManifest } from "../models/webapp-manifest.js";
21
+ import { WEBAPP_MANIFEST_FILE_NAME } from "../models/webapp-manifest.js";
22
+ import { copyDirectoryAsync } from "../utils/fs-helpers.js";
23
+ import { ensureWebAppProjectId } from "../utils/manifest-loader.js";
24
+ import type { PackageArgs, VariantStrategy } from "./variant-strategy.js";
25
+
26
+ /**
27
+ * JS apps strategy for the JS variant.
28
+ * Packages a pre-built `.app` folder whose contents are placed under `content/app`
29
+ * in the resulting .nupkg.
30
+ */
31
+ export class JsAppsStrategy implements VariantStrategy {
32
+ constructor(private readonly fileSystem: IFileSystem) {}
33
+
34
+ /**
35
+ * Variant-specific validation: warn if the `.app` folder is missing or not a directory.
36
+ * Bundle path from the manifest is intentionally ignored for this variant.
37
+ */
38
+ async validateAsync(args: {
39
+ fileSystem: IFileSystem;
40
+ projectPath: string;
41
+ manifest: WebAppManifest;
42
+ logger?: {
43
+ info: (message: string) => void;
44
+ error: (message: string) => void;
45
+ progress: (message: string) => void;
46
+ warn?: (message: string) => void;
47
+ };
48
+ }): Promise<void> {
49
+ const { projectPath, logger } = args;
50
+ const appFolder = Path.join(projectPath, APP_FOLDER_NAME);
51
+
52
+ const exists = await this.fileSystem.exists(appFolder);
53
+ if (!exists) {
54
+ const message = ERROR_MESSAGES.APP_FOLDER_NOT_FOUND(appFolder);
55
+ if (logger?.warn) {
56
+ logger.warn(message);
57
+ } else {
58
+ logger?.info(`Warning: ${message}`);
59
+ }
60
+ return;
61
+ }
62
+
63
+ const stat = await this.fileSystem.stat(appFolder);
64
+ if (!stat?.isDirectory()) {
65
+ const message = ERROR_MESSAGES.APP_FOLDER_NOT_DIRECTORY(appFolder);
66
+ if (logger?.warn) {
67
+ logger.warn(message);
68
+ } else {
69
+ logger?.info(`Warning: ${message}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ async packageAsync(args: PackageArgs): Promise<string> {
75
+ const { projectPath, manifest, outputPath, packageInfo, logger } = args;
76
+
77
+ logger?.info(
78
+ `Packaging JS variant: ${packageInfo.id}@${packageInfo.version}`,
79
+ );
80
+
81
+ const appFolder = Path.join(projectPath, APP_FOLDER_NAME);
82
+
83
+ logger?.progress("Validating .app folder...");
84
+ const appFolderExists = await this.fileSystem.exists(appFolder);
85
+ if (!appFolderExists) {
86
+ throw new Error(ERROR_MESSAGES.APP_FOLDER_NOT_FOUND(appFolder));
87
+ }
88
+
89
+ const appFolderStat = await this.fileSystem.stat(appFolder);
90
+ if (!appFolderStat?.isDirectory()) {
91
+ throw new Error(ERROR_MESSAGES.APP_FOLDER_NOT_DIRECTORY(appFolder));
92
+ }
93
+
94
+ // Bundle directory structure: <output>/bundle/content/app
95
+ const localBuildFolder = Path.join(
96
+ outputPath,
97
+ NugetConstants.OutputFolderName,
98
+ );
99
+ const contentFolder = Path.join(
100
+ localBuildFolder,
101
+ NugetConstants.ContentFolderName,
102
+ );
103
+ const contentAppFolder = Path.join(
104
+ contentFolder,
105
+ CONTENT_APP_FOLDER_NAME,
106
+ );
107
+ await this.fileSystem.mkdir(contentFolder);
108
+
109
+ try {
110
+ // Copy contents of .app into content/app
111
+ logger?.progress("Copying .app folder into content/app...");
112
+ await copyDirectoryAsync(
113
+ this.fileSystem,
114
+ appFolder,
115
+ contentAppFolder,
116
+ );
117
+
118
+ logger?.progress("Preparing metadata files...");
119
+ await this.prepareMetadataFiles(
120
+ contentFolder,
121
+ contentAppFolder,
122
+ packageInfo,
123
+ manifest,
124
+ projectPath,
125
+ );
126
+
127
+ logger?.progress("Creating NuGet package...");
128
+ const nupkgFileName = `${packageInfo.id}.${packageInfo.version}.nupkg`;
129
+ const nupkgPath = Path.join(outputPath, nupkgFileName);
130
+
131
+ const packager = new NugetPackager(this.fileSystem);
132
+ const result = await packager.packAsync(
133
+ localBuildFolder,
134
+ packageInfo,
135
+ nupkgPath,
136
+ );
137
+
138
+ logger?.info(`Package created successfully: ${result.outputPath}`);
139
+ return result.outputPath;
140
+ } finally {
141
+ try {
142
+ await this.fileSystem.rm(localBuildFolder);
143
+ } catch (cleanupError) {
144
+ logger?.error(
145
+ `Failed to cleanup build folder: ${
146
+ cleanupError instanceof Error
147
+ ? cleanupError.message
148
+ : String(cleanupError)
149
+ }`,
150
+ );
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Prepare metadata files (operate.json, package-descriptor.json, bindings.json, bindings_v2.json, entry-points.json)
157
+ */
158
+ private async prepareMetadataFiles(
159
+ contentFolder: string,
160
+ _contentAppFolder: string,
161
+ _packageInfo: {
162
+ id: string;
163
+ version: string;
164
+ author?: string;
165
+ description?: string;
166
+ },
167
+ _manifest: WebAppManifest,
168
+ projectPath: string,
169
+ ): Promise<void> {
170
+ const mainFile = "index.html";
171
+
172
+ // Path referenced from within the package content folder
173
+ const mainRelativeToContent = `${CONTENT_APP_FOLDER_NAME}/${mainFile}`;
174
+
175
+ // operate.json in content folder
176
+ const operatePath = Path.join(
177
+ contentFolder,
178
+ NugetConstants.OperateFileName,
179
+ );
180
+ const operateModel: OperateFileModel & { targetRuntime: string } = {
181
+ projectId: await ensureWebAppProjectId(
182
+ this.fileSystem,
183
+ projectPath,
184
+ ),
185
+ main: mainRelativeToContent,
186
+ contentType: ProjectTypes.WebApp,
187
+ targetFramework: TargetFramework.Portable,
188
+ targetRuntime: TARGET_JS_RUNTIME,
189
+ runtimeOptions: {
190
+ requiresUserInteraction: false,
191
+ isAttended: false,
192
+ },
193
+ };
194
+
195
+ const operateJson = JSON.stringify(operateModel, null, 2);
196
+ await this.fileSystem.writeFile(operatePath, operateJson);
197
+
198
+ // Copy webAppManifest.json from project root into content (as-is)
199
+ const manifestSourcePath = Path.join(
200
+ projectPath,
201
+ WEBAPP_MANIFEST_FILE_NAME,
202
+ );
203
+ const manifestDestPath = Path.join(
204
+ contentFolder,
205
+ WEBAPP_MANIFEST_FILE_NAME,
206
+ );
207
+ const manifestExists = await this.fileSystem.exists(manifestSourcePath);
208
+ if (manifestExists) {
209
+ const manifestContent =
210
+ await this.fileSystem.readFile(manifestSourcePath);
211
+ if (manifestContent) {
212
+ await this.fileSystem.writeFile(
213
+ manifestDestPath,
214
+ manifestContent,
215
+ );
216
+ }
217
+ }
218
+
219
+ // Copy uipath.json from project root into content (if it exists)
220
+ const uipathJsonSourcePath = Path.join(projectPath, "uipath.json");
221
+ const uipathJsonDestPath = Path.join(contentFolder, "uipath.json");
222
+ const uipathJsonExists =
223
+ await this.fileSystem.exists(uipathJsonSourcePath);
224
+ if (uipathJsonExists) {
225
+ const uipathJsonContent =
226
+ await this.fileSystem.readFile(uipathJsonSourcePath);
227
+ if (uipathJsonContent) {
228
+ await this.fileSystem.writeFile(
229
+ uipathJsonDestPath,
230
+ uipathJsonContent,
231
+ );
232
+ }
233
+ }
234
+
235
+ // Create or copy bindings.json (version 1.0)
236
+ const bindingsPath = Path.join(contentFolder, "bindings.json");
237
+ const bindingsExists = await this.fileSystem.exists(bindingsPath);
238
+ if (!bindingsExists) {
239
+ const bindingsJson = JSON.stringify(
240
+ { version: "1.0", resources: [] },
241
+ null,
242
+ 2,
243
+ );
244
+ await this.fileSystem.writeFile(bindingsPath, bindingsJson);
245
+ }
246
+
247
+ // Create or copy bindings_v2.json (version 2.0)
248
+ const bindingsV2Path = Path.join(contentFolder, "bindings_v2.json");
249
+ const bindingsV2Exists = await this.fileSystem.exists(bindingsV2Path);
250
+ if (!bindingsV2Exists) {
251
+ const bindingsV2Json = JSON.stringify(
252
+ { version: "2.0", resources: [] },
253
+ null,
254
+ 2,
255
+ );
256
+ await this.fileSystem.writeFile(bindingsV2Path, bindingsV2Json);
257
+ }
258
+
259
+ // Create or copy entry-points.json
260
+ const entryPointsPath = Path.join(contentFolder, "entry-points.json");
261
+ const projectEntryPointsPath = Path.join(
262
+ projectPath,
263
+ "entry-points.json",
264
+ );
265
+
266
+ let entryPointsContent: string;
267
+ const projectEntryPointsExists = await this.fileSystem.exists(
268
+ projectEntryPointsPath,
269
+ );
270
+ if (projectEntryPointsExists) {
271
+ const entryPointsData = await this.fileSystem.readFile(
272
+ projectEntryPointsPath,
273
+ );
274
+ if (entryPointsData) {
275
+ entryPointsContent = new TextDecoder().decode(entryPointsData);
276
+ } else {
277
+ entryPointsContent = this.createDefaultEntryPoints(
278
+ mainRelativeToContent,
279
+ );
280
+ }
281
+ } else {
282
+ entryPointsContent = this.createDefaultEntryPoints(
283
+ mainRelativeToContent,
284
+ );
285
+ }
286
+
287
+ await this.fileSystem.writeFile(entryPointsPath, entryPointsContent);
288
+
289
+ // Create package-descriptor.json in content folder
290
+ const packageDescriptorPath = Path.join(
291
+ contentFolder,
292
+ NugetConstants.PackageDescriptorFileName,
293
+ );
294
+ const packageDescriptor: PackageDescriptorFileModel = {
295
+ files: {
296
+ [NugetConstants.OperateFileName]: Path.join(
297
+ NugetConstants.ContentFolderName,
298
+ NugetConstants.OperateFileName,
299
+ ),
300
+ "entry-points.json": Path.join(
301
+ NugetConstants.ContentFolderName,
302
+ "entry-points.json",
303
+ ),
304
+ "bindings.json": Path.join(
305
+ NugetConstants.ContentFolderName,
306
+ "bindings_v2.json",
307
+ ),
308
+ },
309
+ };
310
+
311
+ const packageDescriptorJson = JSON.stringify(
312
+ packageDescriptor,
313
+ null,
314
+ 2,
315
+ );
316
+ await this.fileSystem.writeFile(
317
+ packageDescriptorPath,
318
+ packageDescriptorJson,
319
+ );
320
+ }
321
+
322
+ /**
323
+ * Create default entry-points.json structure
324
+ */
325
+ private createDefaultEntryPoints(mainFilePath: string): string {
326
+ const uniqueId = this.generateUniqueId();
327
+
328
+ const entryPoints = {
329
+ $schema: "https://cloud.uipath.com/draft/2024-12/entry-point",
330
+ $id: "entry-points-doc-001",
331
+ entryPoints: [
332
+ {
333
+ filePath: mainFilePath,
334
+ uniqueId: uniqueId,
335
+ type: DEFAULT_ENTRY_POINT_TYPE,
336
+ input: {
337
+ amount: { type: "integer" },
338
+ id: { type: "string" },
339
+ },
340
+ output: {
341
+ status: { type: "string" },
342
+ },
343
+ },
344
+ ],
345
+ };
346
+
347
+ return JSON.stringify(entryPoints, null, 2);
348
+ }
349
+
350
+ /**
351
+ * Generate a cryptographically secure unique ID (UUID v4-like format)
352
+ */
353
+ private generateUniqueId(): string {
354
+ const randomBytes = new Uint8Array(16);
355
+
356
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
357
+ crypto.getRandomValues(randomBytes);
358
+ } else {
359
+ throw new Error("crypto.getRandomValues is not available");
360
+ }
361
+
362
+ // Set version (4) and variant bits according to UUID v4 spec
363
+ randomBytes[6] = (randomBytes[6] & 0x0f) | 0x40;
364
+ randomBytes[8] = (randomBytes[8] & 0x3f) | 0x80;
365
+
366
+ const hex = Array.from(randomBytes)
367
+ .map((b) => b.toString(16).padStart(2, "0"))
368
+ .join("");
369
+
370
+ return [
371
+ hex.substring(0, 8),
372
+ hex.substring(8, 12),
373
+ hex.substring(12, 16),
374
+ hex.substring(16, 20),
375
+ hex.substring(20, 32),
376
+ ].join("-");
377
+ }
378
+ }
@@ -2,6 +2,7 @@ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
2
2
  import type { WebAppManifest } from "../models/webapp-manifest.js";
3
3
  import { WebAppVariantType } from "../models/webapp-manifest.js";
4
4
  import { CodedAppStrategy } from "./coded-app-strategy.js";
5
+ import { JsAppsStrategy } from "./js-apps-strategy.js";
5
6
  import type { VariantStrategy } from "./variant-strategy.js";
6
7
 
7
8
  /**
@@ -26,9 +27,7 @@ export class VariantStrategyFactory {
26
27
  return new CodedAppStrategy(fileSystem);
27
28
 
28
29
  case WebAppVariantType.JS:
29
- throw new Error(
30
- "JS variant is not yet implemented. Only Coded is supported in v1.",
31
- );
30
+ return new JsAppsStrategy(fileSystem);
32
31
 
33
32
  default:
34
33
  throw new Error(
@@ -32,3 +32,32 @@ export async function loadWebAppManifest(
32
32
  );
33
33
  }
34
34
  }
35
+
36
+ /**
37
+ * Resolve a stable projectId for a webapp, persisting it to
38
+ * webAppManifest.json so re-packs are stable.
39
+ */
40
+ export async function ensureWebAppProjectId(
41
+ fileSystem: IFileSystem,
42
+ projectPath: string,
43
+ ): Promise<string> {
44
+ const manifest = await loadWebAppManifest(fileSystem, projectPath);
45
+ if (
46
+ manifest &&
47
+ typeof manifest.projectId === "string" &&
48
+ manifest.projectId.length > 0
49
+ ) {
50
+ return manifest.projectId;
51
+ }
52
+
53
+ const id = crypto.randomUUID();
54
+ if (manifest) {
55
+ const updated: WebAppManifest = { ...manifest, projectId: id };
56
+ const manifestPath = Path.join(projectPath, WEBAPP_MANIFEST_FILE_NAME);
57
+ await fileSystem.writeFile(
58
+ manifestPath,
59
+ `${JSON.stringify(updated, null, 2)}\n`,
60
+ );
61
+ }
62
+ return id;
63
+ }