mcdev 4.2.0 → 4.3.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 +2 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +1 -2
- package/.github/pr-labeler.yml +3 -0
- package/.github/workflows/close_issues_on_merge.yml +18 -0
- package/.github/workflows/pr-labeler.yml +19 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/docs/dist/documentation.md +703 -283
- package/lib/Deployer.js +21 -15
- package/lib/Retriever.js +23 -19
- package/lib/cli.js +36 -6
- package/lib/index.js +57 -11
- package/lib/metadataTypes/AccountUser.js +17 -23
- package/lib/metadataTypes/Asset.js +28 -21
- package/lib/metadataTypes/AttributeGroup.js +1 -2
- package/lib/metadataTypes/Automation.js +75 -37
- package/lib/metadataTypes/Campaign.js +4 -3
- package/lib/metadataTypes/ContentArea.js +2 -3
- package/lib/metadataTypes/DataExtension.js +56 -47
- package/lib/metadataTypes/DataExtensionField.js +9 -12
- package/lib/metadataTypes/DataExtensionTemplate.js +2 -3
- package/lib/metadataTypes/DataExtract.js +1 -2
- package/lib/metadataTypes/DataExtractType.js +1 -2
- package/lib/metadataTypes/Discovery.js +3 -4
- package/lib/metadataTypes/Email.js +20 -6
- package/lib/metadataTypes/EmailSendDefinition.js +5 -8
- package/lib/metadataTypes/EventDefinition.js +29 -2
- package/lib/metadataTypes/FileTransfer.js +1 -2
- package/lib/metadataTypes/Filter.js +1 -2
- package/lib/metadataTypes/Folder.js +12 -14
- package/lib/metadataTypes/FtpLocation.js +1 -2
- package/lib/metadataTypes/ImportFile.js +1 -2
- package/lib/metadataTypes/Interaction.js +678 -12
- package/lib/metadataTypes/List.js +36 -33
- package/lib/metadataTypes/MetadataType.js +170 -124
- package/lib/metadataTypes/MobileCode.js +1 -2
- package/lib/metadataTypes/MobileKeyword.js +1 -2
- package/lib/metadataTypes/Query.js +15 -6
- package/lib/metadataTypes/Role.js +10 -11
- package/lib/metadataTypes/Script.js +2 -5
- package/lib/metadataTypes/SetDefinition.js +1 -2
- package/lib/metadataTypes/TransactionalMessage.js +25 -32
- package/lib/metadataTypes/TransactionalSMS.js +3 -4
- package/lib/metadataTypes/TriggeredSendDefinition.js +232 -56
- package/lib/metadataTypes/definitions/Asset.definition.js +1 -1
- package/lib/metadataTypes/definitions/Automation.definition.js +1 -1
- package/lib/metadataTypes/definitions/DataExtension.definition.js +10 -1
- package/lib/metadataTypes/definitions/Email.definition.js +1 -1
- package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +1 -1
- package/lib/metadataTypes/definitions/EventDefinition.definition.js +40 -1
- package/lib/metadataTypes/definitions/Folder.definition.js +31 -0
- package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -1
- package/lib/metadataTypes/definitions/Interaction.definition.js +47 -26
- package/lib/metadataTypes/definitions/List.definition.js +1 -1
- package/lib/metadataTypes/definitions/Query.definition.js +1 -1
- package/lib/metadataTypes/definitions/Script.definition.js +1 -1
- package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
- package/lib/metadataTypes/definitions/TransactionalPush.definition.js +2 -1
- package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +2 -1
- package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +10 -2
- package/lib/util/auth.js +10 -2
- package/lib/util/cli.js +4 -1
- package/lib/util/config.js +3 -2
- package/lib/util/file.js +7 -3
- package/lib/util/init.js +65 -3
- package/lib/util/util.js +131 -11
- package/package.json +22 -9
- package/test/dataExtension.test.js +10 -10
- package/test/interaction.test.js +123 -0
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +266 -0
- package/test/mockRoot/deploy/testInstance/testBU/interaction/testNew_interaction.interaction-meta.json +266 -0
- package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +0 -3
- package/test/query.test.js +8 -8
- package/test/resourceFactory.js +12 -7
- package/test/resources/1111111/dataExtension/retrieve-response.xml +26 -0
- package/test/resources/9999999/data/v1/customobjectdata/key/childBU_dataextension_test/rowset/get-response.json +13 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +22 -0
- package/test/resources/9999999/eventDefinition/get-expected.json +34 -0
- package/test/resources/9999999/interaction/build-expected.json +260 -0
- package/test/resources/9999999/interaction/get-expected.json +264 -0
- package/test/resources/9999999/interaction/post-expected.json +264 -0
- package/test/resources/9999999/interaction/put-expected.json +264 -0
- package/test/resources/9999999/interaction/template-expected.json +260 -0
- package/test/resources/9999999/interaction/v1/EventDefinitions/get-response.json +43 -0
- package/test/resources/9999999/interaction/v1/interactions/get-response.json +222 -3
- package/test/resources/9999999/interaction/v1/interactions/post-response.json +280 -0
- package/test/resources/9999999/interaction/v1/interactions/put-response.json +280 -0
- package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
- package/test/resources/9999999/query/post-expected.sql +1 -1
- package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
- package/test/transactionalEmail.test.js +7 -7
- package/test/transactionalPush.test.js +7 -7
- package/test/transactionalSMS.test.js +7 -7
- package/test/utils.js +50 -0
- package/types/mcdev.d.js +1 -0
|
@@ -15,6 +15,15 @@ const cache = require('../util/cache');
|
|
|
15
15
|
const deepEqual = require('deep-equal');
|
|
16
16
|
const pLimit = require('p-limit');
|
|
17
17
|
const Mustache = require('mustache');
|
|
18
|
+
/**
|
|
19
|
+
* ensure that Mustache does not escape any characters
|
|
20
|
+
*
|
|
21
|
+
* @param {string} text -
|
|
22
|
+
* @returns {string} text
|
|
23
|
+
*/
|
|
24
|
+
Mustache.escape = function (text) {
|
|
25
|
+
return text;
|
|
26
|
+
};
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
29
|
* MetadataType class that gets extended by their specific metadata type class.
|
|
@@ -103,11 +112,10 @@ class MetadataType {
|
|
|
103
112
|
* @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
|
|
104
113
|
* @param {string} deployDir directory where deploy metadata are saved
|
|
105
114
|
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
106
|
-
* @param {TYPE.BuObject} buObject properties for auth
|
|
107
115
|
* @returns {Promise.<TYPE.MetadataTypeMap>} Promise of keyField => metadata map
|
|
108
116
|
*/
|
|
109
|
-
static async deploy(metadata, deployDir, retrieveDir
|
|
110
|
-
const upsertResults = await this.upsert(metadata, deployDir
|
|
117
|
+
static async deploy(metadata, deployDir, retrieveDir) {
|
|
118
|
+
const upsertResults = await this.upsert(metadata, deployDir);
|
|
111
119
|
await this.postDeployTasks(upsertResults, metadata);
|
|
112
120
|
const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
|
|
113
121
|
if (
|
|
@@ -116,7 +124,7 @@ class MetadataType {
|
|
|
116
124
|
) {
|
|
117
125
|
// * do not await here as this might take a while and has no impact on the deploy
|
|
118
126
|
// * 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
|
|
119
|
-
this.document(
|
|
127
|
+
this.document(savedMetadata, true);
|
|
120
128
|
}
|
|
121
129
|
return upsertResults;
|
|
122
130
|
}
|
|
@@ -144,7 +152,7 @@ class MetadataType {
|
|
|
144
152
|
/**
|
|
145
153
|
* generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve
|
|
146
154
|
*
|
|
147
|
-
* @param {TYPE.MetadataTypeItem} metadata a single
|
|
155
|
+
* @param {TYPE.MetadataTypeItem} metadata a single item
|
|
148
156
|
*/
|
|
149
157
|
static setFolderPath(metadata) {
|
|
150
158
|
if (!this.definition.folderIdField) {
|
|
@@ -166,18 +174,34 @@ class MetadataType {
|
|
|
166
174
|
);
|
|
167
175
|
}
|
|
168
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* generic script that retrieves the folder ID from cache and updates the given metadata with it before deploy
|
|
179
|
+
*
|
|
180
|
+
* @param {TYPE.MetadataTypeItem} metadata a single item
|
|
181
|
+
*/
|
|
182
|
+
static setFolderId(metadata) {
|
|
183
|
+
if (!this.definition.folderIdField) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
metadata[this.definition.folderIdField] = cache.searchForField(
|
|
187
|
+
'folder',
|
|
188
|
+
metadata.r__folder_Path,
|
|
189
|
+
'Path',
|
|
190
|
+
'ID'
|
|
191
|
+
);
|
|
192
|
+
delete metadata.r__folder_Path;
|
|
193
|
+
}
|
|
169
194
|
|
|
170
195
|
/**
|
|
171
196
|
* Gets metadata from Marketing Cloud
|
|
172
197
|
*
|
|
173
198
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
174
199
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
175
|
-
* @param {
|
|
176
|
-
* @param {string} [subType] optionally limit to a single subtype
|
|
200
|
+
* @param {string[]} [subTypeArr] optionally limit to a single subtype
|
|
177
201
|
* @param {string} [key] customer key of single item to retrieve
|
|
178
202
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
|
|
179
203
|
*/
|
|
180
|
-
static retrieve(retrieveDir, additionalFields,
|
|
204
|
+
static retrieve(retrieveDir, additionalFields, subTypeArr, key) {
|
|
181
205
|
Util.metadataLogger('error', this.definition.type, 'retrieve', `Not Supported`);
|
|
182
206
|
const metadata = {};
|
|
183
207
|
return { metadata: null, type: this.definition.type };
|
|
@@ -185,24 +209,23 @@ class MetadataType {
|
|
|
185
209
|
/**
|
|
186
210
|
* Gets metadata from Marketing Cloud
|
|
187
211
|
*
|
|
188
|
-
* @param {TYPE.BuObject} [buObject] properties for auth
|
|
189
212
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
190
|
-
* @param {string} [
|
|
213
|
+
* @param {string[]} [subTypeArr] optionally limit to a single subtype
|
|
191
214
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
|
|
192
215
|
*/
|
|
193
|
-
static retrieveChangelog(
|
|
194
|
-
return this.retrieveForCache(
|
|
216
|
+
static retrieveChangelog(additionalFields, subTypeArr) {
|
|
217
|
+
return this.retrieveForCache(additionalFields, subTypeArr);
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
/**
|
|
198
221
|
* Gets metadata cache with limited fields and does not store value to disk
|
|
199
222
|
*
|
|
200
|
-
* @param {
|
|
201
|
-
* @param {string} [
|
|
223
|
+
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
224
|
+
* @param {string[]} [subTypeArr] optionally limit to a single subtype
|
|
202
225
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
|
|
203
226
|
*/
|
|
204
|
-
static async retrieveForCache(
|
|
205
|
-
return this.retrieve(null,
|
|
227
|
+
static async retrieveForCache(additionalFields, subTypeArr) {
|
|
228
|
+
return this.retrieve(null, additionalFields, subTypeArr);
|
|
206
229
|
}
|
|
207
230
|
/**
|
|
208
231
|
* Gets metadata cache with limited fields and does not store value to disk
|
|
@@ -303,10 +326,9 @@ class MetadataType {
|
|
|
303
326
|
*
|
|
304
327
|
* @param {TYPE.MetadataTypeItem} metadata a single metadata item
|
|
305
328
|
* @param {string} deployDir folder where files for deployment are stored
|
|
306
|
-
* @param {TYPE.BuObject} buObject buObject properties for auth
|
|
307
329
|
* @returns {Promise.<TYPE.MetadataTypeItem>} Promise of a single metadata item
|
|
308
330
|
*/
|
|
309
|
-
static async preDeployTasks(metadata, deployDir
|
|
331
|
+
static async preDeployTasks(metadata, deployDir) {
|
|
310
332
|
return metadata;
|
|
311
333
|
}
|
|
312
334
|
|
|
@@ -341,6 +363,17 @@ class MetadataType {
|
|
|
341
363
|
);
|
|
342
364
|
return;
|
|
343
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Abstract refresh method that needs to be implemented in child metadata type
|
|
368
|
+
*
|
|
369
|
+
* @returns {void}
|
|
370
|
+
*/
|
|
371
|
+
static refresh() {
|
|
372
|
+
Util.logger.error(
|
|
373
|
+
` ☇ skipping ${this.definition.type}: refresh is not supported yet for ${this.definition.type}`
|
|
374
|
+
);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
344
377
|
|
|
345
378
|
/**
|
|
346
379
|
* test if metadata was actually changed or not to potentially skip it during deployment
|
|
@@ -413,10 +446,9 @@ class MetadataType {
|
|
|
413
446
|
*
|
|
414
447
|
* @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
|
|
415
448
|
* @param {string} deployDir directory where deploy metadata are saved
|
|
416
|
-
* @param {TYPE.BuObject} [buObject] properties for auth
|
|
417
449
|
* @returns {Promise.<TYPE.MetadataTypeMap>} keyField => metadata map
|
|
418
450
|
*/
|
|
419
|
-
static async upsert(metadata, deployDir
|
|
451
|
+
static async upsert(metadata, deployDir) {
|
|
420
452
|
const metadataToUpdate = [];
|
|
421
453
|
const metadataToCreate = [];
|
|
422
454
|
let filteredByPreDeploy = 0;
|
|
@@ -428,8 +460,7 @@ class MetadataType {
|
|
|
428
460
|
try {
|
|
429
461
|
deployableMetadata = await this.preDeployTasks(
|
|
430
462
|
metadata[metadataKey],
|
|
431
|
-
deployDir
|
|
432
|
-
buObject
|
|
463
|
+
deployDir
|
|
433
464
|
);
|
|
434
465
|
} catch (ex) {
|
|
435
466
|
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
@@ -445,57 +476,13 @@ class MetadataType {
|
|
|
445
476
|
if (deployableMetadata) {
|
|
446
477
|
metadata[metadataKey] = deployableMetadata;
|
|
447
478
|
// create normalizedKey off of whats in the json rather than from "metadataKey" because preDeployTasks might have altered something (type asset)
|
|
448
|
-
|
|
449
|
-
|
|
479
|
+
this.createOrUpdate(
|
|
480
|
+
metadata,
|
|
481
|
+
metadataKey,
|
|
482
|
+
hasError,
|
|
483
|
+
metadataToUpdate,
|
|
484
|
+
metadataToCreate
|
|
450
485
|
);
|
|
451
|
-
// Update if it already exists; Create it if not
|
|
452
|
-
if (
|
|
453
|
-
Util.logger.level === 'debug' &&
|
|
454
|
-
metadata[metadataKey][this.definition.idField]
|
|
455
|
-
) {
|
|
456
|
-
// TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
|
|
457
|
-
// only used if resource is excluded from cache and we still want to update it
|
|
458
|
-
// needed e.g. to rewire lost folders
|
|
459
|
-
Util.logger.warn(
|
|
460
|
-
' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
|
|
461
|
-
);
|
|
462
|
-
Util.logger.warn(JSON.stringify(metadata[metadataKey]));
|
|
463
|
-
if (hasError) {
|
|
464
|
-
metadataToUpdate.push(null);
|
|
465
|
-
} else {
|
|
466
|
-
metadataToUpdate.push({
|
|
467
|
-
before: {},
|
|
468
|
-
after: metadata[metadataKey],
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
} else if (cache.getByKey(this.definition.type, normalizedKey)) {
|
|
472
|
-
// normal way of processing update files
|
|
473
|
-
const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
|
|
474
|
-
if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
|
|
475
|
-
hasError = true;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (hasError) {
|
|
479
|
-
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
480
|
-
metadataToUpdate.push(null);
|
|
481
|
-
} else {
|
|
482
|
-
// add ObjectId to allow actual update
|
|
483
|
-
metadata[metadataKey][this.definition.idField] =
|
|
484
|
-
cachedVersion[this.definition.idField];
|
|
485
|
-
|
|
486
|
-
metadataToUpdate.push({
|
|
487
|
-
before: cache.getByKey(this.definition.type, normalizedKey),
|
|
488
|
-
after: metadata[metadataKey],
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
if (hasError) {
|
|
493
|
-
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
494
|
-
metadataToCreate.push(null);
|
|
495
|
-
} else {
|
|
496
|
-
metadataToCreate.push(metadata[metadataKey]);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
486
|
} else {
|
|
500
487
|
filteredByPreDeploy++;
|
|
501
488
|
}
|
|
@@ -554,12 +541,72 @@ class MetadataType {
|
|
|
554
541
|
}
|
|
555
542
|
}
|
|
556
543
|
|
|
544
|
+
/**
|
|
545
|
+
*
|
|
546
|
+
* @param {TYPE.MetadataTypeItem} metadata single metadata itme
|
|
547
|
+
* @param {string} metadataKey key of item we are looking at
|
|
548
|
+
* @param {boolean} hasError error flag from previous code
|
|
549
|
+
* @param {TYPE.MetadataTypeItemDiff[]} metadataToUpdate list of items to update
|
|
550
|
+
* @param {TYPE.MetadataTypeItem[]} metadataToCreate list of items to create
|
|
551
|
+
* @returns {void}
|
|
552
|
+
*/
|
|
553
|
+
static createOrUpdate(metadata, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
|
|
554
|
+
const normalizedKey = File.reverseFilterIllegalFilenames(
|
|
555
|
+
metadata[metadataKey][this.definition.keyField]
|
|
556
|
+
);
|
|
557
|
+
// Update if it already exists; Create it if not
|
|
558
|
+
if (Util.logger.level === 'debug' && metadata[metadataKey][this.definition.idField]) {
|
|
559
|
+
// TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
|
|
560
|
+
// only used if resource is excluded from cache and we still want to update it
|
|
561
|
+
// needed e.g. to rewire lost folders
|
|
562
|
+
Util.logger.warn(
|
|
563
|
+
' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
|
|
564
|
+
);
|
|
565
|
+
Util.logger.warn(JSON.stringify(metadata[metadataKey]));
|
|
566
|
+
if (hasError) {
|
|
567
|
+
metadataToUpdate.push(null);
|
|
568
|
+
} else {
|
|
569
|
+
metadataToUpdate.push({
|
|
570
|
+
before: {},
|
|
571
|
+
after: metadata[metadataKey],
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
} else if (cache.getByKey(this.definition.type, normalizedKey)) {
|
|
575
|
+
// normal way of processing update files
|
|
576
|
+
const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
|
|
577
|
+
if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
|
|
578
|
+
hasError = true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (hasError) {
|
|
582
|
+
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
583
|
+
metadataToUpdate.push(null);
|
|
584
|
+
} else {
|
|
585
|
+
// add ObjectId to allow actual update
|
|
586
|
+
metadata[metadataKey][this.definition.idField] =
|
|
587
|
+
cachedVersion[this.definition.idField];
|
|
588
|
+
|
|
589
|
+
metadataToUpdate.push({
|
|
590
|
+
before: cache.getByKey(this.definition.type, normalizedKey),
|
|
591
|
+
after: metadata[metadataKey],
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
if (hasError) {
|
|
596
|
+
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
597
|
+
metadataToCreate.push(null);
|
|
598
|
+
} else {
|
|
599
|
+
metadataToCreate.push(metadata[metadataKey]);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
557
604
|
/**
|
|
558
605
|
* Creates a single metadata entry via REST
|
|
559
606
|
*
|
|
560
607
|
* @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
|
|
561
608
|
* @param {string} uri rest endpoint for POST
|
|
562
|
-
* @returns {Promise} Promise
|
|
609
|
+
* @returns {Promise.<object> | null} Promise of API response or null in case of an error
|
|
563
610
|
*/
|
|
564
611
|
static async createREST(metadataEntry, uri) {
|
|
565
612
|
this.removeNotCreateableFields(metadataEntry);
|
|
@@ -593,7 +640,7 @@ class MetadataType {
|
|
|
593
640
|
* @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
|
|
594
641
|
* @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
|
|
595
642
|
* @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
|
|
596
|
-
* @returns {Promise} Promise
|
|
643
|
+
* @returns {Promise.<object> | null} Promise of API response or null in case of an error
|
|
597
644
|
*/
|
|
598
645
|
static async createSOAP(metadataEntry, overrideType, handleOutside) {
|
|
599
646
|
try {
|
|
@@ -615,7 +662,7 @@ class MetadataType {
|
|
|
615
662
|
return response;
|
|
616
663
|
} catch (ex) {
|
|
617
664
|
this._handleSOAPErrors(ex, 'creating', metadataEntry, handleOutside);
|
|
618
|
-
return
|
|
665
|
+
return null;
|
|
619
666
|
}
|
|
620
667
|
}
|
|
621
668
|
|
|
@@ -624,12 +671,15 @@ class MetadataType {
|
|
|
624
671
|
*
|
|
625
672
|
* @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
|
|
626
673
|
* @param {string} uri rest endpoint for PATCH
|
|
627
|
-
* @
|
|
674
|
+
* @param {boolean} [usePut] some update requests require PUT instead of PATCH
|
|
675
|
+
* @returns {Promise.<object> | null} Promise of API response or null in case of an error
|
|
628
676
|
*/
|
|
629
|
-
static async updateREST(metadataEntry, uri) {
|
|
677
|
+
static async updateREST(metadataEntry, uri, usePut) {
|
|
630
678
|
this.removeNotUpdateableFields(metadataEntry);
|
|
631
679
|
try {
|
|
632
|
-
const response =
|
|
680
|
+
const response = usePut
|
|
681
|
+
? await this.client.rest.put(uri, metadataEntry)
|
|
682
|
+
: await this.client.rest.patch(uri, metadataEntry);
|
|
633
683
|
this.checkForErrors(response);
|
|
634
684
|
// some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
|
|
635
685
|
Util.logger.info(
|
|
@@ -660,7 +710,7 @@ class MetadataType {
|
|
|
660
710
|
* @param {TYPE.MetadataTypeItem} metadataEntry single metadata entry
|
|
661
711
|
* @param {string} [overrideType] can be used if the API type differs from the otherwise used type identifier
|
|
662
712
|
* @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
|
|
663
|
-
* @returns {Promise} Promise
|
|
713
|
+
* @returns {Promise.<object> | null} Promise of API response or null in case of an error
|
|
664
714
|
*/
|
|
665
715
|
static async updateSOAP(metadataEntry, overrideType, handleOutside) {
|
|
666
716
|
try {
|
|
@@ -681,7 +731,7 @@ class MetadataType {
|
|
|
681
731
|
return response;
|
|
682
732
|
} catch (ex) {
|
|
683
733
|
this._handleSOAPErrors(ex, 'updating', metadataEntry, handleOutside);
|
|
684
|
-
return
|
|
734
|
+
return null;
|
|
685
735
|
}
|
|
686
736
|
}
|
|
687
737
|
/**
|
|
@@ -695,23 +745,31 @@ class MetadataType {
|
|
|
695
745
|
if (handleOutside) {
|
|
696
746
|
throw ex;
|
|
697
747
|
} else {
|
|
698
|
-
const errorMsg = ex
|
|
699
|
-
? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
|
|
700
|
-
: ex.message;
|
|
748
|
+
const errorMsg = this.getSOAPErrorMsg(ex);
|
|
701
749
|
const name = metadataEntry ? ` '${metadataEntry[this.definition.keyField]}'` : '';
|
|
702
750
|
Util.logger.error(` ☇ error ${msg} ${this.definition.type}${name}: ${errorMsg}`);
|
|
703
751
|
}
|
|
704
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* helper for {@link _handleSOAPErrors}
|
|
755
|
+
*
|
|
756
|
+
* @param {Error} ex error that occured
|
|
757
|
+
* @returns {string} error message
|
|
758
|
+
*/
|
|
759
|
+
static getSOAPErrorMsg(ex) {
|
|
760
|
+
return ex?.json?.Results?.length
|
|
761
|
+
? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
|
|
762
|
+
: ex.message;
|
|
763
|
+
}
|
|
705
764
|
/**
|
|
706
765
|
* Retrieves SOAP via generic fuel-soap wrapper based metadata of metadata type into local filesystem. executes callback with retrieved metadata
|
|
707
766
|
*
|
|
708
767
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
709
|
-
* @param {TYPE.BuObject} buObject properties for auth
|
|
710
768
|
* @param {TYPE.SoapRequestParams} [requestParams] required for the specific request (filter for example)
|
|
711
769
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
712
770
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of item map
|
|
713
771
|
*/
|
|
714
|
-
static async retrieveSOAP(retrieveDir,
|
|
772
|
+
static async retrieveSOAP(retrieveDir, requestParams, additionalFields) {
|
|
715
773
|
requestParams = requestParams || {};
|
|
716
774
|
const fields = this.getFieldNamesToRetrieve(additionalFields);
|
|
717
775
|
let response;
|
|
@@ -733,10 +791,10 @@ class MetadataType {
|
|
|
733
791
|
`Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
|
|
734
792
|
);
|
|
735
793
|
if (
|
|
736
|
-
buObject &&
|
|
794
|
+
this.buObject &&
|
|
737
795
|
this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)
|
|
738
796
|
) {
|
|
739
|
-
await this.document(
|
|
797
|
+
await this.document(savedMetadata);
|
|
740
798
|
}
|
|
741
799
|
}
|
|
742
800
|
return { metadata: metadata, type: this.definition.type };
|
|
@@ -755,7 +813,7 @@ class MetadataType {
|
|
|
755
813
|
static async retrieveREST(retrieveDir, uri, overrideType, templateVariables, singleRetrieve) {
|
|
756
814
|
const response =
|
|
757
815
|
this.definition.restPagination && !singleRetrieve
|
|
758
|
-
? await this.client.rest.getBulk(uri)
|
|
816
|
+
? await this.client.rest.getBulk(uri, this.definition.restPageSize || 500)
|
|
759
817
|
: await this.client.rest.get(uri);
|
|
760
818
|
const results = this.parseResponseBody(response, singleRetrieve);
|
|
761
819
|
// get extended metadata if applicable
|
|
@@ -770,11 +828,6 @@ class MetadataType {
|
|
|
770
828
|
}
|
|
771
829
|
|
|
772
830
|
if (retrieveDir) {
|
|
773
|
-
// defined colors for optionally printing the keys we filtered by
|
|
774
|
-
const color = {
|
|
775
|
-
reset: '\x1B[0m',
|
|
776
|
-
dim: '\x1B[2m',
|
|
777
|
-
};
|
|
778
831
|
const savedMetadata = await this.saveResults(
|
|
779
832
|
results,
|
|
780
833
|
retrieveDir,
|
|
@@ -784,10 +837,7 @@ class MetadataType {
|
|
|
784
837
|
Util.logger.info(
|
|
785
838
|
`Downloaded: ${overrideType || this.definition.type} (${
|
|
786
839
|
Object.keys(savedMetadata).length
|
|
787
|
-
})` +
|
|
788
|
-
(singleRetrieve === null
|
|
789
|
-
? ''
|
|
790
|
-
: ` ${color.dim}(Key: ${singleRetrieve})${color.reset}`)
|
|
840
|
+
})` + Util.getKeysString(singleRetrieve)
|
|
791
841
|
);
|
|
792
842
|
}
|
|
793
843
|
|
|
@@ -822,6 +872,10 @@ class MetadataType {
|
|
|
822
872
|
}
|
|
823
873
|
} else if (singleRetrieve) {
|
|
824
874
|
// some types will return a single item intead of an array if the key is supported by their api
|
|
875
|
+
// ! currently, the id: prefix is only supported by journey (interaction)
|
|
876
|
+
if (singleRetrieve.startsWith('id:')) {
|
|
877
|
+
singleRetrieve = body[keyField];
|
|
878
|
+
}
|
|
825
879
|
metadataStructure[singleRetrieve] = body;
|
|
826
880
|
return metadataStructure;
|
|
827
881
|
}
|
|
@@ -865,8 +919,8 @@ class MetadataType {
|
|
|
865
919
|
// revert back placeholder to dots
|
|
866
920
|
const originHelper = origin ? origin + '.' + fieldPath : fieldPath;
|
|
867
921
|
|
|
868
|
-
if (this.definition.fields[originHelper]?.skipValidation) {
|
|
869
|
-
// skip if current field should not be validated
|
|
922
|
+
if (this.definition.fields[originHelper]?.skipValidation || originHelper === '@_xsi:type') {
|
|
923
|
+
// skip if current field should not be validated OR if field is internal helper field xsi:type
|
|
870
924
|
return;
|
|
871
925
|
} else if (
|
|
872
926
|
Array.isArray(fieldContent) &&
|
|
@@ -1062,7 +1116,7 @@ class MetadataType {
|
|
|
1062
1116
|
metadataEntry.r__folder_Path
|
|
1063
1117
|
);
|
|
1064
1118
|
if (excludeByDefinition) {
|
|
1065
|
-
Util.logger.debug(errorMsg + ' (
|
|
1119
|
+
Util.logger.debug(errorMsg + ' (Accenture SFMC DevTools default)');
|
|
1066
1120
|
return true;
|
|
1067
1121
|
}
|
|
1068
1122
|
|
|
@@ -1071,7 +1125,7 @@ class MetadataType {
|
|
|
1071
1125
|
metadataEntry.r__folder_Path
|
|
1072
1126
|
);
|
|
1073
1127
|
if (excludeByConfig) {
|
|
1074
|
-
Util.logger.debug(errorMsg + ' (
|
|
1128
|
+
Util.logger.debug(errorMsg + ' (project config)');
|
|
1075
1129
|
return true;
|
|
1076
1130
|
}
|
|
1077
1131
|
}
|
|
@@ -1298,7 +1352,7 @@ class MetadataType {
|
|
|
1298
1352
|
*/
|
|
1299
1353
|
static applyTemplateValues(code, templateVariables) {
|
|
1300
1354
|
// replace template variables with their values
|
|
1301
|
-
return Mustache.render(code, templateVariables);
|
|
1355
|
+
return Mustache.render(code, templateVariables, {}, ['{{{', '}}}']);
|
|
1302
1356
|
}
|
|
1303
1357
|
/**
|
|
1304
1358
|
* helper for {@link buildTemplateForNested}
|
|
@@ -1431,8 +1485,8 @@ class MetadataType {
|
|
|
1431
1485
|
let metadata;
|
|
1432
1486
|
try {
|
|
1433
1487
|
// update all initial variables & create metadata object
|
|
1434
|
-
metadata = JSON.parse(Mustache.render(metadataStr, variables));
|
|
1435
|
-
typeDirArr = typeDirArr.map((el) => Mustache.render(el, variables));
|
|
1488
|
+
metadata = JSON.parse(Mustache.render(metadataStr, variables, {}, ['{{{', '}}}']));
|
|
1489
|
+
typeDirArr = typeDirArr.map((el) => Mustache.render(el, variables, {}, ['{{{', '}}}']));
|
|
1436
1490
|
} catch {
|
|
1437
1491
|
throw new Error(
|
|
1438
1492
|
`${this.definition.type}:: Error applying template variables on ${
|
|
@@ -1478,7 +1532,7 @@ class MetadataType {
|
|
|
1478
1532
|
* Standardizes a check for multiple messages
|
|
1479
1533
|
*
|
|
1480
1534
|
* @param {object} ex response payload from REST API
|
|
1481
|
-
* @returns {string[]} formatted Error Message
|
|
1535
|
+
* @returns {string[] | void} formatted Error Message
|
|
1482
1536
|
*/
|
|
1483
1537
|
static checkForErrors(ex) {
|
|
1484
1538
|
if (ex?.response?.status >= 400 && ex?.response?.status < 600) {
|
|
@@ -1515,12 +1569,11 @@ class MetadataType {
|
|
|
1515
1569
|
/**
|
|
1516
1570
|
* Gets metadata cache with limited fields and does not store value to disk
|
|
1517
1571
|
*
|
|
1518
|
-
* @param {TYPE.BuObject} [buObject] properties for auth
|
|
1519
1572
|
* @param {TYPE.MetadataTypeMap} [metadata] a list of type definitions
|
|
1520
1573
|
* @param {boolean} [isDeploy] used to skip non-supported message during deploy
|
|
1521
1574
|
* @returns {void}
|
|
1522
1575
|
*/
|
|
1523
|
-
static document(
|
|
1576
|
+
static document(metadata, isDeploy) {
|
|
1524
1577
|
if (!isDeploy) {
|
|
1525
1578
|
Util.logger.error(`Documenting type ${this.definition.type} is not supported.`);
|
|
1526
1579
|
}
|
|
@@ -1529,27 +1582,25 @@ class MetadataType {
|
|
|
1529
1582
|
/**
|
|
1530
1583
|
* Delete a metadata item from the specified business unit
|
|
1531
1584
|
*
|
|
1532
|
-
* @param {TYPE.BuObject} buObject references credentials
|
|
1533
1585
|
* @param {string} customerKey Identifier of data extension
|
|
1534
1586
|
* @returns {boolean} deletion success status
|
|
1535
1587
|
*/
|
|
1536
|
-
static deleteByKey(
|
|
1588
|
+
static deleteByKey(customerKey) {
|
|
1537
1589
|
Util.logger.error(`Deletion is not yet supported for ${this.definition.typeName}!`);
|
|
1538
1590
|
return false;
|
|
1539
1591
|
}
|
|
1540
1592
|
/**
|
|
1541
1593
|
* clean up after deleting a metadata item
|
|
1542
1594
|
*
|
|
1543
|
-
* @param {TYPE.BuObject} buObject references credentials
|
|
1544
1595
|
* @param {string} customerKey Identifier of metadata item
|
|
1545
1596
|
* @returns {void}
|
|
1546
1597
|
*/
|
|
1547
|
-
static async postDeleteTasks(
|
|
1598
|
+
static async postDeleteTasks(customerKey) {
|
|
1548
1599
|
// delete local copy: retrieve/cred/bu/type/...json
|
|
1549
1600
|
const jsonFile = File.normalizePath([
|
|
1550
1601
|
this.properties.directories.retrieve,
|
|
1551
|
-
buObject.credential,
|
|
1552
|
-
buObject.businessUnit,
|
|
1602
|
+
this.buObject.credential,
|
|
1603
|
+
this.buObject.businessUnit,
|
|
1553
1604
|
this.definition.type,
|
|
1554
1605
|
`${customerKey}.${this.definition.type}-meta.json`,
|
|
1555
1606
|
]);
|
|
@@ -1559,12 +1610,11 @@ class MetadataType {
|
|
|
1559
1610
|
/**
|
|
1560
1611
|
* Delete a data extension from the specified business unit
|
|
1561
1612
|
*
|
|
1562
|
-
* @param {TYPE.BuObject} buObject references credentials
|
|
1563
1613
|
* @param {string} customerKey Identifier of metadata
|
|
1564
1614
|
* @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
|
|
1565
1615
|
* @returns {boolean} deletion success flag
|
|
1566
1616
|
*/
|
|
1567
|
-
static async deleteByKeySOAP(
|
|
1617
|
+
static async deleteByKeySOAP(customerKey, handleOutside) {
|
|
1568
1618
|
const keyObj = {};
|
|
1569
1619
|
keyObj[this.definition.keyField] = customerKey;
|
|
1570
1620
|
try {
|
|
@@ -1576,7 +1626,7 @@ class MetadataType {
|
|
|
1576
1626
|
if (!handleOutside) {
|
|
1577
1627
|
Util.logger.info(`- deleted ${this.definition.type}: ${customerKey}`);
|
|
1578
1628
|
}
|
|
1579
|
-
this.postDeleteTasks(
|
|
1629
|
+
this.postDeleteTasks(customerKey);
|
|
1580
1630
|
|
|
1581
1631
|
return true;
|
|
1582
1632
|
} catch (ex) {
|
|
@@ -1597,31 +1647,27 @@ class MetadataType {
|
|
|
1597
1647
|
/**
|
|
1598
1648
|
* Delete a data extension from the specified business unit
|
|
1599
1649
|
*
|
|
1600
|
-
* @param {TYPE.BuObject} buObject references credentials
|
|
1601
1650
|
* @param {string} url endpoint
|
|
1602
1651
|
* @param {string} key Identifier of metadata
|
|
1603
1652
|
* @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
|
|
1604
1653
|
* @returns {boolean} deletion success flag
|
|
1605
1654
|
*/
|
|
1606
|
-
static async deleteByKeyREST(
|
|
1655
|
+
static async deleteByKeyREST(url, key, handleOutside) {
|
|
1607
1656
|
const keyObj = {};
|
|
1608
1657
|
keyObj[this.definition.keyField] = key;
|
|
1609
1658
|
try {
|
|
1610
|
-
this.client.rest.delete(url);
|
|
1659
|
+
await this.client.rest.delete(url);
|
|
1611
1660
|
if (!handleOutside) {
|
|
1612
1661
|
Util.logger.info(`- deleted ${this.definition.type}: ${key}`);
|
|
1613
1662
|
}
|
|
1614
|
-
this.postDeleteTasks(
|
|
1663
|
+
this.postDeleteTasks(key);
|
|
1615
1664
|
|
|
1616
1665
|
return true;
|
|
1617
1666
|
} catch (ex) {
|
|
1618
1667
|
if (handleOutside) {
|
|
1619
1668
|
throw ex;
|
|
1620
1669
|
} else {
|
|
1621
|
-
|
|
1622
|
-
? `${ex.results[0].StatusMessage} (Code ${ex.results[0].ErrorCode})`
|
|
1623
|
-
: ex.message;
|
|
1624
|
-
Util.logger.error(`- error deleting ${this.definition.type} '${key}': ${errorMsg}`);
|
|
1670
|
+
Util.logger.errorStack(ex, ` - Deleting ${this.definition.type} '${key}' failed`);
|
|
1625
1671
|
}
|
|
1626
1672
|
|
|
1627
1673
|
return false;
|
|
@@ -16,11 +16,10 @@ class MobileCode extends MetadataType {
|
|
|
16
16
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
17
17
|
* @param {void} [_] unused parameter
|
|
18
18
|
* @param {void} [__] unused parameter
|
|
19
|
-
* @param {void} [___] unused parameter
|
|
20
19
|
* @param {string} [key] customer key of single item to retrieve
|
|
21
20
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
|
|
22
21
|
*/
|
|
23
|
-
static retrieve(retrieveDir, _, __,
|
|
22
|
+
static retrieve(retrieveDir, _, __, key) {
|
|
24
23
|
return super.retrieveREST(
|
|
25
24
|
retrieveDir,
|
|
26
25
|
'/legacy/v1/beta/mobile/code/' + (key ? `?$where=keyword%20eq%20%27${key}%27%20` : ''),
|
|
@@ -19,11 +19,10 @@ class MobileKeyword extends MetadataType {
|
|
|
19
19
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
20
20
|
* @param {void} [_] unused parameter
|
|
21
21
|
* @param {void} [__] unused parameter
|
|
22
|
-
* @param {void} [___] unused parameter
|
|
23
22
|
* @param {string} [key] customer key of single item to retrieve
|
|
24
23
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
|
|
25
24
|
*/
|
|
26
|
-
static retrieve(retrieveDir, _, __,
|
|
25
|
+
static retrieve(retrieveDir, _, __, key) {
|
|
27
26
|
return super.retrieveREST(
|
|
28
27
|
retrieveDir,
|
|
29
28
|
'/legacy/v1/beta/mobile/keyword/?view=simple' +
|