mcdev 4.1.12 → 4.2.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 (119) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +2 -3
  4. package/.nycrc.json +5 -0
  5. package/README.md +34 -1528
  6. package/boilerplate/config.json +2 -6
  7. package/boilerplate/files/.vscode/extensions.json +1 -0
  8. package/boilerplate/files/.vscode/settings.json +3 -0
  9. package/docs/dist/documentation.md +437 -31
  10. package/lib/Deployer.js +10 -8
  11. package/lib/MetadataTypeDefinitions.js +3 -0
  12. package/lib/MetadataTypeInfo.js +3 -0
  13. package/lib/Retriever.js +14 -7
  14. package/lib/cli.js +1 -0
  15. package/lib/index.js +6 -6
  16. package/lib/metadataTypes/AccountUser.js +2 -2
  17. package/lib/metadataTypes/Asset.js +45 -35
  18. package/lib/metadataTypes/Automation.js +4 -4
  19. package/lib/metadataTypes/DataExtension.js +14 -8
  20. package/lib/metadataTypes/DataExtensionField.js +44 -9
  21. package/lib/metadataTypes/Discovery.js +5 -5
  22. package/lib/metadataTypes/Folder.js +30 -6
  23. package/lib/metadataTypes/List.js +115 -17
  24. package/lib/metadataTypes/MetadataType.js +73 -40
  25. package/lib/metadataTypes/Query.js +2 -2
  26. package/lib/metadataTypes/Script.js +2 -2
  27. package/lib/metadataTypes/TransactionalEmail.js +163 -0
  28. package/lib/metadataTypes/TransactionalMessage.js +127 -0
  29. package/lib/metadataTypes/TransactionalPush.js +77 -0
  30. package/lib/metadataTypes/TransactionalSMS.js +354 -0
  31. package/lib/metadataTypes/TriggeredSendDefinition.js +11 -9
  32. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +145 -0
  33. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +109 -0
  34. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +103 -0
  35. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +36 -36
  36. package/lib/util/auth.js +2 -2
  37. package/lib/util/businessUnit.js +1 -1
  38. package/lib/util/cli.js +19 -20
  39. package/lib/util/config.js +13 -12
  40. package/lib/util/devops.js +4 -4
  41. package/lib/util/init.config.js +7 -7
  42. package/lib/util/init.git.js +11 -23
  43. package/lib/util/init.js +67 -3
  44. package/lib/util/util.js +20 -12
  45. package/package.json +19 -12
  46. package/test/dataExtension.test.js +36 -19
  47. package/test/mockRoot/.mcdevrc.json +13 -2
  48. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +27 -7
  49. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  50. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +3 -1
  51. package/test/mockRoot/deploy/testInstance/testBU/query/{testQuery.query-meta.json → testNewQuery.query-meta.json} +3 -3
  52. package/test/mockRoot/deploy/testInstance/testBU/query/{testQuery.query-meta.sql → testNewQuery.query-meta.sql} +0 -0
  53. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +24 -0
  54. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +24 -0
  55. package/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testExisting_tpush.transactionalPush-meta.json +18 -0
  56. package/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testNew_tpush.transactionalPush-meta.json +18 -0
  57. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.amp +4 -0
  58. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json +15 -0
  59. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.amp +4 -0
  60. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json +15 -0
  61. package/test/query.test.js +57 -23
  62. package/test/resources/1111111/businessUnit/retrieve-response.xml +33 -0
  63. package/test/resources/1111111/list/retrieve-response.xml +39 -0
  64. package/test/resources/9999999/asset/v1/content/assets/query/post-response.json +72 -0
  65. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +2 -2
  66. package/test/resources/9999999/automation/v1/queries/get-response.json +2 -2
  67. package/test/resources/9999999/automation/v1/queries/post-response.json +3 -3
  68. package/test/resources/9999999/dataExtension/build-expected.json +2 -2
  69. package/test/resources/9999999/dataFolder/retrieve-response.xml +95 -2
  70. package/test/resources/9999999/interaction/v1/interactions/get-response.json +296 -0
  71. package/test/resources/9999999/legacy/v1/beta/mobile/code/get-response.json +32 -0
  72. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +46 -0
  73. package/test/resources/9999999/list/retrieve-response.xml +78 -0
  74. package/test/resources/9999999/messaging/v1/email/definitions/get-response.json +15 -0
  75. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +19 -0
  76. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +26 -0
  77. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +26 -0
  78. package/test/resources/9999999/messaging/v1/push/definitions/get-response.json +15 -0
  79. package/test/resources/9999999/messaging/v1/push/definitions/post-response.json +13 -0
  80. package/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/get-response.json +13 -0
  81. package/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/patch-response.json +13 -0
  82. package/test/resources/9999999/messaging/v1/sms/definitions/get-response.json +15 -0
  83. package/test/resources/9999999/messaging/v1/sms/definitions/post-response.json +17 -0
  84. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json +18 -0
  85. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json +18 -0
  86. package/test/resources/9999999/query/build-expected.json +2 -2
  87. package/test/resources/9999999/query/build-expected.sql +6 -0
  88. package/test/resources/9999999/query/get-expected.json +1 -1
  89. package/test/resources/9999999/query/get-expected.sql +6 -0
  90. package/test/resources/9999999/query/patch-expected.json +1 -1
  91. package/test/resources/9999999/query/patch-expected.sql +6 -0
  92. package/test/resources/9999999/query/post-expected.json +3 -3
  93. package/test/resources/9999999/query/post-expected.sql +4 -0
  94. package/test/resources/9999999/query/template-expected.json +1 -1
  95. package/test/resources/9999999/query/template-expected.sql +6 -0
  96. package/test/resources/9999999/transactionalEmail/build-expected.json +22 -0
  97. package/test/resources/9999999/transactionalEmail/get-expected.json +24 -0
  98. package/test/resources/9999999/transactionalEmail/patch-expected.json +24 -0
  99. package/test/resources/9999999/transactionalEmail/post-expected.json +24 -0
  100. package/test/resources/9999999/transactionalEmail/template-expected.json +22 -0
  101. package/test/resources/9999999/transactionalPush/build-expected.json +8 -0
  102. package/test/resources/9999999/transactionalPush/get-expected.json +11 -0
  103. package/test/resources/9999999/transactionalPush/patch-expected.json +16 -0
  104. package/test/resources/9999999/transactionalPush/post-expected.json +16 -0
  105. package/test/resources/9999999/transactionalPush/template-expected.json +8 -0
  106. package/test/resources/9999999/transactionalSMS/build-expected.amp +4 -0
  107. package/test/resources/9999999/transactionalSMS/build-expected.json +13 -0
  108. package/test/resources/9999999/transactionalSMS/get-expected.amp +4 -0
  109. package/test/resources/9999999/transactionalSMS/get-expected.json +15 -0
  110. package/test/resources/9999999/transactionalSMS/patch-expected.amp +4 -0
  111. package/test/resources/9999999/transactionalSMS/patch-expected.json +15 -0
  112. package/test/resources/9999999/transactionalSMS/post-expected.amp +4 -0
  113. package/test/resources/9999999/transactionalSMS/post-expected.json +15 -0
  114. package/test/resources/9999999/transactionalSMS/template-expected.amp +4 -0
  115. package/test/resources/9999999/transactionalSMS/template-expected.json +13 -0
  116. package/test/transactionalEmail.test.js +120 -0
  117. package/test/transactionalPush.test.js +120 -0
  118. package/test/transactionalSMS.test.js +149 -0
  119. package/test/utils.js +57 -8
