mcdev 7.10.0 → 7.10.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 (98) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/@types/lib/index.d.ts +4 -4
  3. package/@types/lib/index.d.ts.map +1 -1
  4. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  5. package/@types/lib/metadataTypes/Automation.d.ts +62 -51
  6. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/DataExtract.d.ts +13 -2
  8. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Event.d.ts +8 -0
  10. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/MetadataType.d.ts +19 -3
  14. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/MobileMessage.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/SenderProfile.d.ts +3 -1
  17. package/@types/lib/metadataTypes/SenderProfile.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +37 -1
  19. package/@types/lib/metadataTypes/definitions/DataExtract.definition.d.ts +21 -3
  20. package/@types/lib/metadataTypes/definitions/SenderProfile.definition.d.ts +3 -1
  21. package/@types/lib/util/util.d.ts +1 -0
  22. package/@types/lib/util/util.d.ts.map +1 -1
  23. package/@types/lib/util/validations.d.ts.map +1 -1
  24. package/@types/types/mcdev.d.d.ts +32 -0
  25. package/@types/types/mcdev.d.d.ts.map +1 -1
  26. package/lib/index.js +14 -7
  27. package/lib/metadataTypes/Asset.js +4 -5
  28. package/lib/metadataTypes/Automation.js +202 -399
  29. package/lib/metadataTypes/DataExtension.js +1 -1
  30. package/lib/metadataTypes/DataExtract.js +41 -6
  31. package/lib/metadataTypes/DomainVerification.js +1 -1
  32. package/lib/metadataTypes/EmailSend.js +3 -3
  33. package/lib/metadataTypes/Event.js +39 -20
  34. package/lib/metadataTypes/FileTransfer.js +5 -5
  35. package/lib/metadataTypes/ImportFile.js +9 -7
  36. package/lib/metadataTypes/Journey.js +4 -9
  37. package/lib/metadataTypes/MetadataType.js +80 -58
  38. package/lib/metadataTypes/MobileKeyword.js +2 -2
  39. package/lib/metadataTypes/MobileMessage.js +4 -2
  40. package/lib/metadataTypes/Query.js +5 -5
  41. package/lib/metadataTypes/Script.js +5 -5
  42. package/lib/metadataTypes/SendClassification.js +3 -3
  43. package/lib/metadataTypes/SenderProfile.js +3 -3
  44. package/lib/metadataTypes/TransactionalEmail.js +1 -1
  45. package/lib/metadataTypes/TransactionalMessage.js +1 -1
  46. package/lib/metadataTypes/TriggeredSend.js +3 -3
  47. package/lib/metadataTypes/Verification.js +1 -1
  48. package/lib/metadataTypes/definitions/Automation.definition.js +43 -7
  49. package/lib/metadataTypes/definitions/DataExtract.definition.js +16 -3
  50. package/lib/metadataTypes/definitions/DomainVerification.definition.js +1 -1
  51. package/lib/metadataTypes/definitions/SenderProfile.definition.js +3 -1
  52. package/lib/util/util.js +12 -0
  53. package/lib/util/validations.js +3 -1
  54. package/package.json +1 -1
  55. package/test/general.test.js +39 -34
  56. package/test/mockRoot/.mcdevrc.json +1 -1
  57. package/test/resourceFactory.js +8 -5
  58. package/test/resources/9999999/automation/create-expected.json +2 -1
  59. package/test/resources/9999999/automation/retrieve-expected.json +7 -1
  60. package/test/resources/9999999/automation/retrieve-wait-expected.json +7 -1
  61. package/test/resources/9999999/automation/update-expected.json +24 -2
  62. package/test/resources/9999999/automation/update-testExisting_automation-expected.md +5 -2
  63. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/patch-response.json +1 -0
  64. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/patch-response.json +1 -0
  65. package/test/resources/9999999/dataExtract/build-expected.json +2 -2
  66. package/test/resources/9999999/dataExtract/get-expected.json +2 -2
  67. package/test/resources/9999999/dataExtract/patch-expected.json +2 -2
  68. package/test/resources/9999999/dataExtract/post-expected.json +2 -2
  69. package/test/resources/9999999/dataExtract/template-expected.json +2 -2
  70. package/test/resources/9999999/legacy/v1/beta/automations/notifications/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow-PAUSED/get-response.json +3 -0
  71. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/NewRkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/get-response.json +29 -0
  72. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/get-response.json +50 -0
  73. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow-PAUSED/get-response.json +51 -0
  74. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/bHF6Q0Q3b1VXa21OdVQzZFQ0ckVSQToyNTow/get-response.json +7 -1
  75. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/cDhLQ2o2NExxVVc5N3VZeHF5WEExUToyNTow/get-response.json +79 -0
  76. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +61 -2
  77. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/post-response-pauseSchedule.json +1 -0
  78. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/post-response-schedule.json +1 -0
  79. package/test/resources/9999999/messaging/v1/domainverification/get-response.json +10 -1
  80. package/test/resources/9999999/senderProfile/build-expected.json +1 -1
  81. package/test/resources/9999999/senderProfile/get-expected.json +1 -1
  82. package/test/resources/9999999/senderProfile/patch-expected.json +1 -1
  83. package/test/resources/9999999/senderProfile/retrieve-response.xml +1 -1
  84. package/test/resources/9999999/senderProfile/template-expected.json +1 -1
  85. package/test/type.automation.test.js +18 -17
  86. package/test/type.dataExtract.test.js +4 -4
  87. package/test/type.domainVerification.test.js +3 -3
  88. package/test/type.journey.test.js +11 -4
  89. package/test/type.query.test.js +2 -2
  90. package/test/type.script.test.js +1 -1
  91. package/test/type.senderProfile.test.js +20 -3
  92. package/test/type.triggeredSend.test.js +13 -1
  93. package/types/mcdev.d.js +8 -0
  94. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +0 -52
  95. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml +0 -38
  96. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_pause-response.xml +0 -52
  97. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +0 -52
  98. package/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +0 -52
