@uipath/packager-tool-webapp 1.0.4 → 1.196.0

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