mcdev 7.10.1 → 8.0.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 (166) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/code-test.yml +8 -100
  3. package/.github/workflows/coverage-base-update.yml +2 -2
  4. package/.github/workflows/coverage.yml +1 -1
  5. package/@types/lib/Deployer.d.ts.map +1 -1
  6. package/@types/lib/Retriever.d.ts.map +1 -1
  7. package/@types/lib/index.d.ts +17 -4
  8. package/@types/lib/index.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Asset.d.ts +46 -8
  10. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Automation.d.ts +14 -4
  12. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/DataExtension.d.ts +1 -0
  14. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/DataExtract.d.ts +1 -0
  17. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/Event.d.ts +8 -0
  19. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  20. package/@types/lib/metadataTypes/FileTransfer.d.ts +1 -0
  21. package/@types/lib/metadataTypes/FileTransfer.d.ts.map +1 -1
  22. package/@types/lib/metadataTypes/Folder.d.ts +49 -12
  23. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  24. package/@types/lib/metadataTypes/ImportFile.d.ts +15 -0
  25. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  26. package/@types/lib/metadataTypes/Journey.d.ts +166 -6
  27. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  28. package/@types/lib/metadataTypes/MetadataType.d.ts +6 -3
  29. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  30. package/@types/lib/metadataTypes/MobileKeyword.d.ts +1 -0
  31. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  32. package/@types/lib/metadataTypes/Query.d.ts +1 -0
  33. package/@types/lib/metadataTypes/Query.d.ts.map +1 -1
  34. package/@types/lib/metadataTypes/Script.d.ts +1 -0
  35. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  36. package/@types/lib/metadataTypes/definitions/Asset.definition.d.ts +45 -8
  37. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +6 -0
  38. package/@types/lib/metadataTypes/definitions/Event.definition.d.ts +6 -0
  39. package/@types/lib/metadataTypes/definitions/Folder.definition.d.ts +49 -12
  40. package/@types/lib/metadataTypes/definitions/ImportFile.definition.d.ts +3 -0
  41. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +159 -6
  42. package/@types/lib/util/cache.d.ts +2 -1
  43. package/@types/lib/util/cache.d.ts.map +1 -1
  44. package/@types/lib/util/devops.d.ts.map +1 -1
  45. package/@types/lib/util/util.d.ts +8 -1
  46. package/@types/lib/util/util.d.ts.map +1 -1
  47. package/@types/types/mcdev.d.d.ts +16 -0
  48. package/@types/types/mcdev.d.d.ts.map +1 -1
  49. package/boilerplate/files/.vscode/settings.json +5 -0
  50. package/eslint.config.js +2 -2
  51. package/lib/Deployer.js +3 -0
  52. package/lib/Retriever.js +8 -2
  53. package/lib/cli.js +29 -0
  54. package/lib/index.js +27 -31
  55. package/lib/metadataTypes/Asset.js +155 -17
  56. package/lib/metadataTypes/Automation.js +137 -40
  57. package/lib/metadataTypes/DataExtension.js +2 -10
  58. package/lib/metadataTypes/DataExtensionField.js +9 -15
  59. package/lib/metadataTypes/DataExtract.js +2 -0
  60. package/lib/metadataTypes/Event.js +63 -32
  61. package/lib/metadataTypes/FileTransfer.js +2 -0
  62. package/lib/metadataTypes/Folder.js +99 -27
  63. package/lib/metadataTypes/ImportFile.js +92 -29
  64. package/lib/metadataTypes/Journey.js +184 -24
  65. package/lib/metadataTypes/MetadataType.js +53 -25
  66. package/lib/metadataTypes/MobileKeyword.js +2 -0
  67. package/lib/metadataTypes/Query.js +2 -0
  68. package/lib/metadataTypes/Script.js +2 -0
  69. package/lib/metadataTypes/definitions/Asset.definition.js +196 -142
  70. package/lib/metadataTypes/definitions/Automation.definition.js +6 -0
  71. package/lib/metadataTypes/definitions/DataExtension.definition.js +7 -6
  72. package/lib/metadataTypes/definitions/Event.definition.js +6 -0
  73. package/lib/metadataTypes/definitions/Folder.definition.js +69 -22
  74. package/lib/metadataTypes/definitions/ImportFile.definition.js +3 -0
  75. package/lib/metadataTypes/definitions/Journey.definition.js +165 -11
  76. package/lib/util/cache.js +41 -8
  77. package/lib/util/devops.js +20 -5
  78. package/lib/util/file.js +10 -1
  79. package/lib/util/util.js +26 -12
  80. package/package.json +18 -19
  81. package/test/general.test.js +8 -8
  82. package/test/mockRoot/.mcdev-validations.js +2 -3
  83. package/test/mockRoot/.mcdevrc.json +1 -1
  84. package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +2 -1
  85. package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +2 -1
  86. package/test/resourceFactory.js +31 -5
  87. package/test/resources/1111111/dataExtension/update-expected.json +1 -1
  88. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.js +1 -0
  89. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.json +45 -0
  90. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.json +45 -0
  91. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.jsonc +1 -0
  92. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.json +44 -0
  93. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.xml +1 -0
  94. package/test/resources/9999999/asset/test_interactivecontent-retrieve-expected.json +41 -0
  95. package/test/resources/9999999/asset/test_landingpage-retrieve-expected.json +40 -0
  96. package/test/resources/9999999/asset/test_microsite-retrieve-expected.json +41 -0
  97. package/test/resources/9999999/asset/v1/content/assets/9451/get-response.json +61 -0
  98. package/test/resources/9999999/asset/v1/content/assets/9456/get-response.json +56 -0
  99. package/test/resources/9999999/asset/v1/content/assets/9458/get-response.json +56 -0
  100. package/test/resources/9999999/asset/v1/content/assets/9460/get-response.json +59 -0
  101. package/test/resources/9999999/asset/v1/content/assets/9463/get-response.json +61 -0
  102. package/test/resources/9999999/asset/v1/content/assets/9465/get-response.json +54 -0
  103. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN1,3,4,14,15,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,210,211,212,213,214,215,216,217,218,219,220,221,222.json +94 -1
  104. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232,240,241,242,243,244,245.json +168 -0
  105. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN240,241,242,243,244,245.json +144 -0
  106. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN246,247,248,249.json +132 -0
  107. package/test/resources/9999999/dataExtension/update-callout-afterCreatedViaEvent-expected.xml +1 -1
  108. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml} +44 -1
  109. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml} +44 -0
  110. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +44 -0
  111. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,journey-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,journey-response.xml} +44 -0
  112. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages,ssjsactivity-response.xml +136 -0
  113. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,ssjsactivity-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml} +29 -7
  114. package/test/resources/9999999/dataFolder/{+retrieve-response.xml → retrieve-response.xml} +23 -0
  115. package/test/resources/9999999/dataFolder/update-response.xml +31 -0
  116. package/test/resources/9999999/event/build-expected.json +0 -1
  117. package/test/resources/9999999/event/get-automation-expected.json +0 -1
  118. package/test/resources/9999999/event/get-expected.json +0 -2
  119. package/test/resources/9999999/event/get-published-expected.json +0 -2
  120. package/test/resources/9999999/event/post_withExistingDE-expected.json +0 -2
  121. package/test/resources/9999999/event/post_withSchema-callout-expected.json +3 -0
  122. package/test/resources/9999999/event/post_withSchema-expected.json +0 -2
  123. package/test/resources/9999999/event/put-callout-expected.json +3 -0
  124. package/test/resources/9999999/event/put-expected.json +0 -2
  125. package/test/resources/9999999/event/template-expected.json +0 -1
  126. package/test/resources/9999999/folder-deploy/Data Extensions/testExisting_folder.folder-meta.json +9 -0
  127. package/test/resources/9999999/importFile/get-dataImport-expected.json +1 -1
  128. package/test/resources/9999999/importFile/get-expected.json +4 -1
  129. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=1.json +86 -0
  130. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=2.json +101 -0
  131. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=3.json +86 -0
  132. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/delete-response-versionNumber=1.txt +1 -0
  133. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=1.json +461 -0
  134. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=3.json +461 -0
  135. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=1.json +38 -0
  136. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=2.json +53 -0
  137. package/test/resources/9999999/interaction/v1/interactions/get-response.json +2 -2
  138. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/get-response-versionNumber=1.json +461 -0
  139. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Quicksend/get-response-versionNumber=1.json +253 -0
  140. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/get-response-versionNumber=1.json +219 -0
  141. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/get-response-versionNumber=1.json +226 -0
  142. package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/post-response-versionNumber=3.json +4 -0
  143. package/test/resources/9999999/journey/build-expected.json +0 -1
  144. package/test/resources/9999999/journey/create-transactionaEmail-publish-expected.json +0 -1
  145. package/test/resources/9999999/journey/get-multistep-expected.json +0 -4
  146. package/test/resources/9999999/journey/get-published-expected.json +0 -1
  147. package/test/resources/9999999/journey/get-quicksend-expected.json +0 -1
  148. package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +0 -1
  149. package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +0 -1
  150. package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +0 -1
  151. package/test/resources/9999999/journey/get-transactionalEmail-expected.json +0 -1
  152. package/test/resources/9999999/journey/template-expected.json +0 -1
  153. package/test/resources/9999999/query/patch-expected.sql +1 -1
  154. package/test/resources/9999999/query/post-expected.sql +1 -1
  155. package/test/resources/9999999/script/patch-expected.ssjs +1 -0
  156. package/test/type.asset.test.js +123 -13
  157. package/test/type.automation.test.js +3 -1
  158. package/test/type.folder.test.js +12 -2
  159. package/test/type.journey.test.js +130 -1
  160. package/test/type.script.test.js +1 -0
  161. package/test/type.senderProfile.test.js +1 -0
  162. package/test/utils.js +2 -0
  163. package/types/mcdev.d.js +4 -0
  164. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232.json +0 -32
  165. /package/test/resources/9999999/dataFolder/{+retrieve-QAA-response.xml → retrieve-QAA-response.xml} +0 -0
  166. /package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/{post-response.json → post-response-versionNumber=1.json} +0 -0
