mcdev 4.3.4 → 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 (227) 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/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  5. package/.github/workflows/code-test.yml +36 -0
  6. package/.github/workflows/coverage-base-update.yml +57 -0
  7. package/.github/workflows/coverage-develop-branch.yml +41 -0
  8. package/.github/workflows/coverage-main-branch.yml +41 -0
  9. package/.github/workflows/coverage.yml +77 -0
  10. package/.husky/post-checkout +1 -1
  11. package/.prettierrc +1 -1
  12. package/.vscode/extensions.json +0 -4
  13. package/boilerplate/config.json +1 -1
  14. package/boilerplate/files/.prettierrc +1 -1
  15. package/boilerplate/files/.vscode/extensions.json +1 -1
  16. package/boilerplate/forcedUpdates.json +4 -0
  17. package/docs/dist/documentation.md +1196 -430
  18. package/lib/Builder.js +6 -1
  19. package/lib/Deployer.js +30 -5
  20. package/lib/MetadataTypeDefinitions.js +8 -6
  21. package/lib/MetadataTypeInfo.js +8 -6
  22. package/lib/cli.js +54 -42
  23. package/lib/index.js +82 -8
  24. package/lib/metadataTypes/Asset.js +73 -1
  25. package/lib/metadataTypes/AttributeGroup.js +0 -1
  26. package/lib/metadataTypes/Automation.js +48 -5
  27. package/lib/metadataTypes/Campaign.js +20 -7
  28. package/lib/metadataTypes/ContentArea.js +1 -1
  29. package/lib/metadataTypes/DataExtension.js +221 -184
  30. package/lib/metadataTypes/DataExtensionField.js +12 -19
  31. package/lib/metadataTypes/DataExtensionTemplate.js +1 -1
  32. package/lib/metadataTypes/DataExtract.js +1 -1
  33. package/lib/metadataTypes/DataExtractType.js +1 -1
  34. package/lib/metadataTypes/Email.js +1 -1
  35. package/lib/metadataTypes/{EmailSendDefinition.js → EmailSend.js} +5 -5
  36. package/lib/metadataTypes/{EventDefinition.js → Event.js} +17 -35
  37. package/lib/metadataTypes/{FtpLocation.js → FileLocation.js} +2 -2
  38. package/lib/metadataTypes/FileTransfer.js +8 -7
  39. package/lib/metadataTypes/Filter.js +1 -1
  40. package/lib/metadataTypes/Folder.js +8 -3
  41. package/lib/metadataTypes/ImportFile.js +6 -6
  42. package/lib/metadataTypes/{Interaction.js → Journey.js} +311 -147
  43. package/lib/metadataTypes/List.js +2 -2
  44. package/lib/metadataTypes/MetadataType.js +318 -90
  45. package/lib/metadataTypes/MobileCode.js +0 -1
  46. package/lib/metadataTypes/MobileKeyword.js +336 -40
  47. package/lib/metadataTypes/MobileMessage.js +473 -0
  48. package/lib/metadataTypes/Query.js +114 -32
  49. package/lib/metadataTypes/Role.js +60 -21
  50. package/lib/metadataTypes/Script.js +2 -3
  51. package/lib/metadataTypes/SendClassification.js +40 -0
  52. package/lib/metadataTypes/SetDefinition.js +1 -7
  53. package/lib/metadataTypes/TransactionalEmail.js +2 -3
  54. package/lib/metadataTypes/TransactionalMessage.js +1 -2
  55. package/lib/metadataTypes/TransactionalSMS.js +8 -15
  56. package/lib/metadataTypes/{TriggeredSendDefinition.js → TriggeredSend.js} +35 -27
  57. package/lib/metadataTypes/User.js +1177 -0
  58. package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
  59. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +1 -0
  60. package/lib/metadataTypes/definitions/Automation.definition.js +3 -2
  61. package/lib/metadataTypes/definitions/Campaign.definition.js +79 -4
  62. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -0
  63. package/lib/metadataTypes/definitions/DataExtension.definition.js +2 -1
  64. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -0
  65. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -0
  66. package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -0
  67. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -0
  68. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -0
  69. package/lib/metadataTypes/definitions/Email.definition.js +1 -0
  70. package/lib/metadataTypes/definitions/{EmailSendDefinition.definition.js → EmailSend.definition.js} +4 -2
  71. package/lib/metadataTypes/definitions/{EventDefinition.definition.js → Event.definition.js} +2 -1
  72. package/lib/metadataTypes/definitions/{FtpLocation.definition.js → FileLocation.definition.js} +4 -3
  73. package/lib/metadataTypes/definitions/FileTransfer.definition.js +3 -2
  74. package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
  75. package/lib/metadataTypes/definitions/Folder.definition.js +2 -0
  76. package/lib/metadataTypes/definitions/ImportFile.definition.js +4 -3
  77. package/lib/metadataTypes/definitions/{Interaction.definition.js → Journey.definition.js} +11 -2
  78. package/lib/metadataTypes/definitions/List.definition.js +1 -0
  79. package/lib/metadataTypes/definitions/MobileCode.definition.js +3 -1
  80. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +27 -17
  81. package/lib/metadataTypes/definitions/MobileMessage.definition.js +743 -0
  82. package/lib/metadataTypes/definitions/Query.definition.js +3 -2
  83. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  84. package/lib/metadataTypes/definitions/Script.definition.js +1 -0
  85. package/lib/metadataTypes/definitions/SendClassification.definition.js +114 -0
  86. package/lib/metadataTypes/definitions/SetDefinition.definition.js +1 -0
  87. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  88. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +1 -0
  89. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +1 -0
  90. package/lib/metadataTypes/definitions/{TriggeredSendDefinition.definition.js → TriggeredSend.definition.js} +5 -3
  91. package/lib/metadataTypes/definitions/User.definition.js +365 -0
  92. package/lib/retrieveChangelog.js +1 -2
  93. package/lib/util/auth.js +29 -9
  94. package/lib/util/businessUnit.js +3 -3
  95. package/lib/util/cli.js +55 -7
  96. package/lib/util/devops.js +6 -4
  97. package/lib/util/file.js +55 -13
  98. package/lib/util/init.config.js +1 -2
  99. package/lib/util/init.npm.js +3 -3
  100. package/lib/util/util.js +23 -14
  101. package/package.json +16 -15
  102. package/test/general.test.js +62 -0
  103. package/test/mockRoot/.mcdevrc.json +7 -5
  104. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testBlocked_user.user-meta.json +23 -0
  105. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testExisting_user.user-meta.json +31 -0
  106. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testNew_user.user-meta.json +27 -0
  107. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{childBU_dataextension_test.dataExtension-meta.json → testExisting_dataExtension.dataExtension-meta.json} +2 -2
  108. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{testDataExtension.dataExtension-meta.json → testNew_dataExtension.dataExtension-meta.json} +2 -2
  109. package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_interaction.interaction-meta.json +576 -0
  110. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp +2 -0
  111. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json +10 -0
  112. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp +2 -0
  113. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json +10 -0
  114. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.amp +1 -0
  115. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.json +61 -0
  116. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.amp +1 -0
  117. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.json +60 -0
  118. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  119. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json +1 -1
  120. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql +1 -1
  121. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +1 -1
  122. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -1
  123. package/test/resourceFactory.js +13 -0
  124. package/test/resources/1111111/accountUser/configure-response.xml +70 -0
  125. package/test/resources/1111111/accountUser/create-response.xml +97 -0
  126. package/test/resources/1111111/accountUser/retrieve-response.xml +156 -0
  127. package/test/resources/1111111/accountUser/update-response.xml +111 -0
  128. package/test/resources/1111111/accountUserAccount/retrieve-response.xml +77 -0
  129. package/test/resources/1111111/platform/v1/setup/quickflow/data/get-response.json +455 -0
  130. package/test/resources/1111111/role/retrieve-response.xml +76 -0
  131. package/test/resources/1111111/user/build-expected.json +16 -0
  132. package/test/resources/1111111/user/create-expected.json +21 -0
  133. package/test/resources/1111111/user/retrieve-expected.json +24 -0
  134. package/test/resources/1111111/user/template-expected.json +16 -0
  135. package/test/resources/1111111/user/update-expected.json +21 -0
  136. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/delete-response.json +1 -0
  137. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +17 -0
  138. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +3 -3
  139. package/test/resources/9999999/automation/v1/queries/get-response.json +21 -4
  140. package/test/resources/9999999/automation/v1/queries/post-response.json +4 -4
  141. package/test/resources/9999999/data/v1/customobjectdata/key/{childBU_dataextension_test → testExisting_dataExtension}/rowset/get-response.json +1 -1
  142. package/test/resources/9999999/dataExtension/build-expected.json +3 -3
  143. package/test/resources/9999999/dataExtension/create-expected.json +2 -2
  144. package/test/resources/9999999/dataExtension/create-response.xml +8 -3
  145. package/test/resources/9999999/dataExtension/retrieve-expected.json +3 -3
  146. package/test/resources/9999999/dataExtension/retrieve-response.xml +9 -4
  147. package/test/resources/9999999/dataExtension/template-expected.json +3 -3
  148. package/test/resources/9999999/dataExtension/update-expected.json +3 -3
  149. package/test/resources/9999999/dataExtension/update-response.xml +9 -4
  150. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +14 -9
  151. package/test/resources/9999999/interaction/v1/interactions/get-response.json +312 -0
  152. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/get-response.json +312 -0
  153. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/put-response.json +592 -0
  154. package/test/resources/9999999/journey/build-expected.json +572 -0
  155. package/test/resources/9999999/journey/get-expected.json +576 -0
  156. package/test/resources/9999999/journey/put-expected.json +576 -0
  157. package/test/resources/9999999/journey/template-expected.json +572 -0
  158. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json +42 -0
  159. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow/delete-response.json +0 -0
  160. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -0
  161. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/post-response.json +3 -0
  162. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/delete-response.json +0 -0
  163. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json +106 -0
  164. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/post-response.json +0 -0
  165. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTQ3Ojc4OjA/get-response.json +127 -0
  166. package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +129 -0
  167. package/test/resources/9999999/legacy/v1/beta/mobile/message/post-response.json +3 -0
  168. package/test/resources/9999999/legacy/v1/beta2/data/campaign/get-response.json +29 -0
  169. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  170. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +1 -1
  171. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +1 -1
  172. package/test/resources/9999999/mobileKeyword/build-expected.amp +2 -0
  173. package/test/resources/9999999/mobileKeyword/build-expected.json +9 -0
  174. package/test/resources/9999999/mobileKeyword/get-expected.amp +2 -0
  175. package/test/resources/9999999/mobileKeyword/get-expected.json +15 -0
  176. package/test/resources/9999999/mobileKeyword/post-create-expected.amp +2 -0
  177. package/test/resources/9999999/mobileKeyword/post-create-expected.json +17 -0
  178. package/test/resources/9999999/mobileKeyword/template-expected.amp +2 -0
  179. package/test/resources/9999999/mobileKeyword/template-expected.json +9 -0
  180. package/test/resources/9999999/mobileMessage/build-expected.amp +1 -0
  181. package/test/resources/9999999/mobileMessage/build-expected.json +60 -0
  182. package/test/resources/9999999/mobileMessage/get-expected.amp +1 -0
  183. package/test/resources/9999999/mobileMessage/get-expected.json +61 -0
  184. package/test/resources/9999999/mobileMessage/post-create-expected.amp +1 -0
  185. package/test/resources/9999999/mobileMessage/post-create-expected.json +63 -0
  186. package/test/resources/9999999/mobileMessage/post-update-expected.amp +1 -0
  187. package/test/resources/9999999/mobileMessage/post-update-expected.json +61 -0
  188. package/test/resources/9999999/mobileMessage/template-expected.amp +1 -0
  189. package/test/resources/9999999/mobileMessage/template-expected.json +60 -0
  190. package/test/resources/9999999/query/build-expected.json +1 -1
  191. package/test/resources/9999999/query/get-expected.json +1 -1
  192. package/test/resources/9999999/query/get2-expected.json +11 -0
  193. package/test/resources/9999999/query/patch-expected.json +1 -1
  194. package/test/resources/9999999/query/post-expected.json +1 -1
  195. package/test/resources/9999999/query/template-expected.json +1 -1
  196. package/test/resources/9999999/queryDefinition/retrieve-response.xml +30 -0
  197. package/test/resources/9999999/transactionalEmail/build-expected.json +5 -5
  198. package/test/resources/9999999/transactionalEmail/get-expected.json +1 -1
  199. package/test/resources/9999999/transactionalEmail/patch-expected.json +1 -1
  200. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  201. package/test/resources/9999999/transactionalEmail/template-expected.json +5 -5
  202. package/test/resources/9999999/transactionalPush/build-expected.json +2 -2
  203. package/test/resources/9999999/transactionalPush/template-expected.json +2 -2
  204. package/test/resources/9999999/transactionalSMS/build-expected.json +3 -3
  205. package/test/resources/9999999/transactionalSMS/template-expected.json +3 -3
  206. package/test/{dataExtension.test.js → type.dataExtension.test.js} +78 -21
  207. package/test/{interaction.test.js → type.journey.test.js} +64 -30
  208. package/test/type.mobileKeyword.test.js +250 -0
  209. package/test/type.mobileMessage.test.js +205 -0
  210. package/test/{query.test.js → type.query.test.js} +102 -5
  211. package/test/{transactionalEmail.test.js → type.transactionalEmail.test.js} +40 -2
  212. package/test/{transactionalPush.test.js → type.transactionalPush.test.js} +41 -2
  213. package/test/{transactionalSMS.test.js → type.transactionalSMS.test.js} +73 -3
  214. package/test/type.user.test.js +160 -0
  215. package/test/utils.js +17 -5
  216. package/types/mcdev.d.js +48 -15
  217. package/.github/workflows/code-analysis.yml +0 -57
  218. package/lib/metadataTypes/AccountUser.js +0 -426
  219. package/lib/metadataTypes/definitions/AccountUser.definition.js +0 -227
  220. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +0 -266
  221. package/test/resources/9999999/interaction/build-expected.json +0 -260
  222. package/test/resources/9999999/interaction/get-expected.json +0 -264
  223. package/test/resources/9999999/interaction/put-expected.json +0 -264
  224. package/test/resources/9999999/interaction/template-expected.json +0 -260
  225. package/test/resources/9999999/interaction/v1/interactions/put-response.json +0 -280
  226. /package/test/mockRoot/deploy/testInstance/testBU/{interaction → journey}/testNew_interaction.interaction-meta.json +0 -0
  227. /package/test/resources/9999999/{interaction → journey}/post-expected.json +0 -0
