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
package/test/utils.js CHANGED
@@ -17,10 +17,11 @@ const resourceFactory = require('./resourceFactory');
17
17
  *
18
18
  * @param {string} customerKey of metadata
19
19
  * @param {string} type of metadata
20
+ * @param {string} [buName] used when we need to test on ParentBU
20
21
  * @returns {Promise.<string>} file in string form
21
22
  */
22
- exports.getActualJson = (customerKey, type) =>
23
- File.readJSON(`./retrieve/testInstance/testBU/${type}/${customerKey}.${type}-meta.json`);
23
+ exports.getActualJson = (customerKey, type, buName = 'testBU') =>
24
+ File.readJSON(`./retrieve/testInstance/${buName}/${type}/${customerKey}.${type}-meta.json`);
24
25
  /**
25
26
  * gets file from Retrieve folder
26
27
  *
@@ -36,10 +37,11 @@ exports.getActualFile = (customerKey, type, ext) =>
36
37
  *
37
38
  * @param {string} customerKey of metadata
38
39
  * @param {string} type of metadata
40
+ * @param {string} [buName] used when we need to test on ParentBU
39
41
  * @returns {Promise.<string>} file in string form
40
42
  */
41
- exports.getActualDeployJson = (customerKey, type) =>
42
- File.readJSON(`./deploy/testInstance/testBU/${type}/${customerKey}.${type}-meta.json`);
43
+ exports.getActualDeployJson = (customerKey, type, buName = 'testBU') =>
44
+ File.readJSON(`./deploy/testInstance/${buName}/${type}/${customerKey}.${type}-meta.json`);
43
45
  /**
44
46
  * gets file from Deploy folder
45
47
  *
@@ -143,6 +145,9 @@ exports.mockSetup = (isDeploy) => {
143
145
  fsMockConf.deploy = fsmock.load(path.resolve(__dirname, 'mockRoot/deploy'));
144
146
  }
145
147
  fsmock(fsMockConf);
148
+
149
+ // ! reset exitCode or else tests could influence each other; do this in mockSetup to to ensure correct starting value
150
+ process.exitCode = 0;
146
151
  };
147
152
 
148
153
  /**
@@ -151,6 +156,13 @@ exports.mockSetup = (isDeploy) => {
151
156
  * @returns {void}
152
157
  */
