mcdev 4.3.4 → 5.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 (228) 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 +2 -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/README.md +1 -1
  14. package/boilerplate/config.json +1 -1
  15. package/boilerplate/files/.prettierrc +1 -1
  16. package/boilerplate/files/.vscode/extensions.json +1 -1
  17. package/boilerplate/forcedUpdates.json +4 -0
  18. package/docs/dist/documentation.md +1196 -430
  19. package/lib/Builder.js +6 -1
  20. package/lib/Deployer.js +30 -5
  21. package/lib/MetadataTypeDefinitions.js +8 -6
  22. package/lib/MetadataTypeInfo.js +8 -6
  23. package/lib/cli.js +54 -42
  24. package/lib/index.js +82 -8
  25. package/lib/metadataTypes/Asset.js +73 -1
  26. package/lib/metadataTypes/AttributeGroup.js +0 -1
  27. package/lib/metadataTypes/Automation.js +48 -5
  28. package/lib/metadataTypes/Campaign.js +20 -7
  29. package/lib/metadataTypes/ContentArea.js +1 -1
  30. package/lib/metadataTypes/DataExtension.js +221 -184
  31. package/lib/metadataTypes/DataExtensionField.js +12 -19
  32. package/lib/metadataTypes/DataExtensionTemplate.js +1 -1
  33. package/lib/metadataTypes/DataExtract.js +1 -1
  34. package/lib/metadataTypes/DataExtractType.js +1 -1
  35. package/lib/metadataTypes/Email.js +1 -1
  36. package/lib/metadataTypes/{EmailSendDefinition.js → EmailSend.js} +5 -5
  37. package/lib/metadataTypes/{EventDefinition.js → Event.js} +17 -35
  38. package/lib/metadataTypes/{FtpLocation.js → FileLocation.js} +2 -2
  39. package/lib/metadataTypes/FileTransfer.js +8 -7
  40. package/lib/metadataTypes/Filter.js +1 -1
  41. package/lib/metadataTypes/Folder.js +8 -3
  42. package/lib/metadataTypes/ImportFile.js +6 -6
  43. package/lib/metadataTypes/{Interaction.js → Journey.js} +311 -147
  44. package/lib/metadataTypes/List.js +2 -2
  45. package/lib/metadataTypes/MetadataType.js +318 -90
  46. package/lib/metadataTypes/MobileCode.js +0 -1
  47. package/lib/metadataTypes/MobileKeyword.js +336 -40
  48. package/lib/metadataTypes/MobileMessage.js +473 -0
  49. package/lib/metadataTypes/Query.js +114 -32
  50. package/lib/metadataTypes/Role.js +60 -21
  51. package/lib/metadataTypes/Script.js +2 -3
  52. package/lib/metadataTypes/SendClassification.js +40 -0
  53. package/lib/metadataTypes/SetDefinition.js +1 -7
  54. package/lib/metadataTypes/TransactionalEmail.js +2 -3
  55. package/lib/metadataTypes/TransactionalMessage.js +1 -2
  56. package/lib/metadataTypes/TransactionalSMS.js +8 -15
  57. package/lib/metadataTypes/{TriggeredSendDefinition.js → TriggeredSend.js} +35 -27
  58. package/lib/metadataTypes/User.js +1185 -0
  59. package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
  60. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +1 -0
  61. package/lib/metadataTypes/definitions/Automation.definition.js +3 -2
  62. package/lib/metadataTypes/definitions/Campaign.definition.js +79 -4
  63. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -0
  64. package/lib/metadataTypes/definitions/DataExtension.definition.js +2 -1
  65. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -0
  66. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -0
  67. package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -0
  68. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -0
  69. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -0
  70. package/lib/metadataTypes/definitions/Email.definition.js +1 -0
  71. package/lib/metadataTypes/definitions/{EmailSendDefinition.definition.js → EmailSend.definition.js} +4 -2
  72. package/lib/metadataTypes/definitions/{EventDefinition.definition.js → Event.definition.js} +2 -1
  73. package/lib/metadataTypes/definitions/{FtpLocation.definition.js → FileLocation.definition.js} +4 -3
  74. package/lib/metadataTypes/definitions/FileTransfer.definition.js +3 -2
  75. package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
  76. package/lib/metadataTypes/definitions/Folder.definition.js +2 -0
  77. package/lib/metadataTypes/definitions/ImportFile.definition.js +4 -3
  78. package/lib/metadataTypes/definitions/{Interaction.definition.js → Journey.definition.js} +11 -2
  79. package/lib/metadataTypes/definitions/List.definition.js +1 -0
  80. package/lib/metadataTypes/definitions/MobileCode.definition.js +3 -1
  81. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +27 -17
  82. package/lib/metadataTypes/definitions/MobileMessage.definition.js +743 -0
  83. package/lib/metadataTypes/definitions/Query.definition.js +3 -2
  84. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  85. package/lib/metadataTypes/definitions/Script.definition.js +1 -0
  86. package/lib/metadataTypes/definitions/SendClassification.definition.js +114 -0
  87. package/lib/metadataTypes/definitions/SetDefinition.definition.js +1 -0
  88. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  89. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +1 -0
  90. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +1 -0
  91. package/lib/metadataTypes/definitions/{TriggeredSendDefinition.definition.js → TriggeredSend.definition.js} +5 -3
  92. package/lib/metadataTypes/definitions/User.definition.js +365 -0
  93. package/lib/retrieveChangelog.js +1 -2
  94. package/lib/util/auth.js +38 -9
  95. package/lib/util/businessUnit.js +3 -3
  96. package/lib/util/cli.js +55 -7
  97. package/lib/util/devops.js +6 -4
  98. package/lib/util/file.js +55 -13
  99. package/lib/util/init.config.js +1 -2
  100. package/lib/util/init.npm.js +3 -3
  101. package/lib/util/util.js +23 -14
  102. package/package.json +16 -15
  103. package/test/general.test.js +62 -0
  104. package/test/mockRoot/.mcdevrc.json +7 -5
  105. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testBlocked_user.user-meta.json +23 -0
  106. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testExisting_user.user-meta.json +31 -0
  107. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testNew_user.user-meta.json +27 -0
  108. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{childBU_dataextension_test.dataExtension-meta.json → testExisting_dataExtension.dataExtension-meta.json} +2 -2
  109. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{testDataExtension.dataExtension-meta.json → testNew_dataExtension.dataExtension-meta.json} +2 -2
  110. package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_interaction.interaction-meta.json +576 -0
  111. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp +2 -0
  112. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json +10 -0
  113. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp +2 -0
  114. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json +10 -0
  115. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.amp +1 -0
  116. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.json +61 -0
  117. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.amp +1 -0
  118. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.json +60 -0
  119. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  120. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json +1 -1
  121. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql +1 -1
  122. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +1 -1
  123. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -1
  124. package/test/resourceFactory.js +13 -0
  125. package/test/resources/1111111/accountUser/configure-response.xml +70 -0
  126. package/test/resources/1111111/accountUser/create-response.xml +97 -0
  127. package/test/resources/1111111/accountUser/retrieve-response.xml +156 -0
  128. package/test/resources/1111111/accountUser/update-response.xml +111 -0
  129. package/test/resources/1111111/accountUserAccount/retrieve-response.xml +77 -0
  130. package/test/resources/1111111/platform/v1/setup/quickflow/data/get-response.json +455 -0
  131. package/test/resources/1111111/role/retrieve-response.xml +76 -0
  132. package/test/resources/1111111/user/build-expected.json +16 -0
  133. package/test/resources/1111111/user/create-expected.json +21 -0
  134. package/test/resources/1111111/user/retrieve-expected.json +24 -0
  135. package/test/resources/1111111/user/template-expected.json +16 -0
  136. package/test/resources/1111111/user/update-expected.json +21 -0
  137. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/delete-response.json +1 -0
  138. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +17 -0
  139. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +3 -3
  140. package/test/resources/9999999/automation/v1/queries/get-response.json +21 -4
  141. package/test/resources/9999999/automation/v1/queries/post-response.json +4 -4
  142. package/test/resources/9999999/data/v1/customobjectdata/key/{childBU_dataextension_test → testExisting_dataExtension}/rowset/get-response.json +1 -1
  143. package/test/resources/9999999/dataExtension/build-expected.json +3 -3
  144. package/test/resources/9999999/dataExtension/create-expected.json +2 -2
  145. package/test/resources/9999999/dataExtension/create-response.xml +8 -3
  146. package/test/resources/9999999/dataExtension/retrieve-expected.json +3 -3
  147. package/test/resources/9999999/dataExtension/retrieve-response.xml +9 -4
  148. package/test/resources/9999999/dataExtension/template-expected.json +3 -3
  149. package/test/resources/9999999/dataExtension/update-expected.json +3 -3
  150. package/test/resources/9999999/dataExtension/update-response.xml +9 -4
  151. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +14 -9
  152. package/test/resources/9999999/interaction/v1/interactions/get-response.json +312 -0
  153. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/get-response.json +312 -0
  154. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/put-response.json +592 -0
  155. package/test/resources/9999999/journey/build-expected.json +572 -0
  156. package/test/resources/9999999/journey/get-expected.json +576 -0
  157. package/test/resources/9999999/journey/put-expected.json +576 -0
  158. package/test/resources/9999999/journey/template-expected.json +572 -0
  159. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json +42 -0
  160. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow/delete-response.json +0 -0
  161. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -0
  162. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/post-response.json +3 -0
  163. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/delete-response.json +0 -0
  164. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json +106 -0
  165. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/post-response.json +0 -0
  166. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTQ3Ojc4OjA/get-response.json +127 -0
  167. package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +129 -0
  168. package/test/resources/9999999/legacy/v1/beta/mobile/message/post-response.json +3 -0
  169. package/test/resources/9999999/legacy/v1/beta2/data/campaign/get-response.json +29 -0
  170. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  171. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +1 -1
  172. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +1 -1
  173. package/test/resources/9999999/mobileKeyword/build-expected.amp +2 -0
  174. package/test/resources/9999999/mobileKeyword/build-expected.json +9 -0
  175. package/test/resources/9999999/mobileKeyword/get-expected.amp +2 -0
  176. package/test/resources/9999999/mobileKeyword/get-expected.json +15 -0
  177. package/test/resources/9999999/mobileKeyword/post-create-expected.amp +2 -0
  178. package/test/resources/9999999/mobileKeyword/post-create-expected.json +17 -0
  179. package/test/resources/9999999/mobileKeyword/template-expected.amp +2 -0
  180. package/test/resources/9999999/mobileKeyword/template-expected.json +9 -0
  181. package/test/resources/9999999/mobileMessage/build-expected.amp +1 -0
  182. package/test/resources/9999999/mobileMessage/build-expected.json +60 -0
  183. package/test/resources/9999999/mobileMessage/get-expected.amp +1 -0
  184. package/test/resources/9999999/mobileMessage/get-expected.json +61 -0
  185. package/test/resources/9999999/mobileMessage/post-create-expected.amp +1 -0
  186. package/test/resources/9999999/mobileMessage/post-create-expected.json +63 -0
  187. package/test/resources/9999999/mobileMessage/post-update-expected.amp +1 -0
  188. package/test/resources/9999999/mobileMessage/post-update-expected.json +61 -0
  189. package/test/resources/9999999/mobileMessage/template-expected.amp +1 -0
  190. package/test/resources/9999999/mobileMessage/template-expected.json +60 -0
  191. package/test/resources/9999999/query/build-expected.json +1 -1
  192. package/test/resources/9999999/query/get-expected.json +1 -1
  193. package/test/resources/9999999/query/get2-expected.json +11 -0
  194. package/test/resources/9999999/query/patch-expected.json +1 -1
  195. package/test/resources/9999999/query/post-expected.json +1 -1
  196. package/test/resources/9999999/query/template-expected.json +1 -1
  197. package/test/resources/9999999/queryDefinition/retrieve-response.xml +30 -0
  198. package/test/resources/9999999/transactionalEmail/build-expected.json +5 -5
  199. package/test/resources/9999999/transactionalEmail/get-expected.json +1 -1
  200. package/test/resources/9999999/transactionalEmail/patch-expected.json +1 -1
  201. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  202. package/test/resources/9999999/transactionalEmail/template-expected.json +5 -5
  203. package/test/resources/9999999/transactionalPush/build-expected.json +2 -2
  204. package/test/resources/9999999/transactionalPush/template-expected.json +2 -2
  205. package/test/resources/9999999/transactionalSMS/build-expected.json +3 -3
  206. package/test/resources/9999999/transactionalSMS/template-expected.json +3 -3
  207. package/test/{dataExtension.test.js → type.dataExtension.test.js} +78 -21
  208. package/test/{interaction.test.js → type.journey.test.js} +64 -30
  209. package/test/type.mobileKeyword.test.js +250 -0
  210. package/test/type.mobileMessage.test.js +205 -0
  211. package/test/{query.test.js → type.query.test.js} +102 -5
  212. package/test/{transactionalEmail.test.js → type.transactionalEmail.test.js} +40 -2
  213. package/test/{transactionalPush.test.js → type.transactionalPush.test.js} +41 -2
  214. package/test/{transactionalSMS.test.js → type.transactionalSMS.test.js} +73 -3
  215. package/test/type.user.test.js +160 -0
  216. package/test/utils.js +17 -5
  217. package/types/mcdev.d.js +48 -15
  218. package/.github/workflows/code-analysis.yml +0 -57
  219. package/lib/metadataTypes/AccountUser.js +0 -426
  220. package/lib/metadataTypes/definitions/AccountUser.definition.js +0 -227
  221. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +0 -266
  222. package/test/resources/9999999/interaction/build-expected.json +0 -260
  223. package/test/resources/9999999/interaction/get-expected.json +0 -264
  224. package/test/resources/9999999/interaction/put-expected.json +0 -264
  225. package/test/resources/9999999/interaction/template-expected.json +0 -260
  226. package/test/resources/9999999/interaction/v1/interactions/put-response.json +0 -280
  227. /package/test/mockRoot/deploy/testInstance/testBU/{interaction → journey}/testNew_interaction.interaction-meta.json +0 -0
  228. /package/test/resources/9999999/{interaction → journey}/post-expected.json +0 -0
