@uipath/tasks-tool 0.9.0 → 1.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/tasks-tool",
3
- "version": "0.9.0",
3
+ "version": "1.0.0",
4
4
  "description": "Manage Action Center tasks.",
5
5
  "type": "module",
6
6
  "main": "./dist/tool.js",
@@ -18,15 +18,15 @@
18
18
  "lint:fix": "biome check --write ."
19
19
  },
20
20
  "devDependencies": {
21
- "@types/node": "^25.5.0",
22
- "@uipath/auth": "0.9.0",
23
- "@uipath/common": "0.9.0",
24
- "@uipath/uipath-typescript": "^1.3.1",
21
+ "@types/node": "^25.5.2",
22
+ "@uipath/auth": "1.0.0",
23
+ "@uipath/common": "1.0.0",
24
+ "@uipath/uipath-typescript": "^1.3.7",
25
25
  "commander": "^14.0.3",
26
- "typescript": "^5"
26
+ "typescript": "^6.0.2"
27
27
  },
28
28
  "publishConfig": {
29
29
  "registry": "https://registry.npmjs.org/"
30
30
  },
31
- "gitHead": "3f1b4d8e9f910be81e4cab956537f21dbd5d63ac"
31
+ "gitHead": "d982977e86057fd9d0846c955595c64865aeecab"
32
32
  }
@@ -36,6 +36,7 @@ function mockSdk(overrides: Record<string, unknown> = {}) {
36
36
  reassign: vi.fn().mockResolvedValue({ success: true, data: [] }),
37
37
  unassign: vi.fn().mockResolvedValue({ success: true, data: [] }),
38
38
  complete: vi.fn().mockResolvedValue({ success: true }),
39
+ getUsers: vi.fn().mockResolvedValue([]),
39
40
  ...overrides,
40
41
  },
41
42
  };
@@ -53,6 +54,7 @@ describe("tasks command registration", () => {
53
54
  expect(subcommands).toContain("reassign");
54
55
  expect(subcommands).toContain("unassign");
55
56
  expect(subcommands).toContain("complete");
57
+ expect(subcommands).toContain("users");
56
58
  });
57
59
  });
58
60
 
@@ -690,3 +692,173 @@ describe("tasks complete", () => {
690
692
  expect(process.exitCode).toBe(1);
691
693
  });
692
694
  });
