@uipath/data-fabric-tool 1.1.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.
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
4
  vi.mock("../utils/sdk-client", () => ({
5
5
  createDataFabricClient: vi.fn(),
6
+ connectOrFail: vi.fn(),
6
7
  }));
7
8
 
8
9
  vi.mock("@uipath/common", async (importOriginal) => {
@@ -27,7 +28,7 @@ vi.mock("@uipath/filesystem", () => ({
27
28
  }));
28
29
 
29
30
  import { OutputFormatter } from "@uipath/common";
30
- import { createDataFabricClient } from "../utils/sdk-client";
31
+ import { connectOrFail } from "../utils/sdk-client";
31
32
  import { registerFilesCommand } from "./files";
32
33
 
33
34
  function mockSdk(overrides: Record<string, unknown> = {}) {
@@ -39,7 +40,7 @@ function mockSdk(overrides: Record<string, unknown> = {}) {
39
40
  ...overrides,
40
41
  },
41
42
  };
42
- vi.mocked(createDataFabricClient).mockResolvedValue(sdk as never);
43
+ vi.mocked(connectOrFail).mockResolvedValue(sdk as never);
43
44
  return sdk;
44
45
  }
45
46
 
@@ -134,22 +135,17 @@ describe("files upload", () => {
134
135
  );
135
136
  });
136
137
 