@@ -0,0 +1,473 @@
1
+ 'use strict';
2
+
3
+ const TYPE = require('../../types/mcdev.d');
4
+ const MetadataType = require('./MetadataType');
5
+ const Util = require('../util/util');
6
+ const File = require('../util/file');
7
+ const cache = require('../util/cache');
8
+
9
+ /**
10
+ * MobileMessage MetadataType
11
+ *
12
+ * @augments MetadataType
13
+ */
14
+ class MobileMessage extends MetadataType {
15
+ /**
16
+ * Retrieves Metadata of Mobile Keywords
17
+ *
18
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
19
+ * @param {void} [_] unused parameter
20
+ * @param {void} [__] unused parameter
21
+ * @param {string} [key] customer key of single item to retrieve
22
+ * @returns {Promise.<TYPE.MetadataTypeMapObj> | void} Promise of metadata
23
+ */
24
+ static retrieve(retrieveDir, _, __, key) {
25
+ if (key && key.startsWith('id:')) {
26
+ // if key starts with id: remove it to be compatible with other legacy API types (MetadataType.postCreateTasks_legacyApi)
27
+ key = key.slice(3);
28
+ }
29
+ try {
30
+ return super.retrieveREST(
31
+ retrieveDir,
32
+ '/legacy/v1/beta/mobile/message/' +
33
+ (key ||
34
+ '?view=details&version=3&$sort=lastUpdated%20DESC&$where=isTest%20eq%200%20and%20status%20neq%20%27Archive%27'),
35
+ null,
36
+ key
37
+ );
38
+ } catch (ex) {
39
+ // if the mobileMessage does not exist, the API returns the error "Request failed with status code 400 (ERR_BAD_REQUEST)" which would otherwise bring execution to a hold
40
+ if (key && ex.code === 'ERR_BAD_REQUEST') {
41
+ Util.logger.info(
42
+ `Downloaded: ${this.definition.type} (0)${Util.getKeysString(key)}`
43
+ );
44
+ } else {
45
+ throw ex;
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Retrieves event definition metadata for caching
52
+ *
53
+ * @param {void} _ parameter not used
54
+ * @param {void} __ parameter not used
55
+ * @param {string} [key] customer key of single item to retrieve
56
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
57
+ */
58
+ static retrieveForCache(_, __, key) {
59
+ return this.retrieve(null, null, null, key);
60
+ }
61
+
62
+ /**
63
+ * Updates a single item
64
+ *
65
+ * @param {TYPE.MetadataTypeItem} metadata a single item
66
+ * @returns {Promise} Promise
67
+ */
68
+ static update(metadata) {
69
+ return super.updateREST(
70
+ metadata,
71
+ '/legacy/v1/beta/mobile/message/' + metadata[this.definition.idField],
72
+ 'post' // upsert API, post for insert and update!
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Creates a single item
78
+ *
79
+ * @param {TYPE.MetadataTypeItem} metadata a single item
80
+ * @returns {Promise} Promise
81
+ */
82
+ static create(metadata) {
83
+ return super.createREST(metadata, '/legacy/v1/beta/mobile/message/');
84
+ }
85
+ /**
86
+ * helper for {@link preDeployTasks} that loads extracted code content back into JSON
87
+ *
88
+ * @param {TYPE.MetadataTypeItem} metadata a single definition
89
+ * @param {string} deployDir directory of deploy files
90
+ * @param {string} [templateName] name of the template used to built defintion (prior applying templating)
91
+ * @returns {Promise.<string>} code
92
+ */
93
+ static async _mergeCode(metadata, deployDir, templateName) {
94
+ const fileExtension = 'amp';
95
+ templateName ||= metadata[this.definition.keyField];
96
+ const codePath = File.normalizePath([
97
+ deployDir,
98
+ this.definition.type,
99
+ templateName + '.' + this.definition.type + '-meta',
100
+ ]);
101
+
102
+ if (await File.pathExists(codePath + '.' + fileExtension)) {
103
+ return await File.readFilteredFilename(
104
+ [deployDir, this.definition.type],
105
+ templateName + '.' + this.definition.type + '-meta',
106
+ fileExtension
107
+ );
108
+ } else {
109
+ throw new Error(`Could not find ${codePath}.${fileExtension}`);
110
+ }
111
+ }
112
+ /**
113
+ * helper for {@link parseMetadata} and {@link _buildForNested}
114
+ *
115
+ * @param {string} code the code of the file
116
+ * @returns {{fileExt:string,code:string}} returns found extension and file content
117
+ */
118
+ static prepExtractedCode(code) {
119
+ const fileExt = 'amp';
120
+
121
+ return { fileExt, code };
122
+ }
123
+
124
+ /**
125
+ * should return only the json for all but asset, query and script that are saved as multiple files
126
+ * additionally, the documentation for dataExtension and automation should be returned
127
+ *
128
+ * @param {string[]} keyArr customerkey of the metadata
129
+ * @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
130
+ */
131
+ static getFilesToCommit(keyArr) {
132
+ const path = File.normalizePath([
133
+ this.properties.directories.retrieve,
134
+ this.buObject.credential,
135
+ this.buObject.businessUnit,
136
+ this.definition.type,
137
+ ]);
138
+
139
+ const fileList = keyArr.flatMap((key) => [
140
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
141
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.amp`]),
142
+ ]);
143
+ return fileList;
144
+ }
145
+
146
+ /**
147
+ * manages post retrieve steps
148
+ *
149
+ * @param {TYPE.MetadataTypeItem} metadata a single query
150
+ * @returns {TYPE.CodeExtractItem} Array with one metadata object and one query string
151
+ */
152
+ static postRetrieveTasks(metadata) {
153
+ // mobileCode
154
+ try {
155
+ cache.searchForField('mobileCode', metadata.code.code, 'code', 'code');
156
+ } catch (ex) {
157
+ Util.logger.warn(
158
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
159
+ metadata[this.definition.keyField]
160
+ }): ${ex.message}.`
161
+ );
162
+ }
163
+
164
+ // mobileKeyword
165
+ try {
166
+ if (metadata.keyword?.keyword) {
167
+ cache.searchForField(
168
+ 'mobileKeyword',
169
+ metadata.keyword.keyword,
170
+ 'keyword',
171
+ 'keyword'
172
+ );
173
+ }
174
+ if (metadata.subscriptionKeyword?.keyword) {
175
+ cache.searchForField(
176
+ 'mobileKeyword',
177
+ metadata.subscriptionKeyword.keyword,
178
+ 'keyword',
179
+ 'keyword'
180
+ );
181
+ }
182
+ } catch (ex) {
183
+ Util.logger.warn(
184
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
185
+ metadata[this.definition.keyField]
186
+ }): ${ex.message}.`
187
+ );
188
+ }
189
+ // campaign
190
+ try {
191
+ if (Array.isArray(metadata.campaigns)) {
192
+ metadata.c__campaignNames = [];
193
+
194
+ for (const campaign of metadata.campaigns) {
195
+ try {
196
+ // test if exists
197
+ const test = cache.getByKey('campaign', campaign.name);
198
+ if (!test) {
199
+ throw new Error(`campaign ${campaign.name} not found in cache`);
200
+ }
201
+
202
+ metadata.c__campaignNames.push(campaign.name);
203
+ } catch (ex) {
204
+ Util.logger.warn(
205
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]}: ${
206
+ ex.message
207
+ }`
208
+ );
209
+ }
210
+ }
211
+ delete metadata.campaigns;
212
+ }
213
+ } catch (ex) {
214
+ Util.logger.warn(
215
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
216
+ metadata[this.definition.keyField]
217
+ }): ${ex.message}.`
218
+ );
219
+ }
220
+
221
+ if (metadata.smsTriggeredSendDefinitionId !== '00000000-0000-0000-0000-000000000000') {
222
+ // TODO unknown type
223
+ }
224
+
225
+ if (metadata.triggeredSendName) {
226
+ // TODO unknown type
227
+ }
228
+
229
+ if (metadata.messageObjectId) {
230
+ // TODO unknown type
231
+ }
232
+
233
+ if (metadata.template?.id) {
234
+ // TODO unknown type
235
+ }
236
+
237
+ // extract text/code
238
+ const codeArr = [];
239
+ // keep between tags
240
+ const { fileExt, code } = this.prepExtractedCode(metadata.text);
241
+ delete metadata.text;
242
+ codeArr.push({
243
+ subFolder: null,
244
+ fileName: metadata[this.definition.keyField],
245
+ fileExt: fileExt,
246
+ content: code,
247
+ });
248
+
249
+ return { json: metadata, codeArr: codeArr, subFolder: null };
250
+ }
251
+
252
+ /**
253
+ * prepares an event definition for deployment
254
+ *
255
+ * @param {TYPE.MetadataTypeItem} metadata a single MobileMessage
256
+ * @param {string} deployDir directory of deploy files
257
+ * @returns {TYPE.MetadataTypeItem} Promise
258
+ */
259
+ static async preDeployTasks(metadata, deployDir) {
260
+ // mobileCode
261
+ const code = cache.getByKey('mobileCode', metadata.code.code);
262
+ if (!code) {
263
+ throw new Error(`mobileCode ${metadata.code.code} not found in cache`);
264
+ }
265
+ metadata.code = code;
266
+
267
+ // mobileKeyword
268
+ if (metadata.keyword?.keyword) {
269
+ const keyword = cache.getByKey('mobileKeyword', metadata.keyword.keyword);
270
+ if (!keyword) {
271
+ throw new Error(`mobileKeyword ${metadata.keyword.keyword} not found in cache`);
272
+ }
273
+ metadata.keyword = keyword;
274
+ }
275
+ if (metadata.subscriptionKeyword?.keyword) {
276
+ const keyword = cache.getByKey('mobileKeyword', metadata.subscriptionKeyword.keyword);
277
+ if (!keyword) {
278
+ throw new Error(`mobileKeyword ${metadata.keyword.keyword} not found in cache`);
279
+ }
280
+
281
+ metadata.subscriptionKeyword.keyword = keyword;
282
+ }
283
+
284
+ // campaign
285
+ if (Array.isArray(metadata.c__campaignNames)) {
286
+ metadata.campaigns = [];
287
+
288
+ for (const campaignName of metadata.c__campaignNames) {
289
+ const campaign = cache.getByKey('campaign', campaignName);
290
+ if (!campaign) {
291
+ throw new Error(`campaign ${campaignName} not found in cache`);
292
+ }
293
+ metadata.campaigns.push({
294
+ id: campaign.id,
295
+ name: campaignName,
296
+ display: {
297
+ name: 'color',
298
+ value: campaign.color,
299
+ },
300
+ });
301
+ }
302
+ delete metadata.c__campaignNames;
303
+ }
304
+
305
+ if (metadata.smsTriggeredSendDefinitionId !== '00000000-0000-0000-0000-000000000000') {
306
+ // TODO unknown type
307
+ }
308
+
309
+ // code
310
+ metadata.text = await this._mergeCode(metadata, deployDir);
311
+
312
+ return metadata;
313
+ }
314
+ /**
315
+ * helper for {@link createREST}
316
+ *
317
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
318
+ * @param {object} apiResponse varies depending on the API call
319
+ * @returns {void}
320
+ */
321
+ static async postCreateTasks(metadataEntry, apiResponse) {
322
+ await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
323
+ }
324
+ /**
325
+ * helper for {@link updateREST}
326
+ *
327
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
328
+ * @param {object} apiResponse varies depending on the API call
329
+ * @returns {void}
330
+ */
331
+ static async postUpdateTasks(metadataEntry, apiResponse) {
332
+ await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
333
+ }
334
+ /**
335
+ * helper for {@link MetadataType.buildDefinition}
336
+ * handles extracted code if any are found for complex types
337
+ *
338
+ * @param {string} templateDir Directory where metadata templates are stored
339
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
340
+ * @param {TYPE.MetadataTypeItem} metadata main JSON file that was read from file system
341
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
342
+ * @param {string} templateName name of the template to be built
343
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
344
+ */
345
+ static buildDefinitionForNested(
346
+ templateDir,
347
+ targetDir,
348
+ metadata,
349
+ templateVariables,
350
+ templateName
351
+ ) {
352
+ return this._buildForNested(
353
+ templateDir,
354
+ targetDir,
355
+ metadata,
356
+ templateVariables,
357
+ templateName,
358
+ 'definition'
359
+ );
360
+ }
361
+ /**
362
+ * helper for {@link MetadataType.buildTemplate}
363
+ * handles extracted code if any are found for complex types
364
+ *
365
+ * @example scripts are saved as 1 json and 1 ssjs file. both files need to be run through templating
366
+ * @param {string} templateDir Directory where metadata templates are stored
367
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
368
+ * @param {TYPE.MetadataTypeItem} metadata main JSON file that was read from file system
369
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
370
+ * @param {string} templateName name of the template to be built
371
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
372
+ */
373
+ static buildTemplateForNested(
374
+ templateDir,
375
+ targetDir,
376
+ metadata,
377
+ templateVariables,
378
+ templateName
379
+ ) {
380
+ return this._buildForNested(
381
+ templateDir,
382
+ targetDir,
383
+ metadata,
384
+ templateVariables,
385
+ templateName,
386
+ 'template'
387
+ );
388
+ }
389
+
390
+ /**
391
+ * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested}
392
+ * handles extracted code if any are found for complex types
393
+ *
394
+ * @param {string} templateDir Directory where metadata templates are stored
395
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
396
+ * @param {TYPE.MetadataTypeItem} metadata main JSON file that was read from file system
397
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
398
+ * @param {string} templateName name of the template to be built
399
+ * @param {'definition'|'template'} mode defines what we use this helper for
400
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
401
+ */
402
+ static async _buildForNested(
403
+ templateDir,
404
+ targetDir,
405
+ metadata,
406
+ templateVariables,
407
+ templateName,
408
+ mode
409
+ ) {
410
+ // get code from filesystem
411
+ let code = await this._mergeCode(metadata, templateDir, templateName);
412
+ // try to remove script tags and decide on file extension (html/ssjs)
413
+ const file = this.prepExtractedCode(code);
414
+ const fileExt = file.fileExt;
415
+ code = file.code;
416
+ // apply templating
417
+ try {
418
+ if (mode === 'definition') {
419
+ // replace template variables with their values
420
+ code = this.applyTemplateValues(code, templateVariables);
421
+ } else if (mode === 'template') {
422
+ // replace template values with corresponding variable names
423
+ code = this.applyTemplateNames(code, templateVariables);
424
+ }
425
+ } catch {
426
+ throw new Error(
427
+ `${this.definition.type}:: Error applying template variables on ${
428
+ templateName + '.' + this.definition.type
429
+ }-meta.amp.`
430
+ );
431
+ }
432
+
433
+ // write to file
434
+ const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
435
+ const nestedFilePaths = [];
436
+
437
+ // keep old name if creating templates, otherwise use new name
438
+ const fileName = mode === 'definition' ? metadata[this.definition.keyField] : templateName;
439
+
440
+ for (const targetDir of targetDirArr) {
441
+ File.writeToFile(
442
+ [targetDir, this.definition.type],
443
+ fileName + '.' + this.definition.type + '-meta',
444
+ fileExt,
445
+ code
446
+ );
447
+ nestedFilePaths.push([
448
+ targetDir,
449
+ this.definition.type,
450
+ fileName + '.' + this.definition.type + '-meta.' + fileExt,
451
+ ]);
452
+ }
453
+ return nestedFilePaths;
454
+ }
455
+ /**
456
+ * Delete a metadata item from the specified business unit
457
+ * ! the endpoint expects the ID and not a key but for mcdev in this case key==id
458
+ *
459
+ * @param {string} id Identifier of item
460
+ * @returns {Promise.<boolean>} deletion success status
461
+ */
462
+ static deleteByKey(id) {
463
+ Util.logger.info(
464
+ ' - Note: As long as the provided API key once existed, you will not see an error even if the mobileMessage is already deleted.'
465
+ );
466
+ return super.deleteByKeyREST('/legacy/v1/beta/mobile/message/' + id, id, false);
467
+ }
468
+ }
469
+
470
+ // Assign definition to static attributes
471
+ MobileMessage.definition = require('../MetadataTypeDefinitions').mobileMessage;
472
+
473
+ module.exports = MobileMessage;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const TYPE = require('../../types/mcdev.d');
4
+ const Util = require('../util/util');
4
5
  const MetadataType = require('./MetadataType');
5
6
  const File = require('../util/file');
6
7
  const cache = require('../util/cache');
@@ -32,12 +33,20 @@ class Query extends MetadataType {
32
33
  */
33
34
  static async retrieve(retrieveDir, _, __, key) {
34
35
  await File.initPrettier('sql');
35
- const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
36
+ let objectId = null;
37
+ if (key) {
38
+ objectId = await this._getObjectIdForSingleRetrieve(key);
39
+ if (!objectId) {
40
+ // avoid running the rest request below by returning early
41
+ Util.logger.info(`Downloaded: ${this.definition.type} (0) - Key: ${key}`);
42
+ return { metadata: {}, type: this.definition.type };
43
+ }
44
+ }
45
+
36
46
  return super.retrieveREST(
37
47
  retrieveDir,
38
48
  '/automation/v1/queries/' + (objectId || ''),
39
49
  null,
40
- null,
41
50
  key
42
51
  );
43
52
  }
@@ -51,9 +60,17 @@ class Query extends MetadataType {
51
60
  static async _getObjectIdForSingleRetrieve(key) {
52
61
  const response = await this.client.soap.retrieve('QueryDefinition', ['ObjectID'], {
53
62
  filter: {
54
- leftOperand: 'CustomerKey',
55
- operator: 'equals',
56
- rightOperand: key,
63
+ leftOperand: {
64
+ leftOperand: 'CustomerKey',
65
+ operator: 'equals',
66
+ rightOperand: key,
67
+ },
68
+ operator: 'AND',
69
+ rightOperand: {
70
+ leftOperand: 'Status',
71
+ operator: 'equals',
72
+ rightOperand: 'Active',
73
+ },
57
74
  },
58
75
  });
59
76
  return response?.Results?.length ? response.Results[0].ObjectID : null;
@@ -81,7 +98,6 @@ class Query extends MetadataType {
81
98
  return super.retrieveREST(
82
99
  templateDir,
83
100
  '/automation/v1/queries/?$filter=Name%20eq%20' + encodeURIComponent(name),
84
- null,
85
101
  templateVariables
86
102
  );
87
103
  }
@@ -93,7 +109,58 @@ class Query extends MetadataType {
93
109
  * @returns {TYPE.CodeExtractItem} Array with one metadata object and one query string
94
110
  */
95
111
  static postRetrieveTasks(metadata) {
96
- return this.parseMetadata(metadata);
112
+ // folder
113
+ super.setFolderPath(metadata);
114
+
115
+ // extract SQL
116
+ const codeArr = [
117
+ {
118
+ subFolder: null,
119
+ fileName: metadata[this.definition.keyField],
120
+ fileExt: 'sql',
121
+ content: metadata.queryText,
122
+ },
123
+ ];
124
+ delete metadata.queryText;
125
+
126
+ try {
127
+ if (metadata.targetId) {
128
+ // overwrite targetKey via targetId (it's not updated on name/key change of the DE)
129
+ const targetKey = cache.searchForField(
130
+ 'dataExtension',
131
+ metadata.targetId,
132
+ 'ObjectID',
133
+ 'CustomerKey'
134
+ );
135
+ if (targetKey !== metadata.targetKey) {
136
+ Util.logger.debug(
137
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
138
+ metadata[this.definition.keyField]
139
+ }): Replacing targetKey value in saved JSON '${
140
+ metadata.targetKey
141
+ }' --> '${targetKey}'. Acquired new value from looking up the DE's ObjectID in targetId.`
142
+ );
143
+ metadata.targetKey = targetKey;
144
+ }
145
+ } else {
146
+ // if no targetId is set, at least check if the targetKey points to an existing DE (no override needed)
147
+ cache.searchForField(
148
+ 'dataExtension',
149
+ metadata.targetKey,
150
+ 'CustomerKey',
151
+ 'CustomerKey'
152
+ );
153
+ }
154
+ } catch (ex) {
155
+ Util.logger.warn(
156
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
157
+ metadata[this.definition.keyField]
158
+ }): ${ex.message}.`
159
+ );
160
+ }
161
+ delete metadata.targetId;
162
+
163
+ return { json: metadata, codeArr: codeArr, subFolder: null };
97
164
  }
98
165
 
99
166
  /**
@@ -126,20 +193,32 @@ class Query extends MetadataType {
126
193
  * @returns {Promise.<TYPE.QueryItem>} Promise
127
194
  */
128
195
  static async preDeployTasks(metadata, deployDir) {
196
+ // folder
197
+ super.setFolderId(metadata);
198
+
199
+ // reinject queryText
129
200
  metadata.queryText = await File.readFilteredFilename(
130
201
  deployDir + '/' + this.definition.type,
131
202
  metadata[this.definition.keyField] + '.' + this.definition.type + '-meta',
132
203
  'sql'
133
204
  );
205
+
206
+ // dataExtension
134
207
  metadata.targetKey = cache.searchForField(
135
208
  'dataExtension',
136
209
  metadata.targetKey,
137
210
  'CustomerKey',
138
211
  'CustomerKey'
139
212
  );
140
- // folder
141
- super.setFolderId(metadata);
213
+ // we've seen queries without this ID set - crucial in case the DE ever gets renamed to ensure the query keeps working
214
+ metadata.targetId = cache.searchForField(
215
+ 'dataExtension',
216
+ metadata.targetKey,
217
+ 'CustomerKey',
218
+ 'ObjectID'
219
+ );
142
220
 
221
+ // set ID for Append / Overwrite/ Update action
143
222
  metadata.targetUpdateTypeId =
144
223
  this.definition.targetUpdateTypeMapping[metadata.targetUpdateTypeName];
145
224
  return metadata;
@@ -287,29 +366,6 @@ class Query extends MetadataType {
287
366
  return nestedFilePaths;
288
367
  }
289
368
 
290
- /**
291
- * parses retrieved Metadata before saving
292
- *
293
- * @param {TYPE.QueryItem} metadata a single query activity definition
294
- * @returns {TYPE.CodeExtractItem} a single item with code parts extracted
295
- */
296
- static parseMetadata(metadata) {
297
- // folder
298
- super.setFolderPath(metadata);
299
-
300
- // extract SQL
301
- const codeArr = [
302
- {
303
- subFolder: null,
304
- fileName: metadata[this.definition.keyField],
305
- fileExt: 'sql',
306
- content: metadata.queryText,
307
- },
308
- ];
309
- delete metadata.queryText;
310
-
311
- return { json: metadata, codeArr: codeArr, subFolder: null };
312
- }
313
369
  /**
314
370
  * should return only the json for all but asset, query and script that are saved as multiple files
315
371
  * additionally, the documentation for dataExtension and automation should be returned
@@ -344,6 +400,32 @@ class Query extends MetadataType {
344
400
  return errors.map((msg) => msg.split('Error saving the Query field.').join(''));
345
401
  }
346
402
  }
403
+ /**
404
+ * Delete a metadata item from the specified business unit
405
+ *
406
+ * @param {string} customerKey Identifier of data extension
407
+ * @returns {boolean} deletion success status
408
+ */
409
+ static async deleteByKey(customerKey) {
410
+ // delete only works with the query's object id
411
+ const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
412
+ if (!objectId) {
413
+ Util.logger.error(` - query not found`);
414
+ return false;
415
+ }
416
+ return super.deleteByKeyREST('/automation/v1/queries/' + objectId, customerKey);
417
+ }
418
+ /**
419
+ * clean up after deleting a metadata item
420
+ *
421
+ * @param {string} customerKey Identifier of metadata item
422
+ * @returns {void}
423
+ */
424
+ static async postDeleteTasks(customerKey) {
425
+ // delete local copy: retrieve/cred/bu/.../...-meta.json
426
+ // delete local copy: retrieve/cred/bu/.../...-meta.sql
427
+ await super.postDeleteTasks(customerKey, [`${this.definition.type}-meta.sql`]);
428
+ }
347
429
  }
348
430
 
349
431
  // Assign definition & cache to static attributes