mcdev 4.3.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) 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/.fork/.prettierrc +6 -0
  5. package/.fork/custom-commands.json +13 -1
  6. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  7. package/.github/workflows/code-test.yml +36 -0
  8. package/.github/workflows/coverage-base-update.yml +57 -0
  9. package/.github/workflows/coverage-develop-branch.yml +41 -0
  10. package/.github/workflows/coverage-main-branch.yml +41 -0
  11. package/.github/workflows/coverage.yml +77 -0
  12. package/.husky/post-checkout +1 -1
  13. package/.prettierrc +1 -1
  14. package/.vscode/extensions.json +0 -4
  15. package/boilerplate/config.json +1 -1
  16. package/boilerplate/files/.prettierrc +1 -1
  17. package/boilerplate/files/.vscode/extensions.json +1 -1
  18. package/boilerplate/files/.vscode/settings.json +1 -0
  19. package/boilerplate/forcedUpdates.json +8 -0
  20. package/docs/dist/documentation.md +1261 -433
  21. package/lib/Builder.js +6 -1
  22. package/lib/Deployer.js +34 -10
  23. package/lib/MetadataTypeDefinitions.js +8 -6
  24. package/lib/MetadataTypeInfo.js +8 -6
  25. package/lib/Retriever.js +4 -1
  26. package/lib/cli.js +54 -42
  27. package/lib/index.js +82 -8
  28. package/lib/metadataTypes/Asset.js +185 -31
  29. package/lib/metadataTypes/AttributeGroup.js +0 -1
  30. package/lib/metadataTypes/Automation.js +48 -5
  31. package/lib/metadataTypes/Campaign.js +20 -7
  32. package/lib/metadataTypes/ContentArea.js +1 -1
  33. package/lib/metadataTypes/DataExtension.js +221 -184
  34. package/lib/metadataTypes/DataExtensionField.js +12 -19
  35. package/lib/metadataTypes/DataExtensionTemplate.js +1 -1
  36. package/lib/metadataTypes/DataExtract.js +1 -1
  37. package/lib/metadataTypes/DataExtractType.js +1 -1
  38. package/lib/metadataTypes/Email.js +1 -1
  39. package/lib/metadataTypes/{EmailSendDefinition.js → EmailSend.js} +5 -5
  40. package/lib/metadataTypes/{EventDefinition.js → Event.js} +17 -35
  41. package/lib/metadataTypes/{FtpLocation.js → FileLocation.js} +2 -2
  42. package/lib/metadataTypes/FileTransfer.js +8 -7
  43. package/lib/metadataTypes/Filter.js +1 -1
  44. package/lib/metadataTypes/Folder.js +8 -3
  45. package/lib/metadataTypes/ImportFile.js +6 -6
  46. package/lib/metadataTypes/{Interaction.js → Journey.js} +311 -147
  47. package/lib/metadataTypes/List.js +2 -2
  48. package/lib/metadataTypes/MetadataType.js +318 -90
  49. package/lib/metadataTypes/MobileCode.js +0 -1
  50. package/lib/metadataTypes/MobileKeyword.js +336 -40
  51. package/lib/metadataTypes/MobileMessage.js +473 -0
  52. package/lib/metadataTypes/Query.js +114 -32
  53. package/lib/metadataTypes/Role.js +60 -21
  54. package/lib/metadataTypes/Script.js +5 -7
  55. package/lib/metadataTypes/SendClassification.js +40 -0
  56. package/lib/metadataTypes/SetDefinition.js +1 -7
  57. package/lib/metadataTypes/TransactionalEmail.js +2 -3
  58. package/lib/metadataTypes/TransactionalMessage.js +1 -2
  59. package/lib/metadataTypes/TransactionalSMS.js +8 -15
  60. package/lib/metadataTypes/{TriggeredSendDefinition.js → TriggeredSend.js} +35 -27
  61. package/lib/metadataTypes/User.js +1177 -0
  62. package/lib/metadataTypes/definitions/Asset.definition.js +2 -4
  63. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +1 -0
  64. package/lib/metadataTypes/definitions/Automation.definition.js +3 -2
  65. package/lib/metadataTypes/definitions/Campaign.definition.js +79 -4
  66. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -0
  67. package/lib/metadataTypes/definitions/DataExtension.definition.js +2 -1
  68. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -0
  69. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -0
  70. package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -0
  71. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -0
  72. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -0
  73. package/lib/metadataTypes/definitions/Email.definition.js +1 -0
  74. package/lib/metadataTypes/definitions/{EmailSendDefinition.definition.js → EmailSend.definition.js} +4 -2
  75. package/lib/metadataTypes/definitions/{EventDefinition.definition.js → Event.definition.js} +2 -1
  76. package/lib/metadataTypes/definitions/{FtpLocation.definition.js → FileLocation.definition.js} +4 -3
  77. package/lib/metadataTypes/definitions/FileTransfer.definition.js +3 -2
  78. package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
  79. package/lib/metadataTypes/definitions/Folder.definition.js +2 -0
  80. package/lib/metadataTypes/definitions/ImportFile.definition.js +4 -3
  81. package/lib/metadataTypes/definitions/{Interaction.definition.js → Journey.definition.js} +11 -2
  82. package/lib/metadataTypes/definitions/List.definition.js +1 -0
  83. package/lib/metadataTypes/definitions/MobileCode.definition.js +3 -1
  84. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +27 -17
  85. package/lib/metadataTypes/definitions/MobileMessage.definition.js +743 -0
  86. package/lib/metadataTypes/definitions/Query.definition.js +3 -2
  87. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  88. package/lib/metadataTypes/definitions/Script.definition.js +1 -0
  89. package/lib/metadataTypes/definitions/SendClassification.definition.js +114 -0
  90. package/lib/metadataTypes/definitions/SetDefinition.definition.js +1 -0
  91. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  92. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +1 -0
  93. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +1 -0
  94. package/lib/metadataTypes/definitions/{TriggeredSendDefinition.definition.js → TriggeredSend.definition.js} +5 -3
  95. package/lib/metadataTypes/definitions/User.definition.js +365 -0
  96. package/lib/retrieveChangelog.js +1 -2
  97. package/lib/util/auth.js +29 -9
  98. package/lib/util/businessUnit.js +3 -3
  99. package/lib/util/cli.js +55 -7
  100. package/lib/util/devops.js +93 -8
  101. package/lib/util/file.js +55 -13
  102. package/lib/util/init.config.js +1 -2
  103. package/lib/util/init.npm.js +3 -3
  104. package/lib/util/util.js +68 -14
  105. package/package.json +16 -15
  106. package/test/general.test.js +62 -0
  107. package/test/mockRoot/.mcdevrc.json +7 -5
  108. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testBlocked_user.user-meta.json +23 -0
  109. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testExisting_user.user-meta.json +31 -0
  110. package/test/mockRoot/deploy/testInstance/_ParentBU_/user/testNew_user.user-meta.json +27 -0
  111. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{childBU_dataextension_test.dataExtension-meta.json → testExisting_dataExtension.dataExtension-meta.json} +2 -2
  112. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/{testDataExtension.dataExtension-meta.json → testNew_dataExtension.dataExtension-meta.json} +2 -2
  113. package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_interaction.interaction-meta.json +576 -0
  114. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp +2 -0
  115. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json +10 -0
  116. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp +2 -0
  117. package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json +10 -0
  118. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.amp +1 -0
  119. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/NTIzOjc4OjA.mobileMessage-meta.json +61 -0
  120. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.amp +1 -0
  121. package/test/mockRoot/deploy/testInstance/testBU/mobileMessage/new.mobileMessage-meta.json +60 -0
  122. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  123. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json +1 -1
  124. package/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql +1 -1
  125. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +1 -1
  126. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -1
  127. package/test/resourceFactory.js +13 -0
  128. package/test/resources/1111111/accountUser/configure-response.xml +70 -0
  129. package/test/resources/1111111/accountUser/create-response.xml +97 -0
  130. package/test/resources/1111111/accountUser/retrieve-response.xml +156 -0
  131. package/test/resources/1111111/accountUser/update-response.xml +111 -0
  132. package/test/resources/1111111/accountUserAccount/retrieve-response.xml +77 -0
  133. package/test/resources/1111111/platform/v1/setup/quickflow/data/get-response.json +455 -0
  134. package/test/resources/1111111/role/retrieve-response.xml +76 -0
  135. package/test/resources/1111111/user/build-expected.json +16 -0
  136. package/test/resources/1111111/user/create-expected.json +21 -0
  137. package/test/resources/1111111/user/retrieve-expected.json +24 -0
  138. package/test/resources/1111111/user/template-expected.json +16 -0
  139. package/test/resources/1111111/user/update-expected.json +21 -0
  140. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/delete-response.json +1 -0
  141. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +17 -0
  142. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +3 -3
  143. package/test/resources/9999999/automation/v1/queries/get-response.json +21 -4
  144. package/test/resources/9999999/automation/v1/queries/post-response.json +4 -4
  145. package/test/resources/9999999/data/v1/customobjectdata/key/{childBU_dataextension_test → testExisting_dataExtension}/rowset/get-response.json +1 -1
  146. package/test/resources/9999999/dataExtension/build-expected.json +3 -3
  147. package/test/resources/9999999/dataExtension/create-expected.json +2 -2
  148. package/test/resources/9999999/dataExtension/create-response.xml +8 -3
  149. package/test/resources/9999999/dataExtension/retrieve-expected.json +3 -3
  150. package/test/resources/9999999/dataExtension/retrieve-response.xml +9 -4
  151. package/test/resources/9999999/dataExtension/template-expected.json +3 -3
  152. package/test/resources/9999999/dataExtension/update-expected.json +3 -3
  153. package/test/resources/9999999/dataExtension/update-response.xml +9 -4
  154. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +14 -9
  155. package/test/resources/9999999/interaction/v1/interactions/get-response.json +312 -0
  156. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/get-response.json +312 -0
  157. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_interaction/put-response.json +592 -0
  158. package/test/resources/9999999/journey/build-expected.json +572 -0
  159. package/test/resources/9999999/journey/get-expected.json +576 -0
  160. package/test/resources/9999999/journey/put-expected.json +576 -0
  161. package/test/resources/9999999/journey/template-expected.json +572 -0
  162. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json +42 -0
  163. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow/delete-response.json +0 -0
  164. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -0
  165. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/post-response.json +3 -0
  166. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/delete-response.json +0 -0
  167. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json +106 -0
  168. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/post-response.json +0 -0
  169. package/test/resources/9999999/legacy/v1/beta/mobile/message/NTQ3Ojc4OjA/get-response.json +127 -0
  170. package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +129 -0
  171. package/test/resources/9999999/legacy/v1/beta/mobile/message/post-response.json +3 -0
  172. package/test/resources/9999999/legacy/v1/beta2/data/campaign/get-response.json +29 -0
  173. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  174. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +1 -1
  175. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +1 -1
  176. package/test/resources/9999999/mobileKeyword/build-expected.amp +2 -0
  177. package/test/resources/9999999/mobileKeyword/build-expected.json +9 -0
  178. package/test/resources/9999999/mobileKeyword/get-expected.amp +2 -0
  179. package/test/resources/9999999/mobileKeyword/get-expected.json +15 -0
  180. package/test/resources/9999999/mobileKeyword/post-create-expected.amp +2 -0
  181. package/test/resources/9999999/mobileKeyword/post-create-expected.json +17 -0
  182. package/test/resources/9999999/mobileKeyword/template-expected.amp +2 -0
  183. package/test/resources/9999999/mobileKeyword/template-expected.json +9 -0
  184. package/test/resources/9999999/mobileMessage/build-expected.amp +1 -0
  185. package/test/resources/9999999/mobileMessage/build-expected.json +60 -0
  186. package/test/resources/9999999/mobileMessage/get-expected.amp +1 -0
  187. package/test/resources/9999999/mobileMessage/get-expected.json +61 -0
  188. package/test/resources/9999999/mobileMessage/post-create-expected.amp +1 -0
  189. package/test/resources/9999999/mobileMessage/post-create-expected.json +63 -0
  190. package/test/resources/9999999/mobileMessage/post-update-expected.amp +1 -0
  191. package/test/resources/9999999/mobileMessage/post-update-expected.json +61 -0
  192. package/test/resources/9999999/mobileMessage/template-expected.amp +1 -0
  193. package/test/resources/9999999/mobileMessage/template-expected.json +60 -0
  194. package/test/resources/9999999/query/build-expected.json +1 -1
  195. package/test/resources/9999999/query/get-expected.json +1 -1
  196. package/test/resources/9999999/query/get2-expected.json +11 -0
  197. package/test/resources/9999999/query/patch-expected.json +1 -1
  198. package/test/resources/9999999/query/post-expected.json +1 -1
  199. package/test/resources/9999999/query/template-expected.json +1 -1
  200. package/test/resources/9999999/queryDefinition/retrieve-response.xml +30 -0
  201. package/test/resources/9999999/transactionalEmail/build-expected.json +5 -5
  202. package/test/resources/9999999/transactionalEmail/get-expected.json +1 -1
  203. package/test/resources/9999999/transactionalEmail/patch-expected.json +1 -1
  204. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  205. package/test/resources/9999999/transactionalEmail/template-expected.json +5 -5
  206. package/test/resources/9999999/transactionalPush/build-expected.json +2 -2
  207. package/test/resources/9999999/transactionalPush/template-expected.json +2 -2
  208. package/test/resources/9999999/transactionalSMS/build-expected.json +3 -3
  209. package/test/resources/9999999/transactionalSMS/template-expected.json +3 -3
  210. package/test/{dataExtension.test.js → type.dataExtension.test.js} +78 -21
  211. package/test/{interaction.test.js → type.journey.test.js} +64 -30
  212. package/test/type.mobileKeyword.test.js +250 -0
  213. package/test/type.mobileMessage.test.js +205 -0
  214. package/test/{query.test.js → type.query.test.js} +102 -5
  215. package/test/{transactionalEmail.test.js → type.transactionalEmail.test.js} +40 -2
  216. package/test/{transactionalPush.test.js → type.transactionalPush.test.js} +41 -2
  217. package/test/{transactionalSMS.test.js → type.transactionalSMS.test.js} +73 -3
  218. package/test/type.user.test.js +160 -0
  219. package/test/utils.js +17 -5
  220. package/types/mcdev.d.js +48 -15
  221. package/.github/workflows/code-analysis.yml +0 -57
  222. package/lib/metadataTypes/AccountUser.js +0 -426
  223. package/lib/metadataTypes/definitions/AccountUser.definition.js +0 -227
  224. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +0 -266
  225. package/test/resources/9999999/interaction/build-expected.json +0 -260
  226. package/test/resources/9999999/interaction/get-expected.json +0 -264
  227. package/test/resources/9999999/interaction/put-expected.json +0 -264
  228. package/test/resources/9999999/interaction/template-expected.json +0 -260
  229. package/test/resources/9999999/interaction/v1/interactions/put-response.json +0 -280
  230. /package/test/mockRoot/deploy/testInstance/testBU/{interaction → journey}/testNew_interaction.interaction-meta.json +0 -0
  231. /package/test/resources/9999999/{interaction → journey}/post-expected.json +0 -0
