mcdev 7.3.1 → 7.4.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 (71) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/@types/lib/Deployer.d.ts.map +1 -1
  3. package/@types/lib/index.d.ts.map +1 -1
  4. package/@types/lib/metadataTypes/Asset.d.ts +25 -0
  5. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  6. package/@types/lib/metadataTypes/DataExtension.d.ts +3 -2
  7. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  8. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Event.d.ts +39 -7
  10. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Journey.d.ts +4 -3
  12. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/MetadataType.d.ts +22 -3
  14. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  15. package/@types/lib/util/init.config.d.ts.map +1 -1
  16. package/@types/lib/util/replaceContentBlockReference.d.ts +4 -5
  17. package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
  18. package/@types/lib/util/util.d.ts +18 -2
  19. package/@types/lib/util/util.d.ts.map +1 -1
  20. package/@types/lib/util/validations.d.ts +9 -0
  21. package/@types/lib/util/validations.d.ts.map +1 -0
  22. package/@types/types/mcdev.d.d.ts +195 -0
  23. package/@types/types/mcdev.d.d.ts.map +1 -1
  24. package/boilerplate/config.json +22 -0
  25. package/boilerplate/files/.gitattributes +1 -1
  26. package/boilerplate/files/README.md +1 -1
  27. package/boilerplate/forcedUpdates.json +8 -0
  28. package/boilerplate/gitignore-template +1 -0
  29. package/lib/Deployer.js +5 -0
  30. package/lib/cli.js +28 -3
  31. package/lib/index.js +3 -2
  32. package/lib/metadataTypes/Asset.js +87 -7
  33. package/lib/metadataTypes/DataExtension.js +74 -17
  34. package/lib/metadataTypes/DataExtensionField.js +11 -3
  35. package/lib/metadataTypes/Event.js +171 -105
  36. package/lib/metadataTypes/Journey.js +207 -89
  37. package/lib/metadataTypes/MetadataType.js +182 -37
  38. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +4 -2
  39. package/lib/util/config.js +4 -4
  40. package/lib/util/init.config.js +10 -6
  41. package/lib/util/replaceContentBlockReference.js +15 -11
  42. package/lib/util/util.js +43 -9
  43. package/lib/util/validations.js +66 -0
  44. package/package.json +8 -8
  45. package/test/general.test.js +4 -4
  46. package/test/mockRoot/.mcdevrc.json +15 -1
  47. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +0 -1
  48. package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +0 -1
  49. package/test/resources/9999999/journey/build-expected.json +4 -2
  50. package/test/resources/9999999/journey/get-multistep-expected.json +61 -61
  51. package/test/resources/9999999/journey/get-quicksend-expected.json +4 -2
  52. package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +2 -2
  53. package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +2 -2
  54. package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +6 -2
  55. package/test/resources/9999999/journey/get-transactionalEmail-expected.json +2 -2
  56. package/test/resources/9999999/journey/template-expected.json +4 -2
  57. package/test/resources/9999999/triggeredSend/build-expected.json +0 -1
  58. package/test/resources/9999999/triggeredSend/get-expected.json +0 -1
  59. package/test/resources/9999999/triggeredSend/get-rcb-id-expected.json +0 -1
  60. package/test/resources/9999999/triggeredSend/get-rcb-key-expected.json +0 -1
  61. package/test/resources/9999999/triggeredSend/get-rcb-name-expected.json +0 -1
  62. package/test/resources/9999999/triggeredSend/patch-expected.json +0 -1
  63. package/test/resources/9999999/triggeredSend/post-expected.json +0 -1
  64. package/test/resources/9999999/triggeredSend/template-expected.json +0 -1
  65. package/test/resources/9999999/triggeredSendDefinition/create-response.xml +0 -1
  66. package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml +0 -2
  67. package/test/resources/9999999/triggeredSendDefinition/update-response.xml +0 -1
  68. package/test/type.dataExtension.test.js +3 -3
  69. package/test/type.journey.test.js +2 -2
  70. package/test/utils.js +1 -0
  71. package/types/mcdev.d.js +66 -0
