mcdev 8.3.1 → 9.0.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/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/close_issues_on_merge.yml +4 -0
  3. package/.github/workflows/code-test.yml +2 -2
  4. package/.github/workflows/coverage-base-update.yml +2 -2
  5. package/.github/workflows/coverage-develop-branch.yml +1 -1
  6. package/.github/workflows/coverage-main-branch.yml +1 -1
  7. package/.github/workflows/coverage.yml +2 -2
  8. package/.prettierrc +1 -1
  9. package/@types/lib/index.d.ts.map +1 -1
  10. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/DataFilter.d.ts +1 -0
  14. package/@types/lib/metadataTypes/DataFilter.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/DataFilterHidden.d.ts +1 -0
  16. package/@types/lib/metadataTypes/DataFilterHidden.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/FileLocation.d.ts +74 -0
  19. package/@types/lib/metadataTypes/FileLocation.d.ts.map +1 -1
  20. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/Journey.d.ts +6 -0
  22. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  23. package/@types/lib/metadataTypes/List.d.ts +1 -1
  24. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  25. package/@types/lib/metadataTypes/Script.d.ts +14 -0
  26. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  27. package/@types/lib/metadataTypes/definitions/DataFilter.definition.d.ts +1 -0
  28. package/@types/lib/metadataTypes/definitions/DataFilterHidden.definition.d.ts +1 -0
  29. package/@types/lib/metadataTypes/definitions/FileLocation.definition.d.ts +62 -4
  30. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +6 -0
  31. package/@types/lib/metadataTypes/definitions/Script.definition.d.ts +10 -0
  32. package/@types/lib/util/auth.d.ts.map +1 -1
  33. package/@types/lib/util/cli.d.ts.map +1 -1
  34. package/@types/lib/util/file.d.ts.map +1 -1
  35. package/@types/lib/util/init.config.d.ts.map +1 -1
  36. package/@types/lib/util/init.d.ts +1 -1
  37. package/@types/lib/util/init.d.ts.map +1 -1
  38. package/@types/lib/util/init.git.d.ts.map +1 -1
  39. package/lib/Deployer.js +3 -3
  40. package/lib/cli.js +6 -0
  41. package/lib/index.js +15 -14
  42. package/lib/metadataTypes/Asset.js +7 -8
  43. package/lib/metadataTypes/Automation.js +8 -10
  44. package/lib/metadataTypes/DataExtension.js +4 -3
  45. package/lib/metadataTypes/DataExtensionField.js +3 -3
  46. package/lib/metadataTypes/DataFilter.js +36 -22
  47. package/lib/metadataTypes/Event.js +8 -7
  48. package/lib/metadataTypes/FileLocation.js +204 -4
  49. package/lib/metadataTypes/Folder.js +8 -7
  50. package/lib/metadataTypes/Journey.js +74 -47
  51. package/lib/metadataTypes/List.js +1 -1
  52. package/lib/metadataTypes/MetadataType.js +17 -21
  53. package/lib/metadataTypes/Script.js +16 -0
  54. package/lib/metadataTypes/User.js +2 -2
  55. package/lib/metadataTypes/definitions/DataFilter.definition.js +1 -0
  56. package/lib/metadataTypes/definitions/DataFilterHidden.definition.js +1 -0
  57. package/lib/metadataTypes/definitions/FileLocation.definition.js +52 -7
  58. package/lib/metadataTypes/definitions/Journey.definition.js +6 -0
  59. package/lib/metadataTypes/definitions/Script.definition.js +6 -0
  60. package/lib/util/auth.js +20 -24
  61. package/lib/util/businessUnit.js +1 -1
  62. package/lib/util/cli.js +2 -1
  63. package/lib/util/file.js +2 -1
  64. package/lib/util/init.config.js +3 -1
  65. package/lib/util/init.git.js +2 -1
  66. package/lib/util/init.js +2 -1
  67. package/lib/util/util.js +3 -3
  68. package/package.json +21 -21
  69. package/test/general.test.js +11 -11
  70. package/test/mockRoot/.mcdevrc.json +1 -1
  71. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/ExactTarget Enhanced FTP.fileLocation-meta.json +5 -0
  72. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_aws.fileLocation-meta.json +14 -0
  73. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_exsftp.fileLocation-meta.json +12 -0
  74. package/test/resourceFactory.js +10 -3
  75. package/test/resources/9999999/automation/v1/ftplocations/get-response.json +26 -1
  76. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/get-response.json +10 -0
  77. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +2 -2
  78. package/test/resources/9999999/data/v1/filetransferlocation/Salesforce%20Objects%20%26%20Reports/get-response.json +4 -0
  79. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_aws/patch-response.json +18 -0
  80. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/delete-response.json +4 -0
  81. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/get-response.json +18 -0
  82. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_exsftp/patch-response.json +16 -0
  83. package/test/resources/9999999/data/v1/filetransferlocations/get-response.json +59 -0
  84. package/test/resources/9999999/fileLocation/build-expected.json +14 -0
  85. package/test/resources/9999999/fileLocation/get-aws-expected.json +14 -0
  86. package/test/resources/9999999/fileLocation/get-azure-expected.json +14 -0
  87. package/test/resources/9999999/fileLocation/get-eftp-expected.json +5 -0
  88. package/test/resources/9999999/fileLocation/get-exsftp-expected.json +12 -0
  89. package/test/resources/9999999/fileLocation/get-gcp-expected.json +10 -0
  90. package/test/resources/9999999/fileLocation/get-sor-expected.json +5 -0
  91. package/test/resources/9999999/fileLocation/patch-aws-expected.json +14 -0
  92. package/test/resources/9999999/fileLocation/patch-exsftp-expected.json +12 -0
  93. package/test/resources/9999999/fileLocation/template-expected.json +14 -0
  94. package/test/resources/9999999/interaction/v1/interactions/get-response-status=Published.json +40 -0
  95. package/test/type.automation.test.js +14 -14
  96. package/test/type.dataFilter.test.js +10 -2
  97. package/test/type.fileLocation.test.js +279 -0
  98. package/test/type.fileTransfer.test.js +4 -4
  99. package/test/type.filter.test.js +9 -2
  100. package/test/type.importFile.test.js +5 -5
  101. package/test/type.journey.test.js +26 -0
  102. package/test/type.query.test.js +2 -2
  103. package/test/type.script.test.js +1 -1
  104. package/tsconfig.json +1 -1
  105. package/tsconfig.npmScripts.json +1 -1
  106. package/tsconfig.precommit.json +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];
