mcdev 5.1.0 → 5.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 (256) hide show
  1. package/.eslintrc.json +4 -4
  2. package/.fork/custom-commands.json +12 -0
  3. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  4. package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +19 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +2 -2
  6. package/.github/workflows/coverage-develop-branch.yml +0 -2
  7. package/.github/workflows/coverage-main-branch.yml +0 -2
  8. package/.github/workflows/coverage.yml +0 -2
  9. package/.husky/post-checkout +1 -0
  10. package/.husky/post-merge +1 -0
  11. package/.vscode/extensions.json +4 -0
  12. package/docs/dist/documentation.md +1034 -296
  13. package/lib/Deployer.js +25 -25
  14. package/lib/MetadataTypeDefinitions.js +2 -1
  15. package/lib/MetadataTypeInfo.js +2 -1
  16. package/lib/Retriever.js +1 -1
  17. package/lib/cli.js +165 -10
  18. package/lib/index.js +398 -95
  19. package/lib/metadataTypes/Asset.js +10 -11
  20. package/lib/metadataTypes/AttributeGroup.js +76 -2
  21. package/lib/metadataTypes/AttributeSet.js +367 -0
  22. package/lib/metadataTypes/Automation.js +483 -137
  23. package/lib/metadataTypes/DataExtension.js +465 -68
  24. package/lib/metadataTypes/DataExtensionField.js +31 -14
  25. package/lib/metadataTypes/Event.js +2 -3
  26. package/lib/metadataTypes/Folder.js +1 -1
  27. package/lib/metadataTypes/Journey.js +13 -7
  28. package/lib/metadataTypes/MetadataType.js +212 -54
  29. package/lib/metadataTypes/MobileKeyword.js +9 -9
  30. package/lib/metadataTypes/MobileMessage.js +5 -5
  31. package/lib/metadataTypes/Query.js +26 -10
  32. package/lib/metadataTypes/Script.js +3 -3
  33. package/lib/metadataTypes/TransactionalEmail.js +94 -17
  34. package/lib/metadataTypes/TransactionalMessage.js +3 -2
  35. package/lib/metadataTypes/TransactionalSMS.js +5 -5
  36. package/lib/metadataTypes/TriggeredSend.js +25 -50
  37. package/lib/metadataTypes/User.js +7 -4
  38. package/lib/metadataTypes/Verification.js +230 -0
  39. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +119 -108
  40. package/lib/metadataTypes/definitions/{SetDefinition.definition.js → AttributeSet.definition.js} +123 -43
  41. package/lib/metadataTypes/definitions/Automation.definition.js +23 -15
  42. package/lib/metadataTypes/definitions/ImportFile.definition.js +36 -6
  43. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +19 -1
  44. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +1 -0
  45. package/lib/metadataTypes/definitions/Verification.definition.js +88 -0
  46. package/lib/util/cache.js +9 -4
  47. package/lib/util/cli.js +40 -0
  48. package/lib/util/file.js +2 -2
  49. package/lib/util/init.js +84 -0
  50. package/lib/util/util.js +121 -13
  51. package/package.json +13 -13
  52. package/test/mockRoot/.mcdevrc.json +1 -1
  53. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testExisting_dataExtensionShared.dataExtension-meta.json +59 -0
  54. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testNew_dataExtensionShared.dataExtension-meta.json +23 -0
  55. package/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +1 -2
  56. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +9 -6
  57. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testExisting_dataExtension.dataExtension-meta.json +1 -0
  58. package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testExisting_dataExtract.dataExtract-meta.json +35 -0
  59. package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testNew_dataExtract.dataExtract-meta.json +35 -0
  60. package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testExisting_fileTransfer.fileTransfer-meta.json +17 -0
  61. package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testNew_fileTransfer.fileTransfer-meta.json +17 -0
  62. package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +29 -0
  63. package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +29 -0
  64. package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.json +11 -0
  65. package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.sql +6 -0
  66. package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.json +6 -0
  67. package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.ssjs +1 -0
  68. package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.json +6 -0
  69. package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.ssjs +1 -0
  70. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +3 -4
  71. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -6
  72. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +29 -0
  73. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +29 -0
  74. package/test/mockRoot/deploy/testInstance/testBU/verification/testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  75. package/test/mockRoot/deploy/testInstance/testBU/verification/testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  76. package/test/resourceFactory.js +77 -12
  77. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDCustomerKey=testExisting_userANDEmaillike@-response.xml +27 -0
  78. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDEmaillike@-response.xml +156 -0
  79. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +87 -0
  80. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmaillike@-response.xml +156 -0
  81. package/test/resources/1111111/accountUser/retrieve-CustomerKey=testExisting_userANDActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +27 -0
  82. package/test/resources/1111111/accountUserAccount/retrieve-AccountUser.AccountUserID=700301950-response.xml +60 -0
  83. package/test/resources/1111111/data/v1/customobjectdata/key/testExisting_dataExtensionShared/rowset/get-response.json +13 -0
  84. package/test/resources/1111111/dataExtension/create-expected.json +23 -0
  85. package/test/resources/1111111/dataExtension/create-response.xml +59 -0
  86. package/test/resources/1111111/dataExtension/retrieve-expected.json +55 -0
  87. package/test/resources/1111111/dataExtension/retrieve-expected.md +18 -0
  88. package/test/resources/1111111/dataExtension/retrieve-response.xml +27 -1
  89. package/test/resources/1111111/dataExtension/update-expected.json +55 -0
  90. package/test/resources/1111111/dataExtension/update-response.xml +57 -0
  91. package/test/resources/1111111/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtensionShared].[TriggerUpdate_randomNumber_]-response.xml +45 -0
  92. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  93. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionSharedORDataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  94. package/test/resources/1111111/dataExtensionField/retrieve-response.xml +98 -0
  95. package/test/resources/1111111/dataExtensionTemplate/retrieve-response.xml +303 -0
  96. package/test/resources/1111111/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +387 -0
  97. package/test/resources/1111111/dataFolder/retrieve-response.xml +353 -9
  98. package/test/resources/1111111/user/retrieve-expected.md +4 -2
  99. package/test/resources/9999999/attributeGroup/retrieve-expected.json +25 -0
  100. package/test/resources/9999999/attributeSet/retrieve-expected.json +143 -0
  101. package/test/resources/9999999/automation/build-expected.json +5 -2
  102. package/test/resources/9999999/automation/create-expected.json +11 -8
  103. package/test/resources/9999999/automation/create-testNew_automation-expected.md +5 -4
  104. package/test/resources/9999999/automation/patch_fixKeys-pause-expected.json +44 -0
  105. package/test/resources/9999999/automation/patch_fixKeys-schedule-expected.json +44 -0
  106. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
  107. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_pause-response.xml +42 -0
  108. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +42 -0
  109. package/test/resources/9999999/automation/perform-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
  110. package/test/resources/9999999/automation/retrieve-expected.json +5 -2
  111. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +3 -2
  112. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
  113. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml +38 -0
  114. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_pause-response.xml +52 -0
  115. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +52 -0
  116. package/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
  117. package/test/resources/9999999/automation/template-expected.json +5 -2
  118. package/test/resources/9999999/automation/update-expected.json +1 -2
  119. package/test/resources/9999999/automation/update-testExisting_automation-expected.md +2 -2
  120. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +7 -0
  121. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json +85 -0
  122. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/patch-response.json +85 -0
  123. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/get-response.json +85 -0
  124. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/patch-response.json +85 -0
  125. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/get-response.json +85 -0
  126. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/patch-response.json +85 -0
  127. package/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +1 -1
  128. package/test/resources/9999999/automation/v1/automations/post-response.json +27 -19
  129. package/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/patch-response.json +38 -0
  130. package/test/resources/9999999/automation/v1/dataextracts/post-response.json +38 -0
  131. package/test/resources/9999999/automation/v1/dataextracttypes/get-response.json +50 -0
  132. package/test/resources/9999999/automation/v1/dataverifications/post-response.json +12 -0
  133. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/delete-response.json +0 -0
  134. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/get-response.json +12 -0
  135. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/patch-response.json +12 -0
  136. package/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/patch-response.json +18 -0
  137. package/test/resources/9999999/automation/v1/filetransfers/post-response.json +18 -0
  138. package/test/resources/9999999/automation/v1/ftplocations/get-response.json +18 -0
  139. package/test/resources/9999999/automation/v1/imports/9d16f42c-2260-ed11-b849-48df37d1de8b/patch-response.json +31 -0
  140. package/test/resources/9999999/automation/v1/imports/get-response.json +1 -1
  141. package/test/resources/9999999/automation/v1/imports/post-response.json +30 -0
  142. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dae/actions/start/post-response.txt +1 -0
  143. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/get-response.json +17 -0
  144. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/patch-response.json +18 -0
  145. package/test/resources/9999999/automation/v1/queries/get-response.json +18 -1
  146. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +10 -0
  147. package/test/resources/9999999/automation/v1/scripts/get-response.json +12 -2
  148. package/test/resources/9999999/automation/v1/scripts/post-response.json +10 -0
  149. package/test/resources/9999999/dataExtension/build-expected.json +16 -0
  150. package/test/resources/9999999/dataExtension/delete-response.xml +42 -0
  151. package/test/resources/9999999/dataExtension/retrieve-Name=testExisting_dataExtension-response.xml +52 -0
  152. package/test/resources/9999999/dataExtension/retrieve-expected.json +16 -0
  153. package/test/resources/9999999/dataExtension/retrieve-expected.md +3 -1
  154. package/test/resources/9999999/dataExtension/template-expected.json +16 -0
  155. package/test/resources/9999999/dataExtension/update-expected.json +17 -1
  156. package/test/resources/9999999/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtension].[LastName]-response.xml +44 -0
  157. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +133 -0
  158. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionORDataExtension.CustomerKey=testExisting_dataExtension-response.xml +99 -0
  159. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +36 -1
  160. package/test/resources/9999999/dataExtract/build-expected.json +35 -0
  161. package/test/resources/9999999/dataExtract/get-expected.json +39 -0
  162. package/test/resources/9999999/dataExtract/patch-expected.json +37 -0
  163. package/test/resources/9999999/dataExtract/post-expected.json +37 -0
  164. package/test/resources/9999999/dataExtract/template-expected.json +35 -0
  165. package/test/resources/9999999/dataFolder/retrieve-ContentType=contextual_suppression_listORContentType=publicationORContentType=suppression_listORContentType=mysubsORContentType=list-response.xml +136 -0
  166. package/test/resources/9999999/dataFolder/retrieve-ContentType=ssjsactivity-response.xml +48 -0
  167. package/test/resources/9999999/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +117 -0
  168. package/test/resources/9999999/dataFolder/retrieve-ContentType=triggered_send_journeybuilderORContentType=triggered_sendORContentType=hidden-response.xml +276 -0
  169. package/test/resources/9999999/dataFolder/retrieve-response.xml +23 -0
  170. package/test/resources/9999999/email/retrieve-response.xml +203 -0
  171. package/test/resources/9999999/fileTransfer/build-expected.json +15 -0
  172. package/test/resources/9999999/fileTransfer/get-expected.json +17 -0
  173. package/test/resources/9999999/fileTransfer/patch-expected.json +17 -0
  174. package/test/resources/9999999/fileTransfer/post-expected.json +17 -0
  175. package/test/resources/9999999/fileTransfer/template-expected.json +15 -0
  176. package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +628 -0
  177. package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +20194 -0
  178. package/test/resources/9999999/importFile/build-expected.json +27 -0
  179. package/test/resources/9999999/importFile/get-expected.json +29 -0
  180. package/test/resources/9999999/importFile/patch-expected.json +29 -0
  181. package/test/resources/9999999/importFile/post-expected.json +29 -0
  182. package/test/resources/9999999/importFile/template-expected.json +27 -0
  183. package/test/resources/9999999/interaction/v1/interactions/233d4413-922c-4568-85a5-e5cc77efc3be/delete-response.json +1 -0
  184. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +1 -1
  185. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  186. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/delete-response.json +6 -0
  187. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_pause-response.xml +32 -0
  188. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_schedule-response.xml +32 -0
  189. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_paused-response.xml +32 -0
  190. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_scheduled-response.xml +32 -0
  191. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml +30 -0
  192. package/test/resources/9999999/program/retrieve-response.xml +21 -3
  193. package/test/resources/9999999/query/patch_fixKeys-expected.json +11 -0
  194. package/test/resources/9999999/query/patch_fixKeys-expected.sql +6 -0
  195. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixKeysANDStatus=Active-response.xml +30 -0
  196. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixedKeysANDStatus=Active-response.xml +30 -0
  197. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testNew_queryANDStatus=Active-response.xml +30 -0
  198. package/test/resources/9999999/script/build-expected.json +6 -0
  199. package/test/resources/9999999/script/build-expected.ssjs +1 -0
  200. package/test/resources/9999999/script/get-expected.json +8 -0
  201. package/test/resources/9999999/script/get-expected.ssjs +1 -0
  202. package/test/resources/9999999/script/get_noScriptTag-expected.html +1 -0
  203. package/test/resources/9999999/script/get_noScriptTag-expected.json +8 -0
  204. package/test/resources/9999999/script/patch-expected.json +8 -0
  205. package/test/resources/9999999/script/patch-expected.ssjs +1 -0
  206. package/test/resources/9999999/script/post-expected.json +8 -0
  207. package/test/resources/9999999/script/post-expected.ssjs +1 -0
  208. package/test/resources/9999999/script/template-expected.json +6 -0
  209. package/test/resources/9999999/script/template-expected.ssjs +1 -0
  210. package/test/resources/9999999/transactionalEmail/build-expected.json +3 -7
  211. package/test/resources/9999999/transactionalEmail/get-expected.json +3 -7
  212. package/test/resources/9999999/transactionalEmail/patch-expected.json +3 -7
  213. package/test/resources/9999999/transactionalEmail/post-expected.json +3 -7
  214. package/test/resources/9999999/transactionalEmail/template-expected.json +3 -7
  215. package/test/resources/9999999/triggeredSend/build-expected.json +29 -0
  216. package/test/resources/9999999/triggeredSend/get-expected.json +29 -0
  217. package/test/resources/9999999/triggeredSend/patch-expected.json +29 -0
  218. package/test/resources/9999999/triggeredSend/post-expected.json +29 -0
  219. package/test/resources/9999999/triggeredSend/template-expected.json +29 -0
  220. package/test/resources/9999999/triggeredSendDefinition/create-response.xml +75 -0
  221. package/test/resources/9999999/triggeredSendDefinition/delete-response.xml +36 -0
  222. package/test/resources/9999999/triggeredSendDefinition/{retrieve-response.xml → retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml} +4 -4
  223. package/test/resources/9999999/triggeredSendDefinition/update-response.xml +74 -0
  224. package/test/resources/9999999/verification/build-expected.json +11 -0
  225. package/test/resources/9999999/verification/get-expected.json +11 -0
  226. package/test/resources/9999999/verification/patch-expected.json +11 -0
  227. package/test/resources/9999999/verification/post-expected.json +11 -0
  228. package/test/resources/9999999/verification/template-expected.json +11 -0
  229. package/test/type.attributeGroup.test.js +55 -0
  230. package/test/type.attributeSet.test.js +55 -0
  231. package/test/type.automation.test.js +650 -17
  232. package/test/type.dataExtension.test.js +205 -46
  233. package/test/type.dataExtract.test.js +194 -0
  234. package/test/type.fileTransfer.test.js +192 -0
  235. package/test/type.importFile.test.js +193 -0
  236. package/test/type.journey.test.js +38 -11
  237. package/test/type.mobileKeyword.test.js +6 -5
  238. package/test/type.mobileMessage.test.js +6 -4
  239. package/test/type.query.test.js +470 -17
  240. package/test/type.script.test.js +372 -0
  241. package/test/type.transactionalEmail.test.js +12 -11
  242. package/test/type.transactionalPush.test.js +2 -4
  243. package/test/type.transactionalSMS.test.js +2 -4
  244. package/test/type.triggeredSend.test.js +154 -0
  245. package/test/type.user.test.js +22 -10
  246. package/test/type.verification.test.js +173 -0
  247. package/test/utils.js +11 -2
  248. package/types/mcdev.d.js +14 -0
  249. package/lib/metadataTypes/SetDefinition.js +0 -37
  250. /package/test/resources/1111111/accountUser/{retrieve-response.xml → retrieve-ActiveFlag=trueANDCustomerKey=testExisting_userANDEmaillike@-response.xml} +0 -0
  251. /package/test/resources/1111111/accountUserAccount/{retrieve-response.xml → retrieve-AccountUser.AccountUserIDIN700301950,700301951,7471228-response.xml} +0 -0
  252. /package/test/resources/1111111/businessUnit/{retrieve-response.xml → retrieve-ID=1111111-response.xml} +0 -0
  253. /package/test/resources/1111111/list/{retrieve-response.xml → retrieve-CustomerKey=All SubscribersORListName=All Subscribers-response.xml} +0 -0
  254. /package/test/resources/1111111/role/{retrieve-response.xml → retrieve-IsPrivate=false-response.xml} +0 -0
  255. /package/test/resources/9999999/emailSendDefinition/{retrieve-response.xml → retrieve-IsPlatformObject=falseANDDescriptionnotEqualsSFSendDefinition-response.xml} +0 -0
  256. /package/test/resources/9999999/queryDefinition/{retrieve-response.xml → retrieve-CustomerKey=testExisting_queryANDStatus=Active-response.xml} +0 -0
