mcdev 4.2.1 → 4.3.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.
Files changed (95) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/PULL_REQUEST_TEMPLATE.md +1 -2
  3. package/.github/pr-labeler.yml +3 -0
  4. package/.github/workflows/close_issues_on_merge.yml +18 -0
  5. package/.github/workflows/pr-labeler.yml +19 -0
  6. package/LICENSE +1 -1
  7. package/README.md +1 -1
  8. package/docs/dist/documentation.md +700 -281
  9. package/lib/Deployer.js +21 -15
  10. package/lib/Retriever.js +23 -19
  11. package/lib/cli.js +36 -6
  12. package/lib/index.js +56 -10
  13. package/lib/metadataTypes/AccountUser.js +17 -23
  14. package/lib/metadataTypes/Asset.js +28 -21
  15. package/lib/metadataTypes/AttributeGroup.js +1 -2
  16. package/lib/metadataTypes/Automation.js +75 -37
  17. package/lib/metadataTypes/Campaign.js +4 -3
  18. package/lib/metadataTypes/ContentArea.js +2 -3
  19. package/lib/metadataTypes/DataExtension.js +56 -47
  20. package/lib/metadataTypes/DataExtensionField.js +9 -12
  21. package/lib/metadataTypes/DataExtensionTemplate.js +2 -3
  22. package/lib/metadataTypes/DataExtract.js +1 -2
  23. package/lib/metadataTypes/DataExtractType.js +1 -2
  24. package/lib/metadataTypes/Discovery.js +3 -4
  25. package/lib/metadataTypes/Email.js +20 -6
  26. package/lib/metadataTypes/EmailSendDefinition.js +5 -8
  27. package/lib/metadataTypes/EventDefinition.js +29 -2
  28. package/lib/metadataTypes/FileTransfer.js +1 -2
  29. package/lib/metadataTypes/Filter.js +1 -2
  30. package/lib/metadataTypes/Folder.js +12 -14
  31. package/lib/metadataTypes/FtpLocation.js +1 -2
  32. package/lib/metadataTypes/ImportFile.js +1 -2
  33. package/lib/metadataTypes/Interaction.js +678 -12
  34. package/lib/metadataTypes/List.js +36 -33
  35. package/lib/metadataTypes/MetadataType.js +170 -124
  36. package/lib/metadataTypes/MobileCode.js +1 -2
  37. package/lib/metadataTypes/MobileKeyword.js +1 -2
  38. package/lib/metadataTypes/Query.js +15 -6
  39. package/lib/metadataTypes/Role.js +10 -11
  40. package/lib/metadataTypes/Script.js +2 -5
  41. package/lib/metadataTypes/SetDefinition.js +1 -2
  42. package/lib/metadataTypes/TransactionalMessage.js +25 -32
  43. package/lib/metadataTypes/TransactionalSMS.js +3 -4
  44. package/lib/metadataTypes/TriggeredSendDefinition.js +232 -56
  45. package/lib/metadataTypes/definitions/Asset.definition.js +1 -1
  46. package/lib/metadataTypes/definitions/Automation.definition.js +1 -1
  47. package/lib/metadataTypes/definitions/DataExtension.definition.js +10 -1
  48. package/lib/metadataTypes/definitions/Email.definition.js +1 -1
  49. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +1 -1
  50. package/lib/metadataTypes/definitions/EventDefinition.definition.js +40 -1
  51. package/lib/metadataTypes/definitions/Folder.definition.js +31 -0
  52. package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -1
  53. package/lib/metadataTypes/definitions/Interaction.definition.js +47 -26
  54. package/lib/metadataTypes/definitions/List.definition.js +1 -1
  55. package/lib/metadataTypes/definitions/Query.definition.js +1 -1
  56. package/lib/metadataTypes/definitions/Script.definition.js +1 -1
  57. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  58. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +2 -1
  59. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +2 -1
  60. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +10 -2
  61. package/lib/util/auth.js +10 -2
  62. package/lib/util/cli.js +4 -1
  63. package/lib/util/file.js +7 -3
  64. package/lib/util/init.js +62 -0
  65. package/lib/util/util.js +131 -11
  66. package/package.json +22 -9
  67. package/test/dataExtension.test.js +10 -10
  68. package/test/interaction.test.js +123 -0
  69. package/test/mockRoot/.mcdevrc.json +1 -1
  70. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +266 -0
  71. package/test/mockRoot/deploy/testInstance/testBU/interaction/testNew_interaction.interaction-meta.json +266 -0
  72. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +0 -3
  73. package/test/query.test.js +8 -8
  74. package/test/resourceFactory.js +12 -7
  75. package/test/resources/1111111/dataExtension/retrieve-response.xml +26 -0
  76. package/test/resources/9999999/data/v1/customobjectdata/key/childBU_dataextension_test/rowset/get-response.json +13 -0
  77. package/test/resources/9999999/dataFolder/retrieve-response.xml +22 -0
  78. package/test/resources/9999999/eventDefinition/get-expected.json +34 -0
  79. package/test/resources/9999999/interaction/build-expected.json +260 -0
  80. package/test/resources/9999999/interaction/get-expected.json +264 -0
  81. package/test/resources/9999999/interaction/post-expected.json +264 -0
  82. package/test/resources/9999999/interaction/put-expected.json +264 -0
  83. package/test/resources/9999999/interaction/template-expected.json +260 -0
  84. package/test/resources/9999999/interaction/v1/EventDefinitions/get-response.json +43 -0
  85. package/test/resources/9999999/interaction/v1/interactions/get-response.json +222 -3
  86. package/test/resources/9999999/interaction/v1/interactions/post-response.json +280 -0
  87. package/test/resources/9999999/interaction/v1/interactions/put-response.json +280 -0
  88. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  89. package/test/resources/9999999/query/post-expected.sql +1 -1
  90. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  91. package/test/transactionalEmail.test.js +7 -7
  92. package/test/transactionalPush.test.js +7 -7
  93. package/test/transactionalSMS.test.js +7 -7
  94. package/test/utils.js +50 -0
  95. package/types/mcdev.d.js +1 -0