@@ -1476,7 +1476,7 @@ class DataExtension extends MetadataType {
1476
1476
  * @returns {Promise.<boolean>} deletion success status
1477
1477
  */
1478
1478
  static deleteByKey(customerKey) {
1479
- return super.deleteByKeySOAP(customerKey);
1479
+ return super.deleteByKeySOAP(customerKey, undefined, 310007);
1480
1480
  }
1481
1481
 
1482
1482
  /**
@@ -129,6 +129,21 @@ class DataExtract extends MetadataType {
129
129
  * @returns {MetadataTypeItem} metadata object
130
130
  */
131
131
  static preDeployTasks(metadata) {
132
+ // dataExtension
133
+ if (metadata.r__dataExtension_key) {
134
+ const deField = metadata.dataFields.find((field) => field.name === 'DECustomerKey');
135
+ if (deField) {
136
+ deField.value = cache.searchForField(
137
+ 'dataExtension',
138
+ metadata.r__dataExtension_key,
139
+ 'CustomerKey',
140
+ 'CustomerKey'
141
+ );
142
+ delete metadata.r__dataExtension_key;
143
+ }
144
+ }
145
+
146
+ // dataExtractType
132
147
  metadata.dataExtractTypeId = cache.searchForField(
133
148
  'dataExtractType',
134
149
  metadata.r__dataExtractType_name,
@@ -166,6 +181,7 @@ class DataExtract extends MetadataType {
166
181
  * @returns {MetadataTypeItem} metadata
167
182
  */
168
183
  static postRetrieveTasks(metadata) {
184
+ // user
169
185
  try {
170
186
  metadata.createdBy = cache.searchForField(
171
187
  'user',
@@ -195,6 +211,25 @@ class DataExtract extends MetadataType {
195
211
  }): ${ex.message}.`
196
212
  );
197
213
  }
214
+ // dataExtension
215
+ const deField = metadata.dataFields.find((field) => field.name === 'DECustomerKey');
216
+ if (deField && deField.value) {
217
+ try {
218
+ metadata.r__dataExtension_key = cache.searchForField(
219
+ 'dataExtension',
220
+ deField.value,
221
+ 'CustomerKey',
222
+ 'CustomerKey'
223
+ );
224
+ delete deField.value;
225
+ } catch (ex) {
226
+ Util.logger.warn(
227
+ ` - ${this.definition.type} ${metadata[this.definition.keyField]}: ${ex.message}`
228
+ );
229
+ }
230
+ }
231
+
232
+ // dataExtractType
198
233
  try {
199
234
  metadata.r__dataExtractType_name = cache.searchForField(
200
235
  'dataExtractType',
@@ -208,7 +243,7 @@ class DataExtract extends MetadataType {
208
243
  ` - ${this.definition.type} ${metadata[this.definition.keyField]}: ${ex.message}`
209
244
  );
210
245
  }
211
- return structuredClone(metadata);
246
+ return metadata;
212
247
  }
213
248
 
214
249
  /**
@@ -232,17 +267,17 @@ class DataExtract extends MetadataType {
232
267
  /**
233
268
  * Delete a metadata item from the specified business unit
234
269
  *
235
- * @param {string} customerKey Identifier of data extension
270
+ * @param {string} key Identifier of data extension
236
271
  * @returns {Promise.<boolean>} deletion success flag
237
272
  */
238
- static async deleteByKey(customerKey) {
273
+ static async deleteByKey(key) {
239
274
  // delete only works with the query's object id
240
- const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
275
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
241
276
  if (!objectId) {
242
- Util.logger.error(` - dataExtract ${customerKey} not found`);
277
+ await this.deleteNotFound(key);
243
278
  return false;
244
279
  }
245
- return super.deleteByKeyREST('/automation/v1/dataextracts/' + objectId, customerKey);
280
+ return super.deleteByKeyREST('/automation/v1/dataextracts/' + objectId, key);
246
281
  }
247
282
  }
248
283
 
@@ -262,7 +262,7 @@ class DomainVerification extends MetadataType {
262
262
  static async deleteByKey(key) {
263
263
  const metadataItem = cache.getByKey(this.definition.type, key);
264
264
  if (!metadataItem) {
265
- Util.logger.error(` - ${this.definition.type} ${key} not found`);
265
+ await this.deleteNotFound(key);
266
266
  return false;
267
267
  }
268
268
  if (metadataItem.domainType !== 'UserDomain') {
@@ -90,11 +90,11 @@ class EmailSend extends MetadataType {
90
90
  /**
91
91
  * Delete a metadata item from the specified business unit
92
92
  *
93
- * @param {string} customerKey Identifier of data extension
93
+ * @param {string} key Identifier of data extension
94
94
  * @returns {Promise.<boolean>} deletion success status
95
95
  */
96
- static deleteByKey(customerKey) {
97
- return super.deleteByKeySOAP(customerKey);
96
+ static deleteByKey(key) {
97
+ return super.deleteByKeySOAP(key, undefined, 42002);
98
98
  }
99
99
 
100
100
  /**
@@ -141,26 +141,11 @@ class Event extends MetadataType {
141
141
  * @returns {Promise.<boolean>} deletion success status
142
142
  */
143
143
  static async deleteByKey(key) {
144
- let response = false;
145
- try {
146
- response = await super.deleteByKeyREST(
147
- '/interaction/v1/eventDefinitions/key:' + encodeURIComponent(key),
148
- key,
149
- true
150
- );
151
- Util.logger.info(` - deleted ${this.definition.type}: ${key}`);
152
- } catch (ex) {
153
- if (ex.code === 30000) {
154
- Util.logger.warn(
155
- ` ☇ skipping deletion of ${this.definition.type} ${key}: not found on server`
156
- );
157
- } else {
158
- Util.logger.errorStack(ex, ` - Deleting ${this.definition.type} '${key}' failed`);
159
- }
160
-
161
- return false;
162
- }
163
- return response;
144
+ return await super.deleteByKeyREST(
145
+ '/interaction/v1/eventDefinitions/key:' + encodeURIComponent(key),
146
+ key,
147
+ 30000
148
+ );
164
149
  }
165
150
 
166
151
  /**
@@ -369,8 +354,20 @@ class Event extends MetadataType {
369
354
  }
370
355
  if (!deFound) {
371
356
  if (metadataItem.r__dataExtension_key) {
357
+ if (!metadataItem.schema) {
358
+ // make sure we skip this item
359
+ this.removeFromDeployment(metadataKey, metadataToUpdate, metadataToCreate);
360
+ throw new Error(
361
+ `related dataExtension ${metadataItem.r__dataExtension_key} not found`
362
+ );
363
+ }
372
364
  metadataItem.schema.name = metadataItem.r__dataExtension_key;
373
365
  }
366
+ if (!metadataItem.schema) {
367
+ this.removeFromDeployment(metadataKey, metadataToUpdate, metadataToCreate);
368
+ throw new Error(`no related dataExtension and no schema found`);
369
+ }
370
+
374
371
  Util.logger.warn(
375
372
  `Data Extension ${metadataItem.schema.name || metadataItem[this.definition.keyField]} not found on BU. Creating it automatically based on schema-definition.`
376
373
  );
@@ -384,6 +381,28 @@ class Event extends MetadataType {
384
381
  return createOrUpdateAction;
385
382
  }
386
383
 
384
+ /**
385
+ * helper for {@link createOrUpdate}
386
+ *
387
+ * @param {string} metadataKey key of item we are looking at
388
+ * @param {MetadataTypeItemDiff[]} metadataToUpdate list of items to update
389
+ * @param {MetadataTypeItem[]} metadataToCreate list of items to create
390
+ */
391
+ static removeFromDeployment(metadataKey, metadataToUpdate, metadataToCreate) {
392
+ const removeUpdate = metadataToUpdate.findIndex(
393
+ (el) => el.after[this.definition.keyField] === metadataKey
394
+ );
395
+ if (removeUpdate !== -1) {
396
+ metadataToUpdate.splice(removeUpdate, 1);
397
+ }
398
+ const removeCreate = metadataToCreate.findIndex(
399
+ (el) => el[this.definition.keyField] === metadataKey
400
+ );
401
+ if (removeCreate !== -1) {
402
+ metadataToCreate.splice(removeCreate, 1);
403
+ }
404
+ }
405
+
387
406
  /**
388
407
  * Gets executed after deployment of metadata type
389
408
  *
@@ -187,17 +187,17 @@ class FileTransfer extends MetadataType {
187
187
  /**
188
188
  * Delete a metadata item from the specified business unit
189
189
  *
190
- * @param {string} customerKey Identifier of data extension
190
+ * @param {string} key Identifier of data extension
191
191
  * @returns {Promise.<boolean>} deletion success flag
192
192
  */
193
- static async deleteByKey(customerKey) {
193
+ static async deleteByKey(key) {
194
194
  // delete only works with the query's object id
195
- const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
195
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
196
196
  if (!objectId) {
197
- Util.logger.error(` - fileTransfer ${customerKey} not found`);
197
+ await this.deleteNotFound(key);
198
198
  return false;
199
199
  }
200
- return super.deleteByKeyREST('/automation/v1/filetransfers/' + objectId, customerKey);
200
+ return super.deleteByKeyREST('/automation/v1/filetransfers/' + objectId, key);
201
201
  }
202
202
  }
203
203
 
@@ -228,9 +228,11 @@ class ImportFile extends MetadataType {
228
228
  * @returns {Promise.<MetadataTypeItem>} Promise
229
229
  */
230
230
  static async preDeployTasks(metadata) {
231
- const fileLocation = cache.getByKey('fileLocation', metadata.source.r__fileLocation_name);
231
+ const fileLocation = cache.getByKey('fileLocation', metadata.source?.r__fileLocation_name);
232
232
  if (!fileLocation) {
233
- throw new Error(`fileLocation ${metadata.r__fileLocation_name} not found in cache`);
233
+ throw new Error(
234
+ `fileLocation ${metadata.source?.r__fileLocation_name} not found in cache`
235
+ );
234
236
  }
235
237
 
236
238
  metadata.fileTransferLocationId = fileLocation.id;
@@ -512,17 +514,17 @@ class ImportFile extends MetadataType {
512
514
  /**
513
515
  * Delete a metadata item from the specified business unit
514
516
  *
515
- * @param {string} customerKey Identifier of data extension
517
+ * @param {string} key Identifier of data extension
516
518
  * @returns {Promise.<boolean>} deletion success flag
517
519
  */
518
- static async deleteByKey(customerKey) {
520
+ static async deleteByKey(key) {
519
521
  // delete only works with the query's object id
520
- const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
522
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
521
523
  if (!objectId) {
522
- Util.logger.error(` - importFile ${customerKey} not found`);
524
+ await this.deleteNotFound(key);
523
525
  return false;
524
526
  }
525
- return super.deleteByKeyREST('/automation/v1/imports/' + objectId, customerKey);
527
+ return super.deleteByKeyREST('/automation/v1/imports/' + objectId, key);
526
528
  }
527
529
  }
528
530
 
@@ -256,9 +256,7 @@ class Journey extends MetadataType {
256
256
  }
257
257
  }
258
258
  if (!cachedJourney?.key) {
259
- Util.logger.warn(
260
- ` ☇ skipping deletion of ${this.definition.type} ${key}: not found on server`
261
- );
259
+ await this.deleteNotFound(key);
262
260
  return false;
263
261
  }
264
262
  switch (cachedJourney.definitionType) {
@@ -287,8 +285,7 @@ class Journey extends MetadataType {
287
285
  '/interaction/v1/interactions/' +
288
286
  id +
289
287
  (version === '*' ? '' : `?versionNumber=${version}`),
290
- key,
291
- false
288
+ key
292
289
  );
293
290
  // break;
294
291
  }
@@ -296,8 +293,7 @@ class Journey extends MetadataType {
296
293
  // Quicksend doesnt have versions
297
294
  const isDeleted = await super.deleteByKeyREST(
298
295
  '/interaction/v1/interactions/' + id,
299
- key,
300
- false
296
+ key
301
297
  );
302
298
  return isDeleted;
303
299
  }
@@ -305,8 +301,7 @@ class Journey extends MetadataType {
305
301
  // Transactional dont have versions
306
302
  const isDeleted = await super.deleteByKeyREST(
307
303
  '/interaction/v1/interactions/' + id,
308
- key,
309
- false
304
+ key
310
305
  );
311
306
  const transactionalEmailKey =
312
307
  cachedJourney.activities[0]?.configurationArguments?.triggeredSendKey;
@@ -157,7 +157,7 @@ class MetadataType {
157
157
  ) {
158
158
  // * do not await here as this might take a while and has no impact on the deploy
159
159
  // * this should only be run if documentation is on a per metadata record level. Types that document an overview into a single file will need a full retrieve to work instead
160
- this.document(savedMetadataMap, true);
160
+ await this.document(savedMetadataMap, true);
161
161
  }
162
162
  }
163
163
  return upsertedMetadataMap;
@@ -299,8 +299,7 @@ class MetadataType {
299
299
  * @returns {Promise.<MetadataTypeMapObj>} metadata
300
300
  */
301
301
  static async retrieve(retrieveDir, additionalFields, subTypeArr, key) {
302
- Util.metadataLogger('error', this.definition.type, 'retrieve', `Not Supported`);
303
- const metadata = {};
302
+ Util.notSupportedError(this.definition, 'retrieve');
304
303
  return { metadata: {}, type: this.definition.type };
305
304
  }
306
305
 
@@ -337,13 +336,7 @@ class MetadataType {
337
336
  * @returns {Promise.<MetadataTypeItemObj>} metadata
338
337
  */
339
338
  static async retrieveAsTemplate(templateDir, name, templateVariables, subType) {
340
- Util.logger.error('retrieveAsTemplate is not supported yet for ' + this.definition.type);
341
- Util.metadataLogger(
342
- 'debug',
343
- this.definition.type,
344
- 'retrieveAsTemplate',
345
- `(${templateDir}, ${name}, ${templateVariables}) no templating function`
346
- );
339
+ Util.notSupportedError(this.definition, 'retrieveAsTemplate');
347
340
  return { metadata: null, type: this.definition.type };
348
341
  }
349
342
 
@@ -506,9 +499,7 @@ class MetadataType {
506
499
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
507
500
  */
508
501
  static async create(metadata, deployDir) {
509
- Util.logger.error(
510
- ` ☇ skipping ${Util.getTypeKeyName(this.definition, metadata)}: create is not supported yet for ${this.definition.type}`
511
- );
502
+ Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
512
503
  return;
513
504
  }
514
505
 
@@ -520,9 +511,7 @@ class MetadataType {
520
511
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
521
512
  */
522
513
  static async update(metadata, metadataBefore) {
523
- Util.logger.error(
524
- ` ☇ skipping ${Util.getTypeKeyName(this.definition, metadata)}: update is not supported yet for ${this.definition.type}`
525
- );
514
+ Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
526
515
  return;
527
516
  }
528
517
 
@@ -535,9 +524,7 @@ class MetadataType {
535
524
  * @returns {Promise.<string[]>} Returns list of keys that were refreshed
536
525
  */
537
526
  static async refresh(keyArr, checkKey = true, upsertResults) {
538
- Util.logger.error(
539
- ` ☇ skipping ${this.definition.type}: refresh is not supported yet for ${this.definition.type}`
540
- );
527
+ Util.notSupportedError(this.definition, 'refresh');
541
528
  return [];
542
529
  }
543
530
 
@@ -643,9 +630,7 @@ class MetadataType {
643
630
  * @returns {Promise.<MetadataTypeItem | CodeExtractItem>} key of the item that was updated
644
631
  */
645
632
  static async replaceCbReference(item, retrieveDir, findAssetKeys) {
646
- Util.logger.error(
647
- ` ☇ skipping ${this.definition.type}: replaceCbReference is not supported yet for ${this.definition.type}`
648
- );
633
+ Util.notSupportedError(this.definition, 'replaceCbReference');
649
634
  return [];
650
635
  }
651
636
 
@@ -657,9 +642,19 @@ class MetadataType {
657
642
  * @returns {Promise.<string[]>} Returns list of keys that were executed
658
643
  */
659
644
  static async execute(keyArr, cache) {
660
- Util.logger.error(
661
- ` ☇ skipping ${this.definition.type}: execute is not supported yet for ${this.definition.type}`
662
- );
645
+ Util.notSupportedError(this.definition, 'execute');
646
+ return [];
647
+ }
648
+
649
+ /**
650
+ * Abstract schedule method that needs to be implemented in child metadata type
651
+ *
652
+ * @param {string[]} keyArr customerkey of the metadata
653
+ * @param {MetadataTypeMapObj} [cache] metadata cache used by refresh to avoid recaching
654
+ * @returns {Promise.<string[]>} Returns list of keys that were executed
655
+ */
656
+ static async schedule(keyArr, cache) {
657
+ Util.notSupportedError(this.definition, 'schedule');
663
658
  return [];
664
659
  }
665
660
 
@@ -671,9 +666,7 @@ class MetadataType {
671
666
  * @returns {Promise.<string[]>} Returns list of keys that were paused
672
667
  */
673
668
  static async pause(keyArr, cache) {
674
- Util.logger.error(
675
- ` ☇ skipping ${this.definition.type}: pause is not supported yet for ${this.definition.type}`
676
- );
669
+ Util.notSupportedError(this.definition, 'pause');
677
670
  return [];
678
671
  }
679
672
 
@@ -684,9 +677,7 @@ class MetadataType {
684
677
  * @returns {Promise.<string[]>} Returns list of keys that were stopped
685
678
  */
686
679
  static async stop(keyArr) {
687
- Util.logger.error(
688
- ` ☇ skipping ${this.definition.type}: stop is not supported yet for ${this.definition.type}`
689
- );
680
+ Util.notSupportedError(this.definition, 'stop');
690
681
  return [];
691
682
  }
692
683
 
@@ -1493,7 +1484,7 @@ class MetadataType {
1493
1484
  */
1494
1485
  static async retrieveRESTcollection(urlArray, concurrentRequests = 10, logAmountOfUrls = true) {
1495
1486
  if (logAmountOfUrls) {
1496
- Util.logger.info(
1487
+ Util.logger.debug(
1497
1488
  Util.getGrayMsg(
1498
1489
  ` - ${urlArray?.length} ${this.definition.type}${
1499
1490
  urlArray?.length === 1 ? '' : 's'
@@ -2341,6 +2332,17 @@ class MetadataType {
2341
2332
  );
2342
2333
  }
2343
2334
 
2335
+ // run validations before buildDefinitionForNested because that method writes extracted code to disk
2336
+ metadata = await this.validation(
2337
+ 'buildDefinition',
2338
+ metadata,
2339
+ Array.isArray(targetDir) ? targetDir[0] : targetDir
2340
+ );
2341
+ if (!metadata) {
2342
+ // we encountered a situation in our validation that made us want to filter this record
2343
+ return;
2344
+ }
2345
+
2344
2346
  // handle extracted code
2345
2347
  // run after metadata was templated and converted into JS-object
2346
2348
  // templating to extracted content is applied inside of buildDefinitionForNested()
@@ -2353,16 +2355,6 @@ class MetadataType {
2353
2355
  );
2354
2356
 
2355
2357
  try {
2356
- metadata = await this.validation(
2357
- 'buildDefinition',
2358
- metadata,
2359
- Array.isArray(targetDir) ? targetDir[0] : targetDir
2360
- );
2361
- if (!metadata) {
2362
- // we encountered a situation in our validation that made us want to filter this record
2363
- return;
2364
- }
2365
-
2366
2358
  // write to file
2367
2359
  const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
2368
2360
  for (const targetDir of targetDirArr) {
@@ -2446,7 +2438,7 @@ class MetadataType {
2446
2438
  */
2447
2439
  static document(metadata, isDeploy) {
2448
2440
  if (!isDeploy) {
2449
- Util.logger.error(`Documenting type ${this.definition.type} is not supported.`);
2441
+ Util.notSupportedError(this.definition, 'document');
2450
2442
  }
2451
2443
  }
2452
2444
 
@@ -2457,7 +2449,7 @@ class MetadataType {
2457
2449
  * @returns {Promise.<{key:string, name:string}>} key, name and path of metadata; null if not found
2458
2450
  */
2459
2451
  static async resolveId(id) {
2460
- Util.logger.error(`resolveId type ${this.definition.type} is not supported.`);
2452
+ Util.notSupportedError(this.definition, 'resolveId');
2461
2453
  return;
2462
2454
  }
2463
2455
 
@@ -2468,7 +2460,7 @@ class MetadataType {
2468
2460
  * @returns {Promise.<boolean>} deletion success status
2469
2461
  */
2470
2462
  static async deleteByKey(customerKey) {
2471
- Util.logger.error(`Deletion is not yet supported for ${this.definition.typeName}!`);
2463
+ Util.notSupportedError(this.definition, 'delete');
2472
2464
  return false;
2473
2465
  }
2474
2466
 
@@ -2497,26 +2489,33 @@ class MetadataType {
2497
2489
  /**
2498
2490
  * Delete a data extension from the specified business unit
2499
2491
  *
2500
- * @param {string} customerKey Identifier of metadata
2492
+ * @param {string} key Identifier of metadata
2501
2493
  * @param {string} [overrideKeyField] optionally change the name of the key field if the api uses a different name
2494
+ * @param {number} [codeNotFound] error code that is responded with if the item was not found
2502
2495
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
2503
2496
  * @returns {Promise.<boolean>} deletion success flag
2504
2497
  */
2505
- static async deleteByKeySOAP(customerKey, overrideKeyField, handleOutside) {
2498
+ static async deleteByKeySOAP(key, overrideKeyField, codeNotFound, handleOutside) {
2506
2499
  const metadata = {};
2507
- metadata[overrideKeyField || this.definition.keyField] = customerKey;
2500
+ metadata[overrideKeyField || this.definition.keyField] = key;
2508
2501
  const soapType = this.definition.soapType || this.definition.type;
2509
2502
  try {
2510
2503
  await this.client.soap.delete(Util.capitalizeFirstLetter(soapType), metadata, null);
2511
2504
  if (!handleOutside) {
2512
- Util.logger.info(` - deleted ${this.definition.type}: ${customerKey}`);
2505
+ Util.logger.info(` - deleted ${this.definition.type}: ${key}`);
2513
2506
  }
2514
- this.postDeleteTasks(customerKey);
2507
+ await this.postDeleteTasks(key);
2515
2508
 
2516
2509
  return true;
2517
2510
  } catch (ex) {
2518
2511
  if (handleOutside) {
2519
2512
  throw ex;
2513
+ } else if (
2514
+ codeNotFound &&
2515
+ (ex.results?.[0]?.ErrorCode == codeNotFound ||
2516
+ ex.json?.Results?.[0]?.ErrorCode == codeNotFound)
2517
+ ) {
2518
+ await this.deleteNotFound(key);
2520
2519
  } else {
2521
2520
  const errorMsg = ex?.results?.length
2522
2521
  ? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
@@ -2524,7 +2523,7 @@ class MetadataType {
2524
2523
  ? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
2525
2524
  : ex.message;
2526
2525
  Util.logger.error(
2527
- ` - Deleting ${this.definition.type} '${customerKey}' failed: ${errorMsg}`
2526
+ ` - Deleting ${this.definition.type} '${key}' failed: ${errorMsg}`
2528
2527
  );
2529
2528
  }
2530
2529
 
@@ -2537,21 +2536,27 @@ class MetadataType {
2537
2536
  *
2538
2537
  * @param {string} url endpoint
2539
2538
  * @param {string} key Identifier of metadata
2539
+ * @param {number} [codeNotFound] error code that is responded with if the item was not found
2540
2540
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
2541
2541
  * @returns {Promise.<boolean>} deletion success flag
2542
2542
  */
2543
- static async deleteByKeyREST(url, key, handleOutside) {
2543
+ static async deleteByKeyREST(url, key, codeNotFound, handleOutside) {
2544
2544
  try {
2545
2545
  await this.client.rest.delete(url);
2546
2546
  if (!handleOutside) {
2547
2547
  Util.logger.info(` - deleted ${this.definition.type}: ${key}`);
2548
2548
  }
2549
- this.postDeleteTasks(key);
2549
+ await this.postDeleteTasks(key);
2550
2550
 
2551
2551
  return true;
2552
2552
  } catch (ex) {
2553
2553
  if (handleOutside) {
2554
2554
  throw ex;
2555
+ } else if (
2556
+ codeNotFound &&
2557
+ (ex.code === codeNotFound || ex.response?.status === codeNotFound)
2558
+ ) {
2559
+ await this.deleteNotFound(key);
2555
2560
  } else {
2556
2561
  Util.logger.errorStack(ex, ` - Deleting ${this.definition.type} '${key}' failed`);
2557
2562
  }
@@ -2560,6 +2565,18 @@ class MetadataType {
2560
2565
  }
2561
2566
  }
2562
2567
 
2568
+ /**
2569
+ * helper for {@link deleteByKey}, {@link deleteByKeyREST}, {@link deleteByKeySOAP}
2570
+ *
2571
+ * @param {string} key Identifier of metadata
2572
+ */
2573
+ static async deleteNotFound(key) {
2574
+ Util.logger.error(
2575
+ ` ☇ skipping deletion of ${this.definition.type} ${key}: not found on server`
2576
+ );
2577
+ await this.postDeleteTasks(key);
2578
+ }
2579
+
2563
2580
  /**
2564
2581
  * Returns metadata of a business unit that is saved locally
2565
2582
  *
@@ -2600,7 +2617,9 @@ class MetadataType {
2600
2617
  this.definition.type,
2601
2618
  ]);
2602
2619
 
2603
- const fileList = keyArr.map((key) => File.normalizePath([path, key + typeExtension]));
2620
+ const fileList = keyArr.map((key) =>
2621
+ File.normalizePath([path, File.filterIllegalFilenames(key) + typeExtension])
2622
+ );
2604
2623
  return fileList;
2605
2624
  }
2606
2625
 
@@ -2641,7 +2660,10 @@ class MetadataType {
2641
2660
  // make sure we don't retry components that we have already searched without result
2642
2661
  !notFoundList[this.definition.type].includes(key) &&
2643
2662
  // make sure we don't search for components that we have already found
2644
- !multiTypeKeyList[this.definition.type].includes(key)
2663
+ !multiTypeKeyList[this.definition.type].includes(key) &&
2664
+ // filter out ampscript code that was used instead of actual keys; relevant e.g. for domainVerifications mentioned in senderProfiles
2665
+ !key.startsWith('%%') &&
2666
+ !key.endsWith('%%')
2645
2667
  );
2646
2668
  if (!keyArr.length) {
2647
2669
  return;
@@ -2943,7 +2965,7 @@ class MetadataType {
2943
2965
  let passed;
2944
2966
 
2945
2967
  try {
2946
- passed = await (mode === 'fix'
2968
+ passed = await (!Util.OPTIONS.skipValidation && mode === 'fix'
2947
2969
  ? validationRules[rule].fix()
2948
2970
  : validationRules[rule].passed());
2949
2971
  } catch (ex) {
@@ -2958,7 +2980,7 @@ class MetadataType {
2958
2980
  fixed = true;
2959
2981
  }
2960
2982
  if (!passed) {
2961
- if (mode === 'fix') {
2983
+ if (!Util.OPTIONS.skipValidation && mode === 'fix') {
2962
2984
  if (passed === false) {
2963
2985
  info.push('😎 auto-fixed: ' + validationRules[rule].failedMsg);
2964
2986
  } else if (passed === null) {
@@ -3000,7 +3022,7 @@ class MetadataType {
3000
3022
  );
3001
3023
  }
3002
3024
  // only return "fixed" item if --fix is set, otherwise return the original item
3003
- return Util.OPTIONS.fix || fixed ? item : originalItem;
3025
+ return !Util.OPTIONS.skipValidation && (Util.OPTIONS.fix || fixed) ? item : originalItem;
3004
3026
  }
3005
3027
  }
3006
3028
 
@@ -491,7 +491,7 @@ class MobileKeyword extends MetadataType {
491
491
  // get id from cache
492
492
  const { metadata } = await this.retrieveForCache(undefined, undefined, key);
493
493
  if (!metadata[key]) {
494
- Util.logger.error(` - mobileKeyword ${key} not found`);
494
+ await this.deleteNotFound(key);
495
495
  return false;
496
496
  }
497
497
  const id = metadata[key][this.definition.idField];
@@ -499,7 +499,7 @@ class MobileKeyword extends MetadataType {
499
499
  Util.logger.info(
500
500
  ' - Note: As long as the provided API key once existed, you will not see an error even if the mobileKeyword is already deleted.'
501
501
  );
502
- return super.deleteByKeyREST('/legacy/v1/beta/mobile/keyword/' + id, key, false);
502
+ return super.deleteByKeyREST('/legacy/v1/beta/mobile/keyword/' + id, key);
503
503
  }
504
504
 
505
505
  /**
@@ -491,9 +491,11 @@ class MobileMessage extends MetadataType {
491
491
  */
492
492
  static deleteByKey(id) {
493
493
  Util.logger.info(
494
- ' - Note: As long as the provided API key once existed, you will not see an error even if the mobileMessage is already deleted.'
494
+ Util.getGrayMsg(
495
+ ' - Note: As long as the provided API key once existed, you will not see an error even if the mobileMessage is already deleted.'
496
+ )
495
497
  );
496
- return super.deleteByKeyREST('/legacy/v1/beta/mobile/message/' + id, id, false);
498
+ return super.deleteByKeyREST('/legacy/v1/beta/mobile/message/' + id, id, 400);
497
499
  }
498
500
  }
499
501