@uipath/data-fabric-tool 1.195.0 → 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.
- package/dist/tool.js +4631 -4224
- package/package.json +2 -2
- package/src/commands/choice-sets.spec.ts +45 -12
- package/src/commands/choice-sets.ts +13 -3
- package/src/commands/entities.spec.ts +51 -14
- package/src/commands/entities.ts +21 -5
- package/src/commands/files.spec.ts +45 -3
- package/src/commands/files.ts +21 -2
- package/src/commands/records.spec.ts +87 -0
- package/src/commands/records.ts +24 -5
- package/src/utils/input.spec.ts +127 -0
- package/src/utils/input.ts +30 -1
- package/src/utils/output.spec.ts +22 -9
- package/src/utils/output.ts +27 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/data-fabric-tool",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.196.0",
|
|
5
5
|
"description": "Manage Data Fabric entities and records.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/tool.js",
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"registry": "https://registry.npmjs.org/"
|
|
16
16
|
},
|
|
17
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "94d71f9c52214980a1f0ae62b3f5372095788553"
|
|
18
18
|
}
|
|
@@ -557,7 +557,7 @@ describe("choice-sets delete", () => {
|
|
|
557
557
|
);
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
-
it("should require --confirm", async () => {
|
|
560
|
+
it("should require confirmation (no --yes/--confirm): exit 1, SDK not called", async () => {
|
|
561
561
|
const sdk = mockSdk();
|
|
562
562
|
const program = buildProgram();
|
|
563
563
|
await program.parseAsync([
|
|
@@ -570,13 +570,30 @@ describe("choice-sets delete", () => {
|
|
|
570
570
|
"cleanup",
|
|
571
571
|
]);
|
|
572
572
|
expect(sdk.entities.choicesets.deleteById).not.toHaveBeenCalled();
|
|
573
|
-
expect(OutputFormatter.
|
|
573
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
574
|
+
expect(process.exitCode).toBe(1);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("should accept --yes (canonical confirmation flag)", async () => {
|
|
578
|
+
const sdk = mockSdk();
|
|
579
|
+
const program = buildProgram();
|
|
580
|
+
await program.parseAsync([
|
|
581
|
+
"node",
|
|
582
|
+
"test",
|
|
583
|
+
"choice-sets",
|
|
584
|
+
"delete",
|
|
585
|
+
"cs-1",
|
|
586
|
+
"--yes",
|
|
587
|
+
"--reason",
|
|
588
|
+
"cleanup",
|
|
589
|
+
]);
|
|
590
|
+
expect(sdk.entities.choicesets.deleteById).toHaveBeenCalledWith("cs-1");
|
|
591
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
574
592
|
expect.objectContaining({
|
|
575
|
-
Result: "
|
|
576
|
-
|
|
593
|
+
Result: "Success",
|
|
594
|
+
Code: "ChoiceSetDeleted",
|
|
577
595
|
}),
|
|
578
596
|
);
|
|
579
|
-
expect(process.exitCode).toBe(1);
|
|
580
597
|
});
|
|
581
598
|
|
|
582
599
|
it("should require --reason", async () => {
|
|
@@ -800,7 +817,7 @@ describe("choice-set-values", () => {
|
|
|
800
817
|
expect(process.exitCode).toBe(1);
|
|
801
818
|
});
|
|
802
819
|
|
|
803
|
-
it("should require
|
|
820
|
+
it("should require confirmation for value delete: exit 1, SDK not called", async () => {
|
|
804
821
|
const sdk = mockSdk();
|
|
805
822
|
const program = buildValuesProgram();
|
|
806
823
|
await program.parseAsync([
|
|
@@ -815,15 +832,31 @@ describe("choice-set-values", () => {
|
|
|
815
832
|
"cleanup",
|
|
816
833
|
]);
|
|
817
834
|
expect(sdk.entities.choicesets.deleteValuesById).not.toHaveBeenCalled();
|
|
818
|
-
expect(OutputFormatter.
|
|
819
|
-
expect.objectContaining({
|
|
820
|
-
Result: "Failure",
|
|
821
|
-
Message: "Confirmation required for destructive operation",
|
|
822
|
-
}),
|
|
823
|
-
);
|
|
835
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
824
836
|
expect(process.exitCode).toBe(1);
|
|
825
837
|
});
|
|
826
838
|
|
|
839
|
+
it("should accept --yes for value delete (canonical flag)", async () => {
|
|
840
|
+
const sdk = mockSdk();
|
|
841
|
+
const program = buildValuesProgram();
|
|
842
|
+
await program.parseAsync([
|
|
843
|
+
"node",
|
|
844
|
+
"test",
|
|
845
|
+
"choice-set-values",
|
|
846
|
+
"delete",
|
|
847
|
+
"cs-1",
|
|
848
|
+
"--ids",
|
|
849
|
+
"v-1",
|
|
850
|
+
"--yes",
|
|
851
|
+
"--reason",
|
|
852
|
+
"cleanup",
|
|
853
|
+
]);
|
|
854
|
+
expect(sdk.entities.choicesets.deleteValuesById).toHaveBeenCalled();
|
|
855
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
856
|
+
expect.objectContaining({ Result: "Success" }),
|
|
857
|
+
);
|
|
858
|
+
});
|
|
859
|
+
|
|
827
860
|
it("should require --reason for value delete", async () => {
|
|
828
861
|
const sdk = mockSdk();
|
|
829
862
|
const program = buildValuesProgram();
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
ChoiceSetUpdateOptions,
|
|
14
14
|
ChoiceSetValueInsertOptions,
|
|
15
15
|
} from "@uipath/uipath-typescript";
|
|
16
|
-
import type
|
|
16
|
+
import { type Command, Option } from "commander";
|
|
17
17
|
import { fail, requireDestructiveConfirmation } from "../utils/output";
|
|
18
18
|
import { connectOrFail } from "../utils/sdk-client";
|
|
19
19
|
|
|
@@ -54,6 +54,7 @@ interface UpdateOptions {
|
|
|
54
54
|
|
|
55
55
|
interface DeleteOptions {
|
|
56
56
|
tenant?: string;
|
|
57
|
+
yes?: boolean;
|
|
57
58
|
confirm?: boolean;
|
|
58
59
|
reason?: string;
|
|
59
60
|
}
|
|
@@ -70,6 +71,7 @@ interface ValueUpdateOptions {
|
|
|
70
71
|
interface ValueDeleteOptions {
|
|
71
72
|
tenant?: string;
|
|
72
73
|
ids?: string;
|
|
74
|
+
yes?: boolean;
|
|
73
75
|
confirm?: boolean;
|
|
74
76
|
reason?: string;
|
|
75
77
|
}
|
|
@@ -417,7 +419,10 @@ export const registerChoiceSetsCommand = (program: Command) => {
|
|
|
417
419
|
.addOption(
|
|
418
420
|
createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
|
|
419
421
|
)
|
|
420
|
-
.option("--
|
|
422
|
+
.option("-y, --yes", "Acknowledge this is an irreversible operation")
|
|
423
|
+
.addOption(
|
|
424
|
+
new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
|
|
425
|
+
)
|
|
421
426
|
.option(
|
|
422
427
|
"--reason <reason>",
|
|
423
428
|
"Reason for the deletion — echoed back in the response so the caller can log it",
|
|
@@ -428,6 +433,7 @@ export const registerChoiceSetsCommand = (program: Command) => {
|
|
|
428
433
|
async (choiceSetId: string, options: DeleteOptions) => {
|
|
429
434
|
const reason = requireDestructiveConfirmation(
|
|
430
435
|
options,
|
|
436
|
+
`delete choice set '${choiceSetId}'`,
|
|
431
437
|
'Pass --reason "<text>" to record why the choice set is being deleted.',
|
|
432
438
|
);
|
|
433
439
|
if (reason === null) return;
|
|
@@ -633,7 +639,10 @@ export const registerChoiceSetValuesCommand = (program: Command) => {
|
|
|
633
639
|
createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
|
|
634
640
|
)
|
|
635
641
|
.option("--ids <ids>", "Comma-separated list of value IDs to delete")
|
|
636
|
-
.option("--
|
|
642
|
+
.option("-y, --yes", "Acknowledge this is an irreversible operation")
|
|
643
|
+
.addOption(
|
|
644
|
+
new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
|
|
645
|
+
)
|
|
637
646
|
.option(
|
|
638
647
|
"--reason <reason>",
|
|
639
648
|
"Reason for the deletion — echoed back in the response so the caller can log it",
|
|
@@ -656,6 +665,7 @@ export const registerChoiceSetValuesCommand = (program: Command) => {
|
|
|
656
665
|
|
|
657
666
|
const reason = requireDestructiveConfirmation(
|
|
658
667
|
options,
|
|
668
|
+
`delete ${valueIds.length} value(s) from choice set '${choiceSetId}'`,
|
|
659
669
|
'Pass --reason "<text>" to record why the values are being deleted.',
|
|
660
670
|
);
|
|
661
671
|
if (reason === null) return;
|
|
@@ -1217,7 +1217,7 @@ describe("entities update", () => {
|
|
|
1217
1217
|
expect(process.exitCode).toBe(1);
|
|
1218
1218
|
});
|
|
1219
1219
|
|
|
1220
|
-
it("should reject removeFields without
|
|
1220
|
+
it("should reject removeFields without confirmation: exit 1, SDK not called", async () => {
|
|
1221
1221
|
const sdk = mockSdk();
|
|
1222
1222
|
vi.mocked(readJsonInput).mockResolvedValue({
|
|
1223
1223
|
removeFields: [{ fieldName: "oldField" }],
|
|
@@ -1237,15 +1237,36 @@ describe("entities update", () => {
|
|
|
1237
1237
|
]);
|
|
1238
1238
|
|
|
1239
1239
|
expect(sdk.entities.updateById).not.toHaveBeenCalled();
|
|
1240
|
-
expect(OutputFormatter.
|
|
1241
|
-
expect.objectContaining({
|
|
1242
|
-
Result: "Failure",
|
|
1243
|
-
Message: "Confirmation required for destructive operation",
|
|
1244
|
-
}),
|
|
1245
|
-
);
|
|
1240
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
1246
1241
|
expect(process.exitCode).toBe(1);
|
|
1247
1242
|
});
|
|
1248
1243
|
|
|
1244
|
+
it("should accept removeFields with --yes (canonical flag)", async () => {
|
|
1245
|
+
const sdk = mockSdk();
|
|
1246
|
+
vi.mocked(readJsonInput).mockResolvedValue({
|
|
1247
|
+
removeFields: [{ fieldName: "oldField" }],
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
const program = buildProgram();
|
|
1251
|
+
await program.parseAsync([
|
|
1252
|
+
"node",
|
|
1253
|
+
"test",
|
|
1254
|
+
"entities",
|
|
1255
|
+
"update",
|
|
1256
|
+
"entity-id",
|
|
1257
|
+
"--body",
|
|
1258
|
+
'{"removeFields":[{"fieldName":"oldField"}]}',
|
|
1259
|
+
"--yes",
|
|
1260
|
+
"--reason",
|
|
1261
|
+
"drop legacy",
|
|
1262
|
+
]);
|
|
1263
|
+
|
|
1264
|
+
expect(sdk.entities.updateById).toHaveBeenCalled();
|
|
1265
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
1266
|
+
expect.objectContaining({ Result: "Success" }),
|
|
1267
|
+
);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1249
1270
|
it("should reject removeFields without --reason", async () => {
|
|
1250
1271
|
const sdk = mockSdk();
|
|
1251
1272
|
vi.mocked(readJsonInput).mockResolvedValue({
|
|
@@ -1764,7 +1785,7 @@ describe("entities delete", () => {
|
|
|
1764
1785
|
);
|
|
1765
1786
|
});
|
|
1766
1787
|
|
|
1767
|
-
it("should error when
|
|
1788
|
+
it("should error when confirmation is missing: exit 1, SDK not called", async () => {
|
|
1768
1789
|
const sdk = mockSdk();
|
|
1769
1790
|
|
|
1770
1791
|
const program = buildProgram();
|
|
@@ -1779,15 +1800,31 @@ describe("entities delete", () => {
|
|
|
1779
1800
|
]);
|
|
1780
1801
|
|
|
1781
1802
|
expect(sdk.entities.deleteById).not.toHaveBeenCalled();
|
|
1782
|
-
expect(OutputFormatter.
|
|
1783
|
-
expect.objectContaining({
|
|
1784
|
-
Result: "Failure",
|
|
1785
|
-
Message: "Confirmation required for destructive operation",
|
|
1786
|
-
}),
|
|
1787
|
-
);
|
|
1803
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
1788
1804
|
expect(process.exitCode).toBe(1);
|
|
1789
1805
|
});
|
|
1790
1806
|
|
|
1807
|
+
it("should delete with --yes (canonical confirmation flag)", async () => {
|
|
1808
|
+
const sdk = mockSdk();
|
|
1809
|
+
|
|
1810
|
+
const program = buildProgram();
|
|
1811
|
+
await program.parseAsync([
|
|
1812
|
+
"node",
|
|
1813
|
+
"test",
|
|
1814
|
+
"entities",
|
|
1815
|
+
"delete",
|
|
1816
|
+
"entity-id",
|
|
1817
|
+
"--yes",
|
|
1818
|
+
"--reason",
|
|
1819
|
+
"cleanup",
|
|
1820
|
+
]);
|
|
1821
|
+
|
|
1822
|
+
expect(sdk.entities.deleteById).toHaveBeenCalledWith("entity-id");
|
|
1823
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
1824
|
+
expect.objectContaining({ Result: "Success" }),
|
|
1825
|
+
);
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1791
1828
|
it("should error when --reason is missing", async () => {
|
|
1792
1829
|
const sdk = mockSdk();
|
|
1793
1830
|
|
package/src/commands/entities.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
RawEntityGetResponse,
|
|
16
16
|
} from "@uipath/uipath-typescript";
|
|
17
17
|
import { EntityFieldDataType } from "@uipath/uipath-typescript";
|
|
18
|
-
import type
|
|
18
|
+
import { type Command, Option } from "commander";
|
|
19
19
|
import { readJsonInput } from "../utils/input";
|
|
20
20
|
import { fail, requireDestructiveConfirmation } from "../utils/output";
|
|
21
21
|
import { connectOrFail } from "../utils/sdk-client";
|
|
@@ -39,12 +39,14 @@ interface UpdateEntityOptions {
|
|
|
39
39
|
tenant?: string;
|
|
40
40
|
file?: string;
|
|
41
41
|
body?: string;
|
|
42
|
+
yes?: boolean;
|
|
42
43
|
confirm?: boolean;
|
|
43
44
|
reason?: string;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
interface DeleteOptions {
|
|
47
48
|
tenant?: string;
|
|
49
|
+
yes?: boolean;
|
|
48
50
|
confirm?: boolean;
|
|
49
51
|
reason?: string;
|
|
50
52
|
}
|
|
@@ -278,7 +280,10 @@ export const registerEntitiesCommand = (program: Command) => {
|
|
|
278
280
|
"-f, --file <path>",
|
|
279
281
|
"Path to JSON file with entity definition (fields array required; displayName, description, isRbacEnabled optional)",
|
|
280
282
|
)
|
|
281
|
-
.option(
|
|
283
|
+
.option(
|
|
284
|
+
"--body <json>",
|
|
285
|
+
"Inline JSON entity definition (use `-` to read from stdin)",
|
|
286
|
+
)
|
|
282
287
|
.examples(ENTITIES_CREATE_EXAMPLES)
|
|
283
288
|
.trackedAction(
|
|
284
289
|
processContext,
|
|
@@ -390,11 +395,17 @@ export const registerEntitiesCommand = (program: Command) => {
|
|
|
390
395
|
"-f, --file <path>",
|
|
391
396
|
"Path to JSON file with update options (addFields, updateFields, removeFields, displayName, description, isRbacEnabled)",
|
|
392
397
|
)
|
|
393
|
-
.option("--body <json>", "Inline JSON update options")
|
|
394
398
|
.option(
|
|
395
|
-
"--
|
|
399
|
+
"--body <json>",
|
|
400
|
+
"Inline JSON update options (use `-` to read from stdin)",
|
|
401
|
+
)
|
|
402
|
+
.option(
|
|
403
|
+
"-y, --yes",
|
|
396
404
|
"Required when 'removeFields' is non-empty — acknowledges the field deletion is irreversible",
|
|
397
405
|
)
|
|
406
|
+
.addOption(
|
|
407
|
+
new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
|
|
408
|
+
)
|
|
398
409
|
.option(
|
|
399
410
|
"--reason <reason>",
|
|
400
411
|
"Required when 'removeFields' is non-empty — echoed back in the response so the caller can log it",
|
|
@@ -482,6 +493,7 @@ export const registerEntitiesCommand = (program: Command) => {
|
|
|
482
493
|
|
|
483
494
|
const reason = requireDestructiveConfirmation(
|
|
484
495
|
options,
|
|
496
|
+
`remove fields from entity '${id}'`,
|
|
485
497
|
'Pass --reason "<text>" to record why fields are being removed.',
|
|
486
498
|
);
|
|
487
499
|
if (reason === null) return;
|
|
@@ -593,7 +605,10 @@ export const registerEntitiesCommand = (program: Command) => {
|
|
|
593
605
|
.addOption(
|
|
594
606
|
createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
|
|
595
607
|
)
|
|
596
|
-
.option("--
|
|
608
|
+
.option("-y, --yes", "Acknowledge this is an irreversible operation")
|
|
609
|
+
.addOption(
|
|
610
|
+
new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
|
|
611
|
+
)
|
|
597
612
|
.option(
|
|
598
613
|
"--reason <reason>",
|
|
599
614
|
"Reason for the deletion — echoed back in the response so the caller can log it",
|
|
@@ -604,6 +619,7 @@ export const registerEntitiesCommand = (program: Command) => {
|
|
|
604
619
|
async (id: string, options: DeleteOptions) => {
|
|
605
620
|
const reason = requireDestructiveConfirmation(
|
|
606
621
|
options,
|
|
622
|
+
`delete entity '${id}'`,
|
|
607
623
|
'Pass --reason "<text>" to record why the entity is being deleted.',
|
|
608
624
|
);
|
|
609
625
|
if (reason === null) return;
|
|
@@ -237,7 +237,31 @@ describe("files delete", () => {
|
|
|
237
237
|
it("should delete and return FileDeleted on success", async () => {
|
|
238
238
|
const sdk = mockSdk();
|
|
239
239
|
const program = buildProgram();
|
|
240
|
-
await runCommand(
|
|
240
|
+
await runCommand(
|
|
241
|
+
program,
|
|
242
|
+
"files delete entity-1 record-1 attachment --yes --reason cleanup",
|
|
243
|
+
);
|
|
244
|
+
expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
|
|
245
|
+
"entity-1",
|
|
246
|
+
"record-1",
|
|
247
|
+
"attachment",
|
|
248
|
+
);
|
|
249
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
Result: "Success",
|
|
252
|
+
Code: "FileDeleted",
|
|
253
|
+
Data: expect.objectContaining({ Reason: "cleanup" }),
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should accept --confirm as a deprecated alias for --yes", async () => {
|
|
259
|
+
const sdk = mockSdk();
|
|
260
|
+
const program = buildProgram();
|
|
261
|
+
await runCommand(
|
|
262
|
+
program,
|
|
263
|
+
"files delete entity-1 record-1 attachment --confirm --reason cleanup",
|
|
264
|
+
);
|
|
241
265
|
expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
|
|
242
266
|
"entity-1",
|
|
243
267
|
"record-1",
|
|
@@ -248,11 +272,26 @@ describe("files delete", () => {
|
|
|
248
272
|
);
|
|
249
273
|
});
|
|
250
274
|
|
|
275
|
+
it("should require confirmation: without --yes, exit 1 and SDK not called", async () => {
|
|
276
|
+
const sdk = mockSdk();
|
|
277
|
+
const program = buildProgram();
|
|
278
|
+
await runCommand(
|
|
279
|
+
program,
|
|
280
|
+
"files delete entity-1 record-1 attachment --reason cleanup",
|
|
281
|
+
);
|
|
282
|
+
expect(sdk.entities.deleteAttachment).not.toHaveBeenCalled();
|
|
283
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
284
|
+
expect(process.exitCode).toBe(1);
|
|
285
|
+
});
|
|
286
|
+
|
|
251
287
|
it("should error when delete API fails", async () => {
|
|
252
288
|
const sdk = mockSdk();
|
|
253
289
|
sdk.entities.deleteAttachment.mockRejectedValue(new Error("Forbidden"));
|
|
254
290
|
const program = buildProgram();
|
|
255
|
-
await runCommand(
|
|
291
|
+
await runCommand(
|
|
292
|
+
program,
|
|
293
|
+
"files delete entity-1 record-1 attachment --yes --reason cleanup",
|
|
294
|
+
);
|
|
256
295
|
expect(OutputFormatter.error).toHaveBeenCalledWith(
|
|
257
296
|
expect.objectContaining({
|
|
258
297
|
Result: "Failure",
|
|
@@ -265,7 +304,10 @@ describe("files delete", () => {
|
|
|
265
304
|
const sdk = mockSdk();
|
|
266
305
|
vi.mocked(connectOrFail).mockResolvedValue(undefined);
|
|
267
306
|
const program = buildProgram();
|
|
268
|
-
await runCommand(
|
|
307
|
+
await runCommand(
|
|
308
|
+
program,
|
|
309
|
+
"files delete entity-1 record-1 attachment --yes --reason cleanup",
|
|
310
|
+
);
|
|
269
311
|
expect(sdk.entities.deleteAttachment).not.toHaveBeenCalled();
|
|
270
312
|
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
271
313
|
});
|
package/src/commands/files.ts
CHANGED
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
RESULTS,
|
|
9
9
|
} from "@uipath/common";
|
|
10
10
|
import { getFileSystem } from "@uipath/filesystem";
|
|
11
|
-
import type
|
|
11
|
+
import { type Command, Option } from "commander";
|
|
12
12
|
import { readFileBinary } from "../utils/input";
|
|
13
|
-
import { fail } from "../utils/output";
|
|
13
|
+
import { fail, requireDestructiveConfirmation } from "../utils/output";
|
|
14
14
|
import { connectOrFail } from "../utils/sdk-client";
|
|
15
15
|
|
|
16
16
|
interface UploadOptions {
|
|
@@ -25,6 +25,9 @@ interface DownloadOptions {
|
|
|
25
25
|
|
|
26
26
|
interface DeleteOptions {
|
|
27
27
|
tenant?: string;
|
|
28
|
+
yes?: boolean;
|
|
29
|
+
confirm?: boolean;
|
|
30
|
+
reason?: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const FILES_UPLOAD_EXAMPLES: CommandExample[] = [
|
|
@@ -241,6 +244,14 @@ export const registerFilesCommand = (program: Command) => {
|
|
|
241
244
|
.addOption(
|
|
242
245
|
createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
|
|
243
246
|
)
|
|
247
|
+
.option("-y, --yes", "Acknowledge this is an irreversible operation")
|
|
248
|
+
.addOption(
|
|
249
|
+
new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
|
|
250
|
+
)
|
|
251
|
+
.option(
|
|
252
|
+
"--reason <reason>",
|
|
253
|
+
"Reason for the deletion — echoed back in the response so the caller can log it",
|
|
254
|
+
)
|
|
244
255
|
.examples(FILES_DELETE_EXAMPLES)
|
|
245
256
|
.trackedAction(
|
|
246
257
|
processContext,
|
|
@@ -250,6 +261,13 @@ export const registerFilesCommand = (program: Command) => {
|
|
|
250
261
|
fieldName: string,
|
|
251
262
|
options: DeleteOptions,
|
|
252
263
|
) => {
|
|
264
|
+
const reason = requireDestructiveConfirmation(
|
|
265
|
+
options,
|
|
266
|
+
`delete the file in field '${fieldName}'`,
|
|
267
|
+
'Pass --reason "<text>" to record why the file is being deleted.',
|
|
268
|
+
);
|
|
269
|
+
if (reason === null) return;
|
|
270
|
+
|
|
253
271
|
const sdk = await connectOrFail(options.tenant);
|
|
254
272
|
if (!sdk) return;
|
|
255
273
|
|
|
@@ -275,6 +293,7 @@ export const registerFilesCommand = (program: Command) => {
|
|
|
275
293
|
EntityId: entityId,
|
|
276
294
|
RecordId: recordId,
|
|
277
295
|
FieldName: fieldName,
|
|
296
|
+
Reason: reason,
|
|
278
297
|
},
|
|
279
298
|
});
|
|
280
299
|
},
|
|
@@ -1067,6 +1067,9 @@ describe("records delete", () => {
|
|
|
1067
1067
|
"entity-id",
|
|
1068
1068
|
"rec-1",
|
|
1069
1069
|
"rec-2",
|
|
1070
|
+
"--yes",
|
|
1071
|
+
"--reason",
|
|
1072
|
+
"test cleanup",
|
|
1070
1073
|
]);
|
|
1071
1074
|
|
|
1072
1075
|
expect(sdk.entities.deleteRecordsById).toHaveBeenCalledWith(
|
|
@@ -1093,6 +1096,9 @@ describe("records delete", () => {
|
|
|
1093
1096
|
"delete",
|
|
1094
1097
|
"entity-id",
|
|
1095
1098
|
"rec-1",
|
|
1099
|
+
"--yes",
|
|
1100
|
+
"--reason",
|
|
1101
|
+
"test cleanup",
|
|
1096
1102
|
]);
|
|
1097
1103
|
expect(sdk.entities.deleteRecordsById).toHaveBeenCalled();
|
|
1098
1104
|
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
@@ -1119,6 +1125,9 @@ describe("records delete", () => {
|
|
|
1119
1125
|
"delete",
|
|
1120
1126
|
"entity-id",
|
|
1121
1127
|
"rec-1",
|
|
1128
|
+
"--yes",
|
|
1129
|
+
"--reason",
|
|
1130
|
+
"test cleanup",
|
|
1122
1131
|
]);
|
|
1123
1132
|
|
|
1124
1133
|
expect(OutputFormatter.error).toHaveBeenCalledWith(
|
|
@@ -1142,6 +1151,9 @@ describe("records delete", () => {
|
|
|
1142
1151
|
"entity-id",
|
|
1143
1152
|
"rec-1",
|
|
1144
1153
|
"rec-2",
|
|
1154
|
+
"--yes",
|
|
1155
|
+
"--reason",
|
|
1156
|
+
"test cleanup",
|
|
1145
1157
|
]);
|
|
1146
1158
|
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
1147
1159
|
expect.objectContaining({
|
|
@@ -1155,6 +1167,78 @@ describe("records delete", () => {
|
|
|
1155
1167
|
);
|
|
1156
1168
|
expect(process.exitCode).toBe(1);
|
|
1157
1169
|
});
|
|
1170
|
+
|
|
1171
|
+
it("should accept --confirm as a deprecated alias for --yes", async () => {
|
|
1172
|
+
const sdk = mockSdk();
|
|
1173
|
+
|
|
1174
|
+
const program = buildProgram();
|
|
1175
|
+
await program.parseAsync([
|
|
1176
|
+
"node",
|
|
1177
|
+
"test",
|
|
1178
|
+
"records",
|
|
1179
|
+
"delete",
|
|
1180
|
+
"entity-id",
|
|
1181
|
+
"rec-1",
|
|
1182
|
+
"--confirm",
|
|
1183
|
+
"--reason",
|
|
1184
|
+
"test cleanup",
|
|
1185
|
+
]);
|
|
1186
|
+
|
|
1187
|
+
expect(sdk.entities.deleteRecordsById).toHaveBeenCalledWith(
|
|
1188
|
+
"entity-id",
|
|
1189
|
+
["rec-1"],
|
|
1190
|
+
);
|
|
1191
|
+
expect(OutputFormatter.success).toHaveBeenCalledWith(
|
|
1192
|
+
expect.objectContaining({
|
|
1193
|
+
Result: "Success",
|
|
1194
|
+
Code: "RecordsDeleted",
|
|
1195
|
+
}),
|
|
1196
|
+
);
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
it("should require confirmation: without --yes, exit 1 and SDK not called", async () => {
|
|
1200
|
+
const sdk = mockSdk();
|
|
1201
|
+
|
|
1202
|
+
const program = buildProgram();
|
|
1203
|
+
await program.parseAsync([
|
|
1204
|
+
"node",
|
|
1205
|
+
"test",
|
|
1206
|
+
"records",
|
|
1207
|
+
"delete",
|
|
1208
|
+
"entity-id",
|
|
1209
|
+
"rec-1",
|
|
1210
|
+
"--reason",
|
|
1211
|
+
"test cleanup",
|
|
1212
|
+
]);
|
|
1213
|
+
|
|
1214
|
+
expect(sdk.entities.deleteRecordsById).not.toHaveBeenCalled();
|
|
1215
|
+
expect(OutputFormatter.success).not.toHaveBeenCalled();
|
|
1216
|
+
expect(process.exitCode).toBe(1);
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
it("should require --reason when confirmation is present", async () => {
|
|
1220
|
+
const sdk = mockSdk();
|
|
1221
|
+
|
|
1222
|
+
const program = buildProgram();
|
|
1223
|
+
await program.parseAsync([
|
|
1224
|
+
"node",
|
|
1225
|
+
"test",
|
|
1226
|
+
"records",
|
|
1227
|
+
"delete",
|
|
1228
|
+
"entity-id",
|
|
1229
|
+
"rec-1",
|
|
1230
|
+
"--yes",
|
|
1231
|
+
]);
|
|
1232
|
+
|
|
1233
|
+
expect(sdk.entities.deleteRecordsById).not.toHaveBeenCalled();
|
|
1234
|
+
expect(OutputFormatter.error).toHaveBeenCalledWith(
|
|
1235
|
+
expect.objectContaining({
|
|
1236
|
+
Result: "Failure",
|
|
1237
|
+
Message: "Reason required for destructive operation",
|
|
1238
|
+
}),
|
|
1239
|
+
);
|
|
1240
|
+
expect(process.exitCode).toBe(1);
|
|
1241
|
+
});
|
|
1158
1242
|
});
|
|
1159
1243
|
|
|
1160
1244
|
describe("records delete client error", () => {
|
|
@@ -2242,6 +2326,9 @@ describe("records — negative scenarios", () => {
|
|
|
2242
2326
|
"delete",
|
|
2243
2327
|
"entity-id",
|
|
2244
2328
|
"nonexistent-record-id",
|
|
2329
|
+
"--yes",
|
|
2330
|
+
"--reason",
|
|
2331
|
+
"test cleanup",
|
|
2245
2332
|
]);
|
|
2246
2333
|
|
|
2247
2334
|
expect(OutputFormatter.error).toHaveBeenCalledWith(
|