@@ -25,64 +25,53 @@ class Automation extends MetadataType {
25
25
  * @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
26
26
  */
27
27
  static async retrieve(retrieveDir, _, __, key) {
28
- /** @type {TYPE.SoapRequestParams} */
29
- let requestParams = null;
30
- if (key) {
31
- requestParams = {
32
- filter: {
33
- leftOperand: 'CustomerKey',
34
- operator: 'equals',
35
- rightOperand: key,
36
- },
37
- };
38
- }
39
- const results = await this.client.soap.retrieveBulk('Program', ['ObjectID'], requestParams);
40
- if (results.Results?.length && !key) {
41
- // empty results will come back without "Results" defined
42
- Util.logger.info(
43
- Util.getGrayMsg(
44
- ` - ${results.Results?.length} automation${
45
- results.Results?.length === 1 ? '' : 's'
46
- } found. Retrieving details...`
47
- )
28
+ let metadataMap;
29
+ if (key && this._cachedMetadataMap?.[key]) {
30
+ metadataMap = {};
31
+ metadataMap[key] = this._cachedMetadataMap[key];
32
+ delete this._cachedMetadataMap;
33
+ } else if (!key && this._cachedMetadataMap) {
34
+ metadataMap = this._cachedMetadataMap;
35
+ delete this._cachedMetadataMap;
36
+ } else {
37
+ /** @type {TYPE.SoapRequestParams} */
38
+ let requestParams = null;
39
+ if (key) {
40
+ requestParams = {
41
+ filter: {
42
+ leftOperand: 'CustomerKey',
43
+ operator: 'equals',
44
+ rightOperand: key,
45
+ },
46
+ };
47
+ }
48
+ const results = await this.client.soap.retrieveBulk(
49
+ 'Program',
50
+ ['ObjectID'],
51
+ requestParams
48
52
  );
53
+ // the API seems to handle 50 concurrent requests nicely
54
+ const response = results?.Results?.length
55
+ ? await this.retrieveRESTcollection(
56
+ results?.Results.map((item) => ({
57
+ id: item.ObjectID,
58
+ uri: '/automation/v1/automations/' + item.ObjectID,
59
+ })),
60
+ 50,
61
+ !key
62
+ )
63
+ : null;
64
+ metadataMap = response?.metadata || {};
49
65
  }
50
- const details = results.Results
51
- ? await Promise.all(
52
- results.Results.map(async (a) => {
53
- try {
54
- return await this.client.rest.get(
55
- '/automation/v1/automations/' + a.ObjectID
56
- );
57
- } catch (ex) {
58
- try {
59
- if (ex.message == 'socket hang up') {
60
- // one more retry; it's a rare case but retrying again should solve the issue gracefully
61
- return await this.client.rest.get(
62
- '/automation/v1/automations/' + a.ObjectID
63
- );
64
- }
65
- } catch {
66
- // no extra action needed, handled below
67
- }
68
- // if we do get here, we should log the error and continue instead of failing to download all automations
69
- Util.logger.error(
70
- ` ☇ skipping Automation ${a.ObjectID}: ${ex.message} ${ex.code}`
71
- );
72
- return null;
73
- }
74
- })
75
- )
76
- : [];
77
- let metadataMap = this.parseResponseBody({ items: details });
78
66
 
79
- if (Object.keys(metadataMap).length) {
67
+ if (!this._skipNotificationRetrieve && Object.keys(metadataMap).length) {
80
68
  // attach notification information to each automation that has any
81
69
  await this.#getAutomationNotificationsREST(metadataMap);
82
70
  }
83
71
 
84
72
  // * retrieveDir can be empty when we use it in the context of postDeployTasks
85
73
  if (retrieveDir) {
74
+ this.retrieveDir = retrieveDir;
86
75
  metadataMap = await this.saveResults(metadataMap, retrieveDir, null, null);
87
76
  Util.logger.info(
88
77
  `Downloaded: ${this.definition.type} (${Object.keys(metadataMap).length})` +
@@ -94,6 +83,27 @@ class Automation extends MetadataType {
94
83
  return { metadata: metadataMap, type: this.definition.type };
95
84
  }
96
85
 
86
+ /**
87
+ * helper for {@link this.retrieveRESTcollection}
88
+ *
89
+ * @param {Error} ex exception
90
+ * @param {string} id id or key of item
91
+ * @returns {null} -
92
+ */
93
+ static async handleRESTErrors(ex, id) {
94
+ try {
95
+ if (ex.message == 'socket hang up') {
96
+ // one more retry; it's a rare case but retrying again should solve the issue gracefully
97
+ return await this.client.rest.get('/automation/v1/automations/' + id);
98
+ }
99
+ } catch {
100
+ // no extra action needed, handled below
101
+ }
102
+ // if we do get here, we should log the error and continue instead of failing to download all automations
103
+ Util.logger.error(` ☇ skipping Automation ${id}: ${ex.message} ${ex.code}`);
104
+ return null;
105
+ }
106
+
97
107
  /**
98
108
  * helper for {@link Automation.retrieve} to get Automation Notifications
99
109
  *
@@ -144,11 +154,19 @@ class Automation extends MetadataType {
144
154
  }));
145
155
  found++;
146
156
  } else {
147
- throw new TypeError(JSON.stringify(notificationsResult));
157
+ if (
158
+ !notificationsResult ||
159
+ typeof notificationsResult !== 'object' ||
160
+ Object.keys(notificationsResult).length !== 1 ||
161
+ !notificationsResult?.programId
162
+ ) {
163
+ throw new TypeError(JSON.stringify(notificationsResult));
164
+ }
165
+ // * if there are no automation notifications, the API returns a single object with the programId
148
166
  }
149
167
  } catch (ex) {
150
168
  Util.logger.debug(
151
- ` ☇ skipping Notifications for Automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
169
+ ` ☇ issue retrieving Notifications for automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
152
170
  );
153
171
  skipped++;
154
172
  }
@@ -215,12 +233,18 @@ class Automation extends MetadataType {
215
233
  * @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
216
234
  */
217
235
  static async retrieveForCache() {
218
- // get automations for cache
219
- const results = await this.client.soap.retrieveBulk('Program', [
220
- 'ObjectID',
221
- 'CustomerKey',
222
- 'Name',
223
- ]);
236
+ let results = {};
237
+ if (this._cachedMetadataMap) {
238
+ results.Results = Object.values(this._cachedMetadataMap);
239
+ delete this._cachedMetadataMap;
240
+ } else {
241
+ // get automations for cache
242
+ results = await this.client.soap.retrieveBulk('Program', [
243
+ 'ObjectID',
244
+ 'CustomerKey',
245
+ 'Name',
246
+ ]);
247
+ }
224
248
  const resultsConverted = {};
225
249
  if (Array.isArray(results?.Results)) {
226
250
  // get encodedAutomationID to retrieve notification information
@@ -237,15 +261,16 @@ class Automation extends MetadataType {
237
261
 
238
262
  // merge encodedAutomationID into results
239
263
  for (const m of results.Results) {
240
- resultsConverted[m.CustomerKey] = {
241
- id: m.ObjectID,
242
- key: m.CustomerKey,
243
- name: m.Name,
244
- programId: automationsLegacy.metadata[m.CustomerKey]?.id,
264
+ const key = m.CustomerKey || m.key;
265
+ resultsConverted[key] = {
266
+ id: m.ObjectID || m.id,
267
+ key: key,
268
+ name: m.Name || m.name,
269
+ programId: automationsLegacy.metadata[key]?.id,
270
+ status: automationsLegacy.metadata[key]?.status,
245
271
  };
246
272
  }
247
273
  }
248
-
249
274
  return { metadata: resultsConverted, type: this.definition.type };
250
275
  }
251
276
 
@@ -318,6 +343,29 @@ class Automation extends MetadataType {
318
343
  throw new Error(JSON.stringify(results));
319
344
  }
320
345
  }
346
+ /**
347
+ * helper for {@link Automation.postRetrieveTasks} and {@link Automation.execute}
348
+ *
349
+ * @param {TYPE.AutomationItem} metadata a single automation
350
+ * @returns {boolean} true if the automation schedule is valid
351
+ */
352
+ static #isValidSchedule(metadata) {
353
+ if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
354
+ try {
355
+ if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
356
+ // if we found the id in our list, remove the redundant data
357
+ delete metadata.schedule.timezoneId;
358
+ }
359
+ } catch {
360
+ Util.logger.debug(
361
+ `- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
362
+ );
363
+ }
364
+ return true;
365
+ } else {
366
+ return false;
367
+ }
368
+ }
321
369
  /**
322
370
  * manages post retrieve steps
323
371
  *
@@ -332,34 +380,24 @@ class Automation extends MetadataType {
332
380
  if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
333
381
  // Starting Source == 'Schedule'
334
382
 
335
- try {
336
- if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
337
- // if we found the id in our list, remove the redundant data
338
- delete metadata.schedule.timezoneId;
339
- }
340
- } catch {
341
- Util.logger.debug(
342
- `- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
343
- );
344
- }
345
- try {
346
- // type 'Running' is temporary status only, overwrite with Scheduled for storage.
347
- if (metadata.type === 'scheduled' && metadata.status === 'Running') {
348
- metadata.status = 'Scheduled';
349
- }
350
- } catch {
351
- Util.logger.error(
352
- `- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
353
- );
383
+ if (!this.#isValidSchedule(metadata)) {
354
384
  return;
355
385
  }
386
+ // type 'Running' is temporary status only, overwrite with Scheduled for storage.
387
+ if (metadata.type === 'scheduled' && metadata.status === 'Running') {
388
+ metadata.status = 'Scheduled';
389
+ }
356
390
  } else if (metadata.type === 'triggered' && metadata.fileTrigger) {
357
391
  // Starting Source == 'File Drop'
358
392
  // Do nothing for now
359
393
  }
360
394
  if (metadata.steps) {
395
+ let i = 0;
396
+
361
397
  for (const step of metadata.steps) {
362
- const stepNumber = step.stepNumber || step.step;
398
+ i++;
399
+
400
+ const stepNumber = step.stepNumber || step.step || i;
363
401
  delete step.stepNumber;
364
402
  delete step.step;
365
403
 
@@ -387,18 +425,28 @@ class Automation extends MetadataType {
387
425
  activity.activityObjectId == null
388
426
  ) {
389
427
  Util.logger.debug(
390
- ` - skip parsing of activity due to missing activityObjectId: ${JSON.stringify(
391
- activity
392
- )}`
428
+ ` - skipping ${
429
+ metadata[this.definition.keyField]
430
+ } activity ${stepNumber}.${
431
+ activity.displayOrder
432
+ } due to missing activityObjectId: ${JSON.stringify(activity)}`
393
433
  );
394
434
  // empty if block
435
+ continue;
395
436
  } else if (!this.definition.dependencies.includes(activity.r__type)) {
396
437
  Util.logger.debug(
397
- ` - skip parsing because the type is not set up as a dependecy for ${this.definition.type}`
438
+ ` - skipping ${
439
+ metadata[this.definition.keyField]
440
+ } activity ${stepNumber}.${
441
+ activity.displayOrder
442
+ } because the type ${
443
+ activity.r__type
444
+ } is not set up as a dependency for ${this.definition.type}`
398
445
  );
446
+ continue;
399
447
  }
400
448
  // / if managed by cache we can update references to support deployment
401
- else if (
449
+ if (
402
450
  Definitions[activity.r__type]?.['idField'] &&
403
451
  cache.getCache(this.buObject.mid)[activity.r__type]
404
452
  ) {
@@ -445,6 +493,197 @@ class Automation extends MetadataType {
445
493
  return null;
446
494
  }
447
495
  }
496
+ /**
497
+ * a function to start query execution via API
498
+ *
499
+ * @param {string[]} keyArr customerkey of the metadata
500
+ * @returns {Promise.<string[]>} Returns list of keys that were executed
501
+ */
502
+ static async execute(keyArr) {
503
+ const metadataMap = {};
504
+ for (const key of keyArr) {
505
+ if (Util.OPTIONS.schedule) {
506
+ // schedule
507
+ const results = await this.retrieve(undefined, undefined, undefined, key);
508
+ if (Object.keys(results.metadata).length) {
509
+ for (const resultKey of Object.keys(results.metadata)) {
510
+ if (this.#isValidSchedule(results.metadata[resultKey])) {
511
+ metadataMap[resultKey] = results.metadata[resultKey];
512
+ } else {
513
+ Util.logger.error(
514
+ ` - skipping ${this.definition.type} ${results.metadata[resultKey].name}: no valid schedule settings found.`
515
+ );
516
+ }
517
+ }
518
+ }
519
+ } else {
520
+ // runOnce
521
+ const objectId = await this.#getObjectIdForSingleRetrieve(key);
522
+ metadataMap[key] = {};
523
+ metadataMap[key][this.definition.idField] = objectId;
524
+ metadataMap[key][this.definition.keyField] = key;
525
+ }
526
+ }
527
+ if (!Object.keys(metadataMap).length) {
528
+ Util.logger.error(`No ${this.definition.type} to execute`);
529
+ return false;
530
+ }
531
+ Util.logger.info(
532
+ `Starting automations ${
533
+ Util.OPTIONS.schedule
534
+ ? 'according to schedule'
535
+ : 'to run once (use --schedule or --execute=schedule to schedule instead)'
536
+ }: ${Object.keys(metadataMap).length}`
537
+ );
538
+ const promiseResults = [];
539
+ for (const key of Object.keys(metadataMap)) {
540
+ if (Util.OPTIONS.schedule && metadataMap[key].status === 'Scheduled') {
541
+ // schedule
542
+ Util.logger.info(
543
+ ` - skipping ${this.definition.type} ${metadataMap[key].name}: already scheduled.`
544
+ );
545
+ } else {
546
+ // schedule + runOnce
547
+ promiseResults.push(this.#executeItem(metadataMap, key));
548
+ }
549
+ }
550
+ const results = await Promise.all(promiseResults);
551
+ const executedKeyArr = results
552
+ .filter(Boolean)
553
+ .filter((r) => r.response.OverallStatus === 'OK')
554
+ .map((r) => r.key);
555
+ Util.logger.info(`Executed ${executedKeyArr.length} of ${keyArr.length} items`);
556
+ return executedKeyArr;
557
+ }
558
+ /**
559
+ * helper for {@link Automation.execute}
560
+ *
561
+ * @param {TYPE.AutomationMap} metadataMap map of metadata
562
+ * @param {string} key key of the metadata
563
+ * @returns {Promise.<{key:string, response:object}>} metadata key and API response
564
+ */
565
+ static async #executeItem(metadataMap, key) {
566
+ if (Util.OPTIONS.schedule) {
567
+ this.#preDeploySchedule(metadataMap[key]);
568
+ metadataMap[key].status = 'Scheduled';
569
+ return this.#scheduleAutomation(metadataMap, metadataMap, key);
570
+ } else {
571
+ return this.#runOnce(metadataMap[key]);
572
+ }
573
+ }
574
+
575
+ /**
576
+ * helper for {@link Automation.execute}
577
+ *
578
+ * @param {TYPE.AutomationItem} metadataEntry metadata object
579
+ * @returns {Promise.<{key:string, response:object}>} metadata key and API response
580
+ */
581
+ static async #runOnce(metadataEntry) {
582
+ return super.executeSOAP(metadataEntry);
583
+ }
584
+
585
+ /**
586
+ * Standardizes a check for multiple messages but adds query specific filters to error texts
587
+ *
588
+ * @param {object} ex response payload from REST API
589
+ * @returns {string[] | void} formatted Error Message
590
+ */
591
+ static getErrorsREST(ex) {
592
+ const errors = super.getErrorsREST(ex);
593
+ if (errors?.length > 0) {
594
+ return errors.map((msg) =>
595
+ msg
596
+ .split('403 Forbidden')
597
+ .join('403 Forbidden: Please check if the automation is currently running.')
598
+ );
599
+ }
600
+ return errors;
601
+ }
602
+
603
+ /**
604
+ * a function to start query execution via API
605
+ *
606
+ * @param {string[]} keyArr customerkey of the metadata
607
+ * @returns {Promise.<string[]>} Returns list of keys that were paused
608
+ */
609
+ static async pause(keyArr) {
610
+ const metadataMap = {};
611
+ for (const key of keyArr) {
612
+ if (key) {
613
+ const results = await this.retrieve(undefined, undefined, undefined, key);
614
+ if (Object.keys(results.metadata).length) {
615
+ for (const key of Object.keys(results.metadata)) {
616
+ if (this.#isValidSchedule(results.metadata[key])) {
617
+ metadataMap[key] = results.metadata[key];
618
+ } else {
619
+ Util.logger.error(
620
+ ` - skipping ${this.definition.type} ${results.metadata[key].name}: no valid schedule settings found.`
621
+ );
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ Util.logger.info(`Pausing automations: ${Object.keys(metadataMap).length}`);
629
+ const promiseResults = [];
630
+ for (const key of Object.keys(metadataMap)) {
631
+ if (metadataMap[key].status === 'Scheduled') {
632
+ promiseResults.push(this.#pauseItem(metadataMap[key]));
633
+ } else if (metadataMap[key].status === 'PausedSchedule') {
634
+ Util.logger.info(
635
+ ` - skipping ${this.definition.type} ${metadataMap[key].name}: already paused.`
636
+ );
637
+ } else {
638
+ Util.logger.error(
639
+ ` - skipping ${this.definition.type} ${
640
+ metadataMap[key].name
641
+ }: currently ${metadataMap[
642
+ key
643
+ ].status.toLowerCase()}. Please try again in a few minutes.`
644
+ );
645
+ }
646
+ }
647
+ const pausedKeyArr = (await Promise.all(promiseResults))
648
+ .filter(Boolean)
649
+ .filter((r) => r.response.OverallStatus === 'OK')
650
+ .map((r) => r.key);
651
+
652
+ Util.logger.info(`Paused ${pausedKeyArr.length} of ${keyArr.length} items`);
653
+ return pausedKeyArr;
654
+ }
655
+
656
+ /**
657
+ * helper for {@link Automation.pause}
658
+ *
659
+ * @param {TYPE.AutomationItem} metadata automation metadata
660
+ * @returns {Promise.<{key:string, response:object}>} metadata key and API response
661
+ */
662
+ static async #pauseItem(metadata) {
663
+ const schedule = {};
664
+ try {
665
+ const response = await this.client.soap.schedule(
666
+ 'Automation',
667
+ schedule,
668
+ {
669
+ Interaction: {
670
+ ObjectID: metadata[this.definition.idField],
671
+ },
672
+ },
673
+ 'pause',
674
+ {}
675
+ );
676
+ Util.logger.info(
677
+ ` - paused ${this.definition.type}: ${metadata[this.definition.keyField]} / ${
678
+ metadata[this.definition.nameField]
679
+ }`
680
+ );
681
+ return { key: metadata[this.definition.keyField], response };
682
+ } catch (ex) {
683
+ this._handleSOAPErrors(ex, 'pausing', metadata, false);
684
+ return null;
685
+ }
686
+ }
448
687
 
449
688
  /**
450
689
  * Deploys automation - the saved file is the original one due to large differences required for deployment
@@ -452,11 +691,10 @@ class Automation extends MetadataType {
452
691
  * @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
453
692
  * @param {string} targetBU name/shorthand of target businessUnit for mapping
454
693
  * @param {string} retrieveDir directory where metadata after deploy should be saved
455
- * @param {boolean} [isRefresh] optional flag - so far not used by automation
456
694
  * @returns {Promise.<TYPE.AutomationMap>} Promise
457
695
  */
458
- static async deploy(metadata, targetBU, retrieveDir, isRefresh) {
459
- const upsertResults = await this.upsert(metadata, targetBU, isRefresh);
696
+ static async deploy(metadata, targetBU, retrieveDir) {
697
+ const upsertResults = await this.upsert(metadata, targetBU);
460
698
  const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
461
699
  if (
462
700
  this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
@@ -488,11 +726,47 @@ class Automation extends MetadataType {
488
726
  * @returns {Promise} Promise
489
727
  */
490
728
  static update(metadata, metadataBefore) {
491
- metadata.id = metadataBefore.id;
729
+ if (metadataBefore.status === 'Running') {
730
+ Util.logger.error(
731
+ ` ☇ error updating ${this.definition.type} ${
732
+ metadata[this.definition.keyField] || metadata[this.definition.nameField]
733
+ } / ${
734
+ metadata[this.definition.nameField]
735
+ }: You cannot update an automation that's currently running. Please wait a bit and retry.`
736
+ );
737
+ return null;
738
+ }
492
739
  const uri = '/automation/v1/automations/' + metadata.id;
493
740
  return super.updateREST(metadata, uri);
494
741
  }
495
742
 
743
+ /**
744
+ * helper for {@link Automation.preDeployTasks} and {@link Automation.execute}
745
+ *
746
+ * @param {TYPE.AutomationItem} metadata metadata mapped by their keyField
747
+ */
748
+ static #preDeploySchedule(metadata) {
749
+ delete metadata.schedule.rangeTypeId;
750
+ delete metadata.schedule.pattern;
751
+ delete metadata.schedule.scheduledTime;
752
+ delete metadata.schedule.scheduledStatus;
753
+ if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
754
+ metadata.schedule.timezoneId =
755
+ this.definition.timeZoneMapping[metadata.schedule.timezoneName];
756
+ } else {
757
+ Util.logger.error(
758
+ `Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
759
+ );
760
+ }
761
+
762
+ // the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId
763
+ metadata.schedule.scheduleTypeId = metadata.schedule.typeId;
764
+ delete metadata.schedule.typeId;
765
+
766
+ // prep startSource
767
+ metadata.startSource = { schedule: metadata.schedule, typeId: 1 };
768
+ }
769
+
496
770
  /**
497
771
  * Gets executed before deploying metadata
498
772
  *
@@ -516,25 +790,12 @@ class Automation extends MetadataType {
516
790
  if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) {
517
791
  // Starting Source == 'Schedule'
518
792
 
519
- delete metadata.schedule.rangeTypeId;
520
- delete metadata.schedule.pattern;
521
- delete metadata.schedule.scheduledTime;
522
- delete metadata.schedule.scheduledStatus;
523
- if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
524
- metadata.schedule.timezoneId =
525
- this.definition.timeZoneMapping[metadata.schedule.timezoneName];
526
- } else {
527
- Util.logger.error(
528
- `Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
529
- );
530
- }
531
- delete metadata.schedule.timezoneName;
532
- // the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId
533
- metadata.schedule.scheduleTypeId = metadata.schedule.typeId;
534
- delete metadata.schedule.typeId;
793
+ this.#preDeploySchedule(metadata);
794
+ // * run _buildSchedule here but only to check if things look ok - do not use the returned schedule object for deploy
795
+ this._buildSchedule(metadata.schedule);
535
796
 
536
- // prep startSource
537
- metadata.startSource = { schedule: metadata.schedule, typeId: 1 };
797
+ delete metadata.schedule.timezoneName;
798
+ delete metadata.startSource.schedule.timezoneName;
538
799
  } else if (metadata.type === 'triggered' && metadata.fileTrigger) {
539
800
  // Starting Source == 'File Drop'
540
801
 
@@ -553,6 +814,7 @@ class Automation extends MetadataType {
553
814
  delete metadata.schedule;
554
815
  delete metadata.type;
555
816
  let i = 0;
817
+ const buName = this.buObject.credential + '/' + this.buObject.businessUnit;
556
818
  if (metadata.steps) {
557
819
  for (const step of metadata.steps) {
558
820
  let displayOrder = 0;
@@ -562,6 +824,23 @@ class Automation extends MetadataType {
562
824
  activity.name &&
563
825
  this.definition.dependencies.includes(activity.r__type)
564
826
  ) {
827
+ if (
828
+ activity.r__type === 'verification' &&
829
+ this.createdKeyMap?.[buName]?.verification?.[activity.name]
830
+ ) {
831
+ Util.logger.info(
832
+ Util.getGrayMsg(
833
+ ` - updated verification activity name from ${
834
+ activity.name
835
+ } to ${
836
+ this.createdKeyMap[buName].verification[activity.name]
837
+ }`
838
+ )
839
+ );
840
+ // map structure: cred/bu --> type --> old key --> new key
841
+ activity.name =
842
+ this.createdKeyMap[buName].verification[activity.name];
843
+ }
565
844
  // automations can have empty placeholder for activities with only their type defined
566
845
  activity.activityObjectId = cache.searchForField(
567
846
  activity.r__type,
@@ -632,6 +911,16 @@ class Automation extends MetadataType {
632
911
  }
633
912
  return deployable;
634
913
  }
914
+ /**
915
+ * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP} that removes old files after the key was changed
916
+ *
917
+ * @private
918
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
919
+ * @returns {void}
920
+ */
921
+ static async _postChangeKeyTasks(metadataEntry) {
922
+ super._postChangeKeyTasks(metadataEntry, true);
923
+ }
635
924
 
636
925
  /**
637
926
  * Gets executed after deployment of metadata type
@@ -642,21 +931,53 @@ class Automation extends MetadataType {
642
931
  */
643
932
  static async postDeployTasks(metadataMap, originalMetadataMap) {
644
933
  for (const key in metadataMap) {
934
+ const item = metadataMap[key];
935
+
936
+ const oldKey = Util.changedKeysMap?.[this.definition.type]?.[key] || key;
937
+ delete Util.changedKeysMap?.[this.definition.type]?.[key];
938
+
939
+ if (!item.type) {
940
+ // create response does not return the type attribute
941
+
942
+ const scheduleHelper = item.schedule || item.startSource.schedule;
943
+
944
+ // el.type
945
+ item.type = scheduleHelper
946
+ ? 'scheduled'
947
+ : item.fileTrigger
948
+ ? 'triggered'
949
+ : undefined;
950
+
951
+ // el.schedule.timezoneName
952
+ if (item.type === 'scheduled') {
953
+ // not existing for triggered automations
954
+ scheduleHelper.timezoneName ||= Util.inverseGet(
955
+ this.definition.timeZoneMapping,
956
+ scheduleHelper.timezoneId
957
+ );
958
+ }
959
+
960
+ // el.status
961
+ item.status ||= Util.inverseGet(this.definition.statusMapping, item.statusId);
962
+ }
645
963
  // need to put schedule on here if status is scheduled
646
- await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key);
964
+ await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey);
647
965
 
648
966
  // need to update notifications separately if there are any
649
967
  await Automation.#updateNotificationInfoREST(metadataMap, key);
650
968
 
651
969
  // rewrite upsert to retrieve fields
652
- const metadata = metadataMap[key];
653
- if (metadata.steps) {
654
- for (const step of metadata.steps) {
970
+ if (item.steps) {
971
+ for (const step of item.steps) {
655
972
  step.name = step.annotation;
656
973
  delete step.annotation;
657
974
  }
658
975
  }
659
976
  }
977
+ if (Util.OPTIONS.execute || Util.OPTIONS.schedule) {
978
+ Util.logger.info(`Executing: ${this.definition.type}`);
979
+ await this.execute(Object.keys(metadataMap));
980
+ }
660
981
  }
661
982
  /**
662
983
  * helper for {@link Automation.postDeployTasks}
@@ -706,22 +1027,26 @@ class Automation extends MetadataType {
706
1027
  }
707
1028
 
708
1029
  /**
709
- * helper for {@link postDeployTasks}
1030
+ * helper for {@link Automation.postDeployTasks}
710
1031
  *
711
1032
  * @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
712
1033
  * @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
713
1034
  * @param {string} key current customer key
1035
+ * @param {string} [oldKey] old customer key before fixKey / changeKeyValue / changeKeyField
1036
+ * @returns {Promise.<{key:string, response:object}>} metadata key and API response
714
1037
  */
715
- static async #scheduleAutomation(metadataMap, originalMetadataMap, key) {
716
- if (originalMetadataMap[key]?.type === 'scheduled') {
1038
+ static async #scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey) {
1039
+ let response = null;
1040
+ oldKey ||= key;
1041
+ if (originalMetadataMap[oldKey]?.type === 'scheduled') {
717
1042
  // Starting Source == 'Schedule': Try starting the automation
718
- if (originalMetadataMap[key].status === 'Scheduled') {
1043
+ if (originalMetadataMap[oldKey].status === 'Scheduled') {
719
1044
  let schedule = null;
720
1045
  try {
721
- schedule = this._buildSchedule(originalMetadataMap[key].schedule);
1046
+ schedule = this._buildSchedule(originalMetadataMap[oldKey].schedule);
722
1047
  } catch (ex) {
723
1048
  Util.logger.error(
724
- `- Could not create schedule for automation '${originalMetadataMap[key].name}' to start it: ${ex.message}`
1049
+ `- Could not create schedule for automation '${originalMetadataMap[oldKey].name}' to start it: ${ex.message}`
725
1050
  );
726
1051
  }
727
1052
  if (schedule !== null) {
@@ -734,12 +1059,12 @@ class Automation extends MetadataType {
734
1059
  const schedule_timezoneString = schedule._timezoneString;
735
1060
  delete schedule._timezoneString;
736
1061
  // start the automation
737
- await this.client.soap.schedule(
1062
+ response = await this.client.soap.schedule(
738
1063
  'Automation',
739
1064
  schedule,
740
1065
  {
741
1066
  Interaction: {
742
- ObjectID: metadataMap[key].id,
1067
+ ObjectID: metadataMap[key][this.definition.idField],
743
1068
  },
744
1069
  },
745
1070
  'start',
@@ -753,21 +1078,22 @@ class Automation extends MetadataType {
753
1078
  (schedule_interval > 1 ? 's' : ''));
754
1079
  Util.logger.warn(
755
1080
  ` - scheduled automation '${
756
- originalMetadataMap[key].name
1081
+ originalMetadataMap[oldKey].name
757
1082
  }' deployed as Active: runs every ${intervalString} starting ${
758
1083
  schedule_StartDateTime.split('T').join(' ').split('.')[0]
759
1084
  } ${schedule_timezoneString}`
760
1085
  );
761
- } catch (ex) {
1086
+ } catch {
1087
+ // API does not return anything usefull here. We have to know the rules instead
762
1088
  Util.logger.error(
763
- `- Could not start scheduled automation '${originalMetadataMap[key].name}': ${ex.message}`
1089
+ ` error starting scheduled ${this.definition.type}${key}: Please check schedule settings`
764
1090
  );
765
1091
  }
766
1092
  }
767
1093
  } else {
768
1094
  Util.logger.info(
769
1095
  Util.getGrayMsg(
770
- ` - scheduled automation '${originalMetadataMap[key].name}' deployed as Paused`
1096
+ ` - scheduled automation '${originalMetadataMap[oldKey].name}' deployed as Paused`
771
1097
  )
772
1098
  );
773
1099
  }
@@ -781,6 +1107,7 @@ class Automation extends MetadataType {
781
1107
  metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId;
782
1108
  delete metadataMap[key].schedule.scheduleTypeId;
783
1109
  }
1110
+ return { key, response };
784
1111
  }
785
1112
 
786
1113
  /**
@@ -867,6 +1194,9 @@ class Automation extends MetadataType {
867
1194
  const a = obj.split('=');
868
1195
  recurHelper[a[0]] = a[1];
869
1196
  }
1197
+ if (recurHelper.INTERVAL) {
1198
+ recurHelper.INTERVAL = Number.parseInt(recurHelper.INTERVAL);
1199
+ }
870
1200
  // the ical schedule is all in caps but soap objects require Title Case.
871
1201
  const keyStem = recurHelper.FREQ.charAt(0) + recurHelper.FREQ.slice(1, -2).toLowerCase();
872
1202
 
@@ -895,13 +1225,18 @@ class Automation extends MetadataType {
895
1225
  'Scheduling automatically not supported for Weekly, Monthly and Yearly, please configure manually.'
896
1226
  );
897
1227
  }
1228
+ if (recurHelper.FREQ === 'MINUTELY' && recurHelper.INTERVAL && recurHelper.INTERVAL < 5) {
1229
+ throw new Error(
1230
+ 'The smallest interval you can configure is 5 minutes. Please adjust your schedule.'
1231
+ );
1232
+ }
898
1233
 
899
1234
  if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) {
900
1235
  scheduleObject.timezoneId =
901
1236
  this.definition.timeZoneMapping[scheduleObject.timezoneName];
902
1237
  } else {
903
- Util.logger.error(
904
- `- Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
1238
+ throw new Error(
1239
+ `Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
905
1240
  );
906
1241
  }
907
1242
  schedule.TimeZone.ID = scheduleObject.timezoneId;
@@ -995,8 +1330,8 @@ class Automation extends MetadataType {
995
1330
  // create new Date object reflecting SFMC's servertime
996
1331
  const dateServer = new Date(utc + 3600000 * offsetServer);
997
1332
 
998
- // return time as a string without trailing "Z"
999
- return dateServer.toISOString().slice(0, -1);
1333
+ // return time as a string without trailing "Z" and without miliseconds (separated by .)
1334
+ return dateServer.toISOString().slice(0, -1).split('.')[0];
1000
1335
  }
1001
1336
  /**
1002
1337
  * Experimental: Only working for DataExtensions:
@@ -1023,7 +1358,7 @@ class Automation extends MetadataType {
1023
1358
  const automationType = { scheduled: 'Schedule', triggered: 'File Drop' };
1024
1359
  output += `**Started by:** ${automationType[json.type] || 'Not defined'}\n\n`;
1025
1360
  output += `**Status:** ${json.status}\n\n`;
1026
- if (json.type === 'scheduled') {
1361
+ if (json.type === 'scheduled' || json.schedule) {
1027
1362
  const tz =
1028
1363
  this.definition.timeZoneDifference[
1029
1364
  this.definition.timeZoneMapping[json?.schedule?.timezoneName]
@@ -1033,7 +1368,7 @@ class Automation extends MetadataType {
1033
1368
  output += `**Schedule:**\n\n`;
1034
1369
  output += `* Start: ${json.schedule.startDate.split('T').join(' ')} ${tz}\n`;
1035
1370
  output += `* End: ${json.schedule.endDate.split('T').join(' ')} ${tz}\n`;
1036
- output += `* Timezone: ${json.schedule.timezoneName}\n`;
1371
+ output += `* Timezone: ${json.schedule.timezoneName}\n`;
1037
1372
 
1038
1373
  const ical = {};
1039
1374
  for (const item of json.schedule.icalRecur.split(';')) {
@@ -1042,9 +1377,20 @@ class Automation extends MetadataType {
1042
1377
  }
1043
1378
  const frequency = ical.FREQ.slice(0, -2).toLowerCase();
1044
1379
 
1045
- output += `* Recurrance: every ${ical.INTERVAL > 1 ? ical.INTERVAL : ''} ${
1046
- frequency === 'dai' ? 'day' : frequency
1047
- }${ical.INTERVAL > 1 ? 's' : ''} ${ical.COUNT ? `for ${ical.COUNT} times` : ''}\n`;
1380
+ output += `* Recurrance: `;
1381
+ output +=
1382
+ ical.COUNT == 1
1383
+ ? 'run only once'
1384
+ : `every${ical.INTERVAL > 1 ? ' ' + ical.INTERVAL : ''} ${
1385
+ frequency === 'dai' ? 'day' : frequency
1386
+ }${ical.INTERVAL > 1 ? 's' : ''}${
1387
+ ical.COUNT
1388
+ ? ` for ${ical.COUNT} times`
1389
+ : ical.UNTIL
1390
+ ? ' until end date'
1391
+ : ''
1392
+ }`;
1393
+ output += '\n';
1048
1394
  } else if (json.schedule) {
1049
1395
  output += `**Schedule:** Not defined\n`;
1050
1396
  }
@@ -1224,7 +1570,7 @@ class Automation extends MetadataType {
1224
1570
  * @param {string} key customer key
1225
1571
  * @returns {Promise.<string>} objectId or enpty string
1226
1572
  */
1227
- static async _getObjectIdForSingleRetrieve(key) {
1573
+ static async #getObjectIdForSingleRetrieve(key) {
1228
1574
  const response = await this.client.soap.retrieve('Program', ['ObjectID'], {
1229
1575
  filter: {
1230
1576
  leftOperand: 'CustomerKey',
@@ -1243,7 +1589,7 @@ class Automation extends MetadataType {
1243
1589
  */
1244
1590
  static async deleteByKey(customerKey) {
1245
1591
  // the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
1246
- const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
1592
+ const objectId = customerKey ? await this.#getObjectIdForSingleRetrieve(customerKey) : null;
1247
1593
  if (!objectId) {
1248
1594
  Util.logger.error(` - automation not found`);
1249
1595
  return false;