mcdev 7.10.1 → 8.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 (144) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/.github/workflows/code-test.yml +8 -100
  3. package/@types/lib/Deployer.d.ts.map +1 -1
  4. package/@types/lib/Retriever.d.ts.map +1 -1
  5. package/@types/lib/index.d.ts +16 -4
  6. package/@types/lib/index.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/Asset.d.ts +45 -8
  8. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Automation.d.ts +13 -4
  10. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/Event.d.ts +6 -0
  14. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/Folder.d.ts +49 -12
  16. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/ImportFile.d.ts +14 -0
  18. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  19. package/@types/lib/metadataTypes/Journey.d.ts +166 -6
  20. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/MetadataType.d.ts +5 -3
  22. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  23. package/@types/lib/metadataTypes/definitions/Asset.definition.d.ts +45 -8
  24. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +6 -0
  25. package/@types/lib/metadataTypes/definitions/Event.definition.d.ts +6 -0
  26. package/@types/lib/metadataTypes/definitions/Folder.definition.d.ts +49 -12
  27. package/@types/lib/metadataTypes/definitions/ImportFile.definition.d.ts +3 -0
  28. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +159 -6
  29. package/@types/lib/util/cache.d.ts +2 -1
  30. package/@types/lib/util/cache.d.ts.map +1 -1
  31. package/@types/lib/util/devops.d.ts.map +1 -1
  32. package/@types/types/mcdev.d.d.ts +16 -0
  33. package/@types/types/mcdev.d.d.ts.map +1 -1
  34. package/boilerplate/files/.vscode/settings.json +5 -0
  35. package/eslint.config.js +2 -2
  36. package/lib/Deployer.js +3 -0
  37. package/lib/Retriever.js +8 -2
  38. package/lib/cli.js +29 -0
  39. package/lib/index.js +25 -28
  40. package/lib/metadataTypes/Asset.js +126 -10
  41. package/lib/metadataTypes/Automation.js +135 -40
  42. package/lib/metadataTypes/DataExtension.js +0 -10
  43. package/lib/metadataTypes/DataExtensionField.js +9 -15
  44. package/lib/metadataTypes/Event.js +42 -19
  45. package/lib/metadataTypes/Folder.js +98 -12
  46. package/lib/metadataTypes/ImportFile.js +90 -29
  47. package/lib/metadataTypes/Journey.js +182 -23
  48. package/lib/metadataTypes/MetadataType.js +38 -12
  49. package/lib/metadataTypes/definitions/Asset.definition.js +196 -142
  50. package/lib/metadataTypes/definitions/Automation.definition.js +6 -0
  51. package/lib/metadataTypes/definitions/DataExtension.definition.js +7 -6
  52. package/lib/metadataTypes/definitions/Event.definition.js +6 -0
  53. package/lib/metadataTypes/definitions/Folder.definition.js +69 -22
  54. package/lib/metadataTypes/definitions/ImportFile.definition.js +3 -0
  55. package/lib/metadataTypes/definitions/Journey.definition.js +165 -11
  56. package/lib/util/cache.js +24 -5
  57. package/lib/util/devops.js +20 -5
  58. package/package.json +16 -17
  59. package/test/general.test.js +8 -8
  60. package/test/mockRoot/.mcdev-validations.js +2 -3
  61. package/test/mockRoot/.mcdevrc.json +1 -1
  62. package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +2 -1
  63. package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +2 -1
  64. package/test/resourceFactory.js +31 -5
  65. package/test/resources/1111111/dataExtension/update-expected.json +1 -1
  66. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.js +1 -0
  67. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.json +47 -0
  68. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.json +47 -0
  69. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.jsonc +1 -0
  70. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.json +46 -0
  71. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.xml +1 -0
  72. package/test/resources/9999999/asset/test_interactivecontent-retrieve-expected.json +43 -0
  73. package/test/resources/9999999/asset/test_landingpage-retrieve-expected.json +42 -0
  74. package/test/resources/9999999/asset/test_microsite-retrieve-expected.json +43 -0
  75. package/test/resources/9999999/asset/v1/content/assets/9451/get-response.json +61 -0
  76. package/test/resources/9999999/asset/v1/content/assets/9456/get-response.json +56 -0
  77. package/test/resources/9999999/asset/v1/content/assets/9458/get-response.json +56 -0
  78. package/test/resources/9999999/asset/v1/content/assets/9460/get-response.json +59 -0
  79. package/test/resources/9999999/asset/v1/content/assets/9463/get-response.json +61 -0
  80. package/test/resources/9999999/asset/v1/content/assets/9465/get-response.json +54 -0
  81. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN1,3,4,14,15,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,210,211,212,213,214,215,216,217,218,219,220,221,222.json +94 -1
  82. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232,240,241,242,243,244,245.json +168 -0
  83. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN240,241,242,243,244,245.json +144 -0
  84. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN246,247,248,249.json +132 -0
  85. package/test/resources/9999999/dataExtension/update-callout-afterCreatedViaEvent-expected.xml +1 -1
  86. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,triggered,triggered,useriniti-response.xml} +44 -1
  87. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml → retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml} +44 -0
  88. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml} +44 -0
  89. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,journey-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,journey-response.xml} +44 -0
  90. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages,ssjsactivity-response.xml +136 -0
  91. package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,ssjsactivity-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml} +29 -7
  92. package/test/resources/9999999/dataFolder/{+retrieve-response.xml → retrieve-response.xml} +23 -0
  93. package/test/resources/9999999/dataFolder/update-response.xml +31 -0
  94. package/test/resources/9999999/event/build-expected.json +0 -1
  95. package/test/resources/9999999/event/get-automation-expected.json +0 -1
  96. package/test/resources/9999999/event/get-expected.json +0 -2
  97. package/test/resources/9999999/event/get-published-expected.json +0 -2
  98. package/test/resources/9999999/event/post_withExistingDE-expected.json +0 -2
  99. package/test/resources/9999999/event/post_withSchema-callout-expected.json +3 -0
  100. package/test/resources/9999999/event/post_withSchema-expected.json +0 -2
  101. package/test/resources/9999999/event/put-callout-expected.json +3 -0
  102. package/test/resources/9999999/event/put-expected.json +0 -2
  103. package/test/resources/9999999/event/template-expected.json +0 -1
  104. package/test/resources/9999999/folder-deploy/Data Extensions/testExisting_folder.folder-meta.json +9 -0
  105. package/test/resources/9999999/importFile/get-dataImport-expected.json +1 -1
  106. package/test/resources/9999999/importFile/get-expected.json +4 -1
  107. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=1.json +86 -0
  108. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=2.json +101 -0
  109. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=3.json +86 -0
  110. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/delete-response-versionNumber=1.txt +1 -0
  111. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=1.json +461 -0
  112. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=3.json +461 -0
  113. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=1.json +38 -0
  114. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=2.json +53 -0
  115. package/test/resources/9999999/interaction/v1/interactions/get-response.json +2 -2
  116. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/get-response-versionNumber=1.json +461 -0
  117. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Quicksend/get-response-versionNumber=1.json +253 -0
  118. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/get-response-versionNumber=1.json +219 -0
  119. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/get-response-versionNumber=1.json +226 -0
  120. package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/post-response-versionNumber=3.json +4 -0
  121. package/test/resources/9999999/journey/build-expected.json +0 -1
  122. package/test/resources/9999999/journey/create-transactionaEmail-publish-expected.json +0 -1
  123. package/test/resources/9999999/journey/get-multistep-expected.json +0 -4
  124. package/test/resources/9999999/journey/get-published-expected.json +0 -1
  125. package/test/resources/9999999/journey/get-quicksend-expected.json +0 -1
  126. package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +0 -1
  127. package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +0 -1
  128. package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +0 -1
  129. package/test/resources/9999999/journey/get-transactionalEmail-expected.json +0 -1
  130. package/test/resources/9999999/journey/template-expected.json +0 -1
  131. package/test/resources/9999999/query/patch-expected.sql +1 -1
  132. package/test/resources/9999999/query/post-expected.sql +1 -1
  133. package/test/resources/9999999/script/patch-expected.ssjs +1 -0
  134. package/test/type.asset.test.js +123 -13
  135. package/test/type.automation.test.js +3 -1
  136. package/test/type.folder.test.js +12 -2
  137. package/test/type.journey.test.js +130 -1
  138. package/test/type.script.test.js +1 -0
  139. package/test/type.senderProfile.test.js +1 -0
  140. package/test/utils.js +2 -0
  141. package/types/mcdev.d.js +4 -0
  142. package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN219,220,221,222,223,224,225,226,227,228,230,232.json +0 -32
  143. /package/test/resources/9999999/dataFolder/{+retrieve-QAA-response.xml → retrieve-QAA-response.xml} +0 -0
  144. /package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/{post-response.json → post-response-versionNumber=1.json} +0 -0
