mcdev 5.1.0 → 5.2.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 (192) hide show
  1. package/.eslintrc.json +4 -4
  2. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +2 -2
  4. package/.github/workflows/coverage-develop-branch.yml +0 -2
  5. package/.github/workflows/coverage-main-branch.yml +0 -2
  6. package/.github/workflows/coverage.yml +0 -2
  7. package/.husky/post-checkout +1 -0
  8. package/.husky/post-merge +1 -0
  9. package/.vscode/extensions.json +4 -0
  10. package/docs/dist/documentation.md +633 -286
  11. package/lib/Deployer.js +25 -25
  12. package/lib/MetadataTypeDefinitions.js +1 -1
  13. package/lib/MetadataTypeInfo.js +1 -1
  14. package/lib/Retriever.js +1 -1
  15. package/lib/cli.js +159 -9
  16. package/lib/index.js +395 -95
  17. package/lib/metadataTypes/Asset.js +10 -11
  18. package/lib/metadataTypes/AttributeGroup.js +76 -2
  19. package/lib/metadataTypes/AttributeSet.js +260 -0
  20. package/lib/metadataTypes/Automation.js +413 -96
  21. package/lib/metadataTypes/DataExtension.js +2 -2
  22. package/lib/metadataTypes/DataExtensionField.js +1 -1
  23. package/lib/metadataTypes/Event.js +2 -3
  24. package/lib/metadataTypes/Folder.js +1 -1
  25. package/lib/metadataTypes/Journey.js +5 -6
  26. package/lib/metadataTypes/MetadataType.js +149 -49
  27. package/lib/metadataTypes/MobileKeyword.js +8 -8
  28. package/lib/metadataTypes/MobileMessage.js +5 -5
  29. package/lib/metadataTypes/Query.js +26 -10
  30. package/lib/metadataTypes/Script.js +3 -3
  31. package/lib/metadataTypes/TransactionalSMS.js +5 -5
  32. package/lib/metadataTypes/TriggeredSend.js +25 -50
  33. package/lib/metadataTypes/User.js +7 -4
  34. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +117 -106
  35. package/lib/metadataTypes/definitions/{SetDefinition.definition.js → AttributeSet.definition.js} +54 -27
  36. package/lib/metadataTypes/definitions/Automation.definition.js +22 -15
  37. package/lib/metadataTypes/definitions/ImportFile.definition.js +36 -6
  38. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +1 -0
  39. package/lib/util/cache.js +9 -4
  40. package/lib/util/cli.js +40 -0
  41. package/lib/util/file.js +2 -2
  42. package/lib/util/init.js +84 -0
  43. package/lib/util/util.js +121 -13
  44. package/package.json +11 -11
  45. package/test/mockRoot/.mcdevrc.json +1 -1
  46. package/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +1 -2
  47. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +5 -6
  48. package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testExisting_dataExtract.dataExtract-meta.json +35 -0
  49. package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testNew_dataExtract.dataExtract-meta.json +35 -0
  50. package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testExisting_fileTransfer.fileTransfer-meta.json +17 -0
  51. package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testNew_fileTransfer.fileTransfer-meta.json +17 -0
  52. package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +29 -0
  53. package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +29 -0
  54. package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.json +11 -0
  55. package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.sql +6 -0
  56. package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.json +6 -0
  57. package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.ssjs +1 -0
  58. package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.json +6 -0
  59. package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.ssjs +1 -0
  60. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +29 -0
  61. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +29 -0
  62. package/test/resourceFactory.js +77 -12
  63. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDCustomerKey=testExisting_userANDEmaillike@-response.xml +27 -0
  64. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDEmaillike@-response.xml +156 -0
  65. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +87 -0
  66. package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmaillike@-response.xml +156 -0
  67. package/test/resources/1111111/accountUser/retrieve-CustomerKey=testExisting_userANDActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +27 -0
  68. package/test/resources/1111111/accountUserAccount/retrieve-AccountUser.AccountUserID=700301950-response.xml +60 -0
  69. package/test/resources/1111111/user/retrieve-expected.md +4 -2
  70. package/test/resources/9999999/attributeGroup/retrieve-expected.json +25 -0
  71. package/test/resources/9999999/attributeSet/retrieve-expected.json +748 -0
  72. package/test/resources/9999999/automation/build-expected.json +1 -2
  73. package/test/resources/9999999/automation/create-expected.json +7 -8
  74. package/test/resources/9999999/automation/create-testNew_automation-expected.md +4 -4
  75. package/test/resources/9999999/automation/patch_fixKeys-pause-expected.json +44 -0
  76. package/test/resources/9999999/automation/patch_fixKeys-schedule-expected.json +44 -0
  77. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
  78. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_pause-response.xml +42 -0
  79. package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +42 -0
  80. package/test/resources/9999999/automation/perform-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
  81. package/test/resources/9999999/automation/retrieve-expected.json +1 -2
  82. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +2 -2
  83. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
  84. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml +38 -0
  85. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_pause-response.xml +52 -0
  86. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +52 -0
  87. package/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
  88. package/test/resources/9999999/automation/template-expected.json +1 -2
  89. package/test/resources/9999999/automation/update-expected.json +1 -2
  90. package/test/resources/9999999/automation/update-testExisting_automation-expected.md +2 -2
  91. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json +85 -0
  92. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/patch-response.json +85 -0
  93. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/get-response.json +85 -0
  94. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/patch-response.json +85 -0
  95. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/get-response.json +85 -0
  96. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/patch-response.json +85 -0
  97. package/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +1 -1
  98. package/test/resources/9999999/automation/v1/automations/post-response.json +20 -19
  99. package/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/patch-response.json +38 -0
  100. package/test/resources/9999999/automation/v1/dataextracts/post-response.json +38 -0
  101. package/test/resources/9999999/automation/v1/dataextracttypes/get-response.json +50 -0
  102. package/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/patch-response.json +18 -0
  103. package/test/resources/9999999/automation/v1/filetransfers/post-response.json +18 -0
  104. package/test/resources/9999999/automation/v1/ftplocations/get-response.json +18 -0
  105. package/test/resources/9999999/automation/v1/imports/9d16f42c-2260-ed11-b849-48df37d1de8b/patch-response.json +31 -0
  106. package/test/resources/9999999/automation/v1/imports/get-response.json +1 -1
  107. package/test/resources/9999999/automation/v1/imports/post-response.json +30 -0
  108. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dae/actions/start/post-response.txt +1 -0
  109. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/get-response.json +17 -0
  110. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/patch-response.json +18 -0
  111. package/test/resources/9999999/automation/v1/queries/get-response.json +18 -1
  112. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +10 -0
  113. package/test/resources/9999999/automation/v1/scripts/get-response.json +12 -2
  114. package/test/resources/9999999/automation/v1/scripts/post-response.json +10 -0
  115. package/test/resources/9999999/dataExtension/retrieve-Name=testExisting_dataExtension-response.xml +52 -0
  116. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +98 -0
  117. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionORDataExtension.CustomerKey=testExisting_dataExtension-response.xml +99 -0
  118. package/test/resources/9999999/dataExtract/build-expected.json +35 -0
  119. package/test/resources/9999999/dataExtract/get-expected.json +39 -0
  120. package/test/resources/9999999/dataExtract/patch-expected.json +37 -0
  121. package/test/resources/9999999/dataExtract/post-expected.json +37 -0
  122. package/test/resources/9999999/dataExtract/template-expected.json +35 -0
  123. package/test/resources/9999999/dataFolder/retrieve-ContentType=contextual_suppression_listORContentType=publicationORContentType=suppression_listORContentType=mysubsORContentType=list-response.xml +136 -0
  124. package/test/resources/9999999/dataFolder/retrieve-ContentType=ssjsactivity-response.xml +48 -0
  125. package/test/resources/9999999/dataFolder/retrieve-ContentType=triggered_send_journeybuilderORContentType=triggered_sendORContentType=hidden-response.xml +276 -0
  126. package/test/resources/9999999/dataFolder/retrieve-response.xml +23 -0
  127. package/test/resources/9999999/email/retrieve-response.xml +203 -0
  128. package/test/resources/9999999/fileTransfer/build-expected.json +15 -0
  129. package/test/resources/9999999/fileTransfer/get-expected.json +17 -0
  130. package/test/resources/9999999/fileTransfer/patch-expected.json +17 -0
  131. package/test/resources/9999999/fileTransfer/post-expected.json +17 -0
  132. package/test/resources/9999999/fileTransfer/template-expected.json +15 -0
  133. package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +585 -0
  134. package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +19807 -0
  135. package/test/resources/9999999/importFile/build-expected.json +27 -0
  136. package/test/resources/9999999/importFile/get-expected.json +29 -0
  137. package/test/resources/9999999/importFile/patch-expected.json +29 -0
  138. package/test/resources/9999999/importFile/post-expected.json +29 -0
  139. package/test/resources/9999999/importFile/template-expected.json +27 -0
  140. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_pause-response.xml +32 -0
  141. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_schedule-response.xml +32 -0
  142. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_paused-response.xml +32 -0
  143. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_scheduled-response.xml +32 -0
  144. package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml +30 -0
  145. package/test/resources/9999999/program/retrieve-response.xml +21 -3
  146. package/test/resources/9999999/query/patch_fixKeys-expected.json +11 -0
  147. package/test/resources/9999999/query/patch_fixKeys-expected.sql +6 -0
  148. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixKeysANDStatus=Active-response.xml +30 -0
  149. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixedKeysANDStatus=Active-response.xml +30 -0
  150. package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testNew_queryANDStatus=Active-response.xml +30 -0
  151. package/test/resources/9999999/script/build-expected.json +6 -0
  152. package/test/resources/9999999/script/build-expected.ssjs +1 -0
  153. package/test/resources/9999999/script/get-expected.json +8 -0
  154. package/test/resources/9999999/script/get-expected.ssjs +1 -0
  155. package/test/resources/9999999/script/get_noScriptTag-expected.html +1 -0
  156. package/test/resources/9999999/script/get_noScriptTag-expected.json +8 -0
  157. package/test/resources/9999999/script/patch-expected.json +8 -0
  158. package/test/resources/9999999/script/patch-expected.ssjs +1 -0
  159. package/test/resources/9999999/script/post-expected.json +8 -0
  160. package/test/resources/9999999/script/post-expected.ssjs +1 -0
  161. package/test/resources/9999999/script/template-expected.json +6 -0
  162. package/test/resources/9999999/script/template-expected.ssjs +1 -0
  163. package/test/resources/9999999/triggeredSend/build-expected.json +29 -0
  164. package/test/resources/9999999/triggeredSend/get-expected.json +29 -0
  165. package/test/resources/9999999/triggeredSend/patch-expected.json +29 -0
  166. package/test/resources/9999999/triggeredSend/post-expected.json +29 -0
  167. package/test/resources/9999999/triggeredSend/template-expected.json +29 -0
  168. package/test/resources/9999999/triggeredSendDefinition/create-response.xml +75 -0
  169. package/test/resources/9999999/triggeredSendDefinition/delete-response.xml +36 -0
  170. package/test/resources/9999999/triggeredSendDefinition/{retrieve-response.xml → retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml} +4 -4
  171. package/test/resources/9999999/triggeredSendDefinition/update-response.xml +74 -0
  172. package/test/type.attributeGroup.test.js +55 -0
  173. package/test/type.attributeSet.test.js +55 -0
  174. package/test/type.automation.test.js +638 -11
  175. package/test/type.dataExtension.test.js +0 -1
  176. package/test/type.dataExtract.test.js +187 -0
  177. package/test/type.fileTransfer.test.js +185 -0
  178. package/test/type.importFile.test.js +186 -0
  179. package/test/type.mobileKeyword.test.js +0 -1
  180. package/test/type.query.test.js +464 -13
  181. package/test/type.script.test.js +367 -0
  182. package/test/type.triggeredSend.test.js +152 -0
  183. package/test/type.user.test.js +22 -10
  184. package/test/utils.js +4 -1
  185. package/lib/metadataTypes/SetDefinition.js +0 -37
  186. /package/test/resources/1111111/accountUser/{retrieve-response.xml → retrieve-ActiveFlag=trueANDCustomerKey=testExisting_userANDEmaillike@-response.xml} +0 -0
  187. /package/test/resources/1111111/accountUserAccount/{retrieve-response.xml → retrieve-AccountUser.AccountUserIDIN700301950,700301951,7471228-response.xml} +0 -0
  188. /package/test/resources/1111111/businessUnit/{retrieve-response.xml → retrieve-ID=1111111-response.xml} +0 -0
  189. /package/test/resources/1111111/list/{retrieve-response.xml → retrieve-CustomerKey=All SubscribersORListName=All Subscribers-response.xml} +0 -0
  190. /package/test/resources/1111111/role/{retrieve-response.xml → retrieve-IsPrivate=false-response.xml} +0 -0
  191. /package/test/resources/9999999/emailSendDefinition/{retrieve-response.xml → retrieve-IsPlatformObject=falseANDDescriptionnotEqualsSFSendDefinition-response.xml} +0 -0
  192. /package/test/resources/9999999/queryDefinition/{retrieve-response.xml → retrieve-CustomerKey=testExisting_queryANDStatus=Active-response.xml} +0 -0
