mcdev 4.3.3 → 5.0.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 (231) hide show
  1. package/.coverage-comment-template.md +20 -0
  2. package/.coverage-comment-template.svelte +178 -0
  3. package/.eslintrc.json +2 -0
  4. package/.fork/.prettierrc +6 -0
  5. package/.fork/custom-commands.json +13 -1
  6. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  7. package/.github/workflows/code-test.yml +36 -0
  8. package/.github/workflows/coverage-base-update.yml +57 -0
  9. package/.github/workflows/coverage-develop-branch.yml +41 -0
  10. package/.github/workflows/coverage-main-branch.yml +41 -0
  11. package/.github/workflows/coverage.yml +77 -0
  12. package/.husky/post-checkout +1 -1
  13. package/.prettierrc +1 -1
  14. package/.vscode/extensions.json +0 -4
  15. package/boilerplate/config.json +1 -1
  16. package/boilerplate/files/.prettierrc +1 -1
  17. package/boilerplate/files/.vscode/extensions.json +1 -1
  18. package/boilerplate/files/.vscode/settings.json +1 -0
  19. package/boilerplate/forcedUpdates.json +8 -0
  20. package/docs/dist/documentation.md +1261 -433
  21. package/lib/Builder.js +6 -1
  22. package/lib/Deployer.js +34 -10
  23. package/lib/MetadataTypeDefinitions.js +8 -6
  24. package/lib/MetadataTypeInfo.js +8 -6
  25. package/lib/Retriever.js +4 -1
  26. package/lib/cli.js +54 -42
  27. package/lib/index.js +82 -8
  28. package/lib/metadataTypes/Asset.js +185 -31
  29. package/lib/metadataTypes/AttributeGroup.js +0 -1
  30. package/lib/metadataTypes/Automation.js +48 -5
  31. package/lib/metadataTypes/Campaign.js +20 -7
  32. package/lib/metadataTypes/ContentArea.js +1 -1
  33. package/lib/metadataTypes/DataExtension.js +221 -184
  34. package/lib/metadataTypes/DataExtensionField.js +12 -19
  35. package/lib/metadataTypes/DataExtensionTemplate.js +1 -1
  36. package/lib/metadataTypes/DataExtract.js +1 -1
  37. package/lib/metadataTypes/DataExtractType.js +1 -1
  38. package/lib/metadataTypes/Email.js +1 -1
  39. package/lib/metadataTypes/{EmailSendDefinition.js → EmailSend.js} +5 -5
  40. package/lib/metadataTypes/{EventDefinition.js → Event.js} +17 -35
  41. package/lib/metadataTypes/{FtpLocation.js → FileLocation.js} +2 -2
  42. package/lib/metadataTypes/FileTransfer.js +8 -7
  43. package/lib/metadataTypes/Filter.js +1 -1
  44. package/lib/metadataTypes/Folder.js +8 -3
  45. package/lib/metadataTypes/ImportFile.js +6 -6
  46. package/lib/metadataTypes/{Interaction.js → Journey.js} +311 -147
  47. package/lib/metadataTypes/List.js +2 -2
  48. package/lib/metadataTypes/MetadataType.js +318 -90
  49. package/lib/metadataTypes/MobileCode.js +0 -1
  50. package/lib/metadataTypes/MobileKeyword.js +336 -40
  51. package/lib/metadataTypes/MobileMessage.js +473 -0
  52. package/lib/metadataTypes/Query.js +114 -32
  53. package/lib/metadataTypes/Role.js +60 -21
  54. package/lib/metadataTypes/Script.js +5 -7
  55. package/lib/metadataTypes/SendClassification.js +40 -0
  56. package/lib/metadataTypes/SetDefinition.js +1 -7
  57. package/lib/metadataTypes/TransactionalEmail.js +2 -3
  58. package/lib/metadataTypes/TransactionalMessage.js +1 -2
  59. package/lib/metadataTypes/TransactionalSMS.js +8 -15
  60. package/lib/metadataTypes/{TriggeredSendDefinition.js → TriggeredSend.js} +35 -27
  61. package/lib/metadataTypes/User.js +1177 -0
  62. package/lib/metadataTypes/definitions/Asset.definition.js +2 -4
  63. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +1 -0
  64. package/lib/metadataTypes/definitions/Automation.definition.js +3 -2
  65. package/lib/metadataTypes/definitions/Campaign.definition.js +79 -4
  66. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -0
  67. package/lib/metadataTypes/definitions/DataExtension.definition.js +2 -1
  68. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -0
  69. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -0
  70. package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -0
  71. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -0
  72. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -0
  73. package/lib/metadataTypes/definitions/Email.definition.js +1 -0
  74. package/lib/metadataTypes/definitions/{EmailSendDefinition.definition.js → EmailSend.definition.js} +4 -2
  75. package/lib/metadataTypes/definitions/{EventDefinition.definition.js → Event.definition.js} +2 -1
  76. package/lib/metadataTypes/definitions/{FtpLocation.definition.js → FileLocation.definition.js} +4 -3
  77. package/lib/metadataTypes/definitions/FileTransfer.definition.js +3 -2
  78. package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
  79. package/lib/metadataTypes/definitions/Folder.definition.js +2 -0
  80. package/lib/metadataTypes/definitions/ImportFile.definition.js +4 -3
  81. package/lib/metadataTypes/definitions/{Interaction.definition.js → Journey.definition.js} +11 -2
  82. package/lib/metadataTypes/definitions/List.definition.js +1 -0
  83. package/lib/metadataTypes/definitions/MobileCode.definition.js +3 -1
  84. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +27 -17
  85. package/lib/metadataTypes/definitions/MobileMessage.definition.js +743 -0
  86. package/lib/metadataTypes/definitions/Query.definition.js +3 -2
  87. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  88. package/lib/metadataTypes/definitions/Script.definition.js +1 -0
  89. package/lib/metadataTypes/definitions/SendClassification.definition.js +114 -0
  90. package/lib/metadataTypes/definitions/SetDefinition.definition.js +1 -0
  91. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  92. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +1 -0
  93. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +1 -0
  94. package/lib/metadataTypes/definitions/{TriggeredSendDefinition.definition.js → TriggeredSend.definition.js} +5 -3
  95. package/lib/metadataTypes/definitions/User.definition.js +365 -0
  96. package/lib/retrieveChangelog.js +1 -2
  97. package/lib/util/auth.js +29 -9
  98. package/lib/util/businessUnit.js +3 -3
  99. package/lib/util/cli.js +55 -7
  100. package/lib/util/devops.js +93 -8
  101. package/lib/util/file.js +55 -13
  102. package/lib/util/init.config.js +1 -2
  103. package/lib/util/init.npm.js +3 -3
  104. package/lib/util/util.js +68 -14
  105. package/package.json +16 -15
  106. package/test/general.test.js +62 -0
  107. package/test/mockRoot/.mcdevrc.json +7 -5
  108. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testBlocked_user.user-meta.json +23 -0
  109. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testExisting_user.user-meta.json +31 -0
  110. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testNew_user.user-meta.json +27 -0
  111. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{childBU_dataextension_test.dataExtension-meta.json → testExisting_dataExtension.dataExtension-meta.json} +2 -2
  112. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{testDataExtension.dataExtension-meta.json → testNew_dataExtension.dataExtension-meta.json} +2 -2
  113. package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_interaction.interaction-meta.json +576 -0
  114. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp +2 -0
  115. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json +10 -0
  116. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp +2 -0
  117. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json +10 -0
  118. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.amp +1 -0
  119. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.json +61 -0
  120. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.amp +1 -0
  121. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.json +60 -0
  122. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  123. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json +1 -1
  124. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql +1 -1
  125. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +1 -1
  126. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -1
  127. package/test/resourceFactory.js +13 -0
  128. package/test/resources/1111111/accountUser/configure-response.xml +70 -0
  129. package/test/resources/1111111/accountUser/create-response.xml +97 -0
  130. package/test/resources/1111111/accountUser/retrieve-response.xml +156 -0
  131. package/test/resources/1111111/accountUser/update-response.xml +111 -0
  132. package/test/resources/1111111/accountUserAccount/retrieve-response.xml +77 -0
  133. package/test/resources/1111111/platform/v1/setup/quickflow/data/get-response.json +455 -0
  134. package/test/resources/1111111/role/retrieve-response.xml +76 -0
  135. package/test/resources/1111111/user/build-expected.json +16 -0
  136. package/test/resources/1111111/user/create-expected.json +21 -0
  137. package/test/resources/1111111/user/retrieve-expected.json +24 -0
  138. package/test/resources/1111111/user/template-expected.json +16 -0
  139. package/test/resources/1111111/user/update-expected.json +21 -0
  140. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/delete-response.json +1 -0
  141. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +17 -0
  142. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +3 -3
  143. package/test/resources/9999999/automation/v1/queries/get-response.json +21 -4
  144. package/test/resources/9999999/automation/v1/queries/post-response.json +4 -4
  145. package/test/resources/9999999/data/v1/customobjectdata/key/{childBU_dataextension_test → testExisting_dataExtension}/rowset/get-response.json +1 -1
  146. package/test/resources/9999999/dataExtension/build-expected.json +3 -3
  147. package/test/resources/9999999/dataExtension/create-expected.json +2 -2
  148. package/test/resources/9999999/dataExtension/create-response.xml +8 -3
  149. package/test/resources/9999999/dataExtension/retrieve-expected.json +3 -3
  150. package/test/resources/9999999/dataExtension/retrieve-response.xml +9 -4
  151. package/test/resources/9999999/dataExtension/template-expected.json +3 -3
  152. package/test/resources/9999999/dataExtension/update-expected.json +3 -3
  153. package/test/resources/9999999/dataExtension/update-response.xml +9 -4
  154. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +14 -9
  155. package/test/resources/9999999/interaction/v1/interactions/get-response.json +312 -0
  156. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/get-response.json +312 -0
  157. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/put-response.json +592 -0
  158. package/test/resources/9999999/journey/build-expected.json +572 -0
  159. package/test/resources/9999999/journey/get-expected.json +576 -0
  160. package/test/resources/9999999/journey/put-expected.json +576 -0
  161. package/test/resources/9999999/journey/template-expected.json +572 -0
  162. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json +42 -0
  163. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow/delete-response.json +0 -0
  164. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -0
  165. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/post-response.json +3 -0
  166. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/delete-response.json +0 -0
  167. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json +106 -0
  168. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/post-response.json +0 -0
  169. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTQ3Ojc4OjA/get-response.json +127 -0
  170. package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +129 -0
  171. package/test/resources/9999999/legacy/v1/beta/mobile/message/post-response.json +3 -0
  172. package/test/resources/9999999/legacy/v1/beta2/data/campaign/get-response.json +29 -0
  173. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  174. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +1 -1
  175. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +1 -1
  176. package/test/resources/9999999/mobileKeyword/build-expected.amp +2 -0
  177. package/test/resources/9999999/mobileKeyword/build-expected.json +9 -0
  178. package/test/resources/9999999/mobileKeyword/get-expected.amp +2 -0
  179. package/test/resources/9999999/mobileKeyword/get-expected.json +15 -0
  180. package/test/resources/9999999/mobileKeyword/post-create-expected.amp +2 -0
  181. package/test/resources/9999999/mobileKeyword/post-create-expected.json +17 -0
  182. package/test/resources/9999999/mobileKeyword/template-expected.amp +2 -0
  183. package/test/resources/9999999/mobileKeyword/template-expected.json +9 -0
  184. package/test/resources/9999999/mobileMessage/build-expected.amp +1 -0
  185. package/test/resources/9999999/mobileMessage/build-expected.json +60 -0
  186. package/test/resources/9999999/mobileMessage/get-expected.amp +1 -0
  187. package/test/resources/9999999/mobileMessage/get-expected.json +61 -0
  188. package/test/resources/9999999/mobileMessage/post-create-expected.amp +1 -0
  189. package/test/resources/9999999/mobileMessage/post-create-expected.json +63 -0
  190. package/test/resources/9999999/mobileMessage/post-update-expected.amp +1 -0
  191. package/test/resources/9999999/mobileMessage/post-update-expected.json +61 -0
  192. package/test/resources/9999999/mobileMessage/template-expected.amp +1 -0
  193. package/test/resources/9999999/mobileMessage/template-expected.json +60 -0
  194. package/test/resources/9999999/query/build-expected.json +1 -1
  195. package/test/resources/9999999/query/get-expected.json +1 -1
  196. package/test/resources/9999999/query/get2-expected.json +11 -0
  197. package/test/resources/9999999/query/patch-expected.json +1 -1
  198. package/test/resources/9999999/query/post-expected.json +1 -1
  199. package/test/resources/9999999/query/template-expected.json +1 -1
  200. package/test/resources/9999999/queryDefinition/retrieve-response.xml +30 -0
  201. package/test/resources/9999999/transactionalEmail/build-expected.json +5 -5
  202. package/test/resources/9999999/transactionalEmail/get-expected.json +1 -1
  203. package/test/resources/9999999/transactionalEmail/patch-expected.json +1 -1
  204. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  205. package/test/resources/9999999/transactionalEmail/template-expected.json +5 -5
  206. package/test/resources/9999999/transactionalPush/build-expected.json +2 -2
  207. package/test/resources/9999999/transactionalPush/template-expected.json +2 -2
  208. package/test/resources/9999999/transactionalSMS/build-expected.json +3 -3
  209. package/test/resources/9999999/transactionalSMS/template-expected.json +3 -3
  210. package/test/{dataExtension.test.js → type.dataExtension.test.js} +78 -21
  211. package/test/{interaction.test.js → type.journey.test.js} +64 -30
  212. package/test/type.mobileKeyword.test.js +250 -0
  213. package/test/type.mobileMessage.test.js +205 -0
  214. package/test/{query.test.js → type.query.test.js} +102 -5
  215. package/test/{transactionalEmail.test.js → type.transactionalEmail.test.js} +40 -2
  216. package/test/{transactionalPush.test.js → type.transactionalPush.test.js} +41 -2
  217. package/test/{transactionalSMS.test.js → type.transactionalSMS.test.js} +73 -3
  218. package/test/type.user.test.js +160 -0
  219. package/test/utils.js +17 -5
  220. package/types/mcdev.d.js +48 -15
  221. package/.github/workflows/code-analysis.yml +0 -57
  222. package/lib/metadataTypes/AccountUser.js +0 -426
  223. package/lib/metadataTypes/definitions/AccountUser.definition.js +0 -227
  224. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +0 -266
  225. package/test/resources/9999999/interaction/build-expected.json +0 -260
  226. package/test/resources/9999999/interaction/get-expected.json +0 -264
  227. package/test/resources/9999999/interaction/put-expected.json +0 -264
  228. package/test/resources/9999999/interaction/template-expected.json +0 -260
  229. package/test/resources/9999999/interaction/v1/interactions/put-response.json +0 -280
  230. /package/test/mockRoot/deploy/testInstance/testBU/{interaction → journey}/testNew_interaction.interaction-meta.json +0 -0
  231. /package/test/resources/9999999/{interaction → journey}/post-expected.json +0 -0