@@ -87,14 +87,16 @@ class MetadataType {
87
87
  * Returns fieldnames of Metadata Type. 'this.definition.fields' variable only set in child classes.
88
88
  *
89
89
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
90
+ * @param {boolean} [isCaching] if true, then check if field should be skipped for caching
90
91
  * @returns {string[]} Fieldnames
91
92
  */
92
- static getFieldNamesToRetrieve(additionalFields) {
93
+ static getFieldNamesToRetrieve(additionalFields, isCaching) {
93
94
  const fieldNames = [];
94
95
  for (const fieldName in this.definition.fields) {
95
96
  if (
96
97
  additionalFields?.includes(fieldName) ||
97
- this.definition.fields[fieldName].retrieving
98
+ (this.definition.fields[fieldName].retrieving &&
99
+ !(isCaching && this.definition.fields[fieldName].skipCache))
98
100
  ) {
99
101
  fieldNames.push(fieldName);
100
102
  }
@@ -112,11 +114,11 @@ class MetadataType {
112
114
  * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
113
115
  * @param {string} deployDir directory where deploy metadata are saved
114
116
  * @param {string} retrieveDir directory where metadata after deploy should be saved
117
+ * @param {boolean} [isRefresh] optional flag to indicate that triggeredSend should be refreshed after deployment of assets
115
118
  * @returns {Promise.<TYPE.MetadataTypeMap>} Promise of keyField => metadata map
116
119
  */
117
- static async deploy(metadata, deployDir, retrieveDir) {
118
- const upsertResults = await this.upsert(metadata, deployDir);
119
- await this.postDeployTasks(upsertResults, metadata);
120
+ static async deploy(metadata, deployDir, retrieveDir, isRefresh) {
121
+ const upsertResults = await this.upsert(metadata, deployDir, isRefresh);
120
122
  const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
121
123
  if (
122
124
  this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
@@ -132,11 +134,60 @@ class MetadataType {
132
134
  /**
133
135
  * Gets executed after deployment of metadata type
134
136
  *
135
- * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
137
+ * @param {TYPE.MetadataTypeMap} upsertResults metadata mapped by their keyField as returned by update/create
136
138
  * @param {TYPE.MetadataTypeMap} originalMetadata metadata to be updated (contains additioanl fields)
139
+ * @param {{created: number, updated: number}} createdUpdated counter representing successful creates/updates
140
+ * @param {boolean} [isRefresh] optional flag to indicate that triggeredSend should be refreshed after deployment of assets
141
+ * @returns {void}
142
+ */
143
+ static postDeployTasks(upsertResults, originalMetadata, createdUpdated, isRefresh) {}
144
+
145
+ /**
146
+ * helper for {@link createREST}
147
+ *
148
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
149
+ * @param {object} apiResponse varies depending on the API call
137
150
  * @returns {void}
138
151
  */
139
- static postDeployTasks(metadata, originalMetadata) {}
152
+ static postCreateTasks(metadataEntry, apiResponse) {}
153
+
154
+ /**
155
+ * helper for {@link updateREST}
156
+ *
157
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
158
+ * @param {object} apiResponse varies depending on the API call
159
+ * @returns {void}
160
+ */
161
+ static postUpdateTasks(metadataEntry, apiResponse) {}
162
+
163
+ /**
164
+ * helper for {@link createREST} when legacy API endpoints as these do not return the created item but only their new id
165
+ *
166
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
167
+ * @param {object} apiResponse varies depending on the API call
168
+ * @returns {Promise.<void>} -
169
+ */
170
+ static async postDeployTasks_legacyApi(metadataEntry, apiResponse) {
171
+ if (!apiResponse?.[this.definition.idField] && !metadataEntry?.[this.definition.idField]) {
172
+ return;
173
+ }
174
+ const id =
175
+ apiResponse?.[this.definition.idField] || metadataEntry?.[this.definition.idField];
176
+ // re-retrieve created items because the API does not return any info for them except the new id (api key)
177
+ try {
178
+ const { metadata } = await this.retrieveForCache(null, null, 'id:' + id);
179
+ const item = Object.values(metadata)[0];
180
+ // ensure the "created item" cli log entry has the new auto-generated value
181
+ metadataEntry[this.definition.keyField] = item[this.definition.keyField];
182
+ // ensure postRetrieveTasks has the complete object in "apiResponse"
183
+ Object.assign(apiResponse, item);
184
+ // postRetrieveTasks will be run automatically on this via super.saveResult
185
+ } catch (ex) {
186
+ throw new Error(
187
+ `Could not get details for new ${this.definition.type} ${id} from server (${ex.message})`
188
+ );
189
+ }
190
+ }
140
191
 
141
192
  /**
142
193
  * Gets executed after retreive of metadata type
@@ -222,10 +273,11 @@ class MetadataType {
222
273
  *
223
274
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
224
275
  * @param {string[]} [subTypeArr] optionally limit to a single subtype
276
+ * @param {string} [key] customer key of single item to retrieve
225
277
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
226
278
  */
227
- static async retrieveForCache(additionalFields, subTypeArr) {
228
- return this.retrieve(null, additionalFields, subTypeArr);
279
+ static async retrieveForCache(additionalFields, subTypeArr, key) {
280
+ return this.retrieve(null, additionalFields, subTypeArr, key);
229
281
  }
230
282
  /**
231
283
  * Gets metadata cache with limited fields and does not store value to disk
@@ -246,6 +298,19 @@ class MetadataType {
246
298
  );
247
299
  return { metadata: null, type: this.definition.type };
248
300
  }
301
+ /**
302
+ * Retrieve a specific Script by Name
303
+ *
304
+ * @param {string} templateDir Directory where retrieved metadata directory will be saved
305
+ * @param {string} uri rest endpoint for GET
306
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
307
+ * @param {string} name name (not key) of the metadata item
308
+ * @returns {Promise.<{metadata: TYPE.MetadataTypeItem, type: string}>} Promise
309
+ */
310
+ static async retrieveTemplateREST(templateDir, uri, templateVariables, name) {
311
+ return this.retrieveREST(templateDir, uri, templateVariables, name);
312
+ }
313
+
249
314
  /**
250
315
  * Gets metadata cache with limited fields and does not store value to disk
251
316
  *
@@ -293,6 +358,26 @@ class MetadataType {
293
358
  }
294
359
  // return;
295
360
  }
361
+ if (this.definition.stringifyFieldsBeforeTemplate) {
362
+ // numeric fields are returned as numbers by the SDK/API. If we try to replace them in buildTemplate it would break the JSON format - but not if we stringify them first because then the {{{var}}} is in quotes
363
+ metadataStr = JSON.parse(metadataStr);
364
+ for (const field of this.definition.stringifyFieldsBeforeTemplate) {
365
+ if (metadataStr[field]) {
366
+ if (Array.isArray(metadataStr[field])) {
367
+ for (let i = 0; i < metadataStr[field].length; i++) {
368
+ metadataStr[field][i] = metadataStr[field][i].toString();
369
+ }
370
+ } else if ('object' === typeof metadataStr[field]) {
371
+ for (const subField in metadataStr[field]) {
372
+ metadataStr[field][subField] = metadataStr[field][subField].toString();
373
+ }
374
+ } else {
375
+ metadataStr[field] = metadataStr[field].toString();
376
+ }
377
+ }
378
+ }
379
+ metadataStr = JSON.stringify(metadataStr);
380
+ }
296
381
  const metadata = JSON.parse(Util.replaceByObject(metadataStr, templateVariables));
297
382
  this.keepTemplateFields(metadata);
298
383
 
@@ -444,28 +529,30 @@ class MetadataType {
444
529
  /**
445
530
  * MetadataType upsert, after retrieving from target and comparing to check if create or update operation is needed.
446
531
  *
447
- * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
532
+ * @param {TYPE.MetadataTypeMap} metadataMap metadata mapped by their keyField
448
533
  * @param {string} deployDir directory where deploy metadata are saved
534
+ * @param {boolean} [isRefresh] optional flag to indicate that triggeredSend should be refreshed after deployment of assets
449
535
  * @returns {Promise.<TYPE.MetadataTypeMap>} keyField => metadata map
450
536
  */
451
- static async upsert(metadata, deployDir) {
537
+ static async upsert(metadataMap, deployDir, isRefresh) {
538
+ const orignalMetadataMap = JSON.parse(JSON.stringify(metadataMap));
452
539
  const metadataToUpdate = [];
453
540
  const metadataToCreate = [];
454
541
  let filteredByPreDeploy = 0;
455
- for (const metadataKey in metadata) {
542
+ for (const metadataKey in metadataMap) {
456
543
  let hasError = false;
457
544
  try {
458
545
  // preDeployTasks parsing
459
546
  let deployableMetadata;
460
547
  try {
461
548
  deployableMetadata = await this.preDeployTasks(
462
- metadata[metadataKey],
549
+ metadataMap[metadataKey],
463
550
  deployDir
464
551
  );
465
552
  } catch (ex) {
466
553
  // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
467
554
  hasError = true;
468
- deployableMetadata = metadata[metadataKey];
555
+ deployableMetadata = metadataMap[metadataKey];
469
556
  Util.logger.error(
470
557
  ` ☇ skipping ${this.definition.type} ${
471
558
  deployableMetadata[this.definition.keyField]
@@ -474,10 +561,10 @@ class MetadataType {
474
561
  }
475
562
  // if preDeploy returns nothing then it cannot be deployed so skip deployment
476
563
  if (deployableMetadata) {
477
- metadata[metadataKey] = deployableMetadata;
564
+ metadataMap[metadataKey] = deployableMetadata;
478
565
  // create normalizedKey off of whats in the json rather than from "metadataKey" because preDeployTasks might have altered something (type asset)
479
- this.createOrUpdate(
480
- metadata,
566
+ await this.createOrUpdate(
567
+ metadataMap,
481
568
  metadataKey,
482
569
  hasError,
483
570
  metadataToUpdate,
@@ -518,7 +605,7 @@ class MetadataType {
518
605
  `${this.definition.type} upsert: ${createResults.length} of ${metadataToCreate.length} created / ${updateResults.length} of ${metadataToUpdate.length} updated` +
519
606
  (filteredByPreDeploy > 0 ? ` / ${filteredByPreDeploy} filtered` : '')
520
607
  );
521
-
608
+ let upsertResults;
522
609
  if (this.definition.bodyIteratorField === 'Results') {
523
610
  // if Results then parse as SOAP
524
611
  // put in Retrieve Format for parsing
@@ -530,73 +617,172 @@ class MetadataType {
530
617
  .filter((r) => r !== undefined && r !== null && Object.keys(r).length !== 0)
531
618
  .flatMap((r) => r.Results)
532
619
  .map((r) => r.Object);
533
- return this.parseResponseBody({ Results: metadataResults });
620
+ upsertResults = this.parseResponseBody({ Results: metadataResults });
534
621
  } else {
535
622
  // likely comming from one of the many REST APIs
536
623
  // put in Retrieve Format for parsing
537
624
  // todo add handling when response does not contain items.
538
625
  // @ts-ignore
539
626
  const metadataResults = createResults.concat(updateResults).filter(Boolean);
540
- return this.parseResponseBody(metadataResults);
627
+ upsertResults = this.parseResponseBody(metadataResults);
541
628
  }
629
+ await this.postDeployTasks(
630
+ upsertResults,
631
+ orignalMetadataMap,
632
+ { created: createResults.length, updated: updateResults.length },
633
+ isRefresh
634
+ );
635
+ return upsertResults;
542
636
  }
543
637
 
544
638
  /**
639
+ * helper for {@link MetadataType.upsert}
545
640
  *
546
- * @param {TYPE.MetadataTypeItem} metadata single metadata itme
641
+ * @param {TYPE.MetadataTypeMap} metadataMap list of metadata
547
642
  * @param {string} metadataKey key of item we are looking at
548
643
  * @param {boolean} hasError error flag from previous code
549
644
  * @param {TYPE.MetadataTypeItemDiff[]} metadataToUpdate list of items to update
550
645
  * @param {TYPE.MetadataTypeItem[]} metadataToCreate list of items to create
551
- * @returns {void}
646
+ * @returns {'create' | 'update' | 'skip'} action to take
552
647
  */
553
- static createOrUpdate(metadata, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
648
+ static createOrUpdate(metadataMap, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
554
649
  const normalizedKey = File.reverseFilterIllegalFilenames(
555
- metadata[metadataKey][this.definition.keyField]
650
+ metadataMap[metadataKey][this.definition.keyField]
556
651
  );
557
652
  // Update if it already exists; Create it if not
558
- if (Util.logger.level === 'debug' && metadata[metadataKey][this.definition.idField]) {
653
+ if (
654
+ Util.logger.level === 'debug' &&
655
+ metadataMap[metadataKey][this.definition.idField] &&
656
+ this.definition.idField !== this.definition.keyField
657
+ ) {
559
658
  // TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
560
659
  // only used if resource is excluded from cache and we still want to update it
561
660
  // needed e.g. to rewire lost folders
562
661
  Util.logger.warn(
563
662
  ' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
564
663
  );
565
- Util.logger.warn(JSON.stringify(metadata[metadataKey]));
664
+ Util.logger.warn(JSON.stringify(metadataMap[metadataKey]));
566
665
  if (hasError) {
567
666
  metadataToUpdate.push(null);
667
+ return 'skip';
568
668
  } else {
569
669
  metadataToUpdate.push({
570
670
  before: {},
571
- after: metadata[metadataKey],
671
+ after: metadataMap[metadataKey],
572
672
  });
673
+ return 'update';
573
674
  }
574
675
  } else if (cache.getByKey(this.definition.type, normalizedKey)) {
575
676
  // normal way of processing update files
576
677
  const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
577
- if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
678
+ if (!this.hasChanged(cachedVersion, metadataMap[metadataKey])) {
578
679
  hasError = true;
579
680
  }
681
+ if (Util.OPTIONS.changeKeyField) {
682
+ if (this.definition.keyField == this.definition.idField) {
683
+ Util.logger.error(
684
+ ` - --changeKeyField cannot be used for types that use their ID as key. Skipping change.`
685
+ );
686
+ hasError = true;
687
+ } else if (this.definition.keyIsFixed) {
688
+ Util.logger.error(
689
+ ` - type ${this.definition.type} does not support --changeKeyField and --changeKeyValue. Skipping change.`
690
+ );
691
+ hasError = true;
692
+ } else if (!metadataMap[metadataKey][Util.OPTIONS.changeKeyField]) {
693
+ Util.logger.error(
694
+ ` - --changeKeyField is set to ${Util.OPTIONS.changeKeyField} but no value was found in the metadata. Skipping change.`
695
+ );
696
+ hasError = true;
697
+ } else if (Util.OPTIONS.changeKeyField === this.definition.keyField) {
698
+ // simple issue, used WARN to avoid signaling a problem to ci/cd and don't fail deploy
699
+ Util.logger.warn(
700
+ ` - --changeKeyField is set to the same value as the keyField for ${this.definition.type}. Skipping change.`
701
+ );
702
+ } else if (metadataMap[metadataKey][Util.OPTIONS.changeKeyField]) {
703
+ // NOTE: trim twice while getting the newKey value to remove leading spaces before limiting the length
704
+ const newKey = (metadataMap[metadataKey][Util.OPTIONS.changeKeyField] + '')
705
+ .trim()
706
+ .slice(0, 36)
707
+ .trim();
708
+ if (metadataMap[metadataKey][Util.OPTIONS.changeKeyField] + '' > 36) {
709
+ Util.logger.warn(
710
+ `Customer Keys may not exceed 36 characters. Truncated the value in field ${Util.OPTIONS.changeKeyField} to ${newKey}`
711
+ );
712
+ }
713
+ if (metadataKey == newKey) {
714
+ Util.logger.warn(
715
+ ` - --changeKeyField(${Util.OPTIONS.changeKeyField}) is providing the current value of the key (${metadataKey}). Skipping change.`
716
+ );
717
+ } else {
718
+ Util.logger.info(
719
+ ` - Changing ${this.definition.type} key from ${metadataKey} to ${newKey} via --changeKeyField=${Util.OPTIONS.changeKeyField}`
720
+ );
721
+ metadataMap[metadataKey][this.definition.keyField] = newKey;
722
+
723
+ // ensure we can delete the old file(s) after the successful update
724
+ Util.changedKeysMap[this.definition.type] ||= {};
725
+ Util.changedKeysMap[this.definition.type][newKey] = metadataKey;
726
+ }
727
+ }
728
+ } else if (Util.OPTIONS.changeKeyValue) {
729
+ // NOTE: trim twice while getting the newKey value to remove leading spaces before limiting the length
730
+ const newKey = Util.OPTIONS.changeKeyValue.trim().slice(0, 36).trim();
731
+ if (Util.OPTIONS.changeKeyValue.trim().length > 36) {
732
+ Util.logger.warn(
733
+ `Customer Keys may not exceed 36 characters. Truncated your value to ${newKey}`
734
+ );
735
+ }
736
+ if (this.definition.keyField == this.definition.idField) {
737
+ Util.logger.error(
738
+ ` - --changeKeyValue cannot be used for types that use their ID as key. Skipping change.`
739
+ );
740
+ hasError = true;
741
+ } else if (this.definition.keyIsFixed) {
742
+ Util.logger.error(
743
+ ` - type ${this.definition.type} does not support --changeKeyField and --changeKeyValue. Skipping change.`
744
+ );
745
+ hasError = true;
746
+ } else if (metadataKey == newKey) {
747
+ Util.logger.warn(
748
+ ` - --changeKeyValue is providing the current value of the key (${metadataKey}). Skipping change.`
749
+ );
750
+ } else {
751
+ Util.logger.info(
752
+ ` - Changing ${this.definition.type} key from ${metadataKey} to ${newKey} via --changeKeyValue`
753
+ );
754
+ metadataMap[metadataKey][this.definition.keyField] = newKey;
755
+
756
+ // ensure we can delete the old file(s) after the successful update
757
+ Util.changedKeysMap[this.definition.type] ||= {};
758
+ Util.changedKeysMap[this.definition.type][newKey] = metadataKey;
759
+ }
760
+ }
580
761
 
581
762
  if (hasError) {
582
763
  // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
583
764
  metadataToUpdate.push(null);
765
+ return 'skip';
584
766
  } else {
585
767
  // add ObjectId to allow actual update
586
- metadata[metadataKey][this.definition.idField] =
768
+ metadataMap[metadataKey][this.definition.idField] =
587
769
  cachedVersion[this.definition.idField];
588
770
 
589
771
  metadataToUpdate.push({
590
- before: cache.getByKey(this.definition.type, normalizedKey),
591
- after: metadata[metadataKey],
772
+ before: cachedVersion,
773
+ after: metadataMap[metadataKey],
592
774
  });
775
+
776
+ return 'update';
593
777
  }
594
778
  } else {
595
779
  if (hasError) {
596
780
  // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
597
781
  metadataToCreate.push(null);
782
+ return 'skip';
598
783
  } else {
599
- metadataToCreate.push(metadata[metadataKey]);
784
+ metadataToCreate.push(metadataMap[metadataKey]);
785
+ return 'create';
600
786
  }
601
787
  }
602
788
  }
@@ -611,7 +797,9 @@ class MetadataType {
611
797
  static async createREST(metadataEntry, uri) {
612
798
  this.removeNotCreateableFields(metadataEntry);
613
799
  try {
614
- const response = await this.client.rest.post(uri, metadataEntry);
800
+ // set to empty object in case API returned nothing to be able to update it in helper classes
801
+ const response = (await this.client.rest.post(uri, metadataEntry)) || {};
802
+ await this.postCreateTasks(metadataEntry, response);
615
803
  Util.logger.info(
616
804
  ` - created ${this.definition.type}: ${
617
805
  metadataEntry[this.definition.keyField] ||
@@ -627,8 +815,12 @@ class MetadataType {
627
815
  metadataEntry[this.definition.nameField]
628
816
  } / ${metadataEntry[this.definition.nameField]}:`
629
817
  );
630
- for (const msg of parsedErrors) {
631
- Util.logger.error(' • ' + msg);
818
+ if (parsedErrors.length) {
819
+ for (const msg of parsedErrors) {
820
+ Util.logger.error(' • ' + msg);
821
+ }
822
+ } else if (ex?.message) {
823
+ Util.logger.debug(ex.message);
632
824
  }
633
825
  return null;
634
826
  }
@@ -638,16 +830,15 @@ class MetadataType {
638
830
  * Creates a single metadata entry via fuel-soap (generic lib not wrapper)
639
831
  *
640
832
  * @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
641
- * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
642
833
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
643
834
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
644
835
  */
645
- static async createSOAP(metadataEntry, overrideType, handleOutside) {
836
+ static async createSOAP(metadataEntry, handleOutside) {
837
+ const soapType = this.definition.soapType || this.definition.type;
838
+ this.removeNotCreateableFields(metadataEntry);
646
839
  try {
647
- this.removeNotCreateableFields(metadataEntry);
648
840
  const response = await this.client.soap.create(
649
- overrideType ||
650
- this.definition.type.charAt(0).toUpperCase() + this.definition.type.slice(1),
841
+ soapType.charAt(0).toUpperCase() + soapType.slice(1),
651
842
  metadataEntry,
652
843
  null
653
844
  );
@@ -671,16 +862,17 @@ class MetadataType {
671
862
  *
672
863
  * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
673
864
  * @param {string} uri rest endpoint for PATCH
674
- * @param {boolean} [usePut] some update requests require PUT instead of PATCH
865
+ * @param {'patch'|'post'|'put'} [httpMethod='patch'] defaults to 'patch'; some update requests require PUT instead of PATCH
675
866
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
676
867
  */
677
- static async updateREST(metadataEntry, uri, usePut) {
868
+ static async updateREST(metadataEntry, uri, httpMethod = 'patch') {
678
869
  this.removeNotUpdateableFields(metadataEntry);
679
870
  try {
680
- const response = usePut
681
- ? await this.client.rest.put(uri, metadataEntry)
682
- : await this.client.rest.patch(uri, metadataEntry);
871
+ // set to empty object in case API returned nothing to be able to update it in helper classes
872
+ const response = (await this.client.rest[httpMethod](uri, metadataEntry)) || {};
873
+ await this._postChangeKeyTasks(metadataEntry);
683
874
  this.checkForErrors(response);
875
+ await this.postUpdateTasks(metadataEntry, response);
684
876
  // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
685
877
  Util.logger.info(
686
878
  ` - updated ${this.definition.type}: ${
@@ -704,20 +896,54 @@ class MetadataType {
704
896
  }
705
897
  }
706
898
 
899
+ /**
900
+ * helper for {@link updateREST} and {@link updateSOAP} that removes old files after the key was changed
901
+ *
902
+ * @private
903
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
904
+ * @param {boolean} [keepMap] some types require to check the old-key new-key relationship in their postDeployTasks; currently used by dataExtension only
905
+ * @returns {void}
906
+ */
907
+ static async _postChangeKeyTasks(metadataEntry, keepMap = false) {
908
+ if (
909
+ (Util.OPTIONS.changeKeyField || Util.OPTIONS.changeKeyValue) &&
910
+ Util.changedKeysMap?.[this.definition.type]?.[metadataEntry[this.definition.keyField]]
911
+ ) {
912
+ const oldKey =
913
+ Util.changedKeysMap?.[this.definition.type]?.[
914
+ metadataEntry[this.definition.keyField]
915
+ ];
916
+
917
+ // delete file(s) of old key
918
+ await this.postDeleteTasks(oldKey);
919
+
920
+ // fix key in cache
921
+ const typeCache = cache.getCache()[this.definition.type];
922
+ typeCache[metadataEntry[this.definition.keyField]] = typeCache[oldKey];
923
+ delete typeCache[oldKey];
924
+
925
+ if (!keepMap) {
926
+ // clean entry from to-do list
927
+ delete Util.changedKeysMap?.[this.definition.type]?.[
928
+ metadataEntry[this.definition.keyField]
929
+ ];
930
+ }
931
+ }
932
+ }
933
+
707
934
  /**
708
935
  * Updates a single metadata entry via fuel-soap (generic lib not wrapper)
709
936
  *
710
937
  * @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
711
- * @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
712
938
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
713
939
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
714
940
  */
715
- static async updateSOAP(metadataEntry, overrideType, handleOutside) {
941
+ static async updateSOAP(metadataEntry, handleOutside) {
942
+ const soapType = this.definition.soapType || this.definition.type;
943
+ this.removeNotUpdateableFields(metadataEntry);
716
944
  try {
717
- this.removeNotUpdateableFields(metadataEntry);
718
945
  const response = await this.client.soap.update(
719
- overrideType ||
720
- this.definition.type.charAt(0).toUpperCase() + this.definition.type.slice(1),
946
+ soapType.charAt(0).toUpperCase() + soapType.slice(1),
721
947
  metadataEntry,
722
948
  null
723
949
  );
@@ -728,6 +954,7 @@ class MetadataType {
728
954
  } / ${metadataEntry[this.definition.nameField]}`
729
955
  );
730
956
  }
957
+ await this._postChangeKeyTasks(metadataEntry);
731
958
  return response;
732
959
  } catch (ex) {
733
960
  this._handleSOAPErrors(ex, 'updating', metadataEntry, handleOutside);
@@ -766,19 +993,17 @@ class MetadataType {
766
993
  *
767
994
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
768
995
  * @param {TYPE.SoapRequestParams} [requestParams] required for the specific request (filter for example)
996
+ * @param {string|number} [singleRetrieve] key of single item to filter by
769
997
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
770
998
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of item map
771
999
  */
772
- static async retrieveSOAP(retrieveDir, requestParams, additionalFields) {
773
- requestParams = requestParams || {};
774
- const fields = this.getFieldNamesToRetrieve(additionalFields);
1000
+ static async retrieveSOAP(retrieveDir, requestParams, singleRetrieve, additionalFields) {
1001
+ requestParams ||= {};
1002
+ const fields = this.getFieldNamesToRetrieve(additionalFields, !retrieveDir);
1003
+ const soapType = this.definition.soapType || this.definition.type;
775
1004
  let response;
776
1005
  try {
777
- response = await this.client.soap.retrieveBulk(
778
- this.definition.type,
779
- fields,
780
- requestParams
781
- );
1006
+ response = await this.client.soap.retrieveBulk(soapType, fields, requestParams);
782
1007
  } catch (ex) {
783
1008
  this._handleSOAPErrors(ex, 'retrieving');
784
1009
  return {};
@@ -788,7 +1013,8 @@ class MetadataType {
788
1013
  if (retrieveDir) {
789
1014
  const savedMetadata = await this.saveResults(metadata, retrieveDir, null);
790
1015
  Util.logger.info(
791
- `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
1016
+ `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` +
1017
+ Util.getKeysString(singleRetrieve)
792
1018
  );
793
1019
  if (
794
1020
  this.buObject &&
@@ -805,12 +1031,11 @@ class MetadataType {
805
1031
  *
806
1032
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
807
1033
  * @param {string} uri rest endpoint for GET
808
- * @param {string} [overrideType] force a metadata type (mainly used for Folders)
809
1034
  * @param {TYPE.TemplateMap} [templateVariables] variables to be replaced in the metadata
810
1035
  * @param {string|number} [singleRetrieve] key of single item to filter by
811
1036
  * @returns {Promise.<{metadata: (TYPE.MetadataTypeMap | TYPE.MetadataTypeItem), type: string}>} Promise of item map (single item for templated result)
812
1037
  */
813
- static async retrieveREST(retrieveDir, uri, overrideType, templateVariables, singleRetrieve) {
1038
+ static async retrieveREST(retrieveDir, uri, templateVariables, singleRetrieve) {
814
1039
  const response =
815
1040
  this.definition.restPagination && !singleRetrieve
816
1041
  ? await this.client.rest.getBulk(uri, this.definition.restPageSize || 500)
@@ -831,19 +1056,18 @@ class MetadataType {
831
1056
  const savedMetadata = await this.saveResults(
832
1057
  results,
833
1058
  retrieveDir,
834
- overrideType,
1059
+ null,
835
1060
  templateVariables
836
1061
  );
837
1062
  Util.logger.info(
838
- `Downloaded: ${overrideType || this.definition.type} (${
839
- Object.keys(savedMetadata).length
840
- })` + Util.getKeysString(singleRetrieve)
1063
+ `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` +
1064
+ Util.getKeysString(singleRetrieve)
841
1065
  );
842
1066
  }
843
1067
 
844
1068
  return {
845
1069
  metadata: templateVariables ? Object.values(results)[0] : results,
846
- type: overrideType || this.definition.type,
1070
+ type: this.definition.type,
847
1071
  };
848
1072
  }
849
1073
 
@@ -1535,8 +1759,8 @@ class MetadataType {
1535
1759
  * @returns {string[] | void} formatted Error Message
1536
1760
  */
1537
1761
  static checkForErrors(ex) {
1762
+ const errors = [];
1538
1763
  if (ex?.response?.status >= 400 && ex?.response?.status < 600) {
1539
- const errors = [];
1540
1764
  if (ex.response.data.errors) {
1541
1765
  for (const errMsg of ex.response.data.errors) {
1542
1766
  errors.push(
@@ -1562,8 +1786,8 @@ class MetadataType {
1562
1786
  }
1563
1787
  Util.logger.debug(JSON.stringify(ex.config));
1564
1788
  Util.logger.debug(JSON.stringify(ex.response.data));
1565
- return errors;
1566
1789
  }
1790
+ return errors;
1567
1791
  }
1568
1792
 
1569
1793
  /**
@@ -1593,38 +1817,44 @@ class MetadataType {
1593
1817
  * clean up after deleting a metadata item
1594
1818
  *
1595
1819
  * @param {string} customerKey Identifier of metadata item
1596
- * @returns {void}
1820
+ * @param {string[]} [additionalExtensions] additional file extensions to delete on top of `${this.definition.type}-meta.json`
1821
+ * @returns {Promise.<void>} - Promise
1597
1822
  */
1598
- static async postDeleteTasks(customerKey) {
1599
- // delete local copy: retrieve/cred/bu/type/...json
1600
- const jsonFile = File.normalizePath([
1601
- this.properties.directories.retrieve,
1602
- this.buObject.credential,
1603
- this.buObject.businessUnit,
1604
- this.definition.type,
1605
- `${customerKey}.${this.definition.type}-meta.json`,
1606
- ]);
1607
- await File.remove(jsonFile);
1823
+ static async postDeleteTasks(customerKey, additionalExtensions) {
1824
+ // delete local copy: retrieve/cred/bu/type/...json + whatever additional extensions were passed
1825
+ const extArr = [`${this.definition.type}-meta.json`, ...(additionalExtensions || [])];
1826
+ for (const ext of extArr) {
1827
+ const jsonFile = File.normalizePath([
1828
+ this.properties.directories.retrieve,
1829
+ this.buObject.credential,
1830
+ this.buObject.businessUnit,
1831
+ this.definition.type,
1832
+ `${customerKey}.${ext}`,
1833
+ ]);
1834
+ await File.remove(jsonFile);
1835
+ }
1608
1836
  }
1609
1837
 
1610
1838
  /**
1611
1839
  * Delete a data extension from the specified business unit
1612
1840
  *
1613
1841
  * @param {string} customerKey Identifier of metadata
1842
+ * @param {string} [overrideKeyField] optionally change the name of the key field if the api uses a different name
1614
1843
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1615
1844
  * @returns {boolean} deletion success flag
1616
1845
  */
1617
- static async deleteByKeySOAP(customerKey, handleOutside) {
1618
- const keyObj = {};
1619
- keyObj[this.definition.keyField] = customerKey;
1846
+ static async deleteByKeySOAP(customerKey, overrideKeyField, handleOutside) {
1847
+ const metadata = {};
1848
+ metadata[overrideKeyField || this.definition.keyField] = customerKey;
1849
+ const soapType = this.definition.soapType || this.definition.type;
1620
1850
  try {
1621
- this.client.soap.delete(
1622
- this.definition.type.charAt(0).toUpperCase() + this.definition.type.slice(1),
1623
- keyObj,
1851
+ await this.client.soap.delete(
1852
+ soapType.charAt(0).toUpperCase() + soapType.slice(1),
1853
+ metadata,
1624
1854
  null
1625
1855
  );
1626
1856
  if (!handleOutside) {
1627
- Util.logger.info(`- deleted ${this.definition.type}: ${customerKey}`);
1857
+ Util.logger.info(` - deleted ${this.definition.type}: ${customerKey}`);
1628
1858
  }
1629
1859
  this.postDeleteTasks(customerKey);
1630
1860
 
@@ -1653,12 +1883,10 @@ class MetadataType {
1653
1883
  * @returns {boolean} deletion success flag
1654
1884
  */
1655
1885
  static async deleteByKeyREST(url, key, handleOutside) {
1656
- const keyObj = {};
1657
- keyObj[this.definition.keyField] = key;
1658
1886
  try {
1659
1887
  await this.client.rest.delete(url);
1660
1888
  if (!handleOutside) {
1661
- Util.logger.info(`- deleted ${this.definition.type}: ${key}`);
1889
+ Util.logger.info(` - deleted ${this.definition.type}: ${key}`);
1662
1890
  }
1663
1891
  this.postDeleteTasks(key);
1664
1892
 
@@ -1682,7 +1910,7 @@ class MetadataType {
1682
1910
  * @returns {object} Metadata of BU in local directory
1683
1911
  */
1684
1912
  static readBUMetadataForType(readDir, listBadKeys, buMetadata) {
1685
- buMetadata = buMetadata || {};
1913
+ buMetadata ||= {};
1686
1914
  readDir = File.normalizePath([readDir, this.definition.type]);
1687
1915
  try {
1688
1916
  if (File.pathExistsSync(readDir)) {