@@ -21,16 +21,19 @@ class Folder extends MetadataType {
21
21
  * @param {TYPE.BuObject} buObject properties for auth
22
22
  * @param {void} [___] unused parameter
23
23
  * @param {string} [key] customer key of single item to retrieve
24
+ * @param {string[]} [contentTypeList] content type of folder
24
25
  * @returns {Promise} Promise
25
26
  */
26
- static async retrieve(retrieveDir, additionalFields, buObject, ___, key) {
27
+ static async retrieve(retrieveDir, additionalFields, buObject, ___, key, contentTypeList) {
27
28
  if (key) {
28
29
  Util.logger.error(`Folder.retrieve() does not support key parameter`);
29
30
  }
30
- const queryAllFolders = await this.retrieveHelper(additionalFields, true);
31
+ const queryAllFolders = await this.retrieveHelper(additionalFields, true, contentTypeList);
31
32
 
32
33
  if (buObject.eid !== buObject.mid) {
33
- queryAllFolders.push(...(await this.retrieveHelper(additionalFields, false)));
34
+ queryAllFolders.push(
35
+ ...(await this.retrieveHelper(additionalFields, false, contentTypeList))
36
+ );
34
37
  }
35
38
  const sortPairs = toposort(queryAllFolders.map((a) => [a.ParentFolder.ID, a.ID]));
36
39
  const idMap = {};
@@ -150,10 +153,11 @@ class Folder extends MetadataType {
150
153
  * Retrieves folder metadata for caching
151
154
  *
152
155
  * @param {TYPE.BuObject} buObject properties for auth
156
+ * @param {string[]} [contentTypeList] content type of folder
153
157
  * @returns {Promise} Promise
154
158
  */
155
- static retrieveForCache(buObject) {
156
- return this.retrieve(null, null, buObject);
159
+ static retrieveForCache(buObject, contentTypeList) {
160
+ return this.retrieve(null, null, buObject, null, null, contentTypeList);
157
161
  }
158
162
 
159
163
  /**
@@ -515,10 +519,30 @@ class Folder extends MetadataType {
515
519
  *
516
520
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
517
521
  * @param {boolean} [queryAllAccounts] which queryAllAccounts setting to use
522
+ * @param {string[]} [contentTypeList] content type of folder
518
523
  * @returns {Promise.<object>} soap object
519
524
  */
520
- static async retrieveHelper(additionalFields, queryAllAccounts) {
525
+ static async retrieveHelper(additionalFields, queryAllAccounts, contentTypeList) {
521
526
  const options = { QueryAllAccounts: !!queryAllAccounts };
527
+ if (contentTypeList) {
528
+ for (const contentType of contentTypeList) {
529
+ options.filter = options.filter
530
+ ? {
531
+ leftOperand: {
532
+ leftOperand: 'ContentType',
533
+ operator: 'equals',
534
+ rightOperand: contentType,
535
+ },
536
+ operator: 'OR',
537
+ rightOperand: options.filter,
538
+ }
539
+ : {
540
+ leftOperand: 'ContentType',
541
+ operator: 'equals',
542
+ rightOperand: contentType,
543
+ };
544
+ }
545
+ }
522
546
  const response = await this.client.soap.retrieveBulk(
523
547
  'DataFolder',
524
548
  this.getFieldNamesToRetrieve(additionalFields).filter(
@@ -2,8 +2,10 @@
2
2
 
3
3
  const TYPE = require('../../types/mcdev.d');
4
4
  const MetadataType = require('./MetadataType');
5
+ const Folder = require('./Folder');
5
6
  const Util = require('../util/util');
6
7
  const cache = require('../util/cache');
8
+ const auth = require('../util/auth');
7
9
 
8
10
  /**
9
11
  * List MetadataType
@@ -16,12 +18,12 @@ class List extends MetadataType {
16
18
  *
17
19
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
18
20
  * @param {void} [_] unused parameter
19
- * @param {void} [__] unused parameter
21
+ * @param {TYPE.BuObject} buObject properties for auth
20
22
  * @param {void} [___] unused parameter
21
23
  * @param {string} [key] customer key of single item to retrieve
22
24
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise
23
25
  */
24
- static retrieve(retrieveDir, _, __, ___, key) {
26
+ static async retrieve(retrieveDir, _, buObject, ___, key) {
25
27
  /** @type {TYPE.SoapRequestParams} */
26
28
  let requestParams = null;
27
29
  if (key) {
@@ -42,21 +44,112 @@ class List extends MetadataType {
42
44
  },
43
45
  };
44
46
  }
45
- return super.retrieveSOAP(retrieveDir, null, requestParams);
47
+ const results = await super.retrieveSOAP(retrieveDir, null, requestParams);
48
+ return await this._retrieveParentAllSubs(buObject, results);
46
49
  }
47
50
  /**
48
51
  * Gets metadata cache with limited fields and does not store value to disk
49
52
  *
53
+ * @param {TYPE.BuObject} buObject properties for auth
50
54
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
51
55
  */
52
- static async retrieveForCache() {
53
- const results = await this.retrieve(null);
56
+ static async retrieveForCache(buObject) {
57
+ const results = await this.retrieve(null, null, buObject);
58
+ if (!cache.getCache()?.folder) {
59
+ Util.logger.debug('folders not cached but required for list');
60
+ Util.logger.info(' - Caching dependent Metadata: folder');
61
+ Folder.client = this.client;
62
+ Folder.properties = this.properties;
63
+ const result = await Folder.retrieveForCache(buObject, [
64
+ 'list',
65
+ 'mysubs',
66
+ 'suppression_list',
67
+ 'publication',
68
+ 'contextual_suppression_list',
69
+ ]);
70
+ cache.setMetadata('folder', result.metadata);
71
+ }
54
72
  for (const metadataEntry in results.metadata) {
55
73
  this.parseMetadata(results.metadata[metadataEntry], true);
56
74
  }
57
75
  return results;
58
76
  }
59
77
 
78
+ /**
79
+ * helper for @link retrieveForCache and @link retrieve
80
+ *
81
+ * @param {TYPE.BuObject} buObject properties for auth
82
+ * @param {TYPE.MetadataTypeMapObj} results metadata from retrieve for current BU
83
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise
84
+ */
85
+ static async _retrieveParentAllSubs(buObject, results) {
86
+ if (buObject.eid !== buObject.mid) {
87
+ // for caching, we want to get the All Subscriber List from the Parent Account
88
+ Util.logger.debug(' - Checking MasterUnsubscribeBehavior for current BU');
89
+ /** @type {TYPE.BuObject} */
90
+ const buObjectParentBu = {
91
+ eid: this.properties.credentials[buObject.credential].eid,
92
+ mid: this.properties.credentials[buObject.credential].eid,
93
+ businessUnit: Util.parentBuName,
94
+ credential: buObject.credential,
95
+ };
96
+ try {
97
+ this.client = auth.getSDK(buObjectParentBu);
98
+ } catch (ex) {
99
+ Util.logger.error(ex.message);
100
+ return;
101
+ }
102
+ const buResult = await this.client.soap.retrieve(
103
+ 'BusinessUnit',
104
+ ['MasterUnsubscribeBehavior'],
105
+ {
106
+ QueryAllAccounts: true,
107
+ filter: {
108
+ leftOperand: 'ID',
109
+ operator: 'equals',
110
+ rightOperand: this.properties.credentials[buObject.credential].eid,
111
+ },
112
+ }
113
+ );
114
+ const masterUnsubscribeBehavior = buResult.Results[0]?.MasterUnsubscribeBehavior;
115
+ if (masterUnsubscribeBehavior === 'ENTIRE_ENTERPRISE') {
116
+ Util.logger.debug(` - BU uses ParentBU's All Subscriber List`);
117
+ Util.logger.info(
118
+ ' - Caching dependent Metadata: All Subscriber list (on _ParentBU_)'
119
+ );
120
+ const metadataParentBu = await this.retrieve(
121
+ null,
122
+ null,
123
+ buObjectParentBu,
124
+ null,
125
+ 'All Subscribers'
126
+ );
127
+ // manually set folder path of parent's All Subscriber List to avoid retrieving folders
128
+ for (const key of Object.keys(metadataParentBu.metadata)) {
129
+ metadataParentBu.metadata[key].r__folder_Path = 'my subscribers';
130
+ }
131
+ // find & delete local All Subscriber list to avoid referencing the wrong one
132
+ for (const key of Object.keys(results.metadata)) {
133
+ if (results.metadata[key].ListName === 'All Subscribers') {
134
+ delete results.metadata[key];
135
+ break;
136
+ }
137
+ }
138
+
139
+ // make sure to overwrite parent bu DEs with local ones
140
+ return {
141
+ metadata: { ...metadataParentBu.metadata, ...results.metadata },
142
+ type: results.type,
143
+ };
144
+ } else if (masterUnsubscribeBehavior === 'BUSINESS_UNIT_ONLY') {
145
+ Util.logger.debug(' - BU uses own All Subscriber List');
146
+ }
147
+ // revert client to current default
148
+ this.client = auth.getSDK(this.buObject);
149
+ }
150
+ return results;
151
+ }
152
+
60
153
  /**
61
154
  * Delete a metadata item from the specified business unit
62
155
  *
@@ -85,20 +178,25 @@ class List extends MetadataType {
85
178
  * @returns {TYPE.MetadataTypeItem} Array with one metadata object and one sql string
86
179
  */
87
180
  static parseMetadata(metadata, parseForCache) {
88
- try {
89
- metadata.r__folder_Path = cache.searchForField(
90
- 'folder',
91
- metadata.Category,
92
- 'ID',
93
- 'Path'
94
- );
95
- if (!parseForCache) {
96
- delete metadata.Category;
181
+ if (!metadata.r__folder_Path) {
182
+ // if we cached all subs from parent bu, we don't need to parse the folder path again here
183
+ try {
184
+ metadata.r__folder_Path = cache.searchForField(
185
+ 'folder',
186
+ metadata.Category,
187
+ 'ID',
188
+ 'Path'
189
+ );
190
+ if (!parseForCache) {
191
+ delete metadata.Category;
192
+ }
193
+ } catch (ex) {
194
+ Util.logger.warn(
195
+ ` - List ${metadata.ID}: '${metadata.CustomerKey}': ${ex.message}`
196
+ );
97
197
  }
98
- return metadata;
99
- } catch (ex) {
100
- Util.logger.warn(` - List ${metadata.ID}: '${metadata.CustomerKey}': ${ex.message}`);
101
198
  }
199
+ return metadata;
102
200
  }
103
201
  }
104
202
  // Assign definition to static attributes
@@ -532,8 +532,8 @@ class MetadataType {
532
532
  (filteredByPreDeploy > 0 ? ` / ${filteredByPreDeploy} filtered` : '')
533
533
  );
534
534
 
535
- // if Results then parse as SOAP
536
535
  if (this.definition.bodyIteratorField === 'Results') {
536
+ // if Results then parse as SOAP
537
537
  // put in Retrieve Format for parsing
538
538
  // todo add handling when response does not contain items.
539
539
  // @ts-ignore
@@ -545,11 +545,12 @@ class MetadataType {
545
545
  .map((r) => r.Object);
546
546
  return this.parseResponseBody({ Results: metadataResults });
547
547
  } else {
548
+ // likely comming from one of the many REST APIs
548
549
  // put in Retrieve Format for parsing
549
550
  // todo add handling when response does not contain items.
550
551
  // @ts-ignore
551
552
  const metadataResults = createResults.concat(updateResults).filter(Boolean);
552
- return this.parseResponseBody({ items: metadataResults });
553
+ return this.parseResponseBody(metadataResults);
553
554
  }
554
555
  }
555
556
 
@@ -613,20 +614,7 @@ class MetadataType {
613
614
  }
614
615
  return response;
615
616
  } catch (ex) {
616
- if (!handleOutside) {
617
- const errorMsg =
618
- ex.results && ex.results.length
619
- ? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
620
- : ex.message;
621
- Util.logger.error(
622
- ` ☇ error creating ${this.definition.type} '${
623
- metadataEntry[this.definition.keyField]
624
- }': ${errorMsg}`
625
- );
626
- } else {
627
- throw ex;
628
- }
629
-
617
+ this._handleSOAPErrors(ex, 'creating', metadataEntry, handleOutside);
630
618
  return {};
631
619
  }
632
620
  }
@@ -692,22 +680,28 @@ class MetadataType {
692
680
  }
693
681
  return response;
694
682
  } catch (ex) {
695
- if (!handleOutside) {
696
- const errorMsg = ex?.json?.Results.length
697
- ? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
698
- : ex.message;
699
- Util.logger.error(
700
- ` ☇ error updating ${this.definition.type} '${
701
- metadataEntry[this.definition.keyField]
702
- }': ${errorMsg}`
703
- );
704
- } else {
705
- throw ex;
706
- }
707
-
683
+ this._handleSOAPErrors(ex, 'updating', metadataEntry, handleOutside);
708
684
  return {};
709
685
  }
710
686
  }
687
+ /**
688
+ *
689
+ * @param {Error} ex error that occured
690
+ * @param {'creating'|'updating'} msg what to print in the log
691
+ * @param {TYPE.MetadataTypeItem} [metadataEntry] single metadata entry
692
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
693
+ */
694
+ static _handleSOAPErrors(ex, msg, metadataEntry, handleOutside) {
695
+ if (handleOutside) {
696
+ throw ex;
697
+ } else {
698
+ const errorMsg = ex?.json?.Results?.length
699
+ ? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
700
+ : ex.message;
701
+ const name = metadataEntry ? ` '${metadataEntry[this.definition.keyField]}'` : '';
702
+ Util.logger.error(` ☇ error ${msg} ${this.definition.type}${name}: ${errorMsg}`);
703
+ }
704
+ }
711
705
  /**
712
706
  * Retrieves SOAP via generic fuel-soap wrapper based metadata of metadata type into local filesystem. executes callback with retrieved metadata
713
707
  *
@@ -720,11 +714,17 @@ class MetadataType {
720
714
  static async retrieveSOAP(retrieveDir, buObject, requestParams, additionalFields) {
721
715
  requestParams = requestParams || {};
722
716
  const fields = this.getFieldNamesToRetrieve(additionalFields);
723
- const response = await this.client.soap.retrieveBulk(
724
- this.definition.type,
725
- fields,
726
- requestParams
727
- );
717
+ let response;
718
+ try {
719
+ response = await this.client.soap.retrieveBulk(
720
+ this.definition.type,
721
+ fields,
722
+ requestParams
723
+ );
724
+ } catch (ex) {
725
+ this._handleSOAPErrors(ex, 'retrieving');
726
+ return {};
727
+ }
728
728
  const metadata = this.parseResponseBody(response);
729
729
 
730
730
  if (retrieveDir) {
@@ -785,9 +785,9 @@ class MetadataType {
785
785
  `Downloaded: ${overrideType || this.definition.type} (${
786
786
  Object.keys(savedMetadata).length
787
787
  })` +
788
- (singleRetrieve !== null
789
- ? ` ${color.dim}(Key: ${singleRetrieve})${color.reset}`
790
- : '')
788
+ (singleRetrieve === null
789
+ ? ''
790
+ : ` ${color.dim}(Key: ${singleRetrieve})${color.reset}`)
791
791
  );
792
792
  }
793
793
 
@@ -1454,7 +1454,7 @@ class MetadataType {
1454
1454
 
1455
1455
  try {
1456
1456
  // write to file
1457
- const targetDirArr = !Array.isArray(targetDir) ? [targetDir] : targetDir;
1457
+ const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
1458
1458
 
1459
1459
  for (const targetDir of targetDirArr) {
1460
1460
  await File.writeJSONToFile(
@@ -1580,15 +1580,48 @@ class MetadataType {
1580
1580
 
1581
1581
  return true;
1582
1582
  } catch (ex) {
1583
- if (!handleOutside) {
1583
+ if (handleOutside) {
1584
+ throw ex;
1585
+ } else {
1584
1586
  const errorMsg = ex?.results?.length
1585
1587
  ? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
1586
1588
  : ex.message;
1587
1589
  Util.logger.error(
1588
1590
  `- error deleting ${this.definition.type} '${customerKey}': ${errorMsg}`
1589
1591
  );
1590
- } else {
1592
+ }
1593
+
1594
+ return false;
1595
+ }
1596
+ }
1597
+ /**
1598
+ * Delete a data extension from the specified business unit
1599
+ *
1600
+ * @param {TYPE.BuObject} buObject references credentials
1601
+ * @param {string} url endpoint
1602
+ * @param {string} key Identifier of metadata
1603
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1604
+ * @returns {boolean} deletion success flag
1605
+ */
1606
+ static async deleteByKeyREST(buObject, url, key, handleOutside) {
1607
+ const keyObj = {};
1608
+ keyObj[this.definition.keyField] = key;
1609
+ try {
1610
+ this.client.rest.delete(url);
1611
+ if (!handleOutside) {
1612
+ Util.logger.info(`- deleted ${this.definition.type}: ${key}`);
1613
+ }
1614
+ this.postDeleteTasks(buObject, key);
1615
+
1616
+ return true;
1617
+ } catch (ex) {
1618
+ if (handleOutside) {
1591
1619
  throw ex;
1620
+ } else {
1621
+ const errorMsg = ex?.results?.length
1622
+ ? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
1623
+ : ex.message;
1624
+ Util.logger.error(`- error deleting ${this.definition.type} '${key}': ${errorMsg}`);
1592
1625
  }
1593
1626
 
1594
1627
  return false;
@@ -120,7 +120,7 @@ class Query extends MetadataType {
120
120
  static async preDeployTasks(metadata, deployDir) {
121
121
  metadata.queryText = await File.readFilteredFilename(
122
122
  deployDir + '/' + this.definition.type,
123
- metadata.key + '.' + this.definition.type + '-meta',
123
+ metadata[this.definition.keyField] + '.' + this.definition.type + '-meta',
124
124
  'sql'
125
125
  );
126
126
  metadata.targetKey = cache.searchForField(
@@ -292,7 +292,7 @@ class Query extends MetadataType {
292
292
  const codeArr = [
293
293
  {
294
294
  subFolder: null,
295
- fileName: metadata.key,
295
+ fileName: metadata[this.definition.keyField],
296
296
  fileExt: 'sql',
297
297
  content: metadata.queryText,
298
298
  },
@@ -93,7 +93,7 @@ class Script extends MetadataType {
93
93
  * @returns {Promise.<string>} content for metadata.script
94
94
  */
95
95
  static async _mergeCode(metadata, deployDir, templateName) {
96
- templateName = templateName || metadata.key;
96
+ templateName = templateName || metadata[this.definition.keyField];
97
97
  let code;
98
98
  const codePath = File.normalizePath([
99
99
  deployDir,
@@ -275,7 +275,7 @@ class Script extends MetadataType {
275
275
  delete metadata.script;
276
276
  codeArr.push({
277
277
  subFolder: null,
278
- fileName: metadata.key,
278
+ fileName: metadata[this.definition.keyField],
279
279
  fileExt: fileExt,
280
280
  content: code,
281
281
  });
@@ -0,0 +1,163 @@
1
+ 'use strict';
2
+
3
+ const TYPE = require('../../types/mcdev.d');
4
+ const TransactionalMessage = require('./TransactionalMessage');
5
+ const Util = require('../util/util');
6
+ const cache = require('../util/cache');
7
+
8
+ /**
9
+ * TransactionalEmail MetadataType
10
+ *
11
+ * @augments TransactionalMessage
12
+ */
13
+ class TransactionalEmail extends TransactionalMessage {
14
+ static subType = 'email';
15
+
16
+ /**
17
+ * Updates a single item
18
+ *
19
+ * @param {TYPE.MetadataTypeItem} metadata how the item shall look after the update
20
+ * @returns {Promise} Promise
21
+ */
22
+ static update(metadata) {
23
+ if (metadata.options?.createJourney) {
24
+ // only send this during create or else we might end up with an unexpected outcome
25
+ Util.logger.warn(
26
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
27
+ metadata[this.definition.keyField]
28
+ }): Cannot update journey link during update. If you need to relink this item to a new journey please delete and recreate it.`
29
+ );
30
+ delete metadata.options.createJourney;
31
+ }
32
+ return super.update(metadata);
33
+ }
34
+
35
+ /**
36
+ * prepares for deployment
37
+ *
38
+ * @param {TYPE.MetadataTypeItem} metadata a single item
39
+ * @returns {TYPE.MetadataTypeItem} Promise
40
+ */
41
+ static async preDeployTasks(metadata) {
42
+ // asset
43
+ if (metadata.content?.customerKey) {
44
+ // we merely want to be able to show an error if it does not exist
45
+ cache.searchForField(
46
+ 'asset',
47
+ metadata.content.customerKey,
48
+ 'customerKey',
49
+ 'customerKey'
50
+ );
51
+ }
52
+ // subscriptions: dataExtension
53
+ if (metadata.subscriptions?.dataExtension) {
54
+ // we merely want to be able to show an error if it does not exist
55
+ cache.searchForField(
56
+ 'dataExtension',
57
+ metadata.subscriptions.dataExtension,
58
+ 'CustomerKey',
59
+ 'CustomerKey'
60
+ );
61
+ }
62
+ // subscriptions: list
63
+ if (metadata.subscriptions?.r__list_PathName) {
64
+ metadata.subscriptions.list = cache.getListObjectId(
65
+ metadata.subscriptions.r__list_PathName,
66
+ 'CustomerKey'
67
+ );
68
+ delete metadata.subscriptions.r__list_PathName;
69
+ }
70
+
71
+ // journey
72
+ if (metadata.journey?.interactionKey) {
73
+ // ! update & create enpoints dont accept journey.interactionKey. They only allow to create a new journey
74
+ // cache.searchForField('interaction', metadata.journey.interactionKey, 'key', 'key');
75
+ metadata.options = metadata.options || {};
76
+ metadata.options.createJourney = true; // only send this during create or else we might end up with an unexpected outcome
77
+ delete metadata.journey.interactionKey;
78
+ }
79
+
80
+ return metadata;
81
+ }
82
+ /**
83
+ * manages post retrieve steps
84
+ *
85
+ * @param {TYPE.MetadataTypeItem} metadata a single item
86
+ * @returns {TYPE.MetadataTypeItem} a single item
87
+ */
88
+ static postRetrieveTasks(metadata) {
89
+ // asset
90
+ if (metadata.content?.customerKey) {
91
+ try {
92
+ // we merely want to be able to show an error if it does not exist
93
+ cache.searchForField(
94
+ 'asset',
95
+ metadata.content.customerKey,
96
+ 'customerKey',
97
+ 'customerKey'
98
+ );
99
+ } catch (ex) {
100
+ Util.logger.warn(
101
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
102
+ metadata[this.definition.keyField]
103
+ }): ${ex.message}.`
104
+ );
105
+ }
106
+ }
107
+ // subscriptions: dataExtension
108
+ if (metadata.subscriptions?.dataExtension) {
109
+ try {
110
+ // we merely want to be able to show a warning if it does not exist
111
+ cache.searchForField(
112
+ 'dataExtension',
113
+ metadata.subscriptions.dataExtension,
114
+ 'CustomerKey',
115
+ 'CustomerKey'
116
+ );
117
+ } catch (ex) {
118
+ Util.logger.warn(
119
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
120
+ metadata[this.definition.keyField]
121
+ }): ${ex.message}.`
122
+ );
123
+ }
124
+ }
125
+ // subscriptions: list
126
+ if (metadata.subscriptions?.list) {
127
+ try {
128
+ // List
129
+ metadata.subscriptions.r__list_PathName = cache.getListPathName(
130
+ metadata.subscriptions.list,
131
+ 'CustomerKey'
132
+ );
133
+ delete metadata.subscriptions.list;
134
+ } catch (ex) {
135
+ Util.logger.warn(
136
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
137
+ metadata[this.definition.keyField]
138
+ }): ${ex.message}.`
139
+ );
140
+ }
141
+ }
142
+ // journey
143
+ if (metadata.journey?.interactionKey) {
144
+ try {
145
+ // we merely want to be able to show a warning if it does not exist
146
+ cache.searchForField('interaction', metadata.journey.interactionKey, 'key', 'key');
147
+ } catch (ex) {
148
+ Util.logger.warn(
149
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
150
+ metadata[this.definition.keyField]
151
+ }): ${ex.message}.`
152
+ );
153
+ }
154
+ }
155
+
156
+ return metadata;
157
+ }
158
+ }
159
+
160
+ // Assign definition to static attributes
161
+ TransactionalEmail.definition = require('../MetadataTypeDefinitions').transactionalEmail;
162
+
163
+ module.exports = TransactionalEmail;