package/lib/index.js CHANGED
@@ -51,15 +51,19 @@ class Mcdev {
51
51
  static setOptions(argv) {
52
52
  const knownOptions = [
53
53
  'api',
54
- 'commitHistory',
55
54
  'changeKeyField',
56
55
  'changeKeyValue',
56
+ 'commitHistory',
57
+ 'execute',
57
58
  'filter',
58
59
  'fromRetrieve',
59
60
  'json',
61
+ 'like',
62
+ 'noLogColors',
63
+ 'noLogFile',
60
64
  'refresh',
65
+ 'schedule',
61
66
  'skipInteraction',
62
- 'noLogFile',
63
67
  ];
64
68
  for (const option of knownOptions) {
65
69
  if (argv[option] !== undefined) {
@@ -153,6 +157,7 @@ class Mcdev {
153
157
  * @returns {Promise.<object>} -
154
158
  */
155
159
  static async retrieve(businessUnit, selectedTypesArr, keys, changelogOnly) {
160
+ console.time('Time'); // eslint-disable-line no-console
156
161
  Util.startLogger();
157
162
  Util.logger.info('mcdev:: Retrieve');
158
163
  const properties = await config.getProperties();
@@ -171,22 +176,32 @@ class Mcdev {
171
176
  }
172
177
  }
173
178
  }
174
-
179
+ const resultsObj = {};
175
180
  if (businessUnit === '*') {
176
- Util.logger.info('\n :: Retrieving all BUs for all credentials');
181
+ Util.logger.info(':: Retrieving all BUs for all credentials');
177
182
  let counter_credTotal = 0;
178
183
  for (const cred in properties.credentials) {
179
- Util.logger.info(`\n :: Retrieving all BUs for ${cred}`);
184
+ Util.logger.info(`:: Retrieving all BUs for ${cred}`);
180
185
  let counter_credBu = 0;
181
186
  for (const bu in properties.credentials[cred].businessUnits) {
182
- await this._retrieveBU(cred, bu, selectedTypesArr, keys);
187
+ resultsObj[`${cred}/${bu}`] = await this.#retrieveBU(
188
+ cred,
189
+ bu,
190
+ selectedTypesArr,
191
+ keys
192
+ );
183
193
  counter_credBu++;
184
194
  Util.startLogger(true);
185
195
  }
186
196
  counter_credTotal += counter_credBu;
187
- Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`);
197
+ Util.logger.info(`:: ${counter_credBu} BUs of ${cred}\n`);
188
198
  }
189
- Util.logger.info(`\n :: ${counter_credTotal} BUs in total\n`);
199
+ const credentialCount = Object.keys(properties.credentials).length;
200
+ Util.logger.info(
201
+ `:: Done for ${counter_credTotal} BUs of ${credentialCount} credential${
202
+ credentialCount === 1 ? '' : 's'
203
+ } in total\n`
204
+ );
190
205
  } else {
191
206
  let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
192
207
  // to allow all-BU via user selection we need to run this here already
@@ -210,17 +225,22 @@ class Mcdev {
210
225
  }
211
226
 
212
227
  if (bu === '*' && properties.credentials && properties.credentials[cred]) {
213
- Util.logger.info(`\n :: Retrieving all BUs for ${cred}`);
228
+ Util.logger.info(`:: Retrieving all BUs for ${cred}`);
214
229
  let counter_credBu = 0;
215
230
  for (const bu in properties.credentials[cred].businessUnits) {
216
- await this._retrieveBU(cred, bu, selectedTypesArr, keys);
231
+ resultsObj[`${cred}/${bu}`] = await this.#retrieveBU(
232
+ cred,
233
+ bu,
234
+ selectedTypesArr,
235
+ keys
236
+ );
217
237
  counter_credBu++;
218
238
  Util.startLogger(true);
219
239
  }
220
- Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`);
240
+ Util.logger.info(`:: Done for ${counter_credBu} BUs of ${cred}\n`);
221
241
  } else {
222
242
  // retrieve a single BU; return
223
- const retrieveChangelog = await this._retrieveBU(
243
+ const retrieveChangelog = await this.#retrieveBU(
224
244
  cred,
225
245
  bu,
226
246
  selectedTypesArr,
@@ -228,14 +248,33 @@ class Mcdev {
228
248
  changelogOnly
229
249
  );
230
250
  if (changelogOnly) {
251
+ console.timeEnd('Time'); // eslint-disable-line no-console
231
252
  return retrieveChangelog;
253
+ } else {
254
+ resultsObj[`${cred}/${bu}`] = retrieveChangelog;
232
255
  }
233
256
  Util.logger.info(`:: Done\n`);
234
257
  }
235
258
  }
259
+
260
+ // merge all results into one object
261
+ for (const credBu in resultsObj) {
262
+ for (const type in resultsObj[credBu]) {
263
+ const base = resultsObj[credBu][type][0];
264
+
265
+ for (let i = 1; i < resultsObj[credBu][type].length; i++) {
266
+ // merge all items into the first array
267
+ Object.assign(base, resultsObj[credBu][type][i]);
268
+ }
269
+ resultsObj[credBu][type] = resultsObj[credBu][type][0];
270
+ }
271
+ }
272
+ console.timeEnd('Time'); // eslint-disable-line no-console
273
+
274
+ return resultsObj;
236
275
  }
237
276
  /**
238
- * helper for {@link retrieve}
277
+ * helper for {@link Mcdev.retrieve}
239
278
  *
240
279
  * @private
241
280
  * @param {string} cred name of Credential
@@ -245,7 +284,7 @@ class Mcdev {
245
284
  * @param {boolean} [changelogOnly] skip saving, only create json in memory
246
285
  * @returns {Promise.<object>} ensure that BUs are worked on sequentially
247
286
  */
248
- static async _retrieveBU(cred, bu, selectedTypesArr, keys, changelogOnly) {
287
+ static async #retrieveBU(cred, bu, selectedTypesArr, keys, changelogOnly) {
249
288
  const properties = await config.getProperties();
250
289
  if (!(await config.checkProperties(properties))) {
251
290
  return null;
@@ -263,6 +302,7 @@ class Mcdev {
263
302
  // clean up old folders after types were renamed
264
303
  // TODO: Remove renamedTypes-logic 6 months after version 5 release
265
304
  const renamedTypes = {
305
+ attributeSet: 'setDefinition',
266
306
  emailSend: 'emailSendDefinition',
267
307
  event: 'eventDefinition',
268
308
  fileLocation: 'ftpLocation',
@@ -270,7 +310,7 @@ class Mcdev {
270
310
  triggeredSend: 'triggeredSendDefinition',
271
311
  user: 'accountUser',
272
312
  };
273
- Util.logger.info(`\n :: Retrieving ${cred}/${bu}\n`);
313
+ Util.logger.info(`:: Retrieving ${cred}/${bu}\n`);
274
314
  const retrieveTypesArr = [];
275
315
  if (selectedTypesArr) {
276
316
  for (const selectedType of Array.isArray(selectedTypesArr)
@@ -337,9 +377,7 @@ class Mcdev {
337
377
  null,
338
378
  changelogOnly
339
379
  );
340
- if (changelogOnly) {
341
- return retrieveChangelog;
342
- }
380
+ return retrieveChangelog;
343
381
  } catch (ex) {
344
382
  Util.logger.errorStack(ex, 'mcdev.retrieve failed');
345
383
  }
@@ -352,12 +390,14 @@ class Mcdev {
352
390
  * @param {string} businessUnit references credentials from properties.json
353
391
  * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit deployment to given metadata type
354
392
  * @param {string[]} [keyArr] limit deployment to given metadata keys
355
- * @param {boolean} [fromRetrieve] optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder
356
393
  * @returns {Promise.<Object.<string,TYPE.MultiMetadataTypeMap>>} deployed metadata per BU (first key: bu name, second key: metadata type)
357
394
  */
358
- static async deploy(businessUnit, selectedTypesArr, keyArr, fromRetrieve = false) {
395
+ static async deploy(businessUnit, selectedTypesArr, keyArr) {
396
+ console.time('Time'); // eslint-disable-line no-console
359
397
  Util.startLogger();
360
- return Deployer.deploy(businessUnit, selectedTypesArr, keyArr, fromRetrieve);
398
+ const deployResult = await Deployer.deploy(businessUnit, selectedTypesArr, keyArr);
399
+ console.timeEnd('Time'); // eslint-disable-line no-console
400
+ return deployResult;
361
401
  }
362
402
 
363
403
  /**
@@ -701,56 +741,159 @@ class Mcdev {
701
741
  }
702
742
  }
703
743
  /**
704
- * Start an item (query)
744
+ * Schedule an item (shortcut for execute --schedule)
745
+ *
746
+ * @param {string} businessUnit name of BU
747
+ * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types
748
+ * @param {string[]} [keys] customerkey of the metadata
749
+ * @returns {Promise.<Object.<string, string[]>>} key: business unit name, value: list of scheduled item keys
750
+ */
751
+ static async schedule(businessUnit, selectedType, keys) {
752
+ this.setOptions({ schedule: true });
753
+ return this.#runMethod('execute', businessUnit, selectedType, keys);
754
+ }
755
+ /**
756
+ * Start/execute an item
757
+ *
758
+ * @param {string} businessUnit name of BU
759
+ * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types
760
+ * @param {string[]} [keys] customerkey of the metadata
761
+ * @returns {Promise.<Object.<string, string[]>>} key: business unit name, value: list of executed item keys
762
+ */
763
+ static async execute(businessUnit, selectedType, keys) {
764
+ return this.#runMethod('execute', businessUnit, selectedType, keys);
765
+ }
766
+ /**
767
+ * pause an item
768
+ *
769
+ * @param {string} businessUnit name of BU
770
+ * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types
771
+ * @param {string[]} [keys] customerkey of the metadata
772
+ * @returns {Promise.<Object.<string, string[]>>} key: business unit name, value: list of paused item keys
773
+ */
774
+ static async pause(businessUnit, selectedType, keys) {
775
+ return this.#runMethod('pause', businessUnit, selectedType, keys);
776
+ }
777
+ /**
778
+ * Updates the key to match the name field
779
+ *
780
+ * @param {string} businessUnit name of BU
781
+ * @param {TYPE.SupportedMetadataTypes} selectedType limit to given metadata types
782
+ * @param {string[]} [keys] customerkey of the metadata
783
+ * @returns {Promise.<Object.<string, string[]>>} key: business unit name, value: list of paused item keys
784
+ */
785
+ static async fixKeys(businessUnit, selectedType, keys) {
786
+ return this.#runMethod('fixKeys', businessUnit, selectedType, keys);
787
+ }
788
+ /**
789
+ * run a method across BUs
705
790
  *
791
+ * @param {'execute'|'pause'|'fixKeys'} methodName what to run
706
792
  * @param {string} businessUnit name of BU
707
- * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit to given metadata types
708
- * @param {string[]} keys customerkey of the metadata
709
- * @returns {Promise.<boolean>} true if all started successfully, false if not
793
+ * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types
794
+ * @param {string[]} [keys] customerkey of the metadata
795
+ * @returns {Promise.<Object.<string, string[]>>} key: business unit name, value: list of affected item keys
710
796
  */
711
- static async execute(businessUnit, selectedTypesArr, keys) {
797
+ static async #runMethod(methodName, businessUnit, selectedType, keys) {
712
798
  Util.startLogger();
713
- Util.logger.info('mcdev:: Execute');
799
+ let lang_past;
800
+ let lang_present;
801
+ let requireKeyOrLike;
802
+ let checkMetadataSupport;
803
+ const resultObj = {};
804
+
805
+ switch (methodName) {
806
+ case 'execute': {
807
+ lang_past = 'executed';
808
+ lang_present = 'executing';
809
+ requireKeyOrLike = true;
810
+ checkMetadataSupport = true;
811
+ break;
812
+ }
813
+ case 'pause': {
814
+ lang_past = 'paused';
815
+ lang_present = 'pausing';
816
+ requireKeyOrLike = true;
817
+ checkMetadataSupport = true;
818
+ break;
819
+ }
820
+ case 'fixKeys': {
821
+ lang_past = 'fixed keys';
822
+ lang_present = 'fixing keys';
823
+ requireKeyOrLike = false;
824
+ checkMetadataSupport = false;
825
+ break;
826
+ }
827
+ }
828
+
829
+ Util.logger.info(`mcdev:: ${methodName} ${selectedType}`);
714
830
  const properties = await config.getProperties();
715
831
  let counter_credBu = 0;
716
- let counter_failed = 0;
832
+ let counter_credKeys = 0;
717
833
  if (!(await config.checkProperties(properties))) {
718
834
  // return null here to avoid seeing 2 error messages for the same issue
719
- return null;
835
+ return resultObj;
720
836
  }
721
- if (Array.isArray(selectedTypesArr)) {
722
- // types and keys can be provided but for each type all provided keys are applied as filter
723
- for (const selectedType of Array.isArray(selectedTypesArr)
724
- ? selectedTypesArr
725
- : Object.keys(selectedTypesArr)) {
726
- if (!Util._isValidType(selectedType)) {
727
- return;
728
- }
729
- }
837
+ if (!Util._isValidType(selectedType)) {
838
+ return resultObj;
730
839
  }
840
+ if (
841
+ checkMetadataSupport &&
842
+ !Object.prototype.hasOwnProperty.call(MetadataTypeInfo[selectedType], methodName)
843
+ ) {
844
+ Util.logger.error(
845
+ ` ☇ skipping ${selectedType}: ${methodName} is not supported yet for ${selectedType}`
846
+ );
847
+ return resultObj;
848
+ }
849
+
850
+ if (
851
+ requireKeyOrLike &&
852
+ (!Array.isArray(keys) || !keys.length) &&
853
+ (!Util.OPTIONS.like || !Object.keys(Util.OPTIONS.like).length)
854
+ ) {
855
+ Util.logger.error('At least one key or a --like filter is required.');
856
+ return resultObj;
857
+ } else if (
858
+ Array.isArray(keys) &&
859
+ keys.length &&
860
+ Util.OPTIONS.like &&
861
+ Object.keys(Util.OPTIONS.like).length
862
+ ) {
863
+ Util.logger.error('You can either specify keys OR a --like filter.');
864
+ return resultObj;
865
+ }
866
+
731
867
  if (businessUnit === '*') {
868
+ Util.OPTIONS._multiBuExecution = true;
732
869
  Util.logger.info(
733
- '\n :: Executing the entity on all BUs for all credentials'
870
+ `:: ${lang_present} the ${selectedType} on all BUs for all credentials`
734
871
  );
735
872
  let counter_credTotal = 0;
736
873
  for (const cred in properties.credentials) {
737
- Util.logger.info(`\n :: Executing the entity on all BUs for ${cred}`);
738
-
874
+ Util.logger.info(`:: ${lang_present} ${selectedType} on all BUs for ${cred}`);
875
+ // reset counter per cred
876
+ counter_credKeys = 0;
877
+ counter_credBu = 0;
739
878
  for (const bu in properties.credentials[cred].businessUnits) {
740
- if (await this._executeBU(cred, bu, selectedTypesArr, keys)) {
741
- counter_credBu++;
742
- } else {
743
- counter_failed++;
744
- }
879
+ resultObj[cred + '/' + bu] = await this.#runOnBU(
880
+ methodName,
881
+ cred,
882
+ bu,
883
+ selectedType,
884
+ keys
885
+ );
886
+ counter_credBu++;
887
+ counter_credKeys += resultObj[cred + '/' + bu].length;
745
888
  Util.startLogger(true);
746
889
  }
747
890
  counter_credTotal += counter_credBu;
748
891
  Util.logger.info(
749
- `\n :: Executed the entity on ${counter_credBu} BUs for ${cred}\n`
892
+ `:: ${lang_past} for ${counter_credKeys} ${selectedType}s on ${counter_credBu} BUs for ${cred}`
750
893
  );
751
894
  }
752
895
  Util.logger.info(
753
- `\n :: Executed the entity on ${counter_credTotal} BUs in total\n`
896
+ `:: ${lang_past} ${selectedType} on ${counter_credTotal} BUs in total\n`
754
897
  );
755
898
  } else {
756
899
  let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
@@ -767,91 +910,248 @@ class Mcdev {
767
910
  true
768
911
  );
769
912
  if (buObject === null) {
770
- return;
913
+ return resultObj;
771
914
  } else {
772
915
  cred = buObject.credential;
773
916
  bu = buObject.businessUnit;
774
917
  }
775
918
  }
776
919
  if (bu === '*' && properties.credentials && properties.credentials[cred]) {
777
- Util.logger.info(`\n :: Executing the entity on all BUs for ${cred}`);
778
- let counter_credBu = 0;
920
+ Util.OPTIONS._multiBuExecution = true;
921
+ Util.logger.info(`:: ${lang_present} ${selectedType} on all BUs for ${cred}`);
779
922
  for (const bu in properties.credentials[cred].businessUnits) {
780
- if (await this._executeBU(cred, bu, selectedTypesArr, keys)) {
781
- counter_credBu++;
782
- } else {
783
- counter_failed++;
784
- }
923
+ resultObj[cred + '/' + bu] = await this.#runOnBU(
924
+ methodName,
925
+ cred,
926
+ bu,
927
+ selectedType,
928
+ keys
929
+ );
930
+ counter_credBu++;
931
+ counter_credKeys += resultObj[cred + '/' + bu].length;
785
932
  Util.startLogger(true);
786
933
  }
787
934
  Util.logger.info(
788
- `\n :: Executed the entity on ${counter_credBu} BUs for ${cred}\n`
935
+ `:: ${lang_past} for ${counter_credKeys} ${selectedType}s on ${counter_credBu} BUs for ${cred}`
789
936
  );
790
937
  } else {
791
- // execute the entity on one BU only
792
- if (await this._executeBU(cred, bu, selectedTypesArr, keys)) {
793
- counter_credBu++;
794
- } else {
795
- counter_failed++;
796
- }
797
- Util.logger.info(`\n :: Done\n`);
938
+ // execute runMethod for the entity on one BU only
939
+ resultObj[cred + '/' + bu] = await this.#runOnBU(
940
+ methodName,
941
+ cred,
942
+ bu,
943
+ selectedType,
944
+ keys
945
+ );
946
+ Util.logger.info(`:: Done`);
798
947
  }
799
948
  }
800
- if (counter_credBu !== 0) {
801
- Util.logger.info(`\n :: Executed query on ${counter_credBu} BUs\n`);
802
- }
803
- return counter_failed === 0 ? true : false;
949
+ return resultObj;
804
950
  }
805
951
  /**
806
- * helper for {@link execute}
952
+ * helper for {@link Mcdev.#runMethod}
807
953
  *
954
+ * @param {'execute'|'pause'|'fixKeys'} methodName what to run
808
955
  * @param {string} cred name of Credential
809
956
  * @param {string} bu name of BU
810
- * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit execution to given metadata type
957
+ * @param {TYPE.SupportedMetadataTypes} [type] limit execution to given metadata type
811
958
  * @param {string[]} keyArr customerkey of the metadata
812
- * @returns {Promise.<boolean>} true if all items were executed, false otherwise
959
+ * @returns {Promise.<string[]>} list of keys that were affected
813
960
  */
814
- static async _executeBU(cred, bu, selectedTypesArr, keyArr) {
961
+ static async #runOnBU(methodName, cred, bu, type, keyArr) {
815
962
  const properties = await config.getProperties();
816
- let counter_failed = 0;
963
+ const resultArr = [];
817
964
  const buObject = await Cli.getCredentialObject(
818
965
  properties,
819
966
  cred === null ? null : cred + '/' + bu,
820
967
  null,
821
968
  true
822
969
  );
823
- if (!keyArr || (Array.isArray(keyArr) && !keyArr.length)) {
824
- throw new Error('No keys were provided');
970
+ try {
971
+ if (!type) {
972
+ throw new Error('No type was provided');
973
+ }
974
+ if (buObject !== null) {
975
+ cache.initCache(buObject);
976
+ cred = buObject.credential;
977
+ bu = buObject.businessUnit;
978
+ }
979
+ Util.logger.info(`:: ${methodName} ${type} on ${cred}/${bu}`);
980
+ MetadataTypeInfo[type].client = auth.getSDK(buObject);
981
+
982
+ MetadataTypeInfo[type].properties = properties;
983
+ MetadataTypeInfo[type].buObject = buObject;
984
+ switch (methodName) {
985
+ case 'fixKeys': {
986
+ {
987
+ resultArr.push(...(await this.#fixKeys(cred, bu, type, keyArr)));
988
+
989
+ break;
990
+ }
991
+ }
992
+ default: {
993
+ if (Util.OPTIONS.like && Object.keys(Util.OPTIONS.like).length) {
994
+ keyArr = await this.#retrieveKeysWithLike(type, buObject);
995
+ }
996
+ if (!keyArr || (Array.isArray(keyArr) && !keyArr.length)) {
997
+ throw new Error('No keys were provided');
998
+ } // result will be undefined (false) if methodName is not supported for the type
999
+ resultArr.push(...(await MetadataTypeInfo[type][methodName](keyArr)));
1000
+ }
1001
+ }
1002
+ } catch (ex) {
1003
+ Util.logger.errorStack(ex, 'mcdev.' + methodName + ' failed');
825
1004
  }
826
- if (!selectedTypesArr || (Array.isArray(selectedTypesArr) && !selectedTypesArr.length)) {
827
- throw new Error('No type was provided');
1005
+
1006
+ return resultArr;
1007
+ }
1008
+
1009
+ /**
1010
+ * helper for {@link Mcdev.#runOnBU}
1011
+ *
1012
+ * @param {TYPE.SupportedMetadataTypes} selectedType limit execution to given metadata type
1013
+ * @param {TYPE.BuObject} buObject properties for auth
1014
+ * @returns {string[]} keyArr
1015
+ */
1016
+ static async #retrieveKeysWithLike(selectedType, buObject) {
1017
+ const properties = await config.getProperties();
1018
+
1019
+ // cache depenencies
1020
+ const deployOrder = Util.getMetadataHierachy([selectedType]);
1021
+ for (const type in deployOrder) {
1022
+ const subTypeArr = deployOrder[type];
1023
+ MetadataTypeInfo[type].client = auth.getSDK(buObject);
1024
+ MetadataTypeInfo[type].properties = properties;
1025
+ MetadataTypeInfo[type].buObject = buObject;
1026
+ Util.logger.info(`Caching dependent Metadata: ${type}`);
1027
+ Util.logSubtypes(subTypeArr);
1028
+ const result = await MetadataTypeInfo[type].retrieveForCache(null, subTypeArr);
1029
+ if (result) {
1030
+ if (Array.isArray(result)) {
1031
+ for (const result_i of result) {
1032
+ if (result_i?.metadata && Object.keys(result_i.metadata).length) {
1033
+ cache.mergeMetadata(type, result_i.metadata);
1034
+ }
1035
+ }
1036
+ } else {
1037
+ cache.setMetadata(type, result.metadata);
1038
+ }
1039
+ }
828
1040
  }
829
- if (buObject !== null) {
830
- cache.initCache(buObject);
831
- cred = buObject.credential;
832
- bu = buObject.businessUnit;
1041
+
1042
+ // find all keys in chosen type that match the like-filter
1043
+ const keyArr = [];
1044
+ const metadataMap = cache.getCache()[selectedType];
1045
+ if (!metadataMap) {
1046
+ throw new Error(`Selected type ${selectedType} could not be cached`);
833
1047
  }
834
1048
  Util.logger.info(
835
- `\n :: Executing ${selectedTypesArr.join(', ')} on ${cred}/${bu}\n`
1049
+ Util.getGrayMsg(`Found ${Object.keys(metadataMap).length} ${selectedType}s`)
1050
+ );
1051
+ for (const originalKey in metadataMap) {
1052
+ // hide postRetrieveOutput
1053
+ Util.setLoggingLevel({ silent: true });
1054
+ metadataMap[originalKey] = MetadataTypeInfo[selectedType].postRetrieveTasks(
1055
+ metadataMap[originalKey]
1056
+ );
1057
+ // reactivate logging
1058
+ Util.setLoggingLevel({});
1059
+ if (Util.fieldsLike(metadataMap[originalKey])) {
1060
+ keyArr.push(originalKey);
1061
+ }
1062
+ }
1063
+ Util.logger.info(
1064
+ Util.getGrayMsg(
1065
+ `Identified ${keyArr.length} ${selectedType}${
1066
+ keyArr.length === 1 ? '' : 's'
1067
+ } that match${keyArr.length === 1 ? 'es' : ''} the like-filter`
1068
+ )
1069
+ );
1070
+
1071
+ return keyArr;
1072
+ }
1073
+ /**
1074
+ * Updates the key to match the name field
1075
+ *
1076
+ * @param {string} cred name of Credential
1077
+ * @param {string} bu name of BU
1078
+ * @param {TYPE.SupportedMetadataTypes} type limit execution to given metadata type
1079
+ * @param {string[]} [keyArr] customerkey of the metadata
1080
+ * @returns {Promise.<string[]>} list of keys that were affected
1081
+ */
1082
+ static async #fixKeys(cred, bu, type, keyArr) {
1083
+ const properties = await config.getProperties();
1084
+ let actuallyFixedKeys = [];
1085
+ const resultArr = [];
1086
+
1087
+ if (
1088
+ MetadataTypeDefinitions[type].keyIsFixed === true ||
1089
+ MetadataTypeDefinitions[type].keyField === MetadataTypeDefinitions[type].idField
1090
+ ) {
1091
+ Util.logger.error(`Key cannot be updated for this type`);
1092
+ return resultArr;
1093
+ }
1094
+
1095
+ const buObject = await Cli.getCredentialObject(
1096
+ properties,
1097
+ cred === null ? null : cred + '/' + bu,
1098
+ null,
1099
+ true
836
1100
  );
837
1101
  try {
838
- // more than one type was provided, iterate types and execute items
839
- for (const type of selectedTypesArr) {
840
- try {
841
- MetadataTypeInfo[type].client = auth.getSDK(buObject);
842
- } catch (ex) {
843
- Util.logger.error(ex.message);
844
- return;
845
- }
846
- // result will be undefined (false) if execute is not supported for the type
847
- if (!(await MetadataTypeInfo[type].execute(keyArr))) {
848
- counter_failed++;
1102
+ Util.logger.info(`Retrieving latest versions of ${type} from server`);
1103
+ const retriever = new Retriever(properties, buObject);
1104
+ const retrieved = await retriever.retrieve([type], keyArr, null, false);
1105
+
1106
+ const metadataMap = Object.values(retrieved)[0][0];
1107
+ const keysForDeploy = MetadataTypeInfo[type].getKeysForFixing(metadataMap);
1108
+ if (keysForDeploy.length < 1) {
1109
+ Util.logger.warn(
1110
+ `No items found with a key-name mismatch that match your criteria.\n`
1111
+ );
1112
+ return resultArr;
1113
+ }
1114
+ this.setOptions({
1115
+ changeKeyField: MetadataTypeDefinitions[type].nameField,
1116
+ fromRetrieve: true,
1117
+ });
1118
+ const deployed = await Deployer.deploy(cred + '/' + bu, [type], keysForDeploy);
1119
+ actuallyFixedKeys = Object.keys(Object.values(Object.values(deployed)[0])[0]);
1120
+ resultArr.push(...actuallyFixedKeys);
1121
+ const dependentTypes = await Util.getDependentMetadata(type);
1122
+ if (actuallyFixedKeys && actuallyFixedKeys.length) {
1123
+ Util.logger.info(
1124
+ `Successfully updated ${actuallyFixedKeys.length} key${
1125
+ actuallyFixedKeys.length === 1 ? '' : 's'
1126
+ } of type ${type}`
1127
+ );
1128
+ if (dependentTypes.length) {
1129
+ Util.logger.warn(
1130
+ `Please re-retrieve the following types as your local copies might now be outdated: ${Util.getGrayMsg(
1131
+ dependentTypes.join(', ')
1132
+ )}`
1133
+ );
1134
+ const reRetrieve = await Cli.postFixKeysReretrieve(type, dependentTypes);
1135
+ if (reRetrieve) {
1136
+ Util.logger.info(
1137
+ `Retrieving latest versions of ${dependentTypes.join(', ')} from server`
1138
+ );
1139
+ const retriever = new Retriever(properties, buObject);
1140
+ await retriever.retrieve(dependentTypes, null, null, false);
1141
+ }
1142
+ } else {
1143
+ Util.logger.info(
1144
+ `No dependent types found that need to be re-retrieved after fixing keys of type ${type}.`
1145
+ );
849
1146
  }
1147
+ } else {
1148
+ Util.logger.warn(`No keys of type ${type} updated.`);
850
1149
  }
851
1150
  } catch (ex) {
852
- Util.logger.errorStack(ex, 'mcdev.execute failed');
1151
+ Util.logger.errorStack(ex, 'mcdev.fixKeys failed');
853
1152
  }
854
- return counter_failed === 0 ? true : false;
1153
+ Util.logger.info(`:: Done\n`);
1154
+ return resultArr;
855
1155
  }
856
1156
  }
857
1157