695
+
696
+ describe("tasks users", () => {
697
+ beforeEach(() => {
698
+ vi.resetAllMocks();
699
+ process.exitCode = undefined;
700
+ });
701
+
702
+ it("should list users for a folder", async () => {
703
+ const users = [
704
+ { id: 1, name: "Alice", emailAddress: "alice@company.com" },
705
+ { id: 2, name: "Bob", emailAddress: "bob@company.com" },
706
+ ];
707
+ mockSdk({ getUsers: vi.fn().mockResolvedValue(users) });
708
+
709
+ const program = buildProgram();
710
+ await program.parseAsync(["node", "test", "users", "42"]);
711
+
712
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
713
+ expect.objectContaining({
714
+ Result: "Success",
715
+ Code: "TaskUserList",
716
+ Data: users,
717
+ }),
718
+ );
719
+ });
720
+
721
+ it("should pass folder-id and pageSize to getUsers", async () => {
722
+ const sdk = mockSdk();
723
+
724
+ const program = buildProgram();
725
+ await program.parseAsync(["node", "test", "users", "42"]);
726
+
727
+ expect(sdk.tasks.getUsers).toHaveBeenCalledWith(
728
+ 42,
729
+ expect.objectContaining({ pageSize: 100 }),
730
+ );
731
+ });
732
+
733
+ it("should pass tenant option", async () => {
734
+ mockSdk();
735
+
736
+ const program = buildProgram();
737
+ await program.parseAsync([
738
+ "node",
739
+ "test",
740
+ "users",
741
+ "42",
742
+ "-t",
743
+ "MyTenant",
744
+ ]);
745
+
746
+ expect(createTasksClient).toHaveBeenCalledWith("MyTenant");
747
+ });
748
+
749
+ it("should reject invalid folder-id", async () => {
750
+ mockSdk();
751
+
752
+ const program = buildProgram();
753
+ await program.parseAsync(["node", "test", "users", "abc"]);
754
+
755
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
756
+ expect.objectContaining({
757
+ Result: "Failure",
758
+ Message: expect.stringContaining("<folder-id>"),
759
+ }),
760
+ );
761
+ expect(process.exitCode).toBe(1);
762
+ });
763
+
764
+ it("should paginate through multiple pages", async () => {
765
+ const page1 = {
766
+ items: [{ id: 1, name: "Alice" }],
767
+ hasNextPage: true,
768
+ nextCursor: { value: "cursor-2" },
769
+ };
770
+ const page2 = {
771
+ items: [{ id: 2, name: "Bob" }],
772
+ hasNextPage: false,
773
+ };
774
+ const getUsersMock = vi
775
+ .fn()
776
+ .mockResolvedValueOnce(page1)
777
+ .mockResolvedValueOnce(page2);
778
+ mockSdk({ getUsers: getUsersMock });
779
+
780
+ const program = buildProgram();
781
+ await program.parseAsync(["node", "test", "users", "42"]);
782
+
783
+ expect(getUsersMock).toHaveBeenCalledTimes(2);
784
+ expect(getUsersMock).toHaveBeenLastCalledWith(
785
+ 42,
786
+ expect.objectContaining({ cursor: { value: "cursor-2" } }),
787
+ );
788
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
789
+ expect.objectContaining({
790
+ Code: "TaskUserList",
791
+ Data: [
792
+ { id: 1, name: "Alice" },
793
+ { id: 2, name: "Bob" },
794
+ ],
795
+ }),
796
+ );
797
+ });
798
+
799
+ it("should respect --limit option", async () => {
800
+ const page1 = {
801
+ items: [
802
+ { id: 1, name: "Alice" },
803
+ { id: 2, name: "Bob" },
804
+ { id: 3, name: "Carol" },
805
+ ],
806
+ hasNextPage: true,
807
+ nextCursor: { value: "cursor-2" },
808
+ };
809
+ mockSdk({ getUsers: vi.fn().mockResolvedValue(page1) });
810
+
811
+ const program = buildProgram();
812
+ await program.parseAsync([
813
+ "node",
814
+ "test",
815
+ "users",
816
+ "42",
817
+ "--limit",
818
+ "2",
819
+ ]);
820
+
821
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
822
+ expect.objectContaining({
823
+ Data: [
824
+ { id: 1, name: "Alice" },
825
+ { id: 2, name: "Bob" },
826
+ ],
827
+ }),
828
+ );
829
+ });
830
+
831
+ it("should handle getUsers API error", async () => {
832
+ mockSdk({
833
+ getUsers: vi.fn().mockRejectedValue(new Error("Forbidden")),
834
+ });
835
+
836
+ const program = buildProgram();
837
+ await program.parseAsync(["node", "test", "users", "42"]);
838
+
839
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
840
+ expect.objectContaining({
841
+ Result: "Failure",
842
+ Message: "Error listing task users",
843
+ }),
844
+ );
845
+ expect(process.exitCode).toBe(1);
846
+ });
847
+
848
+ it("should handle client connection error", async () => {
849
+ vi.mocked(createTasksClient).mockRejectedValue(
850
+ new Error("Not logged in"),
851
+ );
852
+
853
+ const program = buildProgram();
854
+ await program.parseAsync(["node", "test", "users", "42"]);
855
+
856
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
857
+ expect.objectContaining({
858
+ Result: "Failure",
859
+ Message: "Error connecting to Action Center",
860
+ }),
861
+ );
862
+ expect(process.exitCode).toBe(1);
863
+ });
864
+ });
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type CommandExample,
2
3
  catchError,
3
4
  extractErrorMessage,
4
5
  extractErrorMessageSync,
@@ -49,6 +50,56 @@ interface CompleteOptions {
49
50
  action?: string;
50
51
  }
51
52
 
53
+ interface UsersOptions {
54
+ tenant?: string;
55
+ limit?: string;
56
+ }
57
+
58
+ const TASKS_USERS_EXAMPLES: CommandExample[] = [
59
+ {
60
+ Description: "List users with task permissions in a folder",
61
+ Command: "uip tasks users 42",
62
+ Output: {
63
+ Code: "TaskUserList",
64
+ Data: [
65
+ {
66
+ id: 1,
67
+ name: "Alice",
68
+ surname: "Smith",
69
+ userName: "alice",
70
+ emailAddress: "alice@company.com",
71
+ displayName: "Alice Smith",
72
+ },
73
+ {
74
+ id: 2,
75
+ name: "Bob",
76
+ surname: "Jones",
77
+ userName: "bob",
78
+ emailAddress: "bob@company.com",
79
+ displayName: "Bob Jones",
80
+ },
81
+ ],
82
+ },
83
+ },
84
+ {
85
+ Description: "Limit the number of users returned",
86
+ Command: "uip tasks users 42 --limit 10",
87
+ Output: {
88
+ Code: "TaskUserList",
89
+ Data: [
90
+ {
91
+ id: 1,
92
+ name: "Alice",
93
+ surname: "Smith",
94
+ userName: "alice",
95
+ emailAddress: "alice@company.com",
96
+ displayName: "Alice Smith",
97
+ },
98
+ ],
99
+ },
100
+ },
101
+ ];
102
+
52
103
  export const registerTasksCommand = (program: Command) => {
53
104
  program
54
105
  .command("list")
@@ -441,6 +492,107 @@ export const registerTasksCommand = (program: Command) => {
441
492
  });