137
- it("should error when SDK connection fails on upload", async () => {
138
- vi.mocked(createDataFabricClient).mockRejectedValue(
139
- new Error("Not logged in"),
140
- );
138
+ it("should bail when SDK connection fails on upload", async () => {
139
+ const sdk = mockSdk();
140
+ vi.mocked(connectOrFail).mockResolvedValue(undefined);
141
141
  mockFs.readFile.mockResolvedValue(new Uint8Array([1, 2, 3]));
142
142
  const program = buildProgram();
143
143
  await runCommand(
144
144
  program,
145
145
  "files upload entity-1 record-1 attachment --file /tmp/report.pdf",
146
146
  );
147
- expect(OutputFormatter.error).toHaveBeenCalledWith(
148
- expect.objectContaining({
149
- Result: "Failure",
150
- Message: "Error connecting to Data Fabric",
151
- }),
152
- );
147
+ expect(sdk.entities.uploadAttachment).not.toHaveBeenCalled();
148
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
153
149
  });
154
150
 
155
151
  it("should error when upload API fails", async () => {
@@ -218,21 +214,16 @@ describe("files download", () => {
218
214
  );
219
215
  });
220
216
 
221
- it("should error when SDK connection fails", async () => {
222
- vi.mocked(createDataFabricClient).mockRejectedValue(
223
- new Error("Not logged in"),
224
- );
217
+ it("should bail when SDK connection fails", async () => {
218
+ const sdk = mockSdk();
219
+ vi.mocked(connectOrFail).mockResolvedValue(undefined);
225
220
  const program = buildProgram();
226
221
  await runCommand(
227
222
  program,
228
223
  "files download entity-1 record-1 attachment",
229
224
  );
230
- expect(OutputFormatter.error).toHaveBeenCalledWith(
231
- expect.objectContaining({
232
- Result: "Failure",
233
- Message: "Error connecting to Data Fabric",
234
- }),
235
- );
225
+ expect(sdk.entities.downloadAttachment).not.toHaveBeenCalled();
226
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
236
227
  });
237
228
  });
238
229
 
@@ -246,7 +237,31 @@ describe("files delete", () => {
246
237
  it("should delete and return FileDeleted on success", async () => {
247
238
  const sdk = mockSdk();
248
239
  const program = buildProgram();
249
- await runCommand(program, "files delete entity-1 record-1 attachment");
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
+ );
250
265
  expect(sdk.entities.deleteAttachment).toHaveBeenCalledWith(
251
266
  "entity-1",
252
267
  "record-1",
@@ -257,11 +272,26 @@ describe("files delete", () => {
257
272
  );
258
273
  });
259
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
+
260
287
  it("should error when delete API fails", async () => {
261
288
  const sdk = mockSdk();
262
289
  sdk.entities.deleteAttachment.mockRejectedValue(new Error("Forbidden"));
263
290
  const program = buildProgram();
264
- await runCommand(program, "files delete entity-1 record-1 attachment");
291
+ await runCommand(
292
+ program,
293
+ "files delete entity-1 record-1 attachment --yes --reason cleanup",
294
+ );
265
295
  expect(OutputFormatter.error).toHaveBeenCalledWith(
266
296
  expect.objectContaining({
267
297
  Result: "Failure",
@@ -270,18 +300,16 @@ describe("files delete", () => {
270
300
  );
271
301
  });
272
302
 
273
- it("should error when SDK connection fails on delete", async () => {
274
- vi.mocked(createDataFabricClient).mockRejectedValue(
275
- new Error("Not logged in"),
276
- );
303
+ it("should bail when SDK connection fails on delete", async () => {
304
+ const sdk = mockSdk();
305
+ vi.mocked(connectOrFail).mockResolvedValue(undefined);
277
306
  const program = buildProgram();
278
- await runCommand(program, "files delete entity-1 record-1 attachment");
279
- expect(OutputFormatter.error).toHaveBeenCalledWith(
280
- expect.objectContaining({
281
- Result: "Failure",
282
- Message: "Error connecting to Data Fabric",
283
- }),
307
+ await runCommand(
308
+ program,
309
+ "files delete entity-1 record-1 attachment --yes --reason cleanup",
284
310
  );
311
+ expect(sdk.entities.deleteAttachment).not.toHaveBeenCalled();
312
+ expect(OutputFormatter.success).not.toHaveBeenCalled();
285
313
  });
286
314
  });
287
315
 
@@ -8,9 +8,10 @@ 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 { createDataFabricClient } from "../utils/sdk-client";
13
+ import { fail, requireDestructiveConfirmation } from "../utils/output";
14
+ import { connectOrFail } from "../utils/sdk-client";
14
15
 
15
16
  interface UploadOptions {
16
17
  tenant?: string;
@@ -24,6 +25,9 @@ interface DownloadOptions {
24
25
 
25
26
  interface DeleteOptions {
26
27
  tenant?: string;
28
+ yes?: boolean;
29
+ confirm?: boolean;
30
+ reason?: string;
27
31
  }
28
32
 
29
33
  const FILES_UPLOAD_EXAMPLES: CommandExample[] = [
@@ -101,41 +105,21 @@ export const registerFilesCommand = (program: Command) => {
101
105
  options: UploadOptions,
102
106
  ) => {
103
107
  if (!options.file) {
104
- OutputFormatter.error({
105
- Result: RESULTS.Failure,
106
- Message: "A file path is required",
107
- Instructions: "Provide a file path via --file.",
108
- });
109
- processContext.exit(1);
110
- return;
108
+ return fail(
109
+ "A file path is required",
110
+ "Provide a file path via --file.",
111
+ );
111
112
  }
112
113
 
113
- const [clientError, sdk] = await catchError(
114
- createDataFabricClient(options.tenant),
115
- );
116
-
117
- if (clientError) {
118
- OutputFormatter.error({
119
- Result: RESULTS.Failure,
120
- Message: "Error connecting to Data Fabric",
121
- Instructions: await extractErrorMessage(clientError),
122
- });
123
- processContext.exit(1);
124
- return;
125
- }
114
+ const sdk = await connectOrFail(options.tenant);
115
+ if (!sdk) return;
126
116
 
127
117
  const [readError, fileContent] = await catchError(
128
118
  readFileBinary(options.file),
129
119
  );
130
120
 
131
121
  if (readError) {
132
- OutputFormatter.error({
133
- Result: RESULTS.Failure,
134
- Message: "Error reading file",
135
- Instructions: readError.message,
136
- });
137
- processContext.exit(1);
138
- return;
122
+ return fail("Error reading file", readError.message);
139
123
  }
140
124
 
141
125
  const fsInst = getFileSystem();
@@ -154,13 +138,10 @@ export const registerFilesCommand = (program: Command) => {
154
138
  );
155
139
 
156
140
  if (uploadError) {
157
- OutputFormatter.error({
158
- Result: RESULTS.Failure,
159
- Message: `Error uploading file to field '${fieldName}'`,
160
- Instructions: await extractErrorMessage(uploadError),
161
- });
162
- processContext.exit(1);
163
- return;
141
+ return fail(
142
+ `Error uploading file to field '${fieldName}'`,
143
+ await extractErrorMessage(uploadError),
144
+ );
164
145
  }
165
146
 
166
147
  OutputFormatter.success({
@@ -198,19 +179,8 @@ export const registerFilesCommand = (program: Command) => {
198
179
  fieldName: string,
199
180
  options: DownloadOptions,
200
181
  ) => {
201
- const [clientError, sdk] = await catchError(
202
- createDataFabricClient(options.tenant),
203
- );
204
-
205
- if (clientError) {
206
- OutputFormatter.error({
207
- Result: RESULTS.Failure,
208
- Message: "Error connecting to Data Fabric",
209
- Instructions: await extractErrorMessage(clientError),
210
- });
211
- processContext.exit(1);
212
- return;
213
- }
182
+ const sdk = await connectOrFail(options.tenant);
183
+ if (!sdk) return;
214
184
 
215
185
  const [downloadError, blob] = await catchError(
216
186
  sdk.entities.downloadAttachment(
@@ -221,13 +191,10 @@ export const registerFilesCommand = (program: Command) => {
221
191
  );
222
192
 
223
193
  if (downloadError) {
224
- OutputFormatter.error({
225
- Result: RESULTS.Failure,
226
- Message: `Error downloading file from field '${fieldName}'`,
227
- Instructions: await extractErrorMessage(downloadError),
228
- });
229
- processContext.exit(1);
230
- return;
194
+ return fail(
195
+ `Error downloading file from field '${fieldName}'`,
196
+ await extractErrorMessage(downloadError),
197
+ );
231
198
  }
232
199
 
233
200
  const outputPath =
@@ -238,13 +205,10 @@ export const registerFilesCommand = (program: Command) => {
238
205
  );
239
206
 
240
207
  if (bufferError) {
241
- OutputFormatter.error({
242
- Result: RESULTS.Failure,
243
- Message: "Error reading downloaded file content",
244
- Instructions: bufferError.message,
245
- });
246
- processContext.exit(1);
247
- return;
208
+ return fail(
209
+ "Error reading downloaded file content",
210
+ bufferError.message,
211
+ );
248
212
  }
249
213
 
250
214
  const [writeError] = await catchError(
@@ -252,13 +216,10 @@ export const registerFilesCommand = (program: Command) => {
252
216
  );
253
217
 
254
218
  if (writeError) {
255
- OutputFormatter.error({
256
- Result: RESULTS.Failure,
257
- Message: "Error writing downloaded file",
258
- Instructions: writeError.message,
259
- });
260
- processContext.exit(1);
261
- return;
219
+ return fail(
220
+ "Error writing downloaded file",
221
+ writeError.message,
222
+ );
262
223
  }
263
224
 
264
225
  OutputFormatter.success({
@@ -283,6 +244,14 @@ export const registerFilesCommand = (program: Command) => {
283
244
  .addOption(
284
245
  createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
285
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
+ )
286
255
  .examples(FILES_DELETE_EXAMPLES)
287
256
  .trackedAction(
288
257
  processContext,
@@ -292,19 +261,15 @@ export const registerFilesCommand = (program: Command) => {
292
261
  fieldName: string,
293
262
  options: DeleteOptions,
294
263
  ) => {
295
- const [clientError, sdk] = await catchError(
296
- createDataFabricClient(options.tenant),
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.',
297
268
  );
269
+ if (reason === null) return;
298
270
 
299
- if (clientError) {
300
- OutputFormatter.error({
301
- Result: RESULTS.Failure,
302
- Message: "Error connecting to Data Fabric",
303
- Instructions: await extractErrorMessage(clientError),
304
- });
305
- processContext.exit(1);
306
- return;
307
- }
271
+ const sdk = await connectOrFail(options.tenant);
272
+ if (!sdk) return;
308
273
 
309
274
  const [deleteError] = await catchError(
310
275
  sdk.entities.deleteAttachment(
@@ -315,13 +280,10 @@ export const registerFilesCommand = (program: Command) => {
315
280
  );
316
281
 
317
282
  if (deleteError) {
318
- OutputFormatter.error({
319
- Result: RESULTS.Failure,
320
- Message: `Error deleting file from field '${fieldName}'`,
321
- Instructions: await extractErrorMessage(deleteError),
322
- });
323
- processContext.exit(1);
324
- return;
283
+ return fail(
284
+ `Error deleting file from field '${fieldName}'`,
285
+ await extractErrorMessage(deleteError),
286
+ );
325
287
  }
326
288
 
327
289
  OutputFormatter.success({
@@ -331,6 +293,7 @@ export const registerFilesCommand = (program: Command) => {
331
293
  EntityId: entityId,
332
294
  RecordId: recordId,
333
295
  FieldName: fieldName,
296
+ Reason: reason,
334
297
  },
335
298
  });
336
299
  },