mcdev 5.2.0 → 5.3.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 (106) hide show
  1. package/.fork/custom-commands.json +12 -0
  2. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  3. package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +19 -0
  4. package/docs/dist/documentation.md +400 -9
  5. package/lib/MetadataTypeDefinitions.js +1 -0
  6. package/lib/MetadataTypeInfo.js +1 -0
  7. package/lib/cli.js +6 -1
  8. package/lib/index.js +4 -1
  9. package/lib/metadataTypes/AttributeSet.js +118 -11
  10. package/lib/metadataTypes/Automation.js +99 -70
  11. package/lib/metadataTypes/DataExtension.js +463 -66
  12. package/lib/metadataTypes/DataExtensionField.js +30 -13
  13. package/lib/metadataTypes/Journey.js +8 -1
  14. package/lib/metadataTypes/MetadataType.js +63 -5
  15. package/lib/metadataTypes/MobileKeyword.js +1 -1
  16. package/lib/metadataTypes/TransactionalEmail.js +94 -17
  17. package/lib/metadataTypes/TransactionalMessage.js +3 -2
  18. package/lib/metadataTypes/Verification.js +230 -0
  19. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +2 -2
  20. package/lib/metadataTypes/definitions/AttributeSet.definition.js +74 -21
  21. package/lib/metadataTypes/definitions/Automation.definition.js +1 -0
  22. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +19 -1
  23. package/lib/metadataTypes/definitions/Verification.definition.js +88 -0
  24. package/package.json +6 -6
  25. package/test/mockRoot/.mcdevrc.json +1 -1
  26. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testExisting_dataExtensionShared.dataExtension-meta.json +59 -0
  27. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testNew_dataExtensionShared.dataExtension-meta.json +23 -0
  28. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +4 -0
  29. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testExisting_dataExtension.dataExtension-meta.json +1 -0
  30. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +3 -4
  31. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -6
  32. package/test/mockRoot/deploy/testInstance/testBU/verification/testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  33. package/test/mockRoot/deploy/testInstance/testBU/verification/testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  34. package/test/resources/1111111/data/v1/customobjectdata/key/testExisting_dataExtensionShared/rowset/get-response.json +13 -0
  35. package/test/resources/1111111/dataExtension/create-expected.json +23 -0
  36. package/test/resources/1111111/dataExtension/create-response.xml +59 -0
  37. package/test/resources/1111111/dataExtension/retrieve-expected.json +55 -0
  38. package/test/resources/1111111/dataExtension/retrieve-expected.md +18 -0
  39. package/test/resources/1111111/dataExtension/retrieve-response.xml +27 -1
  40. package/test/resources/1111111/dataExtension/update-expected.json +55 -0
  41. package/test/resources/1111111/dataExtension/update-response.xml +57 -0
  42. package/test/resources/1111111/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtensionShared].[TriggerUpdate_randomNumber_]-response.xml +45 -0
  43. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  44. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionSharedORDataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  45. package/test/resources/1111111/dataExtensionField/retrieve-response.xml +98 -0
  46. package/test/resources/1111111/dataExtensionTemplate/retrieve-response.xml +303 -0
  47. package/test/resources/1111111/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +387 -0
  48. package/test/resources/1111111/dataFolder/retrieve-response.xml +353 -9
  49. package/test/resources/9999999/attributeSet/retrieve-expected.json +89 -694
  50. package/test/resources/9999999/automation/build-expected.json +4 -0
  51. package/test/resources/9999999/automation/create-expected.json +4 -0
  52. package/test/resources/9999999/automation/create-testNew_automation-expected.md +1 -0
  53. package/test/resources/9999999/automation/retrieve-expected.json +4 -0
  54. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +1 -0
  55. package/test/resources/9999999/automation/template-expected.json +4 -0
  56. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +7 -0
  57. package/test/resources/9999999/automation/v1/automations/post-response.json +7 -0
  58. package/test/resources/9999999/automation/v1/dataverifications/post-response.json +12 -0
  59. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/delete-response.json +0 -0
  60. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/get-response.json +12 -0
  61. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/patch-response.json +12 -0
  62. package/test/resources/9999999/dataExtension/build-expected.json +16 -0
  63. package/test/resources/9999999/dataExtension/delete-response.xml +42 -0
  64. package/test/resources/9999999/dataExtension/retrieve-expected.json +16 -0
  65. package/test/resources/9999999/dataExtension/retrieve-expected.md +3 -1
  66. package/test/resources/9999999/dataExtension/template-expected.json +16 -0
  67. package/test/resources/9999999/dataExtension/update-expected.json +17 -1
  68. package/test/resources/9999999/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtension].[LastName]-response.xml +44 -0
  69. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +36 -1
  70. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +36 -1
  71. package/test/resources/9999999/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +117 -0
  72. package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +43 -0
  73. package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +387 -0
  74. package/test/resources/9999999/interaction/v1/interactions/233d4413-922c-4568-85a5-e5cc77efc3be/delete-response.json +1 -0
  75. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +1 -1
  76. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  77. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/delete-response.json +6 -0
  78. package/test/resources/9999999/transactionalEmail/build-expected.json +3 -7
  79. package/test/resources/9999999/transactionalEmail/get-expected.json +3 -7
  80. package/test/resources/9999999/transactionalEmail/patch-expected.json +3 -7
  81. package/test/resources/9999999/transactionalEmail/post-expected.json +3 -7
  82. package/test/resources/9999999/transactionalEmail/template-expected.json +3 -7
  83. package/test/resources/9999999/verification/build-expected.json +11 -0
  84. package/test/resources/9999999/verification/get-expected.json +11 -0
  85. package/test/resources/9999999/verification/patch-expected.json +11 -0
  86. package/test/resources/9999999/verification/post-expected.json +11 -0
  87. package/test/resources/9999999/verification/template-expected.json +11 -0
  88. package/test/type.attributeGroup.test.js +4 -4
  89. package/test/type.attributeSet.test.js +5 -5
  90. package/test/type.automation.test.js +29 -23
  91. package/test/type.dataExtension.test.js +205 -45
  92. package/test/type.dataExtract.test.js +10 -3
  93. package/test/type.fileTransfer.test.js +10 -3
  94. package/test/type.importFile.test.js +10 -3
  95. package/test/type.journey.test.js +38 -11
  96. package/test/type.mobileKeyword.test.js +6 -4
  97. package/test/type.mobileMessage.test.js +6 -4
  98. package/test/type.query.test.js +8 -6
  99. package/test/type.script.test.js +6 -1
  100. package/test/type.transactionalEmail.test.js +12 -11
  101. package/test/type.transactionalPush.test.js +2 -4
  102. package/test/type.transactionalSMS.test.js +2 -4
  103. package/test/type.triggeredSend.test.js +6 -4
  104. package/test/type.verification.test.js +173 -0
  105. package/test/utils.js +7 -1
  106. package/types/mcdev.d.js +14 -0
