mcdev 7.0.4 → 7.1.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 (335) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/.husky/post-checkout +1 -1
  3. package/.husky/post-merge +7 -1
  4. package/.husky/pre-commit +2 -0
  5. package/@types/lib/Builder.d.ts.map +1 -1
  6. package/@types/lib/Deployer.d.ts +3 -0
  7. package/@types/lib/Deployer.d.ts.map +1 -1
  8. package/@types/lib/index.d.ts +70 -19
  9. package/@types/lib/index.d.ts.map +1 -1
  10. package/@types/lib/metadataTypes/Asset.d.ts +53 -16
  11. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/AttributeGroup.d.ts +3 -0
  13. package/@types/lib/metadataTypes/AttributeGroup.d.ts.map +1 -1
  14. package/@types/lib/metadataTypes/AttributeSet.d.ts +5 -0
  15. package/@types/lib/metadataTypes/AttributeSet.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/Automation.d.ts +11 -0
  17. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/Campaign.d.ts +1 -0
  19. package/@types/lib/metadataTypes/Campaign.d.ts.map +1 -1
  20. package/@types/lib/metadataTypes/ContentArea.d.ts +1 -0
  21. package/@types/lib/metadataTypes/ContentArea.d.ts.map +1 -1
  22. package/@types/lib/metadataTypes/DataExtension.d.ts +5 -2
  23. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  24. package/@types/lib/metadataTypes/DataExtensionField.d.ts +3 -4
  25. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  26. package/@types/lib/metadataTypes/DataExtensionTemplate.d.ts +1 -0
  27. package/@types/lib/metadataTypes/DataExtensionTemplate.d.ts.map +1 -1
  28. package/@types/lib/metadataTypes/DataExtract.d.ts +1 -0
  29. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  30. package/@types/lib/metadataTypes/DataExtractType.d.ts +1 -0
  31. package/@types/lib/metadataTypes/DataExtractType.d.ts.map +1 -1
  32. package/@types/lib/metadataTypes/DeliveryProfile.d.ts +1 -0
  33. package/@types/lib/metadataTypes/DeliveryProfile.d.ts.map +1 -1
  34. package/@types/lib/metadataTypes/Discovery.d.ts +1 -0
  35. package/@types/lib/metadataTypes/Discovery.d.ts.map +1 -1
  36. package/@types/lib/metadataTypes/Email.d.ts +1 -0
  37. package/@types/lib/metadataTypes/Email.d.ts.map +1 -1
  38. package/@types/lib/metadataTypes/EmailSend.d.ts +8 -0
  39. package/@types/lib/metadataTypes/EmailSend.d.ts.map +1 -1
  40. package/@types/lib/metadataTypes/Event.d.ts +3 -0
  41. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  42. package/@types/lib/metadataTypes/FileLocation.d.ts +1 -0
  43. package/@types/lib/metadataTypes/FileLocation.d.ts.map +1 -1
  44. package/@types/lib/metadataTypes/FileTransfer.d.ts +1 -0
  45. package/@types/lib/metadataTypes/FileTransfer.d.ts.map +1 -1
  46. package/@types/lib/metadataTypes/Filter.d.ts +1 -0
  47. package/@types/lib/metadataTypes/Filter.d.ts.map +1 -1
  48. package/@types/lib/metadataTypes/Folder.d.ts +39 -25
  49. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  50. package/@types/lib/metadataTypes/ImportFile.d.ts +5 -0
  51. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  52. package/@types/lib/metadataTypes/Journey.d.ts +42 -3
  53. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  54. package/@types/lib/metadataTypes/List.d.ts +1 -0
  55. package/@types/lib/metadataTypes/List.d.ts.map +1 -1
  56. package/@types/lib/metadataTypes/MetadataType.d.ts +50 -3
  57. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  58. package/@types/lib/metadataTypes/MobileCode.d.ts +1 -0
  59. package/@types/lib/metadataTypes/MobileCode.d.ts.map +1 -1
  60. package/@types/lib/metadataTypes/MobileKeyword.d.ts +9 -0
  61. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  62. package/@types/lib/metadataTypes/MobileMessage.d.ts +4 -0
  63. package/@types/lib/metadataTypes/MobileMessage.d.ts.map +1 -1
  64. package/@types/lib/metadataTypes/Query.d.ts +3 -0
  65. package/@types/lib/metadataTypes/Query.d.ts.map +1 -1
  66. package/@types/lib/metadataTypes/Role.d.ts +1 -0
  67. package/@types/lib/metadataTypes/Role.d.ts.map +1 -1
  68. package/@types/lib/metadataTypes/Script.d.ts +3 -1
  69. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  70. package/@types/lib/metadataTypes/SendClassification.d.ts +3 -0
  71. package/@types/lib/metadataTypes/SendClassification.d.ts.map +1 -1
  72. package/@types/lib/metadataTypes/SenderProfile.d.ts +1 -6
  73. package/@types/lib/metadataTypes/SenderProfile.d.ts.map +1 -1
  74. package/@types/lib/metadataTypes/TransactionalEmail.d.ts +6 -0
  75. package/@types/lib/metadataTypes/TransactionalEmail.d.ts.map +1 -1
  76. package/@types/lib/metadataTypes/TransactionalMessage.d.ts +1 -0
  77. package/@types/lib/metadataTypes/TransactionalMessage.d.ts.map +1 -1
  78. package/@types/lib/metadataTypes/TransactionalPush.d.ts +3 -0
  79. package/@types/lib/metadataTypes/TransactionalPush.d.ts.map +1 -1
  80. package/@types/lib/metadataTypes/TransactionalSMS.d.ts +4 -0
  81. package/@types/lib/metadataTypes/TransactionalSMS.d.ts.map +1 -1
  82. package/@types/lib/metadataTypes/TriggeredSend.d.ts +6 -6
  83. package/@types/lib/metadataTypes/TriggeredSend.d.ts.map +1 -1
  84. package/@types/lib/metadataTypes/User.d.ts +1 -0
  85. package/@types/lib/metadataTypes/User.d.ts.map +1 -1
  86. package/@types/lib/metadataTypes/Verification.d.ts +3 -0
  87. package/@types/lib/metadataTypes/Verification.d.ts.map +1 -1
  88. package/@types/lib/metadataTypes/definitions/Asset.definition.d.ts +24 -14
  89. package/@types/lib/metadataTypes/definitions/AttributeGroup.definition.d.ts +3 -0
  90. package/@types/lib/metadataTypes/definitions/AttributeSet.definition.d.ts +5 -0
  91. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +16 -0
  92. package/@types/lib/metadataTypes/definitions/Campaign.definition.d.ts +1 -0
  93. package/@types/lib/metadataTypes/definitions/ContentArea.definition.d.ts +1 -0
  94. package/@types/lib/metadataTypes/definitions/DataExtension.definition.d.ts +1 -0
  95. package/@types/lib/metadataTypes/definitions/DataExtensionField.definition.d.ts +1 -0
  96. package/@types/lib/metadataTypes/definitions/DataExtensionTemplate.definition.d.ts +1 -0
  97. package/@types/lib/metadataTypes/definitions/DataExtract.definition.d.ts +1 -0
  98. package/@types/lib/metadataTypes/definitions/DataExtractType.definition.d.ts +1 -0
  99. package/@types/lib/metadataTypes/definitions/DeliveryProfile.definition.d.ts +1 -0
  100. package/@types/lib/metadataTypes/definitions/Discovery.definition.d.ts +1 -0
  101. package/@types/lib/metadataTypes/definitions/Email.definition.d.ts +1 -0
  102. package/@types/lib/metadataTypes/definitions/EmailSend.definition.d.ts +8 -0
  103. package/@types/lib/metadataTypes/definitions/Event.definition.d.ts +3 -0
  104. package/@types/lib/metadataTypes/definitions/FileLocation.definition.d.ts +1 -0
  105. package/@types/lib/metadataTypes/definitions/FileTransfer.definition.d.ts +1 -0
  106. package/@types/lib/metadataTypes/definitions/Filter.definition.d.ts +1 -0
  107. package/@types/lib/metadataTypes/definitions/Folder.definition.d.ts +1 -0
  108. package/@types/lib/metadataTypes/definitions/ImportFile.definition.d.ts +5 -0
  109. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +13 -0
  110. package/@types/lib/metadataTypes/definitions/List.definition.d.ts +1 -0
  111. package/@types/lib/metadataTypes/definitions/MobileCode.definition.d.ts +1 -0
  112. package/@types/lib/metadataTypes/definitions/MobileKeyword.definition.d.ts +9 -0
  113. package/@types/lib/metadataTypes/definitions/MobileMessage.definition.d.ts +4 -0
  114. package/@types/lib/metadataTypes/definitions/Query.definition.d.ts +3 -0
  115. package/@types/lib/metadataTypes/definitions/Role.definition.d.ts +1 -0
  116. package/@types/lib/metadataTypes/definitions/Script.definition.d.ts +1 -0
  117. package/@types/lib/metadataTypes/definitions/SendClassification.definition.d.ts +3 -0
  118. package/@types/lib/metadataTypes/definitions/SenderProfile.definition.d.ts +1 -0
  119. package/@types/lib/metadataTypes/definitions/TransactionalEmail.definition.d.ts +6 -0
  120. package/@types/lib/metadataTypes/definitions/TransactionalMessage.definition.d.ts +1 -0
  121. package/@types/lib/metadataTypes/definitions/TransactionalPush.definition.d.ts +3 -0
  122. package/@types/lib/metadataTypes/definitions/TransactionalSMS.definition.d.ts +4 -0
  123. package/@types/lib/metadataTypes/definitions/TriggeredSend.definition.d.ts +6 -0
  124. package/@types/lib/metadataTypes/definitions/User.definition.d.ts +1 -0
  125. package/@types/lib/metadataTypes/definitions/Verification.definition.d.ts +3 -0
  126. package/@types/lib/util/cache.d.ts +10 -0
  127. package/@types/lib/util/cache.d.ts.map +1 -1
  128. package/@types/lib/util/cli.d.ts +3 -6
  129. package/@types/lib/util/cli.d.ts.map +1 -1
  130. package/@types/lib/util/config.d.ts.map +1 -1
  131. package/@types/lib/util/devops.d.ts.map +1 -1
  132. package/@types/lib/util/init.config.d.ts.map +1 -1
  133. package/@types/lib/util/init.d.ts.map +1 -1
  134. package/@types/lib/util/init.git.d.ts.map +1 -1
  135. package/@types/lib/util/replaceContentBlockReference.d.ts +27 -4
  136. package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
  137. package/@types/lib/util/util.d.ts +32 -3
  138. package/@types/lib/util/util.d.ts.map +1 -1
  139. package/@types/types/mcdev.d.d.ts +87 -0
  140. package/@types/types/mcdev.d.d.ts.map +1 -1
  141. package/boilerplate/files/.vscode/settings.json +1 -0
  142. package/boilerplate/forcedUpdates.json +4 -0
  143. package/boilerplate/gitignore-template +0 -1
  144. package/lib/Builder.js +13 -8
  145. package/lib/Deployer.js +12 -7
  146. package/lib/cli.js +179 -14
  147. package/lib/index.js +538 -205
  148. package/lib/metadataTypes/Asset.js +455 -210
  149. package/lib/metadataTypes/Automation.js +34 -0
  150. package/lib/metadataTypes/DataExtension.js +33 -28
  151. package/lib/metadataTypes/DataExtensionField.js +2 -2
  152. package/lib/metadataTypes/Event.js +28 -2
  153. package/lib/metadataTypes/Folder.js +63 -48
  154. package/lib/metadataTypes/Journey.js +330 -56
  155. package/lib/metadataTypes/MetadataType.js +269 -57
  156. package/lib/metadataTypes/MobileKeyword.js +12 -1
  157. package/lib/metadataTypes/Script.js +4 -3
  158. package/lib/metadataTypes/SenderProfile.js +17 -5
  159. package/lib/metadataTypes/TriggeredSend.js +20 -5
  160. package/lib/metadataTypes/definitions/Asset.definition.js +10 -2
  161. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +1 -0
  162. package/lib/metadataTypes/definitions/AttributeSet.definition.js +11 -0
  163. package/lib/metadataTypes/definitions/Automation.definition.js +9 -0
  164. package/lib/metadataTypes/definitions/Campaign.definition.js +1 -0
  165. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -0
  166. package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -0
  167. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -0
  168. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -0
  169. package/lib/metadataTypes/definitions/DataExtract.definition.js +2 -1
  170. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -0
  171. package/lib/metadataTypes/definitions/DeliveryProfile.definition.js +1 -0
  172. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -0
  173. package/lib/metadataTypes/definitions/Email.definition.js +1 -0
  174. package/lib/metadataTypes/definitions/EmailSend.definition.js +8 -0
  175. package/lib/metadataTypes/definitions/Event.definition.js +1 -0
  176. package/lib/metadataTypes/definitions/FileLocation.definition.js +1 -0
  177. package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
  178. package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
  179. package/lib/metadataTypes/definitions/Folder.definition.js +10 -8
  180. package/lib/metadataTypes/definitions/ImportFile.definition.js +8 -1
  181. package/lib/metadataTypes/definitions/Journey.definition.js +28 -0
  182. package/lib/metadataTypes/definitions/List.definition.js +1 -0
  183. package/lib/metadataTypes/definitions/MobileCode.definition.js +1 -0
  184. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +7 -0
  185. package/lib/metadataTypes/definitions/MobileMessage.definition.js +9 -0
  186. package/lib/metadataTypes/definitions/Query.definition.js +3 -0
  187. package/lib/metadataTypes/definitions/Role.definition.js +1 -0
  188. package/lib/metadataTypes/definitions/Script.definition.js +1 -0
  189. package/lib/metadataTypes/definitions/SendClassification.definition.js +4 -0
  190. package/lib/metadataTypes/definitions/SenderProfile.definition.js +1 -0
  191. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +7 -0
  192. package/lib/metadataTypes/definitions/TransactionalMessage.definition.js +1 -0
  193. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +3 -0
  194. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +4 -0
  195. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +10 -3
  196. package/lib/metadataTypes/definitions/User.definition.js +3 -0
  197. package/lib/metadataTypes/definitions/Verification.definition.js +1 -0
  198. package/lib/util/cache.js +35 -0
  199. package/lib/util/cli.js +96 -139
  200. package/lib/util/config.js +11 -19
  201. package/lib/util/devops.js +41 -41
  202. package/lib/util/init.config.js +6 -10
  203. package/lib/util/init.git.js +43 -57
  204. package/lib/util/init.js +35 -59
  205. package/lib/util/replaceContentBlockReference.js +107 -60
  206. package/lib/util/util.js +90 -4
  207. package/package.json +14 -13
  208. package/test/general.test.js +1117 -163
  209. package/test/mockRoot/.mcdevrc.json +1 -1
  210. package/test/mockRoot/deploy/testInstance/testBU/event/testNew_event_withExistingDE.event-meta.json +1 -0
  211. package/test/mockRoot/deploy/testInstance/testBU/event/testNew_event_withSchema.event-meta.json +1 -0
  212. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.json +1 -0
  213. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.json +1 -0
  214. package/test/resourceFactory.js +43 -11
  215. package/test/resources/1111111/dataFolder/retrieve-ContentTypeINshared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml +364 -0
  216. package/test/resources/9999999/asset/{block-1157-retrieve-expected.html → build-asset_htmlblock-expected.html} +1 -1
  217. package/test/resources/9999999/asset/build-asset_htmlblock-expected.json +29 -0
  218. package/test/resources/9999999/asset/build-templatebasedemail-expected.json +65 -0
  219. package/test/resources/9999999/asset/resolveId-1295064-noPath-expected.json +3 -3
  220. package/test/resources/9999999/asset/resolveId-1295064-withPath-expected.json +3 -3
  221. package/test/resources/9999999/asset/retrieve-templatebasedemail-expected.json +65 -0
  222. package/test/resources/9999999/asset/template-emailTemplate-expected.json +20 -0
  223. package/test/resources/9999999/asset/template-templatebasedemail-expected.json +65 -0
  224. package/test/resources/9999999/asset/template-testExisting_asset_htmlblock-expected.json +29 -0
  225. package/test/resources/9999999/asset/testExisting_asset_htmlblock-retrieve-expected.html +23 -0
  226. package/test/resources/9999999/asset/{block-1157-retrieve-expected.json → testExisting_asset_htmlblock-retrieve-expected.json} +3 -3
  227. package/test/resources/9999999/asset/testExisting_asset_message-html-rcb-key-expected.html +6 -6
  228. package/test/resources/9999999/asset/testExisting_asset_message-preheader-rcb-id-expected.amp +1 -1
  229. package/test/resources/9999999/asset/testExisting_asset_message-preheader-rcb-key-expected.amp +4 -4
  230. package/test/resources/9999999/asset/testExisting_asset_message-preheader-rcb-name-expected.amp +1 -1
  231. package/test/resources/9999999/asset/testExisting_asset_message-text-rcb-key-expected.amp +4 -4
  232. package/test/resources/9999999/asset/v1/content/assets/1295064/get-response.json +4 -4
  233. package/test/resources/9999999/asset/v1/content/assets/1295065/get-response.json +60 -0
  234. package/test/resources/9999999/asset/v1/content/assets/1295066/get-response.json +60 -0
  235. package/test/resources/9999999/asset/v1/content/assets/5289/get-response.json +75 -0
  236. package/test/resources/9999999/asset/v1/content/assets/808714/get-response.json +3 -3
  237. package/test/resources/9999999/asset/v1/content/assets/950143/get-response.json +97 -0
  238. package/test/resources/9999999/asset/v1/content/assets/get-response-customerKey=testExisting_asset.json +1 -1
  239. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN195,196,197,198,199,200,201,202,203,210,211,212,213,3.json +78 -2
  240. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN205,206,230,232,1,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,14,193,194,15,195,196,197,198,199,200,201,202,203,210,211,212,213,3,207,208,209,5,214,4,215,216.json +370 -0
  241. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN205,206,230,232,1,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,14,193,194,15,195,196,197,198,199,200,201,202,203,210,211,212,213,3,215,216,217,218,219,220,221,222.json +243 -0
  242. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN205,206,230,232,1,207,208,209,5.json +229 -0
  243. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN205,206,230,232,1.json +98 -1
  244. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN217,218,219,220,221,222,223,224,225,226,227,228.json +7 -0
  245. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN223,224,225,226,227,228,214,4.json +35 -0
  246. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN223,224,225,226,227,228.json +7 -0
  247. package/test/resources/9999999/asset/v1/content/assets/query/{post-response-customerKey=mcdev-issue-1157.json → post-response-customerKey=testExisting_asset_htmlblock.json} +2 -2
  248. package/test/resources/9999999/automation/v1/queries/get-response-Name=testExisting_query.json +24 -0
  249. package/test/resources/9999999/automation/v1/scripts/get-response-name=testExisting_script.json +17 -0
  250. package/test/resources/9999999/automation/v1/scripts/get-response.json +2 -2
  251. package/test/resources/9999999/dataFolder/retrieve-ContentType=asset-shared-QAA-response.xml +70 -0
  252. package/test/resources/9999999/dataFolder/retrieve-ContentType=asset-shared-response.xml +70 -0
  253. package/test/resources/9999999/dataFolder/retrieve-ContentType=journey-response.xml +48 -0
  254. 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 +519 -0
  255. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-sha,dataexten,salesforc,shared_da,shared_da,shared_sa,synchroni,automatio,useriniti,journey,mysubs,list,publicati,queryacti,ssjsactiv,triggered,triggered-response.xml +519 -0
  256. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,journey-response.xml +92 -0
  257. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-QAA-response.xml +115 -0
  258. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,ssjsactivity-response.xml +92 -0
  259. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared-QAA-response.xml +70 -0
  260. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset-shared,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-QAA-response.xml +431 -0
  261. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINautomations,queryactivity-response.xml +70 -0
  262. package/test/resources/9999999/dataFolder/{retrieve-response.xml → retrieve-ContentTypeINdataextension,hidden,queryactivity,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +32 -119
  263. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINdataextension,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-QAA-response.xml +117 -0
  264. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINhidden,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml +46 -0
  265. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINshared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-QAA-response.xml +251 -0
  266. package/test/resources/9999999/dataFolder/retrieve-response-.xml +519 -0
  267. package/test/resources/9999999/event/build-expected.json +0 -1
  268. package/test/resources/9999999/event/get-expected.json +0 -1
  269. package/test/resources/9999999/event/post_withExistingDE-callout-expected.json +3 -0
  270. package/test/resources/9999999/event/post_withExistingDE-expected.json +2 -0
  271. package/test/resources/9999999/event/post_withSchema-callout-expected.json +3 -0
  272. package/test/resources/9999999/event/post_withSchema-expected.json +2 -0
  273. package/test/resources/9999999/event/put-callout-expected.json +3 -1
  274. package/test/resources/9999999/event/put-expected.json +2 -2
  275. package/test/resources/9999999/event/template-expected.json +0 -1
  276. package/test/resources/9999999/interaction/v1/eventDefinitions/key_testExisting_event/put-response.json +2 -1
  277. package/test/resources/9999999/interaction/v1/eventDefinitions/post_withExistingDE-response.json +2 -0
  278. package/test/resources/9999999/interaction/v1/eventDefinitions/post_withSchema-response.json +2 -0
  279. package/test/resources/9999999/interaction/v1/interactions/3c3f4112-9b43-43ca-8a89-aa0375b2c1a2/get-response.json +4 -4
  280. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Quicksend/get-response.json +4 -4
  281. package/test/resources/9999999/interaction/v1/interactions/publishAsync/3c3f4112-9b43-43ca-8a89-aa0375b2c1a2/post-response.json +4 -0
  282. package/test/resources/9999999/interaction/v1/interactions/publishStatus/45f06c0a-3ed2-48b2-a6a8-b5119253f01c/get-response-failed.json +34 -0
  283. package/test/resources/9999999/interaction/v1/interactions/publishStatus/45f06c0a-3ed2-48b2-a6a8-b5119253f01c/get-response-success.json +5 -0
  284. package/test/resources/9999999/interaction/v1/interactions/publishStatus/45f06c0a-3ed2-48b2-a6a8-b5119253f01c/get-response-successWarnings.json +36 -0
  285. package/test/resources/9999999/journey/build-expected.json +5 -5
  286. package/test/resources/9999999/journey/get-multistep-expected.json +1 -1
  287. package/test/resources/9999999/journey/get-quicksend-expected.json +5 -5
  288. package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +1 -1
  289. package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +7 -7
  290. package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +1 -1
  291. package/test/resources/9999999/journey/post-expected.json +1 -1
  292. package/test/resources/9999999/journey/publish-callout-expected.json +1 -0
  293. package/test/resources/9999999/journey/put-expected.json +1 -1
  294. package/test/resources/9999999/journey/template-expected.json +5 -5
  295. package/test/resources/9999999/mobileKeyword/build-expected.json +1 -0
  296. package/test/resources/9999999/mobileKeyword/get-expected.json +1 -0
  297. package/test/resources/9999999/mobileKeyword/post-create-expected.json +1 -0
  298. package/test/resources/9999999/mobileKeyword/template-expected.json +1 -0
  299. package/test/resources/{1111111/accountUser/retrieve-CustomerKey=testExisting_userANDActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml → 9999999/queryDefinition/retrieve-CustomerKey=badANDStatus=Active-response.xml} +10 -11
  300. package/test/resources/9999999/script/get_ampincluded-rcb-key-expected.html +2 -2
  301. package/test/resources/9999999/script/get_ampscript-expected.html +1 -0
  302. package/test/resources/9999999/script/get_ampscript-rcb-id-expected.html +1 -0
  303. package/test/resources/9999999/script/get_ampscript-rcb-key-expected.html +3 -2
  304. package/test/resources/9999999/script/get_ampscript-rcb-name-expected.html +3 -0
  305. package/test/resources/9999999/script/get_mixed-expected.html +2 -2
  306. package/test/resources/9999999/script/get_mixed-rcb-key-expected.html +2 -2
  307. package/test/resources/9999999/senderProfile/get-rcb-key-expected.json +4 -4
  308. package/test/resources/9999999/senderProfile/retrieve-response.xml +1 -1
  309. package/test/resources/9999999/triggeredSend/get-rcb-key-expected.json +4 -4
  310. package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml +1 -1
  311. package/test/type.asset.test.js +189 -39
  312. package/test/type.automation.test.js +134 -58
  313. package/test/type.dataExtract.test.js +4 -4
  314. package/test/type.emailSend.test.js +3 -3
  315. package/test/type.event.test.js +0 -2
  316. package/test/type.journey.test.js +322 -9
  317. package/test/type.query.test.js +33 -29
  318. package/test/type.script.test.js +61 -11
  319. package/test/type.senderProfile.test.js +36 -3
  320. package/test/type.transactionalEmail.test.js +3 -3
  321. package/test/type.triggeredSend.test.js +75 -6
  322. package/test/utils.js +13 -3
  323. package/types/mcdev.d.js +27 -1
  324. /package/test/resources/1111111/accountUser/{retrieve-ActiveFlag=falseANDCustomerKey=testExisting_userANDEmaillike@-response.xml → retrieve-ActiveFlag=falseANDCustomerKey=testExisting_userANDEmaillike@-QAA-response.xml} +0 -0
  325. /package/test/resources/1111111/accountUser/{retrieve-ActiveFlag=falseANDEmaillike@-response.xml → retrieve-ActiveFlag=falseANDEmaillike@-QAA-response.xml} +0 -0
  326. /package/test/resources/1111111/accountUser/{retrieve-ActiveFlag=trueANDCustomerKey=testExisting_userANDEmaillike@-response.xml → retrieve-ActiveFlag=trueANDCustomerKey=testExisting_userANDEmaillike@-QAA-response.xml} +0 -0
  327. /package/test/resources/1111111/accountUser/{retrieve-ActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml → retrieve-ActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-QAA-response.xml} +0 -0
  328. /package/test/resources/1111111/accountUser/{retrieve-ActiveFlag=trueANDEmaillike@-response.xml → retrieve-ActiveFlag=trueANDEmaillike@-QAA-response.xml} +0 -0
  329. /package/test/resources/1111111/businessUnit/{retrieve-ID=1111111-response.xml → retrieve-ID=1111111-QAA-response.xml} +0 -0
  330. /package/test/resources/1111111/dataFolder/{retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml → retrieve-ContentTypeINdataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +0 -0
  331. /package/test/resources/1111111/dataFolder/{retrieve-response.xml → retrieve-response-.xml} +0 -0
  332. /package/test/resources/9999999/dataFolder/{retrieve-ContentType=asset-sharedORContentType=asset-response.xml → retrieve-ContentTypeINasset,asset-shared-response.xml} +0 -0
  333. /package/test/resources/9999999/dataFolder/{retrieve-ContentType=contextual_suppression_listORContentType=publicationORContentType=suppression_listORContentType=mysubsORContentType=list-response.xml → retrieve-ContentTypeINcontextual_suppression_list,list,mysubs,publication,suppression_list-response.xml} +0 -0
  334. /package/test/resources/9999999/dataFolder/{retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml → retrieve-ContentTypeINdataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +0 -0
  335. /package/test/resources/9999999/dataFolder/{retrieve-ContentType=triggered_send_journeybuilderORContentType=triggered_sendORContentType=hidden-response.xml → retrieve-ContentTypeINhidden,triggered_send,triggered_send_journeybuilder-response.xml} +0 -0
@@ -19,6 +19,7 @@ import ReplaceCbReference from '../util/replaceContentBlockReference.js';
19
19
  * @typedef {import('../../types/mcdev.d.js').MetadataTypeMap} MetadataTypeMap
20
20
  * @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams
21
21
  * @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap
22
+ * @typedef {import('../../types/mcdev.d.js').TypeKeyCombo} TypeKeyCombo
22
23
  */
23
24
  /**
24
25
  * @typedef {import('../../types/mcdev.d.js').AssetSubType} AssetSubType
@@ -58,11 +59,40 @@ class Asset extends MetadataType {
58
59
  if (retrieveDir) {
59
60
  await File.initPrettier();
60
61
  }
61
- // loop through subtypes and return results of each subType for caching (saving is handled per subtype)
62
- for (const subType of subTypeArr) {
63
- // each subtype contains multiple different specific types (images contains jpg and png for example)
64
- // we use await here to limit the risk of too many concurrent api requests at time
65
- items.push(...(await this.requestSubType(subType, retrieveDir, key, null, loadShared)));
62
+ if (retrieveDir && !cache.getCache()?.asset) {
63
+ // cache this for 3 reasons:
64
+ // 1) subtypes asset, message and template reference other content blocks by id/key/objectId as part of their views
65
+ // 2) subtype message references a template if it is template-based
66
+ // 3) all non-binary subtypes support ampscript / ssjs which can load otherc content blocks via ContentBlockyByX()
67
+
68
+ Util.logger.info(' - Caching dependent Metadata: asset');
69
+ const resultLocal = await this.retrieveForCache(
70
+ undefined,
71
+ this.definition.selflinkedSubTypes,
72
+ undefined,
73
+ false
74
+ );
75
+ cache.mergeMetadata('asset', resultLocal.metadata);
76
+ Util.logger.info(' - Caching dependent Metadata: shared asset');
77
+ const resultShared = await this.retrieveForCache(
78
+ undefined,
79
+ this.definition.selflinkedSubTypes,
80
+ undefined,
81
+ true
82
+ );
83
+ cache.mergeMetadata('asset', resultShared.metadata);
84
+ }
85
+ if (retrieveDir) {
86
+ // loop through subtypes and return results of each subType for caching (saving is handled per subtype)
87
+ for (const subType of subTypeArr) {
88
+ // each subtype contains multiple different specific types (images contains jpg and png for example)
89
+ // we use await here to limit the risk of too many concurrent api requests at time
90
+ items.push(
91
+ ...(await this.requestSubType(subType, retrieveDir, key, null, loadShared))
92
+ );
93
+ }
94
+ } else {
95
+ items.push(...(await this.requestSubType(subTypeArr, null, key, null, loadShared)));
66
96
  }
67
97
  const metadata = this.parseResponseBody({ items: items });
68
98
  if (retrieveDir) {
@@ -73,44 +103,6 @@ class Asset extends MetadataType {
73
103
  }
74
104
  return { metadata: metadata, type: this.definition.type };
75
105
  }
76
- /**
77
- * Helper for writing Metadata to disk, used for Retrieve and deploy
78
- *
79
- * @param {MetadataTypeMap} results metadata results from deploy
80
- * @param {string} retrieveDir directory where metadata should be stored after deploy/retrieve
81
- * @param {string} [overrideType] for use when there is a subtype (such as folder-queries)
82
- * @param {TemplateMap} [templateVariables] variables to be replaced in the metadata
83
- * @returns {Promise.<MetadataTypeMap>} Promise of saved metadata
84
- */
85
- static async saveResults(results, retrieveDir, overrideType, templateVariables) {
86
- if (Object.keys(results).length) {
87
- // only execute the following if records were found
88
- await this._postRetrieveTasksBulk(results);
89
- }
90
- return super.saveResults(results, retrieveDir, overrideType, templateVariables);
91
- }
92
- /**
93
- * helper for Journey's {@link Asset.saveResults}. Gets executed after retreive of metadata type and
94
- *
95
- * @param {MetadataTypeMap} metadataMap key=customer key, value=metadata
96
- */
97
- static async _postRetrieveTasksBulk(metadataMap) {
98
- // Template-Based Email
99
- let needTemplates = false;
100
- for (const key in metadataMap) {
101
- if (metadataMap[key].assetType.name == 'templatebasedemail') {
102
- needTemplates = true;
103
- break;
104
- }
105
- }
106
-
107
- if (needTemplates && !cache.getCache()?.asset) {
108
- // for
109
- Util.logger.info(' - Caching dependent Metadata: asset-template');
110
- const result = await this.retrieveForCache(undefined, ['template']);
111
- cache.setMetadata('asset', result.metadata);
112
- }
113
- }
114
106
 
115
107
  /**
116
108
  * Retrieves asset metadata for caching
@@ -207,7 +199,7 @@ class Asset extends MetadataType {
207
199
  /**
208
200
  * Retrieves Metadata of a specific asset type
209
201
  *
210
- * @param {string} subType group of similar assets to put in a folder (ie. images)
202
+ * @param {string|string[]} subType group of similar assets to put in a folder (ie. images)
211
203
  * @param {string} [retrieveDir] target directory for saving assets
212
204
  * @param {string} [key] key/id/name to filter by
213
205
  * @param {TemplateMap} [templateVariables] variables to be replaced in the metadata
@@ -215,15 +207,29 @@ class Asset extends MetadataType {
215
207
  * @returns {Promise.<object[]>} Promise
216
208
  */
217
209
  static async requestSubType(subType, retrieveDir, key, templateVariables, loadShared = false) {
210
+ const subTypeArr = Array.isArray(subType) ? subType : [subType];
218
211
  if (retrieveDir) {
219
- Util.logger.info(`- Retrieving Subtype: ${subType}`);
212
+ if (Array.isArray(subType)) {
213
+ throw new TypeError(
214
+ 'requestSubType should not be called with multiple subtypes when retrieving to disk.'
215
+ );
216
+ } else {
217
+ Util.logger.info(`- Retrieving Subtype: ${subType}`);
218
+ }
220
219
  } else {
221
- Util.logger.info(` - Caching Subtype: ${subType}`);
220
+ // in this mode we can accept arrays because we don't need to save this to a subtype folder but just want the records
221
+ Util.logSubtypes(subTypeArr);
222
222
  }
223
223
  /** @type {AssetSubType[]} */
224
- const extendedSubTypeArr = this.definition.extendedSubTypes[subType];
225
- const subtypeIds = extendedSubTypeArr?.map(
226
- (subTypeItemName) => Asset.definition.typeMapping[subTypeItemName]
224
+ const extendedSubTypeArr = subTypeArr.flatMap(
225
+ (subType) => this.definition.extendedSubTypes[subType]
226
+ );
227
+ // the API can only assetType.ids at a time or else will throw: "DbUtility.GetPagedCollection passing through non-timeout DB error (30001)"
228
+ const subtypeIdsList = Util.chunk(
229
+ extendedSubTypeArr?.map(
230
+ (subTypeItemName) => this.definition.typeMapping[subTypeItemName]
231
+ ),
232
+ 50
227
233
  );
228
234
  const uri = '/asset/v1/content/assets/query' + (loadShared ? '?scope=shared' : '');
229
235
  /** @type {AssetRequestParams} */
@@ -233,46 +239,15 @@ class Asset extends MetadataType {
233
239
  pageSize: 50,
234
240
  },
235
241
  query: null,
236
- fields: ['category', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy'], // get folder to allow duplicate name check against cache
242
+ fields: [
243
+ 'category',
244
+ 'createdDate',
245
+ 'createdBy',
246
+ 'modifiedDate',
247
+ 'modifiedBy',
248
+ 'objectID',
249
+ ], // get folder to allow duplicate name check against cache
237
250
  };
238
-
239
- if (key) {
240
- payload.query = {
241
- leftOperand: {
242
- property: 'assetType.id',
243
- simpleOperator: 'in',
244
- value: subtypeIds,
245
- },
246
- logicalOperator: 'AND',
247
- };
248
- if (key.startsWith('id:')) {
249
- payload.query.rightOperand = {
250
- property: this.definition.idField,
251
- simpleOperator: 'equal',
252
- value: key.slice(3),
253
- };
254
- } else if (key.startsWith('name:')) {
255
- payload.query.rightOperand = {
256
- property: this.definition.nameField,
257
- simpleOperator: 'equal',
258
- value: key.slice(5),
259
- };
260
- } else {
261
- payload.query.rightOperand = {
262
- property: this.definition.keyField,
263
- simpleOperator: 'equal',
264
- value: key,
265
- };
266
- }
267
- } else {
268
- payload.query = {
269
- property: 'assetType.id',
270
- simpleOperator: 'in',
271
- value: subtypeIds,
272
- };
273
- payload.sort = [{ property: 'id', direction: 'ASC' }];
274
- }
275
- // for caching we do not need these fields
276
251
  if (retrieveDir) {
277
252
  payload.fields = [
278
253
  'fileProperties',
@@ -287,21 +262,11 @@ class Asset extends MetadataType {
287
262
  'tags',
288
263
  ];
289
264
  }
290
- let moreResults = false;
291
- let lastPage = 0;
265
+
292
266
  let items = [];
293
- do {
294
- payload.page.page = lastPage + 1;
295
- const response = await this.client.rest.post(uri, payload);
296
- if (response?.items?.length) {
297
- // sometimes the api will return a payload without items
298
- // --> ensure we only add proper items-arrays here
299
- items = items.concat(response.items);
300
- }
301
- // check if any more records
302
- if (response?.message?.includes('all shards failed')) {
303
- // When running certain filters, there is a limit of 10k on ElastiCache.
304
- // Since we sort by ID, we can get the last ID then run new requests from there
267
+
268
+ for (const subtypeIds of subtypeIdsList) {
269
+ if (key) {
305
270
  payload.query = {
306
271
  leftOperand: {
307
272
  property: 'assetType.id',
@@ -309,24 +274,75 @@ class Asset extends MetadataType {
309
274
  value: subtypeIds,
310
275
  },
311
276
  logicalOperator: 'AND',
312
- rightOperand: {
313
- property: 'id',
314
- simpleOperator: 'greaterThan',
315
- value: items.at(-1).id,
316
- },
317
277
  };
318
- lastPage = 0;
319
- moreResults = true;
320
- } else if (response.page * response.pageSize < response.count) {
321
- moreResults = true;
322
- lastPage = Number(response.page);
278
+ if (key.startsWith('id:')) {
279
+ payload.query.rightOperand = {
280
+ property: this.definition.idField,
281
+ simpleOperator: 'equal',
282
+ value: key.slice(3),
283
+ };
284
+ } else if (key.startsWith('name:')) {
285
+ payload.query.rightOperand = {
286
+ property: this.definition.nameField,
287
+ simpleOperator: 'equal',
288
+ value: key.slice(5),
289
+ };
290
+ } else {
291
+ payload.query.rightOperand = {
292
+ property: this.definition.keyField,
293
+ simpleOperator: 'equal',
294
+ value: key,
295
+ };
296
+ }
323
297
  } else {
324
- moreResults = false;
298
+ payload.query = {
299
+ property: 'assetType.id',
300
+ simpleOperator: 'in',
301
+ value: subtypeIds,
302
+ };
303
+ // payload.sort = [{ property: 'id', direction: 'ASC' }];
325
304
  }
326
- } while (moreResults);
305
+ // for caching we do not need these fields
306
+ let moreResults = false;
307
+ let lastPage = 0;
308
+ do {
309
+ payload.page.page = lastPage + 1;
310
+ const response = await this.client.rest.post(uri, payload);
311
+ if (response?.items?.length) {
312
+ // sometimes the api will return a payload without items
313
+ // --> ensure we only add proper items-arrays here
314
+ items = items.concat(response.items);
315
+ }
316
+ // check if any more records
317
+ if (response?.message?.includes('all shards failed')) {
318
+ // When running certain filters, there is a limit of 10k on ElastiCache.
319
+ // Since we sort by ID, we can get the last ID then run new requests from there
320
+ payload.query = {
321
+ leftOperand: {
322
+ property: 'assetType.id',
323
+ simpleOperator: 'in',
324
+ value: subtypeIds,
325
+ },
326
+ logicalOperator: 'AND',
327
+ rightOperand: {
328
+ property: 'id',
329
+ simpleOperator: 'greaterThan',
330
+ value: items.at(-1).id,
331
+ },
332
+ };
333
+ lastPage = 0;
334
+ moreResults = true;
335
+ } else if (response.page * response.pageSize < response.count) {
336
+ moreResults = true;
337
+ lastPage = Number(response.page);
338
+ } else {
339
+ moreResults = false;
340
+ }
341
+ } while (moreResults);
342
+ }
327
343
 
328
344
  // only when we save results do we need the complete metadata or files. caching can skip these
329
- if (retrieveDir) {
345
+ if (retrieveDir && !Array.isArray(subType)) {
330
346
  if (items.length > 0) {
331
347
  for (const item of items) {
332
348
  if (item.customerKey.trim() !== item.customerKey) {
@@ -344,10 +360,7 @@ class Asset extends MetadataType {
344
360
  Util.logger.info(` Downloaded asset-${subType}: ${items.length}`);
345
361
  }
346
362
 
347
- return items.map((item) => {
348
- item._subType = subType;
349
- return item;
350
- });
363
+ return items;
351
364
  }
352
365
  /**
353
366
  * Retrieves extended metadata (files or extended content) of asset
@@ -389,6 +402,8 @@ class Asset extends MetadataType {
389
402
  const promiseMap = await Promise.all(
390
403
  items.map((item, index) =>
391
404
  rateLimit(async () => {
405
+ const metadataMapSaveSingle = {};
406
+
392
407
  // this is a file so extended is at another endpoint
393
408
  if (item?.fileProperties?.extension && !completed.includes(item.id)) {
394
409
  try {
@@ -397,8 +412,10 @@ class Asset extends MetadataType {
397
412
  } catch (ex) {
398
413
  failed.push({ item: item, error: ex });
399
414
  }
400
- // even if the extended file failed, still save the metadata
415
+ // still return even if extended failed
401
416
  metadataMap[item.customerKey] = item;
417
+ // even if the extended file failed, still save the metadata
418
+ metadataMapSaveSingle[item.customerKey] = item;
402
419
  }
403
420
  // this is a complex type which stores data in the asset itself
404
421
  else if (!completed.includes(item.id)) {
@@ -406,8 +423,10 @@ class Asset extends MetadataType {
406
423
  const extendedItem = await this.client.rest.get(
407
424
  'asset/v1/content/assets/' + item.id
408
425
  );
409
- // only save the metadata if we have extended content
426
+ // only return the metadata if we have extended content
410
427
  metadataMap[item.customerKey] = extendedItem;
428
+ // only save the metadata if we have extended content
429
+ metadataMapSaveSingle[item.customerKey] = extendedItem;
411
430
  // overwrite the original item with the extended content to ensure retrieve() returns it
412
431
  items[index] = extendedItem;
413
432
  } catch (ex) {
@@ -415,9 +434,9 @@ class Asset extends MetadataType {
415
434
  }
416
435
  }
417
436
  completed.push(item.id);
418
- if (metadataMap[item.customerKey]) {
437
+ if (metadataMapSaveSingle[item.customerKey]) {
419
438
  await this.saveResults(
420
- metadataMap,
439
+ metadataMapSaveSingle,
421
440
  retrieveDir,
422
441
  'asset-' + subType,
423
442
  templateVariables
@@ -730,6 +749,9 @@ class Asset extends MetadataType {
730
749
  // define asset's subtype
731
750
  const subType = this._getSubtype(metadata);
732
751
 
752
+ // #0 format blocks in slots for deployment
753
+ await this._preDeployTasksBocks(metadata);
754
+
733
755
  // #1 get text extracts back into the JSON
734
756
  await this._mergeCode(metadata, deployDir, subType);
735
757
 
@@ -738,7 +760,7 @@ class Asset extends MetadataType {
738
760
 
739
761
  // only execute #3 if we are deploying / copying from one BU to another, not while using mcdev as a developer tool
740
762
  if (
741
- !Util.OPTIONS.noMidSuffix &&
763
+ Util.OPTIONS.autoMidSuffix &&
742
764
  this.buObject.mid &&
743
765
  metadata.memberId != this.buObject.mid && // soft comparison to accomodate for string-version of mid
744
766
  !metadata[this.definition.keyField].endsWith(this.buObject.mid)
@@ -928,9 +950,17 @@ class Asset extends MetadataType {
928
950
  }: ${extractedFile.fileName}.${extractedFile.fileExt}.`
929
951
  );
930
952
  }
953
+
954
+ // apply templating to subfolders
931
955
  extractedFile.subFolder = extractedFile.subFolder
932
956
  .map((el) => (el === templateName ? metadata[this.definition.keyField] : el))
933
957
  .map((el) => this.applyTemplateValues(el, templateVariables));
958
+
959
+ // apply templating to filenames of extracted code
960
+ extractedFile.fileName = extractedFile.fileName
961
+ .split('.')
962
+ .map((el) => (el === templateName ? metadata[this.definition.keyField] : el))
963
+ .join('.');
934
964
  }
935
965
 
936
966
  // #2 binary extracts
@@ -1017,11 +1047,83 @@ class Asset extends MetadataType {
1017
1047
  );
1018
1048
  }
1019
1049
  metadata.category = {
1020
- id: cache.searchForField('folder', metadata.r__folder_Path, 'Path', 'ID'),
1050
+ id: cache.getFolderId(metadata.r__folder_Path),
1021
1051
  };
1022
1052
  delete metadata.r__folder_Path;
1023
1053
  }
1024
1054
 
1055
+ /**
1056
+ * helper for {@link Asset.preDeployTasks} that loads extracted code content back into JSON
1057
+ *
1058
+ * @param {AssetItem} metadata a single asset definition
1059
+ * @returns {Promise.<void>} fileList for templating (disregarded during deployment)
1060
+ */
1061
+ static async _preDeployTasksBocks(metadata) {
1062
+ switch (metadata.assetType.name) {
1063
+ case 'templatebasedemail': // message
1064
+ case 'htmlemail':
1065
+ case 'webpage': {
1066
+ // metadata.views.html.slots.<>.blocks.<>.content (optional)
1067
+ if (metadata?.views?.html?.slots) {
1068
+ await this._preDeployTasksBocks_slots(metadata.views.html.slots);
1069
+ }
1070
+ break;
1071
+ }
1072
+ case 'template': {
1073
+ // metadata.slots.<>.blocks.<>.content (optional)
1074
+ if (metadata?.slots) {
1075
+ await this._preDeployTasksBocks_slots(metadata.slots);
1076
+ }
1077
+
1078
+ break;
1079
+ }
1080
+ }
1081
+ }
1082
+ /**
1083
+ * helper for {@link Asset.preDeployTasks} that loads extracted code content back into JSON
1084
+ *
1085
+ * @param {object} metadataSlots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots
1086
+ * @returns {Promise.<void>} -
1087
+ */
1088
+ static async _preDeployTasksBocks_slots(metadataSlots) {
1089
+ for (const slot in metadataSlots) {
1090
+ if (Object.prototype.hasOwnProperty.call(metadataSlots, slot)) {
1091
+ const slotObj = metadataSlots[slot];
1092
+ // found slot
1093
+ if (slotObj.blocks) {
1094
+ for (const block in slotObj.blocks) {
1095
+ if (Object.prototype.hasOwnProperty.call(slotObj.blocks, block)) {
1096
+ const asset = slotObj.blocks[block];
1097
+ if (asset.r__asset_key) {
1098
+ // by only running the following if r__asset_key was set, we simply skip anything that wasnt resolved during retrieve
1099
+ asset.customerKey = asset.r__asset_key;
1100
+ asset.id = cache.searchForField(
1101
+ 'asset',
1102
+ asset.r__asset_key,
1103
+ 'customerKey',
1104
+ 'id'
1105
+ );
1106
+ asset.objectID = cache.searchForField(
1107
+ 'asset',
1108
+ asset.r__asset_key,
1109
+ 'customerKey',
1110
+ 'objectID'
1111
+ );
1112
+ delete asset.r__asset_key;
1113
+ asset.thumbnail = {
1114
+ thumbnailUrl: '/v1/assets/' + asset.id + '/thumbnail',
1115
+ };
1116
+
1117
+ this.setFolderId(asset);
1118
+ }
1119
+ await this._preDeployTasksBocks(asset);
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1126
+
1025
1127
  /**
1026
1128
  * helper for {@link Asset.preDeployTasks} that loads extracted code content back into JSON
1027
1129
  *
@@ -1406,6 +1508,7 @@ class Asset extends MetadataType {
1406
1508
  if (slotObj.blocks) {
1407
1509
  for (const block in slotObj.blocks) {
1408
1510
  if (Object.prototype.hasOwnProperty.call(slotObj.blocks, block)) {
1511
+ const asset = slotObj.blocks[block];
1409
1512
  const fileName = `${prefix}.[${slot}-${block}]${subtypeExtension}`;
1410
1513
  if (
1411
1514
  await File.pathExists(
@@ -1419,7 +1522,7 @@ class Asset extends MetadataType {
1419
1522
  // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
1420
1523
  // if an extracted block was found, save it back into JSON
1421
1524
  if (!fileListOnly) {
1422
- slotObj.blocks[block].content = await File.readFilteredFilename(
1525
+ asset.content = await File.readFilteredFilename(
1423
1526
  [...readDirArr, 'blocks'],
1424
1527
  fileName,
1425
1528
  'html'
@@ -1431,21 +1534,35 @@ class Asset extends MetadataType {
1431
1534
  subFolder: [...subDirArr, templateFileName, 'blocks'],
1432
1535
  fileName: fileName,
1433
1536
  fileExt: 'html',
1434
- content: slotObj.blocks[block].content,
1537
+ content: asset.content,
1435
1538
  });
1436
1539
  }
1437
1540
  }
1438
- if (slotObj.blocks[block].slots) {
1541
+ if (asset.slots) {
1439
1542
  // * recursion: each block can have slots of its own
1440
1543
  await this._mergeCode_slots(
1441
1544
  `${prefix}.[${slot}-${block}]`,
1442
- slotObj.blocks[block].slots,
1545
+ asset.slots,
1443
1546
  readDirArr,
1444
1547
  subtypeExtension,
1445
1548
  subDirArr,
1446
1549
  fileList,
1447
1550
  customerKey,
1448
- templateName
1551
+ templateName,
1552
+ fileListOnly
1553
+ );
1554
+ } else if (asset?.views?.html?.slots) {
1555
+ // * recursion: each block can have slots of its own
1556
+ await this._mergeCode_slots(
1557
+ `${prefix}.[${slot}-${block}]`,
1558
+ asset.views.html.slots,
1559
+ readDirArr,
1560
+ subtypeExtension,
1561
+ subDirArr,
1562
+ fileList,
1563
+ customerKey,
1564
+ templateName,
1565
+ fileListOnly
1449
1566
  );
1450
1567
  }
1451
1568
  }
@@ -1635,6 +1752,8 @@ class Asset extends MetadataType {
1635
1752
  }
1636
1753
  }
1637
1754
  /**
1755
+ * helper for {@link Asset.postRetrieveTasks} via {@link Asset._extractCode}
1756
+ *
1638
1757
  * @param {string} prefix usually the customerkey
1639
1758
  * @param {object} metadataSlots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots
1640
1759
  * @param {object[]} codeArr to be extended array for extracted code
@@ -1647,22 +1766,47 @@ class Asset extends MetadataType {
1647
1766
  // found slot
1648
1767
  for (const block in slotObj.blocks) {
1649
1768
  if (Object.prototype.hasOwnProperty.call(slotObj.blocks, block)) {
1650
- if (slotObj.blocks[block].content) {
1769
+ const asset = slotObj.blocks[block];
1770
+ if (asset.content) {
1651
1771
  // found content block
1652
- const code = slotObj.blocks[block].content;
1772
+ const code = asset.content;
1653
1773
  codeArr.push({
1654
1774
  subFolder: ['blocks'],
1655
1775
  fileName: `${prefix}.[${slot}-${block}]`,
1656
1776
  fileExt: 'html',
1657
1777
  content: code,
1658
1778
  });
1659
- delete slotObj.blocks[block].content;
1779
+ delete asset.content;
1780
+ }
1781
+ // clean up fields from the other content block
1782
+ if (asset.id) {
1783
+ try {
1784
+ asset.r__asset_key = cache.searchForField(
1785
+ 'asset',
1786
+ asset.id,
1787
+ 'id',
1788
+ 'customerKey'
1789
+ );
1790
+ // only delete this if we found the asset in cache, otherwise ignore
1791
+ delete asset.id;
1792
+ delete asset.objectID;
1793
+ delete asset.customerKey;
1794
+ delete asset.thumbnail;
1795
+ } catch {
1796
+ Util.logger.debug(
1797
+ ` - asset id:${asset.id} / key:${asset.customerKey} / name:${asset.name} not found in cache`
1798
+ );
1799
+ }
1660
1800
  }
1661
- if (slotObj.blocks[block].slots) {
1801
+ if (asset.category?.id) {
1802
+ this.setFolderPath(asset);
1803
+ }
1804
+
1805
+ if (asset.slots) {
1662
1806
  // * recursion: each block can have slots of its own
1663
1807
  this._extractCode_slots(
1664
1808
  `${prefix}.[${slot}-${block}]`,
1665
- slotObj.blocks[block].slots,
1809
+ asset.slots,
1666
1810
  codeArr
1667
1811
  );
1668
1812
  }
@@ -1674,9 +1818,9 @@ class Asset extends MetadataType {
1674
1818
  /**
1675
1819
  * Returns file contents mapped to their fileName without '.json' ending
1676
1820
  *
1677
- * @param {string} dir directory that contains '.json' files to be read
1678
- * @param {boolean} _ unused parameter
1679
- * @param {string[]} selectedSubType asset, message, ...
1821
+ * @param {string} dir directory with json files, e.g. /retrieve/cred/bu/asset, /deploy/cred/bu/asset, /template/asset
1822
+ * @param {boolean} [_] unused parameter
1823
+ * @param {string[]} [selectedSubType] asset, message, ...
1680
1824
  * @returns {Promise.<MetadataTypeMap>} fileName => fileContent map
1681
1825
  */
1682
1826
  static async getJsonFromFS(dir, _, selectedSubType) {
@@ -1691,46 +1835,7 @@ class Asset extends MetadataType {
1691
1835
  continue;
1692
1836
  }
1693
1837
  const currentdir = File.normalizePath([dir, subtype]);
1694
- if (await File.pathExists(currentdir)) {
1695
- const files = await File.readdir(currentdir, { withFileTypes: true });
1696
-
1697
- for (const dirent of files) {
1698
- try {
1699
- let thisDir = currentdir;
1700
- let fileName = dirent.name;
1701
- if (dirent.isDirectory()) {
1702
- // complex types with more than one extracted piece of code are saved in their
1703
- // own subfolder (with folder name = CustomerKey)
1704
- // this section aims to find that json in the subfolder
1705
- const subfolderFiles = await File.readdir(
1706
- File.normalizePath([currentdir, dirent.name])
1707
- );
1708
- for (const subFileName of subfolderFiles) {
1709
- if (subFileName.endsWith('-meta.json')) {
1710
- fileName = subFileName;
1711
- thisDir = File.normalizePath([currentdir, dirent.name]);
1712
- }
1713
- }
1714
- }
1715
- if (fileName.endsWith('-meta.json')) {
1716
- const fileContent = await File.readJSONFile(
1717
- thisDir,
1718
- fileName,
1719
- false
1720
- );
1721
- // subtype will change the metadata suffix length
1722
- const fileNameWithoutEnding = fileName.slice(
1723
- 0,
1724
- -17 - subtype.length
1725
- );
1726
- fileName2FileContent[fileNameWithoutEnding] = fileContent;
1727
- }
1728
- } catch (ex) {
1729
- // by catching this in the loop we gracefully handle the issue and move on to the next file
1730
- Util.metadataLogger('debug', this.definition.type, 'getJsonFromFS', ex);
1731
- }
1732
- }
1733
- }
1838
+ await this._getJsonFromFS(currentdir, subtype, fileName2FileContent);
1734
1839
  }
1735
1840
  } catch (ex) {
1736
1841
  // this will catch issues with readdir
@@ -1739,6 +1844,41 @@ class Asset extends MetadataType {
1739
1844
  }
1740
1845
  return fileName2FileContent;
1741
1846
  }
1847
+ /**
1848
+ * helper for {@link Asset.getJsonFromFS} that reads the file system for metadata files
1849
+ *
1850
+ * @param {string} currentdir directory to scan
1851
+ * @param {string} subtype single subtype of asset
1852
+ * @param {MetadataTypeMap} fileName2FileContent fileName => fileContent map
1853
+ */
1854
+ static async _getJsonFromFS(currentdir, subtype, fileName2FileContent) {
1855
+ if (await File.pathExists(currentdir)) {
1856
+ const fileEnding = `.asset-${subtype}-meta.json`;
1857
+ const fileEndingLength = fileEnding.length;
1858
+ const files = await File.readdir(currentdir, { withFileTypes: true });
1859
+ for (const dirent of files) {
1860
+ const fileName = dirent.name;
1861
+ try {
1862
+ if (dirent.isDirectory()) {
1863
+ await this._getJsonFromFS(
1864
+ File.normalizePath([currentdir, fileName]),
1865
+ subtype,
1866
+ fileName2FileContent
1867
+ );
1868
+ } else if (fileName.endsWith(fileEnding)) {
1869
+ const fileContent = await File.readJSONFile(currentdir, fileName, false);
1870
+ // subtype will change the metadata suffix length
1871
+ const fileNameWithoutEnding = fileName.slice(0, -fileEndingLength);
1872
+ fileName2FileContent[fileNameWithoutEnding] = fileContent;
1873
+ }
1874
+ } catch (ex) {
1875
+ // by catching this in the loop we gracefully handle the issue and move on to the next file
1876
+ Util.metadataLogger('debug', this.definition.type, 'getJsonFromFS', ex);
1877
+ }
1878
+ }
1879
+ }
1880
+ }
1881
+
1742
1882
  /**
1743
1883
  * check template directory for complex types that open subfolders for their subtypes
1744
1884
  *
@@ -2058,7 +2198,7 @@ class Asset extends MetadataType {
2058
2198
  }
2059
2199
  if (json.sharedWith && Array.isArray(json.sharedWith)) {
2060
2200
  Util.logger.warn(
2061
- ` with: ${json.sharedWith
2201
+ ` - Shared with: ${json.sharedWith
2062
2202
  .map(
2063
2203
  (mid) =>
2064
2204
  Util.inverseGet(
@@ -2169,9 +2309,10 @@ class Asset extends MetadataType {
2169
2309
  *
2170
2310
  * @param {MetadataTypeItem} item single metadata item
2171
2311
  * @param {string} retrieveDir directory where metadata is saved
2312
+ * @param {Set.<string>} [findAssetKeys] list of keys that were found referenced via ContentBlockByX; if set, method only gets keys and runs no updates
2172
2313
  * @returns {Promise.<MetadataTypeItem>} key of the item that was updated
2173
2314
  */
2174
- static async replaceCbReference(item, retrieveDir) {
2315
+ static async replaceCbReference(item, retrieveDir, findAssetKeys) {
2175
2316
  const responseItem = structuredClone(item);
2176
2317
  const parentName = `${this.definition.type} ${item[this.definition.keyField]}`;
2177
2318
  let changes = false;
@@ -2190,7 +2331,11 @@ class Asset extends MetadataType {
2190
2331
  const fileListChanged = [];
2191
2332
  for (const file of fileList) {
2192
2333
  try {
2193
- file.content = ReplaceCbReference.replaceReference(file.content, parentName);
2334
+ file.content = ReplaceCbReference.replaceReference(
2335
+ file.content,
2336
+ parentName,
2337
+ findAssetKeys
2338
+ );
2194
2339
  changes = true;
2195
2340
  fileListChanged.push(file);
2196
2341
  } catch (ex) {
@@ -2199,16 +2344,17 @@ class Asset extends MetadataType {
2199
2344
  }
2200
2345
  }
2201
2346
  }
2202
-
2203
- // save what was changed regardless of other errors
2204
- for (const extractedFile of fileListChanged) {
2205
- File.writeToFile(
2206
- [retrieveDir, ...extractedFile.subFolder],
2207
- extractedFile.fileName,
2208
- extractedFile.fileExt,
2209
- extractedFile.content,
2210
- extractedFile.encoding || null
2211
- );
2347
+ if (!findAssetKeys) {
2348
+ // save what was changed regardless of other errors
2349
+ for (const extractedFile of fileListChanged) {
2350
+ File.writeToFile(
2351
+ [retrieveDir, ...extractedFile.subFolder],
2352
+ extractedFile.fileName,
2353
+ extractedFile.fileExt,
2354
+ extractedFile.content,
2355
+ extractedFile.encoding || null
2356
+ );
2357
+ }
2212
2358
  }
2213
2359
 
2214
2360
  if (error) {
@@ -2217,7 +2363,7 @@ class Asset extends MetadataType {
2217
2363
 
2218
2364
  if (!changes) {
2219
2365
  const ex = new Error('No changes made to the code.');
2220
- // @ts-expect-error TODO: create custom Error object
2366
+ // @ts-expect-error custom error object
2221
2367
  ex.code = 200;
2222
2368
  throw ex;
2223
2369
  }
@@ -2232,9 +2378,10 @@ class Asset extends MetadataType {
2232
2378
  *
2233
2379
  * @param {MetadataTypeMap} metadataMap list of metadata (keyField => metadata)
2234
2380
  * @param {string} retrieveDir retrieve dir including cred and bu
2381
+ * @param {Set.<string>} [findAssetKeys] list of keys that were found referenced via ContentBlockByX; if set, method only gets keys and runs no updates
2235
2382
  * @returns {Promise.<string[]>} Returns list of keys for which references were replaced
2236
2383
  */
2237
- static async replaceCbReferenceLoop(metadataMap, retrieveDir) {
2384
+ static async replaceCbReferenceLoop(metadataMap, retrieveDir, findAssetKeys) {
2238
2385
  const keysForDeploy = [];
2239
2386
  if (!metadataMap) {
2240
2387
  // if a type was skipped e.g. because it shall only be looked at on the parent then we would expect metadataMap to be undefined
@@ -2246,8 +2393,7 @@ class Asset extends MetadataType {
2246
2393
  .join(' and ');
2247
2394
 
2248
2395
  if (Object.keys(metadataMap).length) {
2249
- Util.logger.info(`Searching ${this.definition.type} for ${fromDescription}:`);
2250
- // const baseDir = [retrieveDir, ...this.definition.type.split('-')];
2396
+ Util.logger.debug(` - Searching in ${this.definition.type} `);
2251
2397
  const deployMap = {};
2252
2398
 
2253
2399
  for (const key in metadataMap) {
@@ -2260,34 +2406,133 @@ class Asset extends MetadataType {
2260
2406
 
2261
2407
  try {
2262
2408
  // add key but make sure to turn it into string or else numeric keys will be filtered later
2263
- deployMap[key] = await this.replaceCbReference(item, retrieveDir);
2409
+ deployMap[key] = await this.replaceCbReference(
2410
+ item,
2411
+ retrieveDir,
2412
+ findAssetKeys
2413
+ );
2264
2414
 
2265
2415
  // ! this method is equal to the super version except that it does not run saveToDisk here
2266
2416
  // await this.saveToDisk(deployMap, key, baseDir);
2267
-
2268
- keysForDeploy.push(key + '');
2269
- Util.logger.info(` - added ${this.definition.type} to update queue: ${key}`);
2417
+ if (findAssetKeys) {
2418
+ keysForDeploy.push(...[...findAssetKeys].map((key) => key + ''));
2419
+ } else {
2420
+ keysForDeploy.push(key + '');
2421
+ Util.logger.info(
2422
+ ` - added ${this.definition.type} to update queue: ${key}`
2423
+ );
2424
+ }
2270
2425
  } catch (ex) {
2271
2426
  if (ex.code !== 200) {
2272
2427
  // dont print error if we simply did not find relevant content blocks
2273
2428
  Util.logger.errorStack(ex, ex.message);
2274
2429
  }
2275
- Util.logger.info(
2276
- Util.getGrayMsg(
2277
- ` ☇ skipping ${this.definition.type} ${
2278
- item[this.definition.keyField]
2279
- }: no ${fromDescription} found`
2280
- )
2281
- );
2430
+ if (!findAssetKeys) {
2431
+ Util.logger.info(
2432
+ Util.getGrayMsg(
2433
+ ` ☇ skipping ${this.definition.type} ${
2434
+ item[this.definition.keyField]
2435
+ }: no ${fromDescription} found`
2436
+ )
2437
+ );
2438
+ }
2282
2439
  }
2283
2440
  }
2284
-
2285
- Util.logger.info(
2286
- `Found ${keysForDeploy.length} ${this.definition.type}${keysForDeploy.length === 1 ? '' : 's'} to update`
2287
- );
2441
+ if (!findAssetKeys) {
2442
+ Util.logger.info(
2443
+ `Found ${keysForDeploy.length} ${this.definition.type}${keysForDeploy.length === 1 ? '' : 's'} to update`
2444
+ );
2445
+ }
2288
2446
  }
2289
2447
  return keysForDeploy;
2290
2448
  }
2449
+ /**
2450
+ *
2451
+ * @param {string[]} keyArr limit retrieval to given metadata type
2452
+ * @param {string} retrieveDir retrieve dir including cred and bu
2453
+ * @param {Set.<string>} findAssetKeys list of keys that were found referenced via ContentBlockByX; if set, method only gets keys and runs no updates
2454
+ * @returns {Promise.<Set.<string>>} found asset keys
2455
+ */
2456
+ static async getCbReferenceKeys(keyArr, retrieveDir, findAssetKeys) {
2457
+ if (!Object.prototype.hasOwnProperty.call(this, 'replaceCbReference')) {
2458
+ // only types that have a replaceCbReference method actually have ampscript/ssjs
2459
+ return;
2460
+ }
2461
+ if (!this.getJsonFromFSCache) {
2462
+ // avoid re-reading the same files in every recursive iteration
2463
+ this.getJsonFromFSCache = await this.getJsonFromFS(
2464
+ File.normalizePath([retrieveDir, this.definition.type])
2465
+ );
2466
+ }
2467
+ // get all metadata of the current type; then filter by keys in selectedTypes
2468
+ const metadataMap = Util.filterObjByKeys(this.getJsonFromFSCache, [...findAssetKeys]);
2469
+ const newKeysFound = new Set(
2470
+ await this.replaceCbReferenceLoop(metadataMap, retrieveDir, findAssetKeys)
2471
+ );
2472
+ if (newKeysFound.size) {
2473
+ const keysToCrawl = [];
2474
+ for (const value of newKeysFound) {
2475
+ if (!keyArr.includes(value)) {
2476
+ keyArr.push(value);
2477
+ keysToCrawl.push(value);
2478
+ }
2479
+ }
2480
+ Util.logger.info(Util.getGrayMsg(` - asset: ${keysToCrawl.join(', ')}`));
2481
+
2482
+ if (keysToCrawl.length) {
2483
+ findAssetKeys = new Set([
2484
+ ...findAssetKeys,
2485
+ ...(await this.getCbReferenceKeys(keyArr, retrieveDir, new Set(keysToCrawl))),
2486
+ ]);
2487
+ }
2488
+ }
2489
+ return findAssetKeys;
2490
+ }
2491
+
2492
+ /**
2493
+ * optional helper for {@link this.getDependentTypes}
2494
+ *
2495
+ * @param {object} metadataItem metadata json read from filesystem
2496
+ * @param {TypeKeyCombo} dependentTypeKeyCombo list started in this.getDependentTypes
2497
+ */
2498
+ static getDependentFilesExtra(metadataItem, dependentTypeKeyCombo) {
2499
+ const dependentKeyArr = [];
2500
+ // search:
2501
+ // - metadata.views.html.slots
2502
+ // - metadata.slots
2503
+ if (metadataItem.views?.html?.slots) {
2504
+ this._getDependentFilesExtra(metadataItem.views.html.slots, dependentKeyArr);
2505
+ }
2506
+ if (metadataItem.slots) {
2507
+ this._getDependentFilesExtra(metadataItem.slots, dependentKeyArr);
2508
+ }
2509
+ if (dependentKeyArr.length) {
2510
+ dependentTypeKeyCombo.asset ||= [];
2511
+ dependentTypeKeyCombo.asset.push(...dependentKeyArr);
2512
+ }
2513
+ }
2514
+ /**
2515
+ * @param {object} slots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots
2516
+ * @param {string[]} dependentKeyArr list of found keys
2517
+ */
2518
+ static _getDependentFilesExtra(slots, dependentKeyArr) {
2519
+ for (const slot in slots) {
2520
+ for (const block in slots[slot].blocks) {
2521
+ const asset = slots[slot].blocks[block];
2522
+ if (asset.r__asset_key) {
2523
+ dependentKeyArr.push(asset.r__asset_key);
2524
+ }
2525
+ if (asset.views?.html?.slots) {
2526
+ // * recursion: each block can have slots of its own
2527
+ this._getDependentFilesExtra(asset.views.html.slots, dependentKeyArr);
2528
+ }
2529
+ if (asset.slots) {
2530
+ // * recursion: each block can have slots of its own
2531
+ this._getDependentFilesExtra(asset.slots, dependentKeyArr);
2532
+ }
2533
+ }
2534
+ }
2535
+ }
2291
2536
  }
2292
2537
 
2293
2538
  // Assign definition to static attributes