@@ -182,7 +182,8 @@ class Journey extends MetadataType {
182
182
  // if the interaction does not exist, the API returns an error code which would otherwise bring execution to a hold
183
183
  if (
184
184
  [
185
- 'Interaction matching key not found.',
185
+ 'Interaction matching key not found.', // might be outdated
186
+ 'Interaction matching criteria not found.', // seen in 2025-05
186
187
  'Must provide a valid ID or Key parameter',
187
188
  ].includes(ex.message) ||
188
189
  (key && ex.code === 'ERR_BAD_REQUEST')
@@ -270,14 +271,14 @@ class Journey extends MetadataType {
270
271
  if (version !== '*') {
271
272
  if (!/^\d+$/.test(version)) {
272
273
  Util.logger.error(
273
- 'Version is required for deleting interactions to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
274
+ 'Version is required for deleting journeys to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
274
275
  key +
275
276
  '/4'
276
277
  );
277
278
  return false;
278
279
  }
279
280
  Util.logger.warn(
280
- `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all interactions on this BU.`
281
+ `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all journeys on this BU.`
281
282
  );
282
283
  }
283
284
 
@@ -639,6 +640,9 @@ class Journey extends MetadataType {
639
640
  * @param {MetadataTypeItem} metadata a single item
640
641
  */
641
642
  static _postRetrieveTasks_activities(metadata) {
643
+ if (!metadata.activities) {
644
+ return;
645
+ }
642
646
  for (const activity of metadata.activities) {
643
647
  switch (activity.type) {
644
648
  case 'EMAILV2': {
@@ -1407,9 +1411,7 @@ class Journey extends MetadataType {
1407
1411
  default: {
1408
1412
  // it is expected that we'll see 'sms' and 'push' here in the future
1409
1413
  throw new Error(
1410
- `channel ${
1411
- metadata.channel
1412
- } is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1414
+ `channel ${metadata.channel} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1413
1415
  );
1414
1416
  }
1415
1417
  }
@@ -1418,9 +1420,7 @@ class Journey extends MetadataType {
1418
1420
  }
1419
1421
  default: {
1420
1422
  throw new Error(
1421
- `definitionType ${
1422
- metadata.definitionType
1423
- } is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1423
+ `definitionType ${metadata.definitionType} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1424
1424
  );
1425
1425
  }
1426
1426
  }
@@ -2333,8 +2333,8 @@ class Journey extends MetadataType {
2333
2333
  case 'PublishCompleted': {
2334
2334
  spinner.stop();
2335
2335
  const action = statusUrl.includes('/validateStatus')
2336
- ? 'validation successful -'
2337
- : 'published';
2336
+ ? '🔎 validation successful -'
2337
+ : '🚀 published';
2338
2338
 
2339
2339
  Util.logger.info(` - ${action} ${this.definition.type}: ${key} / ${name}`);
2340
2340
  this._showPublishStatusDetails(response);
@@ -2567,6 +2567,159 @@ class Journey extends MetadataType {
2567
2567
  return executedKeyArr.filter(Boolean);
2568
2568
  }
2569
2569
 
2570
+ /**
2571
+ * audit latest or given journey version
2572
+ *
2573
+ * @param {string[]} keyArr customerkey of the metadata
2574
+ * @returns {Promise.<string[]>} Returns list of keys that were paused
2575
+ */
2576
+ static async audit(keyArr) {
2577
+ let version;
2578
+ const endpoint = '/interaction/v1/interactions/';
2579
+ const auditedKeyArr = [];
2580
+ const apiLimit = pLimit(20);
2581
+ const journeyCache = await this.retrieveForCache();
2582
+
2583
+ const ignoredLogProperties = [
2584
+ 'executionMode',
2585
+ 'id',
2586
+ 'originalDefinitionId',
2587
+ 'publishRequestId',
2588
+ ];
2589
+ const preformatedProperties = [
2590
+ 'action',
2591
+ 'description',
2592
+ 'errors',
2593
+ 'key',
2594
+ 'name',
2595
+ 'publishedVersion',
2596
+ 'publishStatus',
2597
+ 'timeStamp',
2598
+ 'user',
2599
+ 'versionNumber',
2600
+ ];
2601
+
2602
+ await Promise.allSettled(
2603
+ keyArr.map((key) =>
2604
+ apiLimit(async () => {
2605
+ [key, version] = key.split('/');
2606
+ if (!journeyCache.metadata[key]) {
2607
+ Util.logger.error(
2608
+ ` ☇ skipping audit log of ${this.definition.type} ${key}: not found on server`
2609
+ );
2610
+ return;
2611
+ }
2612
+ const toBeAuditedVersions = [];
2613
+ if (version === '*' || !version) {
2614
+ switch (journeyCache.metadata[key].definitionType) {
2615
+ case 'Transactional':
2616
+ case 'Multistep': {
2617
+ // transactional send journeys technically only have one version, but if you delete them and then create one with the same key their version number goes up and old audit logs are still available
2618
+ // multi-step journey versions could be retrieved but then we lose info of any versions that might have been deleted. Therefore we add all versions starting with the latest and counting down to 1
2619
+ for (let i = journeyCache.metadata[key].version; i > 0; i--) {
2620
+ toBeAuditedVersions.push(i);
2621
+ }
2622
+ }
2623
+ }
2624
+ } else {
2625
+ toBeAuditedVersions.push(version);
2626
+ }
2627
+
2628
+ Util.logger.info(
2629
+ ` - Audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name}`
2630
+ );
2631
+ const rateLimitActivities = pLimit(1);
2632
+ const auditedVersions = (
2633
+ await Promise.all(
2634
+ toBeAuditedVersions.map((version) =>
2635
+ rateLimitActivities(async () => {
2636
+ try {
2637
+ const response = await this.client.rest.getBulk(
2638
+ endpoint +
2639
+ journeyCache.metadata[key].id +
2640
+ `/audit/all?versionNumber=${version}`
2641
+ );
2642
+
2643
+ Util.logger.info(
2644
+ ` - Version ${version}:\n - ` +
2645
+ response.items
2646
+ .map((log) => {
2647
+ let msg =
2648
+ `${log.action} version ${log.versionNumber} - ${Util.getGrayMsg(log.timeStamp.replace('T', ' ').slice(0, 19))}: ${log.user.name}\n` +
2649
+ (log.publishedVersion
2650
+ ? ` Published Version: ` +
2651
+ log.publishedVersion +
2652
+ '\n'
2653
+ : '') +
2654
+ (log.publishStatus
2655
+ ? ` Publish Status: ` +
2656
+ log.publishStatus +
2657
+ '\n'
2658
+ : '') +
2659
+ (log.key === key
2660
+ ? ''
2661
+ : ` Key: ` + log.key + '\n') +
2662
+ (log.name ===
2663
+ journeyCache.metadata[key].name
2664
+ ? ''
2665
+ : ` Key: ` + log.key + '\n') +
2666
+ (log.errors
2667
+ ? ` Errors:\n • ` +
2668
+ log.errors
2669
+ .map((error) =>
2670
+ error.ErrorDetail.replaceAll(
2671
+ /[\r\n]/g,
2672
+ ' '
2673
+ )
2674
+ )
2675
+ .join('\n • ') +
2676
+ '\n'
2677
+ : '');
2678
+ for (const key in log) {
2679
+ if (
2680
+ !ignoredLogProperties.includes(
2681
+ key
2682
+ ) &&
2683
+ !preformatedProperties.includes(key)
2684
+ ) {
2685
+ msg +=
2686
+ ` ${key}: ` +
2687
+ log[key] +
2688
+ '\n';
2689
+ }
2690
+ }
2691
+ return msg;
2692
+ })
2693
+ .join(' - ')
2694
+ );
2695
+ // Util.logger.info(JSON.stringify(auditLog, null, 2));
2696
+ return version;
2697
+ } catch (ex) {
2698
+ if (journeyCache.metadata[key].version > version) {
2699
+ Util.logger.error(
2700
+ ` - Retrieving audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: Version was not found. The highest available version seems to be ${journeyCache.metadata[key].version}`
2701
+ );
2702
+ } else {
2703
+ Util.logger.error(
2704
+ ` - Retrieving audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2705
+ );
2706
+ }
2707
+ return;
2708
+ }
2709
+ })
2710
+ )
2711
+ )
2712
+ ).filter(Boolean);
2713
+ if (auditedVersions.length === toBeAuditedVersions.length) {
2714
+ auditedKeyArr.push(key);
2715
+ }
2716
+ })
2717
+ )
2718
+ );
2719
+
2720
+ return auditedKeyArr;
2721
+ }
2722
+
2570
2723
  /**
2571
2724
  * stops latest journey version
2572
2725
  *
@@ -2577,11 +2730,11 @@ class Journey extends MetadataType {
2577
2730
  let version;
2578
2731
  const endpoint = '/interaction/v1/interactions/stop/';
2579
2732
  const stoppedKeyArr = [];
2580
- const pauseTransactionalKeyArr = [];
2733
+ const stopTransactionalKeyArr = [];
2581
2734
  const apiLimit = pLimit(20);
2582
2735
  const journeyCache = await this.retrieveForCache();
2583
2736
 
2584
- const stoppableJourneyStatus = ['Paused', 'Published', 'Finishing'];
2737
+ const stoppableJourneyStatus = ['Paused', 'Published', 'Unpublished']; // 'Unpublished' is shown as 'Finishing' in the UI
2585
2738
 
2586
2739
  await Promise.allSettled(
2587
2740
  keyArr.map((key) =>
@@ -2591,7 +2744,7 @@ class Journey extends MetadataType {
2591
2744
  switch (journeyCache.metadata[key].definitionType) {
2592
2745
  case 'Transactional': {
2593
2746
  // transactional send journeys cannot be "stopped" but only "paused"
2594
- pauseTransactionalKeyArr.push(key);
2747
+ stopTransactionalKeyArr.push(key);
2595
2748
  break;
2596
2749
  }
2597
2750
  case 'Multistep': {
@@ -2652,12 +2805,12 @@ class Journey extends MetadataType {
2652
2805
  {}
2653
2806
  );
2654
2807
  Util.logger.info(
2655
- ` - Stopped ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version}`
2808
+ ` - stopped ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version}`
2656
2809
  );
2657
2810
  return version;
2658
2811
  } catch (ex) {
2659
2812
  Util.logger.error(
2660
- ` - Stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2813
+ ` - stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2661
2814
  );
2662
2815
  return;
2663
2816
  }
@@ -2685,7 +2838,7 @@ class Journey extends MetadataType {
2685
2838
  })
2686
2839
  )
2687
2840
  );
2688
- stoppedKeyArr.push(...(await this.pause(pauseTransactionalKeyArr)));
2841
+ stoppedKeyArr.push(...(await this.pause(stopTransactionalKeyArr)));
2689
2842
 
2690
2843
  return stoppedKeyArr;
2691
2844
  }
@@ -2708,16 +2861,20 @@ class Journey extends MetadataType {
2708
2861
  apiLimit(async () => {
2709
2862
  [key, version] = key.split('/');
2710
2863
  if (journeyCache.metadata[key]) {
2711
- if (journeyCache.metadata[key].status === 'Paused') {
2864
+ if (
2865
+ (!version || journeyCache.metadata[key].version == version) &&
2866
+ journeyCache.metadata[key].status === 'Paused'
2867
+ ) {
2712
2868
  Util.logger.info(
2713
- ` ${this.definition.type} ${
2714
- key
2715
- } (${journeyCache.metadata[key].name}): already paused`
2869
+ ` ${this.definition.type} ${key} (${journeyCache.metadata[key].name}): already paused`
2716
2870
  );
2717
2871
  // still add this key because technically this method is supposed to pause a journey and this journey is paused. mission accomplished. Also, we need that for _refreshItem() to function
2718
2872
  pausedKeyArr.push(key);
2719
2873
  return;
2720
- } else if (journeyCache.metadata[key].status !== 'Published') {
2874
+ } else if (
2875
+ (!version || journeyCache.metadata[key].version == version) &&
2876
+ journeyCache.metadata[key].status !== 'Published'
2877
+ ) {
2721
2878
  Util.logger.error(
2722
2879
  ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2723
2880
  );
@@ -2757,7 +2914,7 @@ class Journey extends MetadataType {
2757
2914
  {}
2758
2915
  );
2759
2916
  Util.logger.info(
2760
- ` -- 🛑 paused ${this.definition.type} ${key}/${version} / ${journeyCache.metadata[key].name}`
2917
+ ` - 🛑 paused ${this.definition.type} ${key}/${version} / ${journeyCache.metadata[key].name}`
2761
2918
  );
2762
2919
  pausedKeyArr.push(key);
2763
2920
  } catch (ex) {
@@ -2803,7 +2960,10 @@ class Journey extends MetadataType {
2803
2960
  apiLimit(async () => {
2804
2961
  [key, version] = key.split('/');
2805
2962
  if (journeyCache.metadata[key]) {
2806
- if (journeyCache.metadata[key].status !== 'Paused') {
2963
+ if (
2964
+ (!version || journeyCache.metadata[key].version == version) &&
2965
+ journeyCache.metadata[key].status !== 'Paused'
2966
+ ) {
2807
2967
  Util.logger.error(
2808
2968
  ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2809
2969
  );
@@ -299,7 +299,7 @@ class MetadataType {
299
299
  * @returns {Promise.<MetadataTypeMapObj>} metadata
300
300
  */
301
301
  static async retrieve(retrieveDir, additionalFields, subTypeArr, key) {
302
- Util.notSupportedError(this.definition, 'retrieve');
302
+ Util.logNotSupported(this.definition, 'retrieve');
303
303
  return { metadata: {}, type: this.definition.type };
304
304
  }
305
305
 
@@ -329,6 +329,7 @@ class MetadataType {
329
329
  /**
330
330
  * Gets metadata cache with limited fields and does not store value to disk
331
331
  *
332
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
332
333
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
333
334
  * @param {string} name name of the metadata file
334
335
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
@@ -336,7 +337,8 @@ class MetadataType {
336
337
  * @returns {Promise.<MetadataTypeItemObj>} metadata
337
338
  */
338
339
  static async retrieveAsTemplate(templateDir, name, templateVariables, subType) {
339
- Util.notSupportedError(this.definition, 'retrieveAsTemplate');
340
+ Util.logNotSupported(this.definition, 'retrieveAsTemplate');
341
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
340
342
  return { metadata: null, type: this.definition.type };
341
343
  }
342
344
 
@@ -499,7 +501,7 @@ class MetadataType {
499
501
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
500
502
  */
501
503
  static async create(metadata, deployDir) {
502
- Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
504
+ Util.logNotSupported(this.definition, 'create', metadata);
503
505
  return;
504
506
  }
505
507
 
@@ -511,7 +513,7 @@ class MetadataType {
511
513
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
512
514
  */
513
515
  static async update(metadata, metadataBefore) {
514
- Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
516
+ Util.logNotSupported(this.definition, 'update', metadata);
515
517
  return;
516
518
  }
517
519
 
@@ -524,7 +526,7 @@ class MetadataType {
524
526
  * @returns {Promise.<string[]>} Returns list of keys that were refreshed
525
527
  */
526
528
  static async refresh(keyArr, checkKey = true, upsertResults) {
527
- Util.notSupportedError(this.definition, 'refresh');
529
+ Util.logNotSupported(this.definition, 'refresh');
528
530
  return [];
529
531
  }
530
532
 
@@ -630,7 +632,7 @@ class MetadataType {
630
632
  * @returns {Promise.<MetadataTypeItem | CodeExtractItem>} key of the item that was updated
631
633
  */
632
634
  static async replaceCbReference(item, retrieveDir, findAssetKeys) {
633
- Util.notSupportedError(this.definition, 'replaceCbReference');
635
+ Util.logNotSupported(this.definition, 'replaceCbReference');
634
636
  return [];
635
637
  }
636
638
 
@@ -642,7 +644,7 @@ class MetadataType {
642
644
  * @returns {Promise.<string[]>} Returns list of keys that were executed
643
645
  */
644
646
  static async execute(keyArr, cache) {
645
- Util.notSupportedError(this.definition, 'execute');
647
+ Util.logNotSupported(this.definition, 'execute');
646
648
  return [];
647
649
  }
648
650
 
@@ -654,7 +656,7 @@ class MetadataType {
654
656
  * @returns {Promise.<string[]>} Returns list of keys that were executed
655
657
  */
656
658
  static async schedule(keyArr, cache) {
657
- Util.notSupportedError(this.definition, 'schedule');
659
+ Util.logNotSupported(this.definition, 'schedule');
658
660
  return [];
659
661
  }
660
662
 
@@ -666,7 +668,7 @@ class MetadataType {
666
668
  * @returns {Promise.<string[]>} Returns list of keys that were paused
667
669
  */
668
670
  static async pause(keyArr, cache) {
669
- Util.notSupportedError(this.definition, 'pause');
671
+ Util.logNotSupported(this.definition, 'pause');
670
672
  return [];
671
673
  }
672
674
 
@@ -677,7 +679,7 @@ class MetadataType {
677
679
  * @returns {Promise.<string[]>} Returns list of keys that were stopped
678
680
  */
679
681
  static async stop(keyArr) {
680
- Util.notSupportedError(this.definition, 'stop');
682
+ Util.logNotSupported(this.definition, 'stop');
681
683
  return [];
682
684
  }
683
685
 
@@ -1251,9 +1253,10 @@ class MetadataType {
1251
1253
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
1252
1254
  * @param {string} uri rest endpoint for PATCH
1253
1255
  * @param {'patch'|'post'|'put'} [httpMethod] defaults to 'patch'; some update requests require PUT instead of PATCH
1256
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1254
1257
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
1255
1258
  */
1256
- static async updateREST(metadataEntry, uri, httpMethod = 'patch') {
1259
+ static async updateREST(metadataEntry, uri, httpMethod = 'patch', handleOutside) {
1257
1260
  const metadataClone = structuredClone(metadataEntry);
1258
1261
  this.removeNotUpdateableFields(metadataEntry);
1259
1262
  try {
@@ -1263,7 +1266,11 @@ class MetadataType {
1263
1266
  this.getErrorsREST(response);
1264
1267
  response = await this.postUpdateTasks(metadataEntry, response, metadataClone);
1265
1268
  // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
1266
- Util.logger.info(` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`);
1269
+ if (!handleOutside) {
1270
+ Util.logger.info(
1271
+ ` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`
1272
+ );
1273
+ }
1267
1274
  return response;
1268
1275
  } catch (ex) {
1269
1276
  const parsedErrors = this.getErrorsREST(ex);
@@ -1435,15 +1442,35 @@ class MetadataType {
1435
1442
  const results = this.parseResponseBody(response, singleRetrieve);
1436
1443
  // get extended metadata if applicable
1437
1444
  if (this.definition.hasExtended) {
1438
- Util.logger.debug(' - retrieving extended metadata');
1439
- const extended = await this.client.rest.getCollection(
1445
+ const amount = Object.keys(results).length;
1446
+ if (amount > 100) {
1447
+ Util.logger.info(
1448
+ Util.getGrayMsg(
1449
+ ` - retrieving extended metadata for ${amount} ${this.definition.type}s...`
1450
+ )
1451
+ );
1452
+ } else {
1453
+ Util.logger.debug(' - retrieving extended metadata');
1454
+ }
1455
+ const rateLimit = pLimit(this.definition.restConcurrentLimit || 5);
1456
+ const extended = await Promise.all(
1440
1457
  Object.keys(results)
1441
- .map((key) =>
1442
- uri.endsWith(results[key][this.definition.idField])
1458
+ .map((key) => {
1459
+ const url = uri.endsWith(results[key][this.definition.idField])
1443
1460
  ? null
1444
- : uri + results[key][this.definition.idField]
1445
- )
1461
+ : uri + results[key][this.definition.idField];
1462
+ return url ? { url, key } : null;
1463
+ })
1446
1464
  .filter(Boolean)
1465
+ .map((el) =>
1466
+ rateLimit(async () => {
1467
+ try {
1468
+ return await this.client.rest.get(el.url);
1469
+ } catch (ex) {
1470
+ return await this.handleRESTErrors(ex, el.key, el.url);
1471
+ }
1472
+ })
1473
+ )
1447
1474
  );
1448
1475
  for (const ext of extended) {
1449
1476
  const key = ext[this.definition.keyField];
@@ -1501,7 +1528,7 @@ class MetadataType {
1501
1528
  try {
1502
1529
  return await this.client.rest.get(item.uri);
1503
1530
  } catch (ex) {
1504
- return this.handleRESTErrors(ex, item.id);
1531
+ return await this.handleRESTErrors(ex, item.id, item.uri);
1505
1532
  }
1506
1533
  })
1507
1534
  )
@@ -1523,12 +1550,13 @@ class MetadataType {
1523
1550
  * helper for {@link this.retrieveRESTcollection}
1524
1551
  *
1525
1552
  * @param {RestError} ex exception
1526
- * @param {string} id id or key of item
1553
+ * @param {string} key id or key of item
1554
+ * @param {string} url url to call for retry
1527
1555
  * @returns {Promise.<any>} -
1528
1556
  */
1529
- static async handleRESTErrors(ex, id) {
1557
+ static async handleRESTErrors(ex, key, url) {
1530
1558
  // if the ID is too short, the system will throw the 400 error
1531
- Util.logger.debug(` ☇ skipping ${this.definition.type} ${id}: ${ex.message} ${ex.code}`);
1559
+ Util.logger.debug(` ☇ skipping ${this.definition.type} ${key}: ${ex.message} ${ex.code}`);
1532
1560
 
1533
1561
  return null;
1534
1562
  }
@@ -2438,7 +2466,7 @@ class MetadataType {
2438
2466
  */
2439
2467
  static document(metadata, isDeploy) {
2440
2468
  if (!isDeploy) {
2441
- Util.notSupportedError(this.definition, 'document');
2469
+ Util.logNotSupported(this.definition, 'document');
2442
2470
  }
2443
2471
  }
2444
2472
 
@@ -2449,7 +2477,7 @@ class MetadataType {
2449
2477
  * @returns {Promise.<{key:string, name:string}>} key, name and path of metadata; null if not found
2450
2478
  */
2451
2479
  static async resolveId(id) {
2452
- Util.notSupportedError(this.definition, 'resolveId');
2480
+ Util.logNotSupported(this.definition, 'resolveId');
2453
2481
  return;
2454
2482
  }
2455
2483
 
@@ -2460,7 +2488,7 @@ class MetadataType {
2460
2488
  * @returns {Promise.<boolean>} deletion success status
2461
2489
  */
2462
2490
  static async deleteByKey(customerKey) {
2463
- Util.notSupportedError(this.definition, 'delete');
2491
+ Util.logNotSupported(this.definition, 'delete');
2464
2492
  return false;
2465
2493
  }
2466
2494
 
@@ -137,12 +137,14 @@ class MobileKeyword extends MetadataType {
137
137
  /**
138
138
  * retrieve an item and create a template from it
139
139
  *
140
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
140
141
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
141
142
  * @param {string} key name of the metadata file
142
143
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
143
144
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
144
145
  */
145
146
  static async retrieveAsTemplate(templateDir, key, templateVariables) {
147
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
146
148
  try {
147
149
  let queryParams;
148
150
  [key, queryParams] = this.#getRetrieveKeyAndUrl(key);
@@ -127,12 +127,14 @@ class Query extends MetadataType {
127
127
  /**
128
128
  * Retrieve a specific Query by Name
129
129
  *
130
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
130
131
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
131
132
  * @param {string} name name of the metadata file
132
133
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
133
134
  * @returns {Promise.<{metadata: Query, type: string}>} Promise of metadata
134
135
  */
135
136
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
137
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
136
138
  await File.initPrettier('sql');
137
139
  return super.retrieveREST(
138
140
  templateDir,
@@ -57,12 +57,14 @@ class Script extends MetadataType {
57
57
  /**
58
58
  * Retrieve a specific Script by Name
59
59
  *
60
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
60
61
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
61
62
  * @param {string} name name of the metadata file
62
63
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
63
64
  * @returns {Promise.<{metadata: ScriptItem, type: string}>} Promise
64
65
  */
65
66
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
67
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
66
68
  await File.initPrettier('ssjs');
67
69
  return super.retrieveREST(
68
70
  templateDir,