@@ -224,7 +224,9 @@ class Folder extends MetadataType {
224
224
  'folder',
225
225
  deployableMetadata.Path,
226
226
  'Path',
227
- 'ID'
227
+ 'ID',
228
+ undefined,
229
+ true
228
230
  );
229
231
  const cachedVersion = cache.getByKey(
230
232
  'folder',
@@ -232,7 +234,9 @@ class Folder extends MetadataType {
232
234
  'folder',
233
235
  deployableMetadata.Path,
234
236
  'Path',
235
- this.definition.keyField
237
+ this.definition.keyField,
238
+ undefined,
239
+ true
236
240
  )
237
241
  );
238
242
  if (
@@ -371,7 +375,7 @@ class Folder extends MetadataType {
371
375
  }
372
376
  const path = metadataEntry.Path;
373
377
  try {
374
- if (this.definition.deployFolderTypesRest.includes(metadataEntry.ContentType)) {
378
+ if (this.definition.deployFolderTypesEmailRest.includes(metadataEntry.ContentType)) {
375
379
  // * The SOAP endpoint for creating folders does not support folders for automations nor journeys. The Rest endpoint on the other hand errors out on certain characters in the folder names that are actually valid. We therefore only use Rest for the folder types that are not supported by SOAP.
376
380
  const restPayload = {
377
381
  parentCatId: metadataEntry.ParentFolder.ID,
@@ -379,7 +383,7 @@ class Folder extends MetadataType {
379
383
  catType: metadataEntry.ContentType,
380
384
  };
381
385
  const response = await super.createREST(restPayload, '/email/v1/category', true);
382
- if (response?.objectId) {
386
+ if (response?.categoryId) {
383
387
  // convert the response to the same format as the SOAP response
384
388
 
385
389
  // set the new folder ID
@@ -400,7 +404,48 @@ class Folder extends MetadataType {
400
404
  Util.logger.info(` - created folder: ${path}`);
401
405
  return returnObject;
402
406
  } else {
403
- throw new Error(response);
407
+ throw new Error(JSON.stringify(response));
408
+ }
409
+ } else if (
410
+ this.definition.deployFolderTypesAssetRest.includes(metadataEntry.ContentType)
411
+ ) {
412
+ // * The SOAP endpoint for creating folders does not support folders for automations nor journeys. The Rest endpoint on the other hand errors out on certain characters in the folder names that are actually valid. We therefore only use Rest for the folder types that are not supported by SOAP.
413
+ const restPayload = {
414
+ parentId: metadataEntry.ParentFolder.ID,
415
+ categoryType: metadataEntry.ContentType,
416
+ extendable: 1,
417
+ editable: 1,
418
+ name: metadataEntry.Name,
419
+ description: '',
420
+ };
421
+
422
+ const response = await super.createREST(
423
+ restPayload,
424
+ '/asset/v1/content/categories',
425
+ true
426
+ );
427
+ if (response?.id) {
428
+ // convert the response to the same format as the SOAP response
429
+
430
+ // set the new folder ID
431
+ metadataEntry.ID = response.id;
432
+ // set the client ID to ensure we can find the newly created folders as parents for folders created afterwards inside of it
433
+ metadataEntry.Client = {
434
+ ID: metadataEntry.Client?.ID || this.buObject.mid,
435
+ };
436
+ // the following is a bit of a hack to make the response look like the SOAP response; not sure if we actually need that anywhere like this --> future developers feel free to double check
437
+ const returnObject = {
438
+ Results: [
439
+ {
440
+ Object: metadataEntry,
441
+ },
442
+ ],
443
+ };
444
+
445
+ Util.logger.info(` - created folder: ${path}`);
446
+ return returnObject;
447
+ } else {
448
+ throw new Error(JSON.stringify(response));
404
449
  }
405
450
  } else {
406
451
  const response = await super.createSOAP(metadataEntry, true);
@@ -446,13 +491,54 @@ class Folder extends MetadataType {
446
491
  }
447
492
  const path = metadataEntry.Path;
448
493
  try {
449
- const response = await super.updateSOAP(metadataEntry, true);
450
- if (response.Results?.[0]?.StatusCode === 'OK') {
451
- response.Results[0].Object = metadataEntry;
452
- response.Results[0].Object.CustomerKey = metadataEntry.CustomerKey;
453
- delete response.Results[0].Object.$;
454
- Util.logger.info(` - updated folder: ${path}`);
455
- return response;
494
+ let response;
495
+ if (this.definition.deployFolderTypesAssetRest.includes(metadataEntry.ContentType)) {
496
+ // * The SOAP endpoint for creating folders does not support folders for automations nor journeys. The Rest endpoint on the other hand errors out on certain characters in the folder names that are actually valid. We therefore only use Rest for the folder types that are not supported by SOAP.
497
+ const restPayload = {
498
+ id: metadataEntry.ID,
499
+ parentId: metadataEntry.ParentFolder.ID,
500
+ // categoryType: metadataEntry.ContentType,
501
+ extendable: 1, // ?
502
+ editable: 1, // ?
503
+ name: metadataEntry.Name,
504
+ description: '',
505
+ };
506
+ response = await super.updateREST(
507
+ restPayload,
508
+ '/asset/v1/content/categories/' + metadataEntry.ID,
509
+ 'put',
510
+ true
511
+ );
512
+ if (response?.id) {
513
+ // convert the response to the same format as the SOAP response
514
+
515
+ // set the client ID to ensure we can find the newly created folders as parents for folders created afterwards inside of it
516
+ metadataEntry.Client = {
517
+ ID: metadataEntry.Client?.ID || this.buObject.mid,
518
+ };
519
+ // the following is a bit of a hack to make the response look like the SOAP response; not sure if we actually need that anywhere like this --> future developers feel free to double check
520
+ const returnObject = {
521
+ Results: [
522
+ {
523
+ Object: metadataEntry,
524
+ },
525
+ ],
526
+ };
527
+
528
+ Util.logger.info(` - updated folder: ${path}`);
529
+ return returnObject;
530
+ } else {
531
+ throw new Error(JSON.stringify(response));
532
+ }
533
+ } else {
534
+ response = await super.updateSOAP(metadataEntry, true);
535
+ if (response.Results?.[0]?.StatusCode === 'OK') {
536
+ response.Results[0].Object = metadataEntry;
537
+ response.Results[0].Object.CustomerKey = metadataEntry.CustomerKey;
538
+ delete response.Results[0].Object.$;
539
+ Util.logger.info(` - updated folder: ${path}`);
540
+ return response;
541
+ }
456
542
  }
457
543
  } catch (ex) {
458
544
  if (ex?.results) {
@@ -16,6 +16,7 @@ import cache from '../util/cache.js';
16
16
  * @typedef {import('../../types/mcdev.d.js').MetadataTypeMapObj} MetadataTypeMapObj
17
17
  * @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams
18
18
  * @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap
19
+ * @typedef {import('../../types/mcdev.d.js').SDKError} SDKError
19
20
  */
20
21
 
21
22
  /**
@@ -85,6 +86,38 @@ class ImportFile extends MetadataType {
85
86
  );
86
87
  }
87
88
 
89
+ /**
90
+ * helper for {@link MetadataType.retrieveRESTcollection}
91
+ *
92
+ * @param {SDKError} ex exception
93
+ * @param {string} key id or key of item
94
+ * @param {string} url url to call for retry
95
+ * @returns {Promise.<any>} can return retry-result
96
+ */
97
+ static async handleRESTErrors(ex, key, url) {
98
+ try {
99
+ if (ex.code == 'ERR_BAD_RESPONSE') {
100
+ // one more retry; it's a rare case but retrying again should solve the issue gracefully
101
+ Util.logger.info(
102
+ ` - Connection problem (Code: ${ex.code}). Retrying once${
103
+ ex.endpoint
104
+ ? Util.getGrayMsg(
105
+ ' - ' + ex.endpoint.split('rest.marketingcloudapis.com')[1]
106
+ )
107
+ : ''
108
+ }`
109
+ );
110
+ Util.logger.errorStack(ex);
111
+ return await this.client.rest.get(url);
112
+ }
113
+ } catch {
114
+ // no extra action needed, handled below
115
+ }
116
+ // if we do get here, we should log the error and continue instead of failing to download all automations
117
+ Util.logger.error(` ☇ skipping ${this.definition.type} ${key}: ${ex.message} ${ex.code}`);
118
+ return null;
119
+ }
120
+
88
121
  /**
89
122
  * Retrieves import definition metadata for caching
90
123
  *
@@ -228,16 +261,21 @@ class ImportFile extends MetadataType {
228
261
  * @returns {Promise.<MetadataTypeItem>} Promise
229
262
  */
230
263
  static async preDeployTasks(metadata) {
231
- const fileLocation = cache.getByKey('fileLocation', metadata.source?.r__fileLocation_name);
232
- if (!fileLocation) {
233
- throw new Error(
234
- `fileLocation ${metadata.source?.r__fileLocation_name} not found in cache`
264
+ if (metadata.source?.r__fileLocation_name) {
265
+ const fileLocation = cache.getByKey(
266
+ 'fileLocation',
267
+ metadata.source?.r__fileLocation_name
235
268
  );
236
- }
269
+ if (!fileLocation) {
270
+ throw new Error(
271
+ `fileLocation ${metadata.source?.r__fileLocation_name} not found in cache`
272
+ );
273
+ }
237
274
 
238
- metadata.fileTransferLocationId = fileLocation.id;
239
- metadata.fileTransferLocationTypeId = fileLocation.locationTypeId;
240
- delete metadata.source.r__fileLocation_name;
275
+ metadata.fileTransferLocationId = fileLocation.id;
276
+ metadata.fileTransferLocationTypeId = fileLocation.locationTypeId;
277
+ delete metadata.source.r__fileLocation_name;
278
+ }
241
279
 
242
280
  switch (metadata.destination.c__type) {
243
281
  case 'DataExtension': {
@@ -332,18 +370,14 @@ class ImportFile extends MetadataType {
332
370
  delete metadata.source.r__dataExtension_key;
333
371
  }
334
372
 
335
- Util.logger.debug(
336
- ` - importFile ${metadata[this.definition.keyField]}: Import Destination Type ${
337
- metadata.destination.c__type
338
- } not fully supported. Deploy might fail.`
373
+ throw new Error(
374
+ `Import Destination Type ${metadata.destination.c__type} not fully supported.`
339
375
  );
340
- break;
341
376
  }
342
377
  default: {
343
- Util.logger.debug(
344
- ` - importFile ${metadata[this.definition.keyField]}: Import Destination Type ${
345
- metadata.destination.c__type
346
- } not fully supported. Deploy might fail.`
378
+ // e.g. WhatsApp
379
+ throw new Error(
380
+ `Import Destination Type ${metadata.destination.c__type} not fully supported.`
347
381
  );
348
382
  }
349
383
  }
@@ -381,18 +415,24 @@ class ImportFile extends MetadataType {
381
415
  // destination.c__type SMS & DataExtension both set fileNamingPattern to _CustomObject and they both define a DE as source
382
416
  metadata.source = {
383
417
  c__type:
384
- metadata.fileNamingPattern === '_CustomObject' ? 'DataExtension' : 'File Location',
418
+ metadata.fileNamingPattern === '_CustomObject' ||
419
+ metadata.fileSpec === '_CustomObject'
420
+ ? 'DataExtension'
421
+ : 'File Location',
385
422
  };
386
- try {
387
- metadata.source.r__fileLocation_name = cache.searchForField(
388
- 'fileLocation',
389
- metadata.fileTransferLocationId,
390
- 'id',
391
- 'name'
392
- );
393
- delete metadata.fileTransferLocationId;
394
- } catch (ex) {
395
- Util.logger.warn(` - importFile ${metadata.customerKey}: ${ex.message}`);
423
+
424
+ if (metadata.fileTransferLocationId !== '00000000-0000-0000-0000-000000000000') {
425
+ try {
426
+ metadata.source.r__fileLocation_name = cache.searchForField(
427
+ 'fileLocation',
428
+ metadata.fileTransferLocationId,
429
+ 'id',
430
+ 'name'
431
+ );
432
+ delete metadata.fileTransferLocationId;
433
+ } catch (ex) {
434
+ Util.logger.warn(` - importFile ${metadata.customerKey}: ${ex.message}`);
435
+ }
396
436
  }
397
437
 
398
438
  switch (metadata.destination.c__type) {
@@ -481,6 +521,27 @@ class ImportFile extends MetadataType {
481
521
 
482
522
  break;
483
523
  }
524
+ case 'WhatsApp': {
525
+ if (
526
+ metadata.source.c__type === 'DataExtension' &&
527
+ metadata.sourceCustomObjectId !== ''
528
+ ) {
529
+ // only happens for dataimport activities (summer24 release)
530
+ try {
531
+ metadata.source.r__dataExtension_key = cache.searchForField(
532
+ 'dataExtension',
533
+ metadata.sourceCustomObjectId,
534
+ 'ObjectID',
535
+ 'CustomerKey'
536
+ );
537
+ delete metadata.sourceCustomObjectId;
538
+ delete metadata.sourceDataExtensionName;
539
+ } catch (ex) {
540
+ Util.logger.warn(` - importFile ${metadata.customerKey}: ${ex.message}`);
541
+ }
542
+ }
543
+ break;
544
+ }
484
545
  default: {
485
546
  Util.logger.debug(
486
547
  ` - importFile ${metadata.customerKey}: Destination Type ${metadata.destinationObjectTypeId} not fully supported. Deploy might fail.`
@@ -489,7 +550,7 @@ class ImportFile extends MetadataType {
489
550
  }
490
551
  delete metadata.destinationObjectTypeId;
491
552
 
492
- if (metadata.blankFileProcessingType) {
553
+ if (metadata.blankFileProcessingType !== undefined) {
493
554
  // omit this if not set
494
555
  metadata.c__blankFileProcessing = Util.inverseGet(
495
556
  this.definition.blankFileProcessingTypeMapping,
@@ -270,14 +270,14 @@ class Journey extends MetadataType {
270
270
  if (version !== '*') {
271
271
  if (!/^\d+$/.test(version)) {
272
272
  Util.logger.error(
273
- 'Version is required for deleting interactions to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
273
+ 'Version is required for deleting journeys to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
274
274
  key +
275
275
  '/4'
276
276
  );
277
277
  return false;
278
278
  }
279
279
  Util.logger.warn(
280
- `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all interactions on this BU.`
280
+ `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all journeys on this BU.`
281
281
  );
282
282
  }
283
283
 
@@ -639,6 +639,9 @@ class Journey extends MetadataType {
639
639
  * @param {MetadataTypeItem} metadata a single item
640
640
  */
641
641
  static _postRetrieveTasks_activities(metadata) {
642
+ if (!metadata.activities) {
643
+ return;
644
+ }
642
645
  for (const activity of metadata.activities) {
643
646
  switch (activity.type) {
644
647
  case 'EMAILV2': {
@@ -1407,9 +1410,7 @@ class Journey extends MetadataType {
1407
1410
  default: {
1408
1411
  // it is expected that we'll see 'sms' and 'push' here in the future
1409
1412
  throw new Error(
1410
- `channel ${
1411
- metadata.channel
1412
- } is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1413
+ `channel ${metadata.channel} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1413
1414
  );
1414
1415
  }
1415
1416
  }
@@ -1418,9 +1419,7 @@ class Journey extends MetadataType {
1418
1419
  }
1419
1420
  default: {
1420
1421
  throw new Error(
1421
- `definitionType ${
1422
- metadata.definitionType
1423
- } is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1422
+ `definitionType ${metadata.definitionType} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
1424
1423
  );
1425
1424
  }
1426
1425
  }
@@ -2333,8 +2332,8 @@ class Journey extends MetadataType {
2333
2332
  case 'PublishCompleted': {
2334
2333
  spinner.stop();
2335
2334
  const action = statusUrl.includes('/validateStatus')
2336
- ? 'validation successful -'
2337
- : 'published';
2335
+ ? '🔎 validation successful -'
2336
+ : '🚀 published';
2338
2337
 
2339
2338
  Util.logger.info(` - ${action} ${this.definition.type}: ${key} / ${name}`);
2340
2339
  this._showPublishStatusDetails(response);
@@ -2567,6 +2566,159 @@ class Journey extends MetadataType {
2567
2566
  return executedKeyArr.filter(Boolean);
2568
2567
  }
2569
2568
 
2569
+ /**
2570
+ * audit latest or given journey version
2571
+ *
2572
+ * @param {string[]} keyArr customerkey of the metadata
2573
+ * @returns {Promise.<string[]>} Returns list of keys that were paused
2574
+ */
2575
+ static async audit(keyArr) {
2576
+ let version;
2577
+ const endpoint = '/interaction/v1/interactions/';
2578
+ const auditedKeyArr = [];
2579
+ const apiLimit = pLimit(20);
2580
+ const journeyCache = await this.retrieveForCache();
2581
+
2582
+ const ignoredLogProperties = [
2583
+ 'executionMode',
2584
+ 'id',
2585
+ 'originalDefinitionId',
2586
+ 'publishRequestId',
2587
+ ];
2588
+ const preformatedProperties = [
2589
+ 'action',
2590
+ 'description',
2591
+ 'errors',
2592
+ 'key',
2593
+ 'name',
2594
+ 'publishedVersion',
2595
+ 'publishStatus',
2596
+ 'timeStamp',
2597
+ 'user',
2598
+ 'versionNumber',
2599
+ ];
2600
+
2601
+ await Promise.allSettled(
2602
+ keyArr.map((key) =>
2603
+ apiLimit(async () => {
2604
+ [key, version] = key.split('/');
2605
+ if (!journeyCache.metadata[key]) {
2606
+ Util.logger.error(
2607
+ ` ☇ skipping audit log of ${this.definition.type} ${key}: not found on server`
2608
+ );
2609
+ return;
2610
+ }
2611
+ const toBeAuditedVersions = [];
2612
+ if (version === '*' || !version) {
2613
+ switch (journeyCache.metadata[key].definitionType) {
2614
+ case 'Transactional':
2615
+ case 'Multistep': {
2616
+ // transactional send journeys technically only have one version, but if you delete them and then create one with the same key their version number goes up and old audit logs are still available
2617
+ // multi-step journey versions could be retrieved but then we lose info of any versions that might have been deleted. Therefore we add all versions starting with the latest and counting down to 1
2618
+ for (let i = journeyCache.metadata[key].version; i > 0; i--) {
2619
+ toBeAuditedVersions.push(i);
2620
+ }
2621
+ }
2622
+ }
2623
+ } else {
2624
+ toBeAuditedVersions.push(version);
2625
+ }
2626
+
2627
+ Util.logger.info(
2628
+ ` - Audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name}`
2629
+ );
2630
+ const rateLimitActivities = pLimit(1);
2631
+ const auditedVersions = (
2632
+ await Promise.all(
2633
+ toBeAuditedVersions.map((version) =>
2634
+ rateLimitActivities(async () => {
2635
+ try {
2636
+ const response = await this.client.rest.getBulk(
2637
+ endpoint +
2638
+ journeyCache.metadata[key].id +
2639
+ `/audit/all?versionNumber=${version}`
2640
+ );
2641
+
2642
+ Util.logger.info(
2643
+ ` - Version ${version}:\n - ` +
2644
+ response.items
2645
+ .map((log) => {
2646
+ let msg =
2647
+ `${log.action} version ${log.versionNumber} - ${Util.getGrayMsg(log.timeStamp.replace('T', ' ').slice(0, 19))}: ${log.user.name}\n` +
2648
+ (log.publishedVersion
2649
+ ? ` Published Version: ` +
2650
+ log.publishedVersion +
2651
+ '\n'
2652
+ : '') +
2653
+ (log.publishStatus
2654
+ ? ` Publish Status: ` +
2655
+ log.publishStatus +
2656
+ '\n'
2657
+ : '') +
2658
+ (log.key === key
2659
+ ? ''
2660
+ : ` Key: ` + log.key + '\n') +
2661
+ (log.name ===
2662
+ journeyCache.metadata[key].name
2663
+ ? ''
2664
+ : ` Key: ` + log.key + '\n') +
2665
+ (log.errors
2666
+ ? ` Errors:\n • ` +
2667
+ log.errors
2668
+ .map((error) =>
2669
+ error.ErrorDetail.replaceAll(
2670
+ /[\r\n]/g,
2671
+ ' '
2672
+ )
2673
+ )
2674
+ .join('\n • ') +
2675
+ '\n'
2676
+ : '');
2677
+ for (const key in log) {
2678
+ if (
2679
+ !ignoredLogProperties.includes(
2680
+ key
2681
+ ) &&
2682
+ !preformatedProperties.includes(key)
2683
+ ) {
2684
+ msg +=
2685
+ ` ${key}: ` +
2686
+ log[key] +
2687
+ '\n';
2688
+ }
2689
+ }
2690
+ return msg;
2691
+ })
2692
+ .join(' - ')
2693
+ );
2694
+ // Util.logger.info(JSON.stringify(auditLog, null, 2));
2695
+ return version;
2696
+ } catch (ex) {
2697
+ if (journeyCache.metadata[key].version > version) {
2698
+ Util.logger.error(
2699
+ ` - Retrieving audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: Version was not found. The highest available version seems to be ${journeyCache.metadata[key].version}`
2700
+ );
2701
+ } else {
2702
+ Util.logger.error(
2703
+ ` - Retrieving audit log for ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2704
+ );
2705
+ }
2706
+ return;
2707
+ }
2708
+ })
2709
+ )
2710
+ )
2711
+ ).filter(Boolean);
2712
+ if (auditedVersions.length === toBeAuditedVersions.length) {
2713
+ auditedKeyArr.push(key);
2714
+ }
2715
+ })
2716
+ )
2717
+ );
2718
+
2719
+ return auditedKeyArr;
2720
+ }
2721
+
2570
2722
  /**
2571
2723
  * stops latest journey version
2572
2724
  *
@@ -2577,11 +2729,11 @@ class Journey extends MetadataType {
2577
2729
  let version;
2578
2730
  const endpoint = '/interaction/v1/interactions/stop/';
2579
2731
  const stoppedKeyArr = [];
2580
- const pauseTransactionalKeyArr = [];
2732
+ const stopTransactionalKeyArr = [];
2581
2733
  const apiLimit = pLimit(20);
2582
2734
  const journeyCache = await this.retrieveForCache();
2583
2735
 
2584
- const stoppableJourneyStatus = ['Paused', 'Published', 'Finishing'];
2736
+ const stoppableJourneyStatus = ['Paused', 'Published', 'Unpublished']; // 'Unpublished' is shown as 'Finishing' in the UI
2585
2737
 
2586
2738
  await Promise.allSettled(
2587
2739
  keyArr.map((key) =>
@@ -2591,7 +2743,7 @@ class Journey extends MetadataType {
2591
2743
  switch (journeyCache.metadata[key].definitionType) {
2592
2744
  case 'Transactional': {
2593
2745
  // transactional send journeys cannot be "stopped" but only "paused"
2594
- pauseTransactionalKeyArr.push(key);
2746
+ stopTransactionalKeyArr.push(key);
2595
2747
  break;
2596
2748
  }
2597
2749
  case 'Multistep': {
@@ -2652,12 +2804,12 @@ class Journey extends MetadataType {
2652
2804
  {}
2653
2805
  );
2654
2806
  Util.logger.info(
2655
- ` - Stopped ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version}`
2807
+ ` - stopped ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version}`
2656
2808
  );
2657
2809
  return version;
2658
2810
  } catch (ex) {
2659
2811
  Util.logger.error(
2660
- ` - Stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2812
+ ` - stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2661
2813
  );
2662
2814
  return;
2663
2815
  }
@@ -2685,7 +2837,7 @@ class Journey extends MetadataType {
2685
2837
  })
2686
2838
  )
2687
2839
  );
2688
- stoppedKeyArr.push(...(await this.pause(pauseTransactionalKeyArr)));
2840
+ stoppedKeyArr.push(...(await this.pause(stopTransactionalKeyArr)));
2689
2841
 
2690
2842
  return stoppedKeyArr;
2691
2843
  }
@@ -2708,16 +2860,20 @@ class Journey extends MetadataType {
2708
2860
  apiLimit(async () => {
2709
2861
  [key, version] = key.split('/');
2710
2862
  if (journeyCache.metadata[key]) {
2711
- if (journeyCache.metadata[key].status === 'Paused') {
2863
+ if (
2864
+ (!version || journeyCache.metadata[key].version == version) &&
2865
+ journeyCache.metadata[key].status === 'Paused'
2866
+ ) {
2712
2867
  Util.logger.info(
2713
- ` ${this.definition.type} ${
2714
- key
2715
- } (${journeyCache.metadata[key].name}): already paused`
2868
+ ` ${this.definition.type} ${key} (${journeyCache.metadata[key].name}): already paused`
2716
2869
  );
2717
2870
  // still add this key because technically this method is supposed to pause a journey and this journey is paused. mission accomplished. Also, we need that for _refreshItem() to function
2718
2871
  pausedKeyArr.push(key);
2719
2872
  return;
2720
- } else if (journeyCache.metadata[key].status !== 'Published') {
2873
+ } else if (
2874
+ (!version || journeyCache.metadata[key].version == version) &&
2875
+ journeyCache.metadata[key].status !== 'Published'
2876
+ ) {
2721
2877
  Util.logger.error(
2722
2878
  ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2723
2879
  );
@@ -2757,7 +2913,7 @@ class Journey extends MetadataType {
2757
2913
  {}
2758
2914
  );
2759
2915
  Util.logger.info(
2760
- ` -- 🛑 paused ${this.definition.type} ${key}/${version} / ${journeyCache.metadata[key].name}`
2916
+ ` - 🛑 paused ${this.definition.type} ${key}/${version} / ${journeyCache.metadata[key].name}`
2761
2917
  );
2762
2918
  pausedKeyArr.push(key);
2763
2919
  } catch (ex) {
@@ -2803,7 +2959,10 @@ class Journey extends MetadataType {
2803
2959
  apiLimit(async () => {
2804
2960
  [key, version] = key.split('/');
2805
2961
  if (journeyCache.metadata[key]) {
2806
- if (journeyCache.metadata[key].status !== 'Paused') {
2962
+ if (
2963
+ (!version || journeyCache.metadata[key].version == version) &&
2964
+ journeyCache.metadata[key].status !== 'Paused'
2965
+ ) {
2807
2966
  Util.logger.error(
2808
2967
  ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2809
2968
  );