mcdev 7.10.1 → 8.0.1

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 (166) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/code-test.yml +8 -100
  3. package/.github/workflows/coverage-base-update.yml +2 -2
  4. package/.github/workflows/coverage.yml +1 -1
  5. package/@types/lib/Deployer.d.ts.map +1 -1
  6. package/@types/lib/Retriever.d.ts.map +1 -1
  7. package/@types/lib/index.d.ts +17 -4
  8. package/@types/lib/index.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Asset.d.ts +46 -8
  10. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Automation.d.ts +14 -4
  12. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/DataExtension.d.ts +1 -0
  14. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/DataExtract.d.ts +1 -0
  17. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/Event.d.ts +8 -0
  19. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  20. package/@types/lib/metadataTypes/FileTransfer.d.ts +1 -0
  21. package/@types/lib/metadataTypes/FileTransfer.d.ts.map +1 -1
  22. package/@types/lib/metadataTypes/Folder.d.ts +49 -12
  23. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  24. package/@types/lib/metadataTypes/ImportFile.d.ts +15 -0
  25. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  26. package/@types/lib/metadataTypes/Journey.d.ts +166 -6
  27. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  28. package/@types/lib/metadataTypes/MetadataType.d.ts +6 -3
  29. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  30. package/@types/lib/metadataTypes/MobileKeyword.d.ts +1 -0
  31. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  32. package/@types/lib/metadataTypes/Query.d.ts +1 -0
  33. package/@types/lib/metadataTypes/Query.d.ts.map +1 -1
  34. package/@types/lib/metadataTypes/Script.d.ts +1 -0
  35. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  36. package/@types/lib/metadataTypes/definitions/Asset.definition.d.ts +45 -8
  37. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +6 -0
  38. package/@types/lib/metadataTypes/definitions/Event.definition.d.ts +6 -0
  39. package/@types/lib/metadataTypes/definitions/Folder.definition.d.ts +49 -12
  40. package/@types/lib/metadataTypes/definitions/ImportFile.definition.d.ts +3 -0
  41. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +159 -6
  42. package/@types/lib/util/cache.d.ts +2 -1
  43. package/@types/lib/util/cache.d.ts.map +1 -1
  44. package/@types/lib/util/devops.d.ts.map +1 -1
  45. package/@types/lib/util/util.d.ts +8 -1
  46. package/@types/lib/util/util.d.ts.map +1 -1
  47. package/@types/types/mcdev.d.d.ts +16 -0
  48. package/@types/types/mcdev.d.d.ts.map +1 -1
  49. package/boilerplate/files/.vscode/settings.json +5 -0
  50. package/eslint.config.js +2 -2
  51. package/lib/Deployer.js +3 -0
  52. package/lib/Retriever.js +8 -2
  53. package/lib/cli.js +29 -0
  54. package/lib/index.js +27 -31
  55. package/lib/metadataTypes/Asset.js +155 -17
  56. package/lib/metadataTypes/Automation.js +137 -40
  57. package/lib/metadataTypes/DataExtension.js +2 -10
  58. package/lib/metadataTypes/DataExtensionField.js +9 -15
  59. package/lib/metadataTypes/DataExtract.js +2 -0
  60. package/lib/metadataTypes/Event.js +63 -32
  61. package/lib/metadataTypes/FileTransfer.js +2 -0
  62. package/lib/metadataTypes/Folder.js +99 -27
  63. package/lib/metadataTypes/ImportFile.js +92 -29
  64. package/lib/metadataTypes/Journey.js +184 -24
  65. package/lib/metadataTypes/MetadataType.js +53 -25
  66. package/lib/metadataTypes/MobileKeyword.js +2 -0
  67. package/lib/metadataTypes/Query.js +2 -0
  68. package/lib/metadataTypes/Script.js +2 -0
  69. package/lib/metadataTypes/definitions/Asset.definition.js +196 -142
  70. package/lib/metadataTypes/definitions/Automation.definition.js +6 -0
  71. package/lib/metadataTypes/definitions/DataExtension.definition.js +7 -6
  72. package/lib/metadataTypes/definitions/Event.definition.js +6 -0
  73. package/lib/metadataTypes/definitions/Folder.definition.js +69 -22
  74. package/lib/metadataTypes/definitions/ImportFile.definition.js +3 -0
  75. package/lib/metadataTypes/definitions/Journey.definition.js +165 -11
  76. package/lib/util/cache.js +41 -8
  77. package/lib/util/devops.js +20 -5
  78. package/lib/util/file.js +10 -1
  79. package/lib/util/util.js +26 -12
  80. package/package.json +18 -19
  81. package/test/general.test.js +8 -8
  82. package/test/mockRoot/.mcdev-validations.js +2 -3
  83. package/test/mockRoot/.mcdevrc.json +1 -1
  84. package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +2 -1
  85. package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +2 -1
  86. package/test/resourceFactory.js +31 -5
  87. package/test/resources/1111111/dataExtension/update-expected.json +1 -1
  88. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.js +1 -0
  89. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.json +45 -0
  90. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.json +45 -0
  91. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.jsonc +1 -0
  92. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.json +44 -0
  93. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.xml +1 -0
  94. package/test/resources/9999999/asset/test_interactivecontent-retrieve-expected.json +41 -0
  95. package/test/resources/9999999/asset/test_landingpage-retrieve-expected.json +40 -0
  96. package/test/resources/9999999/asset/test_microsite-retrieve-expected.json +41 -0
  97. package/test/resources/9999999/asset/v1/content/assets/9451/get-response.json +61 -0
  98. package/test/resources/9999999/asset/v1/content/assets/9456/get-response.json +56 -0
  99. package/test/resources/9999999/asset/v1/content/assets/9458/get-response.json +56 -0
  100. package/test/resources/9999999/asset/v1/content/assets/9460/get-response.json +59 -0
  101. package/test/resources/9999999/asset/v1/content/assets/9463/get-response.json +61 -0
  102. package/test/resources/9999999/asset/v1/content/assets/9465/get-response.json +54 -0
  103. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN1,3,4,14,15,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,210,211,212,213,214,215,216,217,218,219,220,221,222.json +94 -1
  104. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232,240,241,242,243,244,245.json +168 -0
  105. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN240,241,242,243,244,245.json +144 -0
  106. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN246,247,248,249.json +132 -0
  107. package/test/resources/9999999/dataExtension/update-callout-afterCreatedViaEvent-expected.xml +1 -1
  108. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml} +44 -1
  109. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml} +44 -0
  110. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +44 -0
  111. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,journey-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,journey-response.xml} +44 -0
  112. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages,ssjsactivity-response.xml +136 -0
  113. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,ssjsactivity-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml} +29 -7
  114. package/test/resources/9999999/dataFolder/{+retrieve-response.xml → retrieve-response.xml} +23 -0
  115. package/test/resources/9999999/dataFolder/update-response.xml +31 -0
  116. package/test/resources/9999999/event/build-expected.json +0 -1
  117. package/test/resources/9999999/event/get-automation-expected.json +0 -1
  118. package/test/resources/9999999/event/get-expected.json +0 -2
  119. package/test/resources/9999999/event/get-published-expected.json +0 -2
  120. package/test/resources/9999999/event/post_withExistingDE-expected.json +0 -2
  121. package/test/resources/9999999/event/post_withSchema-callout-expected.json +3 -0
  122. package/test/resources/9999999/event/post_withSchema-expected.json +0 -2
  123. package/test/resources/9999999/event/put-callout-expected.json +3 -0
  124. package/test/resources/9999999/event/put-expected.json +0 -2
  125. package/test/resources/9999999/event/template-expected.json +0 -1
  126. package/test/resources/9999999/folder-deploy/Data Extensions/testExisting_folder.folder-meta.json +9 -0
  127. package/test/resources/9999999/importFile/get-dataImport-expected.json +1 -1
  128. package/test/resources/9999999/importFile/get-expected.json +4 -1
  129. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=1.json +86 -0
  130. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=2.json +101 -0
  131. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=3.json +86 -0
  132. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/delete-response-versionNumber=1.txt +1 -0
  133. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=1.json +461 -0
  134. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=3.json +461 -0
  135. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=1.json +38 -0
  136. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=2.json +53 -0
  137. package/test/resources/9999999/interaction/v1/interactions/get-response.json +2 -2
  138. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/get-response-versionNumber=1.json +461 -0
  139. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Quicksend/get-response-versionNumber=1.json +253 -0
  140. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/get-response-versionNumber=1.json +219 -0
  141. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/get-response-versionNumber=1.json +226 -0
  142. package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/post-response-versionNumber=3.json +4 -0
  143. package/test/resources/9999999/journey/build-expected.json +0 -1
  144. package/test/resources/9999999/journey/create-transactionaEmail-publish-expected.json +0 -1
  145. package/test/resources/9999999/journey/get-multistep-expected.json +0 -4
  146. package/test/resources/9999999/journey/get-published-expected.json +0 -1
  147. package/test/resources/9999999/journey/get-quicksend-expected.json +0 -1
  148. package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +0 -1
  149. package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +0 -1
  150. package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +0 -1
  151. package/test/resources/9999999/journey/get-transactionalEmail-expected.json +0 -1
  152. package/test/resources/9999999/journey/template-expected.json +0 -1
  153. package/test/resources/9999999/query/patch-expected.sql +1 -1
  154. package/test/resources/9999999/query/post-expected.sql +1 -1
  155. package/test/resources/9999999/script/patch-expected.ssjs +1 -0
  156. package/test/type.asset.test.js +123 -13
  157. package/test/type.automation.test.js +3 -1
  158. package/test/type.folder.test.js +12 -2
  159. package/test/type.journey.test.js +130 -1
  160. package/test/type.script.test.js +1 -0
  161. package/test/type.senderProfile.test.js +1 -0
  162. package/test/utils.js +2 -0
  163. package/types/mcdev.d.js +4 -0
  164. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232.json +0 -32
  165. /package/test/resources/9999999/dataFolder/{+retrieve-QAA-response.xml → retrieve-QAA-response.xml} +0 -0
  166. /package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/{post-response.json → post-response-versionNumber=1.json} +0 -0
