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.
- package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
- package/.github/workflows/code-test.yml +8 -100
- package/@types/lib/Deployer.d.ts.map +1 -1
- package/@types/lib/Retriever.d.ts.map +1 -1
- package/@types/lib/index.d.ts +16 -4
- package/@types/lib/index.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Asset.d.ts +45 -8
- package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Automation.d.ts +13 -4
- package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Event.d.ts +6 -0
- package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Folder.d.ts +49 -12
- package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
- package/@types/lib/metadataTypes/ImportFile.d.ts +14 -0
- package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Journey.d.ts +166 -6
- package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
- package/@types/lib/metadataTypes/MetadataType.d.ts +5 -3
- package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
- package/@types/lib/metadataTypes/definitions/Asset.definition.d.ts +45 -8
- package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +6 -0
- package/@types/lib/metadataTypes/definitions/Event.definition.d.ts +6 -0
- package/@types/lib/metadataTypes/definitions/Folder.definition.d.ts +49 -12
- package/@types/lib/metadataTypes/definitions/ImportFile.definition.d.ts +3 -0
- package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +159 -6
- package/@types/lib/util/cache.d.ts +2 -1
- package/@types/lib/util/cache.d.ts.map +1 -1
- package/@types/lib/util/devops.d.ts.map +1 -1
- package/@types/types/mcdev.d.d.ts +16 -0
- package/@types/types/mcdev.d.d.ts.map +1 -1
- package/boilerplate/files/.vscode/settings.json +5 -0
- package/eslint.config.js +2 -2
- package/lib/Deployer.js +3 -0
- package/lib/Retriever.js +8 -2
- package/lib/cli.js +29 -0
- package/lib/index.js +25 -28
- package/lib/metadataTypes/Asset.js +126 -10
- package/lib/metadataTypes/Automation.js +135 -40
- package/lib/metadataTypes/DataExtension.js +0 -10
- package/lib/metadataTypes/DataExtensionField.js +9 -15
- package/lib/metadataTypes/Event.js +42 -19
- package/lib/metadataTypes/Folder.js +98 -12
- package/lib/metadataTypes/ImportFile.js +90 -29
- package/lib/metadataTypes/Journey.js +182 -23
- package/lib/metadataTypes/MetadataType.js +38 -12
- package/lib/metadataTypes/definitions/Asset.definition.js +196 -142
- package/lib/metadataTypes/definitions/Automation.definition.js +6 -0
- package/lib/metadataTypes/definitions/DataExtension.definition.js +7 -6
- package/lib/metadataTypes/definitions/Event.definition.js +6 -0
- package/lib/metadataTypes/definitions/Folder.definition.js +69 -22
- package/lib/metadataTypes/definitions/ImportFile.definition.js +3 -0
- package/lib/metadataTypes/definitions/Journey.definition.js +165 -11
- package/lib/util/cache.js +24 -5
- package/lib/util/devops.js +20 -5
- package/package.json +16 -17
- package/test/general.test.js +8 -8
- package/test/mockRoot/.mcdev-validations.js +2 -3
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +2 -1
- package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +2 -1
- package/test/resourceFactory.js +31 -5
- package/test/resources/1111111/dataExtension/update-expected.json +1 -1
- package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.js +1 -0
- package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.json +47 -0
- package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.json +47 -0
- package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.jsonc +1 -0
- package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.json +46 -0
- package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.xml +1 -0
- package/test/resources/9999999/asset/test_interactivecontent-retrieve-expected.json +43 -0
- package/test/resources/9999999/asset/test_landingpage-retrieve-expected.json +42 -0
- package/test/resources/9999999/asset/test_microsite-retrieve-expected.json +43 -0
- package/test/resources/9999999/asset/v1/content/assets/9451/get-response.json +61 -0
- package/test/resources/9999999/asset/v1/content/assets/9456/get-response.json +56 -0
- package/test/resources/9999999/asset/v1/content/assets/9458/get-response.json +56 -0
- package/test/resources/9999999/asset/v1/content/assets/9460/get-response.json +59 -0
- package/test/resources/9999999/asset/v1/content/assets/9463/get-response.json +61 -0
- package/test/resources/9999999/asset/v1/content/assets/9465/get-response.json +54 -0
- 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
- 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
- package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN240,241,242,243,244,245.json +144 -0
- package/test/resources/9999999/asset/v1/content/assets/query/post-response-assetType.idIN246,247,248,249.json +132 -0
- package/test/resources/9999999/dataExtension/update-callout-afterCreatedViaEvent-expected.xml +1 -1
- 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
- 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
- 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
- package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,journey-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages,journey-response.xml} +44 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages,ssjsactivity-response.xml +136 -0
- package/test/resources/9999999/dataFolder/{retrieve-ContentTypeINasset,asset-shared,ssjsactivity-response.xml → retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml} +29 -7
- package/test/resources/9999999/dataFolder/{+retrieve-response.xml → retrieve-response.xml} +23 -0
- package/test/resources/9999999/dataFolder/update-response.xml +31 -0
- package/test/resources/9999999/event/build-expected.json +0 -1
- package/test/resources/9999999/event/get-automation-expected.json +0 -1
- package/test/resources/9999999/event/get-expected.json +0 -2
- package/test/resources/9999999/event/get-published-expected.json +0 -2
- package/test/resources/9999999/event/post_withExistingDE-expected.json +0 -2
- package/test/resources/9999999/event/post_withSchema-callout-expected.json +3 -0
- package/test/resources/9999999/event/post_withSchema-expected.json +0 -2
- package/test/resources/9999999/event/put-callout-expected.json +3 -0
- package/test/resources/9999999/event/put-expected.json +0 -2
- package/test/resources/9999999/event/template-expected.json +0 -1
- package/test/resources/9999999/folder-deploy/Data Extensions/testExisting_folder.folder-meta.json +9 -0
- package/test/resources/9999999/importFile/get-dataImport-expected.json +1 -1
- package/test/resources/9999999/importFile/get-expected.json +4 -1
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=1.json +86 -0
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=2.json +101 -0
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/audit/all/get-response-versionNumber=3.json +86 -0
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/delete-response-versionNumber=1.txt +1 -0
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=1.json +461 -0
- package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response-versionNumber=3.json +461 -0
- package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=1.json +38 -0
- package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/audit/all/get-response-versionNumber=2.json +53 -0
- package/test/resources/9999999/interaction/v1/interactions/get-response.json +2 -2
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/get-response-versionNumber=1.json +461 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Quicksend/get-response-versionNumber=1.json +253 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/get-response-versionNumber=1.json +219 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/get-response-versionNumber=1.json +226 -0
- package/test/resources/9999999/interaction/v1/interactions/publishAsync/0175b971-71a3-4d8e-98ac-48121f3fbf4f/post-response-versionNumber=3.json +4 -0
- package/test/resources/9999999/journey/build-expected.json +0 -1
- package/test/resources/9999999/journey/create-transactionaEmail-publish-expected.json +0 -1
- package/test/resources/9999999/journey/get-multistep-expected.json +0 -4
- package/test/resources/9999999/journey/get-published-expected.json +0 -1
- package/test/resources/9999999/journey/get-quicksend-expected.json +0 -1
- package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +0 -1
- package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +0 -1
- package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +0 -1
- package/test/resources/9999999/journey/get-transactionalEmail-expected.json +0 -1
- package/test/resources/9999999/journey/template-expected.json +0 -1
- package/test/resources/9999999/query/patch-expected.sql +1 -1
- package/test/resources/9999999/query/post-expected.sql +1 -1
- package/test/resources/9999999/script/patch-expected.ssjs +1 -0
- package/test/type.asset.test.js +123 -13
- package/test/type.automation.test.js +3 -1
- package/test/type.folder.test.js +12 -2
- package/test/type.journey.test.js +130 -1
- package/test/type.script.test.js +1 -0
- package/test/type.senderProfile.test.js +1 -0
- package/test/utils.js +2 -0
- package/types/mcdev.d.js +4 -0
- 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
- /package/test/resources/9999999/dataFolder/{+retrieve-QAA-response.xml → retrieve-QAA-response.xml} +0 -0
- /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.
|
|
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?.
|
|
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
|
-
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
336
|
-
`
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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'
|
|
418
|
+
metadata.fileNamingPattern === '_CustomObject' ||
|
|
419
|
+
metadata.fileSpec === '_CustomObject'
|
|
420
|
+
? 'DataExtension'
|
|
421
|
+
: 'File Location',
|
|
385
422
|
};
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
metadata.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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
|
|
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
|
|
2732
|
+
const stopTransactionalKeyArr = [];
|
|
2581
2733
|
const apiLimit = pLimit(20);
|
|
2582
2734
|
const journeyCache = await this.retrieveForCache();
|
|
2583
2735
|
|
|
2584
|
-
const stoppableJourneyStatus = ['Paused', 'Published', '
|
|
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
|
-
|
|
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
|
-
` -
|
|
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
|
-
` -
|
|
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(
|
|
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 (
|
|
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 (
|
|
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
|
-
`
|
|
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 (
|
|
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
|
);
|