mcdev 8.3.1 → 8.4.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 (70) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/.github/workflows/close_issues_on_merge.yml +4 -0
  3. package/.github/workflows/coverage-base-update.yml +2 -2
  4. package/.github/workflows/coverage-develop-branch.yml +1 -1
  5. package/.github/workflows/coverage-main-branch.yml +1 -1
  6. package/.github/workflows/coverage.yml +2 -2
  7. package/@types/lib/index.d.ts.map +1 -1
  8. package/@types/lib/metadataTypes/DataFilter.d.ts +1 -0
  9. package/@types/lib/metadataTypes/DataFilter.d.ts.map +1 -1
  10. package/@types/lib/metadataTypes/DataFilterHidden.d.ts +1 -0
  11. package/@types/lib/metadataTypes/DataFilterHidden.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/FileLocation.d.ts +74 -0
  13. package/@types/lib/metadataTypes/FileLocation.d.ts.map +1 -1
  14. package/@types/lib/metadataTypes/Journey.d.ts +6 -0
  15. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/Script.d.ts +14 -0
  17. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/definitions/DataFilter.definition.d.ts +1 -0
  19. package/@types/lib/metadataTypes/definitions/DataFilterHidden.definition.d.ts +1 -0
  20. package/@types/lib/metadataTypes/definitions/FileLocation.definition.d.ts +62 -4
  21. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +6 -0
  22. package/@types/lib/metadataTypes/definitions/Script.definition.d.ts +10 -0
  23. package/lib/cli.js +6 -0
  24. package/lib/index.js +1 -0
  25. package/lib/metadataTypes/DataFilter.js +36 -22
  26. package/lib/metadataTypes/Event.js +2 -2
  27. package/lib/metadataTypes/FileLocation.js +204 -4
  28. package/lib/metadataTypes/Journey.js +72 -45
  29. package/lib/metadataTypes/Script.js +16 -0
  30. package/lib/metadataTypes/definitions/DataFilter.definition.js +1 -0
  31. package/lib/metadataTypes/definitions/DataFilterHidden.definition.js +1 -0
  32. package/lib/metadataTypes/definitions/FileLocation.definition.js +52 -7
  33. package/lib/metadataTypes/definitions/Journey.definition.js +6 -0
  34. package/lib/metadataTypes/definitions/Script.definition.js +6 -0
  35. package/package.json +11 -11
  36. package/test/general.test.js +11 -11
  37. package/test/mockRoot/.mcdevrc.json +1 -1
  38. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/ExactTarget Enhanced FTP.fileLocation-meta.json +5 -0
  39. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_aws.fileLocation-meta.json +14 -0
  40. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_exsftp.fileLocation-meta.json +12 -0
  41. package/test/resourceFactory.js +9 -2
  42. package/test/resources/9999999/automation/v1/ftplocations/get-response.json +26 -1
  43. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/get-response.json +10 -0
  44. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +2 -2
  45. package/test/resources/9999999/data/v1/filetransferlocation/Salesforce%20Objects%20%26%20Reports/get-response.json +4 -0
  46. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_aws/patch-response.json +18 -0
  47. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/delete-response.json +4 -0
  48. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/get-response.json +18 -0
  49. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_exsftp/patch-response.json +16 -0
  50. package/test/resources/9999999/data/v1/filetransferlocations/get-response.json +59 -0
  51. package/test/resources/9999999/fileLocation/build-expected.json +14 -0
  52. package/test/resources/9999999/fileLocation/get-aws-expected.json +14 -0
  53. package/test/resources/9999999/fileLocation/get-azure-expected.json +14 -0
  54. package/test/resources/9999999/fileLocation/get-eftp-expected.json +5 -0
  55. package/test/resources/9999999/fileLocation/get-exsftp-expected.json +12 -0
  56. package/test/resources/9999999/fileLocation/get-gcp-expected.json +10 -0
  57. package/test/resources/9999999/fileLocation/get-sor-expected.json +5 -0
  58. package/test/resources/9999999/fileLocation/patch-aws-expected.json +14 -0
  59. package/test/resources/9999999/fileLocation/patch-exsftp-expected.json +12 -0
  60. package/test/resources/9999999/fileLocation/template-expected.json +14 -0
  61. package/test/resources/9999999/interaction/v1/interactions/get-response-status=Published.json +40 -0
  62. package/test/type.automation.test.js +14 -14
  63. package/test/type.dataFilter.test.js +10 -2
  64. package/test/type.fileLocation.test.js +279 -0
  65. package/test/type.fileTransfer.test.js +4 -4
  66. package/test/type.filter.test.js +9 -2
  67. package/test/type.importFile.test.js +5 -5
  68. package/test/type.journey.test.js +26 -0
  69. package/test/type.query.test.js +2 -2
  70. package/test/type.script.test.js +1 -1