153
158
  exports.mockReset = () => {
159
+ // remove all options that might have been set by previous tests
160
+ for (const key in Util.OPTIONS) {
161
+ if (Object.prototype.hasOwnProperty.call(Util.OPTIONS, key)) {
162
+ delete Util.OPTIONS[key];
163
+ }
164
+ }
165
+ // reset sfmc login
154
166
  auth.clearSessions();
155
167
  fsmock.restore();
156
168
  apimock.restore();
@@ -202,5 +214,5 @@ exports.logAPIHistoryDebug = () => {
202
214
  * @returns {string} escaped string
203
215
  */
204
216
  function escapeRegExp(str) {
205
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
217
+ return str.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
206
218
  }
package/types/mcdev.d.js CHANGED
@@ -12,7 +12,7 @@ const SDK = require('sfmc-sdk');
12
12
  */
13
13
  /**
14
14
  * @typedef {Object.<string, string>} TemplateMap
15
- * @typedef {'accountUser'|'asset'|'asset-archive'|'asset-asset'|'asset-audio'|'asset-block'|'asset-code'|'asset-document'|'asset-image'|'asset-message'|'asset-other'|'asset-rawimage'|'asset-template'|'asset-textfile'|'asset-video'|'attributeGroup'|'automation'|'campaign'|'contentArea'|'dataExtension'|'dataExtensionField'|'dataExtensionTemplate'|'dataExtract'|'dataExtractType'|'discovery'|'email'|'emailSendDefinition'|'eventDefinition'|'fileTransfer'|'filter'|'folder'|'ftpLocation'|'importFile'|'interaction'|'list'|'mobileCode'|'mobileKeyword'|'query'|'role'|'script'|'setDefinition'|'triggeredSendDefinition'} SupportedMetadataTypes
15
+ * @typedef {'accountUser'|'asset'|'asset-archive'|'asset-asset'|'asset-audio'|'asset-block'|'asset-code'|'asset-document'|'asset-image'|'asset-message'|'asset-other'|'asset-rawimage'|'asset-template'|'asset-textfile'|'asset-video'|'attributeGroup'|'automation'|'campaign'|'contentArea'|'dataExtension'|'dataExtensionField'|'dataExtensionTemplate'|'dataExtract'|'dataExtractType'|'discovery'|'email'|'emailSend'|'event'|'fileTransfer'|'filter'|'folder'|'fileLocation'|'importFile'|'interaction'|'list'|'mobileCode'|'mobileKeyword'|'query'|'role'|'script'|'setDefinition'|'triggeredSend'} SupportedMetadataTypes
16
16
  * @typedef {Object.<SupportedMetadataTypes, string[]>} TypeKeyCombo object-key=metadata type, value=array of external keys
17
17
  */
18
18
 
@@ -21,10 +21,10 @@ const SDK = require('sfmc-sdk');
21
21
  * @typedef {Object.<string, MetadataTypeItem>} MetadataTypeMap key=customer key
22
22
  * @typedef {Object.<string, MetadataTypeMap>} MultiMetadataTypeMap key=Supported MetadataType
23
23
  * @typedef {Object.<string, MetadataTypeItem[]>} MultiMetadataTypeList key=Supported MetadataType
24
- * @typedef {{metadata:MetadataTypeMap,type:SupportedMetadataTypes}} MetadataTypeMapObj
25
- * @typedef {{metadata:MetadataTypeItem,type:SupportedMetadataTypes}} MetadataTypeItemObj
24
+ * @typedef {{metadata: MetadataTypeMap, type: SupportedMetadataTypes}} MetadataTypeMapObj
25
+ * @typedef {{metadata: MetadataTypeItem, type: SupportedMetadataTypes}} MetadataTypeItemObj
26
26
  * @typedef {Object.<number, MultiMetadataTypeMap>} Cache key=MID
27
- * @typedef {{before: TYPE.MetadataTypeItem, after: TYPE.MetadataTypeItem}} MetadataTypeItemDiff used during update
27
+ * @typedef {{before: MetadataTypeItem, after: MetadataTypeItem}} MetadataTypeItemDiff used during update
28
28
  */
29
29
 
30
30
  /**
@@ -119,24 +119,57 @@ const SDK = require('sfmc-sdk');
119
119
  * @typedef {Object.<string, DataExtensionItem>} DataExtensionMap
120
120
  */
121
121
  /**
122
- * @typedef {object} AccountUserDocument
123
- * @property {string} TYPE user.type__c
124
- * @property {string} UserID user.UserID
125
- * @property {string} AccountUserID user.AccountUserID
122
+ * @typedef {object} UserDocument
123
+ * @property {string} [ID] equal to UserID; optional in update/create calls
124
+ * @property {string} UserID equal to ID; required in update/create calls
125
+ * @property {number} [AccountUserID] user.AccountUserID
126
+ * @property {number} c__AccountUserID copy of AccountUserID
126
127
  * @property {string} CustomerKey user.CustomerKey
127
128
  * @property {string} Name user.Name
128
129
  * @property {string} Email user.Email
129
130
  * @property {string} NotificationEmailAddress user.NotificationEmailAddress
130
- * @property {string} ActiveFlag user.ActiveFlag === true ? '✓' : '-'
131
- * @property {string} IsAPIUser user.IsAPIUser === true ? '✓' : '-'
132
- * @property {string} MustChangePassword user.MustChangePassword === true ? '✓' : '-'
133
- * @property {string} DefaultBusinessUnit defaultBUName
134
- * @property {string} AssociatedBusinessUnits__c associatedBus
135
- * @property {string} Roles roles
136
- * @property {string} UserPermissions userPermissions
131
+ * @property {boolean} ActiveFlag user.ActiveFlag === true ? '✓' : '-'
132
+ * @property {boolean} IsAPIUser user.IsAPIUser === true ? '✓' : '-'
133
+ * @property {boolean} MustChangePassword user.MustChangePassword === true ? '✓' : '-'
134
+ * @property {number} DefaultBusinessUnit defaultBUName
135
+ * @property {number[]} c__AssociatedBusinessUnits associatedBus
136
+ * @property {object} [Roles] (API only)
137
+ * @property {object[]} [Roles.Role] roles (API only)
138
+ * @property {string[]} c__RoleNamesGlobal roles
139
+ * @property {string[]} UserPermissions userPermissions
137
140
  * @property {string} LastSuccessfulLogin this.timeSinceDate(user.LastSuccessfulLogin)
138
141
  * @property {string} CreatedDate user.CreatedDate
139
142
  * @property {string} ModifiedDate user.ModifiedDate
143
+ * @property {object} Client -
144
+ * @property {number} [Client.ID] EID e.g:7281698
145
+ * @property {number} Client.ModifiedBy AccountUserID of user who last modified this user
146
+ * @property {'User'|'Installed Package'} c__type -
147
+ * @property {boolean} [IsLocked] (API only)
148
+ * @property {boolean} [Unlock] used to unlock a user that has IsLocked === true
149
+ * @property {boolean} c__IsLocked_readOnly copy of IsLocked
150
+ * @property {string} c__TimeZoneName name of timezone
151
+ * @property {object} [TimeZone] (API only)
152
+ * @property {string} [TimeZone.Name] (API only)
153
+ * @property {string} [TimeZone.ID] (API only)
154
+ * @property {'en-US'|'fr-CA'|'fr-FR'|'de-DE'|'it-IT'|'ja-JP'|'pt-BR'|'es-419'|'es-ES'} c__LocaleCode fr-CA, en-US, ...
155
+ * @property {object} [Locale] (API only)
156
+ * @property {'en-US'|'fr-CA'|'fr-FR'|'de-DE'|'it-IT'|'ja-JP'|'pt-BR'|'es-419'|'es-ES'} [Locale.LocaleCode] (API only)
157
+ * @typedef {{before:UserDocument,after:UserDocument}} UserDocumentDiff
158
+ * @typedef {Object.<string, UserDocument>} UserDocumentMap key=customer key
159
+ */
160
+ /**
161
+ * @typedef {object} AccountUserConfiguration
162
+ * @property {object} Client wrapper
163
+ * @property {number} Client.ID EID e.g:7281698
164
+ * @property {string} [PartnerKey] empty string
165
+ * @property {number} ID User ID e.g:717133502
166
+ * @property {string} [ObjectID] empty string
167
+ * @property {number} [Delete] 0,1
168
+ * @property {BusinessUnitAssignmentConfiguration} BusinessUnitAssignmentConfiguration -
169
+ * @typedef {object} BusinessUnitAssignmentConfiguration
170
+ * @property {object} BusinessUnitIds wrapper
171
+ * @property {number[]|number} BusinessUnitIds.BusinessUnitId e.g:[518003624]
172
+ * @property {boolean} IsDelete assign BU if false, remove assignment if true
140
173
  */
141
174
 
142
175
  /**
@@ -1,57 +0,0 @@
1
- # For most projects, this workflow file will not need changing; you simply need
2
- # to commit it to your repository.
3
- #
4
- # You may wish to alter this file to override the set of languages analyzed,
5
- # or to provide custom queries or build logic.
6
- #
7
- # ******** NOTE ********
8
- # We have attempted to detect the languages in your repository. Please check
9
- # the `language` matrix defined below to confirm you have the correct set of
10
- # supported CodeQL languages.
11
- #
12
- name: 'CodeAnalysis'
13
-
14
- on:
15
- push:
16
- branches: [main, develop, hotfix]
17
- pull_request:
18
- # The branches below must be a subset of the branches above
19
- branches: [main, develop, hotfix]
20
-
21
- jobs:
22
- analyzeAndTest:
23
- name: Analyze & Test
24
- runs-on: ubuntu-latest
25
- permissions:
26
- actions: read
27
- contents: read
28
- security-events: write
29
-
30
- strategy:
31
- fail-fast: false
32
- matrix:
33
- language: ['javascript']
34
-
35
- steps:
36
- - name: Checkout repository
37
- uses: actions/checkout@v3
38
-
39
- - uses: actions/setup-node@v3
40
- with:
41
- node-version: 16
42
- registry-url: https://registry.npmjs.org/
43
-
44
- - run: npm ci --ignore-scripts
45
-
46
- - run: npm run lint
47
-
48
- # Initializes the CodeQL tools for scanning.
49
- - name: Initialize CodeQL
50
- uses: github/codeql-action/init@v2
51
- with:
52
- languages: ${{ matrix.language }}
53
- - name: Perform CodeQL Analysis
54
- uses: github/codeql-action/analyze@v2
55
-
56
- # Assuming code passes, run tests
57
- - run: npm run test
@@ -1,426 +0,0 @@
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
-
8
- /**
9
- * MessageSendActivity MetadataType
10
- *
11
- * @augments MetadataType
12
- */
13
- class AccountUser extends MetadataType {
14
- /**
15
- * Retrieves SOAP based metadata of metadata type into local filesystem. executes callback with retrieved metadata
16
- *
17
- * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
18
- * @param {void} _ unused parameter
19
- * @param {void} [__] unused parameter
20
- * @param {string} [key] customer key of single item to retrieve
21
- * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
22
- */
23
- static async retrieve(retrieveDir, _, __, key) {
24
- if (this.buObject.eid !== this.buObject.mid) {
25
- Util.logger.info(' - Skipping User retrieval on non-parent BU');
26
- return;
27
- }
28
- return this._retrieve(retrieveDir, key);
29
- }
30
- /**
31
- * Retrieves SOAP based metadata of metadata type into local filesystem. executes callback with retrieved metadata
32
- *
33
- * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
34
- */
35
- static async retrieveChangelog() {
36
- return this._retrieve();
37
- }
38
- /**
39
- * Retrieves SOAP based metadata of metadata type into local filesystem. executes callback with retrieved metadata
40
- *
41
- * @private
42
- * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
43
- * @param {string} [key] customer key of single item to retrieve
44
- * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
45
- */
46
- static async _retrieve(retrieveDir, key) {
47
- Util.logger.info(' - Caching dependent Metadata: AccountUserAccount');
48
-
49
- // get BUs that each users have access to
50
- const optionsBUs = {};
51
-
52
- const resultsBatch = (
53
- await this.client.soap.retrieveBulk(
54
- 'AccountUserAccount',
55
- ['AccountUser.AccountUserID', 'AccountUser.UserID', 'Account.ID', 'Account.Name'],
56
- optionsBUs
57
- )
58
- ).Results;
59
- this.userIdBuMap = {};
60
- for (const item of resultsBatch) {
61
- this.userIdBuMap[item.AccountUser.AccountUserID] =
62
- this.userIdBuMap[item.AccountUser.AccountUserID] || [];
63
- // push to array if not already in array
64
- if (
65
- !this.userIdBuMap[item.AccountUser.AccountUserID].some(
66
- (bu) => bu.ID === item.Account.ID
67
- )
68
- ) {
69
- this.userIdBuMap[item.AccountUser.AccountUserID].push({
70
- ID: item.Account.ID,
71
- Name: item.Account.Name,
72
- });
73
- }
74
- }
75
- // get actual user details
76
- /** @type {TYPE.SoapRequestParams} */
77
- let requestParams = {
78
- QueryAllAccounts: true,
79
-
80
- filter: {
81
- leftOperand: {
82
- // normal users
83
- leftOperand: 'Email',
84
- operator: 'like',
85
- rightOperand: '@',
86
- },
87
- operator: 'OR',
88
- rightOperand: {
89
- // installed packages
90
- leftOperand: {
91
- leftOperand: 'Name',
92
- operator: 'like',
93
- rightOperand: ' app user', // ! will not work if the name was too long as "app user" might be cut off
94
- },
95
- operator: 'AND',
96
- rightOperand: {
97
- // this is used to filter out system generated installed packages. in our testing, at least those installed packages created in the last few years have hat set this to false while additional (hidden) installed packages have it set to true.
98
- leftOperand: 'MustChangePassword',
99
- operator: 'equals',
100
- rightOperand: 'false',
101
- },
102
- },
103
- },
104
- };
105
- if (key) {
106
- // move original filter down one level into rightOperand and add key filter into leftOperand
107
- requestParams = {
108
- filter: {
109
- leftOperand: {
110
- leftOperand: 'CustomerKey',
111
- operator: 'equals',
112
- rightOperand: key,
113
- },
114
- operator: 'AND',
115
- rightOperand: requestParams.filter,
116
- },
117
- };
118
- }
119
- Util.logger.info(` - Loading ${this.definition.type}. This might take a while...`);
120
- return super.retrieveSOAP(retrieveDir, requestParams);
121
- }
122
- /**
123
- *
124
- * @private
125
- * @param {string} date first date
126
- * @returns {number} time difference
127
- */
128
- static _timeSinceDate(date) {
129
- const interval = 'days';
130
- const second = 1000,
131
- minute = second * 60,
132
- hour = minute * 60,
133
- day = hour * 24,
134
- week = day * 7;
135
- date = new Date(date);
136
- const now = new Date();
137
- const timediff = now - date;
138
- if (Number.isNaN(timediff)) {
139
- return Number.NaN;
140
- }
141
- let result;
142
- switch (interval) {
143
- case 'years': {
144
- result = now.getFullYear() - date.getFullYear();
145
- break;
146
- }
147
- case 'months': {
148
- result =
149
- now.getFullYear() * 12 +
150
- now.getMonth() -
151
- (date.getFullYear() * 12 + date.getMonth());
152
- break;
153
- }
154
- case 'weeks': {
155
- result = Math.floor(timediff / week);
156
- break;
157
- }
158
- case 'days': {
159
- result = Math.floor(timediff / day);
160
- break;
161
- }
162
- case 'hours': {
163
- result = Math.floor(timediff / hour);
164
- break;
165
- }
166
- case 'minutes': {
167
- result = Math.floor(timediff / minute);
168
- break;
169
- }
170
- case 'seconds': {
171
- result = Math.floor(timediff / second);
172
- break;
173
- }
174
- default: {
175
- return;
176
- }
177
- }
178
- return result + ' ' + interval;
179
- }
180
- /**
181
- * helper to print bu names
182
- *
183
- * @private
184
- * @param {number} id bu id
185
- * @returns {string} "bu name (bu id)""
186
- */
187
- static _getBuName(id) {
188
- const name = this.buObject.eid == id ? '_ParentBU_' : this.buIdName[id];
189
- return `<nobr>${name} (${id})</nobr>`;
190
- }
191
-
192
- /**
193
- * helper that gets BU names from config
194
- *
195
- * @private
196
- */
197
- static _getBuNames() {
198
- this.buIdName = {};
199
- for (const cred in this.properties.credentials) {
200
- for (const buName in this.properties.credentials[cred].businessUnits) {
201
- this.buIdName[this.properties.credentials[cred].businessUnits[buName]] = buName;
202
- }
203
- }
204
- }
205
- /**
206
- * Creates markdown documentation of all roles
207
- *
208
- * @param {TYPE.MetadataTypeMap} [metadata] user list
209
- * @returns {Promise.<void>} -
210
- */
211
- static async document(metadata) {
212
- if (this.buObject.eid !== this.buObject.mid) {
213
- Util.logger.error(
214
- `Users can only be retrieved & documented for the ${Util.parentBuName}`
215
- );
216
- return;
217
- }
218
- if (!metadata) {
219
- // load users from disk if document was called directly and not part of a retrieve
220
- try {
221
- metadata = this.readBUMetadataForType(
222
- File.normalizePath([
223
- this.properties.directories.retrieve,
224
- this.buObject.credential,
225
- Util.parentBuName,
226
- ]),
227
- true
228
- ).accountUser;
229
- } catch (ex) {
230
- Util.logger.error(ex.message);
231
- return;
232
- }
233
- }
234
- // init map of BU Ids > BU Name
235
- this._getBuNames();
236
-
237
- // initialize permission object
238
- this.allPermissions = {};
239
- /**
240
- * @type {TYPE.AccountUserDocument[]}
241
- */
242
- const users = [];
243
- // traverse all permissions recursively and write them into allPermissions object once it has reached the end
244
- for (const id in metadata) {
245
- const user = metadata[id];
246
- // TODO resolve user permissions to something readable
247
- let userPermissions = '';
248
- if (user.UserPermissions) {
249
- if (!user.UserPermissions.length) {
250
- // 1 single user permission found, normalize it
251
- user.UserPermissions = [user.UserPermissions];
252
- }
253
- userPermissions = user.UserPermissions.map((item) => item.ID * 1)
254
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
255
- .join(', ');
256
- }
257
- // user roles
258
- // TODO think about what to do with "individual role" entries
259
- let roles = '';
260
- if (user.Roles) {
261
- roles =
262
- '<nobr>' +
263
- user.Roles.map((item) => item.Name)
264
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
265
- .join(',</nobr><br> <nobr>') +
266
- '</nobr>';
267
- }
268
- let associatedBus = '';
269
- if (user.AssociatedBusinessUnits__c) {
270
- user.AssociatedBusinessUnits__c.push({
271
- ID: user.DefaultBusinessUnit,
272
- });
273
- // ensure associatedBus have no duplicates
274
- associatedBus = [
275
- ...new Set(
276
- user.AssociatedBusinessUnits__c.map((item) => this._getBuName(item.ID))
277
- ),
278
- ]
279
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
280
- .join(',<br> ');
281
- }
282
- const defaultBUName = this._getBuName(user.DefaultBusinessUnit);
283
- users.push({
284
- TYPE: user.type__c,
285
- UserID: user.UserID,
286
- AccountUserID: user.AccountUserID,
287
- CustomerKey: user.CustomerKey,
288
- Name: user.Name,
289
- Email: user.Email,
290
- NotificationEmailAddress: user.NotificationEmailAddress,
291
- ActiveFlag: user.ActiveFlag === true ? '✓' : '-',
292
- IsAPIUser: user.IsAPIUser === true ? '✓' : '-',
293
- MustChangePassword: user.MustChangePassword === true ? '✓' : '-',
294
- DefaultBusinessUnit: defaultBUName,
295
- AssociatedBusinessUnits__c: associatedBus,
296
- Roles: roles,
297
- UserPermissions: userPermissions,
298
- LastSuccessfulLogin: this._timeSinceDate(user.LastSuccessfulLogin),
299
- CreatedDate: user.CreatedDate.split('T').join(' '),
300
- ModifiedDate: user.ModifiedDate.split('T').join(' '),
301
- });
302
- }
303
- users.sort((a, b) => (a.Name < b.Name ? -1 : a.Name > b.Name ? 1 : 0));
304
- const columnsToPrint = [
305
- ['Name', 'Name'],
306
- ['Last successful Login', 'LastSuccessfulLogin'],
307
- ['Active', 'ActiveFlag'],
308
- ['API User', 'IsAPIUser'],
309
- ['Must change PW', 'MustChangePassword'],
310
- ['Default BU', 'DefaultBusinessUnit'],
311
- ['BU Access', 'AssociatedBusinessUnits__c'],
312
- ['Roles', 'Roles'],
313
- ['User Permissions', 'UserPermissions'],
314
- ['Login', 'UserID'],
315
- ['ID', 'AccountUserID'],
316
- ['Key', 'CustomerKey'],
317
- ['E-Mail', 'Email'],
318
- ['Notification E-Mail', 'NotificationEmailAddress'],
319
- ['Modified Date', 'ModifiedDate'],
320
- ['Created Date', 'CreatedDate'],
321
- ];
322
- let output = `# User Overview - ${this.buObject.credential}`;
323
- output += this._generateDocMd(
324
- users.filter((user) => user.TYPE === 'User' && user.ActiveFlag === '✓'),
325
- 'User',
326
- columnsToPrint
327
- );
328
- output += this._generateDocMd(
329
- users.filter((user) => user.TYPE === 'User' && user.ActiveFlag === '-'),
330
- 'Inactivated User',
331
- columnsToPrint
332
- );
333
- output += this._generateDocMd(
334
- users.filter((user) => user.TYPE === 'Installed Package'),
335
- 'Installed Package',
336
- columnsToPrint
337
- );
338
- const docPath = File.normalizePath([this.properties.directories.docs, 'user']);
339
-
340
- try {
341
- const filename = this.buObject.credential;
342
- // write to disk
343
- await File.writeToFile(docPath, filename + '.accountUsers', 'md', output);
344
- Util.logger.info(`Created ${File.normalizePath([docPath, filename])}.accountUsers.md`);
345
- if (['html', 'both'].includes(this.properties.options.documentType)) {
346
- Util.logger.warn(
347
- ' - HTML-based documentation of accountUser currently not supported.'
348
- );
349
- }
350
- } catch (ex) {
351
- Util.logger.error(`AccountUser.document():: error | `, ex.message);
352
- }
353
- }
354
- /**
355
- *
356
- * @private
357
- * @param {object[]} users list of users and installed package
358
- * @param {'Installed Package'|'User'} type choose what sub type to print
359
- * @param {Array[]} columnsToPrint helper array
360
- * @returns {string} markdown
361
- */
362
- static _generateDocMd(users, type, columnsToPrint) {
363
- let output = `\n\n## ${type}s (${users.length})\n\n`;
364
- let tableSeparator = '';
365
- for (const column of columnsToPrint) {
366
- output += `| ${column[0]} `;
367
- tableSeparator += '| --- ';
368
- }
369
- output += `|\n${tableSeparator}|\n`;
370
- for (const user of users) {
371
- for (const column of columnsToPrint) {
372
- output += `| ${user[column[1]]} `;
373
- }
374
- output += `|\n`;
375
- }
376
- return output;
377
- }
378
-
379
- /**
380
- * manages post retrieve steps
381
- *
382
- * @param {TYPE.MetadataTypeItem} metadata a single query
383
- * @returns {TYPE.MetadataTypeItem} Array with one metadata object and one query string
384
- */
385
- static postRetrieveTasks(metadata) {
386
- return this.parseMetadata(metadata);
387
- }
388
- /**
389
- * parses retrieved Metadata before saving
390
- *
391
- * @param {TYPE.MetadataTypeItem} metadata a single query activity definition
392
- * @returns {TYPE.MetadataTypeItem} Array with one metadata object and one sql string
393
- */
394
- static parseMetadata(metadata) {
395
- metadata.type__c = 'Installed Package';
396
- if (metadata.Email.includes('@') && !metadata.Name.endsWith('app user')) {
397
- metadata.type__c = 'User';
398
- }
399
-
400
- metadata.AssociatedBusinessUnits__c = this.userIdBuMap[metadata.ID] || [];
401
-
402
- let roles;
403
- if (metadata.Roles?.Role) {
404
- // normalize to always use array
405
- if (!metadata.Roles.Role.length) {
406
- metadata.Roles.Role = [metadata.Roles.Role];
407
- }
408
- // convert complex object into basic set of info
409
- roles = metadata.Roles.Role.map((item) => ({
410
- Name: item.Name,
411
- CustomerKey: item.CustomerKey,
412
- })).sort((a, b) => (a.Name < b.Name ? -1 : a.Name > b.Name ? 1 : 0));
413
- } else {
414
- // set to empty array
415
- roles = [];
416
- }
417
- metadata.Roles = roles;
418
-
419
- return metadata;
420
- }
421
- }
422
-
423
- // Assign definition to static attributes
424
- AccountUser.definition = require('../MetadataTypeDefinitions').accountUser;
425
-
426
- module.exports = AccountUser;