@@ -15,6 +15,15 @@ const cache = require('../util/cache');
15
15
  const deepEqual = require('deep-equal');
16
16
  const pLimit = require('p-limit');
17
17
  const Mustache = require('mustache');
18
+ /**
19
+ * ensure that Mustache does not escape any characters
20
+ *
21
+ * @param {string} text -
22
+ * @returns {string} text
23
+ */
24
+ Mustache.escape = function (text) {
25
+ return text;
26
+ };
18
27
 
19
28
  /**
20
29
  * MetadataType class that gets extended by their specific metadata type class.
@@ -103,11 +112,10 @@ class MetadataType {
103
112
  * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
104
113
  * @param {string} deployDir directory where deploy metadata are saved
105
114
  * @param {string} retrieveDir directory where metadata after deploy should be saved
106
- * @param {TYPE.BuObject} buObject properties for auth
107
115
  * @returns {Promise.<TYPE.MetadataTypeMap>} Promise of keyField => metadata map
108
116
  */
109
- static async deploy(metadata, deployDir, retrieveDir, buObject) {
110
- const upsertResults = await this.upsert(metadata, deployDir, buObject);
117
+ static async deploy(metadata, deployDir, retrieveDir) {
118
+ const upsertResults = await this.upsert(metadata, deployDir);
111
119
  await this.postDeployTasks(upsertResults, metadata);
112
120
  const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
113
121
  if (
@@ -116,7 +124,7 @@ class MetadataType {
116
124
  ) {
117
125
  // * do not await here as this might take a while and has no impact on the deploy
118
126
  // * this should only be run if documentation is on a per metadata record level. Types that document an overview into a single file will need a full retrieve to work instead
119
- this.document(buObject, savedMetadata, true);
127
+ this.document(savedMetadata, true);
120
128
  }
121
129
  return upsertResults;
122
130
  }
@@ -144,7 +152,7 @@ class MetadataType {
144
152
  /**
145
153
  * generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve
146
154
  *
147
- * @param {TYPE.MetadataTypeItem} metadata a single script activity definition
155
+ * @param {TYPE.MetadataTypeItem} metadata a single item
148
156
  */
149
157
  static setFolderPath(metadata) {
150
158
  if (!this.definition.folderIdField) {
@@ -166,18 +174,34 @@ class MetadataType {
166
174
  );
167
175
  }
168
176
  }
177
+ /**
178
+ * generic script that retrieves the folder ID from cache and updates the given metadata with it before deploy
179
+ *
180
+ * @param {TYPE.MetadataTypeItem} metadata a single item
181
+ */
182
+ static setFolderId(metadata) {
183
+ if (!this.definition.folderIdField) {
184
+ return;
185
+ }
186
+ metadata[this.definition.folderIdField] = cache.searchForField(
187
+ 'folder',
188
+ metadata.r__folder_Path,
189
+ 'Path',
190
+ 'ID'
191
+ );
192
+ delete metadata.r__folder_Path;
193
+ }
169
194
 
170
195
  /**
171
196
  * Gets metadata from Marketing Cloud
172
197
  *
173
198
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
174
199
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
175
- * @param {TYPE.BuObject} buObject properties for auth
176
- * @param {string} [subType] optionally limit to a single subtype
200
+ * @param {string[]} [subTypeArr] optionally limit to a single subtype
177
201
  * @param {string} [key] customer key of single item to retrieve
178
202
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
179
203
  */
180
- static retrieve(retrieveDir, additionalFields, buObject, subType, key) {
204
+ static retrieve(retrieveDir, additionalFields, subTypeArr, key) {
181
205
  Util.metadataLogger('error', this.definition.type, 'retrieve', `Not Supported`);
182
206
  const metadata = {};
183
207
  return { metadata: null, type: this.definition.type };
@@ -185,24 +209,23 @@ class MetadataType {
185
209
  /**
186
210
  * Gets metadata from Marketing Cloud
187
211
  *
188
- * @param {TYPE.BuObject} [buObject] properties for auth
189
212
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
190
- * @param {string} [subType] optionally limit to a single subtype
213
+ * @param {string[]} [subTypeArr] optionally limit to a single subtype
191
214
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
192
215
  */
193
- static retrieveChangelog(buObject, additionalFields, subType) {
194
- return this.retrieveForCache(buObject, subType);
216
+ static retrieveChangelog(additionalFields, subTypeArr) {
217
+ return this.retrieveForCache(additionalFields, subTypeArr);
195
218
  }
196
219
 
197
220
  /**
198
221
  * Gets metadata cache with limited fields and does not store value to disk
199
222
  *
200
- * @param {TYPE.BuObject} buObject properties for auth
201
- * @param {string} [subType] optionally limit to a single subtype
223
+ * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
224
+ * @param {string[]} [subTypeArr] optionally limit to a single subtype
202
225
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
203
226
  */
204
- static async retrieveForCache(buObject, subType) {
205
- return this.retrieve(null, null, buObject, subType);
227
+ static async retrieveForCache(additionalFields, subTypeArr) {
228
+ return this.retrieve(null, additionalFields, subTypeArr);
206
229
  }
207
230
  /**
208
231
  * Gets metadata cache with limited fields and does not store value to disk
@@ -303,10 +326,9 @@ class MetadataType {
303
326
  *
304
327
  * @param {TYPE.MetadataTypeItem} metadata a single metadata item
305
328
  * @param {string} deployDir folder where files for deployment are stored
306
- * @param {TYPE.BuObject} buObject buObject properties for auth
307
329
  * @returns {Promise.<TYPE.MetadataTypeItem>} Promise of a single metadata item
308
330
  */
309
- static async preDeployTasks(metadata, deployDir, buObject) {
331
+ static async preDeployTasks(metadata, deployDir) {
310
332
  return metadata;
311
333
  }
312
334
 
@@ -341,6 +363,17 @@ class MetadataType {
341
363
  );
342
364
  return;
343
365
  }
366
+ /**
367
+ * Abstract refresh method that needs to be implemented in child metadata type
368
+ *
369
+ * @returns {void}
370
+ */
371
+ static refresh() {
372
+ Util.logger.error(
373
+ ` ☇ skipping ${this.definition.type}: refresh is not supported yet for ${this.definition.type}`
374
+ );
375
+ return;
376
+ }
344
377
 
345
378
  /**
346
379
  * test if metadata was actually changed or not to potentially skip it during deployment
@@ -413,10 +446,9 @@ class MetadataType {
413
446
  *
414
447
  * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
415
448
  * @param {string} deployDir directory where deploy metadata are saved
416
- * @param {TYPE.BuObject} [buObject] properties for auth
417
449
  * @returns {Promise.<TYPE.MetadataTypeMap>} keyField => metadata map
418
450
  */
419
- static async upsert(metadata, deployDir, buObject) {
451
+ static async upsert(metadata, deployDir) {
420
452
  const metadataToUpdate = [];
421
453
  const metadataToCreate = [];
422
454
  let filteredByPreDeploy = 0;
@@ -428,8 +460,7 @@ class MetadataType {
428
460
  try {
429
461
  deployableMetadata = await this.preDeployTasks(
430
462
  metadata[metadataKey],
431
- deployDir,
432
- buObject
463
+ deployDir
433
464
  );
434
465
  } catch (ex) {
435
466
  // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
@@ -445,57 +476,13 @@ class MetadataType {
445
476
  if (deployableMetadata) {
446
477
  metadata[metadataKey] = deployableMetadata;
447
478
  // create normalizedKey off of whats in the json rather than from "metadataKey" because preDeployTasks might have altered something (type asset)
448
- const normalizedKey = File.reverseFilterIllegalFilenames(
449
- deployableMetadata[this.definition.keyField]
479
+ this.createOrUpdate(
480
+ metadata,
481
+ metadataKey,
482
+ hasError,
483
+ metadataToUpdate,
484
+ metadataToCreate
450
485
  );
451
- // Update if it already exists; Create it if not
452
- if (
453
- Util.logger.level === 'debug' &&
454
- metadata[metadataKey][this.definition.idField]
455
- ) {
456
- // TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
457
- // only used if resource is excluded from cache and we still want to update it
458
- // needed e.g. to rewire lost folders
459
- Util.logger.warn(
460
- ' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
461
- );
462
- Util.logger.warn(JSON.stringify(metadata[metadataKey]));
463
- if (hasError) {
464
- metadataToUpdate.push(null);
465
- } else {
466
- metadataToUpdate.push({
467
- before: {},
468
- after: metadata[metadataKey],
469
- });
470
- }
471
- } else if (cache.getByKey(this.definition.type, normalizedKey)) {
472
- // normal way of processing update files
473
- const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
474
- if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
475
- hasError = true;
476
- }
477
-
478
- if (hasError) {
479
- // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
480
- metadataToUpdate.push(null);
481
- } else {
482
- // add ObjectId to allow actual update
483
- metadata[metadataKey][this.definition.idField] =
484
- cachedVersion[this.definition.idField];
485
-
486
- metadataToUpdate.push({
487
- before: cache.getByKey(this.definition.type, normalizedKey),
488
- after: metadata[metadataKey],
489
- });
490
- }
491
- } else {
492
- if (hasError) {
493
- // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
494
- metadataToCreate.push(null);
495
- } else {
496
- metadataToCreate.push(metadata[metadataKey]);
497
- }
498
- }
499
486
  } else {
500
487
  filteredByPreDeploy++;
501
488
  }
@@ -554,12 +541,72 @@ class MetadataType {
554
541
  }
555
542
  }
556
543
 
544
+ /**
545
+ *
546
+ * @param {TYPE.MetadataTypeItem} metadata single metadata itme
547
+ * @param {string} metadataKey key of item we are looking at
548
+ * @param {boolean} hasError error flag from previous code
549
+ * @param {TYPE.MetadataTypeItemDiff[]} metadataToUpdate list of items to update
550
+ * @param {TYPE.MetadataTypeItem[]} metadataToCreate list of items to create
551
+ * @returns {void}
552
+ */
553
+ static createOrUpdate(metadata, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
554
+ const normalizedKey = File.reverseFilterIllegalFilenames(
555
+ metadata[metadataKey][this.definition.keyField]
556
+ );
557
+ // Update if it already exists; Create it if not
558
+ if (Util.logger.level === 'debug' && metadata[metadataKey][this.definition.idField]) {
559
+ // TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
560
+ // only used if resource is excluded from cache and we still want to update it
561
+ // needed e.g. to rewire lost folders
562
+ Util.logger.warn(
563
+ ' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
564
+ );
565
+ Util.logger.warn(JSON.stringify(metadata[metadataKey]));
566
+ if (hasError) {
567
+ metadataToUpdate.push(null);
568
+ } else {
569
+ metadataToUpdate.push({
570
+ before: {},
571
+ after: metadata[metadataKey],
572
+ });
573
+ }
574
+ } else if (cache.getByKey(this.definition.type, normalizedKey)) {
575
+ // normal way of processing update files
576
+ const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
577
+ if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
578
+ hasError = true;
579
+ }
580
+
581
+ if (hasError) {
582
+ // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
583
+ metadataToUpdate.push(null);
584
+ } else {
585
+ // add ObjectId to allow actual update
586
+ metadata[metadataKey][this.definition.idField] =
587
+ cachedVersion[this.definition.idField];
588
+
589
+ metadataToUpdate.push({
590
+ before: cache.getByKey(this.definition.type, normalizedKey),
591
+ after: metadata[metadataKey],
592
+ });
593
+ }
594
+ } else {
595
+ if (hasError) {
596
+ // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
597
+ metadataToCreate.push(null);
598
+ } else {
599
+ metadataToCreate.push(metadata[metadataKey]);
600
+ }
601
+ }
602
+ }
603
+
557
604
  /**
558
605
  * Creates a single metadata entry via REST
559
606
  *
560
607
  * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
561
608
  * @param {string} uri rest endpoint for POST
562
- * @returns {Promise} Promise
609
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
563
610
  */
564
611
  static async createREST(metadataEntry, uri) {
565
612
  this.removeNotCreateableFields(metadataEntry);
@@ -593,7 +640,7 @@ class MetadataType {
593
640
  * @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
594
641
  * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
595
642
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
596
- * @returns {Promise} Promise
643
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
597
644
  */
598
645
  static async createSOAP(metadataEntry, overrideType, handleOutside) {
599
646
  try {
@@ -615,7 +662,7 @@ class MetadataType {
615
662
  return response;
616
663
  } catch (ex) {
617
664
  this._handleSOAPErrors(ex, 'creating', metadataEntry, handleOutside);
618
- return {};
665
+ return null;
619
666
  }
620
667
  }
621
668
 
@@ -624,12 +671,15 @@ class MetadataType {
624
671
  *
625
672
  * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
626
673
  * @param {string} uri rest endpoint for PATCH
627
- * @returns {Promise} Promise
674
+ * @param {boolean} [usePut] some update requests require PUT instead of PATCH
675
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
628
676
  */
629
- static async updateREST(metadataEntry, uri) {
677
+ static async updateREST(metadataEntry, uri, usePut) {
630
678
  this.removeNotUpdateableFields(metadataEntry);
631
679
  try {
632
- const response = await this.client.rest.patch(uri, metadataEntry);
680
+ const response = usePut
681
+ ? await this.client.rest.put(uri, metadataEntry)
682
+ : await this.client.rest.patch(uri, metadataEntry);
633
683
  this.checkForErrors(response);
634
684
  // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
635
685
  Util.logger.info(
@@ -660,7 +710,7 @@ class MetadataType {
660
710
  * @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
661
711
  * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
662
712
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
663
- * @returns {Promise} Promise
713
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
664
714
  */
665
715
  static async updateSOAP(metadataEntry, overrideType, handleOutside) {
666
716
  try {
@@ -681,7 +731,7 @@ class MetadataType {
681
731
  return response;
682
732
  } catch (ex) {
683
733
  this._handleSOAPErrors(ex, 'updating', metadataEntry, handleOutside);
684
- return {};
734
+ return null;
685
735
  }
686
736
  }
687
737
  /**
@@ -695,23 +745,31 @@ class MetadataType {
695
745
  if (handleOutside) {
696
746
  throw ex;
697
747
  } else {
698
- const errorMsg = ex?.json?.Results?.length
699
- ? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
700
- : ex.message;
748
+ const errorMsg = this.getSOAPErrorMsg(ex);
701
749
  const name = metadataEntry ? ` '${metadataEntry[this.definition.keyField]}'` : '';
702
750
  Util.logger.error(` ☇ error ${msg} ${this.definition.type}${name}: ${errorMsg}`);
703
751
  }
704
752
  }
753
+ /**
754
+ * helper for {@link _handleSOAPErrors}
755
+ *
756
+ * @param {Error} ex error that occured
757
+ * @returns {string} error message
758
+ */
759
+ static getSOAPErrorMsg(ex) {
760
+ return ex?.json?.Results?.length
761
+ ? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
762
+ : ex.message;
763
+ }
705
764
  /**
706
765
  * Retrieves SOAP via generic fuel-soap wrapper based metadata of metadata type into local filesystem. executes callback with retrieved metadata
707
766
  *
708
767
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
709
- * @param {TYPE.BuObject} buObject properties for auth
710
768
  * @param {TYPE.SoapRequestParams} [requestParams] required for the specific request (filter for example)
711
769
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
712
770
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of item map
713
771
  */
714
- static async retrieveSOAP(retrieveDir, buObject, requestParams, additionalFields) {
772
+ static async retrieveSOAP(retrieveDir, requestParams, additionalFields) {
715
773
  requestParams = requestParams || {};
716
774
  const fields = this.getFieldNamesToRetrieve(additionalFields);
717
775
  let response;
@@ -733,10 +791,10 @@ class MetadataType {
733
791
  `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
734
792
  );
735
793
  if (
736
- buObject &&
794
+ this.buObject &&
737
795
  this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)
738
796
  ) {
739
- await this.document(buObject, savedMetadata);
797
+ await this.document(savedMetadata);
740
798
  }
741
799
  }
742
800
  return { metadata: metadata, type: this.definition.type };
@@ -755,7 +813,7 @@ class MetadataType {
755
813
  static async retrieveREST(retrieveDir, uri, overrideType, templateVariables, singleRetrieve) {
756
814
  const response =
757
815
  this.definition.restPagination && !singleRetrieve
758
- ? await this.client.rest.getBulk(uri)
816
+ ? await this.client.rest.getBulk(uri, this.definition.restPageSize || 500)
759
817
  : await this.client.rest.get(uri);
760
818
  const results = this.parseResponseBody(response, singleRetrieve);
761
819
  // get extended metadata if applicable
@@ -770,11 +828,6 @@ class MetadataType {
770
828
  }
771
829
 
772
830
  if (retrieveDir) {
773
- // defined colors for optionally printing the keys we filtered by
774
- const color = {
775
- reset: '\x1B[0m',
776
- dim: '\x1B[2m',
777
- };
778
831
  const savedMetadata = await this.saveResults(
779
832
  results,
780
833
  retrieveDir,
@@ -784,10 +837,7 @@ class MetadataType {
784
837
  Util.logger.info(
785
838
  `Downloaded: ${overrideType || this.definition.type} (${
786
839
  Object.keys(savedMetadata).length
787
- })` +
788
- (singleRetrieve === null
789
- ? ''
790
- : ` ${color.dim}(Key: ${singleRetrieve})${color.reset}`)
840
+ })` + Util.getKeysString(singleRetrieve)
791
841
  );
792
842
  }
793
843
 
@@ -822,6 +872,10 @@ class MetadataType {
822
872
  }
823
873
  } else if (singleRetrieve) {
824
874
  // some types will return a single item intead of an array if the key is supported by their api
875
+ // ! currently, the id: prefix is only supported by journey (interaction)
876
+ if (singleRetrieve.startsWith('id:')) {
877
+ singleRetrieve = body[keyField];
878
+ }
825
879
  metadataStructure[singleRetrieve] = body;
826
880
  return metadataStructure;
827
881
  }
@@ -865,8 +919,8 @@ class MetadataType {
865
919
  // revert back placeholder to dots
866
920
  const originHelper = origin ? origin + '.' + fieldPath : fieldPath;
867
921
 
868
- if (this.definition.fields[originHelper]?.skipValidation) {
869
- // skip if current field should not be validated
922
+ if (this.definition.fields[originHelper]?.skipValidation || originHelper === '@_xsi:type') {
923
+ // skip if current field should not be validated OR if field is internal helper field xsi:type
870
924
  return;
871
925
  } else if (
872
926
  Array.isArray(fieldContent) &&
@@ -1062,7 +1116,7 @@ class MetadataType {
1062
1116
  metadataEntry.r__folder_Path
1063
1117
  );
1064
1118
  if (excludeByDefinition) {
1065
- Util.logger.debug(errorMsg + ' (project config)');
1119
+ Util.logger.debug(errorMsg + ' (Accenture SFMC DevTools default)');
1066
1120
  return true;
1067
1121
  }
1068
1122
 
@@ -1071,7 +1125,7 @@ class MetadataType {
1071
1125
  metadataEntry.r__folder_Path
1072
1126
  );
1073
1127
  if (excludeByConfig) {
1074
- Util.logger.debug(errorMsg + ' (Accenture SFMC DevTools default)');
1128
+ Util.logger.debug(errorMsg + ' (project config)');
1075
1129
  return true;
1076
1130
  }
1077
1131
  }
@@ -1298,7 +1352,7 @@ class MetadataType {
1298
1352
  */
1299
1353
  static applyTemplateValues(code, templateVariables) {
1300
1354
  // replace template variables with their values
1301
- return Mustache.render(code, templateVariables);
1355
+ return Mustache.render(code, templateVariables, {}, ['{{{', '}}}']);
1302
1356
  }
1303
1357
  /**
1304
1358
  * helper for {@link buildTemplateForNested}
@@ -1431,8 +1485,8 @@ class MetadataType {
1431
1485
  let metadata;
1432
1486
  try {
1433
1487
  // update all initial variables & create metadata object
1434
- metadata = JSON.parse(Mustache.render(metadataStr, variables));
1435
- typeDirArr = typeDirArr.map((el) => Mustache.render(el, variables));
1488
+ metadata = JSON.parse(Mustache.render(metadataStr, variables, {}, ['{{{', '}}}']));
1489
+ typeDirArr = typeDirArr.map((el) => Mustache.render(el, variables, {}, ['{{{', '}}}']));
1436
1490
  } catch {
1437
1491
  throw new Error(
1438
1492
  `${this.definition.type}:: Error applying template variables on ${
@@ -1478,7 +1532,7 @@ class MetadataType {
1478
1532
  * Standardizes a check for multiple messages
1479
1533
  *
1480
1534
  * @param {object} ex response payload from REST API
1481
- * @returns {string[]} formatted Error Message
1535
+ * @returns {string[] | void} formatted Error Message
1482
1536
  */
1483
1537
  static checkForErrors(ex) {
1484
1538
  if (ex?.response?.status >= 400 && ex?.response?.status < 600) {
@@ -1515,12 +1569,11 @@ class MetadataType {
1515
1569
  /**
1516
1570
  * Gets metadata cache with limited fields and does not store value to disk
1517
1571
  *
1518
- * @param {TYPE.BuObject} [buObject] properties for auth
1519
1572
  * @param {TYPE.MetadataTypeMap} [metadata] a list of type definitions
1520
1573
  * @param {boolean} [isDeploy] used to skip non-supported message during deploy
1521
1574
  * @returns {void}
1522
1575
  */
1523
- static document(buObject, metadata, isDeploy) {
1576
+ static document(metadata, isDeploy) {
1524
1577
  if (!isDeploy) {
1525
1578
  Util.logger.error(`Documenting type ${this.definition.type} is not supported.`);
1526
1579
  }
@@ -1529,27 +1582,25 @@ class MetadataType {
1529
1582
  /**
1530
1583
  * Delete a metadata item from the specified business unit
1531
1584
  *
1532
- * @param {TYPE.BuObject} buObject references credentials
1533
1585
  * @param {string} customerKey Identifier of data extension
1534
1586
  * @returns {boolean} deletion success status
1535
1587
  */
1536
- static deleteByKey(buObject, customerKey) {
1588
+ static deleteByKey(customerKey) {
1537
1589
  Util.logger.error(`Deletion is not yet supported for ${this.definition.typeName}!`);
1538
1590
  return false;
1539
1591
  }
1540
1592
  /**
1541
1593
  * clean up after deleting a metadata item
1542
1594
  *
1543
- * @param {TYPE.BuObject} buObject references credentials
1544
1595
  * @param {string} customerKey Identifier of metadata item
1545
1596
  * @returns {void}
1546
1597
  */
1547
- static async postDeleteTasks(buObject, customerKey) {
1598
+ static async postDeleteTasks(customerKey) {
1548
1599
  // delete local copy: retrieve/cred/bu/type/...json
1549
1600
  const jsonFile = File.normalizePath([
1550
1601
  this.properties.directories.retrieve,
1551
- buObject.credential,
1552
- buObject.businessUnit,
1602
+ this.buObject.credential,
1603
+ this.buObject.businessUnit,
1553
1604
  this.definition.type,
1554
1605
  `${customerKey}.${this.definition.type}-meta.json`,
1555
1606
  ]);
@@ -1559,12 +1610,11 @@ class MetadataType {
1559
1610
  /**
1560
1611
  * Delete a data extension from the specified business unit
1561
1612
  *
1562
- * @param {TYPE.BuObject} buObject references credentials
1563
1613
  * @param {string} customerKey Identifier of metadata
1564
1614
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1565
1615
  * @returns {boolean} deletion success flag
1566
1616
  */
1567
- static async deleteByKeySOAP(buObject, customerKey, handleOutside) {
1617
+ static async deleteByKeySOAP(customerKey, handleOutside) {
1568
1618
  const keyObj = {};
1569
1619
  keyObj[this.definition.keyField] = customerKey;
1570
1620
  try {
@@ -1576,7 +1626,7 @@ class MetadataType {
1576
1626
  if (!handleOutside) {
1577
1627
  Util.logger.info(`- deleted ${this.definition.type}: ${customerKey}`);
1578
1628
  }
1579
- this.postDeleteTasks(buObject, customerKey);
1629
+ this.postDeleteTasks(customerKey);
1580
1630
 
1581
1631
  return true;
1582
1632
  } catch (ex) {
@@ -1597,31 +1647,27 @@ class MetadataType {
1597
1647
  /**
1598
1648
  * Delete a data extension from the specified business unit
1599
1649
  *
1600
- * @param {TYPE.BuObject} buObject references credentials
1601
1650
  * @param {string} url endpoint
1602
1651
  * @param {string} key Identifier of metadata
1603
1652
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1604
1653
  * @returns {boolean} deletion success flag
1605
1654
  */
1606
- static async deleteByKeyREST(buObject, url, key, handleOutside) {
1655
+ static async deleteByKeyREST(url, key, handleOutside) {
1607
1656
  const keyObj = {};
1608
1657
  keyObj[this.definition.keyField] = key;
1609
1658
  try {
1610
- this.client.rest.delete(url);
1659
+ await this.client.rest.delete(url);
1611
1660
  if (!handleOutside) {
1612
1661
  Util.logger.info(`- deleted ${this.definition.type}: ${key}`);
1613
1662
  }
1614
- this.postDeleteTasks(buObject, key);
1663
+ this.postDeleteTasks(key);
1615
1664
 
1616
1665
  return true;
1617
1666
  } catch (ex) {
1618
1667
  if (handleOutside) {
1619
1668
  throw ex;
1620
1669
  } else {
1621
- const errorMsg = ex?.results?.length
1622
- ? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
1623
- : ex.message;
1624
- Util.logger.error(`- error deleting ${this.definition.type} '${key}': ${errorMsg}`);
1670
+ Util.logger.errorStack(ex, ` - Deleting ${this.definition.type} '${key}' failed`);
1625
1671
  }
1626
1672
 
1627
1673
  return false;
@@ -16,11 +16,10 @@ class MobileCode extends MetadataType {
16
16
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
17
17
  * @param {void} [_] unused parameter
18
18
  * @param {void} [__] unused parameter
19
- * @param {void} [___] unused parameter
20
19
  * @param {string} [key] customer key of single item to retrieve
21
20
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
22
21
  */
23
- static retrieve(retrieveDir, _, __, ___, key) {
22
+ static retrieve(retrieveDir, _, __, key) {
24
23
  return super.retrieveREST(
25
24
  retrieveDir,
26
25
  '/legacy/v1/beta/mobile/code/' + (key ? `?$where=keyword%20eq%20%27${key}%27%20` : ''),
@@ -19,11 +19,10 @@ class MobileKeyword extends MetadataType {
19
19
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
20
20
  * @param {void} [_] unused parameter
21
21
  * @param {void} [__] unused parameter
22
- * @param {void} [___] unused parameter
23
22
  * @param {string} [key] customer key of single item to retrieve
24
23
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
25
24
  */
26
- static retrieve(retrieveDir, _, __, ___, key) {
25
+ static retrieve(retrieveDir, _, __, key) {
27
26
  return super.retrieveREST(
28
27
  retrieveDir,
29
28
  '/legacy/v1/beta/mobile/keyword/?view=simple' +