@@ -38,6 +38,7 @@ const MetadataTypeDefinitions = {
38
38
  transactionalSMS: require('./metadataTypes/definitions/TransactionalSMS.definition'),
39
39
  triggeredSend: require('./metadataTypes/definitions/TriggeredSend.definition'),
40
40
  user: require('./metadataTypes/definitions/User.definition'),
41
+ verification: require('./metadataTypes/definitions/Verification.definition'),
41
42
  };
42
43
 
43
44
  module.exports = MetadataTypeDefinitions;
@@ -38,6 +38,7 @@ const MetadataTypeInfo = {
38
38
  transactionalSMS: require('./metadataTypes/TransactionalSMS'),
39
39
  triggeredSend: require('./metadataTypes/TriggeredSend'),
40
40
  user: require('./metadataTypes/User'),
41
+ verification: require('./metadataTypes/Verification'),
41
42
  };
42
43
 
43
44
  module.exports = MetadataTypeInfo;
package/lib/cli.js CHANGED
@@ -45,7 +45,7 @@ yargs
45
45
  },
46
46
  })
47
47
  .command({
48
- command: 'deploy [BU] [TYPE] [KEY] [--fromRetrieve] [--refresh]',
48
+ command: 'deploy [BU] [TYPE] [KEY]',
49
49
  aliases: ['d'],
50
50
  desc: 'deploys local metadata to a business unit',
51
51
  builder: (yargs) => {
@@ -97,6 +97,11 @@ yargs
97
97
  group: 'Options for deploy:',
98
98
  describe:
99
99
  'optionally start existing schedule instead of running item once immediately (only works for automations)',
100
+ })
101
+ .option('fixShared', {
102
+ group: 'Options for deploy:',
103
+ describe:
104
+ "optionally ensure that updates to shared DataExtensions become visible in child BU's data designer (SF Known issue W-11031095)",
100
105
  });
101
106
  },
