mcdev 3.0.1 → 3.1.1

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.
Files changed (42) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/ISSUE_TEMPLATE/bug.yml +72 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/ISSUE_TEMPLATE/task.md +10 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  6. package/.issuetracker +11 -3
  7. package/.vscode/settings.json +3 -3
  8. package/CHANGELOG.md +103 -1
  9. package/README.md +245 -141
  10. package/boilerplate/config.json +3 -2
  11. package/docs/dist/documentation.md +803 -337
  12. package/lib/Deployer.js +4 -1
  13. package/lib/MetadataTypeDefinitions.js +1 -0
  14. package/lib/MetadataTypeInfo.js +1 -0
  15. package/lib/Retriever.js +32 -17
  16. package/lib/cli.js +295 -0
  17. package/lib/index.js +774 -1019
  18. package/lib/metadataTypes/AccountUser.js +389 -0
  19. package/lib/metadataTypes/Asset.js +20 -12
  20. package/lib/metadataTypes/Automation.js +115 -52
  21. package/lib/metadataTypes/DataExtension.js +159 -113
  22. package/lib/metadataTypes/DataExtensionField.js +134 -4
  23. package/lib/metadataTypes/Folder.js +66 -69
  24. package/lib/metadataTypes/ImportFile.js +4 -6
  25. package/lib/metadataTypes/MetadataType.js +136 -67
  26. package/lib/metadataTypes/Query.js +2 -3
  27. package/lib/metadataTypes/Role.js +13 -8
  28. package/lib/metadataTypes/definitions/AccountUser.definition.js +227 -0
  29. package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
  30. package/lib/metadataTypes/definitions/Campaign.definition.js +1 -1
  31. package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -1
  32. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -1
  33. package/lib/metadataTypes/definitions/Folder.definition.js +1 -1
  34. package/lib/metadataTypes/definitions/ImportFile.definition.js +2 -1
  35. package/lib/metadataTypes/definitions/Script.definition.js +5 -5
  36. package/lib/retrieveChangelog.js +96 -0
  37. package/lib/util/cli.js +4 -6
  38. package/lib/util/init.git.js +2 -1
  39. package/lib/util/util.js +31 -15
  40. package/package.json +19 -23
  41. package/img/README.md/troubleshoot-nodejs-postinstall.jpg +0 -0
  42. package/postinstall.js +0 -41