@@ -42,22 +42,10 @@ class DataExtension extends MetadataType {
42
42
  * if create or update operation is needed.
43
43
  *
44
44
  * @param {DataExtensionMap} metadataMap dataExtensions mapped by their customerKey
45
- * @returns {Promise} Promise
45
+ * @param {string} deployDir directory where deploy metadata are saved
46
+ * @returns {Promise.<MetadataTypeMap>} keyField => metadata map
46
47
  */
47
- static async upsert(metadataMap) {
48
- // get existing DE-fields for DE-keys in deployment package to properly handle add/update/delete of fields
49
- // we need to use IN here because it would fail otherwise if we try to deploy too many DEs at the same time
50
- /** @type {SoapRequestParams} */
51
- const fieldOptions = {
52
- filter: {
53
- leftOperand: 'DataExtension.CustomerKey',
54
- operator: 'IN',
55
- rightOperand: Object.keys(metadataMap),
56
- },
57
- };
58
- Util.logger.info(` - Caching dependent Metadata: dataExtensionField`);
59
- await this.#attachFields(metadataMap, fieldOptions);
60
-
48
+ static async upsert(metadataMap, deployDir) {
61
49
  /** @type {object[]} */
62
50
  const metadataToCreate = [];
63
51
  /** @type {object[]} */
@@ -66,6 +54,11 @@ class DataExtension extends MetadataType {
66
54
  for (const metadataKey in metadataMap) {
67
55
  try {
68
56
  metadataMap[metadataKey] = await this.preDeployTasks(metadataMap[metadataKey]);
57
+ metadataMap[metadataKey] = await this.validation(
58
+ 'deploy',
59
+ metadataMap[metadataKey],
60
+ deployDir
61
+ );
69
62
  } catch (ex) {
70
63
  // output error & remove from deploy list
71
64
  Util.logger.error(
@@ -187,7 +180,7 @@ class DataExtension extends MetadataType {
187
180
  DataExtension.oldFields[metadataMap[metadataKey][this.definition.keyField]] =
188
181
  await DataExtensionField.prepareDeployColumnsOnUpdate(
189
182
  metadataMap[metadataKey].Fields,
190
- metadataKey
183
+ Util.matchedByName?.[this.definition.type]?.[metadataKey] || metadataKey
191
184
  );
192
185
 
193
186
  if (
@@ -893,7 +886,7 @@ class DataExtension extends MetadataType {
893
886
  if (metadata[field?.DataExtension?.CustomerKey]) {
894
887
  metadata[field.DataExtension.CustomerKey].Fields.push(field);
895
888
  } else {
896
- Util.logger.warn(` - Issue retrieving data extension fields. key='${key}'`);
889
+ // field was retrieved for which we do not have the right dataExtension. This might be due to us having to resort to not using a DE filter to avoid the "String or binary data would be truncated." error
897
890
  }
898
891
  }
899
892
 
@@ -1632,6 +1625,70 @@ class DataExtension extends MetadataType {
1632
1625
  return super.getFilesToCommit(keyArr);
1633
1626
  }
1634
1627
  }
1628
+ /**
1629
+ * helper for {@link MetadataType.createOrUpdate}
1630
+ *
1631
+ * @param {MetadataTypeItem} metadataItem to be deployed item
1632
+ * @returns {MetadataTypeItem} cached item or undefined
1633
+ */
1634
+ static getCacheMatchedByName(metadataItem) {
1635
+ let cacheMatchedByName;
1636
+
1637
+ if (Util.OPTIONS.matchName) {
1638
+ // make sure to run the search ONLY if OPTIONS.matchName is true and definition.allowMatchingByName signals support
1639
+ const typeCache = cache.getCache()?.[this.definition.type];
1640
+ const potentials = [];
1641
+ for (const key in typeCache) {
1642
+ const cachedItem = typeCache[key];
1643
+ if (
1644
+ cachedItem[this.definition.nameField] ===
1645
+ metadataItem[this.definition.nameField]
1646
+ ) {
1647
+ potentials.push(cachedItem);
1648
+ }
1649
+ }
1650
+ if (potentials.length > 1) {
1651
+ throw new Error(
1652
+ `found multiple name matches in cache for ${this.definition.type} ${metadataItem[this.definition.keyField]} / ${metadataItem[this.definition.nameField]}. Check their keys for more details: ${potentials.map((p) => p[this.definition.keyField]).join(', ')}`
1653
+ );
1654
+ } else if (potentials.length === 1) {
1655
+ // only one item found, confirm that it's in the same folder
1656
+ const deployFolderPath = cache.searchForField(
1657
+ 'folder',
1658
+ metadataItem[this.definition.folderIdField],
1659
+ 'ID',
1660
+ 'Path'
1661
+ );
1662
+ if (
1663
+ potentials[0][this.definition.folderIdField] ===
1664
+ metadataItem[this.definition.folderIdField]
1665
+ ) {
1666
+ cacheMatchedByName = potentials[0];
1667
+
1668
+ Util.logger.info(
1669
+ Util.getGrayMsg(
1670
+ ` - found ${this.definition.type} ${metadataItem[this.definition.keyField]} in cache by name "${metadataItem[this.definition.nameField]}" and folder "${deployFolderPath}": ${cacheMatchedByName[this.definition.keyField]}`
1671
+ )
1672
+ );
1673
+ } else {
1674
+ const cacheFolderPath = cache.searchForField(
1675
+ 'folder',
1676
+ potentials[0][this.definition.folderIdField],
1677
+ 'ID',
1678
+ 'Path'
1679
+ );
1680
+ throw new Error(
1681
+ `found ${this.definition.type} ${metadataItem[this.definition.keyField]} in cache by name "${metadataItem[this.definition.nameField]}" but in different folders: "${deployFolderPath}" vs "${cacheFolderPath}": ${potentials[0][this.definition.keyField]}`
1682
+ );
1683
+ }
1684
+ } else {
1685
+ Util.logger.debug(
1686
+ ` - no name-match found for ${this.definition.type} ${metadataItem[this.definition.keyField]}. Creating new ${this.definition.type} instead.`
1687
+ );
1688
+ }
1689
+ }
1690
+ return cacheMatchedByName;
1691
+ }
1635
1692
  }
1636
1693
 
1637
1694
  // Assign definition to static attributes
@@ -48,7 +48,13 @@ class DataExtensionField extends MetadataType {
48
48
  * @returns {Promise.<{metadata: DataExtensionFieldMap, type: string}>} Promise of items
49
49
  */
50
50
  static async retrieveForCacheDE(requestParams, additionalFields) {
51
- return super.retrieveSOAP(null, requestParams, null, additionalFields);
51
+ let response;
52
+ response = await super.retrieveSOAP(null, requestParams, null, additionalFields);
53
+ if (!response) {
54
+ // try again but without filters as a workaround for the "String or binary data would be truncated." issue
55
+ response = await super.retrieveSOAP(null, {}, null, additionalFields);
56
+ }
57
+ return response;
52
58
  }
53
59
 
54
60
  /**
@@ -143,11 +149,13 @@ class DataExtensionField extends MetadataType {
143
149
  const existingFieldByName = {};
144
150
 
145
151
  for (const key of Object.keys(fieldsObj)) {
146
- existingFieldByName[fieldsObj[key].Name] = fieldsObj[key];
152
+ // make sure we stringify the name in case it looked numeric and then lowercase it for easy comparison as the server is comparing field names case-insensitive
153
+ existingFieldByName[(fieldsObj[key].Name + '').toLowerCase()] = fieldsObj[key];
147
154
  }
148
155
  for (let i = deployColumns.length - 1; i >= 0; i--) {
149
156
  const item = deployColumns[i];
150
- const itemOld = existingFieldByName[item.Name];
157
+ // make sure we stringify the name in case it looked numeric and then lowercase it for easy comparison as the server is comparing field names case-insensitive
158
+ const itemOld = existingFieldByName[(item.Name + '').toLowerCase()];
151
159
  if (itemOld) {
152
160
  // field is getting updated ---
153
161
 
@@ -21,6 +21,8 @@ import pLimit from 'p-limit';
21
21
  *
22
22
  * @typedef {import('../../types/mcdev.d.js').ReferenceObject} ReferenceObject
23
23
  * @typedef {import('../../types/mcdev.d.js').SfObjectField} SfObjectField
24
+ * @typedef {import('../../types/mcdev.d.js').configurationArguments} configurationArguments
25
+ * @typedef {import('../../types/mcdev.d.js').Conditions} Conditions
24
26
  */
25
27
 
26
28
  /**
@@ -42,10 +44,10 @@ class Event extends MetadataType {
42
44
  * @param {string} [key] customer key of single item to retrieve
43
45
  * @returns {Promise.<MetadataTypeMapObj>} Promise of metadata
44
46
  */
45
- static retrieve(retrieveDir, _, __, key) {
47
+ static async retrieve(retrieveDir, _, __, key) {
46
48
  Util.logBeta(this.definition.type);
47
49
  try {
48
- return super.retrieveREST(
50
+ return await super.retrieveREST(
49
51
  retrieveDir,
50
52
  `/interaction/v1/eventDefinitions${
51
53
  key ? '/key:' + encodeURIComponent(key) : ''
@@ -447,7 +449,8 @@ class Event extends MetadataType {
447
449
  await this.postRetrieveTasks_SalesforceEntryEvents(
448
450
  metadata.type,
449
451
  metadata.configurationArguments,
450
- metadata.eventDefinitionKey
452
+ metadata.eventDefinitionKey,
453
+ metadata.publishedInteractionCount >= 1
451
454
  );
452
455
  } catch (ex) {
453
456
  Util.logger.warn(
@@ -464,6 +467,12 @@ class Event extends MetadataType {
464
467
  referencedObjects: {},
465
468
  /** @type {Object.<string, Object.<string, SfObjectField>>} object-name > field-name > field data */
466
469
  objectFields: {},
470
+ /** @type {Object.<string, Promise.<any>>} */
471
+ loadingFields: {},
472
+ /** @type {Object.<string, Promise.<any>>} */
473
+ loadingRelatedObjects: {},
474
+ /** @type {Promise.<any>} */
475
+ loadingWorkflowObjects: null,
467
476
  };
468
477
 
469
478
  /**
@@ -479,53 +488,20 @@ class Event extends MetadataType {
479
488
  // 1 get all available Salesforce objects
480
489
  // similar response to /jbint/getWorkflowObjects
481
490
  if (!this.sfObjects.workflowObjects) {
482
- Util.logger.info(Util.getGrayMsg(' - Caching Salesforce Objects'));
483
- const workflowObjectsResponse = await this.client.rest.get(
484
- `/data/v1/integration/member/salesforce/workflowobjects`
485
- );
486
- this.sfObjects.workflowObjects = workflowObjectsResponse
487
- ? workflowObjectsResponse.map((o) => o.apiname)
488
- : [];
491
+ if (!this.sfObjects.loadingWorkflowObjects) {
492
+ this.sfObjects.loadingWorkflowObjects = this._getWorkflowObjects();
493
+ }
494
+ await this.sfObjects.loadingWorkflowObjects;
489
495
  }
490
496
 
491
497
  // 2 get objects related to the selected object
492
498
  // same response as /jbint/getRelatedObjects?type=<objectAPIName>
493
499
  if (!this.sfObjects.referencedObjects?.[objectAPIName]) {
494
- Util.logger.info(
495
- Util.getGrayMsg(' - Caching Related Salesforce Objects for ' + objectAPIName)
496
- );
497
- try {
498
- const referenceObjectsResponse = await this.client.rest.get(
499
- `/data/v1/integration/member/salesforce/object/${objectAPIName}/referenceobjects`
500
- );
501
- // add itself first so that we get the fields for objectAPIName as well
502
- const selfReference = {
503
- referenceObjectName: objectAPIName,
504
- relationshipName: objectAPIName,
505
- };
506
- this.sfObjects.referencedObjects[objectAPIName] = referenceObjectsResponse
507
- ? [selfReference, ...referenceObjectsResponse]
508
- : [selfReference];
509
- if (
510
- referenceObjectsResponse.some((el) => el.referenceObjectName === 'Lead') &&
511
- referenceObjectsResponse.some((el) => el.referenceObjectName === 'Contact')
512
- ) {
513
- // add fake object "Common" to referenced objects for testing
514
- this.sfObjects.referencedObjects[objectAPIName].push({
515
- displayname: 'Common',
516
- relationshipIdName: 'Id',
517
- relationshipName: 'Common',
518
- isPolymorphic: false,
519
- referenceObjectName: 'Common',
520
- });
521
- }
522
- } catch (ex) {
523
- if (ex.code === 'ERR_BAD_RESPONSE') {
524
- throw new Error(
525
- `Could not find Salesforce entry object ${objectAPIName} on connected org.`
526
- );
527
- }
500
+ if (!this.sfObjects.loadingRelatedObjects[objectAPIName]) {
501
+ this.sfObjects.loadingRelatedObjects[objectAPIName] =
502
+ this._getRelatedSfObjects(objectAPIName);
528
503
  }
504
+ await this.sfObjects.loadingRelatedObjects[objectAPIName];
529
505
 
530
506
  // 3 get fields
531
507
  const rateLimit = pLimit(20);
@@ -541,47 +517,9 @@ class Event extends MetadataType {
541
517
  await Promise.all(
542
518
  uniqueSfObjectNames.map((objectAPIName) =>
543
519
  rateLimit(async () => {
544
- if (
545
- this.sfObjects.objectFields[objectAPIName] ||
546
- objectAPIName === 'Common'
547
- ) {
548
- return;
549
- }
550
- Util.logger.info(
551
- Util.getGrayMsg(
552
- ' - Caching Fields for Salesforce Object ' + objectAPIName
553
- )
554
- );
555
-
556
- const referenceObjectsFieldsResponse = await this.client.rest.get(
557
- `/legacy/v1/beta/integration/member/salesforce/object/${objectAPIName}`
558
- );
559
- if (referenceObjectsFieldsResponse?.sfobjectfields?.length) {
560
- Util.logger.debug(
561
- `Found ${referenceObjectsFieldsResponse?.sfobjectfields?.length} fields for Salesforce Object ${objectAPIName}`
562
- );
563
- this.sfObjects.objectFields[objectAPIName] = {};
564
- // !add default fields that are somehow not always returned by this legacy beta API
565
- for (const field of this.defaultSalesforceFields) {
566
- // @ts-expect-error hack to work around shortcomings of legacy beta API
567
- this.sfObjects.objectFields[objectAPIName][field] = {
568
- label: field,
569
- name: field,
570
- };
571
- }
572
- // add fields returned by API
573
- for (const field of referenceObjectsFieldsResponse.sfobjectfields) {
574
- this.sfObjects.objectFields[objectAPIName][field.name] = field;
575
- }
576
- } else {
577
- Util.logger.debug(
578
- `Could not find any fields for Salesforce Object ${objectAPIName}`
579
- );
580
- throw new Error(
581
- `Could not find any fields for Salesforce Object ${objectAPIName}`
582
- );
583
- }
584
- return;
520
+ this.sfObjects.loadingFields[objectAPIName] ||=
521
+ this._getSalesforceObjectFields(objectAPIName);
522
+ return this.sfObjects.loadingFields[objectAPIName];
585
523
  })
586
524
  )
587
525
  );
@@ -619,6 +557,101 @@ class Event extends MetadataType {
619
557
  this.sfObjects.objectFields[contactLeadName];
620
558
  }
621
559
  }
560
+ return;
561
+ }
562
+ /**
563
+ * helper that allows skipping to run this again in multi-key retrieval
564
+ */
565
+ static async _getWorkflowObjects() {
566
+ Util.logger.info(Util.getGrayMsg(' - Caching Salesforce Objects'));
567
+ const workflowObjectsResponse = await this.client.rest.get(
568
+ `/data/v1/integration/member/salesforce/workflowobjects`
569
+ );
570
+ this.sfObjects.workflowObjects = workflowObjectsResponse
571
+ ? workflowObjectsResponse.map((o) => o.apiname)
572
+ : [];
573
+ }
574
+ /**
575
+ * helper that allows skipping to run this again in multi-key retrieval
576
+ *
577
+ * @param {string} objectAPIName SF entry object of the current event
578
+ */
579
+ static async _getRelatedSfObjects(objectAPIName) {
580
+ Util.logger.info(
581
+ Util.getGrayMsg(' - Caching Related Salesforce Objects for ' + objectAPIName)
582
+ );
583
+ try {
584
+ const referenceObjectsResponse = await this.client.rest.get(
585
+ `/data/v1/integration/member/salesforce/object/${objectAPIName}/referenceobjects`
586
+ );
587
+ // add itself first so that we get the fields for objectAPIName as well
588
+ const selfReference = {
589
+ referenceObjectName: objectAPIName,
590
+ relationshipName: objectAPIName,
591
+ };
592
+ this.sfObjects.referencedObjects[objectAPIName] = referenceObjectsResponse
593
+ ? [selfReference, ...referenceObjectsResponse]
594
+ : [selfReference];
595
+ if (
596
+ referenceObjectsResponse.some((el) => el.referenceObjectName === 'Lead') &&
597
+ referenceObjectsResponse.some((el) => el.referenceObjectName === 'Contact')
598
+ ) {
599
+ // add fake object "Common" to referenced objects for testing
600
+ this.sfObjects.referencedObjects[objectAPIName].push({
601
+ displayname: 'Common',
602
+ relationshipIdName: 'Id',
603
+ relationshipName: 'Common',
604
+ isPolymorphic: false,
605
+ referenceObjectName: 'Common',
606
+ });
607
+ }
608
+ } catch (ex) {
609
+ if (ex.code === 'ERR_BAD_RESPONSE') {
610
+ throw new Error(
611
+ `Could not find Salesforce entry object ${objectAPIName} on connected org.`
612
+ );
613
+ }
614
+ }
615
+ }
616
+ /**
617
+ * helper that allows skipping to run this again in multi-key retrieval
618
+ *
619
+ * @param {string} objectAPIName SF object for which to get the fields
620
+ */
621
+ static async _getSalesforceObjectFields(objectAPIName) {
622
+ if (this.sfObjects.objectFields[objectAPIName] || objectAPIName === 'Common') {
623
+ return;
624
+ }
625
+ Util.logger.info(
626
+ Util.getGrayMsg(' - Caching Fields for Salesforce Object ' + objectAPIName)
627
+ );
628
+
629
+ const referenceObjectsFieldsResponse = await this.client.rest.get(
630
+ `/legacy/v1/beta/integration/member/salesforce/object/${objectAPIName}`
631
+ );
632
+ if (referenceObjectsFieldsResponse?.sfobjectfields?.length) {
633
+ Util.logger.debug(
634
+ `Found ${referenceObjectsFieldsResponse?.sfobjectfields?.length} fields for Salesforce Object ${objectAPIName}`
635
+ );
636
+ this.sfObjects.objectFields[objectAPIName] = {};
637
+ // !add default fields that are somehow not always returned by this legacy beta API
638
+ for (const field of this.defaultSalesforceFields) {
639
+ // @ts-expect-error hack to work around shortcomings of legacy beta API
640
+ this.sfObjects.objectFields[objectAPIName][field] = {
641
+ label: field,
642
+ name: field,
643
+ };
644
+ }
645
+ // add fields returned by API
646
+ for (const field of referenceObjectsFieldsResponse.sfobjectfields) {
647
+ this.sfObjects.objectFields[objectAPIName][field.name] = field;
648
+ }
649
+ } else {
650
+ Util.logger.warn(
651
+ `Could not cache fields for Salesforce Object '${objectAPIName}'. This is likely caused by insufficient access of your MC-Connect integration user. Please check assigned permission sets / the profile.`
652
+ );
653
+ }
654
+ return;
622
655
  }
623
656
 
624
657
  static defaultSalesforceFields = [
@@ -637,26 +670,41 @@ class Event extends MetadataType {
637
670
 
638
671
  /**
639
672
  *
640
- * @param {any} ca trigger[0].configurationArguments
673
+ * @param {configurationArguments} ca trigger[0].configurationArguments
674
+ * @param {boolean} isPublished if the current item is published it means we do not need to do contact vs common checks
641
675
  */
642
- static checkSalesforceEntryEvents(ca) {
676
+ static checkSalesforceEntryEvents(ca, isPublished) {
643
677
  // 1 check eventDataConfig
644
- const edcObjects = ca.eventDataConfig.objects;
678
+ const edcObjects = ca.eventDataConfig.objects.sort((a, b) =>
679
+ a.dePrefix.localeCompare(b.dePrefix)
680
+ );
645
681
  const errors = [];
646
682
  const dePrefixFields = {};
647
683
  const dePrefixRelationshipMap = {};
648
684
  const dePrefixReferenceObjectMap = {};
649
- const checkCommon = ca.whoToInject === 'Contact ID/Lead ID (Contacts and Leads)';
685
+ // SFMC only uses "Common" to aggreagate Contacts and Leads if that was actively selected in the entry event. Also, already published journeys/events continue to work even if fields would later be changed, leading to a shift from or to the "common" fake-object.
686
+ const checkCommon =
687
+ ca.whoToInject === 'Contact ID/Lead ID (Contacts and Leads)' && !isPublished;
650
688
  for (const object of edcObjects) {
651
689
  // create secondary object to quickly check eventDataSummary against
652
690
  dePrefixFields[object.dePrefix] = object.fields;
653
- dePrefixRelationshipMap[object.dePrefix] = object.relationshipName;
654
- dePrefixReferenceObjectMap[object.dePrefix] = object.referenceObject;
691
+
692
+ // if the current object is the entry object then relationshipName and referenceObject are set to empty strings because it's not "referencing" a "relationship" but just listing its own fields
693
+ dePrefixRelationshipMap[object.dePrefix] =
694
+ object.relationshipName === ''
695
+ ? object.dePrefix.split(':')[0]
696
+ : object.relationshipName;
697
+ dePrefixReferenceObjectMap[object.dePrefix] =
698
+ object.referenceObject === ''
699
+ ? object.dePrefix.split(':')[0]
700
+ : object.referenceObject;
655
701
 
656
702
  // 1.1 check if fields in eventDataConfig exist in Salesforce
657
703
  // if it has no value this is the entry-source object itself
658
704
  const referencedObject =
659
705
  object.referenceObject === '' ? ca.objectAPIName : object.referenceObject;
706
+ // sort list of fields alphabetically
707
+ object.fields.sort();
660
708
  // check if object was cached earlier
661
709
  if (!this.sfObjects.workflowObjects.includes(referencedObject)) {
662
710
  errors.push(`Salesforce object ${referencedObject} not found on connected org.`);
@@ -666,7 +714,8 @@ class Event extends MetadataType {
666
714
  ) {
667
715
  // check if we found fields for the object
668
716
  errors.push(
669
- `No fields found for Salesforce object ${referencedObject} on connected org.`
717
+ `Fields for Salesforce object ${referencedObject} could not be checked. Fields selected in entry event: ` +
718
+ object.fields.join(', ')
670
719
  );
671
720
  } else {
672
721
  // check if the fields selected in the eventDefinition are actually available
@@ -689,7 +738,7 @@ class Event extends MetadataType {
689
738
  if (!ca.eventDataSummary.includes(object.dePrefix + fieldName)) {
690
739
  // TODO instead, remove in postRetrieve and re-add in preDeploy
691
740
  errors.push(
692
- `Field ${object.dePrefix + fieldName} is listed under eventDataConfig for referenceObject ${object.referenceObject} but not in eventDataSummary`
741
+ `Field ${object.dePrefix + fieldName} is listed under eventDataConfig${object.referenceObject ? ` for referenceObject ` + object.referenceObject : ''} but not in eventDataSummary`
693
742
  );
694
743
  }
695
744
  }
@@ -704,7 +753,11 @@ class Event extends MetadataType {
704
753
  const fieldPath = fieldName.split(':');
705
754
  fieldName = fieldPath.pop();
706
755
  const dePrefix = fieldPath.join(':') + ':';
707
- if (!dePrefixFields[dePrefix].includes(fieldName)) {
756
+ if (!dePrefixFields[dePrefix]) {
757
+ errors.push(
758
+ `Field ${dePrefix + fieldName} is listed under eventDataSummary but object ${dePrefix} was not found in eventDataConfig`
759
+ );
760
+ } else if (!dePrefixFields[dePrefix]?.includes(fieldName)) {
708
761
  errors.push(
709
762
  `Field ${dePrefix + fieldName} is listed under eventDataSummary but not in eventDataConfig`
710
763
  );
@@ -859,19 +912,23 @@ class Event extends MetadataType {
859
912
  /**
860
913
  *
861
914
  * @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
862
- * @param {any} ca trigger[0].configurationArguments
915
+ * @param {configurationArguments} ca trigger[0].configurationArguments
863
916
  * @param {string} key of event / journey
864
- * @param {string} [type] optionally provide type for error on missing configurationArguments attributes
917
+ * @param {boolean} isPublished if the current item is published it means we do not need to do contact vs common checks
918
+ * @param {string} [type] optionally provide metadatype for error on missing configurationArguments attributes
865
919
  * @returns {Promise.<void>} -
866
920
  */
867
- static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, type) {
921
+ static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, isPublished, type) {
868
922
  if (triggerType !== 'SalesforceObjectTriggerV2' || !ca) {
869
923
  return;
870
924
  }
871
925
  // normalize payload because these fields are sometimes set as strings and sometimes as objects
926
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
872
927
  if (ca.objectApiName) {
873
928
  // on event only the uppercase version is used. lets make sure we normalize that here.
929
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
874
930
  ca.objectAPIName = ca.objectApiName;
931
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
875
932
  delete ca.objectApiName;
876
933
  }
877
934
 
@@ -893,7 +950,8 @@ class Event extends MetadataType {
893
950
  : ca.eventDataConfig;
894
951
  ca.eventDataSummary =
895
952
  'string' === typeof ca.eventDataSummary
896
- ? ca.eventDataSummary.split('; ').filter(Boolean)
953
+ ? // @ts-expect-error transforming this from API-string-format to from mcdev-format
954
+ ca.eventDataSummary.split('; ').filter(Boolean).sort()
897
955
  : ca.eventDataSummary;
898
956
  ca.passThroughArgument =
899
957
  'string' === typeof ca.passThroughArgument
@@ -913,13 +971,13 @@ class Event extends MetadataType {
913
971
  await this.getSalesforceObjects(ca.objectAPIName);
914
972
 
915
973
  // check if whats on the journey matches what SF Core offers
916
- this.checkSalesforceEntryEvents(ca);
974
+ this.checkSalesforceEntryEvents(ca, isPublished);
917
975
  }
918
976
 
919
977
  /**
920
978
  *
921
979
  * @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
922
- * @param {any} ca trigger[0].configurationArguments
980
+ * @param {configurationArguments} ca trigger[0].configurationArguments
923
981
  * @returns {Promise.<void>} -
924
982
  */
925
983
  static async preDeployTasks_SalesforceEntryEvents(triggerType, ca) {
@@ -947,35 +1005,43 @@ class Event extends MetadataType {
947
1005
  await this.getSalesforceObjects(ca.objectAPIName);
948
1006
 
949
1007
  // check if whats on the journey matches what SF Core offers
950
- this.checkSalesforceEntryEvents(ca);
1008
+ this.checkSalesforceEntryEvents(ca, false);
951
1009
 
952
1010
  // normalize payload because these fields are sometimes set as strings and sometimes as objects
1011
+ // @ts-expect-error reverting this back from mcdev-format to API format
953
1012
  ca.contactKey =
954
1013
  'object' === typeof ca.contactKey ? JSON.stringify(ca.contactKey) : ca.contactKey;
1014
+ // @ts-expect-error reverting this back from mcdev-format to API format
955
1015
  ca.eventDataConfig =
956
1016
  'object' === typeof ca.eventDataConfig
957
1017
  ? JSON.stringify(ca.eventDataConfig)
958
1018
  : ca.eventDataConfig;
959
- ca.eventDataSummary =
960
- 'object' === typeof ca.eventDataSummary
961
- ? ca.eventDataSummary.join('; ') + '; '
962
- : ca.eventDataSummary;
1019
+ // @ts-expect-error reverting this back from mcdev-format to API format
1020
+ ca.eventDataSummary = Array.isArray(ca.eventDataSummary)
1021
+ ? ca.eventDataSummary.join('; ') + '; '
1022
+ : ca.eventDataSummary;
1023
+ // @ts-expect-error reverting this back from mcdev-format to API format
963
1024
  ca.passThroughArgument =
964
1025
  'object' === typeof ca.passThroughArgument
965
1026
  ? JSON.stringify(ca.passThroughArgument)
966
1027
  : ca.passThroughArgument;
1028
+ // @ts-expect-error reverting this back from mcdev-format to API format
967
1029
  ca.primaryObjectFilterCriteria =
968
1030
  'object' === typeof ca.primaryObjectFilterCriteria
969
1031
  ? JSON.stringify(ca.primaryObjectFilterCriteria)
970
1032
  : ca.primaryObjectFilterCriteria;
1033
+ // @ts-expect-error reverting this back from mcdev-format to API format
971
1034
  ca.relatedObjectFilterCriteria =
972
1035
  'object' === typeof ca.relatedObjectFilterCriteria
973
1036
  ? JSON.stringify(ca.relatedObjectFilterCriteria)
974
1037
  : ca.relatedObjectFilterCriteria;
975
1038
 
1039
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
976
1040
  if (ca.objectApiName) {
977
1041
  // on event only the uppercase version is used. lets make sure we normalize that here.
1042
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
978
1043
  ca.objectAPIName = ca.objectApiName;
1044
+ // @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
979
1045
  delete ca.objectApiName;
980
1046
  }
981
1047
  }