@@ -6,6 +6,7 @@ import File from '../util/file.js';
6
6
  import Definitions from '../MetadataTypeDefinitions.js';
7
7
  import cache from '../util/cache.js';
8
8
  import pLimit from 'p-limit';
9
+ import Retriever from '../Retriever.js';
9
10
 
10
11
  /**
11
12
  * @typedef {import('../../types/mcdev.d.js').BuObject} BuObject
@@ -92,7 +93,7 @@ class Automation extends MetadataType {
92
93
  id: objectID,
93
94
  uri: '/automation/v1/automations/' + objectID,
94
95
  })),
95
- 50,
96
+ 10,
96
97
  !key
97
98
  )
98
99
  : null;
@@ -125,20 +126,32 @@ class Automation extends MetadataType {
125
126
  * helper for {@link this.retrieveRESTcollection}
126
127
  *
127
128
  * @param {SDKError} ex exception
128
- * @param {string} id id or key of item
129
+ * @param {string} key id or key of item
130
+ * @param {string} url url to call for retry
129
131
  * @returns {Promise.<any>} can return retry-result
130
132
  */
131
- static async handleRESTErrors(ex, id) {
133
+ static async handleRESTErrors(ex, key, url) {
132
134
  try {
133
- if (ex.message == 'socket hang up') {
135
+ if (ex.message == 'socket hang up' || ex.code == 'ERR_BAD_RESPONSE') {
134
136
  // one more retry; it's a rare case but retrying again should solve the issue gracefully
135
- return await this.client.rest.get('/automation/v1/automations/' + id);
137
+ Util.logger.info(
138
+ ` - Connection problem (Code: ${ex.code}). Retrying once
139
+ ${
140
+ ex.endpoint
141
+ ? Util.getGrayMsg(
142
+ ' - ' + ex.endpoint.split('rest.marketingcloudapis.com')[1]
143
+ )
144
+ : ''
145
+ }`
146
+ );
147
+ Util.logger.errorStack(ex);
148
+ return await this.client.rest.get(url);
136
149
  }
137
150
  } catch {
138
151
  // no extra action needed, handled below
139
152
  }
140
153
  // if we do get here, we should log the error and continue instead of failing to download all automations
141
- Util.logger.error(` ☇ skipping Automation ${id}: ${ex.message} ${ex.code}`);
154
+ Util.logger.error(` ☇ skipping ${this.definition.type} ${key}: ${ex.message} ${ex.code}`);
142
155
  return null;
143
156
  }
144
157
 
@@ -216,6 +229,10 @@ class Automation extends MetadataType {
216
229
  if (extended?.scheduleObject?.id && item.schedule) {
217
230
  // save schedule id in cached metadata for retrieval during scheduling
218
231
  item.schedule.id = extended.scheduleObject.id;
232
+ item.schedule.description = extended.scheduleObject.description;
233
+ item.schedule.icalRecur ||= extended.scheduleObject.iCalRecur;
234
+ item.schedule.startDate ||= extended.scheduleObject.startDate;
235
+ item.schedule.timezoneName ||= extended.scheduleObject.timeZone;
219
236
  }
220
237
 
221
238
  // add timezone to wait activities
@@ -401,12 +418,14 @@ class Automation extends MetadataType {
401
418
  /**
402
419
  * Retrieve a specific Automation Definition by Name
403
420
  *
421
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
404
422
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
405
423
  * @param {string} name name of the metadata file
406
424
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
407
425
  * @returns {Promise.<AutomationItemObj>} Promise of metadata
408
426
  */
409
427
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
428
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
410
429
  const results = await this.client.soap.retrieve('Program', ['ObjectID', 'Name'], {
411
430
  filter: {
412
431
  leftOperand: 'Name',
@@ -717,22 +736,56 @@ class Automation extends MetadataType {
717
736
  Util.logger.info(
718
737
  ` ☇ skipping ${Util.getTypeKeyName(this.definition, item)}: already ${mode === 'schedule' ? 'activated' : 'paused'}.`
719
738
  );
720
- } else {
721
- // schedule
722
- promiseResults.push(
723
- this.#schedulePauseItem(mode, key, item.legacyId, item.schedule.id)
724
- );
739
+ continue;
740
+ }
741
+ if (mode === 'schedule') {
742
+ try {
743
+ this._checkSchedule(item.schedule, true);
744
+ } catch (ex) {
745
+ Util.logger.error(
746
+ ` ☇ skipping ${this.definition.type} ${key} (${item.schedule.description}): ${ex.message}`
747
+ );
748
+ continue;
749
+ }
725
750
  }
751
+
752
+ // schedule
753
+ promiseResults.push(
754
+ this.#schedulePauseItem(
755
+ mode,
756
+ key,
757
+ item.legacyId,
758
+ item.schedule.id,
759
+ item.schedule.description
760
+ )
761
+ );
726
762
  }
727
763
  const results = await Promise.all(promiseResults);
728
764
  const updatedKeyArr = results
729
765
  .filter(Boolean)
730
- .filter((r) => r.response.id)
766
+ .filter((r) => r.response?.id)
731
767
  .map((r) => r.key);
732
768
  Util.logger.info(
733
769
  `${mode === 'schedule' ? 'Activated' : 'Paused'} ${updatedKeyArr.length} of ${keyArr.length} items`
734
770
  );
735
771
 
772
+ if (updatedKeyArr.length) {
773
+ Util.logger.info(
774
+ Util.getGrayMsg(
775
+ `Caching ${this.definition.type} post-${mode === 'schedule' ? 'activation' : 'pausing'} to update local files.`
776
+ )
777
+ );
778
+ // re-retrieve the items that were activated / paused
779
+ const retriever = new Retriever(this.properties, this.buObject);
780
+ try {
781
+ await retriever.retrieve([this.definition.type], updatedKeyArr);
782
+ } catch (ex) {
783
+ Util.logger.warn(
784
+ `Could not re-retrieve ${mode === 'schedule' ? 'activated' : 'paused'} ${this.definition.type}s: ${ex.message}`
785
+ );
786
+ }
787
+ }
788
+
736
789
  return updatedKeyArr;
737
790
  }
738
791
 
@@ -743,15 +796,39 @@ class Automation extends MetadataType {
743
796
  * @param {string} key automation key
744
797
  * @param {string} automationLegacyId automation id
745
798
  * @param {string} [scheduleLegacyId] schedule id
799
+ * @param {string} [description] schedule description
746
800
  * @returns {Promise.<{key:string, response:object}>} metadata key and API response
747
801
  */
748
- static async #schedulePauseItem(mode, key, automationLegacyId, scheduleLegacyId) {
802
+ static async #schedulePauseItem(mode, key, automationLegacyId, scheduleLegacyId, description) {
749
803
  if (!scheduleLegacyId) {
750
804
  const extended = await this.client.rest.get(
751
805
  `/legacy/v1/beta/bulk/automations/automation/definition/` + automationLegacyId
752
806
  );
753
- if (extended.scheduleObject?.id) {
754
- scheduleLegacyId = extended.scheduleObject.id;
807
+ /** @type {AutomationSchedule} */
808
+ const scheduleObjectLegacy = extended.scheduleObject;
809
+ if (scheduleObjectLegacy?.id) {
810
+ scheduleLegacyId = scheduleObjectLegacy.id;
811
+ if (mode === 'schedule') {
812
+ // convert legacy API schedule to new schedule
813
+ /** @type {AutomationSchedule} */
814
+ const scheduleObject = {
815
+ id: scheduleObjectLegacy.id,
816
+ typeId: null,
817
+ endDate: null,
818
+ startDate: scheduleObjectLegacy.startDate,
819
+ timezoneName: scheduleObjectLegacy.timeZone,
820
+ icalRecur: scheduleObjectLegacy.iCalRecur,
821
+ };
822
+ try {
823
+ this._checkSchedule(scheduleObject);
824
+ } catch (ex) {
825
+ Util.logger.error(
826
+ ` ☇ skipping ${this.definition.type} ${key}: ${ex.message}`
827
+ );
828
+ return null;
829
+ }
830
+ }
831
+ description = scheduleObjectLegacy.description;
755
832
  } else {
756
833
  Util.logger.error(
757
834
  ` ☇ skipping ${this.definition.type} ${key}: no valid schedule settings found.`
@@ -759,21 +836,28 @@ class Automation extends MetadataType {
759
836
  return null;
760
837
  }
761
838
  }
762
- const response = await this.client.rest.post(
763
- '/legacy/v1/beta/bulk/automations/automation/definition/?action=' +
764
- (mode === 'schedule' ? 'schedule' : 'pauseSchedule'),
765
- {
766
- id: automationLegacyId,
767
- scheduleObject: {
768
- id: scheduleLegacyId,
769
- },
839
+ let response;
840
+ try {
841
+ response = await this.client.rest.post(
842
+ '/legacy/v1/beta/bulk/automations/automation/definition/?action=' +
843
+ (mode === 'schedule' ? 'schedule' : 'pauseSchedule'),
844
+ {
845
+ id: automationLegacyId,
846
+ scheduleObject: {
847
+ id: scheduleLegacyId,
848
+ },
849
+ }
850
+ );
851
+ if (response?.id === automationLegacyId) {
852
+ const item = {};
853
+ item[this.definition.keyField] = key;
854
+ Util.logger.info(
855
+ ` - ${mode === 'schedule' ? '✅ activated' : '🛑 paused'} scheduled ${Util.getTypeKeyName(this.definition, item)}${mode === 'schedule' ? ' (' + description + ')' : ''}`
856
+ );
770
857
  }
771
- );
772
- if (response?.id === automationLegacyId) {
773
- const item = {};
774
- item[this.definition.keyField] = key;
775
- Util.logger.info(
776
- ` - ${mode === 'schedule' ? '✅ activated' : '🛑 paused'} scheduled ${Util.getTypeKeyName(this.definition, item)}`
858
+ } catch (ex) {
859
+ Util.logger.error(
860
+ ` error ${mode === 'schedule' ? 'activating' : 'pausing'} ${this.definition.type} ${key} (${description}): ${ex.message}`
777
861
  );
778
862
  }
779
863
 
@@ -1258,15 +1342,6 @@ class Automation extends MetadataType {
1258
1342
  'Path'
1259
1343
  );
1260
1344
  delete metadata[folderIdField];
1261
- if (metadata.r__folder_Path !== 'my automations') {
1262
- Util.logger.verbose(
1263
- `- automation '${
1264
- metadata[this.definition.nameField]
1265
- }' is located in subfolder ${
1266
- metadata.r__folder_Path
1267
- }. Please note that creating automation folders is not supported via API and hence you will have to create it manually in the GUI if you choose to deploy this automation.`
1268
- );
1269
- }
1270
1345
  } catch (ex) {
1271
1346
  Util.logger.warn(
1272
1347
  ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${
@@ -1281,9 +1356,10 @@ class Automation extends MetadataType {
1281
1356
  * based on combination of ical string and start/end dates.
1282
1357
  *
1283
1358
  * @param {AutomationSchedule} scheduleObject child of automation metadata used for scheduling
1359
+ * @param {boolean} [errorOnNotSchedulable] used if run for schedule command
1284
1360
  * @returns {void} throws and error in case of problems
1285
1361
  */
1286
- static _checkSchedule(scheduleObject) {
1362
+ static _checkSchedule(scheduleObject, errorOnNotSchedulable = false) {
1287
1363
  // build recurrence
1288
1364
  const recurHelper = {};
1289
1365
  // ical values are split by ; then have key values split by =
@@ -1300,7 +1376,6 @@ class Automation extends MetadataType {
1300
1376
  'The smallest interval you can configure is 5 minutes. Please adjust your schedule.'
1301
1377
  );
1302
1378
  }
1303
-
1304
1379
  if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) {
1305
1380
  scheduleObject.timezoneId =
1306
1381
  this.definition.timeZoneMapping[scheduleObject.timezoneName];
@@ -1309,6 +1384,28 @@ class Automation extends MetadataType {
1309
1384
  `Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
1310
1385
  );
1311
1386
  }
1387
+
1388
+ if (recurHelper.COUNT == 1) {
1389
+ const timezoneString = this.definition.timeZoneDifference[scheduleObject.timezoneId];
1390
+ // add tz to input date to ensure Date() creates a date object with the right tz
1391
+ const inputStartDateString = scheduleObject.startDate + timezoneString;
1392
+ if (new Date(inputStartDateString) > new Date()) {
1393
+ const msg =
1394
+ 'The automation will run only once. Please make sure this is intended and that the start date is in the future.';
1395
+ if (errorOnNotSchedulable) {
1396
+ throw new Error(msg);
1397
+ } else {
1398
+ Util.logger.warn(msg);
1399
+ }
1400
+ } else {
1401
+ const msg = `The automation will run only once but its start date is in the past. While this can be saved, it cannot be scheduled.`;
1402
+ if (errorOnNotSchedulable) {
1403
+ throw new Error(msg);
1404
+ } else {
1405
+ Util.logger.warn(msg);
1406
+ }
1407
+ }
1408
+ }
1312
1409
  }
1313
1410
 
1314
1411
  /**
@@ -84,11 +84,6 @@ class DataExtension extends MetadataType {
84
84
  continue;
85
85
  }
86
86
  }
87
- if (metadataToUpdate.length) {
88
- Util.logger.info(
89
- ' - Please note that Data Retention Policies can only be set during creation, not during update.'
90
- );
91
- }
92
87
  const createLimit = pLimit(10);
93
88
  const createResults = (
94
89
  await Promise.allSettled(
@@ -316,11 +311,6 @@ class DataExtension extends MetadataType {
316
311
  : null;
317
312
  if (cachedVersion) {
318
313
  // UPDATE
319
- // restore retention values that are typically not returned by the update call
320
- item.RowBasedRetention = cachedVersion.RowBasedRetention;
321
- item.ResetRetentionPeriodOnImport = cachedVersion.ResetRetentionPeriodOnImport;
322
- item.DeleteAtEndOfRetentionPeriod = cachedVersion.DeleteAtEndOfRetentionPeriod;
323
- item.RetainUntil = cachedVersion.RetainUntil;
324
314
 
325
315
  const existingFields = DataExtension.oldFields[item[this.definition.keyField]];
326
316
 
@@ -1503,12 +1493,14 @@ class DataExtension extends MetadataType {
1503
1493
  /**
1504
1494
  * Retrieves dataExtension metadata in template format.
1505
1495
  *
1496
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
1506
1497
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
1507
1498
  * @param {string} name name of the metadata item
1508
1499
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
1509
1500
  * @returns {Promise.<{metadata: DataExtensionItem, type: string}>} Promise of items
1510
1501
  */
1511
1502
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
1503
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
1512
1504
  /** @type {SoapRequestParams} */
1513
1505
  const options = {
1514
1506
  filter: {
@@ -175,10 +175,9 @@ class DataExtensionField extends MetadataType {
175
175
  // Updating to a new FieldType will result in an error; warn & afterwards remove it
176
176
  if (itemOld.FieldType !== item.FieldType) {
177
177
  // applicable: with or without data but simply ignored by API
178
- Util.logger.warn(
179
- ` - The Field Type of an existing field cannot be changed. Keeping the original value for [${deKey}].[${item.Name}]: '${itemOld.FieldType}'`
178
+ Util.logger.error(
179
+ ` - The Field Type of an existing field cannot be changed. [${deKey}].[${item.Name}]: '${itemOld.FieldType}'`
180
180
  );
181
- item.FieldType = itemOld.FieldType;
182
181
  }
183
182
  if (item.FieldType !== 'Decimal') {
184
183
  // remove scale - it's only used for "Decimal" to define the digits behind the decimal
@@ -188,17 +187,15 @@ class DataExtensionField extends MetadataType {
188
187
 
189
188
  if (itemOld.MaxLength > item.MaxLength) {
190
189
  // applicable: with or without data (Code 310007)
191
- Util.logger.warn(
192
- ` - The length of an existing field cannot be decreased. Keeping the original value for [${deKey}].[${item.Name}]: '${itemOld.MaxLength}'`
190
+ Util.logger.error(
191
+ ` - The length of an existing field cannot be decreased. [${deKey}].[${item.Name}]: '${itemOld.MaxLength}'`
193
192
  );
194
- item.MaxLength = itemOld.MaxLength;
195
193
  }
196
194
  if (Util.isFalse(itemOld.IsRequired) && Util.isTrue(item.IsRequired)) {
197
195
  // applicable: with or without data (Code 310007)
198
- Util.logger.warn(
199
- ` - A field cannot be changed to be required on update after it was created to allow nulls. Resetting to not equired: [${deKey}].[${item.Name}]`
196
+ Util.logger.error(
197
+ ` - A field cannot be changed to be required on update after it was created to allow nulls. [${deKey}].[${item.Name}]`
200
198
  );
201
- item.IsRequired = itemOld.IsRequired;
202
199
  }
203
200
 
204
201
  // enable renaming
@@ -241,9 +238,8 @@ class DataExtensionField extends MetadataType {
241
238
  item.IsRequired = false;
242
239
  } else {
243
240
  Util.logger.error(
244
- `- You cannot add a new primary key field to an existing table that has data. Removing [${deKey}].[${item.Name}] from deployment`
241
+ `- You cannot add a new primary key field to an existing table that has data. [${deKey}].[${item.Name}]`
245
242
  );
246
- deployColumns.splice(i, 1);
247
243
  }
248
244
  }
249
245
  if (item.Name_new) {
@@ -268,15 +264,13 @@ class DataExtensionField extends MetadataType {
268
264
  // filter bad manual changes to the json
269
265
  if (!Util.isTrue(item.IsRequired) && !Util.isFalse(item.IsRequired)) {
270
266
  Util.logger.error(
271
- `- Invalid value for 'IsRequired' of [${deKey}].[${item.Name}]. Found '${item.IsRequired}' instead of 'true'/'false'. Removing field from deploy!`
267
+ `- Invalid value for 'IsRequired' of [${deKey}].[${item.Name}]. Found '${item.IsRequired}' instead of 'true'/'false'.`
272
268
  );
273
- deployColumns.splice(i, 1);
274
269
  }
275
270
  if (!Util.isTrue(item.IsPrimaryKey) && !Util.isFalse(item.IsPrimaryKey)) {
276
271
  Util.logger.error(
277
- `- Invalid value for 'IsPrimaryKey' of [${deKey}].[${item.Name}]. Found '${item.IsPrimaryKey}' instead of 'true'/'false'. Removing field from deploy!`
272
+ `- Invalid value for 'IsPrimaryKey' of [${deKey}].[${item.Name}]. Found '${item.IsPrimaryKey}' instead of 'true'/'false'.`
278
273
  );
279
- deployColumns.splice(i, 1);
280
274
  }
281
275
  }
282
276
 
@@ -50,12 +50,14 @@ class DataExtract extends MetadataType {
50
50
  /**
51
51
  * Retrieve a specific dataExtract Definition by Name
52
52
  *
53
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
53
54
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
54
55
  * @param {string} name name of the metadata file
55
56
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
56
57
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
57
58
  */
58
59
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
60
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
59
61
  const res = await this.client.rest.get(
60
62
  '/automation/v1/dataextracts/?$filter=name%20eq%20' + encodeURIComponent(name)
61
63
  );
@@ -33,6 +33,7 @@ import pLimit from 'p-limit';
33
33
  */
34
34
  class Event extends MetadataType {
35
35
  static reCacheDataExtensions = [];
36
+ static createdKeys = [];
36
37
 
37
38
  /**
38
39
  * Retrieves Metadata of Event Definition.
@@ -81,12 +82,14 @@ class Event extends MetadataType {
81
82
  /**
82
83
  * Retrieve a specific Event Definition by Name
83
84
  *
85
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
84
86
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
85
87
  * @param {string} name name of the metadata file
86
88
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
87
89
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
88
90
  */
89
91
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
92
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
90
93
  const res = await this.client.rest.get(
91
94
  '/interaction/v1/eventDefinitions?name=' + encodeURIComponent(name)
92
95
  );
@@ -158,7 +161,10 @@ class Event extends MetadataType {
158
161
  */
159
162
  static async deploy(metadata, deployDir, retrieveDir) {
160
163
  Util.logBeta(this.definition.type);
161
- return super.deploy(metadata, deployDir, retrieveDir);
164
+ const metadataMap = await super.deploy(metadata, deployDir, retrieveDir);
165
+
166
+ this.createdKeys.length = 0; // reset createdKeys after deploy to ensure it's not used in future retrieves
167
+ return metadataMap;
162
168
  }
163
169
 
164
170
  /**
@@ -168,6 +174,7 @@ class Event extends MetadataType {
168
174
  * @returns {Promise} Promise
169
175
  */
170
176
  static create(metadata) {
177
+ this.createdKeys.push(metadata[this.definition.keyField]);
171
178
  return super.createREST(metadata, '/interaction/v1/eventDefinitions/');
172
179
  }
173
180
 
@@ -215,8 +222,7 @@ class Event extends MetadataType {
215
222
  break;
216
223
  }
217
224
  case 'SalesforceObjectTriggerV2': {
218
- metadata.iconUrl ||=
219
- '/events/js/salesforce-event/events/NetworkMember/images/SF-Event-Icon.svg';
225
+ metadata.iconUrl ||= '/events/js/salesforce-event/images/SF-Event-Icon.svg';
220
226
  break;
221
227
  }
222
228
  case 'AutomationAudience': {
@@ -225,6 +231,11 @@ class Event extends MetadataType {
225
231
  }
226
232
  }
227
233
 
234
+ // filter
235
+ if (!metadata.filterDefinitionId) {
236
+ metadata.filterDefinitionId = '00000000-0000-0000-0000-000000000000';
237
+ }
238
+
228
239
  // automation
229
240
  if (metadata.r__automation_key) {
230
241
  metadata.automationId = cache.searchForField(
@@ -236,8 +247,14 @@ class Event extends MetadataType {
236
247
  if (metadata.arguments) {
237
248
  metadata.arguments.automationId = metadata.automationId;
238
249
  }
239
- delete metadata.arguments.automationId;
250
+ } else if (!metadata.automationId) {
251
+ // if no automation was linked during retrieve we remove the field and hence need to re-set it before deployment
252
+ metadata.automationId = '00000000-0000-0000-0000-000000000000';
253
+ if (metadata.arguments) {
254
+ metadata.arguments.automationId = metadata.automationId;
255
+ }
240
256
  }
257
+
241
258
  // dataExteension
242
259
  // is resolved in createOrUpdate
243
260
 
@@ -529,28 +546,40 @@ class Event extends MetadataType {
529
546
  );
530
547
  }
531
548
 
549
+ // filter
550
+ if (
551
+ metadata?.filterDefinitionId &&
552
+ metadata.filterDefinitionId === '00000000-0000-0000-0000-000000000000'
553
+ ) {
554
+ // if no filter is linked this placeholder value is set which holds no value
555
+ delete metadata.filterDefinitionId;
556
+ }
557
+
532
558
  // automation
533
- try {
534
- if (
535
- metadata?.automationId &&
536
- metadata?.automationId !== '00000000-0000-0000-0000-000000000000'
537
- ) {
538
- metadata.r__automation_key = cache.searchForField(
539
- 'automation',
540
- metadata.automationId,
541
- 'id',
542
- 'key'
543
- );
559
+ if (metadata?.automationId) {
560
+ if (metadata?.automationId === '00000000-0000-0000-0000-000000000000') {
561
+ // if no automation is linked this placeholder value is set which holds no value
544
562
  delete metadata.automationId;
545
- delete metadata.arguments?.automationId;
563
+ } else {
564
+ try {
565
+ metadata.r__automation_key = cache.searchForField(
566
+ 'automation',
567
+ metadata.automationId,
568
+ 'id',
569
+ 'key'
570
+ );
571
+ delete metadata.automationId;
572
+ delete metadata.arguments?.automationId;
573
+ } catch (ex) {
574
+ Util.logger.warn(
575
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
576
+ metadata[this.definition.keyField]
577
+ }): ${ex.message}.`
578
+ );
579
+ }
546
580
  }
547
- } catch (ex) {
548
- Util.logger.warn(
549
- ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
550
- metadata[this.definition.keyField]
551
- }): ${ex.message}.`
552
- );
553
581
  }
582
+
554
583
  // dataExtension
555
584
  try {
556
585
  metadata.r__dataExtension_key = cache.searchForField(
@@ -578,16 +607,18 @@ class Event extends MetadataType {
578
607
  if (metadata.mode === 'Production') {
579
608
  delete metadata.mode;
580
609
  }
581
- if (metadata.interactionCount === 0 && metadata.publishedInteractionCount === 0) {
582
- Util.logger.warn(
583
- ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is no longer used and could therefore be deleted. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
584
- );
585
- } else if (metadata.publishedInteractionCount === 0) {
586
- Util.logger.info(
587
- Util.getGrayMsg(
588
- ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is currently inactive. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
589
- )
590
- );
610
+ if (!this.createdKeys.includes(metadata[this.definition.keyField])) {
611
+ if (metadata.interactionCount === 0 && metadata.publishedInteractionCount === 0) {
612
+ Util.logger.warn(
613
+ ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is not used and could therefore be deleted. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
614
+ );
615
+ } else if (metadata.publishedInteractionCount === 0) {
616
+ Util.logger.info(
617
+ Util.getGrayMsg(
618
+ ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is currently inactive. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
619
+ )
620
+ );
621
+ }
591
622
  }
592
623
 
593
624
  try {
@@ -50,12 +50,14 @@ class FileTransfer extends MetadataType {
50
50
  /**
51
51
  * Retrieve a specific File Transfer Definition by Name
52
52
  *
53
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
53
54
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
54
55
  * @param {string} name name of the metadata file
55
56
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
56
57
  * @returns {Promise.<MetadataTypeItemObj>} Promise
57
58
  */
58
59
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
60
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
59
61
  const res = await this.client.rest.get(
60
62
  '/automation/v1/filetransfers/?$filter=name%20eq%20' + encodeURIComponent(name)
61
63
  );