442
493
  },
443
494
  );
495
+
496
+ program
497
+ .command("users")
498
+ .description(
499
+ "List users with task permissions in a folder. Use the user ID with 'tasks assign'.",
500
+ )
501
+ .argument("<folder-id>", "Folder ID")
502
+ .option("-t, --tenant <tenant-name>", "Tenant name")
503
+ .option(
504
+ "-l, --limit <number>",
505
+ "Maximum number of items to return (fetches all if omitted)",
506
+ )
507
+ .examples(TASKS_USERS_EXAMPLES)
508
+ .trackedAction(
509
+ processContext,
510
+ async (folderId: string, options: UsersOptions) => {
511
+ const [clientError, sdk] = await catchError(
512
+ createTasksClient(options.tenant),
513
+ );
514
+
515
+ if (clientError) {
516
+ OutputFormatter.error({
517
+ Result: RESULTS.Failure,
518
+ Message: "Error connecting to Action Center",
519
+ Instructions: await extractErrorMessage(clientError),
520
+ });
521
+ processContext.exit(1);
522
+ return;
523
+ }
524
+
525
+ const parsedFolderId = parsePositiveInt(
526
+ folderId,
527
+ "<folder-id>",
528
+ );
529
+ if (!parsedFolderId.valid) {
530
+ reportInvalidInt(parsedFolderId);
531
+ return;
532
+ }
533
+
534
+ let limit: number | undefined;
535
+ if (options.limit) {
536
+ const parsed = parsePositiveInt(options.limit, "--limit");
537
+ if (!parsed.valid) {
538
+ reportInvalidInt(parsed);
539
+ return;
540
+ }
541
+ limit = parsed.value;
542
+ }
543
+
544
+ const allItems: unknown[] = [];
545
+ let cursor: { value: string } | undefined;
546
+
547
+ do {
548
+ const getOptions: Record<string, unknown> = {
549
+ pageSize: DEFAULT_PAGE_SIZE,
550
+ };
551
+ if (cursor) {
552
+ getOptions.cursor = cursor;
553
+ }
554
+
555
+ const [usersError, result] = await catchError(
556
+ sdk.tasks.getUsers(parsedFolderId.value, getOptions),
557
+ );
558
+
559
+ if (usersError) {
560
+ OutputFormatter.error({
561
+ Result: RESULTS.Failure,
562
+ Message: "Error listing task users",
563
+ Instructions: await extractErrorMessage(usersError),
564
+ });
565
+ processContext.exit(1);
566
+ return;
567
+ }
568
+
569
+ if (Array.isArray(result)) {
570
+ allItems.push(...result);
571
+ break;
572
+ }
573
+
574
+ const page = result as unknown as Record<string, unknown>;
575
+ const items = (page.items ?? []) as unknown[];
576
+ allItems.push(...items);
577
+
578
+ cursor = page.hasNextPage
579
+ ? (page.nextCursor as { value: string })
580
+ : undefined;
581
+
582
+ if (limit && allItems.length >= limit) {
583
+ break;
584
+ }
585
+ } while (cursor);
586
+
587
+ const data = limit ? allItems.slice(0, limit) : allItems;
588
+
589
+ OutputFormatter.success({
590
+ Result: RESULTS.Success,
591
+ Code: "TaskUserList",
592
+ Data: data,
593
+ });
594
+ },
595
+ );
444
596
  };
445
597
 
446
598
  async function assignOrReassign(
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ type E2EManifest,
4
+ MANIFEST_PATH,
5
+ } from "../../../tests/e2e-shared/global-setup";
6
+ import {
7
+ EXEC_TIMEOUT,
8
+ localCliRaw,
9
+ readManifest,
10
+ } from "../../../tests/e2e-shared/helpers";
11
+
12
+ const HELP_BUDGET_MS = 1_000;
13
+
14
+ const manifest = readManifest<E2EManifest>(MANIFEST_PATH);
15
+ const projectDir = manifest.sharedInstall.tempDir;
16
+
17
+ describe("tasks performance", () => {
18
+ it(
19
+ `uip tasks -h completes within ${HELP_BUDGET_MS}ms`,
20
+ () => {
21
+ const start = performance.now();
22
+ localCliRaw(projectDir, "tasks -h");
23
+ expect(performance.now() - start).toBeLessThan(HELP_BUDGET_MS);
24
+ },
25
+ EXEC_TIMEOUT,
26
+ );
27
+ });