@uipath/data-fabric-tool 1.198.0 → 1.199.0-preview.104

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.
@@ -6,6 +6,8 @@ import {
6
6
  OutputFormatter,
7
7
  processContext,
8
8
  RESULTS,
9
+ runChecks,
10
+ type ValidationError,
9
11
  } from "@uipath/common";
10
12
  import type {
11
13
  EntityCreateFieldOptions,
@@ -20,6 +22,15 @@ import { type Command, Option } from "commander";
20
22
  import { readJsonInput } from "../utils/input";
21
23
  import { fail, requireDestructiveConfirmation } from "../utils/output";
22
24
  import { connectOrFail } from "../utils/sdk-client";
25
+ import {
26
+ findInvalidFieldConstraints,
27
+ findInvalidFieldName,
28
+ findIsUniqueInUpdateFields,
29
+ findMissingComplexFieldExtras,
30
+ findReferenceFolderKeyOnChoiceSet,
31
+ findUiBrokenFieldType,
32
+ validateName,
33
+ } from "../utils/validate";
23
34
 
24
35
  interface ListOptions {
25
36
  tenant?: string;
@@ -145,13 +156,14 @@ const ENTITIES_DELETE_EXAMPLES: CommandExample[] = [
145
156
  const ENTITIES_CREATE_EXAMPLES: CommandExample[] = [
146
157
  {
147
158
  Description:
148
- "Create an entity with choice-set, relationship, and file fields. " +
159
+ "Create an entity with choice-set, relationship, file, and large-text fields. " +
149
160
  "CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE require 'choiceSetId' (from 'df choice-sets list'). " +
150
161
  "RELATIONSHIP requires 'referenceEntityId' (UUID of the target entity, from 'df entities list') and 'referenceFieldId' (UUID of the field on the target entity, from 'df entities get <target-id>'). " +
151
162
  "Note: a RELATIONSHIP column on a record always stores the target record's UUID 'Id' (regardless of which 'referenceFieldId' configured the join) — see 'df records insert' for how to write the value. " +
152
- "FILE fields take only 'fieldName' and 'type' — the server auto-wires the internal attachment reference; populate them later with 'uip df files upload <entity-id> <record-id> <field-name> --file <path>'.",
163
+ "FILE fields take only 'fieldName' and 'type' — the server auto-wires the internal attachment reference; populate them later with 'uip df files upload <entity-id> <record-id> <field-name> --file <path>'. " +
164
+ "MULTILINE_MAX holds very large text ('lengthLimit' = UTF-16 byte budget, 1-131072; default max ~65,536 chars). Not filterable/sortable; 'records list'/'query' return a size marker — full content via 'records get'. Tenant-gated by the 'MultilineMax' feature flag.",
153
165
  Command:
154
- 'uip df entities create Expense --body \'{"displayName":"Expense","fields":[{"fieldName":"category","type":"CHOICE_SET_SINGLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000001","isRequired":true},{"fieldName":"tags","type":"CHOICE_SET_MULTIPLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000002"},{"fieldName":"submitter","type":"RELATIONSHIP","referenceEntityId":"a1b2c3d4-0000-0000-0000-000000000010","referenceFieldId":"f1000000-0000-0000-0000-000000000100","isRequired":true},{"fieldName":"receipt","type":"FILE"}]}\'',
166
+ 'uip df entities create Expense --body \'{"displayName":"Expense","fields":[{"fieldName":"category","type":"CHOICE_SET_SINGLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000001","isRequired":true},{"fieldName":"tags","type":"CHOICE_SET_MULTIPLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000002"},{"fieldName":"submitter","type":"RELATIONSHIP","referenceEntityId":"a1b2c3d4-0000-0000-0000-000000000010","referenceFieldId":"f1000000-0000-0000-0000-000000000100","isRequired":true},{"fieldName":"receipt","type":"FILE"},{"fieldName":"notes","type":"MULTILINE_MAX"}]}\'',
155
167
  Output: {
156
168
  Code: "EntityCreated",
157
169
  Data: { ID: "a1b2c3d4-0000-0000-0000-000000000004" },
@@ -204,6 +216,20 @@ function pickKnownUpdateKeys(
204
216
  return out as EntityUpdateByIdOptions;
205
217
  }
206
218
 
219
+ // Run every check that applies to a `fields` / `addFields` payload:
220
+ // name rules and UI-broken-type substitution. The SDK-enum type check
221
+ // stays inline at the call site because it needs `hasInvalidFieldType`
222
+ // (bound to the SDK enum, not portable to @uipath/common).
223
+ function validateFieldsPayload(fields: unknown[]): ValidationError | null {
224
+ return runChecks([
225
+ () => findInvalidFieldName(fields),
226
+ () => findUiBrokenFieldType(fields),
227
+ () => findReferenceFolderKeyOnChoiceSet(fields),
228
+ () => findMissingComplexFieldExtras(fields),
229
+ () => findInvalidFieldConstraints(fields),
230
+ ]);
231
+ }
232
+
207
233
  export const registerEntitiesCommand = (program: Command) => {
208
234
  const entities = program
209
235
  .command("entities")
@@ -408,6 +434,12 @@ export const registerEntitiesCommand = (program: Command) => {
408
434
  );
409
435
  }
410
436
 
437
+ const err = runChecks([
438
+ () => validateName(name, "entity"),
439
+ () => validateFieldsPayload(definition.fields as unknown[]),
440
+ ]);
441
+ if (err) return fail(err.message, err.instructions);
442
+
411
443
  const sdk = await connectOrFail(options.tenant);
412
444
  if (!sdk) return;
413
445
 
@@ -599,6 +631,12 @@ export const registerEntitiesCommand = (program: Command) => {
599
631
  );
600
632
  }
601
633
 
634
+ const addErr = validateFieldsPayload(
635
+ input.addFields as unknown[],
636
+ );
637
+ if (addErr)
638
+ return fail(addErr.message, addErr.instructions);
639
+
602
640
  const addNames = (
603
641
  input.addFields as { fieldName: string }[]
604
642
  ).map((f) => f.fieldName);
@@ -639,6 +677,12 @@ export const registerEntitiesCommand = (program: Command) => {
639
677
  'Use \'df entities get <entityId>\' to find field IDs. Example: {"id":"<fieldId>","displayName":"Total Amount","isRequired":true}',
640
678
  );
641
679
  }
680
+
681
+ const updateErr = findIsUniqueInUpdateFields(
682
+ input.updateFields as unknown[],
683
+ );
684
+ if (updateErr)
685
+ return fail(updateErr.message, updateErr.instructions);
642
686
  }
643
687
 
644
688
  const sdk = await connectOrFail(options.tenant);
@@ -37,6 +37,9 @@ function buildProgram(): Command {
37
37
  function mockSdk(overrides: Record<string, unknown> = {}) {
38
38
  const sdk = {
39
39
  entities: {
40
+ // Default schema has no FILE fields — insert/update FILE-payload
41
+ // guard is a no-op unless a test overrides this.
42
+ getById: vi.fn().mockResolvedValue({ fields: [] }),
40
43
  getAllRecords: vi.fn().mockResolvedValue({
41
44
  items: [],
42
45
  totalCount: 0,
@@ -781,6 +784,66 @@ describe("records insert", () => {
781
784
  );
782
785
  expect(process.exitCode).toBe(1);
783
786
  });
787
+
788
+ it("skips FILE-payload validation when schema fetch fails and lets the SDK error surface", async () => {
789
+ const sdk = mockSdk();
790
+ vi.mocked(sdk.entities.getById).mockRejectedValue(
791
+ new Error("Entity not found"),
792
+ );
793
+ vi.mocked(sdk.entities.insertRecordById).mockRejectedValue(
794
+ new Error("Entity not found"),
795
+ );
796
+
797
+ const program = buildProgram();
798
+ await program.parseAsync([
799
+ "node",
800
+ "test",
801
+ "records",
802
+ "insert",
803
+ "missing-entity-id",
804
+ "--body",
805
+ '{"title":"Q1"}',
806
+ ]);
807
+
808
+ expect(sdk.entities.insertRecordById).toHaveBeenCalled();
809
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
810
+ expect.objectContaining({
811
+ Result: "Failure",
812
+ Message: "Error inserting record",
813
+ }),
814
+ );
815
+ });
816
+
817
+ it("rejects a FILE-typed key in the insert body", async () => {
818
+ const sdk = mockSdk();
819
+ vi.mocked(sdk.entities.getById).mockResolvedValue({
820
+ fields: [
821
+ { name: "title", fieldDataType: { name: "STRING" } },
822
+ { name: "receipt", fieldDataType: { name: "FILE" } },
823
+ ],
824
+ } as never);
825
+
826
+ const program = buildProgram();
827
+ await program.parseAsync([
828
+ "node",
829
+ "test",
830
+ "records",
831
+ "insert",
832
+ "entity-id",
833
+ "--body",
834
+ '{"title":"Q1","receipt":"/tmp/r.pdf"}',
835
+ ]);
836
+
837
+ expect(sdk.entities.insertRecordById).not.toHaveBeenCalled();
838
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
839
+ expect.objectContaining({
840
+ Result: "Failure",
841
+ Message: expect.stringContaining("FILE-typed field 'receipt'"),
842
+ Instructions: expect.stringContaining("files upload"),
843
+ }),
844
+ );
845
+ expect(process.exitCode).toBe(1);
846
+ });
784
847
  });
785
848
 
786
849
  describe("records update", () => {
@@ -1408,8 +1471,8 @@ describe("records query", () => {
1408
1471
  queryFilters: [
1409
1472
  {
1410
1473
  fieldName: "amount",
1411
- operator: "GreaterThan",
1412
- value: 100,
1474
+ operator: ">",
1475
+ value: "100",
1413
1476
  },
1414
1477
  ],
1415
1478
  },
@@ -1550,7 +1613,7 @@ describe("records query", () => {
1550
1613
  queryFilters: [
1551
1614
  {
1552
1615
  fieldName: "status",
1553
- operator: "Equals",
1616
+ operator: "=",
1554
1617
  value: "Active",
1555
1618
  },
1556
1619
  ],
@@ -1953,6 +2016,59 @@ describe("records import", () => {
1953
2016
  });
1954
2017
  });
1955
2018
 
2019
+ describe("records query — filter validation", () => {
2020
+ beforeEach(() => {
2021
+ vi.resetAllMocks();
2022
+ process.exitCode = undefined;
2023
+ });
2024
+
2025
+ it("rejects an unsupported operator", async () => {
2026
+ const sdk = mockSdk();
2027
+ const program = buildProgram();
2028
+ await program.parseAsync([
2029
+ "node",
2030
+ "test",
2031
+ "records",
2032
+ "query",
2033
+ "entity-id",
2034
+ "--body",
2035
+ '{"filterGroup":{"queryFilters":[{"fieldName":"Status","operator":"==","value":"Active"}]}}',
2036
+ ]);
2037
+
2038
+ expect(sdk.entities.queryRecordsById).not.toHaveBeenCalled();
2039
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
2040
+ expect.objectContaining({
2041
+ Result: "Failure",
2042
+ Message: expect.stringContaining("unsupported operator '=='"),
2043
+ }),
2044
+ );
2045
+ expect(process.exitCode).toBe(1);
2046
+ });
2047
+
2048
+ it("rejects 'in' without a valueList", async () => {
2049
+ const sdk = mockSdk();
2050
+ const program = buildProgram();
2051
+ await program.parseAsync([
2052
+ "node",
2053
+ "test",
2054
+ "records",
2055
+ "query",
2056
+ "entity-id",
2057
+ "--body",
2058
+ '{"filterGroup":{"queryFilters":[{"fieldName":"Status","operator":"in","value":"Active"}]}}',
2059
+ ]);
2060
+
2061
+ expect(sdk.entities.queryRecordsById).not.toHaveBeenCalled();
2062
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
2063
+ expect.objectContaining({
2064
+ Result: "Failure",
2065
+ Message: expect.stringContaining("requires 'valueList'"),
2066
+ }),
2067
+ );
2068
+ expect(process.exitCode).toBe(1);
2069
+ });
2070
+ });
2071
+
1956
2072
  describe("records query — pagination", () => {
1957
2073
  beforeEach(() => {
1958
2074
  vi.resetAllMocks();
@@ -13,6 +13,60 @@ import { type Command, Option } from "commander";
13
13
  import { readFileBinary, readJsonInput } from "../utils/input";
14
14
  import { fail, requireDestructiveConfirmation } from "../utils/output";
15
15
  import { connectOrFail } from "../utils/sdk-client";
16
+ import { findFilePayloadKeys, validateQueryFilter } from "../utils/validate";
17
+
18
+ // Fetch the entity schema and return the set of FILE-typed field names.
19
+ // One RTT — worth it: without this, `records insert/update` silently
20
+ // strips FILE keys server-side and returns Success unchanged.
21
+ //
22
+ // On any fetch error, returns an empty set instead of surfacing the error.
23
+ // The SDK write call that follows will hit the same failure and produce
24
+ // the authoritative message ("Entity not found", auth issue, network,
25
+ // etc.) — pre-empting it here would hide the real error behind a
26
+ // misleading "validation" wrapper.
27
+ async function loadFileFieldNames(
28
+ sdk: Awaited<ReturnType<typeof connectOrFail>>,
29
+ entityId: string,
30
+ folderKey?: string,
31
+ ): Promise<Set<string>> {
32
+ const names = new Set<string>();
33
+ if (!sdk) return names;
34
+ const [schemaErr, entity] = await catchError(
35
+ sdk.entities.getById(
36
+ entityId,
37
+ folderKey !== undefined ? { folderKey } : undefined,
38
+ ),
39
+ );
40
+ if (schemaErr) return names;
41
+ // Detect FILE fields by their data-type name. The `isAttachment` boolean
42
+ // exists on the SDK type but the server does not reliably set it (all
43
+ // fields — including FILE — come back with `IsAttachment: false`), so
44
+ // relying on it silently skipped the FILE check in live tests.
45
+ //
46
+ // The response also uses PascalCase at runtime while the SDK's TypeScript
47
+ // type declares camelCase — read both to survive that skew.
48
+ const rec = entity as unknown as {
49
+ fields?: Array<Record<string, unknown>>;
50
+ Fields?: Array<Record<string, unknown>>;
51
+ };
52
+ const fieldList = rec.fields ?? rec.Fields ?? [];
53
+ for (const f of fieldList) {
54
+ const dataType = (f.fieldDataType ?? f.FieldDataType) as
55
+ | { name?: string; Name?: string }
56
+ | undefined;
57
+ const typeName = dataType?.name ?? dataType?.Name;
58
+ const fieldName =
59
+ typeof f.name === "string"
60
+ ? f.name
61
+ : typeof f.Name === "string"
62
+ ? f.Name
63
+ : undefined;
64
+ if (typeName === "FILE" && fieldName !== undefined) {
65
+ names.add(fieldName);
66
+ }
67
+ }
68
+ return names;
69
+ }
16
70
 
17
71
  interface ListOptions {
18
72
  tenant?: string;
@@ -72,7 +126,8 @@ interface BatchResult {
72
126
 
73
127
  const RECORDS_LIST_EXAMPLES: CommandExample[] = [
74
128
  {
75
- Description: "List records in an entity.",
129
+ Description:
130
+ "List records in an entity. MULTILINE_MAX fields return a size marker (see 'notes' below) — full value via 'records get'.",
76
131
  Command:
77
132
  "uip df records list a1b2c3d4-0000-0000-0000-000000000001 --limit 2",
78
133
  Output: {
@@ -82,10 +137,12 @@ const RECORDS_LIST_EXAMPLES: CommandExample[] = [
82
137
  {
83
138
  Id: "b2c3d4e5-0000-0000-0000-000000000001",
84
139
  amount: 1500,
140
+ notes: "HasValue=true Length=20000",
85
141
  },
86
142
  {
87
143
  Id: "b2c3d4e5-0000-0000-0000-000000000002",
88
144
  amount: 2750,
145
+ notes: null,
89
146
  },
90
147
  ],
91
148
  totalCount: 2,
@@ -97,7 +154,8 @@ const RECORDS_LIST_EXAMPLES: CommandExample[] = [
97
154
 
98
155
  const RECORDS_GET_EXAMPLES: CommandExample[] = [
99
156
  {
100
- Description: "Get a record by ID",
157
+ Description:
158
+ "Get a record by ID. Returns MULTILINE_MAX fields in full (see 'notes' below).",
101
159
  Command:
102
160
  "uip df records get a1b2c3d4-0000-0000-0000-000000000001 b2c3d4e5-0000-0000-0000-000000000001",
103
161
  Output: {
@@ -106,6 +164,7 @@ const RECORDS_GET_EXAMPLES: CommandExample[] = [
106
164
  Id: "b2c3d4e5-0000-0000-0000-000000000001",
107
165
  amount: 1500,
108
166
  status: "Paid",
167
+ notes: "Customer disputed the Q1 late fee. Timeline of the case: … (the complete 20,000-character text, returned in full)",
109
168
  },
110
169
  },
111
170
  },
@@ -411,6 +470,14 @@ export const registerRecordsCommand = (program: Command) => {
411
470
  ? { folderKey: options.folderKey }
412
471
  : undefined;
413
472
 
473
+ const fileNames = await loadFileFieldNames(
474
+ sdk,
475
+ entityId,
476
+ options.folderKey,
477
+ );
478
+ const fileErr = findFilePayloadKeys(recordsList, fileNames);
479
+ if (fileErr) return fail(fileErr.message, fileErr.instructions);
480
+
414
481
  if (recordsList.length === 1) {
415
482
  const [insertError, result] = await catchError(
416
483
  sdk.entities.insertRecordById(
@@ -511,6 +578,14 @@ export const registerRecordsCommand = (program: Command) => {
511
578
  ? { folderKey: options.folderKey }
512
579
  : undefined;
513
580
 
581
+ const fileNames = await loadFileFieldNames(
582
+ sdk,
583
+ entityId,
584
+ options.folderKey,
585
+ );
586
+ const fileErr = findFilePayloadKeys(recordsList, fileNames);
587
+ if (fileErr) return fail(fileErr.message, fileErr.instructions);
588
+
514
589
  if (recordsList.length === 1) {
515
590
  const record = recordsList[0];
516
591
  const recordId =
@@ -685,6 +760,12 @@ export const registerRecordsCommand = (program: Command) => {
685
760
  );
686
761
  }
687
762
  queryBody = parsed as Record<string, unknown>;
763
+
764
+ const filterErr = validateQueryFilter(
765
+ queryBody.filterGroup,
766
+ );
767
+ if (filterErr)
768
+ return fail(filterErr.message, filterErr.instructions);
688
769
  }
689
770
 
690
771
  const sdk = await connectOrFail(options.tenant);
@@ -0,0 +1,153 @@
1
+ import { Command } from "commander";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ vi.mock("../utils/sdk-client", () => ({
5
+ createDataFabricClient: vi.fn(),
6
+ connectOrFail: vi.fn(),
7
+ }));
8
+
9
+ vi.mock("@uipath/common", async (importOriginal) => {
10
+ const actual = await importOriginal<typeof import("@uipath/common")>();
11
+ return {
12
+ ...actual,
13
+ OutputFormatter: {
14
+ success: vi.fn(),
15
+ error: vi.fn(),
16
+ },
17
+ };
18
+ });
19
+
20
+ import { OutputFormatter } from "@uipath/common";
21
+ import { connectOrFail } from "../utils/sdk-client";
22
+ import { registerRolesCommand } from "./roles";
23
+
24
+ function buildProgram(): Command {
25
+ const program = new Command();
26
+ program.name("test").exitOverride();
27
+ registerRolesCommand(program);
28
+ return program;
29
+ }
30
+
31
+ function mockSdk(overrides: Record<string, unknown> = {}) {
32
+ const sdk = {
33
+ entities: {
34
+ roles: {
35
+ getAll: vi.fn().mockResolvedValue([]),
36
+ ...overrides,
37
+ },
38
+ },
39
+ };
40
+ vi.mocked(connectOrFail).mockResolvedValue(sdk as never);
41
+ return sdk;
42
+ }
43
+
44
+ describe("roles", () => {
45
+ beforeEach(() => {
46
+ vi.resetAllMocks();
47
+ process.exitCode = undefined;
48
+ });
49
+
50
+ it("should register the roles command as hidden with a list subcommand", () => {
51
+ const program = buildProgram();
52
+ const cmd = program.commands.find((c) => c.name() === "roles");
53
+ expect(cmd).toBeDefined();
54
+ expect(program.commands.some((c) => c.name() === "roles")).toBe(true);
55
+ const subNames = cmd?.commands.map((c) => c.name()) ?? [];
56
+ expect(subNames).toContain("list");
57
+ });
58
+
59
+ it("should list roles with stats enabled by default", async () => {
60
+ const sdk = mockSdk();
61
+ const rawRoles = [
62
+ {
63
+ id: "c1d2e3f4-0000-0000-0000-000000000001",
64
+ name: "DataWriter",
65
+ type: "System",
66
+ directoryEntityCount: 3,
67
+ },
68
+ ];
69
+ vi.mocked(sdk.entities.roles.getAll).mockResolvedValue(
70
+ rawRoles as never,
71
+ );
72
+
73
+ const program = buildProgram();
74
+ await program.parseAsync(["node", "test", "roles", "list"]);
75
+
76
+ expect(sdk.entities.roles.getAll).toHaveBeenCalledWith({
77
+ stats: true,
78
+ });
79
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
80
+ expect.objectContaining({
81
+ Result: "Success",
82
+ Code: "RoleList",
83
+ Data: rawRoles,
84
+ }),
85
+ );
86
+ });
87
+
88
+ it("should pass stats false with --no-stats", async () => {
89
+ const sdk = mockSdk();
90
+
91
+ const program = buildProgram();
92
+ await program.parseAsync([
93
+ "node",
94
+ "test",
95
+ "roles",
96
+ "list",
97
+ "--no-stats",
98
+ ]);
99
+
100
+ expect(sdk.entities.roles.getAll).toHaveBeenCalledWith({
101
+ stats: false,
102
+ });
103
+ });
104
+
105
+ it("should forward --folder-key", async () => {
106
+ const sdk = mockSdk();
107
+
108
+ const program = buildProgram();
109
+ await program.parseAsync([
110
+ "node",
111
+ "test",
112
+ "roles",
113
+ "list",
114
+ "--folder-key",
115
+ "f-key-1",
116
+ ]);
117
+
118
+ expect(sdk.entities.roles.getAll).toHaveBeenCalledWith({
119
+ stats: true,
120
+ folderKey: "f-key-1",
121
+ });
122
+ });
123
+
124
+ it("should fail with exit 1 when the SDK call errors", async () => {
125
+ const sdk = mockSdk();
126
+ vi.mocked(sdk.entities.roles.getAll).mockRejectedValue(
127
+ new Error("boom"),
128
+ );
129
+
130
+ const program = buildProgram();
131
+ await program.parseAsync(["node", "test", "roles", "list"]);
132
+
133
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
134
+ expect.objectContaining({
135
+ Result: "Failure",
136
+ Message: "Error listing roles",
137
+ }),
138
+ );
139
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
140
+ expect(process.exitCode).toBe(1);
141
+ });
142
+
143
+ it("should bail when SDK connection fails", async () => {
144
+ const sdk = mockSdk();
145
+ vi.mocked(connectOrFail).mockResolvedValue(undefined);
146
+
147
+ const program = buildProgram();
148
+ await program.parseAsync(["node", "test", "roles", "list"]);
149
+
150
+ expect(sdk.entities.roles.getAll).not.toHaveBeenCalled();
151
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
152
+ });
153
+ });
@@ -0,0 +1,88 @@
1
+ import {
2
+ type CommandExample,
3
+ catchError,
4
+ createHiddenDeprecatedTenantOption,
5
+ extractErrorMessage,
6
+ OutputFormatter,
7
+ processContext,
8
+ RESULTS,
9
+ } from "@uipath/common";
10
+ import type { Command } from "commander";
11
+ import { fail } from "../utils/output";
12
+ import { connectOrFail } from "../utils/sdk-client";
13
+
14
+ interface ListOptions {
15
+ tenant?: string;
16
+ stats: boolean;
17
+ folderKey?: string;
18
+ }
19
+
20
+ const ROLES_LIST_EXAMPLES: CommandExample[] = [
21
+ {
22
+ Description: "List Data Fabric access roles.",
23
+ Command: "uip df roles list",
24
+ Output: {
25
+ Code: "RoleList",
26
+ Data: [
27
+ {
28
+ Id: "c1d2e3f4-0000-0000-0000-000000000001",
29
+ Name: "DataWriter",
30
+ Type: "System",
31
+ DirectoryEntityCount: 3,
32
+ },
33
+ ],
34
+ },
35
+ },
36
+ ];
37
+
38
+ /**
39
+ * Internal manage-access surface — hidden from help output and not part of
40
+ * the documented `uip df` command set.
41
+ */
42
+ export const registerRolesCommand = (program: Command) => {
43
+ const roles = program
44
+ .command("roles", { hidden: true })
45
+ .description("Manage Data Fabric access roles");
46
+
47
+ roles
48
+ .command("list")
49
+ .description("List Data Fabric access roles")
50
+ .addOption(
51
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
52
+ )
53
+ .option(
54
+ "--no-stats",
55
+ "Skip role statistics (directory entity counts) in the response",
56
+ )
57
+ .option(
58
+ "--folder-key <key>",
59
+ "Folder key (GUID) for folder-scoped role requests",
60
+ )
61
+ .examples(ROLES_LIST_EXAMPLES)
62
+ .trackedAction(processContext, async (options: ListOptions) => {
63
+ const sdk = await connectOrFail(options.tenant);
64
+ if (!sdk) return;
65
+
66
+ const [listError, result] = await catchError(
67
+ sdk.entities.roles.getAll({
68
+ stats: options.stats,
69
+ ...(options.folderKey !== undefined && {
70
+ folderKey: options.folderKey,
71
+ }),
72
+ }),
73
+ );
74
+
75
+ if (listError) {
76
+ return fail(
77
+ "Error listing roles",
78
+ await extractErrorMessage(listError),
79
+ );
80
+ }
81
+
82
+ OutputFormatter.success({
83
+ Result: RESULTS.Success,
84
+ Code: "RoleList",
85
+ Data: result ?? [],
86
+ });
87
+ });
88
+ };
package/src/tool.ts CHANGED
@@ -4,9 +4,11 @@ import {
4
4
  registerChoiceSetsCommand,
5
5
  registerChoiceSetValuesCommand,
6
6
  } from "./commands/choice-sets";
7
+ import { registerDirectoryCommand } from "./commands/directory";
7
8
  import { registerEntitiesCommand } from "./commands/entities";
8
9
  import { registerFilesCommand } from "./commands/files";
9
10
  import { registerRecordsCommand } from "./commands/records";
11
+ import { registerRolesCommand } from "./commands/roles";
10
12
 
11
13
  export const metadata = {
12
14
  name: "data-fabric-tool",
@@ -22,4 +24,6 @@ export const registerCommands = async (program: Command): Promise<void> => {
22
24
  registerFilesCommand(program);
23
25
  registerChoiceSetsCommand(program);
24
26
  registerChoiceSetValuesCommand(program);
27
+ registerRolesCommand(program);
28
+ registerDirectoryCommand(program);
25
29
  };