@@ -25,7 +25,7 @@ class Asset extends MetadataType {
25
25
  */
26
26
  static async retrieve(retrieveDir, _, subTypeArr, key) {
27
27
  const items = [];
28
- subTypeArr = subTypeArr || this._getSubTypes();
28
+ subTypeArr ||= this._getSubTypes();
29
29
  await File.initPrettier();
30
30
  // loop through subtypes and return results of each subType for caching (saving is handled per subtype)
31
31
  for (const subType of subTypeArr) {
@@ -113,6 +113,7 @@ class Asset extends MetadataType {
113
113
  * @returns {Promise} Promise
114
114
  */
115
115
  static create(metadata) {
116
+ delete metadata.businessUnitAvailability;
116
117
  const uri = '/asset/v1/content/assets/';
117
118
  return super.createREST(metadata, uri);
118
119
  }
@@ -495,6 +496,78 @@ class Asset extends MetadataType {
495
496
  return metadata;
496
497
  }
497
498
 
499
+ /**
500
+ * Gets executed after deployment of metadata type
501
+ *
502
+ * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
503
+ * @param {TYPE.MetadataTypeMap} _ originalMetadata to be updated (contains additioanl fields)
504
+ * @param {{created: number, updated: number}} createdUpdated counter representing successful creates/updates
505
+ * @param {boolean} [isRefresh] optional flag to indicate that triggeredSend should be refreshed after deployment of assets
506
+ * @returns {Promise.<void>} -
507
+ */
508
+ static async postDeployTasks(metadata, _, createdUpdated, isRefresh) {
509
+ if (isRefresh) {
510
+ if (createdUpdated.updated) {
511
+ // only run this if assets were updated. for created assets we do not expect
512
+ this._refreshTriggeredSend(metadata);
513
+ } else {
514
+ Util.logger.warn(
515
+ 'You set the --refresh flag but no updated assets found. Skipping refresh of triggeredSendDefinitions.'
516
+ );
517
+ }
518
+ }
519
+ }
520
+
521
+ /**
522
+ * helper for {@link postDeployTasks}. triggers a refresh of active triggerredSendDefinitions associated with the updated asset-message items. Gets executed if isRefresh is true.
523
+ *
524
+ * @private
525
+ * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
526
+ * @returns {Promise.<void>} -
527
+ */
528
+ static async _refreshTriggeredSend(metadata) {
529
+ // get legacyData.legacyId from assets to compare to TSD's metadata.Email.ID to
530
+ const legacyIdArr = Object.keys(metadata)
531
+ .map((key) => metadata[key]?.legacyData?.legacyId)
532
+ .filter(Boolean);
533
+
534
+ if (!legacyIdArr.length) {
535
+ Util.logger.warn(
536
+ 'No legacyId found in updated emails. Skipping refresh of triggeredSendDefinitions.'
537
+ );
538
+ return;
539
+ }
540
+ // prep triggeredSendDefinition class
541
+ const TriggeredSend = require('./TriggeredSend');
542
+ TriggeredSend.properties = this.properties;
543
+ TriggeredSend.buObject = this.buObject;
544
+ TriggeredSend.client = this.client;
545
+ try {
546
+ // find refreshable TSDs
547
+ const tsdObj = (await TriggeredSend.findRefreshableItems()).metadata;
548
+
549
+ const tsdCountInitial = Object.keys(tsdObj).length;
550
+ const emailCount = legacyIdArr.length;
551
+ // filter TSDs by legacyId
552
+ for (const key in tsdObj) {
553
+ if (!legacyIdArr.includes(tsdObj[key].Email.ID)) {
554
+ delete tsdObj[key];
555
+ }
556
+ }
557
+ const tsdCountFiltered = Object.keys(tsdObj).length;
558
+ Util.logger.info(
559
+ `Found ${tsdCountFiltered} out of ${tsdCountInitial} total triggeredSendDefinitions for ${emailCount} deployed emails. Commencing validation...`
560
+ );
561
+
562
+ // get keys of TSDs to refresh
563
+ const keyArr = await TriggeredSend.getKeysForValidTSDs(tsdObj);
564
+
565
+ await TriggeredSend.refresh(keyArr);
566
+ } catch {
567
+ Util.logger.warn('Failed to refresh triggeredSendDefinition');
568
+ }
569
+ }
570
+
498
571
  /**
499
572
  * prepares an asset definition for deployment
500
573
  *
@@ -818,16 +891,17 @@ class Asset extends MetadataType {
818
891
 
819
892
  // metadata.views.html.content (mandatory)
820
893
  // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
894
+ const fileName = 'views.html.content' + subtypeExtension;
821
895
  if (
822
896
  (await File.pathExists(
823
- File.normalizePath([...readDirArr, `index${subtypeExtension}.html`])
897
+ File.normalizePath([...readDirArr, `${fileName}.html`])
824
898
  )) &&
825
899
  metadata.views.html
826
900
  ) {
827
901
  if (!fileListOnly) {
828
902
  metadata.views.html.content = await File.readFilteredFilename(
829
903
  readDirArr,
830
- 'index' + subtypeExtension,
904
+ fileName,
831
905
  'html'
832
906
  );
833
907
  }
@@ -836,7 +910,7 @@ class Asset extends MetadataType {
836
910
  // to use this method in templating, store a copy of the info in fileList
837
911
  fileList.push({
838
912
  subFolder: [...subDirArr, customerKey],
839
- fileName: 'index' + subtypeExtension,
913
+ fileName: fileName,
840
914
  fileExt: 'html',
841
915
  content: metadata.views.html.content,
842
916
  });
@@ -859,6 +933,59 @@ class Asset extends MetadataType {
859
933
  }
860
934
  break;
861
935
  }
936
+ case 'template': {
937
+ // template-template
938
+ // this complex type always creates its own subdir per asset
939
+ subDirArr = [this.definition.type, subType];
940
+ readDirArr = [deployDir, ...subDirArr, templateName || customerKey];
941
+ const fileName = 'content' + subtypeExtension;
942
+
943
+ const fileExtArr = ['html']; // eslint-disable-line no-case-declarations
944
+ for (const ext of fileExtArr) {
945
+ if (
946
+ await File.pathExists(
947
+ File.normalizePath([...readDirArr, `${fileName}.${ext}`])
948
+ )
949
+ ) {
950
+ // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
951
+ if (!fileListOnly) {
952
+ metadata.content = await File.readFilteredFilename(
953
+ readDirArr,
954
+ fileName,
955
+ ext
956
+ );
957
+ }
958
+ if (templateName) {
959
+ // to use this method in templating, store a copy of the info in fileList
960
+ fileList.push({
961
+ subFolder: subDirArr,
962
+ fileName: fileName,
963
+ fileExt: ext,
964
+ content: metadata.content,
965
+ });
966
+ }
967
+ // break loop when found
968
+ break;
969
+ }
970
+ }
971
+
972
+ // metadata.slots.<>.blocks.<>.content (optional)
973
+ if (metadata?.slots) {
974
+ await this._mergeCode_slots(
975
+ 'slots',
976
+ metadata.slots,
977
+ readDirArr,
978
+ subtypeExtension,
979
+ subDirArr,
980
+ fileList,
981
+ customerKey,
982
+ templateName,
983
+ fileListOnly
984
+ );
985
+ }
986
+
987
+ break;
988
+ }
862
989
  case 'textonlyemail': {
863
990
  // message
864
991
  // metadata.views.text.content
@@ -970,7 +1097,6 @@ class Asset extends MetadataType {
970
1097
 
971
1098
  break;
972
1099
  }
973
- case 'template': // template-template
974
1100
  case 'buttonblock': // block - Button Block
975
1101
  case 'freeformblock': // block
976
1102
  case 'htmlblock': // block
@@ -983,12 +1109,7 @@ class Asset extends MetadataType {
983
1109
  // metadata.content
984
1110
  subDirArr = [this.definition.type, subType];
985
1111
  readDirArr = [deployDir, ...subDirArr];
986
- const fileExtArr = ['html']; // eslint-disable-line no-case-declarations
987
- if (metadata.assetType.name === 'codesnippetblock') {
988
- // extracted code snippets should end on the right extension
989
- // we are making a few assumptions during retrieve to pick the right one
990
- fileExtArr.push('amp', 'sjss');
991
- }
1112
+ const fileExtArr = ['html', 'ssjs', 'amp']; // eslint-disable-line no-case-declarations
992
1113
  for (const ext of fileExtArr) {
993
1114
  if (
994
1115
  await File.pathExists(
@@ -1005,6 +1126,9 @@ class Asset extends MetadataType {
1005
1126
  (templateName || customerKey) + subtypeExtension,
1006
1127
  ext
1007
1128
  );
1129
+ if (ext === 'ssjs') {
1130
+ metadata.content = `<script runat="server">\n${metadata.content}</script>`;
1131
+ }
1008
1132
  }
1009
1133
  if (templateName) {
1010
1134
  // to use this method in templating, store a copy of the info in fileList
@@ -1117,14 +1241,14 @@ class Asset extends MetadataType {
1117
1241
  // unfortunately, asset's key can contain spaces at beginning/end which can break the file system when folders are created with it
1118
1242
  const customerKey = metadata.customerKey.trim();
1119
1243
  switch (metadata.assetType.name) {
1120
- case 'templatebasedemail': // message
1244
+ case 'templatebasedemail': // message-templatebasedemail
1121
1245
  case 'htmlemail': {
1122
- // message
1246
+ // message-htmlemail
1123
1247
  // metadata.views.html.content (mandatory)
1124
1248
  if (metadata.views?.html?.content?.length) {
1125
1249
  codeArr.push({
1126
1250
  subFolder: null,
1127
- fileName: 'index',
1251
+ fileName: 'views.html.content',
1128
1252
  fileExt: 'html',
1129
1253
  content: metadata.views.html.content,
1130
1254
  });
@@ -1142,8 +1266,32 @@ class Asset extends MetadataType {
1142
1266
  subFolder: [customerKey],
1143
1267
  };
1144
1268
  }
1269
+ case 'template': {
1270
+ // template-template
1271
+ // metadata.content
1272
+ const fileExt = 'html'; // eslint-disable-line no-case-declarations
1273
+ if (metadata?.content?.length) {
1274
+ codeArr.push({
1275
+ subFolder: null,
1276
+ fileName: 'content',
1277
+ fileExt: fileExt,
1278
+ content: metadata.content,
1279
+ });
1280
+ delete metadata.content;
1281
+ }
1282
+ // metadata.slots.<>.blocks.<>.content (optional)
1283
+ if (metadata.slots) {
1284
+ this._extractCode_slots('slots', metadata.slots, codeArr);
1285
+ }
1286
+
1287
+ return {
1288
+ json: metadata,
1289
+ codeArr: codeArr,
1290
+ subFolder: [customerKey],
1291
+ };
1292
+ }
1145
1293
  case 'textonlyemail': {
1146
- // message
1294
+ // message-textonlyemail
1147
1295
  // metadata.views.text.content
1148
1296
  if (metadata.views?.text?.content?.length) {
1149
1297
  codeArr.push({
@@ -1157,7 +1305,7 @@ class Asset extends MetadataType {
1157
1305
  return { json: metadata, codeArr: codeArr, subFolder: null };
1158
1306
  }
1159
1307
  case 'webpage': {
1160
- // asset
1308
+ // asset-webpage
1161
1309
  // metadata.views.html.content (pre & post 20222)
1162
1310
  if (metadata.views?.html?.content?.length) {
1163
1311
  codeArr.push({
@@ -1192,31 +1340,37 @@ class Asset extends MetadataType {
1192
1340
  subFolder: [customerKey],
1193
1341
  };
1194
1342
  }
1195
- case 'template': // template-template
1196
- case 'buttonblock': // block - Button Block
1197
- case 'freeformblock': // block
1198
- case 'htmlblock': // block
1199
- case 'icemailformblock': // block - Interactive Content Email Form
1200
- case 'imageblock': // block - Image Block
1201
- case 'textblock': // block
1202
- case 'smartcaptureblock': // other
1343
+ case 'buttonblock': // block-buttonblock
1344
+ case 'freeformblock': // block-freeformblock
1345
+ case 'htmlblock': // block-htmlblock
1346
+ case 'icemailformblock': // block-icemailformblock - Interactive Content Email Form
1347
+ case 'imageblock': // block-imageblock - Image Block
1348
+ case 'textblock': // block-textblock
1349
+ case 'smartcaptureblock': // other-smartcaptureblock
1203
1350
  case 'codesnippetblock': {
1204
- // other
1351
+ // other-codesnippetblock
1205
1352
  // metadata.content
1206
- let fileExt = 'html'; // eslint-disable-line no-case-declarations
1207
- if (
1353
+ let fileExt;
1354
+ let content = metadata?.content;
1355
+ const ssjs = Util.getSsjs(metadata?.content);
1356
+
1357
+ if (ssjs) {
1358
+ fileExt = 'ssjs';
1359
+ content = ssjs;
1360
+ } else if (
1208
1361
  metadata.assetType.name === 'codesnippetblock' && // extracted code snippets should end on the right extension
1209
- // we are making a few assumptions during retrieve to pick the right one
1210
- metadata?.content?.includes('%%[')
1362
+ content?.includes('%%[')
1211
1363
  ) {
1212
1364
  fileExt = 'amp';
1365
+ } else {
1366
+ fileExt = 'html';
1213
1367
  }
1214
- if (metadata?.content?.length) {
1368
+ if (content?.length) {
1215
1369
  codeArr.push({
1216
1370
  subFolder: null,
1217
1371
  fileName: customerKey,
1218
1372
  fileExt: fileExt,
1219
- content: metadata.content,
1373
+ content: content,
1220
1374
  });
1221
1375
  delete metadata.content;
1222
1376
  }
@@ -23,7 +23,6 @@ class AttributeGroup extends MetadataType {
23
23
  retrieveDir,
24
24
  '/hub/v1/contacts/schema/attributeGroups',
25
25
  null,
26
- null,
27
26
  key
28
27
  );
29
28
  }
@@ -230,12 +230,11 @@ class Automation extends MetadataType {
230
230
  * @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
231
231
  * @param {string} targetBU name/shorthand of target businessUnit for mapping
232
232
  * @param {string} retrieveDir directory where metadata after deploy should be saved
233
+ * @param {boolean} [isRefresh] optional flag - so far not used by automation
233
234
  * @returns {Promise.<TYPE.AutomationMap>} Promise
234
235
  */
235
- static async deploy(metadata, targetBU, retrieveDir) {
236
- const orignalMetadata = JSON.parse(JSON.stringify(metadata));
237
- const upsertResults = await this.upsert(metadata, targetBU);
238
- await this.postDeployTasks(upsertResults, orignalMetadata);
236
+ static async deploy(metadata, targetBU, retrieveDir, isRefresh) {
237
+ const upsertResults = await this.upsert(metadata, targetBU, isRefresh);
239
238
  await this.saveResults(upsertResults, retrieveDir, null);
240
239
  return upsertResults;
241
240
  }
@@ -868,7 +867,7 @@ class Automation extends MetadataType {
868
867
  row1.push(
869
868
  `| ${column.title}${
870
869
  column.description
871
- ? `<br>_<small>${column.description.replace(/\n/g, '<br>')}</small>_`
870
+ ? `<br>_<small>${column.description.replaceAll('\n', '<br>')}</small>_`
872
871
  : ''
873
872
  } `
874
873
  );
@@ -1000,6 +999,50 @@ class Automation extends MetadataType {
1000
999
  return super.getFilesToCommit(keyArr);
1001
1000
  }
1002
1001
  }
1002
+ /**
1003
+ * helper to allow us to select single metadata entries via REST
1004
+ *
1005
+ * @private
1006
+ * @param {string} key customer key
1007
+ * @returns {Promise.<string>} objectId or enpty string
1008
+ */
1009
+ static async _getObjectIdForSingleRetrieve(key) {
1010
+ const response = await this.client.soap.retrieve('Program', ['ObjectID'], {
1011
+ filter: {
1012
+ leftOperand: 'CustomerKey',
1013
+ operator: 'equals',
1014
+ rightOperand: key,
1015
+ },
1016
+ });
1017
+ return response?.Results?.length ? response.Results[0].ObjectID : null;
1018
+ }
1019
+
1020
+ /**
1021
+ * Delete a metadata item from the specified business unit
1022
+ *
1023
+ * @param {string} customerKey Identifier of data extension
1024
+ * @returns {boolean} deletion success status
1025
+ */
1026
+ static async deleteByKey(customerKey) {
1027
+ // the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
1028
+ const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
1029
+ if (!objectId) {
1030
+ Util.logger.error(` - automation not found`);
1031
+ return false;
1032
+ }
1033
+ return super.deleteByKeySOAP(customerKey, 'CustomerKey');
1034
+ }
1035
+ /**
1036
+ * clean up after deleting a metadata item
1037
+ *
1038
+ * @param {string} customerKey Identifier of metadata item
1039
+ * @returns {void}
1040
+ */
1041
+ static async postDeleteTasks(customerKey) {
1042
+ // delete local copy: retrieve/cred/bu/.../...-meta.json
1043
+ // delete local copy: retrieve/cred/bu/.../...-doc.md
1044
+ await super.postDeleteTasks(customerKey, [`${this.definition.type}-doc.md`]);
1045
+ }
1003
1046
  }
1004
1047
 
1005
1048
  // Assign definition to static attributes
@@ -21,31 +21,44 @@ class Campaign extends MetadataType {
21
21
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise
22
22
  */
23
23
  static async retrieve(retrieveDir, _, __, key) {
24
- const res = await super.retrieveREST(retrieveDir, '/hub/v1/campaigns', null, null, key);
24
+ const res = await super.retrieveREST(
25
+ retrieveDir,
26
+ '/legacy/v1/beta2/data/campaign/',
27
+ null,
28
+ key
29
+ );
25
30
  // get assignments
26
-
31
+ Util.logger.info(`Retrieving: campaignAsset`);
27
32
  const campaignAssets = await Promise.all(
28
33
  Object.keys(res.metadata).map((key) =>
29
- this.getAssetTags(retrieveDir, res.metadata[key].id, key)
34
+ this.getAssetTags(retrieveDir, res.metadata[key].campaignId, key)
30
35
  )
31
36
  );
32
37
  Util.logger.info(
33
- `Downloaded: campaignAssets (${campaignAssets.flat().length})` + Util.getKeysString(key)
38
+ `Downloaded: campaignAsset (${campaignAssets.flat().length})` + Util.getKeysString(key)
34
39
  );
35
40
 
36
41
  return res;
37
42
  }
43
+ /**
44
+ * Retrieves event definition metadata for caching
45
+ *
46
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
47
+ */
48
+ static retrieveForCache() {
49
+ return super.retrieveREST(null, '/legacy/v1/beta2/data/campaign/');
50
+ }
38
51
 
39
52
  /**
40
53
  * Parses campaign asset response body and returns metadata entries mapped to their id
41
54
  *
42
55
  * @param {string} retrieveDir folder where to save
43
- * @param {string} id of camapaign to retrieve
56
+ * @param {string} campaignId of camapaign to retrieve
44
57
  * @param {string} name of camapaign for saving
45
58
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Campaign Asset Object
46
59
  */
47
- static async getAssetTags(retrieveDir, id, name) {
48
- const res = await this.client.rest.getBulk(`/hub/v1/campaigns/${id}/assets`);
60
+ static async getAssetTags(retrieveDir, campaignId, name) {
61
+ const res = await this.client.rest.getBulk(`/hub/v1/campaigns/${campaignId}/assets`);
49
62
 
50
63
  for (const asset of res.items) {
51
64
  await File.writeJSONToFile(
@@ -36,7 +36,7 @@ class ContentArea extends MetadataType {
36
36
  };
37
37
  }
38
38
  // !dont activate `await File.initPrettier('html');` as we only want to retrieve for migration and formatting might mess with the outcome
39
- return super.retrieveSOAP(retrieveDir, requestParams);
39
+ return super.retrieveSOAP(retrieveDir, requestParams, key);
40
40
  }
41
41
  /**
42
42
  * manages post retrieve steps