package/lib/util/cli.js CHANGED
@@ -8,6 +8,7 @@ const inquirer = require('inquirer');
8
8
  const MetadataDefinitions = require('./../MetadataTypeDefinitions');
9
9
  const Util = require('./util');
10
10
  const auth = require('./auth');
11
+ require('console.table');
11
12
 
12
13
  /**
13
14
  * CLI helper class
@@ -315,7 +316,7 @@ const Cli = {
315
316
  if (properties && properties.credentials[value]) {
316
317
  return `There already is an account with the name '${value}' in your config.`;
317
318
  }
318
- const converted = encodeURIComponent(value).replace(/[*]/g, '_STAR_');
319
+ const converted = encodeURIComponent(value).replaceAll(/[*]/g, '_STAR_');
319
320
  if (value != converted) {
320
321
  return 'Please do not use any special chars';
321
322
  }
@@ -526,15 +527,57 @@ const Cli = {
526
527
  /**
527
528
  * shows metadata type descriptions
528
529
  *
529
- * @returns {void}
530
+ * @returns {object[]} list of supported types with their apiNames
530
531
  */
531
532
  explainTypes() {
532
- // overwrites default layout of console.table
533
- require('console.table');
533
+ const MetadataTypeInfo = require('./../MetadataTypeInfo');
534
+ const TransactionalMessage = require('./../metadataTypes/TransactionalMessage');
535
+ const json = [];
536
+ const apiNameArr = Object.keys(MetadataDefinitions);
537
+
538
+ for (const apiName of apiNameArr) {
539
+ const details = MetadataDefinitions[apiName];
540
+ const supportCheckClass = apiName.startsWith('transactional')
541
+ ? TransactionalMessage
542
+ : MetadataTypeInfo[apiName];
543
+ json.push({
544
+ name: details.typeName,
545
+ apiName: details.type,
546
+ retrieveByDefault: details.typeRetrieveByDefault,
547
+ supports: {
548
+ retrieve: Object.prototype.hasOwnProperty.call(supportCheckClass, 'retrieve'),
549
+ create: Object.prototype.hasOwnProperty.call(supportCheckClass, 'create'),
550
+ update: Object.prototype.hasOwnProperty.call(supportCheckClass, 'update'),
551
+ delete: Object.prototype.hasOwnProperty.call(supportCheckClass, 'deleteByKey'),
552
+ changeKey:
553
+ supportCheckClass.definition.keyIsFixed === false &&
554
+ supportCheckClass.definition.keyField !==
555
+ supportCheckClass.definition.idField &&
556
+ supportCheckClass.definition.fields[supportCheckClass.definition.keyField]
557
+ .isUpdateable &&
558
+ Object.prototype.hasOwnProperty.call(supportCheckClass, 'update'),
559
+ buildTemplate: Object.prototype.hasOwnProperty.call(
560
+ supportCheckClass,
561
+ 'create'
562
+ ), // supported for all types that can be created
563
+ retrieveAsTemplate: Object.prototype.hasOwnProperty.call(
564
+ supportCheckClass,
565
+ 'retrieveAsTemplate'
566
+ ),
567
+ },
568
+ description: details.typeDescription,
569
+ });
570
+ }
571
+ if (Util.OPTIONS.json) {
572
+ if (Util.OPTIONS.loggerLevel !== 'error') {
573
+ console.log(json); // eslint-disable-line no-console
574
+ }
575
+ return json;
576
+ }
534
577
 
535
578
  const typeChoices = [];
536
579
  for (const el in MetadataDefinitions) {
537
- if (MetadataDefinitions[el].subTypes) {
580
+ if (MetadataDefinitions[el].subTypes && MetadataDefinitions[el].extendedSubTypes) {
538
581
  // used for assets to show whats available by default
539
582
  typeChoices.push({
540
583
  Name: MetadataDefinitions[el].typeName,
@@ -545,9 +588,11 @@ const Cli = {
545
588
  for (const subtype of MetadataDefinitions[el].subTypes) {
546
589
  lastCountdown--;
547
590
  const subTypeRetrieveByDefault =
591
+ Array.isArray(MetadataDefinitions[el].typeRetrieveByDefault) &&
548
592
  MetadataDefinitions[el].typeRetrieveByDefault.includes(subtype);
593
+
549
594
  const definition =
550
- ' ' + MetadataDefinitions[el].extendedSubTypes[subtype].join(', ');
595
+ ' ' + MetadataDefinitions[el].extendedSubTypes?.[subtype]?.join(', ');
551
596
  typeChoices.push({
552
597
  Name:
553
598
  MetadataDefinitions[el].typeName.replace('-[Subtype]', ': ') + subtype,
@@ -577,7 +622,10 @@ const Cli = {
577
622
  }
578
623
  return 0;
579
624
  });
580
- console.table(typeChoices); // eslint-disable-line no-console
625
+ if (Util.OPTIONS.loggerLevel !== 'error') {
626
+ console.table(typeChoices); // eslint-disable-line no-console
627
+ }
628
+ return json;
581
629
  },
582
630
  };
583
631
 
@@ -3,6 +3,7 @@ const File = require('./file');
3
3
  const path = require('node:path');
4
4
  const inquirer = require('inquirer');
5
5
  const Util = require('./util');
6
+ const Cli = require('./cli');
6
7
  const git = require('simple-git')();
7
8
  const Builder = require('../Builder');
8
9
  const MetadataType = require('../MetadataTypeInfo');
@@ -20,9 +21,10 @@ const DevOps = {
20
21
  * @param {string} [range] git commit range
21
22
  * @param {boolean} [saveToDeployDir] if true, copy metadata changes into deploy directory
22
23
  * @param {string} [filterPaths] filter file paths that start with any specified path (comma separated)
24
+ * @param {number} [commitHistory] cli option to override default commit history value in config
23
25
  * @returns {Promise.<TYPE.DeltaPkgItem[]>} -
24
26
  */
25
- async getDeltaList(properties, range, saveToDeployDir, filterPaths) {
27
+ async getDeltaList(properties, range, saveToDeployDir, filterPaths, commitHistory) {
26
28
  const rangeUserInput = range;
27
29
  filterPaths = filterPaths
28
30
  ? filterPaths.split(',').map((filePath) =>
@@ -46,7 +48,7 @@ const DevOps = {
46
48
  // Current commit is skipped due to no changes
47
49
  const commits = await git.log([
48
50
  '--skip=1',
49
- `-${properties.options.deployment.commitHistory || 10}`,
51
+ `-${commitHistory || properties.options.deployment.commitHistory || 10}`,
50
52
  ]);
51
53
  const display = commits.all.map((commit) => ({
52
54
  name: commit.date + ' / ' + commit.message + ' / ' + commit.author_name,
@@ -104,7 +106,7 @@ const DevOps = {
104
106
  }
105
107
 
106
108
  // get metadata type from file name
107
- file.type = path.basename(file.file).split('.')[1].split('-').shift();
109
+ file.type = file.file.split('/')[3];
108
110
 
109
111
  return file;
110
112
  })
@@ -130,7 +132,14 @@ const DevOps = {
130
132
  file.externalKey = null;
131
133
  file.name = path.basename(file.file).split('.').shift();
132
134
  } else {
133
- file.externalKey = path.basename(file.file).split('.').shift();
135
+ // find the key in paths like:
136
+ // - retrieve/cred/bu/asset/block/016aecc7-7063-4b78-93f4-aa119ea933c7.asset-block-meta.html
137
+ // - retrieve/cred/bu/asset/message/003c1ef5-f538-473a-91da-26942024a64a/blocks/views.html.slots.[bottom-8frq7iw2k99].asset-message-meta.html
138
+ // - retrieve/cred/bu/query/03efd5f1-ba1f-487a-9c9a-36aeb2ae5192.query-meta.sql
139
+ file.externalKey =
140
+ file.type === 'asset'
141
+ ? file.file.split('/')[5].split('.')[0] // assets have an additional folder level for their subtype
142
+ : file.file.split('/')[4].split('.')[0];
134
143
  file.name = null;
135
144
  }
136
145
 
@@ -207,8 +216,83 @@ const DevOps = {
207
216
  Util.logger.info(`Saved report in ${path.join(directoryDeltaPkg, 'delta_package.md')}`);
208
217
 
209
218
  // Copy filtered list of files into deploy directory
219
+ // only do that if we do not use templating
210
220
  if (saveToDeployDir) {
211
- /** @type {TYPE.DeltaPkgItem} */
221
+ // if templating is not used, we need to add related files to the delta package
222
+ const typeKeysMap = {};
223
+ /** @type {Object.<string, TYPE.BuObject>} */
224
+ const buObjects = {};
225
+ for (const file of delta) {
226
+ if (file.gitAction === 'delete' || file.type === 'folder') {
227
+ continue;
228
+ }
229
+ if (typeKeysMap[file.type]) {
230
+ typeKeysMap[file.type].push(file.externalKey);
231
+ } else {
232
+ typeKeysMap[file.type] = [file.externalKey];
233
+ }
234
+ if (!buObjects[`${file._credential}/${file._businessUnit}`]) {
235
+ buObjects[`${file._credential}/${file._businessUnit}`] =
236
+ await Cli.getCredentialObject(
237
+ properties,
238
+ `${file._credential}/${file._businessUnit}`
239
+ );
240
+ }
241
+ }
242
+ // a bit crude but works for now
243
+ for (const buObject of Object.values(buObjects)) {
244
+ for (const type in typeKeysMap) {
245
+ MetadataType[type].buObject = buObject;
246
+ MetadataType[type].properties = properties;
247
+ const additionalFiles = await MetadataType[type].getFilesToCommit(
248
+ typeKeysMap[type]
249
+ );
250
+ if (additionalFiles?.length) {
251
+ delta.push(
252
+ ...additionalFiles
253
+ .map((filePath) => ({
254
+ file: path.normalize(filePath).split('\\').join('/'),
255
+ type,
256
+ gitAction: 'add/update',
257
+ }))
258
+ .filter(
259
+ // avoid adding files that we already have in the list
260
+ (addFile) =>
261
+ !delta.find((existFile) => existFile.file === addFile.file)
262
+ )
263
+ );
264
+ }
265
+ }
266
+ }
267
+
268
+ let responses;
269
+ if (!Util.skipInteraction) {
270
+ // deploy folder is in targets for definition creation
271
+ // recommend to purge their content first
272
+ responses = await inquirer.prompt([
273
+ {
274
+ type: 'confirm',
275
+ name: 'isPurgeDeployFolder',
276
+ message:
277
+ 'Do you want to empty the deploy folder (ensures no files from previous deployments remain)?',
278
+ default: true,
279
+ },
280
+ ]);
281
+ }
282
+ if (Util.skipInteraction || responses.isPurgeDeployFolder) {
283
+ // Clear output folder structure for selected sub-type
284
+ for (const buObject of Object.values(buObjects)) {
285
+ await File.remove(
286
+ File.normalizePath([
287
+ properties.directories.deploy,
288
+ buObject.credential,
289
+ buObject.businessUnit,
290
+ ])
291
+ );
292
+ }
293
+ }
294
+
295
+ /** @type {TYPE.DeltaPkgItem[]} */
212
296
  const copied = delta.map((file) =>
213
297
  File.copyFile(
214
298
  file.file,
@@ -253,9 +337,10 @@ const DevOps = {
253
337
  * @param {TYPE.Mcdevrc} properties project config file
254
338
  * @param {string} range git commit range
255
339
  * @param {TYPE.DeltaPkgItem[]} [diffArr] instead of running git diff the method can also get a list of files to process
340
+ * @param {number} [commitHistory] cli option to override default commit history value in config
256
341
  * @returns {Promise.<TYPE.DeltaPkgItem[]>} -
257
342
  */
258
- async buildDeltaDefinitions(properties, range, diffArr) {
343
+ async buildDeltaDefinitions(properties, range, diffArr, commitHistory) {
259
344
  const skipInteraction = Util.skipInteraction;
260
345
  // check if sourceTargetMapping is valid
261
346
  if (
@@ -311,7 +396,7 @@ const DevOps = {
311
396
 
312
397
  const delta = Array.isArray(diffArr)
313
398
  ? diffArr
314
- : await DevOps.getDeltaList(properties, range, false, sourceBU);
399
+ : await DevOps.getDeltaList(properties, range, false, sourceBU, commitHistory);
315
400
  // If only chaing templating and buildDefinition if required
316
401
  if (!delta || delta.length === 0) {
317
402
  // info/error messages was printed by DevOps.createDeltaPkg() already
@@ -395,7 +480,7 @@ const DevOps = {
395
480
  },
396
481
  ]);
397
482
  }
398
- if (skipInteraction || responses.isPurgeDeployFolders) {
483
+ if (skipInteraction || responses.isPurgeDeployFolder) {
399
484
  // Clear output folder structure for selected sub-type
400
485
  await File.remove(File.normalizePath([properties.directories.deploy]));
401
486
  }
package/lib/util/file.js CHANGED
@@ -6,6 +6,7 @@ const fs = require('fs-extra');
6
6
  const packageJson = require('../../package.json');
7
7
  const path = require('node:path');
8
8
  const prettier = require('prettier');
9
+ const beautyAmp = require('beauty-amp-core');
9
10
  const Util = require('./util');
10
11
  const updateNotifier = require('update-notifier');
11
12
 
@@ -52,7 +53,7 @@ const File = {
52
53
  filterIllegalPathChars(path) {
53
54
  return (
54
55
  encodeURIComponent(path)
55
- .replace(/[*]/g, '_STAR_')
56
+ .replaceAll(/[*]/g, '_STAR_')
56
57
  // convert space back
57
58
  .split('%20')
58
59
  .join(' ')
@@ -74,6 +75,9 @@ const File = {
74
75
  // convert brackets back for asset blocks
75
76
  .split('%5D')
76
77
  .join(']')
78
+ // convert @ back for users
79
+ .split('%40')
80
+ .join('@')
77
81
  );
78
82
  },
79
83
 
@@ -86,7 +90,7 @@ const File = {
86
90
  filterIllegalFilenames(filename) {
87
91
  return (
88
92
  encodeURIComponent(filename)
89
- .replace(/[*]/g, '_STAR_')
93
+ .replaceAll(/[*]/g, '_STAR_')
90
94
  // convert space back
91
95
  .split('%20')
92
96
  .join(' ')
@@ -102,6 +106,9 @@ const File = {
102
106
  // convert brackets back for asset blocks
103
107
  .split('%5D')
104
108
  .join(']')
109
+ // convert @ back for users
110
+ .split('%40')
111
+ .join('@')
105
112
  );
106
113
  },
107
114
 
@@ -162,12 +169,47 @@ const File = {
162
169
  * @returns {Promise.<boolean>} Promise
163
170
  */
164
171
  writePrettyToFile: async function (directory, filename, filetype, content, templateVariables) {
165
- let formatted = await this._beautify_prettier(directory, filename, filetype, content);
172
+ let formatted =
173
+ filetype === 'amp'
174
+ ? this._beautify_beautyAmp(content)
175
+ : await this._beautify_prettier(directory, filename, filetype, content);
166
176
  if (templateVariables) {
167
177
  formatted = Util.replaceByObject(formatted, templateVariables);
168
178
  }
169
179
  return this.writeToFile(directory, filename, filetype, formatted);
170
180
  },
181
+ /**
182
+ * helper for {@link File.writePrettyToFile}, applying beautyAmp onto given stringified content
183
+ *
184
+ * @param {string} content filecontent
185
+ * @returns {string} original string on error; formatted string on success
186
+ */
187
+ _beautify_beautyAmp: function (content) {
188
+ // immutable at the moment:
189
+ const ampscript = {
190
+ capitalizeAndOrNot: true,
191
+ capitalizeIfFor: true,
192
+ capitalizeSet: true,
193
+ capitalizeVar: true,
194
+ maxParametersPerLine: 4,
195
+ };
196
+ // immutable at the moment:
197
+ const editor = {
198
+ insertSpaces: true,
199
+ tabSize: 4,
200
+ };
201
+ // logs trough console only for the moment.
202
+ const logs = {
203
+ loggerOn: false, // <= disable logging
204
+ };
205
+ try {
206
+ beautyAmp.setup(ampscript, editor, logs);
207
+ return beautyAmp.beautify(content);
208
+ } catch (ex) {
209
+ Util.logger.debug('File._beautify_beautyAmp:: error | ' + ex.message);
210
+ return content;
211
+ }
212
+ },
171
213
  /**
172
214
  * helper for {@link File.writePrettyToFile}, applying prettier onto given stringified content
173
215
  * ! Important: run 'await File.initPrettier()' in your MetadataType.retrieve() once before hitting this
@@ -179,16 +221,16 @@ const File = {
179
221
  * @returns {string} original string on error; formatted string on success
180
222
  */
181
223
  _beautify_prettier: function (directory, filename, filetype, content) {
182
- if (!FileFs.prettierConfig) {
183
- // either no prettier config in project directory or initPrettier was not run before this
184
- return content;
185
- } else if (content.includes('%%[') || content.includes('%%=')) {
186
- // in case we find AMPScript we need to abort beautifying as prettier
187
- // will throw an error falsely assuming bad syntax
188
- return content;
189
- }
190
224
  let formatted = '';
191
225
  try {
226
+ if (!FileFs.prettierConfig) {
227
+ // either no prettier config in project directory or initPrettier was not run before this
228
+ return content;
229
+ } else if (content.includes('%%[') || content.includes('%%=')) {
230
+ // in case we find AMPScript we need to abort beautifying as prettier
231
+ // will throw an error falsely assuming bad syntax
232
+ return this._beautify_beautyAmp(content);
233
+ }
192
234
  // load the right prettier config relative to our file
193
235
  switch (filetype) {
194
236
  case 'htm':
@@ -257,7 +299,7 @@ const File = {
257
299
  directory,
258
300
  filename + '.error',
259
301
  'log',
260
- `Error Log\nParser: ${FileFs.prettierConfig.parser}\n${ex.message.replace(
302
+ `Error Log\nParser: ${FileFs.prettierConfig.parser}\n${ex.message.replaceAll(
261
303
  /[\u001B\u009B][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
262
304
 
263
305
  ''
@@ -477,7 +519,7 @@ const File = {
477
519
  */
478
520
  async initPrettier(filetype) {
479
521
  if (FileFs.prettierConfig === null) {
480
- filetype = filetype || 'html';
522
+ filetype ||= 'html';
481
523
  try {
482
524
  // pass in project dir with fake index.html to avoid "no parser" error
483
525
  // by using process.cwd we are limiting ourselves to a config in the project root
@@ -337,8 +337,7 @@ const Init = {
337
337
  'files',
338
338
  fileName
339
339
  );
340
- boilerplateFileContent =
341
- boilerplateFileContent || (await File.readFile(boilerplateFileName, 'utf8'));
340
+ boilerplateFileContent ||= await File.readFile(boilerplateFileName, 'utf8');
342
341
 
343
342
  if (await File.pathExists(fileName)) {
344
343
  const existingFileContent = await File.readFile(fileName, 'utf8');
@@ -43,8 +43,8 @@ const Init = {
43
43
  repoName ||
44
44
  currentFolderName
45
45
  .toLowerCase()
46
- .replace(/[^a-z0-9 ]/gi, '')
47
- .replace(/ /gi, '-');
46
+ .replaceAll(/[^a-z0-9 ]/gi, '')
47
+ .replaceAll(/ /gi, '-');
48
48
  projectPackageJson = { name: standardNpmName };
49
49
  this._getDefaultPackageJson(projectPackageJson);
50
50
  await File.writeToFile('./', 'package', 'json', JSON.stringify(projectPackageJson));
@@ -128,7 +128,7 @@ const Init = {
128
128
  * @returns {Promise.<{script: object, author: string, license: string}>} extended currentContent
129
129
  */
130
130
  async _getDefaultPackageJson(currentContent) {
131
- currentContent = currentContent || {};
131
+ currentContent ||= {};
132
132
  // #1 scripts
133
133
  const predefinedCommandList = {
134
134
  build: 'sfmc-build all',
package/lib/util/util.js CHANGED
@@ -20,7 +20,8 @@ const Util = {
20
20
  /** @type {TYPE.skipInteraction} */
21
21
  skipInteraction: false,
22
22
  packageJsonMcdev: packageJsonMcdev,
23
-
23
+ OPTIONS: {},
24
+ changedKeysMap: {},
24
25
  /**
25
26
  * helper that allows filtering an object by its keys
26
27
  *
@@ -286,7 +287,7 @@ const Util = {
286
287
 
287
288
  sortable.sort((a, b) => b[1].length - a[1].length);
288
289
  for (const pair of sortable) {
289
- const escVal = pair[1].toString().replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
290
+ const escVal = pair[1].toString().replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
290
291
  const regString = new RegExp(escVal, 'g');
291
292
  str = str.replace(regString, '{{{' + pair[0] + '}}}');
292
293
  }
@@ -418,20 +419,24 @@ const Util = {
418
419
  * @returns {string} output of command if hideOutput is true
419
420
  */
420
421
  execSync(cmd, args, hideOutput) {
421
- args = args || [];
422
+ args ||= [];
422
423
  Util.logger.info('⚡ ' + cmd + ' ' + args.join(' '));
423
424
 
424
- if (hideOutput) {
425
- // no output displayed to user but instead returned to parsed elsewhere
426
- return child_process
427
- .execSync(cmd + ' ' + args.join(' '))
428
- .toString()
429
- .trim();
430
- } else {
431
- // the following options ensure the user sees the same output and
432
- // interaction options as if the command was manually run
433
- child_process.execSync(cmd + ' ' + args.join(' '), { stdio: [0, 1, 2] });
434
- return null;
425
+ try {
426
+ if (hideOutput) {
427
+ // no output displayed to user but instead returned to parsed elsewhere
428
+ return child_process
429
+ .execSync(cmd + ' ' + args.join(' '))
430
+ .toString()
431
+ .trim();
432
+ } else {
433
+ // the following options ensure the user sees the same output and
434
+ // interaction options as if the command was manually run
435
+ child_process.execSync(cmd + ' ' + args.join(' '), { stdio: [0, 1, 2] });
436
+ return null;
437
+ }
438
+ } catch {
439
+ // avoid errors from execSync to bubble up
435
440
  }
436
441
  },
437
442
  /**
@@ -469,23 +474,27 @@ const Util = {
469
474
  if (argv.silent) {
470
475
  // only errors printed to CLI
471
476
  Util.logger.level = 'error';
477
+ Util.OPTIONS.loggerLevel = 'error';
472
478
  Util.loggerTransports.console.level = 'error';
473
479
  Util.logger.debug('CLI logger set to: silent');
474
480
  } else if (argv.verbose) {
475
481
  // chatty user cli logs
476
482
  Util.logger.level = 'verbose';
483
+ Util.OPTIONS.loggerLevel = 'verbose';
477
484
  Util.loggerTransports.console.level = 'verbose';
478
485
  Util.logger.debug('CLI logger set to: verbose');
479
486
  } else {
480
487
  // default user cli logs
481
488
  // TODO to be switched to "warn" when cli-process is integrated
482
489
  Util.logger.level = 'info';
490
+ Util.OPTIONS.loggerLevel = 'info';
483
491
  Util.loggerTransports.console.level = 'info';
484
492
  Util.logger.debug('CLI logger set to: info / default');
485
493
  }
486
494
  if (argv.debug) {
487
495
  // enables developer output & features. no change to actual logs
488
496
  Util.logger.level = 'debug';
497
+ Util.OPTIONS.loggerLevel = 'debug';
489
498
  Util.loggerTransports.console.level = 'debug';
490
499
  Util.logger.debug('CLI logger set to: debug');
491
500
  }
@@ -587,6 +596,51 @@ const Util = {
587
596
  setTimeout(resolve, ms);
588
597
  });
589
598
  },
599
+ /**
600
+ * helper for {@link Asset._extractCode} and {@link Script.prepExtractedCode} to determine if a code block is a valid SSJS block
601
+ *
602
+ * @example the following is invalid:
603
+ * <script runat="server">
604
+ * // 1
605
+ * </script>
606
+ * <script runat="server">
607
+ * // 2
608
+ * </script>
609
+ *
610
+ * the following is valid:
611
+ * <script runat="server">
612
+ * // 3
613
+ * </script>
614
+ * @param {string} code code block to check
615
+ * @returns {string} the SSJS code if code block is a valid SSJS block, otherwise null
616
+ */
617
+ getSsjs(code) {
618
+ if (!code) {
619
+ return null;
620
+ }
621
+ // \s* whitespace characters, zero or more times
622
+ // [^>]*? any character that is not a >, zero or more times, un-greedily
623
+ // (.*) capture any character, zero or more times
624
+ // /ms multiline and dotall flags
625
+ // ideally the code looks like <script runat="server">...</script>
626
+ const scriptRegex = /^<\s*script [^>]*?>(.*)<\/\s*script\s*>$/ms;
627
+ code = code.trim();
628
+ const regexMatches = scriptRegex.exec(code);
629
+ if (regexMatches?.length > 1) {
630
+ // script found
631
+ /* eslint-disable unicorn/prefer-ternary */
632
+ if (regexMatches[1].includes('<script')) {
633
+ // nested script found
634
+ return null;
635
+ } else {
636
+ // no nested script found
637
+ return regexMatches[1];
638
+ }
639
+ /* eslint-enable unicorn/prefer-ternary */
640
+ }
641
+ // no script found
642
+ return null;
643
+ },
590
644
  };
591
645
  /**
592
646
  * wrapper around our standard winston logging to console and logfile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcdev",
3
- "version": "4.3.3",
3
+ "version": "5.0.0",
4
4
  "description": "Accenture Salesforce Marketing Cloud DevTools",
5
5
  "author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "mcdev": "./lib/cli.js"
36
36
  },
37
37
  "engines": {
38
- "node": ">=14.16"
38
+ "node": ">=15.0.0"
39
39
  },
40
40
  "scripts": {
41
41
  "start": "node lib/cli.js",
@@ -51,8 +51,8 @@
51
51
  "upgrade": "npm-check --update",
52
52
  "manual-prepare": "husky install",
53
53
  "lint-and-test": "run-s lint test",
54
- "test": "mocha",
55
- "coverage": "nyc mocha",
54
+ "test": "mocha --reporter-option maxDiffSize=25000",
55
+ "coverage": "nyc npm run test",
56
56
  "version:major": "npm version --no-commit-hooks major",
57
57
  "version:minor": "npm version --no-commit-hooks minor",
58
58
  "version:patch": "npm version --no-commit-hooks patch"
@@ -65,14 +65,14 @@
65
65
  "console.table": "0.10.0",
66
66
  "deep-equal": "2.1.0",
67
67
  "fs-extra": "11.1.0",
68
- "inquirer": "8.2.2",
68
+ "inquirer": "8.2.5",
69
69
  "json-to-table": "4.2.1",
70
70
  "mustache": "4.2.0",
71
71
  "p-limit": "3.1.0",
72
- "prettier": "2.8.3",
73
- "prettier-plugin-sql": "0.12.1",
74
- "semver": "7.3.8",
75
- "sfmc-sdk": "0.6.3",
72
+ "prettier": "2.8.8",
73
+ "prettier-plugin-sql": "0.14.0",
74
+ "semver": "7.5.0",
75
+ "sfmc-sdk": "1.0.0",
76
76
  "simple-git": "3.16.0",
77
77
  "toposort": "2.0.2",
78
78
  "update-notifier": "5.1.0",
@@ -81,19 +81,20 @@
81
81
  },
82
82
  "devDependencies": {
83
83
  "assert": "2.0.0",
84
- "axios-mock-adapter": "1.21.2",
84
+ "axios-mock-adapter": "1.21.3",
85
85
  "chai": "4.3.7",
86
86
  "chai-files": "1.4.0",
87
- "eslint": "8.32.0",
88
- "eslint-config-prettier": "8.6.0",
87
+ "eslint": "8.39.0",
88
+ "eslint-config-prettier": "8.7.0",
89
89
  "eslint-config-ssjs": "1.1.11",
90
- "eslint-plugin-jsdoc": "39.6.4",
90
+ "eslint-plugin-jsdoc": "43.1.1",
91
91
  "eslint-plugin-mocha": "10.1.0",
92
92
  "eslint-plugin-prettier": "4.2.1",
93
- "eslint-plugin-unicorn": "45.0.2",
93
+ "eslint-plugin-unicorn": "46.0.0",
94
+ "fast-xml-parser": "4.2.2",
94
95
  "husky": "8.0.3",
95
96
  "jsdoc-to-markdown": "8.0.0",
96
- "lint-staged": "13.1.0",
97
+ "lint-staged": "13.2.0",
97
98
  "mocha": "10.2.0",
98
99
  "mock-fs": "5.2.0",
99
100
  "npm-check": "6.0.1",