@@ -695,7 +695,7 @@ class Event extends MetadataType {
695
695
  ...new Set(
696
696
  Object.values(this.sfObjects.referencedObjects[objectAPIName])
697
697
  .map((el) => el.referenceObjectName)
698
- .sort()
698
+ .toSorted()
699
699
  ),
700
700
  ]
701
701
  : [];
@@ -793,7 +793,8 @@ class Event extends MetadataType {
793
793
  } catch (ex) {
794
794
  if (ex.code === 'ERR_BAD_RESPONSE') {
795
795
  throw new Error(
796
- `Could not find Salesforce entry object ${objectAPIName} on connected org.`
796
+ `Could not find Salesforce entry object ${objectAPIName} on connected org.`,
797
+ { cause: ex }
797
798
  );
798
799
  }
799
800
  }
@@ -861,7 +862,7 @@ class Event extends MetadataType {
861
862
  */
862
863
  static checkSalesforceEntryEvents(ca, isPublished) {
863
864
  // 1 check eventDataConfig
864
- const edcObjects = ca.eventDataConfig.objects.sort((a, b) =>
865
+ const edcObjects = ca.eventDataConfig.objects.toSorted((a, b) =>
865
866
  a.dePrefix.localeCompare(b.dePrefix)
866
867
  );
867
868
  const warnings = [];
@@ -1067,7 +1068,7 @@ class Event extends MetadataType {
1067
1068
  if (errors.length > 1) {
1068
1069
  errors.unshift(``);
1069
1070
  }
1070
- throw new Error(errors.join('\n · ')); // eslint-disable-line unicorn/error-message
1071
+ throw new Error(errors.join('\n · '));
1071
1072
  }
1072
1073
  if (warnings?.length) {
1073
1074
  // add a line break
@@ -1156,7 +1157,7 @@ class Event extends MetadataType {
1156
1157
  ca.eventDataSummary =
1157
1158
  'string' === typeof ca.eventDataSummary
1158
1159
  ? // @ts-expect-error transforming this from API-string-format to from mcdev-format
1159
- ca.eventDataSummary.split('; ').filter(Boolean).sort()
1160
+ ca.eventDataSummary.split('; ').filter(Boolean).toSorted()
1160
1161
  : ca.eventDataSummary;
1161
1162
  ca.passThroughArgument =
1162
1163
  'string' === typeof ca.passThroughArgument
@@ -1204,7 +1205,7 @@ class Event extends MetadataType {
1204
1205
  eventDataSummary =
1205
1206
  'string' === typeof eventDataSummary
1206
1207
  ? // @ts-expect-error transforming this from API-string-format to from mcdev-format
1207
- eventDataSummary.split('; ').filter(Boolean).sort()
1208
+ eventDataSummary.split('; ').filter(Boolean).toSorted()
1208
1209
  : eventDataSummary;
1209
1210
 
1210
1211
  const errors = [];
@@ -1220,7 +1221,7 @@ class Event extends MetadataType {
1220
1221
  if (errors.length > 1) {
1221
1222
  errors.unshift(``);
1222
1223
  }
1223
- throw new Error(errors.join('\n · ')); // eslint-disable-line unicorn/error-message
1224
+ throw new Error(errors.join('\n · '));
1224
1225
  }
1225
1226
  }
1226
1227
 
@@ -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
@@ -309,11 +309,12 @@ class Folder extends MetadataType {
309
309
  parsed[normalizedKey] = parsed['undefined'];
310
310
  delete parsed['undefined'];
311
311
  }
312
- const newObject = {};
313
- newObject[normalizedKey] = Object.assign(
314
- beforeMetadata,
315
- parsed[normalizedKey]
316
- );
312
+ const newObject = {
313
+ [normalizedKey]: Object.assign(
314
+ beforeMetadata,
315
+ parsed[normalizedKey]
316
+ ),
317
+ };
317
318
  cache.mergeMetadata('folder', newObject);
318
319
 
319
320
  upsertResults[metadataKey] = beforeMetadata;
@@ -678,7 +679,7 @@ class Folder extends MetadataType {
678
679
  return fileName2FileContent;
679
680
  } catch (ex) {
680
681
  Util.metadataLogger('error', this.definition.type, 'getJsonFromFS', ex);
681
- throw new Error(ex);
682
+ throw ex;
682
683
  }
683
684
  }
684
685
 
@@ -697,7 +698,7 @@ class Folder extends MetadataType {
697
698
  leftOperand: 'ContentType',
698
699
  operator: contentTypeList.length === 1 ? 'equals' : 'IN',
699
700
  rightOperand:
700
- contentTypeList.length === 1 ? contentTypeList[0] : contentTypeList.sort(),
701
+ contentTypeList.length === 1 ? contentTypeList[0] : contentTypeList.toSorted(),
701
702
  };
702
703
  options.filter = options.filter
703
704
  ? {