@uipath/data-fabric-tool 1.0.4 → 1.195.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.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type CommandExample,
3
3
  catchError,
4
+ createHiddenDeprecatedTenantOption,
4
5
  extractErrorMessage,
5
6
  OutputFormatter,
6
7
  processContext,
@@ -16,7 +17,8 @@ import type {
16
17
  import { EntityFieldDataType } from "@uipath/uipath-typescript";
17
18
  import type { Command } from "commander";
18
19
  import { readJsonInput } from "../utils/input";
19
- import { createDataFabricClient } from "../utils/sdk-client";
20
+ import { fail, requireDestructiveConfirmation } from "../utils/output";
21
+ import { connectOrFail } from "../utils/sdk-client";
20
22
 
21
23
  interface ListOptions {
22
24
  tenant?: string;
@@ -49,28 +51,25 @@ interface DeleteOptions {
49
51
 
50
52
  const ENTITIES_LIST_EXAMPLES: CommandExample[] = [
51
53
  {
52
- Description: "List Data Fabric entities",
54
+ Description: "List Data Fabric entities.",
53
55
  Command: "uip df entities list --native-only",
54
56
  Output: {
55
57
  Code: "EntityList",
56
58
  Data: [
57
59
  {
58
- Name: "Invoice",
59
- DisplayName: "Invoice",
60
- ID: "a1b2c3d4-0000-0000-0000-000000000001",
61
- Type: "Standard",
62
- Source: "Native",
63
- Description: "Invoice records",
64
- FieldCount: 8,
65
- },
66
- {
67
- Name: "Customer",
68
- DisplayName: "Customer",
69
- ID: "a1b2c3d4-0000-0000-0000-000000000002",
70
- Type: "Standard",
71
- Source: "Native",
72
- Description: "Customer records",
73
- FieldCount: 6,
60
+ id: "a1b2c3d4-0000-0000-0000-000000000001",
61
+ name: "Invoice",
62
+ displayName: "Invoice",
63
+ entityType: "Standard",
64
+ description: "Invoice records",
65
+ isRbacEnabled: false,
66
+ fields: [
67
+ {
68
+ id: "f1000000-0000-0000-0000-000000000001",
69
+ name: "amount",
70
+ },
71
+ ],
72
+ externalFields: [],
74
73
  },
75
74
  ],
76
75
  },
@@ -79,34 +78,35 @@ const ENTITIES_LIST_EXAMPLES: CommandExample[] = [
79
78
 
80
79
  const ENTITIES_GET_EXAMPLES: CommandExample[] = [
81
80
  {
82
- Description: "Get entity schema by ID",
81
+ Description: "Get entity schema by ID.",
83
82
  Command: "uip df entities get a1b2c3d4-0000-0000-0000-000000000001",
84
83
  Output: {
85
84
  Code: "EntitySchema",
86
85
  Data: {
87
- Name: "Invoice",
88
- DisplayName: "Invoice",
89
- ID: "a1b2c3d4-0000-0000-0000-000000000001",
90
- Type: "Standard",
91
- Description: "Invoice records",
92
- Fields: [
93
- {
94
- ID: "f1000000-0000-0000-0000-000000000001",
95
- Name: "id",
96
- DisplayName: "ID",
97
- Type: "Guid",
98
- Required: true,
99
- PrimaryKey: true,
100
- System: true,
101
- },
86
+ id: "a1b2c3d4-0000-0000-0000-000000000001",
87
+ name: "Invoice",
88
+ displayName: "Invoice",
89
+ entityType: "Standard",
90
+ description: "Invoice records",
91
+ isRbacEnabled: false,
92
+ fields: [
102
93
  {
103
- ID: "f1000000-0000-0000-0000-000000000002",
104
- Name: "amount",
105
- DisplayName: "Amount",
106
- Type: "Decimal",
107
- Required: true,
108
- PrimaryKey: false,
109
- System: false,
94
+ id: "f1000000-0000-0000-0000-000000000002",
95
+ name: "amount",
96
+ displayName: "Amount",
97
+ fieldDataType: { name: "DECIMAL" },
98
+ sqlType: {
99
+ name: "DECIMAL",
100
+ decimalPrecision: 2,
101
+ minValue: 0,
102
+ maxValue: 999999,
103
+ },
104
+ isRequired: true,
105
+ isUnique: false,
106
+ isEncrypted: false,
107
+ isRbacEnabled: false,
108
+ isPrimaryKey: false,
109
+ isSystemField: false,
110
110
  },
111
111
  ],
112
112
  },
@@ -130,6 +130,22 @@ const ENTITIES_DELETE_EXAMPLES: CommandExample[] = [
130
130
  },
131
131
  ];
132
132
 
133
+ const ENTITIES_CREATE_EXAMPLES: CommandExample[] = [
134
+ {
135
+ Description:
136
+ "Create an entity with choice-set, relationship, and file fields. " +
137
+ "CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE require 'choiceSetId' (from 'df choice-sets list'). " +
138
+ "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>'). " +
139
+ "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.",
140
+ Command:
141
+ '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"}]}\'',
142
+ Output: {
143
+ Code: "EntityCreated",
144
+ Data: { ID: "a1b2c3d4-0000-0000-0000-000000000004" },
145
+ },
146
+ },
147
+ ];
148
+
133
149
  const VALID_FIELD_TYPES = new Set(Object.values(EntityFieldDataType));
134
150
  const VALID_FIELD_TYPES_LIST = [...VALID_FIELD_TYPES].join(", ");
135
151
 
@@ -170,61 +186,34 @@ export const registerEntitiesCommand = (program: Command) => {
170
186
  entities
171
187
  .command("list")
172
188
  .description("List all Data Fabric entities")
173
- .option("-t, --tenant <tenant-name>", "Tenant name")
189
+ .addOption(
190
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
191
+ )
174
192
  .option(
175
193
  "--native-only",
176
194
  "Show only native entities (exclude federated entities with external connections)",
177
195
  )
178
196
  .examples(ENTITIES_LIST_EXAMPLES)
179
197
  .trackedAction(processContext, async (options: ListOptions) => {
180
- const [clientError, sdk] = await catchError(
181
- createDataFabricClient(options.tenant),
182
- );
183
-
184
- if (clientError) {
185
- OutputFormatter.error({
186
- Result: RESULTS.Failure,
187
- Message: "Error connecting to Data Fabric",
188
- Instructions: await extractErrorMessage(clientError),
189
- });
190
- processContext.exit(1);
191
- return;
192
- }
198
+ const sdk = await connectOrFail(options.tenant);
199
+ if (!sdk) return;
193
200
 
194
201
  const [listError, result] = await catchError(sdk.entities.getAll());
195
202
 
196
203
  if (listError) {
197
- OutputFormatter.error({
198
- Result: RESULTS.Failure,
199
- Message: "Error listing entities",
200
- Instructions: await extractErrorMessage(listError),
201
- });
202
- processContext.exit(1);
203
- return;
204
+ return fail(
205
+ "Error listing entities",
206
+ await extractErrorMessage(listError),
207
+ );
204
208
  }
205
209
 
206
- const entityList = (result ?? [])
207
- .map((e) => {
208
- const entity = e as unknown as RawEntityGetResponse;
209
- const externalFields: ExternalSourceFields[] =
210
- entity.externalFields ?? [];
211
- const isNative = externalFields.length === 0;
212
- const connectorName =
213
- externalFields[0]?.externalConnectionDetail
214
- ?.connectorName;
215
- return {
216
- Name: entity.name,
217
- DisplayName: entity.displayName || entity.name,
218
- ID: entity.id,
219
- Type: entity.entityType,
220
- Source: isNative
221
- ? "Native"
222
- : `Federated${connectorName ? ` (${connectorName})` : ""}`,
223
- Description: entity.description || "",
224
- FieldCount: entity.fields.length,
225
- };
226
- })
227
- .filter((e) => !options.nativeOnly || e.Source === "Native");
210
+ const entityList = (result ?? []).filter((e) => {
211
+ if (!options.nativeOnly) return true;
212
+ const entity = e as unknown as RawEntityGetResponse;
213
+ const externalFields: ExternalSourceFields[] =
214
+ entity.externalFields ?? [];
215
+ return externalFields.length === 0;
216
+ });
228
217
 
229
218
  OutputFormatter.success({
230
219
  Result: RESULTS.Success,
@@ -237,73 +226,40 @@ export const registerEntitiesCommand = (program: Command) => {
237
226
  .command("get")
238
227
  .description("Get schema details of a Data Fabric entity")
239
228
  .argument("<id>", "Entity ID")
240
- .option("-t, --tenant <tenant-name>", "Tenant name")
229
+ .addOption(
230
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
231
+ )
241
232
  .examples(ENTITIES_GET_EXAMPLES)
242
233
  .trackedAction(
243
234
  processContext,
244
235
  async (id: string, options: GetOptions) => {
245
- const [clientError, sdk] = await catchError(
246
- createDataFabricClient(options.tenant),
247
- );
248
-
249
- if (clientError) {
250
- OutputFormatter.error({
251
- Result: RESULTS.Failure,
252
- Message: "Error connecting to Data Fabric",
253
- Instructions: await extractErrorMessage(clientError),
254
- });
255
- processContext.exit(1);
256
- return;
257
- }
236
+ const sdk = await connectOrFail(options.tenant);
237
+ if (!sdk) return;
258
238
 
259
239
  const [getError, entity] = await catchError(
260
240
  sdk.entities.getById(id),
261
241
  );
262
242
 
263
243
  if (getError) {
264
- OutputFormatter.error({
265
- Result: RESULTS.Failure,
266
- Message: `Error getting entity schema '${id}'`,
267
- Instructions: await extractErrorMessage(getError),
268
- });
269
- processContext.exit(1);
270
- return;
244
+ return fail(
245
+ `Error getting entity schema '${id}'`,
246
+ await extractErrorMessage(getError),
247
+ );
271
248
  }
272
249
 
273
250
  const e = entity as unknown as RawEntityGetResponse;
274
251
 
275
252
  if (!e?.fields) {
276
- OutputFormatter.error({
277
- Result: RESULTS.Failure,
278
- Message: `Entity '${id}' not found`,
279
- Instructions:
280
- "Verify the entity ID exists. Use 'df entities list' to see available entities.",
281
- });
282
- processContext.exit(1);
283
- return;
253
+ return fail(
254
+ `Entity '${id}' not found`,
255
+ "Verify the entity ID exists. Use 'df entities list' to see available entities.",
256
+ );
284
257
  }
285
258
 
286
- const fields = e.fields.map((field) => ({
287
- ID: field.id,
288
- Name: field.name,
289
- DisplayName: field.displayName,
290
- Type: field.fieldDataType?.name,
291
- Required: field.isRequired,
292
- PrimaryKey: field.isPrimaryKey,
293
- System: field.isSystemField,
294
- }));
295
-
296
259
  OutputFormatter.success({
297
260
  Result: RESULTS.Success,
298
261
  Code: "EntitySchema",
299
- Data: {
300
- Name: e.name,
301
- DisplayName: e.displayName || e.name,
302
- ID: e.id,
303
- Type: e.entityType,
304
- Description: e.description || "",
305
- Fields: fields,
306
- },
262
+ Data: e,
307
263
  });
308
264
  },
309
265
  );
@@ -315,12 +271,15 @@ export const registerEntitiesCommand = (program: Command) => {
315
271
  "<name>",
316
272
  "Entity name (must start with a letter; letters, numbers, and underscores only)",
317
273
  )
318
- .option("-t, --tenant <tenant-name>", "Tenant name")
274
+ .addOption(
275
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
276
+ )
319
277
  .option(
320
278
  "-f, --file <path>",
321
279
  "Path to JSON file with entity definition (fields array required; displayName, description, isRbacEnabled optional)",
322
280
  )
323
281
  .option("--body <json>", "Inline JSON entity definition")
282
+ .examples(ENTITIES_CREATE_EXAMPLES)
324
283
  .trackedAction(
325
284
  processContext,
326
285
  async (name: string, options: CreateOptions) => {
@@ -333,13 +292,10 @@ export const registerEntitiesCommand = (program: Command) => {
333
292
  );
334
293
 
335
294
  if (parseError) {
336
- OutputFormatter.error({
337
- Result: RESULTS.Failure,
338
- Message: "Error parsing entity definition",
339
- Instructions: parseError.message,
340
- });
341
- processContext.exit(1);
342
- return;
295
+ return fail(
296
+ "Error parsing entity definition",
297
+ parseError.message,
298
+ );
343
299
  }
344
300
 
345
301
  if (
@@ -347,27 +303,18 @@ export const registerEntitiesCommand = (program: Command) => {
347
303
  parsed === null ||
348
304
  Array.isArray(parsed)
349
305
  ) {
350
- OutputFormatter.error({
351
- Result: RESULTS.Failure,
352
- Message: "Entity definition must be a JSON object",
353
- Instructions:
354
- "Provide a JSON object with a 'fields' array and optional displayName, description, isRbacEnabled.",
355
- });
356
- processContext.exit(1);
357
- return;
306
+ return fail(
307
+ "Entity definition must be a JSON object",
308
+ "Provide a JSON object with a 'fields' array and optional displayName, description, isRbacEnabled.",
309
+ );
358
310
  }
359
311
 
360
312
  const definition = parsed as Record<string, unknown>;
361
313
  if (!Array.isArray(definition.fields)) {
362
- OutputFormatter.error({
363
- Result: RESULTS.Failure,
364
- Message:
365
- "Entity definition must include a 'fields' array",
366
- Instructions:
367
- "Provide a JSON object with a 'fields' array containing field definitions.",
368
- });
369
- processContext.exit(1);
370
- return;
314
+ return fail(
315
+ "Entity definition must include a 'fields' array",
316
+ "Provide a JSON object with a 'fields' array containing field definitions.",
317
+ );
371
318
  }
372
319
 
373
320
  const hasInvalidField = (definition.fields as unknown[]).some(
@@ -378,39 +325,21 @@ export const registerEntitiesCommand = (program: Command) => {
378
325
  "string",
379
326
  );
380
327
  if (hasInvalidField) {
381
- OutputFormatter.error({
382
- Result: RESULTS.Failure,
383
- Message: "Each field must include a 'fieldName' string",
384
- Instructions:
385
- 'Example: {"fieldName":"title","type":"STRING"}',
386
- });
387
- processContext.exit(1);
388
- return;
328
+ return fail(
329
+ "Each field must include a 'fieldName' string",
330
+ 'Example: {"fieldName":"title","type":"STRING"}',
331
+ );
389
332
  }
390
333
 
391
334
  if (hasInvalidFieldType(definition.fields as unknown[])) {
392
- OutputFormatter.error({
393
- Result: RESULTS.Failure,
394
- Message: "Invalid field type in fields",
395
- Instructions: `Valid types: ${VALID_FIELD_TYPES_LIST}`,
396
- });
397
- processContext.exit(1);
398
- return;
335
+ return fail(
336
+ "Invalid field type in fields",
337
+ `Valid types: ${VALID_FIELD_TYPES_LIST}`,
338
+ );
399
339
  }
400
340
 
401
- const [clientError, sdk] = await catchError(
402
- createDataFabricClient(options.tenant),
403
- );
404
-
405
- if (clientError) {
406
- OutputFormatter.error({
407
- Result: RESULTS.Failure,
408
- Message: "Error connecting to Data Fabric",
409
- Instructions: await extractErrorMessage(clientError),
410
- });
411
- processContext.exit(1);
412
- return;
413
- }
341
+ const sdk = await connectOrFail(options.tenant);
342
+ if (!sdk) return;
414
343
 
415
344
  const createOpts = {
416
345
  ...(definition.displayName !== undefined && {
@@ -436,13 +365,10 @@ export const registerEntitiesCommand = (program: Command) => {
436
365
  );
437
366
 
438
367
  if (createError) {
439
- OutputFormatter.error({
440
- Result: RESULTS.Failure,
441
- Message: "Error creating entity",
442
- Instructions: await extractErrorMessage(createError),
443
- });
444
- processContext.exit(1);
445
- return;
368
+ return fail(
369
+ "Error creating entity",
370
+ await extractErrorMessage(createError),
371
+ );
446
372
  }
447
373
 
448
374
  OutputFormatter.success({
@@ -457,7 +383,9 @@ export const registerEntitiesCommand = (program: Command) => {
457
383
  .command("update")
458
384
  .description("Update schema or metadata of a Data Fabric entity")
459
385
  .argument("<id>", "Entity ID")
460
- .option("-t, --tenant <tenant-name>", "Tenant name")
386
+ .addOption(
387
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
388
+ )
461
389
  .option(
462
390
  "-f, --file <path>",
463
391
  "Path to JSON file with update options (addFields, updateFields, removeFields, displayName, description, isRbacEnabled)",
@@ -483,13 +411,10 @@ export const registerEntitiesCommand = (program: Command) => {
483
411
  );
484
412
 
485
413
  if (parseError) {
486
- OutputFormatter.error({
487
- Result: RESULTS.Failure,
488
- Message: "Error parsing update options",
489
- Instructions: parseError.message,
490
- });
491
- processContext.exit(1);
492
- return;
414
+ return fail(
415
+ "Error parsing update options",
416
+ parseError.message,
417
+ );
493
418
  }
494
419
 
495
420
  if (
@@ -497,14 +422,10 @@ export const registerEntitiesCommand = (program: Command) => {
497
422
  parsed === null ||
498
423
  Array.isArray(parsed)
499
424
  ) {
500
- OutputFormatter.error({
501
- Result: RESULTS.Failure,
502
- Message: "Update options must be a JSON object",
503
- Instructions:
504
- "Provide a JSON object with addFields, updateFields, removeFields, displayName, description, or isRbacEnabled.",
505
- });
506
- processContext.exit(1);
507
- return;
425
+ return fail(
426
+ "Update options must be a JSON object",
427
+ "Provide a JSON object with addFields, updateFields, removeFields, displayName, description, or isRbacEnabled.",
428
+ );
508
429
  }
509
430
 
510
431
  const input = parsed as Record<string, unknown>;
@@ -513,42 +434,30 @@ export const registerEntitiesCommand = (program: Command) => {
513
434
  input.addFields !== undefined &&
514
435
  !Array.isArray(input.addFields)
515
436
  ) {
516
- OutputFormatter.error({
517
- Result: RESULTS.Failure,
518
- Message: "'addFields' must be an array",
519
- Instructions:
520
- 'Example: {"addFields":[{"fieldName":"title","type":"STRING"}]}',
521
- });
522
- processContext.exit(1);
523
- return;
437
+ return fail(
438
+ "'addFields' must be an array",
439
+ 'Example: {"addFields":[{"fieldName":"title","type":"STRING"}]}',
440
+ );
524
441
  }
525
442
 
526
443
  if (
527
444
  input.updateFields !== undefined &&
528
445
  !Array.isArray(input.updateFields)
529
446
  ) {
530
- OutputFormatter.error({
531
- Result: RESULTS.Failure,
532
- Message: "'updateFields' must be an array",
533
- Instructions:
534
- 'Example: {"updateFields":[{"id":"<fieldId>","displayName":"New Name"}]}',
535
- });
536
- processContext.exit(1);
537
- return;
447
+ return fail(
448
+ "'updateFields' must be an array",
449
+ 'Example: {"updateFields":[{"id":"<fieldId>","displayName":"New Name"}]}',
450
+ );
538
451
  }
539
452
 
540
453
  if (
541
454
  input.removeFields !== undefined &&
542
455
  !Array.isArray(input.removeFields)
543
456
  ) {
544
- OutputFormatter.error({
545
- Result: RESULTS.Failure,
546
- Message: "'removeFields' must be an array",
547
- Instructions:
548
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
549
- });
550
- processContext.exit(1);
551
- return;
457
+ return fail(
458
+ "'removeFields' must be an array",
459
+ 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
460
+ );
552
461
  }
553
462
 
554
463
  let trimmedReason: string | undefined;
@@ -565,41 +474,18 @@ export const registerEntitiesCommand = (program: Command) => {
565
474
  return typeof fn !== "string" || fn.trim() === "";
566
475
  });
567
476
  if (hasInvalidRemove) {
568
- OutputFormatter.error({
569
- Result: RESULTS.Failure,
570
- Message:
571
- "Each field in removeFields must include a non-empty 'fieldName' string",
572
- Instructions:
573
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
574
- });
575
- processContext.exit(1);
576
- return;
577
- }
578
-
579
- if (options.confirm !== true) {
580
- OutputFormatter.error({
581
- Result: RESULTS.Failure,
582
- Message:
583
- "Confirmation required for destructive operation",
584
- Instructions:
585
- "Pass --confirm to acknowledge field deletion is irreversible.",
586
- });
587
- processContext.exit(1);
588
- return;
477
+ return fail(
478
+ "Each field in removeFields must include a non-empty 'fieldName' string",
479
+ 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
480
+ );
589
481
  }
590
482
 
591
- trimmedReason = options.reason?.trim();
592
- if (trimmedReason === undefined || trimmedReason === "") {
593
- OutputFormatter.error({
594
- Result: RESULTS.Failure,
595
- Message:
596
- "Reason required for destructive operation",
597
- Instructions:
598
- 'Pass --reason "<text>" to record why fields are being removed.',
599
- });
600
- processContext.exit(1);
601
- return;
602
- }
483
+ const reason = requireDestructiveConfirmation(
484
+ options,
485
+ 'Pass --reason "<text>" to record why fields are being removed.',
486
+ );
487
+ if (reason === null) return;
488
+ trimmedReason = reason;
603
489
 
604
490
  removedFieldNames = (
605
491
  removeFields as { fieldName: string }[]
@@ -616,25 +502,17 @@ export const registerEntitiesCommand = (program: Command) => {
616
502
  },
617
503
  );
618
504
  if (hasInvalidField) {
619
- OutputFormatter.error({
620
- Result: RESULTS.Failure,
621
- Message:
622
- "Each field in addFields must include a non-empty 'fieldName' string",
623
- Instructions:
624
- 'Example: {"fieldName":"title","type":"STRING"}',
625
- });
626
- processContext.exit(1);
627
- return;
505
+ return fail(
506
+ "Each field in addFields must include a non-empty 'fieldName' string",
507
+ 'Example: {"fieldName":"title","type":"STRING"}',
508
+ );
628
509
  }
629
510
 
630
511
  if (hasInvalidFieldType(input.addFields as unknown[])) {
631
- OutputFormatter.error({
632
- Result: RESULTS.Failure,
633
- Message: "Invalid field type in addFields",
634
- Instructions: `Valid types: ${VALID_FIELD_TYPES_LIST}`,
635
- });
636
- processContext.exit(1);
637
- return;
512
+ return fail(
513
+ "Invalid field type in addFields",
514
+ `Valid types: ${VALID_FIELD_TYPES_LIST}`,
515
+ );
638
516
  }
639
517
 
640
518
  const addNames = (
@@ -644,14 +522,10 @@ export const registerEntitiesCommand = (program: Command) => {
644
522
  (name, i) => addNames.indexOf(name) !== i,
645
523
  );
646
524
  if (duplicateAdd !== undefined) {
647
- OutputFormatter.error({
648
- Result: RESULTS.Failure,
649
- Message: `Duplicate fieldName '${duplicateAdd}' in addFields`,
650
- Instructions:
651
- "Each entry in addFields must have a unique fieldName.",
652
- });
653
- processContext.exit(1);
654
- return;
525
+ return fail(
526
+ `Duplicate fieldName '${duplicateAdd}' in addFields`,
527
+ "Each entry in addFields must have a unique fieldName.",
528
+ );
655
529
  }
656
530
 
657
531
  if (removedFieldNames.length > 0) {
@@ -659,14 +533,10 @@ export const registerEntitiesCommand = (program: Command) => {
659
533
  removedFieldNames.includes(name),
660
534
  );
661
535
  if (conflict !== undefined) {
662
- OutputFormatter.error({
663
- Result: RESULTS.Failure,
664
- Message: `Field '${conflict}' appears in both addFields and removeFields`,
665
- Instructions:
666
- "A single update cannot add and remove the same field. Split into two calls if you need to recreate it.",
667
- });
668
- processContext.exit(1);
669
- return;
536
+ return fail(
537
+ `Field '${conflict}' appears in both addFields and removeFields`,
538
+ "A single update cannot add and remove the same field. Split into two calls if you need to recreate it.",
539
+ );
670
540
  }
671
541
  }
672
542
  }
@@ -680,31 +550,15 @@ export const registerEntitiesCommand = (program: Command) => {
680
550
  return typeof fid !== "string" || fid.trim() === "";
681
551
  });
682
552
  if (hasInvalidField) {
683
- OutputFormatter.error({
684
- Result: RESULTS.Failure,
685
- Message:
686
- "Each field in updateFields must include a non-empty 'id' string",
687
- Instructions:
688
- 'Use \'df entities get <entityId>\' to find field IDs. Example: {"id":"<fieldId>","displayName":"Total Amount","isRequired":true}',
689
- });
690
- processContext.exit(1);
691
- return;
553
+ return fail(
554
+ "Each field in updateFields must include a non-empty 'id' string",
555
+ 'Use \'df entities get <entityId>\' to find field IDs. Example: {"id":"<fieldId>","displayName":"Total Amount","isRequired":true}',
556
+ );
692
557
  }
693
558
  }
694
559
 
695
- const [clientError, sdk] = await catchError(
696
- createDataFabricClient(options.tenant),
697
- );
698
-
699
- if (clientError) {
700
- OutputFormatter.error({
701
- Result: RESULTS.Failure,
702
- Message: "Error connecting to Data Fabric",
703
- Instructions: await extractErrorMessage(clientError),
704
- });
705
- processContext.exit(1);
706
- return;
707
- }
560
+ const sdk = await connectOrFail(options.tenant);
561
+ if (!sdk) return;
708
562
 
709
563
  const entityService: EntityServiceModel = sdk.entities;
710
564
  const [updateError] = await catchError(
@@ -712,13 +566,10 @@ export const registerEntitiesCommand = (program: Command) => {
712
566
  );
713
567
 
714
568
  if (updateError) {
715
- OutputFormatter.error({
716
- Result: RESULTS.Failure,
717
- Message: "Error updating entity",
718
- Instructions: await extractErrorMessage(updateError),
719
- });
720
- processContext.exit(1);
721
- return;
569
+ return fail(
570
+ "Error updating entity",
571
+ await extractErrorMessage(updateError),
572
+ );
722
573
  }
723
574
 
724
575
  OutputFormatter.success({
@@ -739,7 +590,9 @@ export const registerEntitiesCommand = (program: Command) => {
739
590
  .command("delete")
740
591
  .description("Delete a Data Fabric entity (irreversible)")
741
592
  .argument("<id>", "Entity ID")
742
- .option("-t, --tenant <tenant-name>", "Tenant name")
593
+ .addOption(
594
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
595
+ )
743
596
  .option("--confirm", "Acknowledge this is an irreversible operation")
744
597
  .option(
745
598
  "--reason <reason>",
@@ -749,43 +602,14 @@ export const registerEntitiesCommand = (program: Command) => {
749
602
  .trackedAction(
750
603
  processContext,
751
604
  async (id: string, options: DeleteOptions) => {
752
- if (options.confirm !== true) {
753
- OutputFormatter.error({
754
- Result: RESULTS.Failure,
755
- Message:
756
- "Confirmation required for destructive operation",
757
- Instructions:
758
- "Pass --confirm to acknowledge this is irreversible.",
759
- });
760
- processContext.exit(1);
761
- return;
762
- }
763
-
764
- const reason = options.reason?.trim();
765
- if (reason === undefined || reason === "") {
766
- OutputFormatter.error({
767
- Result: RESULTS.Failure,
768
- Message: "Reason required for destructive operation",
769
- Instructions:
770
- 'Pass --reason "<text>" to record why the entity is being deleted.',
771
- });
772
- processContext.exit(1);
773
- return;
774
- }
775
-
776
- const [clientError, sdk] = await catchError(
777
- createDataFabricClient(options.tenant),
605
+ const reason = requireDestructiveConfirmation(
606
+ options,
607
+ 'Pass --reason "<text>" to record why the entity is being deleted.',
778
608
  );
609
+ if (reason === null) return;
779
610
 
780
- if (clientError) {
781
- OutputFormatter.error({
782
- Result: RESULTS.Failure,
783
- Message: "Error connecting to Data Fabric",
784
- Instructions: await extractErrorMessage(clientError),
785
- });
786
- processContext.exit(1);
787
- return;
788
- }
611
+ const sdk = await connectOrFail(options.tenant);
612
+ if (!sdk) return;
789
613
 
790
614
  const entityService: EntityServiceModel = sdk.entities;
791
615
  const [deleteError] = await catchError(
@@ -793,13 +617,10 @@ export const registerEntitiesCommand = (program: Command) => {
793
617
  );
794
618
 
795
619
  if (deleteError) {
796
- OutputFormatter.error({
797
- Result: RESULTS.Failure,
798
- Message: `Error deleting entity '${id}'`,
799
- Instructions: await extractErrorMessage(deleteError),
800
- });
801
- processContext.exit(1);
802
- return;
620
+ return fail(
621
+ `Error deleting entity '${id}'`,
622
+ await extractErrorMessage(deleteError),
623
+ );
803
624
  }
804
625
 
805
626
  OutputFormatter.success({