@@ -110,8 +110,10 @@ class DataFilter extends MetadataType {
110
110
  */
111
111
  static async _getFilterFolderIds(recached = false) {
112
112
  const fromCache =
113
- this.cache.folderFilter || cache.getCache().folder
114
- ? Object.values(this.cache.folderFilter || cache.getCache().folder)
113
+ this.cache[this.buObject.mid]?.folderFilter || cache.getCache().folder
114
+ ? Object.values(
115
+ this.cache[this.buObject.mid]?.folderFilter || cache.getCache().folder
116
+ )
115
117
  .filter((item) => item.ContentType === 'filterdefinition')
116
118
  .filter(
117
119
  (item) =>
@@ -135,7 +137,10 @@ class DataFilter extends MetadataType {
135
137
  Folder.client = this.client;
136
138
  Folder.buObject = this.buObject;
137
139
  Folder.properties = this.properties;
138
- this.cache.folderFilter = (await Folder.retrieveForCache(null, subTypeArr)).metadata;
140
+ this.cache[this.buObject.mid] ||= {};
141
+ this.cache[this.buObject.mid].folderFilter = (
142
+ await Folder.retrieveForCache(null, subTypeArr)
143
+ ).metadata;
139
144
  return this._getFilterFolderIds(true);
140
145
  }
141
146
  /**
@@ -145,9 +150,9 @@ class DataFilter extends MetadataType {
145
150
  */
146
151
  static async _getMeasureFolderIds() {
147
152
  const fromCache =
148
- this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder
153
+ this.cache[this.buObject.mid]?.folderMeasure || cache.getCache().folder
149
154
  ? Object.values(
150
- this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder
155
+ this.cache[this.buObject.mid]?.folderMeasure || cache.getCache().folder
151
156
  )
152
157
  .filter((item) => item.ContentType === 'measure')
153
158
  .map((item) => item.ID)
@@ -163,8 +168,9 @@ class DataFilter extends MetadataType {
163
168
  Folder.client = this.client;
164
169
  Folder.buObject = this.buObject;
165
170
  Folder.properties = this.properties;
166
- this.cache.folderMeasure ||= {};
167
- this.cache.folderMeasure[this.buObject.mid] = (
171
+ this.cache[this.buObject.mid] ||= {};
172
+ this.cache[this.buObject.mid].folderMeasure ||= {};
173
+ this.cache[this.buObject.mid].folderMeasure = (
168
174
  await Folder.retrieveForCache(null, subTypeArr)
169
175
  ).metadata;
170
176
  return this._getMeasureFolderIds();
@@ -205,8 +211,8 @@ class DataFilter extends MetadataType {
205
211
  .map((item) => item.r__source_dataExtension_key)
206
212
  .filter(
207
213
  (deKey) =>
208
- !this.cache.dataExtensionField ||
209
- !this.cache.dataExtensionField[deKey]
214
+ !this.cache[this.buObject.mid]?.dataExtensionField ||
215
+ !this.cache[this.buObject.mid]?.dataExtensionField[deKey]
210
216
  )
211
217
  .filter(Boolean);
212
218
  if (deKeys.length) {
@@ -246,7 +252,8 @@ class DataFilter extends MetadataType {
246
252
  * @param {DataExtensionFieldMap} deFieldCache -
247
253
  */
248
254
  static saveDataExtensionFieldCacheToMap(deFieldCache) {
249
- this.cache.dataExtensionField ||= {};
255
+ this.cache[this.buObject.mid] ||= {};
256
+ this.cache[this.buObject.mid].dataExtensionField ||= {};
250
257
 
251
258
  if (!deFieldCache) {
252
259
  return;
@@ -255,11 +262,16 @@ class DataFilter extends MetadataType {
255
262
  if (!field?.DataExtension?.CustomerKey) {
256
263
  continue;
257
264
  }
258
- if (!this.cache.dataExtensionField[field.DataExtension.CustomerKey]) {
265
+ if (
266
+ !this.cache[this.buObject.mid].dataExtensionField[field.DataExtension.CustomerKey]
267
+ ) {
259
268
  /** @type {Object.<string, DataExtensionFieldItem[]>} */
260
- this.cache.dataExtensionField[field.DataExtension.CustomerKey] = [];
269
+ this.cache[this.buObject.mid].dataExtensionField[field.DataExtension.CustomerKey] =
270
+ [];
261
271
  }
262
- this.cache.dataExtensionField[field.DataExtension.CustomerKey].push(field);
272
+ this.cache[this.buObject.mid].dataExtensionField[field.DataExtension.CustomerKey].push(
273
+ field
274
+ );
263
275
  }
264
276
  }
265
277
  /**
@@ -268,7 +280,7 @@ class DataFilter extends MetadataType {
268
280
  * @param {DataFilterMap} metadataTypeMap -
269
281
  */
270
282
  static async _cacheContactAttributes(metadataTypeMap) {
271
- if (this.cache.contactAttributes?.[this.buObject.mid]) {
283
+ if (this.cache[this.buObject.mid]?.contactAttributes) {
272
284
  return;
273
285
  }
274
286
  const subscriberFilters = Object.values(metadataTypeMap)
@@ -279,8 +291,8 @@ class DataFilter extends MetadataType {
279
291
  const response = await this.client.rest.get('/email/v1/Contacts/Attributes/');
280
292
  const keyFieldBackup = this.definition.keyField;
281
293
  this.definition.keyField = 'id';
282
- this.cache.contactAttributes ||= {};
283
- this.cache.contactAttributes[this.buObject.mid] = this.parseResponseBody(response);
294
+ this.cache[this.buObject.mid] ||= {};
295
+ this.cache[this.buObject.mid].contactAttributes = this.parseResponseBody(response);
284
296
  this.definition.keyField = keyFieldBackup;
285
297
  }
286
298
  }
@@ -290,7 +302,7 @@ class DataFilter extends MetadataType {
290
302
  * @param {DataFilterMap} metadataTypeMap -
291
303
  */
292
304
  static async _cacheMeasures(metadataTypeMap) {
293
- if (this.cache.measures?.[this.buObject.mid]) {
305
+ if (this.cache?.[this.buObject.mid]?.measures) {
294
306
  return;
295
307
  }
296
308
  const subscriberFilters = Object.values(metadataTypeMap)
@@ -312,8 +324,8 @@ class DataFilter extends MetadataType {
312
324
 
313
325
  const keyFieldBackup = this.definition.keyField;
314
326
  this.definition.keyField = 'measureID';
315
- this.cache.measures ||= {};
316
- this.cache.measures[this.buObject.mid] = this.parseResponseBody(response);
327
+ this.cache[this.buObject.mid] ||= {};
328
+ this.cache[this.buObject.mid].measures = this.parseResponseBody(response);
317
329
  this.definition.keyField = keyFieldBackup;
318
330
  }
319
331
  }
@@ -439,7 +451,9 @@ class DataFilter extends MetadataType {
439
451
  return this._resolveFields(
440
452
  metadata,
441
453
  mode,
442
- this.cache.dataExtensionField[metadata.r__source_dataExtension_key],
454
+ this.cache[this.buObject.mid].dataExtensionField[
455
+ metadata.r__source_dataExtension_key
456
+ ],
443
457
  metadata.c__filterDefinition?.ConditionSet
444
458
  );
445
459
  }
@@ -530,8 +544,8 @@ class DataFilter extends MetadataType {
530
544
  metadata.c__filterDefinition?.ConditionSet
531
545
  );
532
546
  }
533
- const contactAttributes = this.cache.contactAttributes[this.buObject.mid];
534
- const measures = this.cache.measures[this.buObject.mid];
547
+ const contactAttributes = this.cache[this.buObject.mid]?.contactAttributes;
548
+ const measures = this.cache[this.buObject.mid]?.measures;
535
549
  const conditionsArr = Array.isArray(filter.Condition)
536
550
  ? filter.Condition
537
551
  : [filter.Condition];
@@ -1067,7 +1067,7 @@ class Event extends MetadataType {
1067
1067
  if (errors.length > 1) {
1068
1068
  errors.unshift(``);
1069
1069
  }
1070
- throw new Error(errors.join('\n · ')); // eslint-disable-line unicorn/error-message
1070
+ throw new Error(errors.join('\n · '));
1071
1071
  }
1072
1072
  if (warnings?.length) {
1073
1073
  // add a line break
@@ -1220,7 +1220,7 @@ class Event extends MetadataType {
1220
1220
  if (errors.length > 1) {
1221
1221
  errors.unshift(``);
1222
1222
  }
1223
- throw new Error(errors.join('\n · ')); // eslint-disable-line unicorn/error-message
1223
+ throw new Error(errors.join('\n · '));
1224
1224
  }
1225
1225
  }
1226
1226
 
@@ -22,6 +22,7 @@ import { Util } from '../util/util.js';
22
22
  * @augments MetadataType
23
23
  */
24
24
  class FileLocation extends MetadataType {
25
+ static cache = {};
25
26
  /**
26
27
  * Retrieves Metadata of FileLocation
27
28
  * Endpoint /automation/v1/ftplocations/ return all FileLocations
@@ -32,8 +33,32 @@ class FileLocation extends MetadataType {
32
33
  * @param {string} [key] customer key of single item to retrieve
33
34
  * @returns {Promise.<MetadataTypeMapObj>} Promise
34
35
  */
35
- static retrieve(retrieveDir, _, __, key) {
36
- return super.retrieveREST(retrieveDir, '/automation/v1/ftplocations/', null, key);
36
+ static async retrieve(retrieveDir, _, __, key) {
37
+ try {
38
+ const dataItems = await super.retrieveREST(
39
+ null,
40
+ '/data/v1/filetransferlocation' + (key ? '/' + encodeURIComponent(key) : 's'),
41
+ null,
42
+ key
43
+ );
44
+ this.cache[this.buObject.mid] ||= {};
45
+ this.cache[this.buObject.mid].dataItems = dataItems.metadata;
46
+ } catch (ex) {
47
+ if (ex.code === 'ERR_BAD_REQUEST') {
48
+ // if retrieve-by-key comes up empty, the data-endpoint returns a code 400
49
+ Util.logger.debug(ex.message);
50
+ } else {
51
+ Util.logger.warn(ex.message);
52
+ }
53
+ }
54
+ const items = await super.retrieveREST(
55
+ retrieveDir,
56
+ '/automation/v1/ftplocations/',
57
+ null,
58
+ key
59
+ );
60
+
61
+ return items;
37
62
  }
38
63
 
39
64
  /**
@@ -42,9 +67,156 @@ class FileLocation extends MetadataType {
42
67
  * @returns {Promise.<MetadataTypeMapObj>} Promise
43
68
  */
44
69
  static async retrieveForCache() {
45
- return super.retrieveREST(null, '/automation/v1/ftplocations/');
70
+ return this.retrieve(null);
71
+ }
72
+
73
+ /**
74
+ * Creates a single item
75
+ *
76
+ * @param {MetadataTypeItem} metadata a single item
77
+ * @returns {Promise.<MetadataTypeItem>} Promise
78
+ */
79
+ static create(metadata) {
80
+ return this.createREST(metadata, '/data/v1/filetransferlocation');
81
+ }
82
+ /**
83
+ * Updates a single item
84
+ *
85
+ * @param {MetadataTypeItem} metadata a single item
86
+ * @returns {Promise.<MetadataTypeItem>} Promise
87
+ */
88
+ static update(metadata) {
89
+ return this.updateREST(
90
+ metadata,
91
+ '/data/v1/filetransferlocation/' +
92
+ encodeURIComponent(metadata[this.definition.keyField])
93
+ );
46
94
  }
47
95
 
96
+ /**
97
+ * helper for {@link MetadataType.parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
98
+ *
99
+ * @param {MetadataTypeItem} metadata single item
100
+ */
101
+ static createCustomKeyField(metadata) {
102
+ if (metadata.fileTransferLocation) {
103
+ const fileTransferLocation = metadata.fileTransferLocation;
104
+ for (const key of Object.keys(metadata)) {
105
+ delete metadata[key];
106
+ }
107
+ Object.assign(metadata, fileTransferLocation);
108
+ } else {
109
+ if (!metadata.customerKey && this.cache[this.buObject.mid]?.dataItems) {
110
+ const nameMatch = Object.values(this.cache[this.buObject.mid].dataItems).find(
111
+ (item) => item.name === metadata.name
112
+ );
113
+ if (nameMatch) {
114
+ Object.assign(metadata, nameMatch);
115
+ }
116
+ }
117
+ if (!this.definition.locationTypeIdMappingDeployable[metadata.locationTypeId]) {
118
+ // old file location types are only returned by the automation-endpoint which does not return customerKey field - but also these are not updatable and hence we can improvise here
119
+
120
+ metadata.customerKey ||= metadata.name;
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Creates a single metadata entry via REST
127
+ *
128
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
129
+ * @param {string} uri rest endpoint for POST
130
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
131
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
132
+ */
133
+ static async createREST(metadataEntry, uri, handleOutside) {
134
+ this.removeNotCreateableFields(metadataEntry);
135
+ const createPayload = { fileTransferLocation: metadataEntry };
136
+ try {
137
+ // set to empty object in case API returned nothing to be able to update it in helper classes
138
+ let response = (await this.client.rest.post(uri, createPayload)) || {};
139
+ response = await this.postCreateTasks(metadataEntry, response);
140
+ if (!handleOutside) {
141
+ Util.logger.info(
142
+ ` - created ${Util.getTypeKeyName(this.definition, metadataEntry)}`
143
+ );
144
+ }
145
+ return response;
146
+ } catch (ex) {
147
+ const parsedErrors = this.getErrorsREST(ex);
148
+ Util.logger.error(
149
+ ` ☇ error creating ${Util.getTypeKeyName(this.definition, metadataEntry)}:`
150
+ );
151
+ if (parsedErrors.length) {
152
+ for (const msg of parsedErrors) {
153
+ Util.logger.error(' • ' + msg);
154
+ }
155
+ } else if (ex?.message) {
156
+ Util.logger.debug(ex.message);
157
+ }
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * helper for {@link MetadataType.createREST}
164
+ *
165
+ * @param {MetadataTypeItem} _ a single metadata Entry
166
+ * @param {object} apiResponse varies depending on the API call
167
+ * @returns {Promise.<object>} apiResponse, potentially modified
168
+ */
169
+ static async postCreateTasks(_, apiResponse) {
170
+ return apiResponse?.fileTransferLocation || apiResponse;
171
+ }
172
+
173
+ /**
174
+ * Updates a single metadata entry via REST
175
+ *
176
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
177
+ * @param {string} uri rest endpoint for PATCH
178
+ * @param {'patch'|'post'|'put'} [httpMethod] defaults to 'patch'; some update requests require PUT instead of PATCH
179
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
180
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
181
+ */
182
+ static async updateREST(metadataEntry, uri, httpMethod = 'patch', handleOutside) {
183
+ this.removeNotUpdateableFields(metadataEntry);
184
+ const updatePayload = { fileTransferLocation: metadataEntry };
185
+ try {
186
+ // set to empty object in case API returned nothing to be able to update it in helper classes
187
+ let response = (await this.client.rest[httpMethod](uri, updatePayload)) || {};
188
+ await this._postChangeKeyTasks(metadataEntry);
189
+ this.getErrorsREST(response);
190
+ response = await this.postUpdateTasks(metadataEntry, response);
191
+ // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
192
+ if (!handleOutside) {
193
+ Util.logger.info(
194
+ ` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`
195
+ );
196
+ }
197
+ return response;
198
+ } catch (ex) {
199
+ const parsedErrors = this.getErrorsREST(ex);
200
+ Util.logger.error(
201
+ ` ☇ error updating ${Util.getTypeKeyName(this.definition, metadataEntry)}:`
202
+ );
203
+ for (const msg of parsedErrors) {
204
+ Util.logger.error(' • ' + msg);
205
+ }
206
+ return null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP}
212
+ *
213
+ * @param {MetadataTypeItem} _ a single metadata Entry
214
+ * @param {object} apiResponse varies depending on the API call
215
+ * @returns {Promise.<object>} apiResponse, potentially modified
216
+ */
217
+ static postUpdateTasks(_, apiResponse) {
218
+ return apiResponse?.fileTransferLocation || apiResponse;
219
+ }
48
220
  /**
49
221
  * prepares a import definition for deployment
50
222
  *
@@ -54,6 +226,14 @@ class FileLocation extends MetadataType {
54
226
  static async preDeployTasks(metadata) {
55
227
  if (metadata.c__locationType) {
56
228
  metadata.locationTypeId = this.definition.locationTypeMapping[metadata.c__locationType];
229
+ if (this.definition.locationTypeMappingDeployable[metadata.c__locationType]) {
230
+ metadata.locationType =
231
+ this.definition.locationTypeMappingDeployable[metadata.c__locationType];
232
+ } else {
233
+ throw new Error(
234
+ `Only FileLocations of types ${Object.keys(this.definition.locationTypeMappingDeployable).join(', ')} can be deployed via mcdev.`
235
+ );
236
+ }
57
237
  }
58
238
  return metadata;
59
239
  }
@@ -65,7 +245,7 @@ class FileLocation extends MetadataType {
65
245
  * @returns {MetadataTypeItem} parsed metadata
66
246
  */
67
247
  static postRetrieveTasks(metadata) {
68
- if (metadata.locationTypeId) {
248
+ if (metadata.locationTypeId !== undefined) {
69
249
  try {
70
250
  metadata.c__locationType = Util.inverseGet(
71
251
  this.definition.locationTypeMapping,
@@ -78,9 +258,29 @@ class FileLocation extends MetadataType {
78
258
  metadata.locationTypeId
79
259
  );
80
260
  }
261
+ } else if (metadata.locationType) {
262
+ // assuming create/update of new types
263
+ metadata.c__locationType = Util.inverseGet(
264
+ this.definition.locationTypeMappingDeployable,
265
+ metadata.locationType
266
+ );
81
267
  }
82
268
  return metadata;
83
269
  }
270
+
271
+ /**
272
+ * Delete a metadata item from the specified business unit
273
+ *
274
+ * @param {string} key Identifier of item
275
+ * @returns {Promise.<boolean>} deletion success flag
276
+ */
277
+ static async deleteByKey(key) {
278
+ return super.deleteByKeyREST(
279
+ '/data/v1/filetransferlocation/' + encodeURIComponent(key),
280
+ key,
281
+ 400
282
+ );
283
+ }
84
284
  }
85
285
 
86
286
  // Assign definition to static attributes
@@ -50,6 +50,11 @@ class Journey extends MetadataType {
50
50
 
51
51
  let singleKey = '';
52
52
  let mode = 'all';
53
+ const additionalParams = new URLSearchParams();
54
+ if (Util.OPTIONS.onlyPublished) {
55
+ Util.logger.info(' - Retrieving only published versions of journeys');
56
+ additionalParams.set('status', 'Published');
57
+ }
53
58
  if (key) {
54
59
  if (key.startsWith('%23')) {
55
60
  // correct the format
@@ -74,7 +79,7 @@ class Journey extends MetadataType {
74
79
  }
75
80
  mode = 'id';
76
81
  } else if (key.startsWith('name:')) {
77
- singleKey = '?nameOrDescription=' + encodeURIComponent(key.slice(5));
82
+ additionalParams.set('nameOrDescription', key.slice(5));
78
83
  mode = 'name';
79
84
  } else {
80
85
  // assume actual key was provided
@@ -85,7 +90,10 @@ class Journey extends MetadataType {
85
90
 
86
91
  try {
87
92
  const uri = `/interaction/v1/interactions/`;
88
- if ((singleKey && (mode === 'key' || mode === 'id')) || !retrieveDir) {
93
+ if (
94
+ (!Util.OPTIONS.onlyPublished && singleKey && (mode === 'key' || mode === 'id')) ||
95
+ !retrieveDir
96
+ ) {
89
97
  // full details for retrieve, only base data for caching; reduces caching time from minutes to seconds
90
98
  const extras = retrieveDir && singleKey ? extrasDefault : '';
91
99
 
@@ -97,13 +105,12 @@ class Journey extends MetadataType {
97
105
  key
98
106
  );
99
107
  } else {
108
+ const url =
109
+ uri + (additionalParams.size > 0 ? '?' + additionalParams.toString() : '');
100
110
  // retrieve all
101
111
  const results = this.definition.restPagination
102
- ? await this.client.rest.getBulk(
103
- uri + (mode === 'name' ? singleKey : ''),
104
- this.definition.restPageSize || 500
105
- )
106
- : await this.client.rest.get(uri + (mode === 'name' ? singleKey : ''));
112
+ ? await this.client.rest.getBulk(url, this.definition.restPageSize || 500)
113
+ : await this.client.rest.get(url);
107
114
 
108
115
  if (results.items?.length) {
109
116
  // empty results will come back without "items" defined
@@ -115,51 +122,71 @@ class Journey extends MetadataType {
115
122
  }
116
123
  // full details for retrieve
117
124
  const extras = extrasDefault;
125
+ let details = [];
118
126
  let parsed;
119
127
  if (retrieveDir) {
120
128
  const searchName = mode === 'name' ? key.slice(5) : null;
121
129
  const foundKey = [];
130
+ let journey;
122
131
  // get extra details for saving this
123
- const details = results.items
124
- ? await Promise.all(
125
- results.items.map(async (a) => {
126
- if (mode === 'name') {
127
- // when filtering by name, the API in fact does a LIKE search with placeholders left and right of the search term - and also searches the description field.
128
- if (searchName === a[this.definition.nameField]) {
129
- foundKey.push(a[this.definition.keyField]);
130
- } else {
131
- // skip because the name does not match
132
+ if (Util.OPTIONS.onlyPublished && mode === 'key') {
133
+ journey = results.items.find(
134
+ (a) => a[this.definition.keyField] === singleKey.slice(4)
135
+ );
136
+ } else if (Util.OPTIONS.onlyPublished && mode === 'id') {
137
+ journey = results.items.find(
138
+ (a) => a[this.definition.idField] === singleKey
139
+ );
140
+ } else {
141
+ details = results.items
142
+ ? await Promise.all(
143
+ results.items.map(async (a) => {
144
+ if (mode === 'name') {
145
+ // when filtering by name, the API in fact does a LIKE search with placeholders left and right of the search term - and also searches the description field.
146
+ if (searchName === a[this.definition.nameField]) {
147
+ foundKey.push(a[this.definition.keyField]);
148
+ } else {
149
+ // skip because the name does not match
150
+ return null;
151
+ }
152
+ }
153
+ try {
154
+ return await this.client.rest.get(
155
+ `${uri}key:${a[this.definition.keyField]}?extras=${extras}` +
156
+ `&versionNumber=${a.version}`
157
+ );
158
+ } catch (ex) {
159
+ // if we do get here, we should log the error and continue instead of failing to download all automations
160
+ Util.logger.warn(
161
+ ` ☇ skipping ${this.definition.type} ${
162
+ a[this.definition.nameField]
163
+ } (${a[this.definition.keyField]}): ${ex.message} (${
164
+ ex.code
165
+ })${
166
+ ex.endpoint
167
+ ? Util.getGrayMsg(
168
+ ' - ' +
169
+ ex.endpoint.split(
170
+ 'rest.marketingcloudapis.com'
171
+ )[1]
172
+ )
173
+ : ''
174
+ }`
175
+ );
132
176
  return null;
133
177
  }
134
- }
135
- try {
136
- return await this.client.rest.get(
137
- `${uri}key:${a[this.definition.keyField]}?extras=${extras}` +
138
- `&versionNumber=${a.version}`
139
- );
140
- } catch (ex) {
141
- // if we do get here, we should log the error and continue instead of failing to download all automations
142
- Util.logger.warn(
143
- ` ☇ skipping ${this.definition.type} ${
144
- a[this.definition.nameField]
145
- } (${a[this.definition.keyField]}): ${ex.message} (${
146
- ex.code
147
- })${
148
- ex.endpoint
149
- ? Util.getGrayMsg(
150
- ' - ' +
151
- ex.endpoint.split(
152
- 'rest.marketingcloudapis.com'
153
- )[1]
154
- )
155
- : ''
156
- }`
157
- );
158
- return null;
159
- }
160
- })
161
- )
162
- : [];
178
+ })
179
+ )
180
+ : [];
181
+ }
182
+ if (Util.OPTIONS.onlyPublished && journey) {
183
+ details.push(
184
+ await this.client.rest.get(
185
+ `${uri}${singleKey}?extras=${extras}` +
186
+ `&versionNumber=${journey.version}`
187
+ )
188
+ );
189
+ }
163
190
  parsed = this.parseResponseBody({ items: details.filter(Boolean) });
164
191
  // * retrieveDir is mandatory in this method as it is not used for caching (there is a seperate method for that)
165
192
  const savedMetadata = await this.saveResults(parsed, retrieveDir, null, null);
@@ -83,6 +83,22 @@ class Script extends MetadataType {
83
83
  return super.updateREST(script, '/automation/v1/scripts/' + script.ssjsActivityId);
84
84
  }
85
85
 
86
+ /**
87
+ * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP}
88
+ *
89
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
90
+ * @param {object} apiResponse varies depending on the API call
91
+ * @returns {Promise.<object>} apiResponse, potentially modified
92
+ */
93
+ static async postUpdateTasks(metadataEntry, apiResponse) {
94
+ // script update endpoint returns wrong values for createdDate and modifiedDate - need to re-retrieve to get correct values
95
+ const ssjsActivityId = apiResponse?.ssjsActivityId;
96
+ if (ssjsActivityId) {
97
+ apiResponse = await this.client.rest.get('/automation/v1/scripts/' + ssjsActivityId);
98
+ }
99
+ return apiResponse;
100
+ }
101
+
86
102
  /**
87
103
  * Creates a single Script
88
104
  *
@@ -23,6 +23,7 @@ export default {
23
23
  soapType: 'FilterDefinition',
24
24
  typeDescription: 'Defines an audience based on specified rules. Used by Filter Activities.',
25
25
  typeRetrieveByDefault: true,
26
+ typeCdpByDefault: true,
26
27
  typeName: 'Data Filter',
27
28
  fields: {
28
29
  // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well
@@ -24,6 +24,7 @@ export default {
24
24
  typeDescription:
25
25
  'Defines an audience based on specified rules. Auto-generated by filtered DEs and filtered Lists.',
26
26
  typeRetrieveByDefault: false,
27
+ typeCdpByDefault: false,
27
28
  typeName: 'Data Filter (auto-generated)',
28
29
  fields: {
29
30
  // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well