@uipath/data-fabric-tool 1.195.0 → 1.197.0-preview.59

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.
@@ -109,6 +109,7 @@ describe("files upload", () => {
109
109
  "record-1",
110
110
  "attachment",
111
111
  expect.any(File),
112
+ undefined,
112
113
  );
113
114
  expect(OutputFormatter.success).toHaveBeenCalledWith(
114
115
  expect.objectContaining({
@@ -132,6 +133,7 @@ describe("files upload", () => {
132
133
  "record-1",
133
134
  "attachment",
134
135
  expect.objectContaining({ name: "upload" }),
136
+ undefined,
135
137
  );
136
138
  });
137
139
 
@@ -237,22 +239,63 @@ describe("files delete", () => {
237
239
  it("should delete and return FileDeleted on success", async () => {
238
240
  const sdk = mockSdk();
239
241
  const program = buildProgram();
240
- await runCommand(program, "files delete entity-1 record-1 attachment");
242
+ await runCommand(
243
+ program,
244
+ "files delete entity-1 record-1 attachment --yes --reason cleanup",
245
+ );
246
+ expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
247
+ "entity-1",
248
+ "record-1",
249
+ "attachment",
250
+ undefined,
251
+ );
252
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
253
+ expect.objectContaining({
254
+ Result: "Success",
255
+ Code: "FileDeleted",
256
+ Data: expect.objectContaining({ Reason: "cleanup" }),
257
+ }),
258
+ );
259
+ });
260
+
261
+ it("should accept --confirm as a deprecated alias for --yes", async () => {
262
+ const sdk = mockSdk();
263
+ const program = buildProgram();
264
+ await runCommand(
265
+ program,
266
+ "files delete entity-1 record-1 attachment --confirm --reason cleanup",
267
+ );
241
268
  expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
242
269
  "entity-1",
243
270
  "record-1",
244
271
  "attachment",
272
+ undefined,
245
273
  );
246
274
  expect(OutputFormatter.success).toHaveBeenCalledWith(
247
275
  expect.objectContaining({ Result: "Success", Code: "FileDeleted" }),
248
276
  );
249
277
  });
250
278
 
279
+ it("should require confirmation: without --yes, exit 1 and SDK not called", async () => {
280
+ const sdk = mockSdk();
281
+ const program = buildProgram();
282
+ await runCommand(
283
+ program,
284
+ "files delete entity-1 record-1 attachment --reason cleanup",
285
+ );
286
+ expect(sdk.entities.deleteAttachment).not.toHaveBeenCalled();
287
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
288
+ expect(process.exitCode).toBe(1);
289
+ });
290
+
251
291
  it("should error when delete API fails", async () => {
252
292
  const sdk = mockSdk();
253
293
  sdk.entities.deleteAttachment.mockRejectedValue(new Error("Forbidden"));
254
294
  const program = buildProgram();
255
- await runCommand(program, "files delete entity-1 record-1 attachment");
295
+ await runCommand(
296
+ program,
297
+ "files delete entity-1 record-1 attachment --yes --reason cleanup",
298
+ );
256
299
  expect(OutputFormatter.error).toHaveBeenCalledWith(
257
300
  expect.objectContaining({
258
301
  Result: "Failure",
@@ -265,7 +308,10 @@ describe("files delete", () => {
265
308
  const sdk = mockSdk();
266
309
  vi.mocked(connectOrFail).mockResolvedValue(undefined);
267
310
  const program = buildProgram();
268
- await runCommand(program, "files delete entity-1 record-1 attachment");
311
+ await runCommand(
312
+ program,
313
+ "files delete entity-1 record-1 attachment --yes --reason cleanup",
314
+ );
269
315
  expect(sdk.entities.deleteAttachment).not.toHaveBeenCalled();
270
316
  expect(OutputFormatter.success).not.toHaveBeenCalled();
271
317
  });
@@ -328,3 +374,62 @@ describe("files download edge cases", () => {
328
374
  );
329
375
  });
330
376
  });
377
+
378
+ describe("files --folder-key forwarding", () => {
379
+ beforeEach(() => {
380
+ vi.clearAllMocks();
381
+ vi.mocked(OutputFormatter.success).mockReset();
382
+ vi.mocked(OutputFormatter.error).mockReset();
383
+ });
384
+
385
+ it("should forward --folder-key to uploadAttachment", async () => {
386
+ const sdk = mockSdk();
387
+ mockFs.readFile.mockResolvedValue(new Uint8Array([1, 2, 3]));
388
+ const program = buildProgram();
389
+ await runCommand(
390
+ program,
391
+ "files upload entity-1 record-1 attachment --file /tmp/report.pdf --folder-key folder-guid-1",
392
+ );
393
+ expect(sdk.entities.uploadAttachment).toHaveBeenCalledWith(
394
+ "entity-1",
395
+ "record-1",
396
+ "attachment",
397
+ expect.any(File),
398
+ { folderKey: "folder-guid-1" },
399
+ );
400
+ });
401
+
402
+ it("should forward --folder-key to downloadAttachment", async () => {
403
+ const blob = new Blob(["data"]);
404
+ const sdk = mockSdk({
405
+ downloadAttachment: vi.fn().mockResolvedValue(blob),
406
+ });
407
+ mockFs.writeFile.mockResolvedValue(undefined);
408
+ const program = buildProgram();
409
+ await runCommand(
410
+ program,
411
+ "files download entity-1 record-1 attachment --folder-key folder-guid-2",
412
+ );
413
+ expect(sdk.entities.downloadAttachment).toHaveBeenCalledWith(
414
+ "entity-1",
415
+ "record-1",
416
+ "attachment",
417
+ { folderKey: "folder-guid-2" },
418
+ );
419
+ });
420
+
421
+ it("should forward --folder-key to deleteAttachment", async () => {
422
+ const sdk = mockSdk();
423
+ const program = buildProgram();
424
+ await runCommand(
425
+ program,
426
+ "files delete entity-1 record-1 attachment --yes --reason cleanup --folder-key folder-guid-3",
427
+ );
428
+ expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
429
+ "entity-1",
430
+ "record-1",
431
+ "attachment",
432
+ { folderKey: "folder-guid-3" },
433
+ );
434
+ });
435
+ });
@@ -8,23 +8,29 @@ import {
8
8
  RESULTS,
9
9
  } from "@uipath/common";
10
10
  import { getFileSystem } from "@uipath/filesystem";
11
- import type { Command } from "commander";
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 {
17
17
  tenant?: string;
18
18
  file?: string;
19
+ folderKey?: string;
19
20
  }
20
21
 
21
22
  interface DownloadOptions {
22
23
  tenant?: string;
23
24
  destination?: string;
25
+ folderKey?: string;
24
26
  }
25
27
 
26
28
  interface DeleteOptions {
27
29
  tenant?: string;
30
+ yes?: boolean;
31
+ confirm?: boolean;
32
+ reason?: string;
33
+ folderKey?: string;
28
34
  }
29
35
 
30
36
  const FILES_UPLOAD_EXAMPLES: CommandExample[] = [
@@ -92,6 +98,10 @@ export const registerFilesCommand = (program: Command) => {
92
98
  createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
93
99
  )
94
100
  .option("-f, --file <path>", "Path to the file to upload")
101
+ .option(
102
+ "--folder-key <key>",
103
+ "Folder key (GUID) of the folder containing the entity (for folder-scoped entities)",
104
+ )
95
105
  .examples(FILES_UPLOAD_EXAMPLES)
96
106
  .trackedAction(
97
107
  processContext,
@@ -131,6 +141,9 @@ export const registerFilesCommand = (program: Command) => {
131
141
  [fileContent as Uint8Array<ArrayBuffer>],
132
142
  fileName,
133
143
  ),
144
+ options.folderKey !== undefined
145
+ ? { folderKey: options.folderKey }
146
+ : undefined,
134
147
  ),
135
148
  );
136
149
 
@@ -167,6 +180,10 @@ export const registerFilesCommand = (program: Command) => {
167
180
  "--destination <path>",
168
181
  "Output file path (defaults to <record-id>_<field-name>.bin)",
169
182
  )
183
+ .option(
184
+ "--folder-key <key>",
185
+ "Folder key (GUID) of the folder containing the entity (for folder-scoped entities)",
186
+ )
170
187
  .examples(FILES_DOWNLOAD_EXAMPLES)
171
188
  .trackedAction(
172
189
  processContext,
@@ -184,6 +201,9 @@ export const registerFilesCommand = (program: Command) => {
184
201
  entityId,
185
202
  recordId,
186
203
  fieldName,
204
+ options.folderKey !== undefined
205
+ ? { folderKey: options.folderKey }
206
+ : undefined,
187
207
  ),
188
208
  );
189
209
 
@@ -241,6 +261,18 @@ export const registerFilesCommand = (program: Command) => {
241
261
  .addOption(
242
262
  createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
243
263
  )
264
+ .option("-y, --yes", "Acknowledge this is an irreversible operation")
265
+ .addOption(
266
+ new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
267
+ )
268
+ .option(
269
+ "--reason <reason>",
270
+ "Reason for the deletion — echoed back in the response so the caller can log it",
271
+ )
272
+ .option(
273
+ "--folder-key <key>",
274
+ "Folder key (GUID) of the folder containing the entity (for folder-scoped entities)",
275
+ )
244
276
  .examples(FILES_DELETE_EXAMPLES)
245
277
  .trackedAction(
246
278
  processContext,
@@ -250,6 +282,13 @@ export const registerFilesCommand = (program: Command) => {
250
282
  fieldName: string,
251
283
  options: DeleteOptions,
252
284
  ) => {
285
+ const reason = requireDestructiveConfirmation(
286
+ options,
287
+ `delete the file in field '${fieldName}'`,
288
+ 'Pass --reason "<text>" to record why the file is being deleted.',
289
+ );
290
+ if (reason === null) return;
291
+
253
292
  const sdk = await connectOrFail(options.tenant);
254
293
  if (!sdk) return;
255
294
 
@@ -258,6 +297,9 @@ export const registerFilesCommand = (program: Command) => {
258
297
  entityId,
259
298
  recordId,
260
299
  fieldName,
300
+ options.folderKey !== undefined
301
+ ? { folderKey: options.folderKey }
302
+ : undefined,
261
303
  ),
262
304
  );
263
305
 
@@ -275,6 +317,7 @@ export const registerFilesCommand = (program: Command) => {
275
317
  EntityId: entityId,
276
318
  RecordId: recordId,
277
319
  FieldName: fieldName,
320
+ Reason: reason,
278
321
  },
279
322
  });
280
323
  },