@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.
@@ -15,9 +15,10 @@ import type {
15
15
  RawEntityGetResponse,
16
16
  } from "@uipath/uipath-typescript";
17
17
  import { EntityFieldDataType } from "@uipath/uipath-typescript";
18
- import type { Command } from "commander";
18
+ import { type Command, Option } from "commander";
19
19
  import { readJsonInput } from "../utils/input";
20
- import { createDataFabricClient } from "../utils/sdk-client";
20
+ import { fail, requireDestructiveConfirmation } from "../utils/output";
21
+ import { connectOrFail } from "../utils/sdk-client";
21
22
 
22
23
  interface ListOptions {
23
24
  tenant?: string;
@@ -38,40 +39,39 @@ interface UpdateEntityOptions {
38
39
  tenant?: string;
39
40
  file?: string;
40
41
  body?: string;
42
+ yes?: boolean;
41
43
  confirm?: boolean;
42
44
  reason?: string;
43
45
  }
44
46
 
45
47
  interface DeleteOptions {
46
48
  tenant?: string;
49
+ yes?: boolean;
47
50
  confirm?: boolean;
48
51
  reason?: string;
49
52
  }
50
53
 
51
54
  const ENTITIES_LIST_EXAMPLES: CommandExample[] = [
52
55
  {
53
- Description: "List Data Fabric entities",
56
+ Description: "List Data Fabric entities.",
54
57
  Command: "uip df entities list --native-only",
55
58
  Output: {
56
59
  Code: "EntityList",
57
60
  Data: [
58
61
  {
59
- Name: "Invoice",
60
- DisplayName: "Invoice",
61
- ID: "a1b2c3d4-0000-0000-0000-000000000001",
62
- Type: "Standard",
63
- Source: "Native",
64
- Description: "Invoice records",
65
- FieldCount: 8,
66
- },
67
- {
68
- Name: "Customer",
69
- DisplayName: "Customer",
70
- ID: "a1b2c3d4-0000-0000-0000-000000000002",
71
- Type: "Standard",
72
- Source: "Native",
73
- Description: "Customer records",
74
- FieldCount: 6,
62
+ id: "a1b2c3d4-0000-0000-0000-000000000001",
63
+ name: "Invoice",
64
+ displayName: "Invoice",
65
+ entityType: "Standard",
66
+ description: "Invoice records",
67
+ isRbacEnabled: false,
68
+ fields: [
69
+ {
70
+ id: "f1000000-0000-0000-0000-000000000001",
71
+ name: "amount",
72
+ },
73
+ ],
74
+ externalFields: [],
75
75
  },
76
76
  ],
77
77
  },
@@ -80,34 +80,35 @@ const ENTITIES_LIST_EXAMPLES: CommandExample[] = [
80
80
 
81
81
  const ENTITIES_GET_EXAMPLES: CommandExample[] = [
82
82
  {
83
- Description: "Get entity schema by ID",
83
+ Description: "Get entity schema by ID.",
84
84
  Command: "uip df entities get a1b2c3d4-0000-0000-0000-000000000001",
85
85
  Output: {
86
86
  Code: "EntitySchema",
87
87
  Data: {
88
- Name: "Invoice",
89
- DisplayName: "Invoice",
90
- ID: "a1b2c3d4-0000-0000-0000-000000000001",
91
- Type: "Standard",
92
- Description: "Invoice records",
93
- Fields: [
94
- {
95
- ID: "f1000000-0000-0000-0000-000000000001",
96
- Name: "id",
97
- DisplayName: "ID",
98
- Type: "Guid",
99
- Required: true,
100
- PrimaryKey: true,
101
- System: true,
102
- },
88
+ id: "a1b2c3d4-0000-0000-0000-000000000001",
89
+ name: "Invoice",
90
+ displayName: "Invoice",
91
+ entityType: "Standard",
92
+ description: "Invoice records",
93
+ isRbacEnabled: false,
94
+ fields: [
103
95
  {
104
- ID: "f1000000-0000-0000-0000-000000000002",
105
- Name: "amount",
106
- DisplayName: "Amount",
107
- Type: "Decimal",
108
- Required: true,
109
- PrimaryKey: false,
110
- System: false,
96
+ id: "f1000000-0000-0000-0000-000000000002",
97
+ name: "amount",
98
+ displayName: "Amount",
99
+ fieldDataType: { name: "DECIMAL" },
100
+ sqlType: {
101
+ name: "DECIMAL",
102
+ decimalPrecision: 2,
103
+ minValue: 0,
104
+ maxValue: 999999,
105
+ },
106
+ isRequired: true,
107
+ isUnique: false,
108
+ isEncrypted: false,
109
+ isRbacEnabled: false,
110
+ isPrimaryKey: false,
111
+ isSystemField: false,
111
112
  },
112
113
  ],
113
114
  },
@@ -134,12 +135,12 @@ const ENTITIES_DELETE_EXAMPLES: CommandExample[] = [
134
135
  const ENTITIES_CREATE_EXAMPLES: CommandExample[] = [
135
136
  {
136
137
  Description:
137
- "Create an entity with a choice-set field (single-select and multi-select) and a relationship to another entity. " +
138
+ "Create an entity with choice-set, relationship, and file fields. " +
138
139
  "CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE require 'choiceSetId' (from 'df choice-sets list'). " +
139
- "RELATIONSHIP requires 'referenceEntityName' (target entity) and 'referenceFieldName' (the field used to resolve joins on read; e.g. 'Id' or a unique key like 'Email'). " +
140
- "Note: regardless of 'referenceFieldName', a relationship column on a record always stores the target record's UUID 'Id' — see 'df records insert' for how to write the value.",
140
+ "RELATIONSHIP and FILE both require '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>'). " +
141
+ "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.",
141
142
  Command:
142
- '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","referenceEntityName":"Employee","referenceFieldName":"Id","isRequired":true}]}\'',
143
+ '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","referenceEntityId":"a1b2c3d4-0000-0000-0000-000000000099","referenceFieldId":"f1000000-0000-0000-0000-000000000199"}]}\'',
143
144
  Output: {
144
145
  Code: "EntityCreated",
145
146
  Data: { ID: "a1b2c3d4-0000-0000-0000-000000000004" },
@@ -196,54 +197,25 @@ export const registerEntitiesCommand = (program: Command) => {
196
197
  )
197
198
  .examples(ENTITIES_LIST_EXAMPLES)
198
199
  .trackedAction(processContext, async (options: ListOptions) => {
199
- const [clientError, sdk] = await catchError(
200
- createDataFabricClient(options.tenant),
201
- );
202
-
203
- if (clientError) {
204
- OutputFormatter.error({
205
- Result: RESULTS.Failure,
206
- Message: "Error connecting to Data Fabric",
207
- Instructions: await extractErrorMessage(clientError),
208
- });
209
- processContext.exit(1);
210
- return;
211
- }
200
+ const sdk = await connectOrFail(options.tenant);
201
+ if (!sdk) return;
212
202
 
213
203
  const [listError, result] = await catchError(sdk.entities.getAll());
214
204
 
215
205
  if (listError) {
216
- OutputFormatter.error({
217
- Result: RESULTS.Failure,
218
- Message: "Error listing entities",
219
- Instructions: await extractErrorMessage(listError),
220
- });
221
- processContext.exit(1);
222
- return;
206
+ return fail(
207
+ "Error listing entities",
208
+ await extractErrorMessage(listError),
209
+ );
223
210
  }
224
211
 
225
- const entityList = (result ?? [])
226
- .map((e) => {
227
- const entity = e as unknown as RawEntityGetResponse;
228
- const externalFields: ExternalSourceFields[] =
229
- entity.externalFields ?? [];
230
- const isNative = externalFields.length === 0;
231
- const connectorName =
232
- externalFields[0]?.externalConnectionDetail
233
- ?.connectorName;
234
- return {
235
- Name: entity.name,
236
- DisplayName: entity.displayName || entity.name,
237
- ID: entity.id,
238
- Type: entity.entityType,
239
- Source: isNative
240
- ? "Native"
241
- : `Federated${connectorName ? ` (${connectorName})` : ""}`,
242
- Description: entity.description || "",
243
- FieldCount: entity.fields.length,
244
- };
245
- })
246
- .filter((e) => !options.nativeOnly || e.Source === "Native");
212
+ const entityList = (result ?? []).filter((e) => {
213
+ if (!options.nativeOnly) return true;
214
+ const entity = e as unknown as RawEntityGetResponse;
215
+ const externalFields: ExternalSourceFields[] =
216
+ entity.externalFields ?? [];
217
+ return externalFields.length === 0;
218
+ });
247
219
 
248
220
  OutputFormatter.success({
249
221
  Result: RESULTS.Success,
@@ -263,68 +235,33 @@ export const registerEntitiesCommand = (program: Command) => {
263
235
  .trackedAction(
264
236
  processContext,
265
237
  async (id: string, options: GetOptions) => {
266
- const [clientError, sdk] = await catchError(
267
- createDataFabricClient(options.tenant),
268
- );
269
-
270
- if (clientError) {
271
- OutputFormatter.error({
272
- Result: RESULTS.Failure,
273
- Message: "Error connecting to Data Fabric",
274
- Instructions: await extractErrorMessage(clientError),
275
- });
276
- processContext.exit(1);
277
- return;
278
- }
238
+ const sdk = await connectOrFail(options.tenant);
239
+ if (!sdk) return;
279
240
 
280
241
  const [getError, entity] = await catchError(
281
242
  sdk.entities.getById(id),
282
243
  );
283
244
 
284
245
  if (getError) {
285
- OutputFormatter.error({
286
- Result: RESULTS.Failure,
287
- Message: `Error getting entity schema '${id}'`,
288
- Instructions: await extractErrorMessage(getError),
289
- });
290
- processContext.exit(1);
291
- return;
246
+ return fail(
247
+ `Error getting entity schema '${id}'`,
248
+ await extractErrorMessage(getError),
249
+ );
292
250
  }
293
251
 
294
252
  const e = entity as unknown as RawEntityGetResponse;
295
253
 
296
254
  if (!e?.fields) {
297
- OutputFormatter.error({
298
- Result: RESULTS.Failure,
299
- Message: `Entity '${id}' not found`,
300
- Instructions:
301
- "Verify the entity ID exists. Use 'df entities list' to see available entities.",
302
- });
303
- processContext.exit(1);
304
- return;
255
+ return fail(
256
+ `Entity '${id}' not found`,
257
+ "Verify the entity ID exists. Use 'df entities list' to see available entities.",
258
+ );
305
259
  }
306
260
 
307
- const fields = e.fields.map((field) => ({
308
- ID: field.id,
309
- Name: field.name,
310
- DisplayName: field.displayName,
311
- Type: field.fieldDataType?.name,
312
- Required: field.isRequired,
313
- PrimaryKey: field.isPrimaryKey,
314
- System: field.isSystemField,
315
- }));
316
-
317
261
  OutputFormatter.success({
318
262
  Result: RESULTS.Success,
319
263
  Code: "EntitySchema",
320
- Data: {
321
- Name: e.name,
322
- DisplayName: e.displayName || e.name,
323
- ID: e.id,
324
- Type: e.entityType,
325
- Description: e.description || "",
326
- Fields: fields,
327
- },
264
+ Data: e,
328
265
  });
329
266
  },
330
267
  );
@@ -343,7 +280,10 @@ export const registerEntitiesCommand = (program: Command) => {
343
280
  "-f, --file <path>",
344
281
  "Path to JSON file with entity definition (fields array required; displayName, description, isRbacEnabled optional)",
345
282
  )
346
- .option("--body <json>", "Inline JSON entity definition")
283
+ .option(
284
+ "--body <json>",
285
+ "Inline JSON entity definition (use `-` to read from stdin)",
286
+ )
347
287
  .examples(ENTITIES_CREATE_EXAMPLES)
348
288
  .trackedAction(
349
289
  processContext,
@@ -357,13 +297,10 @@ export const registerEntitiesCommand = (program: Command) => {
357
297
  );
358
298
 
359
299
  if (parseError) {
360
- OutputFormatter.error({
361
- Result: RESULTS.Failure,
362
- Message: "Error parsing entity definition",
363
- Instructions: parseError.message,
364
- });
365
- processContext.exit(1);
366
- return;
300
+ return fail(
301
+ "Error parsing entity definition",
302
+ parseError.message,
303
+ );
367
304
  }
368
305
 
369
306
  if (
@@ -371,27 +308,18 @@ export const registerEntitiesCommand = (program: Command) => {
371
308
  parsed === null ||
372
309
  Array.isArray(parsed)
373
310
  ) {
374
- OutputFormatter.error({
375
- Result: RESULTS.Failure,
376
- Message: "Entity definition must be a JSON object",
377
- Instructions:
378
- "Provide a JSON object with a 'fields' array and optional displayName, description, isRbacEnabled.",
379
- });
380
- processContext.exit(1);
381
- return;
311
+ return fail(
312
+ "Entity definition must be a JSON object",
313
+ "Provide a JSON object with a 'fields' array and optional displayName, description, isRbacEnabled.",
314
+ );
382
315
  }
383
316
 
384
317
  const definition = parsed as Record<string, unknown>;
385
318
  if (!Array.isArray(definition.fields)) {
386
- OutputFormatter.error({
387
- Result: RESULTS.Failure,
388
- Message:
389
- "Entity definition must include a 'fields' array",
390
- Instructions:
391
- "Provide a JSON object with a 'fields' array containing field definitions.",
392
- });
393
- processContext.exit(1);
394
- return;
319
+ return fail(
320
+ "Entity definition must include a 'fields' array",
321
+ "Provide a JSON object with a 'fields' array containing field definitions.",
322
+ );
395
323
  }
396
324
 
397
325
  const hasInvalidField = (definition.fields as unknown[]).some(
@@ -402,39 +330,21 @@ export const registerEntitiesCommand = (program: Command) => {
402
330
  "string",
403
331
  );
404
332
  if (hasInvalidField) {
405
- OutputFormatter.error({
406
- Result: RESULTS.Failure,
407
- Message: "Each field must include a 'fieldName' string",
408
- Instructions:
409
- 'Example: {"fieldName":"title","type":"STRING"}',
410
- });
411
- processContext.exit(1);
412
- return;
333
+ return fail(
334
+ "Each field must include a 'fieldName' string",
335
+ 'Example: {"fieldName":"title","type":"STRING"}',
336
+ );
413
337
  }
414
338
 
415
339
  if (hasInvalidFieldType(definition.fields as unknown[])) {
416
- OutputFormatter.error({
417
- Result: RESULTS.Failure,
418
- Message: "Invalid field type in fields",
419
- Instructions: `Valid types: ${VALID_FIELD_TYPES_LIST}`,
420
- });
421
- processContext.exit(1);
422
- return;
340
+ return fail(
341
+ "Invalid field type in fields",
342
+ `Valid types: ${VALID_FIELD_TYPES_LIST}`,
343
+ );
423
344
  }
424
345
 
425
- const [clientError, sdk] = await catchError(
426
- createDataFabricClient(options.tenant),
427
- );
428
-
429
- if (clientError) {
430
- OutputFormatter.error({
431
- Result: RESULTS.Failure,
432
- Message: "Error connecting to Data Fabric",
433
- Instructions: await extractErrorMessage(clientError),
434
- });
435
- processContext.exit(1);
436
- return;
437
- }
346
+ const sdk = await connectOrFail(options.tenant);
347
+ if (!sdk) return;
438
348
 
439
349
  const createOpts = {
440
350
  ...(definition.displayName !== undefined && {
@@ -460,13 +370,10 @@ export const registerEntitiesCommand = (program: Command) => {
460
370
  );
461
371
 
462
372
  if (createError) {
463
- OutputFormatter.error({
464
- Result: RESULTS.Failure,
465
- Message: "Error creating entity",
466
- Instructions: await extractErrorMessage(createError),
467
- });
468
- processContext.exit(1);
469
- return;
373
+ return fail(
374
+ "Error creating entity",
375
+ await extractErrorMessage(createError),
376
+ );
470
377
  }
471
378
 
472
379
  OutputFormatter.success({
@@ -488,11 +395,17 @@ export const registerEntitiesCommand = (program: Command) => {
488
395
  "-f, --file <path>",
489
396
  "Path to JSON file with update options (addFields, updateFields, removeFields, displayName, description, isRbacEnabled)",
490
397
  )
491
- .option("--body <json>", "Inline JSON update options")
492
398
  .option(
493
- "--confirm",
399
+ "--body <json>",
400
+ "Inline JSON update options (use `-` to read from stdin)",
401
+ )
402
+ .option(
403
+ "-y, --yes",
494
404
  "Required when 'removeFields' is non-empty — acknowledges the field deletion is irreversible",
495
405
  )
406
+ .addOption(
407
+ new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
408
+ )
496
409
  .option(
497
410
  "--reason <reason>",
498
411
  "Required when 'removeFields' is non-empty — echoed back in the response so the caller can log it",
@@ -509,13 +422,10 @@ export const registerEntitiesCommand = (program: Command) => {
509
422
  );
510
423
 
511
424
  if (parseError) {
512
- OutputFormatter.error({
513
- Result: RESULTS.Failure,
514
- Message: "Error parsing update options",
515
- Instructions: parseError.message,
516
- });
517
- processContext.exit(1);
518
- return;
425
+ return fail(
426
+ "Error parsing update options",
427
+ parseError.message,
428
+ );
519
429
  }
520
430
 
521
431
  if (
@@ -523,14 +433,10 @@ export const registerEntitiesCommand = (program: Command) => {
523
433
  parsed === null ||
524
434
  Array.isArray(parsed)
525
435
  ) {
526
- OutputFormatter.error({
527
- Result: RESULTS.Failure,
528
- Message: "Update options must be a JSON object",
529
- Instructions:
530
- "Provide a JSON object with addFields, updateFields, removeFields, displayName, description, or isRbacEnabled.",
531
- });
532
- processContext.exit(1);
533
- return;
436
+ return fail(
437
+ "Update options must be a JSON object",
438
+ "Provide a JSON object with addFields, updateFields, removeFields, displayName, description, or isRbacEnabled.",
439
+ );
534
440
  }
535
441
 
536
442
  const input = parsed as Record<string, unknown>;
@@ -539,42 +445,30 @@ export const registerEntitiesCommand = (program: Command) => {
539
445
  input.addFields !== undefined &&
540
446
  !Array.isArray(input.addFields)
541
447
  ) {
542
- OutputFormatter.error({
543
- Result: RESULTS.Failure,
544
- Message: "'addFields' must be an array",
545
- Instructions:
546
- 'Example: {"addFields":[{"fieldName":"title","type":"STRING"}]}',
547
- });
548
- processContext.exit(1);
549
- return;
448
+ return fail(
449
+ "'addFields' must be an array",
450
+ 'Example: {"addFields":[{"fieldName":"title","type":"STRING"}]}',
451
+ );
550
452
  }
551
453
 
552
454
  if (
553
455
  input.updateFields !== undefined &&
554
456
  !Array.isArray(input.updateFields)
555
457
  ) {
556
- OutputFormatter.error({
557
- Result: RESULTS.Failure,
558
- Message: "'updateFields' must be an array",
559
- Instructions:
560
- 'Example: {"updateFields":[{"id":"<fieldId>","displayName":"New Name"}]}',
561
- });
562
- processContext.exit(1);
563
- return;
458
+ return fail(
459
+ "'updateFields' must be an array",
460
+ 'Example: {"updateFields":[{"id":"<fieldId>","displayName":"New Name"}]}',
461
+ );
564
462
  }
565
463
 
566
464
  if (
567
465
  input.removeFields !== undefined &&
568
466
  !Array.isArray(input.removeFields)
569
467
  ) {
570
- OutputFormatter.error({
571
- Result: RESULTS.Failure,
572
- Message: "'removeFields' must be an array",
573
- Instructions:
574
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
575
- });
576
- processContext.exit(1);
577
- return;
468
+ return fail(
469
+ "'removeFields' must be an array",
470
+ 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
471
+ );
578
472
  }
579
473
 
580
474
  let trimmedReason: string | undefined;
@@ -591,41 +485,19 @@ export const registerEntitiesCommand = (program: Command) => {
591
485
  return typeof fn !== "string" || fn.trim() === "";
592
486
  });
593
487
  if (hasInvalidRemove) {
594
- OutputFormatter.error({
595
- Result: RESULTS.Failure,
596
- Message:
597
- "Each field in removeFields must include a non-empty 'fieldName' string",
598
- Instructions:
599
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
600
- });
601
- processContext.exit(1);
602
- return;
603
- }
604
-
605
- if (options.confirm !== true) {
606
- OutputFormatter.error({
607
- Result: RESULTS.Failure,
608
- Message:
609
- "Confirmation required for destructive operation",
610
- Instructions:
611
- "Pass --confirm to acknowledge field deletion is irreversible.",
612
- });
613
- processContext.exit(1);
614
- return;
488
+ return fail(
489
+ "Each field in removeFields must include a non-empty 'fieldName' string",
490
+ 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
491
+ );
615
492
  }
616
493
 
617
- trimmedReason = options.reason?.trim();
618
- if (trimmedReason === undefined || trimmedReason === "") {
619
- OutputFormatter.error({
620
- Result: RESULTS.Failure,
621
- Message:
622
- "Reason required for destructive operation",
623
- Instructions:
624
- 'Pass --reason "<text>" to record why fields are being removed.',
625
- });
626
- processContext.exit(1);
627
- return;
628
- }
494
+ const reason = requireDestructiveConfirmation(
495
+ options,
496
+ `remove fields from entity '${id}'`,
497
+ 'Pass --reason "<text>" to record why fields are being removed.',
498
+ );
499
+ if (reason === null) return;
500
+ trimmedReason = reason;
629
501
 
630
502
  removedFieldNames = (
631
503
  removeFields as { fieldName: string }[]
@@ -642,25 +514,17 @@ export const registerEntitiesCommand = (program: Command) => {
642
514
  },
643
515
  );
644
516
  if (hasInvalidField) {
645
- OutputFormatter.error({
646
- Result: RESULTS.Failure,
647
- Message:
648
- "Each field in addFields must include a non-empty 'fieldName' string",
649
- Instructions:
650
- 'Example: {"fieldName":"title","type":"STRING"}',
651
- });
652
- processContext.exit(1);
653
- return;
517
+ return fail(
518
+ "Each field in addFields must include a non-empty 'fieldName' string",
519
+ 'Example: {"fieldName":"title","type":"STRING"}',
520
+ );
654
521
  }
655
522
 
656
523
  if (hasInvalidFieldType(input.addFields as unknown[])) {
657
- OutputFormatter.error({
658
- Result: RESULTS.Failure,
659
- Message: "Invalid field type in addFields",
660
- Instructions: `Valid types: ${VALID_FIELD_TYPES_LIST}`,
661
- });
662
- processContext.exit(1);
663
- return;
524
+ return fail(
525
+ "Invalid field type in addFields",
526
+ `Valid types: ${VALID_FIELD_TYPES_LIST}`,
527
+ );
664
528
  }
665
529
 
666
530
  const addNames = (
@@ -670,14 +534,10 @@ export const registerEntitiesCommand = (program: Command) => {
670
534
  (name, i) => addNames.indexOf(name) !== i,
671
535
  );
672
536
  if (duplicateAdd !== undefined) {
673
- OutputFormatter.error({
674
- Result: RESULTS.Failure,
675
- Message: `Duplicate fieldName '${duplicateAdd}' in addFields`,
676
- Instructions:
677
- "Each entry in addFields must have a unique fieldName.",
678
- });
679
- processContext.exit(1);
680
- return;
537
+ return fail(
538
+ `Duplicate fieldName '${duplicateAdd}' in addFields`,
539
+ "Each entry in addFields must have a unique fieldName.",
540
+ );
681
541
  }
682
542
 
683
543
  if (removedFieldNames.length > 0) {
@@ -685,14 +545,10 @@ export const registerEntitiesCommand = (program: Command) => {
685
545
  removedFieldNames.includes(name),
686
546
  );
687
547
  if (conflict !== undefined) {
688
- OutputFormatter.error({
689
- Result: RESULTS.Failure,
690
- Message: `Field '${conflict}' appears in both addFields and removeFields`,
691
- Instructions:
692
- "A single update cannot add and remove the same field. Split into two calls if you need to recreate it.",
693
- });
694
- processContext.exit(1);
695
- return;
548
+ return fail(
549
+ `Field '${conflict}' appears in both addFields and removeFields`,
550
+ "A single update cannot add and remove the same field. Split into two calls if you need to recreate it.",
551
+ );
696
552
  }
697
553
  }
698
554
  }
@@ -706,31 +562,15 @@ export const registerEntitiesCommand = (program: Command) => {
706
562
  return typeof fid !== "string" || fid.trim() === "";
707
563
  });
708
564
  if (hasInvalidField) {
709
- OutputFormatter.error({
710
- Result: RESULTS.Failure,
711
- Message:
712
- "Each field in updateFields must include a non-empty 'id' string",
713
- Instructions:
714
- 'Use \'df entities get <entityId>\' to find field IDs. Example: {"id":"<fieldId>","displayName":"Total Amount","isRequired":true}',
715
- });
716
- processContext.exit(1);
717
- return;
565
+ return fail(
566
+ "Each field in updateFields must include a non-empty 'id' string",
567
+ 'Use \'df entities get <entityId>\' to find field IDs. Example: {"id":"<fieldId>","displayName":"Total Amount","isRequired":true}',
568
+ );
718
569
  }
719
570
  }
720
571
 
721
- const [clientError, sdk] = await catchError(
722
- createDataFabricClient(options.tenant),
723
- );
724
-
725
- if (clientError) {
726
- OutputFormatter.error({
727
- Result: RESULTS.Failure,
728
- Message: "Error connecting to Data Fabric",
729
- Instructions: await extractErrorMessage(clientError),
730
- });
731
- processContext.exit(1);
732
- return;
733
- }
572
+ const sdk = await connectOrFail(options.tenant);
573
+ if (!sdk) return;
734
574
 
735
575
  const entityService: EntityServiceModel = sdk.entities;
736
576
  const [updateError] = await catchError(
@@ -738,13 +578,10 @@ export const registerEntitiesCommand = (program: Command) => {
738
578
  );
739
579
 
740
580
  if (updateError) {
741
- OutputFormatter.error({
742
- Result: RESULTS.Failure,
743
- Message: "Error updating entity",
744
- Instructions: await extractErrorMessage(updateError),
745
- });
746
- processContext.exit(1);
747
- return;
581
+ return fail(
582
+ "Error updating entity",
583
+ await extractErrorMessage(updateError),
584
+ );
748
585
  }
749
586
 
750
587
  OutputFormatter.success({
@@ -768,7 +605,10 @@ export const registerEntitiesCommand = (program: Command) => {
768
605
  .addOption(
769
606
  createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
770
607
  )
771
- .option("--confirm", "Acknowledge this is an irreversible operation")
608
+ .option("-y, --yes", "Acknowledge this is an irreversible operation")
609
+ .addOption(
610
+ new Option("--confirm", "Deprecated alias for --yes").hideHelp(),
611
+ )
772
612
  .option(
773
613
  "--reason <reason>",
774
614
  "Reason for the deletion — echoed back in the response so the caller can log it",
@@ -777,43 +617,15 @@ export const registerEntitiesCommand = (program: Command) => {
777
617
  .trackedAction(
778
618
  processContext,
779
619
  async (id: string, options: DeleteOptions) => {
780
- if (options.confirm !== true) {
781
- OutputFormatter.error({
782
- Result: RESULTS.Failure,
783
- Message:
784
- "Confirmation required for destructive operation",
785
- Instructions:
786
- "Pass --confirm to acknowledge this is irreversible.",
787
- });
788
- processContext.exit(1);
789
- return;
790
- }
791
-
792
- const reason = options.reason?.trim();
793
- if (reason === undefined || reason === "") {
794
- OutputFormatter.error({
795
- Result: RESULTS.Failure,
796
- Message: "Reason required for destructive operation",
797
- Instructions:
798
- 'Pass --reason "<text>" to record why the entity is being deleted.',
799
- });
800
- processContext.exit(1);
801
- return;
802
- }
803
-
804
- const [clientError, sdk] = await catchError(
805
- createDataFabricClient(options.tenant),
620
+ const reason = requireDestructiveConfirmation(
621
+ options,
622
+ `delete entity '${id}'`,
623
+ 'Pass --reason "<text>" to record why the entity is being deleted.',
806
624
  );
625
+ if (reason === null) return;
807
626
 
808
- if (clientError) {
809
- OutputFormatter.error({
810
- Result: RESULTS.Failure,
811
- Message: "Error connecting to Data Fabric",
812
- Instructions: await extractErrorMessage(clientError),
813
- });
814
- processContext.exit(1);
815
- return;
816
- }
627
+ const sdk = await connectOrFail(options.tenant);
628
+ if (!sdk) return;
817
629
 
818
630
  const entityService: EntityServiceModel = sdk.entities;
819
631
  const [deleteError] = await catchError(
@@ -821,13 +633,10 @@ export const registerEntitiesCommand = (program: Command) => {
821
633
  );
822
634
 
823
635
  if (deleteError) {
824
- OutputFormatter.error({
825
- Result: RESULTS.Failure,
826
- Message: `Error deleting entity '${id}'`,
827
- Instructions: await extractErrorMessage(deleteError),
828
- });
829
- processContext.exit(1);
830
- return;
636
+ return fail(
637
+ `Error deleting entity '${id}'`,
638
+ await extractErrorMessage(deleteError),
639
+ );
831
640
  }
832
641
 
833
642
  OutputFormatter.success({