102
107
  handler: (argv) => {
package/lib/index.js CHANGED
@@ -56,12 +56,14 @@ class Mcdev {
56
56
  'commitHistory',
57
57
  'execute',
58
58
  'filter',
59
+ 'fixShared',
59
60
  'fromRetrieve',
60
61
  'json',
61
62
  'like',
62
63
  'noLogColors',
63
64
  'noLogFile',
64
65
  'refresh',
66
+ '_runningTest',
65
67
  'schedule',
66
68
  'skipInteraction',
67
69
  ];
@@ -310,7 +312,8 @@ class Mcdev {
310
312
  triggeredSend: 'triggeredSendDefinition',
311
313
  user: 'accountUser',
312
314
  };
313
- Util.logger.info(`:: Retrieving ${cred}/${bu}\n`);
315
+ Util.logger.info('');
316
+ Util.logger.info(`:: Retrieving ${cred}/${bu}`);
314
317
  const retrieveTypesArr = [];
315
318
  if (selectedTypesArr) {
316
319
  for (const selectedType of Array.isArray(selectedTypesArr)
@@ -31,13 +31,7 @@ class AttributeSet extends MetadataType {
31
31
  const result = await AttributeGroup.retrieveForCache();
32
32
  cache.setMetadata('attributeGroup', result.metadata);
33
33
  }
34
- return super.retrieveREST(
35
- retrieveDir,
36
- '/hub/v1/contacts/schema/setDefinitions',
37
- null,
38
- null,
39
- key
40
- );
34
+ return super.retrieveREST(retrieveDir, '/hub/v1/contacts/schema/setDefinitions', null, key);
41
35
  }
42
36
  /**
43
37
  * Retrieves Metadata of schema set definitions for caching.
@@ -47,6 +41,110 @@ class AttributeSet extends MetadataType {
47
41
  static retrieveForCache() {
48
42
  return super.retrieveREST(null, '/hub/v1/contacts/schema/setDefinitions');
49
43
  }
44
+ /**
45
+ * used to identify updated shared data extensions that are used in attributeSets.
46
+ * helper for DataExtension.#fixShared_onBU
47
+ *
48
+ * @param {Object.<string, string>} sharedDataExtensionMap ID-Key relationship of shared data extensions
49
+ * @param {object} fixShared_fields DataExtensionField.fixShared_fields
50
+ * @returns {Promise.<string[]>} Promise of list of shared dataExtension IDs
51
+ */
52
+ static async fixShared_retrieve(sharedDataExtensionMap, fixShared_fields) {
53
+ if (!Object.keys(sharedDataExtensionMap).length) {
54
+ return [];
55
+ }
56
+ const result = await super.retrieveREST(null, '/hub/v1/contacts/schema/setDefinitions');
57
+ const metadataMap = result?.metadata;
58
+ if (metadataMap && Object.keys(metadataMap).length) {
59
+ const sharedDeIds = Object.keys(metadataMap)
60
+ .filter(
61
+ (asKey) =>
62
+ metadataMap[asKey].storageLogicalType === 'ExactTargetSchema' ||
63
+ metadataMap[asKey].storageLogicalType === 'DataExtension'
64
+ )
65
+ .filter((asKey) => {
66
+ // check if dataExtension ID is found on any attributeSet of this BU
67
+ if (sharedDataExtensionMap[metadataMap[asKey].storageReferenceID.value]) {
68
+ Util.logger.debug(
69
+ ` shared dataExtension ID ${metadataMap[asKey].storageReferenceID.value} found in attributeSet ${asKey}`
70
+ );
71
+ return true;
72
+ } else {
73
+ return false;
74
+ }
75
+ })
76
+ .filter((asKey) => {
77
+ // check if any of the dataExtension fields dont exist on the attributeSet or are out of date
78
+ const deKey =
79
+ sharedDataExtensionMap[metadataMap[asKey].storageReferenceID.value];
80
+ const asFields = metadataMap[asKey].valueDefinitions;
81
+ const deFields = Object.values(fixShared_fields[deKey]);
82
+ return deFields.some((deField) => {
83
+ const search = asFields.filter((asf) => asf.name === deField.Name);
84
+ if (!search.length) {
85
+ Util.logger.debug(
86
+ Util.getGrayMsg(
87
+ ` - Field ${deField.Name} not found in attributeSet; Note: only first recognized difference is printed to log`
88
+ )
89
+ );
90
+ return true;
91
+ }
92
+ const asField = search[0];
93
+ if (asField.dataType !== deField.FieldType) {
94
+ Util.logger.debug(
95
+ Util.getGrayMsg(
96
+ ` - Field ${deField.Name} FieldType changed (old: ${asField.dataType}; new: ${deField.FieldType}); Note: only first recognized difference is printed to log`
97
+ )
98
+ );
99
+ return true;
100
+ }
101
+ asField.defaultValue ||= '';
102
+ if (
103
+ (asField.defaultValue && deField.DefaultValue === '') ||
104
+ (deField.FieldType === 'Boolean' &&
105
+ deField.DefaultValue !== '' &&
106
+ (deField.DefaultValue
107
+ ? 'True'
108
+ : 'False' !== asField.defaultValue)) ||
109
+ (deField.FieldType !== 'Boolean' &&
110
+ deField.DefaultValue !== asField.defaultValue)
111
+ ) {
112
+ Util.logger.debug(
113
+ ` - Field ${deField.Name} DefaultValue changed (old: ${asField.defaultValue}; new: ${deField.DefaultValue}); Note: only first recognized difference is printed to log`
114
+ );
115
+ return true;
116
+ }
117
+ // some field types don't carry the length property. reset to 0 to ease comparison
118
+ asField.length ||= 0;
119
+ if (asField.length !== deField.MaxLength) {
120
+ Util.logger.debug(
121
+ ` - Field ${deField.Name} MaxLength changed (old: ${asField.length}; new: ${deField.MaxLength}); Note: only first recognized difference is printed to log`
122
+ );
123
+ return true;
124
+ }
125
+ if (asField.isNullable !== deField.IsRequired) {
126
+ Util.logger.debug(
127
+ ` - Field ${deField.Name} IsRequired changed (old: ${asField.isNullable}; new: ${deField.IsRequired}); Note: only first recognized difference is printed to log`
128
+ );
129
+ return true;
130
+ }
131
+ if (asField.isPrimaryKey !== deField.IsPrimaryKey) {
132
+ Util.logger.debug(
133
+ ` - Field ${deField.Name} IsPrimaryKey changed (old: ${asField.isPrimaryKey}; new: ${deField.IsPrimaryKey}); Note: only first recognized difference is printed to log`
134
+ );
135
+ return true;
136
+ }
137
+ return false;
138
+ });
139
+ })
140
+ .map((key) => metadataMap[key].storageReferenceID.value)
141
+ .filter(Boolean);
142
+ return sharedDeIds;
143
+ } else {
144
+ // nothing to do - return empty array
145
+ return [];
146
+ }
147
+ }
50
148
 
51
149
  /**
52
150
  * Builds map of metadata entries mapped to their keyfields
@@ -83,7 +181,7 @@ class AttributeSet extends MetadataType {
83
181
  switch (metadata.storageLogicalType) {
84
182
  case 'ExactTargetSchema': // synced / shared DEs
85
183
  case 'DataExtension': {
86
- // local DEs
184
+ // shared / local DEs
87
185
  try {
88
186
  metadata.r__dataExtension_CustomerKey = cache.searchForField(
89
187
  'dataExtension',
@@ -226,6 +324,12 @@ class AttributeSet extends MetadataType {
226
324
  // Member ID
227
325
  delete metadata.customObjectOwnerMID;
228
326
 
327
+ // remove duplicate ID fields (main field is definitionID)
328
+ delete metadata.setDefinitionID;
329
+ if (metadata.dataRetentionProperties?.setDefinitionID) {
330
+ delete metadata.dataRetentionProperties?.setDefinitionID;
331
+ }
332
+
229
333
  // connectingID.identifierType seems to be always set to 'FullyQualifiedName' - to be sure we check it here and remove it if it's the case
230
334
  if (metadata.connectingID?.identifierType === 'FullyQualifiedName') {
231
335
  // remove useless field
@@ -241,8 +345,11 @@ class AttributeSet extends MetadataType {
241
345
  * @returns {object[]} all system value definitions
242
346
  */
243
347
  static _getSystemValueDefinitions() {
244
- if (!this.systemValueDefinitions) {
245
- this.systemValueDefinitions = Object.values(cache.getCache()['attributeSet'])
348
+ this.systemValueDefinitions ||= {};
349
+ if (!this.systemValueDefinitions[this.buObject.mid]) {
350
+ this.systemValueDefinitions[this.buObject.mid] = Object.values(
351
+ cache.getCache()['attributeSet']
352
+ )
246
353
  .flatMap((item) => {
247
354
  if (item.isSystemDefined) {
248
355
  return item.valueDefinitions;
@@ -250,7 +357,7 @@ class AttributeSet extends MetadataType {
250
357
  })
251
358
  .filter(Boolean);
252
359
  }
253
- return this.systemValueDefinitions;
360
+ return this.systemValueDefinitions[this.buObject.mid];
254
361
  }
255
362
  }
256
363
 
@@ -25,71 +25,53 @@ class Automation extends MetadataType {
25
25
  * @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
26
26
  */
27
27
  static async retrieve(retrieveDir, _, __, key) {
28
- /** @type {TYPE.SoapRequestParams} */
29
- let requestParams = null;
30
- if (key) {
31
- requestParams = {
32
- filter: {
33
- leftOperand: 'CustomerKey',
34
- operator: 'equals',
35
- rightOperand: key,
36
- },
37
- };
38
- }
39
- const results = await this.client.soap.retrieveBulk('Program', ['ObjectID'], requestParams);
40
- if (results.Results?.length && !key) {
41
- // empty results will come back without "Results" defined
42
- Util.logger.info(
43
- Util.getGrayMsg(
44
- ` - ${results.Results?.length} automation${
45
- results.Results?.length === 1 ? '' : 's'
46
- } found. Retrieving details...`
47
- )
28
+ let metadataMap;
29
+ if (key && this._cachedMetadataMap?.[key]) {
30
+ metadataMap = {};
31
+ metadataMap[key] = this._cachedMetadataMap[key];
32
+ delete this._cachedMetadataMap;
33
+ } else if (!key && this._cachedMetadataMap) {
34
+ metadataMap = this._cachedMetadataMap;
35
+ delete this._cachedMetadataMap;
36
+ } else {
37
+ /** @type {TYPE.SoapRequestParams} */
38
+ let requestParams = null;
39
+ if (key) {
40
+ requestParams = {
41
+ filter: {
42
+ leftOperand: 'CustomerKey',
43
+ operator: 'equals',
44
+ rightOperand: key,
45
+ },
46
+ };
47
+ }
48
+ const results = await this.client.soap.retrieveBulk(
49
+ 'Program',
50
+ ['ObjectID'],
51
+ requestParams
48
52
  );
49
- }
50
- // the API seems to handle 50 concurrent requests nicely
51
- const rateLimit = pLimit(50);
52
-
53
- const details = results.Results
54
- ? await Promise.all(
55
- results.Results.map(async (item) =>
56
- rateLimit(async () => {
57
- try {
58
- return await this.client.rest.get(
59
- '/automation/v1/automations/' + item.ObjectID
60
- );
61
- } catch (ex) {
62
- try {
63
- if (ex.message == 'socket hang up') {
64
- // one more retry; it's a rare case but retrying again should solve the issue gracefully
65
- return await this.client.rest.get(
66
- '/automation/v1/automations/' + item.ObjectID
67
- );
68
- }
69
- } catch {
70
- // no extra action needed, handled below
71
- }
72
- // if we do get here, we should log the error and continue instead of failing to download all automations
73
- Util.logger.error(
74
- ` ☇ skipping Automation ${item.ObjectID}: ${ex.message} ${ex.code}`
75
- );
76
- return null;
77
- }
78
- })
53
+ // the API seems to handle 50 concurrent requests nicely
54
+ const response = results?.Results?.length
55
+ ? await this.retrieveRESTcollection(
56
+ results?.Results.map((item) => ({
57
+ id: item.ObjectID,
58
+ uri: '/automation/v1/automations/' + item.ObjectID,
59
+ })),
60
+ 50,
61
+ !key
79
62
  )
80
- )
81
- : [];
82
-
83
- // * if retrieving some automations fails, a null element would remain in the details-array for each of them that needs to be filtered to prevent it from causing issues elsewhere
84
- let metadataMap = this.parseResponseBody({ items: details.filter(Boolean) });
63
+ : null;
64
+ metadataMap = response?.metadata || {};
65
+ }
85
66
 
86
- if (Object.keys(metadataMap).length) {
67
+ if (!this._skipNotificationRetrieve && Object.keys(metadataMap).length) {
87
68
  // attach notification information to each automation that has any
88
69
  await this.#getAutomationNotificationsREST(metadataMap);
89
70
  }
90
71
 
91
72
  // * retrieveDir can be empty when we use it in the context of postDeployTasks
92
73
  if (retrieveDir) {
74
+ this.retrieveDir = retrieveDir;
93
75
  metadataMap = await this.saveResults(metadataMap, retrieveDir, null, null);
94
76
  Util.logger.info(
95
77
  `Downloaded: ${this.definition.type} (${Object.keys(metadataMap).length})` +
@@ -101,6 +83,27 @@ class Automation extends MetadataType {
101
83
  return { metadata: metadataMap, type: this.definition.type };
102
84
  }
103
85
 
86
+ /**
87
+ * helper for {@link this.retrieveRESTcollection}
88
+ *
89
+ * @param {Error} ex exception
90
+ * @param {string} id id or key of item
91
+ * @returns {null} -
92
+ */
93
+ static async handleRESTErrors(ex, id) {
94
+ try {
95
+ if (ex.message == 'socket hang up') {
96
+ // one more retry; it's a rare case but retrying again should solve the issue gracefully
97
+ return await this.client.rest.get('/automation/v1/automations/' + id);
98
+ }
99
+ } catch {
100
+ // no extra action needed, handled below
101
+ }
102
+ // if we do get here, we should log the error and continue instead of failing to download all automations
103
+ Util.logger.error(` ☇ skipping Automation ${id}: ${ex.message} ${ex.code}`);
104
+ return null;
105
+ }
106
+
104
107
  /**
105
108
  * helper for {@link Automation.retrieve} to get Automation Notifications
106
109
  *
@@ -230,12 +233,18 @@ class Automation extends MetadataType {
230
233
  * @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
231
234
  */
232
235
  static async retrieveForCache() {
233
- // get automations for cache
234
- const results = await this.client.soap.retrieveBulk('Program', [
235
- 'ObjectID',
236
- 'CustomerKey',
237
- 'Name',
238
- ]);
236
+ let results = {};
237
+ if (this._cachedMetadataMap) {
238
+ results.Results = Object.values(this._cachedMetadataMap);
239
+ delete this._cachedMetadataMap;
240
+ } else {
241
+ // get automations for cache
242
+ results = await this.client.soap.retrieveBulk('Program', [
243
+ 'ObjectID',
244
+ 'CustomerKey',
245
+ 'Name',
246
+ ]);
247
+ }
239
248
  const resultsConverted = {};
240
249
  if (Array.isArray(results?.Results)) {
241
250
  // get encodedAutomationID to retrieve notification information
@@ -252,16 +261,16 @@ class Automation extends MetadataType {
252
261
 
253
262
  // merge encodedAutomationID into results
254
263
  for (const m of results.Results) {
255
- resultsConverted[m.CustomerKey] = {
256
- id: m.ObjectID,
257
- key: m.CustomerKey,
258
- name: m.Name,
259
- programId: automationsLegacy.metadata[m.CustomerKey]?.id,
260
- status: automationsLegacy.metadata[m.CustomerKey]?.status,
264
+ const key = m.CustomerKey || m.key;
265
+ resultsConverted[key] = {
266
+ id: m.ObjectID || m.id,
267
+ key: key,
268
+ name: m.Name || m.name,
269
+ programId: automationsLegacy.metadata[key]?.id,
270
+ status: automationsLegacy.metadata[key]?.status,
261
271
  };
262
272
  }
263
273
  }
264
-
265
274
  return { metadata: resultsConverted, type: this.definition.type };
266
275
  }
267
276
 
@@ -423,6 +432,7 @@ class Automation extends MetadataType {
423
432
  } due to missing activityObjectId: ${JSON.stringify(activity)}`
424
433
  );
425
434
  // empty if block
435
+ continue;
426
436
  } else if (!this.definition.dependencies.includes(activity.r__type)) {
427
437
  Util.logger.debug(
428
438
  ` - skipping ${
@@ -433,9 +443,10 @@ class Automation extends MetadataType {
433
443
  activity.r__type
434
444
  } is not set up as a dependency for ${this.definition.type}`
435
445
  );
446
+ continue;
436
447
  }
437
448
  // / if managed by cache we can update references to support deployment
438
- else if (
449
+ if (
439
450
  Definitions[activity.r__type]?.['idField'] &&
440
451
  cache.getCache(this.buObject.mid)[activity.r__type]
441
452
  ) {
@@ -803,6 +814,7 @@ class Automation extends MetadataType {
803
814
  delete metadata.schedule;
804
815
  delete metadata.type;
805
816
  let i = 0;
817
+ const buName = this.buObject.credential + '/' + this.buObject.businessUnit;
806
818
  if (metadata.steps) {
807
819
  for (const step of metadata.steps) {
808
820
  let displayOrder = 0;
@@ -812,6 +824,23 @@ class Automation extends MetadataType {
812
824
  activity.name &&
813
825
  this.definition.dependencies.includes(activity.r__type)
814
826
  ) {
827
+ if (
828
+ activity.r__type === 'verification' &&
829
+ this.createdKeyMap?.[buName]?.verification?.[activity.name]
830
+ ) {
831
+ Util.logger.info(
832
+ Util.getGrayMsg(
833
+ ` - updated verification activity name from ${
834
+ activity.name
835
+ } to ${
836
+ this.createdKeyMap[buName].verification[activity.name]
837
+ }`
838
+ )
839
+ );
840
+ // map structure: cred/bu --> type --> old key --> new key
841
+ activity.name =
842
+ this.createdKeyMap[buName].verification[activity.name];
843
+ }
815
844
  // automations can have empty placeholder for activities with only their type defined
816
845
  activity.activityObjectId = cache.searchForField(
817
846
  activity.r__type,