mcdev 7.4.0 → 7.4.2
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.
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/@types/lib/Deployer.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Event.d.ts +13 -7
- package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Folder.d.ts +1 -2
- package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
- package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
- package/@types/lib/util/util.d.ts +7 -0
- package/@types/lib/util/util.d.ts.map +1 -1
- package/@types/lib/util/validations.d.ts +2 -0
- package/@types/lib/util/validations.d.ts.map +1 -1
- package/@types/types/mcdev.d.d.ts +212 -0
- package/@types/types/mcdev.d.d.ts.map +1 -1
- package/boilerplate/files/.vscode/settings.json +0 -3
- package/boilerplate/forcedUpdates.json +8 -0
- package/boilerplate/gitignore-template +1 -0
- package/lib/Deployer.js +11 -3
- package/lib/cli.js +7 -1
- package/lib/metadataTypes/DataExtension.js +0 -13
- package/lib/metadataTypes/DataExtensionField.js +4 -2
- package/lib/metadataTypes/Event.js +53 -19
- package/lib/metadataTypes/Folder.js +8 -5
- package/lib/metadataTypes/Journey.js +43 -12
- package/lib/metadataTypes/MetadataType.js +11 -8
- package/lib/metadataTypes/definitions/TriggeredSend.definition.js +4 -2
- package/lib/util/config.js +4 -4
- package/lib/util/util.js +14 -0
- package/lib/util/validations.js +9 -2
- package/package.json +11 -11
- package/test/general.test.js +4 -4
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +0 -1
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +0 -1
- package/test/resources/9999999/journey/build-expected.json +4 -2
- package/test/resources/9999999/journey/get-multistep-expected.json +63 -61
- package/test/resources/9999999/journey/get-quicksend-expected.json +4 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +2 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +2 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +6 -2
- package/test/resources/9999999/journey/get-transactionalEmail-expected.json +2 -2
- package/test/resources/9999999/journey/template-expected.json +4 -2
- package/test/resources/9999999/triggeredSend/build-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-id-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-key-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-name-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/patch-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/post-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/template-expected.json +0 -1
- package/test/resources/9999999/triggeredSendDefinition/create-response.xml +0 -1
- package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml +0 -2
- package/test/resources/9999999/triggeredSendDefinition/update-response.xml +0 -1
- package/test/type.dataExtension.test.js +3 -3
- package/types/mcdev.d.js +79 -0
|
@@ -149,11 +149,13 @@ class DataExtensionField extends MetadataType {
|
|
|
149
149
|
const existingFieldByName = {};
|
|
150
150
|
|
|
151
151
|
for (const key of Object.keys(fieldsObj)) {
|
|
152
|
-
|
|
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];
|
|
153
154
|
}
|
|
154
155
|
for (let i = deployColumns.length - 1; i >= 0; i--) {
|
|
155
156
|
const item = deployColumns[i];
|
|
156
|
-
|
|
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()];
|
|
157
159
|
if (itemOld) {
|
|
158
160
|
// field is getting updated ---
|
|
159
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(
|
|
@@ -644,8 +647,9 @@ class Event extends MetadataType {
|
|
|
644
647
|
this.sfObjects.objectFields[objectAPIName][field.name] = field;
|
|
645
648
|
}
|
|
646
649
|
} else {
|
|
647
|
-
Util.logger.
|
|
648
|
-
|
|
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
|
+
);
|
|
649
653
|
}
|
|
650
654
|
return;
|
|
651
655
|
}
|
|
@@ -666,26 +670,41 @@ class Event extends MetadataType {
|
|
|
666
670
|
|
|
667
671
|
/**
|
|
668
672
|
*
|
|
669
|
-
* @param {
|
|
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
|
|
670
675
|
*/
|
|
671
|
-
static checkSalesforceEntryEvents(ca) {
|
|
676
|
+
static checkSalesforceEntryEvents(ca, isPublished) {
|
|
672
677
|
// 1 check eventDataConfig
|
|
673
|
-
const edcObjects = ca.eventDataConfig.objects
|
|
678
|
+
const edcObjects = ca.eventDataConfig.objects.sort((a, b) =>
|
|
679
|
+
a.dePrefix.localeCompare(b.dePrefix)
|
|
680
|
+
);
|
|
674
681
|
const errors = [];
|
|
675
682
|
const dePrefixFields = {};
|
|
676
683
|
const dePrefixRelationshipMap = {};
|
|
677
684
|
const dePrefixReferenceObjectMap = {};
|
|
678
|
-
|
|
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;
|
|
679
688
|
for (const object of edcObjects) {
|
|
680
689
|
// create secondary object to quickly check eventDataSummary against
|
|
681
690
|
dePrefixFields[object.dePrefix] = object.fields;
|
|
682
|
-
|
|
683
|
-
|
|
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;
|
|
684
701
|
|
|
685
702
|
// 1.1 check if fields in eventDataConfig exist in Salesforce
|
|
686
703
|
// if it has no value this is the entry-source object itself
|
|
687
704
|
const referencedObject =
|
|
688
705
|
object.referenceObject === '' ? ca.objectAPIName : object.referenceObject;
|
|
706
|
+
// sort list of fields alphabetically
|
|
707
|
+
object.fields.sort();
|
|
689
708
|
// check if object was cached earlier
|
|
690
709
|
if (!this.sfObjects.workflowObjects.includes(referencedObject)) {
|
|
691
710
|
errors.push(`Salesforce object ${referencedObject} not found on connected org.`);
|
|
@@ -695,7 +714,8 @@ class Event extends MetadataType {
|
|
|
695
714
|
) {
|
|
696
715
|
// check if we found fields for the object
|
|
697
716
|
errors.push(
|
|
698
|
-
`
|
|
717
|
+
`Fields for Salesforce object ${referencedObject} could not be checked. Fields selected in entry event: ` +
|
|
718
|
+
object.fields.join(', ')
|
|
699
719
|
);
|
|
700
720
|
} else {
|
|
701
721
|
// check if the fields selected in the eventDefinition are actually available
|
|
@@ -892,19 +912,23 @@ class Event extends MetadataType {
|
|
|
892
912
|
/**
|
|
893
913
|
*
|
|
894
914
|
* @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
|
|
895
|
-
* @param {
|
|
915
|
+
* @param {configurationArguments} ca trigger[0].configurationArguments
|
|
896
916
|
* @param {string} key of event / journey
|
|
897
|
-
* @param {
|
|
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
|
|
898
919
|
* @returns {Promise.<void>} -
|
|
899
920
|
*/
|
|
900
|
-
static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, type) {
|
|
921
|
+
static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, isPublished, type) {
|
|
901
922
|
if (triggerType !== 'SalesforceObjectTriggerV2' || !ca) {
|
|
902
923
|
return;
|
|
903
924
|
}
|
|
904
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
|
|
905
927
|
if (ca.objectApiName) {
|
|
906
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
|
|
907
930
|
ca.objectAPIName = ca.objectApiName;
|
|
931
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
908
932
|
delete ca.objectApiName;
|
|
909
933
|
}
|
|
910
934
|
|
|
@@ -926,7 +950,8 @@ class Event extends MetadataType {
|
|
|
926
950
|
: ca.eventDataConfig;
|
|
927
951
|
ca.eventDataSummary =
|
|
928
952
|
'string' === typeof ca.eventDataSummary
|
|
929
|
-
?
|
|
953
|
+
? // @ts-expect-error transforming this from API-string-format to from mcdev-format
|
|
954
|
+
ca.eventDataSummary.split('; ').filter(Boolean).sort()
|
|
930
955
|
: ca.eventDataSummary;
|
|
931
956
|
ca.passThroughArgument =
|
|
932
957
|
'string' === typeof ca.passThroughArgument
|
|
@@ -946,13 +971,13 @@ class Event extends MetadataType {
|
|
|
946
971
|
await this.getSalesforceObjects(ca.objectAPIName);
|
|
947
972
|
|
|
948
973
|
// check if whats on the journey matches what SF Core offers
|
|
949
|
-
this.checkSalesforceEntryEvents(ca);
|
|
974
|
+
this.checkSalesforceEntryEvents(ca, isPublished);
|
|
950
975
|
}
|
|
951
976
|
|
|
952
977
|
/**
|
|
953
978
|
*
|
|
954
979
|
* @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
|
|
955
|
-
* @param {
|
|
980
|
+
* @param {configurationArguments} ca trigger[0].configurationArguments
|
|
956
981
|
* @returns {Promise.<void>} -
|
|
957
982
|
*/
|
|
958
983
|
static async preDeployTasks_SalesforceEntryEvents(triggerType, ca) {
|
|
@@ -980,34 +1005,43 @@ class Event extends MetadataType {
|
|
|
980
1005
|
await this.getSalesforceObjects(ca.objectAPIName);
|
|
981
1006
|
|
|
982
1007
|
// check if whats on the journey matches what SF Core offers
|
|
983
|
-
this.checkSalesforceEntryEvents(ca);
|
|
1008
|
+
this.checkSalesforceEntryEvents(ca, false);
|
|
984
1009
|
|
|
985
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
|
|
986
1012
|
ca.contactKey =
|
|
987
1013
|
'object' === typeof ca.contactKey ? JSON.stringify(ca.contactKey) : ca.contactKey;
|
|
1014
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
988
1015
|
ca.eventDataConfig =
|
|
989
1016
|
'object' === typeof ca.eventDataConfig
|
|
990
1017
|
? JSON.stringify(ca.eventDataConfig)
|
|
991
1018
|
: ca.eventDataConfig;
|
|
1019
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
992
1020
|
ca.eventDataSummary = Array.isArray(ca.eventDataSummary)
|
|
993
1021
|
? ca.eventDataSummary.join('; ') + '; '
|
|
994
1022
|
: ca.eventDataSummary;
|
|
1023
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
995
1024
|
ca.passThroughArgument =
|
|
996
1025
|
'object' === typeof ca.passThroughArgument
|
|
997
1026
|
? JSON.stringify(ca.passThroughArgument)
|
|
998
1027
|
: ca.passThroughArgument;
|
|
1028
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
999
1029
|
ca.primaryObjectFilterCriteria =
|
|
1000
1030
|
'object' === typeof ca.primaryObjectFilterCriteria
|
|
1001
1031
|
? JSON.stringify(ca.primaryObjectFilterCriteria)
|
|
1002
1032
|
: ca.primaryObjectFilterCriteria;
|
|
1033
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
1003
1034
|
ca.relatedObjectFilterCriteria =
|
|
1004
1035
|
'object' === typeof ca.relatedObjectFilterCriteria
|
|
1005
1036
|
? JSON.stringify(ca.relatedObjectFilterCriteria)
|
|
1006
1037
|
: ca.relatedObjectFilterCriteria;
|
|
1007
1038
|
|
|
1039
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
1008
1040
|
if (ca.objectApiName) {
|
|
1009
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
|
|
1010
1043
|
ca.objectAPIName = ca.objectApiName;
|
|
1044
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
1011
1045
|
delete ca.objectApiName;
|
|
1012
1046
|
}
|
|
1013
1047
|
}
|
|
@@ -164,7 +164,7 @@ class Folder extends MetadataType {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
if (retrieveDir) {
|
|
167
|
-
const savedMetadata = await this.saveResults(metadata, retrieveDir
|
|
167
|
+
const savedMetadata = await this.saveResults(metadata, retrieveDir);
|
|
168
168
|
Util.logger.info(
|
|
169
169
|
`Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
|
|
170
170
|
);
|
|
@@ -644,17 +644,20 @@ class Folder extends MetadataType {
|
|
|
644
644
|
*
|
|
645
645
|
* @param {ListMap} results metadata results from deploy
|
|
646
646
|
* @param {string} retrieveDir directory where metadata should be stored after deploy/retrieve
|
|
647
|
-
* @param {number | string} mid unused parameter
|
|
648
647
|
* @returns {Promise.<ListMap>} Promise of saved metadata
|
|
649
648
|
*/
|
|
650
|
-
static async saveResults(results, retrieveDir
|
|
649
|
+
static async saveResults(results, retrieveDir) {
|
|
651
650
|
/** @type {ListMap} */
|
|
652
651
|
const savedResults = {};
|
|
653
652
|
for (const metadataEntry in results) {
|
|
654
653
|
try {
|
|
655
654
|
// skip saving shared folders as they technically live in parent.
|
|
656
|
-
|
|
657
|
-
|
|
655
|
+
if (
|
|
656
|
+
results[metadataEntry].Client &&
|
|
657
|
+
this.buObject.mid != results[metadataEntry].Client.ID
|
|
658
|
+
) {
|
|
659
|
+
// deploy: folders auto-generated by deploy do not have .Client set and hence this check will be skipped
|
|
660
|
+
// retrieve: Client.ID is set to the MID of the BU that the folder belongs to; we only want folders of the current BU saved here
|
|
658
661
|
continue;
|
|
659
662
|
} else if (
|
|
660
663
|
results[metadataEntry] &&
|
|
@@ -185,7 +185,7 @@ class Journey extends MetadataType {
|
|
|
185
185
|
// if the interaction does not exist, the API returns an error code which would otherwise bring execution to a hold
|
|
186
186
|
if (
|
|
187
187
|
[
|
|
188
|
-
'
|
|
188
|
+
'Interaction matching key not found.',
|
|
189
189
|
'Must provide a valid ID or Key parameter',
|
|
190
190
|
].includes(ex.message)
|
|
191
191
|
) {
|
|
@@ -419,6 +419,7 @@ class Journey extends MetadataType {
|
|
|
419
419
|
metadata.triggers[0].type,
|
|
420
420
|
metadata.triggers[0].configurationArguments,
|
|
421
421
|
metadata.key,
|
|
422
|
+
metadata.status === 'Published',
|
|
422
423
|
this.definition.type
|
|
423
424
|
);
|
|
424
425
|
} catch (ex) {
|
|
@@ -562,7 +563,6 @@ class Journey extends MetadataType {
|
|
|
562
563
|
switch (activity.type) {
|
|
563
564
|
case 'EMAILV2': {
|
|
564
565
|
// triggeredSend + email+asset
|
|
565
|
-
// TODO email / asset
|
|
566
566
|
const configurationArguments = activity.configurationArguments;
|
|
567
567
|
if (configurationArguments) {
|
|
568
568
|
try {
|
|
@@ -634,12 +634,15 @@ class Journey extends MetadataType {
|
|
|
634
634
|
triggeredSend.emailId = linkedTS.Email?.ID;
|
|
635
635
|
triggeredSend.dynamicEmailSubject = linkedTS.DynamicEmailSubject;
|
|
636
636
|
triggeredSend.emailSubject = linkedTS.EmailSubject;
|
|
637
|
-
|
|
637
|
+
// only the bccEmail field can be retrieved for triggeredSends, not the ccEmail field; for some reason BccEmail can be retrieved but does not return a value even if stored correctly in the journey.
|
|
638
|
+
// triggeredSend.bccEmail = linkedTS.BccEmail;
|
|
638
639
|
triggeredSend.isMultipart = linkedTS.IsMultipart;
|
|
639
640
|
triggeredSend.autoAddSubscribers = linkedTS.AutoAddSubscribers;
|
|
640
641
|
triggeredSend.autoUpdateSubscribers =
|
|
641
642
|
linkedTS.AutoUpdateSubscribers;
|
|
642
643
|
triggeredSend.isTrackingClicks = !linkedTS.SuppressTracking;
|
|
644
|
+
triggeredSend.suppressTracking = linkedTS.SuppressTracking;
|
|
645
|
+
triggeredSend.triggeredSendStatus = linkedTS.TriggeredSendStatus;
|
|
643
646
|
// from name & email are set in the senderProfile, not in the triggeredSend
|
|
644
647
|
// triggeredSend.fromName = linkedTS.FromName;
|
|
645
648
|
// triggeredSend.fromAddress = linkedTS.FromAddress;
|
|
@@ -735,6 +738,13 @@ class Journey extends MetadataType {
|
|
|
735
738
|
delete triggeredSend.key;
|
|
736
739
|
}
|
|
737
740
|
|
|
741
|
+
triggeredSend.ccEmail = triggeredSend.ccEmail
|
|
742
|
+
.split(';')
|
|
743
|
+
.filter((el) => el !== '');
|
|
744
|
+
triggeredSend.bccEmail = triggeredSend.bccEmail
|
|
745
|
+
.split(';')
|
|
746
|
+
.filter((el) => el !== '');
|
|
747
|
+
|
|
738
748
|
// List (optional)
|
|
739
749
|
triggeredSend.r__list_PathName ||= {};
|
|
740
750
|
if (triggeredSend.publicationListId) {
|
|
@@ -891,6 +901,10 @@ class Journey extends MetadataType {
|
|
|
891
901
|
}
|
|
892
902
|
}
|
|
893
903
|
}
|
|
904
|
+
|
|
905
|
+
// sort attributes of triggeredSend alphabetically to allow for easier pull request reviews
|
|
906
|
+
configurationArguments.triggeredSend =
|
|
907
|
+
Util.sortObjectAttributes(triggeredSend);
|
|
894
908
|
}
|
|
895
909
|
break;
|
|
896
910
|
}
|
|
@@ -1009,12 +1023,10 @@ class Journey extends MetadataType {
|
|
|
1009
1023
|
break;
|
|
1010
1024
|
}
|
|
1011
1025
|
}
|
|
1012
|
-
// TODO: Filters / activities[].type === 'MULTICRITERIADECISION'
|
|
1013
|
-
// - activities[].arguments.filterResult
|
|
1014
|
-
// - activities[].arguments.configurationArguments.criteria
|
|
1015
|
-
|
|
1016
|
-
// TODO: wait activity / activities[].type === 'WAIT'
|
|
1017
1026
|
}
|
|
1027
|
+
|
|
1028
|
+
// apply sorting by activity key to work around the API shuffling activities around
|
|
1029
|
+
metadata.activities = metadata.activities.sort((a, b) => a.key.localeCompare(b.key));
|
|
1018
1030
|
}
|
|
1019
1031
|
|
|
1020
1032
|
/**
|
|
@@ -1221,6 +1233,15 @@ class Journey extends MetadataType {
|
|
|
1221
1233
|
delete triggeredSend.r__triggeredSend_key;
|
|
1222
1234
|
}
|
|
1223
1235
|
|
|
1236
|
+
triggeredSend.ccEmail =
|
|
1237
|
+
typeof triggeredSend.ccEmail === 'string'
|
|
1238
|
+
? triggeredSend.ccEmail
|
|
1239
|
+
: triggeredSend.ccEmail.join(';');
|
|
1240
|
+
triggeredSend.bccEmail =
|
|
1241
|
+
typeof triggeredSend.bccEmail === 'string'
|
|
1242
|
+
? triggeredSend.bccEmail
|
|
1243
|
+
: triggeredSend.bccEmail.join(';');
|
|
1244
|
+
|
|
1224
1245
|
// List (optional)
|
|
1225
1246
|
if (triggeredSend.r__list_PathName) {
|
|
1226
1247
|
if (triggeredSend.r__list_PathName.publicationList) {
|
|
@@ -1475,11 +1496,16 @@ class Journey extends MetadataType {
|
|
|
1475
1496
|
if (triggeredSend) {
|
|
1476
1497
|
// the following is very similar but not equal to the variables in TriggeredSend.js
|
|
1477
1498
|
try {
|
|
1478
|
-
|
|
1479
|
-
triggeredSend.bccEmail
|
|
1499
|
+
let bccEmail =
|
|
1500
|
+
typeof triggeredSend.bccEmail === 'string'
|
|
1501
|
+
? triggeredSend.bccEmail
|
|
1502
|
+
: triggeredSend.bccEmail.join(';');
|
|
1503
|
+
bccEmail = ReplaceCbReference.replaceReference(
|
|
1504
|
+
bccEmail,
|
|
1480
1505
|
parentName,
|
|
1481
1506
|
findAssetKeys
|
|
1482
1507
|
);
|
|
1508
|
+
triggeredSend.bccEmail = bccEmail.split(';').filter((el) => el !== '');
|
|
1483
1509
|
changes = true;
|
|
1484
1510
|
} catch (ex) {
|
|
1485
1511
|
if (ex.code !== 200) {
|
|
@@ -1487,11 +1513,16 @@ class Journey extends MetadataType {
|
|
|
1487
1513
|
}
|
|
1488
1514
|
}
|
|
1489
1515
|
try {
|
|
1490
|
-
|
|
1491
|
-
triggeredSend.ccEmail
|
|
1516
|
+
let ccEmail =
|
|
1517
|
+
typeof triggeredSend.ccEmail === 'string'
|
|
1518
|
+
? triggeredSend.ccEmail
|
|
1519
|
+
: triggeredSend.ccEmail.join(';');
|
|
1520
|
+
ccEmail = ReplaceCbReference.replaceReference(
|
|
1521
|
+
ccEmail,
|
|
1492
1522
|
parentName,
|
|
1493
1523
|
findAssetKeys
|
|
1494
1524
|
);
|
|
1525
|
+
triggeredSend.ccEmail = ccEmail.split(';').filter((el) => el !== '');
|
|
1495
1526
|
changes = true;
|
|
1496
1527
|
} catch (ex) {
|
|
1497
1528
|
if (ex.code !== 200) {
|
|
@@ -135,14 +135,17 @@ class MetadataType {
|
|
|
135
135
|
*/
|
|
136
136
|
static async deploy(metadataMap, deployDir, retrieveDir) {
|
|
137
137
|
const upsertedMetadataMap = await this.upsert(metadataMap, deployDir);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
if (retrieveDir) {
|
|
139
|
+
// deploy can be run with retrieveDir set to null for deploying auto-created foldes - these should not be saved to the retrieve-folder while everything else should
|
|
140
|
+
const savedMetadataMap = await this.saveResults(upsertedMetadataMap, retrieveDir, null);
|
|
141
|
+
if (
|
|
142
|
+
this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
|
|
143
|
+
!this.definition.documentInOneFile
|
|
144
|
+
) {
|
|
145
|
+
// * do not await here as this might take a while and has no impact on the deploy
|
|
146
|
+
// * this should only be run if documentation is on a per metadata record level. Types that document an overview into a single file will need a full retrieve to work instead
|
|
147
|
+
this.document(savedMetadataMap, true);
|
|
148
|
+
}
|
|
146
149
|
}
|
|
147
150
|
return upsertedMetadataMap;
|
|
148
151
|
}
|
|
@@ -67,10 +67,11 @@ export default {
|
|
|
67
67
|
templating: true,
|
|
68
68
|
},
|
|
69
69
|
BccEmail: {
|
|
70
|
+
// while this can be retrieved, it seems to be always returned empty
|
|
70
71
|
isCreateable: true,
|
|
71
72
|
isUpdateable: true,
|
|
72
|
-
retrieving:
|
|
73
|
-
templating:
|
|
73
|
+
retrieving: false,
|
|
74
|
+
templating: false,
|
|
74
75
|
},
|
|
75
76
|
CategoryID: {
|
|
76
77
|
isCreateable: true,
|
|
@@ -79,6 +80,7 @@ export default {
|
|
|
79
80
|
templating: true,
|
|
80
81
|
},
|
|
81
82
|
CCEmail: {
|
|
83
|
+
// this field is updatable but not retrievable for some reason
|
|
82
84
|
isCreateable: true,
|
|
83
85
|
isUpdateable: true,
|
|
84
86
|
retrieving: false,
|
package/lib/util/config.js
CHANGED
|
@@ -202,7 +202,7 @@ const config = {
|
|
|
202
202
|
Array.isArray(defaultProps[key][subkey])
|
|
203
203
|
? 'Array'
|
|
204
204
|
: typeof defaultProps[key][subkey]
|
|
205
|
-
}): ${defaultProps[key][subkey]}`
|
|
205
|
+
}): ${typeof defaultProps[key][subkey] === 'object' ? JSON.stringify(defaultProps[key][subkey]) : defaultProps[key][subkey]}`
|
|
206
206
|
);
|
|
207
207
|
solutionSet.add(
|
|
208
208
|
`Run 'mcdev upgrade' to fix missing or changed configuration options`
|
|
@@ -270,15 +270,15 @@ const config = {
|
|
|
270
270
|
errorMsgOutput.push(' - ' + msg);
|
|
271
271
|
}
|
|
272
272
|
Util.logger.error(errorMsgOutput.join('\n'));
|
|
273
|
-
if (Util.skipInteraction) {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
273
|
Util.logger.info(
|
|
277
274
|
[
|
|
278
275
|
'Here is what you can do to fix these issues:',
|
|
279
276
|
...Array.from(solutionSet),
|
|
280
277
|
].join('\n- ')
|
|
281
278
|
);
|
|
279
|
+
if (Util.skipInteraction) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
282
|
const runUpgradeNow = await confirm({
|
|
283
283
|
message: `Do you want to run 'mcdev upgrade' now?`,
|
|
284
284
|
default: true,
|
package/lib/util/util.js
CHANGED
|
@@ -1138,6 +1138,20 @@ export const Util = {
|
|
|
1138
1138
|
});
|
|
1139
1139
|
return [...new Set(values.sort())];
|
|
1140
1140
|
},
|
|
1141
|
+
/**
|
|
1142
|
+
* helper that returns a new object with sorted attributes of the given object
|
|
1143
|
+
*
|
|
1144
|
+
* @param {object} obj object with unsorted attributes
|
|
1145
|
+
* @returns {object} obj but with sorted attributes
|
|
1146
|
+
*/
|
|
1147
|
+
sortObjectAttributes(obj) {
|
|
1148
|
+
return Object.keys(obj)
|
|
1149
|
+
.sort()
|
|
1150
|
+
.reduce((acc, key) => {
|
|
1151
|
+
acc[key] = obj[key];
|
|
1152
|
+
return acc;
|
|
1153
|
+
}, {});
|
|
1154
|
+
},
|
|
1141
1155
|
};
|
|
1142
1156
|
|
|
1143
1157
|
Util.startLogger(false, true);
|
package/lib/util/validations.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Util } from './util.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../../types/mcdev.d.js').validationRuleList} validationRuleList
|
|
7
|
+
* @typedef {import('../../types/mcdev.d.js').validationRuleTest} validationRuleTest
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {validationRuleList} */
|
|
5
11
|
let customRules = {};
|
|
6
12
|
let customRuleImport;
|
|
7
13
|
try {
|
|
@@ -28,11 +34,12 @@ export default async function validation(definition, item, targetDir) {
|
|
|
28
34
|
'Could not load custom validation rules from .mcdev-validations.js: ' + ex.message
|
|
29
35
|
);
|
|
30
36
|
}
|
|
37
|
+
/** @type {validationRuleList} */
|
|
31
38
|
const defaultRules = {
|
|
32
39
|
noGuidKeys: {
|
|
33
40
|
failedMsg: 'Please update the key to a readable value. Currently still in GUID format.',
|
|
34
41
|
/**
|
|
35
|
-
* @
|
|
42
|
+
* @type {validationRuleTest}
|
|
36
43
|
*/
|
|
37
44
|
passed: function () {
|
|
38
45
|
const key = item[definition.keyField];
|
|
@@ -48,7 +55,7 @@ export default async function validation(definition, item, targetDir) {
|
|
|
48
55
|
noRootFolder: {
|
|
49
56
|
failedMsg: 'Root folder not allowed. Current folder: ' + item.r__folder_Path,
|
|
50
57
|
/**
|
|
51
|
-
* @
|
|
58
|
+
* @type {validationRuleTest}
|
|
52
59
|
*/
|
|
53
60
|
passed: function () {
|
|
54
61
|
/** @type {string} */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcdev",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.2",
|
|
4
4
|
"description": "Accenture Salesforce Marketing Cloud DevTools",
|
|
5
5
|
"author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"console.table": "0.10.0",
|
|
76
76
|
"deep-equal": "2.2.3",
|
|
77
77
|
"fs-extra": "11.2.0",
|
|
78
|
-
"inquirer": "
|
|
78
|
+
"inquirer": "11.0.2",
|
|
79
79
|
"json-to-table": "4.2.1",
|
|
80
80
|
"mustache": "4.2.0",
|
|
81
81
|
"p-limit": "6.1.0",
|
|
@@ -85,39 +85,39 @@
|
|
|
85
85
|
"sfmc-sdk": "2.1.2",
|
|
86
86
|
"simple-git": "3.25.0",
|
|
87
87
|
"toposort": "2.0.2",
|
|
88
|
-
"update-notifier": "7.
|
|
88
|
+
"update-notifier": "7.3.1",
|
|
89
89
|
"winston": "3.14.2",
|
|
90
90
|
"yargs": "17.7.2",
|
|
91
91
|
"yocto-spinner": "0.1.0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@eslint/js": "9.
|
|
94
|
+
"@eslint/js": "9.10.0",
|
|
95
95
|
"@types/fs-extra": "11.0.4",
|
|
96
96
|
"@types/inquirer": "9.0.7",
|
|
97
|
-
"@types/mocha": "10.0.
|
|
98
|
-
"@types/node": "22.
|
|
97
|
+
"@types/mocha": "10.0.8",
|
|
98
|
+
"@types/node": "22.5.5",
|
|
99
99
|
"@types/yargs": "17.0.33",
|
|
100
100
|
"assert": "2.1.0",
|
|
101
101
|
"axios-mock-adapter": "2.0.0",
|
|
102
102
|
"c8": "10.0.0",
|
|
103
103
|
"chai": "5.1.1",
|
|
104
104
|
"chai-files": "1.4.0",
|
|
105
|
-
"eslint": "9.
|
|
105
|
+
"eslint": "9.10.0",
|
|
106
106
|
"eslint-config-prettier": "9.1.0",
|
|
107
107
|
"eslint-config-ssjs": "2.0.0",
|
|
108
|
-
"eslint-plugin-jsdoc": "50.2.
|
|
108
|
+
"eslint-plugin-jsdoc": "50.2.4",
|
|
109
109
|
"eslint-plugin-mocha": "10.5.0",
|
|
110
110
|
"eslint-plugin-prettier": "5.2.1",
|
|
111
111
|
"eslint-plugin-unicorn": "55.0.0",
|
|
112
112
|
"fast-xml-parser": "4.4.1",
|
|
113
113
|
"globals": "15.9.0",
|
|
114
|
-
"husky": "9.1.
|
|
115
|
-
"lint-staged": "15.2.
|
|
114
|
+
"husky": "9.1.6",
|
|
115
|
+
"lint-staged": "15.2.10",
|
|
116
116
|
"mocha": "10.7.3",
|
|
117
117
|
"mock-fs": "5.2.0",
|
|
118
118
|
"npm-run-all": "4.1.5",
|
|
119
119
|
"prettier-eslint": "16.3.0",
|
|
120
|
-
"typescript": "5.
|
|
120
|
+
"typescript": "5.6.2"
|
|
121
121
|
},
|
|
122
122
|
"optionalDependencies": {
|
|
123
123
|
"fsevents": "*"
|
package/test/general.test.js
CHANGED
|
@@ -569,7 +569,7 @@ describe('GENERAL', () => {
|
|
|
569
569
|
);
|
|
570
570
|
assert.equal(
|
|
571
571
|
testUtils.getAPIHistoryLength(),
|
|
572
|
-
|
|
572
|
+
13,
|
|
573
573
|
'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
|
|
574
574
|
);
|
|
575
575
|
});
|
|
@@ -632,7 +632,7 @@ describe('GENERAL', () => {
|
|
|
632
632
|
);
|
|
633
633
|
assert.equal(
|
|
634
634
|
testUtils.getAPIHistoryLength(),
|
|
635
|
-
|
|
635
|
+
14,
|
|
636
636
|
'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
|
|
637
637
|
);
|
|
638
638
|
});
|
|
@@ -697,7 +697,7 @@ describe('GENERAL', () => {
|
|
|
697
697
|
);
|
|
698
698
|
assert.equal(
|
|
699
699
|
testUtils.getAPIHistoryLength(),
|
|
700
|
-
|
|
700
|
+
11,
|
|
701
701
|
'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
|
|
702
702
|
);
|
|
703
703
|
});
|
|
@@ -2251,7 +2251,7 @@ describe('GENERAL', () => {
|
|
|
2251
2251
|
);
|
|
2252
2252
|
assert.equal(
|
|
2253
2253
|
testUtils.getAPIHistoryLength(),
|
|
2254
|
-
|
|
2254
|
+
13,
|
|
2255
2255
|
'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
|
|
2256
2256
|
);
|
|
2257
2257
|
});
|