@@ -13,16 +13,16 @@ class Folder extends MetadataType {
13
13
  /**
14
14
  * Retrieves metadata of metadata type into local filesystem. executes callback with retrieved metadata
15
15
  * @param {String} retrieveDir Directory where retrieved metadata directory will be saved
16
- * @param {String[]} [overrideFields] Returns specified fields even if their retrieve definition is not set to true
16
+ * @param {String[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
17
17
  * @param {Object} buObject properties for auth
18
18
  * @returns {Promise} Promise
19
19
  */
20
- static async retrieve(retrieveDir, overrideFields, buObject) {
21
- const queryAllFolders = await this.retrieveHelper(overrideFields, true);
20
+ static async retrieve(retrieveDir, additionalFields, buObject) {
21
+ const queryAllFolders = await this.retrieveHelper(additionalFields, true);
22
22
  // if this is the parent, no need to query twice as QueryAllAccounts works.
23
23
 
24
24
  if (buObject.eid !== buObject.mid) {
25
- queryAllFolders.push(...(await this.retrieveHelper(overrideFields, false)));
25
+ queryAllFolders.push(...(await this.retrieveHelper(additionalFields, false)));
26
26
  }
27
27
  const sortPairs = toposort(queryAllFolders.map((a) => [a.ParentFolder.ID, a.ID]));
28
28
  const idMap = {};
@@ -258,39 +258,36 @@ class Folder extends MetadataType {
258
258
  * @param {Object} metadata metadata of the folder
259
259
  * @returns {Promise} Promise
260
260
  */
261
- static create(metadata) {
262
- const tempCustomerKey = metadata.CustomerKey;
261
+ static async create(metadata) {
262
+ if (metadata.Parent && metadata.Parent.ID && metadata.Parent.ID === 0) {
263
+ Util.logger.error(
264
+ `${this.definition.type}-${metadata.ContentType}.create:: Cannot create Root Folder: ${metadata.Name}`
265
+ );
266
+ return {};
267
+ }
263
268
  const path = metadata.Path;
264
- this.removeNotCreateableFields(metadata);
265
- return new Promise((resolve) => {
266
- if (metadata.Parent && metadata.Parent.ID && metadata.Parent.ID === 0) {
269
+ try {
270
+ const response = await super.createSOAP(metadata, 'DataFolder', true);
271
+ if (response) {
272
+ response.body.Results[0].Object = metadata;
273
+ response.body.Results[0].Object.ID = response.body.Results[0].NewID;
274
+ response.body.Results[0].Object.CustomerKey = metadata.CustomerKey;
275
+ delete response.body.Results[0].Object.$;
276
+
277
+ Util.logger.info(`- created folder: ${path}`);
278
+ return response;
279
+ }
280
+ } catch (ex) {
281
+ if (ex && ex.results) {
267
282
  Util.logger.error(
268
- `${this.definition.type}-${metadata.ContentType}.create:: Cannot create Root Folder: ${metadata.Name}`
283
+ `${this.definition.type}-${metadata.ContentType}.create:: error creating: '${path}'. ${ex.results[0].StatusMessage}`
284
+ );
285
+ } else if (ex) {
286
+ Util.logger.error(
287
+ `${this.definition.type}-${metadata.ContentType}.create:: error creating: '${path}'. ${ex.message}`
269
288
  );
270
- resolve({});
271
- } else {
272
- this.client.SoapClient.create('DataFolder', metadata, null, (ex, response) => {
273
- if (ex && ex.results) {
274
- Util.logger.error(
275
- `${this.definition.type}-${metadata.ContentType}.create:: error creating: ${path}. ${ex.results[0].StatusMessage}`
276
- );
277
- resolve();
278
- } else if (ex) {
279
- Util.logger.error(
280
- `${this.definition.type}-${metadata.ContentType}.create:: error creating: ${path}. ${ex.message}`
281
- );
282
- resolve();
283
- } else {
284
- response.body.Results[0].Object = metadata;
285
- response.body.Results[0].Object.ID = response.body.Results[0].NewID;
286
- response.body.Results[0].Object.CustomerKey = tempCustomerKey;
287
- delete response.body.Results[0].Object.$;
288
- Util.logger.info(`- created folder: ${path}`);
289
- resolve(response);
290
- }
291
- });
292
289
  }
293
- });
290
+ }
294
291
  }
295
292
 
296
293
  /**
@@ -299,30 +296,27 @@ class Folder extends MetadataType {
299
296
  * @returns {Promise} Promise
300
297
  */
301
298
  static async update(metadata) {
302
- return await new Promise((resolve) => {
303
- const tempCustomerKey = metadata.CustomerKey;
304
- const path = metadata.Path;
305
- this.removeNotUpdateableFields(metadata);
306
- this.client.SoapClient.update('DataFolder', metadata, null, (ex, response) => {
307
- if (ex && ex.results) {
308
- Util.logger.error(
309
- `${this.definition.type}-${metadata.ContentType}.update:: error updating: '${path}'. ${ex.results[0].StatusMessage}`
310
- );
311
- resolve();
312
- } else if (ex) {
313
- Util.logger.error(
314
- `${this.definition.type}-${metadata.ContentType}.update:: error updating: '${path}'. ${ex.message}`
315
- );
316
- resolve();
317
- } else {
318
- response.body.Results[0].Object = metadata;
319
- response.body.Results[0].Object.CustomerKey = tempCustomerKey;
320
- delete response.body.Results[0].Object.$;
321
- Util.logger.info(`- updated folder: ${path}`);
322
- resolve(response);
323
- }
324
- });
325
- });
299
+ const path = metadata.Path;
300
+ try {
301
+ const response = await super.updateSOAP(metadata, 'DataFolder', true);
302
+ if (response) {
303
+ response.body.Results[0].Object = metadata;
304
+ response.body.Results[0].Object.CustomerKey = metadata.CustomerKey;
305
+ delete response.body.Results[0].Object.$;
306
+ Util.logger.info(`- updated folder: ${path}`);
307
+ return response;
308
+ }
309
+ } catch (ex) {
310
+ if (ex && ex.results) {
311
+ Util.logger.error(
312
+ `${this.definition.type}-${metadata.ContentType}.update:: error updating: '${path}'. ${ex.results[0].StatusMessage}`
313
+ );
314
+ } else if (ex) {
315
+ Util.logger.error(
316
+ `${this.definition.type}-${metadata.ContentType}.update:: error updating: '${path}'. ${ex.message}`
317
+ );
318
+ }
319
+ }
326
320
  }
327
321
 
328
322
  /**
@@ -451,25 +445,28 @@ class Folder extends MetadataType {
451
445
 
452
446
  /**
453
447
  * Helper to retrieve the folders as promise
454
- * @param {String[]} [overrideFields] Returns specified fields even if their retrieve definition is not set to true
448
+ * @param {String[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
455
449
  * @param {Boolean} [queryAllAccounts] which queryAllAccounts setting to use
456
450
  * @returns {Promise<Object>} soap object
457
451
  */
458
- static async retrieveHelper(overrideFields, queryAllAccounts) {
452
+ static async retrieveHelper(additionalFields, queryAllAccounts) {
459
453
  const options = { queryAllAccounts: !!queryAllAccounts };
460
454
  let status = 'MoreDataAvailable';
461
455
  const Results = [];
462
456
  do {
463
- const response = await new Promise((resolve, reject) => {
464
- // filtered out path as we need them to be stored locally, but do not want to try and retrieve
465
- this.client.SoapClient.retrieve(
466
- 'DataFolder',
467
- this.getFieldNamesToRetrieve(overrideFields).filter(
468
- (field) => !field.includes('Path')
469
- ),
470
- options,
471
- (ex, response) => (ex ? reject(ex) : resolve(response))
472
- );
457
+ let response;
458
+ await Util.retryOnError(`Retrying ${this.definition.type}`, async () => {
459
+ response = await new Promise((resolve, reject) => {
460
+ // filtered out path as we need them to be stored locally, but do not want to try and retrieve
461
+ this.client.SoapClient.retrieve(
462
+ 'DataFolder',
463
+ this.getFieldNamesToRetrieve(additionalFields).filter(
464
+ (field) => !field.includes('Path')
465
+ ),
466
+ options,
467
+ (ex, response) => (ex ? reject(ex) : resolve(response))
468
+ );
469
+ });
473
470
  });
474
471
  // merge results with existing
475
472
  Results.push(...response.body.Results);
@@ -147,12 +147,10 @@ class ImportFile extends MetadataType {
147
147
  }
148
148
  }
149
149
  // When the destinationObjectTypeId is 584 is refers to Mobile Connect which is not supported as an Import Type
150
- metadata.destinationObjectTypeId = this.definition.destinationObjectTypeMapping[
151
- metadata.c__destinationType
152
- ];
153
- metadata.subscriberImportTypeId = this.definition.subscriberImportTypeMapping[
154
- metadata.c__subscriberImportType
155
- ];
150
+ metadata.destinationObjectTypeId =
151
+ this.definition.destinationObjectTypeMapping[metadata.c__destinationType];
152
+ metadata.subscriberImportTypeId =
153
+ this.definition.subscriberImportTypeMapping[metadata.c__subscriberImportType];
156
154
  metadata.updateTypeId = this.definition.updateTypeMapping[metadata.c__dataAction];
157
155
  return metadata;
158
156
  }
@@ -194,16 +194,26 @@ class MetadataType {
194
194
  /**
195
195
  * Gets metadata from Marketing Cloud
196
196
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
197
- * @param {string[]} [overrideFields] Returns specified fields even if their retrieve definition is not set to true
197
+ * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
198
198
  * @param {Util.BuObject} buObject properties for auth
199
199
  * @param {string} [subType] optionally limit to a single subtype
200
200
  * @returns {Promise<{metadata:MetadataTypeMap,type:string}>} metadata
201
201
  */
202
- static retrieve(retrieveDir, overrideFields, buObject, subType) {
202
+ static retrieve(retrieveDir, additionalFields, buObject, subType) {
203
203
  Util.metadataLogger('error', this.definition.type, 'retrieve', `Not Supported`);
204
204
  const metadata = {};
205
205
  return { metadata: null, type: this.definition.type };
206
206
  }
207
+ /**
208
+ * Gets metadata from Marketing Cloud
209
+ * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
210
+ * @param {Util.BuObject} buObject properties for auth
211
+ * @param {string} [subType] optionally limit to a single subtype
212
+ * @returns {Promise<{metadata:MetadataTypeMap,type:string}>} metadata
213
+ */
214
+ static retrieveChangelog(additionalFields, buObject, subType) {
215
+ return this.retrieve(null, additionalFields, buObject, subType);
216
+ }
207
217
 
208
218
  /**
209
219
  * Gets metadata cache with limited fields and does not store value to disk
@@ -304,9 +314,10 @@ class MetadataType {
304
314
  });
305
315
  } else if (this.cache[this.definition.type][normalizedKey]) {
306
316
  // normal way of processing update files
307
- metadata[metadataKey][this.definition.idField] = this.cache[
308
- this.definition.type
309
- ][normalizedKey][this.definition.idField];
317
+ metadata[metadataKey][this.definition.idField] =
318
+ this.cache[this.definition.type][normalizedKey][
319
+ this.definition.idField
320
+ ];
310
321
  metadataToUpdate.push({
311
322
  before: this.cache[this.definition.type][normalizedKey],
312
323
  after: metadata[metadataKey],
@@ -403,31 +414,54 @@ class MetadataType {
403
414
  /**
404
415
  * Creates a single metadata entry via fuel-soap (generic lib not wrapper)
405
416
  * @param {MetadataTypeItem} metadataEntry single metadata entry
417
+ * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
418
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
406
419
  * @returns {Promise} Promise
407
420
  */
408
- static async createSOAP(metadataEntry) {
421
+ static async createSOAP(metadataEntry, overrideType, handleOutside) {
409
422
  try {
410
- const res = await new Promise((resolve, reject) => {
411
- this.removeNotCreateableFields(metadataEntry);
412
- this.client.SoapClient.create(
413
- this.definition.type.charAt(0).toUpperCase() + this.definition.type.slice(1),
414
- metadataEntry,
415
- null,
416
- (error, response) => (error ? reject(error) : resolve(response))
417
- );
418
- });
419
- Util.logger.info(
420
- `- created ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
423
+ this.removeNotCreateableFields(metadataEntry);
424
+ let response;
425
+ await Util.retryOnError(
426
+ `Retrying to create ${this.definition.type}: ${
427
+ metadataEntry[this.definition.nameField]
428
+ }`,
429
+ async () =>
430
+ (response = await new Promise((resolve, reject) => {
431
+ this.client.SoapClient.create(
432
+ overrideType ||
433
+ this.definition.type.charAt(0).toUpperCase() +
434
+ this.definition.type.slice(1),
435
+ metadataEntry,
436
+ null,
437
+ (error, response) => (error ? reject(error) : resolve(response))
438
+ );
439
+ }))
421
440
  );
422
- return res;
441
+ if (!handleOutside) {
442
+ Util.logger.info(
443
+ `- created ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
444
+ );
445
+ }
446
+ return response;
423
447
  } catch (ex) {
424
- Util.logger.error(
425
- `- error creating ${this.definition.type}: ${
426
- metadataEntry[this.definition.keyField]
427
- } (${ex.message})`
428
- );
448
+ if (!handleOutside) {
449
+ let errorMsg;
450
+ if (ex.results && ex.results.length) {
451
+ errorMsg = `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`;
452
+ } else {
453
+ errorMsg = ex.message;
454
+ }
455
+ Util.logger.error(
456
+ `- error creating ${this.definition.type} '${
457
+ metadataEntry[this.definition.keyField]
458
+ }': ${errorMsg}`
459
+ );
460
+ } else {
461
+ throw ex;
462
+ }
429
463
 
430
- return null;
464
+ return {};
431
465
  }
432
466
  }
433
467
 
@@ -451,8 +485,12 @@ class MetadataType {
451
485
  async () => (response = await this.client.RestClient.patch(options))
452
486
  );
453
487
  this.checkForErrors(response);
488
+ // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
454
489
  Util.logger.info(
455
- `- updated ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
490
+ `- updated ${this.definition.type}: ${
491
+ metadataEntry[this.definition.keyField] ||
492
+ metadataEntry[this.definition.nameField]
493
+ }`
456
494
  );
457
495
  return response;
458
496
  } catch (ex) {
@@ -468,31 +506,54 @@ class MetadataType {
468
506
  /**
469
507
  * Updates a single metadata entry via fuel-soap (generic lib not wrapper)
470
508
  * @param {MetadataTypeItem} metadataEntry single metadata entry
509
+ * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
510
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
471
511
  * @returns {Promise} Promise
472
512
  */
473
- static async updateSOAP(metadataEntry) {
513
+ static async updateSOAP(metadataEntry, overrideType, handleOutside) {
514
+ let response;
474
515
  try {
475
- const res = await new Promise((resolve, reject) => {
476
- this.removeNotUpdateableFields(metadataEntry);
477
- this.client.SoapClient.update(
478
- this.definition.type.charAt(0).toUpperCase() + this.definition.type.slice(1),
479
- metadataEntry,
480
- null,
481
- (error, response) => (error ? reject(error) : resolve(response))
482
- );
483
- });
484
- Util.logger.info(
485
- `- updated ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
516
+ this.removeNotUpdateableFields(metadataEntry);
517
+ await Util.retryOnError(
518
+ `Retrying to update ${this.definition.type}: ${
519
+ metadataEntry[this.definition.nameField]
520
+ }`,
521
+ async () =>
522
+ (response = await new Promise((resolve, reject) => {
523
+ this.client.SoapClient.update(
524
+ overrideType ||
525
+ this.definition.type.charAt(0).toUpperCase() +
526
+ this.definition.type.slice(1),
527
+ metadataEntry,
528
+ null,
529
+ (error, response) => (error ? reject(error) : resolve(response))
530
+ );
531
+ }))
486
532
  );
487
- return res;
533
+ if (!handleOutside) {
534
+ Util.logger.info(
535
+ `- updated ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
536
+ );
537
+ }
538
+ return response;
488
539
  } catch (ex) {
489
- Util.logger.error(
490
- `- error updating ${this.definition.type}: ${
491
- metadataEntry[this.definition.keyField]
492
- } (${ex.message})`
493
- );
540
+ if (!handleOutside) {
541
+ let errorMsg;
542
+ if (ex.results && ex.results.length) {
543
+ errorMsg = `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`;
544
+ } else {
545
+ errorMsg = ex.message;
546
+ }
547
+ Util.logger.error(
548
+ `- error updating ${this.definition.type} '${
549
+ metadataEntry[this.definition.keyField]
550
+ }': ${errorMsg}`
551
+ );
552
+ } else {
553
+ throw ex;
554
+ }
494
555
 
495
- return null;
556
+ return {};
496
557
  }
497
558
  }
498
559
  /**
@@ -501,18 +562,24 @@ class MetadataType {
501
562
  * @param {Util.BuObject} buObject properties for auth
502
563
  * @param {Object} [options] required for the specific request (filter for example)
503
564
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
565
+ * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
504
566
  * @returns {Promise<{metadata:MetadataTypeMap,type:string}>} Promise of item map
505
567
  */
506
- static async retrieveSOAPgeneric(retrieveDir, buObject, options, additionalFields) {
568
+ static async retrieveSOAPgeneric(
569
+ retrieveDir,
570
+ buObject,
571
+ options,
572
+ additionalFields,
573
+ overrideType
574
+ ) {
507
575
  const fields = this.getFieldNamesToRetrieve(additionalFields);
508
576
 
509
- const metadata = await this.retrieveSOAPBody(fields, options);
577
+ const metadata = await this.retrieveSOAPBody(fields, options, overrideType);
510
578
  if (retrieveDir) {
511
579
  const savedMetadata = await this.saveResults(metadata, retrieveDir, null);
512
580
  Util.logger.info(
513
581
  `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
514
582
  );
515
-
516
583
  if (
517
584
  buObject &&
518
585
  this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)
@@ -536,23 +603,27 @@ class MetadataType {
536
603
  options = options || {};
537
604
  let metadata = {};
538
605
  do {
539
- const resultsBatch = await new Promise((resolve) => {
540
- this.client.SoapClient.retrieve(
541
- type || this.definition.type,
542
- fields,
543
- options || {},
544
- (error, response) => {
545
- if (error) {
546
- Util.logger.debug(`SOAP.retrieve Error: ${error.message}`);
606
+ let resultsBatch;
607
+ await Util.retryOnError(`Retrying ${this.definition.type}`, async () => {
608
+ resultsBatch = await new Promise((resolve, reject) => {
609
+ this.client.SoapClient.retrieve(
610
+ type || this.definition.type,
611
+ fields,
612
+ options || {},
613
+ (error, response) => {
614
+ if (error) {
615
+ Util.logger.debug(`SOAP.retrieve Error: ${error.message}`);
616
+ reject(error);
617
+ }
618
+ if (response) {
619
+ resolve(response.body);
620
+ } else {
621
+ // fallback, lets make sure surrounding methods know we got an empty result back
622
+ resolve({});
623
+ }
547
624
  }
548
- if (response) {
549
- resolve(response.body);
550
- } else {
551
- // fallback, lets make sure surrounding methods know we got an empty result back
552
- resolve({});
553
- }
554
- }
555
- );
625
+ );
626
+ });
556
627
  });
557
628
  status = resultsBatch.OverallStatus;
558
629
  if (status === 'MoreDataAvailable') {
@@ -591,11 +662,9 @@ class MetadataType {
591
662
  do {
592
663
  options.uri = this.paginate(options.uri, lastPage);
593
664
  let response;
594
- try {
665
+ await Util.retryOnError(`Retrying ${this.definition.type}`, async () => {
595
666
  response = await this.client.RestClient.get(options);
596
- } catch (ex) {
597
- Util.logger.debug(ex.message);
598
- }
667
+ });
599
668
  const metadata = this.parseResponseBody(response.body);
600
669
  results = Object.assign(results, metadata);
601
670
  if (
@@ -144,9 +144,8 @@ class Query extends MetadataType {
144
144
  } catch (ex) {
145
145
  throw new Error(`Query '${metadata.key}': ${ex.message}`);
146
146
  }
147
- metadata.targetUpdateTypeId = this.definition.targetUpdateTypeMapping[
148
- metadata.targetUpdateTypeName
149
- ];
147
+ metadata.targetUpdateTypeId =
148
+ this.definition.targetUpdateTypeMapping[metadata.targetUpdateTypeName];
150
149
  return metadata;
151
150
  }
152
151
 
@@ -107,14 +107,19 @@ class Role extends MetadataType {
107
107
  return;
108
108
  }
109
109
  if (!metadata) {
110
- metadata = this.readBUMetadataForType(
111
- File.normalizePath([
112
- this.properties.directories.retrieve,
113
- buObject.credential,
114
- Util.parentBuName,
115
- ]),
116
- true
117
- ).role;
110
+ try {
111
+ metadata = this.readBUMetadataForType(
112
+ File.normalizePath([
113
+ this.properties.directories.retrieve,
114
+ buObject.credential,
115
+ Util.parentBuName,
116
+ ]),
117
+ true
118
+ ).role;
119
+ } catch (ex) {
120
+ Util.logger.error(ex.message);
121
+ return;
122
+ }
118
123
  }
119
124
  const directory = this.properties.directories.roles;
120
125