mcdev 5.2.0 → 6.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 (191) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.fork/custom-commands.json +12 -0
  3. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  4. package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +19 -0
  5. package/.github/workflows/code-test.yml +1 -1
  6. package/.github/workflows/coverage-base-update.yml +1 -1
  7. package/.github/workflows/coverage-develop-branch.yml +2 -2
  8. package/.github/workflows/coverage-main-branch.yml +2 -2
  9. package/.github/workflows/coverage.yml +2 -2
  10. package/.github/workflows/npm-publish.yml +2 -2
  11. package/.prettierrc +7 -0
  12. package/docs/dist/documentation.md +388 -482
  13. package/jsconfig.json +5 -1
  14. package/lib/Builder.js +8 -8
  15. package/lib/Deployer.js +10 -10
  16. package/lib/MetadataTypeDefinitions.js +73 -38
  17. package/lib/MetadataTypeInfo.js +72 -37
  18. package/lib/Retriever.js +8 -8
  19. package/lib/cli.js +12 -7
  20. package/lib/index.js +28 -18
  21. package/lib/metadataTypes/Asset.js +12 -10
  22. package/lib/metadataTypes/AttributeGroup.js +7 -6
  23. package/lib/metadataTypes/AttributeSet.js +126 -18
  24. package/lib/metadataTypes/Automation.js +107 -78
  25. package/lib/metadataTypes/Campaign.js +7 -6
  26. package/lib/metadataTypes/ContentArea.js +7 -6
  27. package/lib/metadataTypes/DataExtension.js +475 -78
  28. package/lib/metadataTypes/DataExtensionField.js +36 -18
  29. package/lib/metadataTypes/DataExtensionTemplate.js +5 -4
  30. package/lib/metadataTypes/DataExtract.js +8 -7
  31. package/lib/metadataTypes/DataExtractType.js +5 -4
  32. package/lib/metadataTypes/Discovery.js +6 -5
  33. package/lib/metadataTypes/Email.js +6 -5
  34. package/lib/metadataTypes/EmailSend.js +7 -6
  35. package/lib/metadataTypes/Event.js +8 -7
  36. package/lib/metadataTypes/FileLocation.js +5 -4
  37. package/lib/metadataTypes/FileTransfer.js +8 -7
  38. package/lib/metadataTypes/Filter.js +5 -4
  39. package/lib/metadataTypes/Folder.js +9 -8
  40. package/lib/metadataTypes/ImportFile.js +8 -7
  41. package/lib/metadataTypes/Journey.js +17 -9
  42. package/lib/metadataTypes/List.js +9 -8
  43. package/lib/metadataTypes/MetadataType.js +100 -34
  44. package/lib/metadataTypes/MobileCode.js +5 -4
  45. package/lib/metadataTypes/MobileKeyword.js +9 -8
  46. package/lib/metadataTypes/MobileMessage.js +8 -7
  47. package/lib/metadataTypes/Query.js +9 -8
  48. package/lib/metadataTypes/Role.js +8 -7
  49. package/lib/metadataTypes/Script.js +7 -6
  50. package/lib/metadataTypes/SendClassification.js +5 -4
  51. package/lib/metadataTypes/TransactionalEmail.js +101 -23
  52. package/lib/metadataTypes/TransactionalMessage.js +9 -7
  53. package/lib/metadataTypes/TransactionalPush.js +7 -6
  54. package/lib/metadataTypes/TransactionalSMS.js +9 -8
  55. package/lib/metadataTypes/TriggeredSend.js +15 -12
  56. package/lib/metadataTypes/User.js +8 -7
  57. package/lib/metadataTypes/Verification.js +230 -0
  58. package/lib/metadataTypes/definitions/Asset.definition.js +1 -1
  59. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +3 -3
  60. package/lib/metadataTypes/definitions/AttributeSet.definition.js +75 -22
  61. package/lib/metadataTypes/definitions/Automation.definition.js +2 -1
  62. package/lib/metadataTypes/definitions/Campaign.definition.js +1 -1
  63. package/lib/metadataTypes/definitions/ContentArea.definition.js +1 -1
  64. package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -1
  65. package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -1
  66. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +1 -1
  67. package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -1
  68. package/lib/metadataTypes/definitions/DataExtractType.definition.js +1 -1
  69. package/lib/metadataTypes/definitions/Discovery.definition.js +1 -1
  70. package/lib/metadataTypes/definitions/Email.definition.js +1 -1
  71. package/lib/metadataTypes/definitions/EmailSend.definition.js +1 -1
  72. package/lib/metadataTypes/definitions/Event.definition.js +1 -1
  73. package/lib/metadataTypes/definitions/FileLocation.definition.js +1 -1
  74. package/lib/metadataTypes/definitions/FileTransfer.definition.js +1 -1
  75. package/lib/metadataTypes/definitions/Filter.definition.js +1 -1
  76. package/lib/metadataTypes/definitions/Folder.definition.js +1 -1
  77. package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -1
  78. package/lib/metadataTypes/definitions/Journey.definition.js +1 -1
  79. package/lib/metadataTypes/definitions/List.definition.js +1 -1
  80. package/lib/metadataTypes/definitions/MobileCode.definition.js +1 -1
  81. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +1 -1
  82. package/lib/metadataTypes/definitions/MobileMessage.definition.js +1 -1
  83. package/lib/metadataTypes/definitions/Query.definition.js +1 -1
  84. package/lib/metadataTypes/definitions/Role.definition.js +1 -1
  85. package/lib/metadataTypes/definitions/Script.definition.js +1 -1
  86. package/lib/metadataTypes/definitions/SendClassification.definition.js +1 -1
  87. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +20 -2
  88. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +1 -1
  89. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +1 -1
  90. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +1 -1
  91. package/lib/metadataTypes/definitions/User.definition.js +1 -1
  92. package/lib/metadataTypes/definitions/Verification.definition.js +88 -0
  93. package/lib/retrieveChangelog.js +4 -3
  94. package/lib/util/auth.js +11 -8
  95. package/lib/util/businessUnit.js +5 -5
  96. package/lib/util/cache.js +3 -3
  97. package/lib/util/cli.js +15 -13
  98. package/lib/util/config.js +10 -7
  99. package/lib/util/devops.js +12 -11
  100. package/lib/util/file.js +15 -14
  101. package/lib/util/init.config.js +11 -9
  102. package/lib/util/init.git.js +8 -7
  103. package/lib/util/init.js +12 -12
  104. package/lib/util/init.npm.js +7 -5
  105. package/lib/util/util.js +14 -12
  106. package/package.json +32 -27
  107. package/test/general.test.js +4 -6
  108. package/test/mockRoot/.mcdevrc.json +1 -1
  109. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testExisting_dataExtensionShared.dataExtension-meta.json +59 -0
  110. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testNew_dataExtensionShared.dataExtension-meta.json +23 -0
  111. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +4 -0
  112. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testExisting_dataExtension.dataExtension-meta.json +1 -0
  113. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +3 -4
  114. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -6
  115. package/test/mockRoot/deploy/testInstance/testBU/verification/testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  116. package/test/mockRoot/deploy/testInstance/testBU/verification/testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  117. package/test/resourceFactory.js +52 -26
  118. package/test/resources/1111111/data/v1/customobjectdata/key/testExisting_dataExtensionShared/rowset/get-response.json +13 -0
  119. package/test/resources/1111111/dataExtension/create-expected.json +23 -0
  120. package/test/resources/1111111/dataExtension/create-response.xml +59 -0
  121. package/test/resources/1111111/dataExtension/retrieve-expected.json +55 -0
  122. package/test/resources/1111111/dataExtension/retrieve-expected.md +18 -0
  123. package/test/resources/1111111/dataExtension/retrieve-response.xml +27 -1
  124. package/test/resources/1111111/dataExtension/update-expected.json +55 -0
  125. package/test/resources/1111111/dataExtension/update-response.xml +57 -0
  126. package/test/resources/1111111/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtensionShared].[TriggerUpdate_randomNumber_]-response.xml +45 -0
  127. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  128. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionSharedORDataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  129. package/test/resources/1111111/dataExtensionField/retrieve-response.xml +98 -0
  130. package/test/resources/1111111/dataExtensionTemplate/retrieve-response.xml +303 -0
  131. package/test/resources/1111111/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +387 -0
  132. package/test/resources/1111111/dataFolder/retrieve-response.xml +353 -9
  133. package/test/resources/9999999/attributeSet/retrieve-expected.json +89 -694
  134. package/test/resources/9999999/automation/build-expected.json +4 -0
  135. package/test/resources/9999999/automation/create-expected.json +4 -0
  136. package/test/resources/9999999/automation/create-testNew_automation-expected.md +1 -0
  137. package/test/resources/9999999/automation/retrieve-expected.json +4 -0
  138. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +1 -0
  139. package/test/resources/9999999/automation/template-expected.json +4 -0
  140. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +7 -0
  141. package/test/resources/9999999/automation/v1/automations/post-response.json +7 -0
  142. package/test/resources/9999999/automation/v1/dataverifications/post-response.json +12 -0
  143. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/delete-response.json +0 -0
  144. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/get-response.json +12 -0
  145. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/patch-response.json +12 -0
  146. package/test/resources/9999999/dataExtension/build-expected.json +16 -0
  147. package/test/resources/9999999/dataExtension/delete-response.xml +42 -0
  148. package/test/resources/9999999/dataExtension/retrieve-expected.json +16 -0
  149. package/test/resources/9999999/dataExtension/retrieve-expected.md +3 -1
  150. package/test/resources/9999999/dataExtension/template-expected.json +16 -0
  151. package/test/resources/9999999/dataExtension/update-expected.json +17 -1
  152. package/test/resources/9999999/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtension].[LastName]-response.xml +44 -0
  153. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +36 -1
  154. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +36 -1
  155. package/test/resources/9999999/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +117 -0
  156. package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +43 -0
  157. package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +387 -0
  158. package/test/resources/9999999/interaction/v1/interactions/233d4413-922c-4568-85a5-e5cc77efc3be/delete-response.json +1 -0
  159. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +1 -1
  160. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  161. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/delete-response.json +6 -0
  162. package/test/resources/9999999/transactionalEmail/build-expected.json +3 -7
  163. package/test/resources/9999999/transactionalEmail/get-expected.json +3 -7
  164. package/test/resources/9999999/transactionalEmail/patch-expected.json +3 -7
  165. package/test/resources/9999999/transactionalEmail/post-expected.json +3 -7
  166. package/test/resources/9999999/transactionalEmail/template-expected.json +3 -7
  167. package/test/resources/9999999/verification/build-expected.json +11 -0
  168. package/test/resources/9999999/verification/get-expected.json +11 -0
  169. package/test/resources/9999999/verification/patch-expected.json +11 -0
  170. package/test/resources/9999999/verification/post-expected.json +11 -0
  171. package/test/resources/9999999/verification/template-expected.json +11 -0
  172. package/test/type.attributeGroup.test.js +9 -12
  173. package/test/type.attributeSet.test.js +10 -13
  174. package/test/type.automation.test.js +34 -32
  175. package/test/type.dataExtension.test.js +210 -54
  176. package/test/type.dataExtract.test.js +15 -9
  177. package/test/type.fileTransfer.test.js +15 -9
  178. package/test/type.importFile.test.js +15 -9
  179. package/test/type.journey.test.js +43 -17
  180. package/test/type.mobileKeyword.test.js +11 -11
  181. package/test/type.mobileMessage.test.js +11 -11
  182. package/test/type.query.test.js +13 -14
  183. package/test/type.script.test.js +11 -9
  184. package/test/type.transactionalEmail.test.js +17 -17
  185. package/test/type.transactionalPush.test.js +7 -10
  186. package/test/type.transactionalSMS.test.js +7 -11
  187. package/test/type.triggeredSend.test.js +11 -10
  188. package/test/type.user.test.js +6 -8
  189. package/test/type.verification.test.js +172 -0
  190. package/test/utils.js +68 -48
  191. package/types/mcdev.d.js +16 -2
@@ -1,15 +1,17 @@
1
1
  'use strict';
2
2
 
3
- const jsonToTable = require('json-to-table');
4
- const TYPE = require('../../types/mcdev.d');
5
- const MetadataType = require('./MetadataType');
6
- const DataExtensionField = require('./DataExtensionField');
7
- const Folder = require('./Folder');
8
- const Util = require('../util/util');
9
- const File = require('../util/file');
10
- const auth = require('../util/auth');
11
- const cache = require('../util/cache');
12
- const pLimit = require('p-limit');
3
+ import jsonToTable from 'json-to-table';
4
+ import TYPE from '../../types/mcdev.d.js';
5
+ import MetadataType from './MetadataType.js';
6
+ import AttributeSet from './AttributeSet.js';
7
+ import DataExtensionField from './DataExtensionField.js';
8
+ import Folder from './Folder.js';
9
+ import { Util } from '../util/util.js';
10
+ import File from '../util/file.js';
11
+ import auth from '../util/auth.js';
12
+ import cache from '../util/cache.js';
13
+ import pLimit from 'p-limit';
14
+ import inquirer from 'inquirer';
13
15
 
14
16
  /**
15
17
  * DataExtension MetadataType
@@ -45,7 +47,7 @@ class DataExtension extends MetadataType {
45
47
  };
46
48
  }
47
49
  Util.logger.info(` - Caching dependent Metadata: dataExtensionField`);
48
- await this._attachFields(metadataMap, fieldOptions);
50
+ await this.#attachFields(metadataMap, fieldOptions);
49
51
 
50
52
  /** @type {object[]} */
51
53
  const metadataToCreate = [];
@@ -283,7 +285,7 @@ class DataExtension extends MetadataType {
283
285
  * @param {{created: number, updated: number}} createdUpdated counter representing successful creates/updates
284
286
  * @returns {void}
285
287
  */
286
- static postDeployTasks(upsertedMetadata, originalMetadata, createdUpdated) {
288
+ static async postDeployTasks(upsertedMetadata, originalMetadata, createdUpdated) {
287
289
  for (const key in upsertedMetadata) {
288
290
  const item = upsertedMetadata[key];
289
291
 
@@ -354,6 +356,376 @@ class DataExtension extends MetadataType {
354
356
  DataExtensionField.postRetrieveTasks(field, true);
355
357
  }
356
358
  }
359
+ await this.#fixShared(upsertedMetadata, originalMetadata, createdUpdated);
360
+ }
361
+
362
+ /**
363
+ * takes care of updating attribute groups on child BUs after an update to Shared DataExtensions
364
+ * helper for {@link DataExtension.postDeployTasks}
365
+ * fixes an issue where shared data extensions are not visible in data designer on child BU; SF known issue: https://issues.salesforce.com/#q=W-11031095
366
+ *
367
+ * @param {TYPE.DataExtensionMap} upsertedMetadata metadata mapped by their keyField
368
+ * @param {TYPE.DataExtensionMap} originalMetadata metadata to be updated (contains additioanl fields)
369
+ * @param {{created: number, updated: number}} createdUpdated counter representing successful creates/updates
370
+ * @returns {void}
371
+ */
372
+ static async #fixShared(upsertedMetadata, originalMetadata, createdUpdated) {
373
+ if (this.buObject.eid !== this.buObject.mid) {
374
+ // only if we were executing a deploy on parent bu could we be deploying shared data extensions
375
+ Util.logger.debug(`Skipping fixShared logic because we are not executing on Parent BU`);
376
+ return;
377
+ }
378
+ if (createdUpdated.updated === 0) {
379
+ // only if updates were made could the issue in https://issues.salesforce.com/#q=W-11031095 affect data designer
380
+ Util.logger.debug(`Skipping fixShared logic because nothing was updated`);
381
+ return;
382
+ }
383
+
384
+ // find all shared data extensions
385
+ if (!this.deployedSharedKeys.length) {
386
+ Util.logger.debug(
387
+ `Skipping fixShared logic because no Shared Data Extensions were updated`
388
+ );
389
+ return;
390
+ }
391
+
392
+ const sharedDataExtensionsKeys = this.deployedSharedKeys;
393
+ this.deployedSharedKeys = null;
394
+
395
+ if (Util.OPTIONS.fixShared) {
396
+ // select which BUs to run this for
397
+ const selectedBuNames = await this.#fixShared_getBUs();
398
+
399
+ // backup settings
400
+ const buObjectBak = this.buObject;
401
+ const clientBak = this.client;
402
+
403
+ // get dataExtension ID-Key relationship
404
+ const sharedDataExtensionMap = {};
405
+ for (const key of sharedDataExtensionsKeys) {
406
+ try {
407
+ const id = cache.searchForField(
408
+ 'dataExtension',
409
+ key,
410
+ 'CustomerKey',
411
+ 'ObjectID',
412
+ this.buObject.eid
413
+ );
414
+ sharedDataExtensionMap[id] = key;
415
+ } catch {
416
+ continue;
417
+ }
418
+ }
419
+
420
+ // run the fix-data-model logic
421
+ Util.logger.info(
422
+ `Fixing Shared Data Extensions details in data models of child BUs` +
423
+ Util.getKeysString(sharedDataExtensionsKeys)
424
+ );
425
+
426
+ for (const buName of selectedBuNames) {
427
+ await this.#fixShared_onBU(buName, buObjectBak, clientBak, sharedDataExtensionMap);
428
+ }
429
+ Util.logger.info(`Finished fixing Shared Data Extensions details in data models`);
430
+
431
+ // restore settings
432
+ this.buObject = buObjectBak;
433
+ this.client = clientBak;
434
+ } else {
435
+ Util.logger.warn(
436
+ 'Shared Data Extensions were updated but --fixShared option is not set. This can result in your changes not being visible in attribute groups on child BUs.'
437
+ );
438
+ Util.logger.info(
439
+ 'We recommend to re-run your deployment with the --fixShared option unless you are sure your Shared Data Extension is not used in attribute groups on any child BU.'
440
+ );
441
+ }
442
+ }
443
+
444
+ /**
445
+ * helper for {@link DataExtension.#fixShared}
446
+ *
447
+ * @returns {string[]} list of selected BU names
448
+ */
449
+ static async #fixShared_getBUs() {
450
+ const buListObj = this.properties.credentials[this.buObject.credential].businessUnits;
451
+ const fixBuPreselected = [];
452
+ const availableBuNames = Object.keys(buListObj).filter(
453
+ (buName) => buName !== Util.parentBuName
454
+ );
455
+ if (typeof Util.OPTIONS.fixShared === 'string') {
456
+ if (Util.OPTIONS.fixShared === '*') {
457
+ // pre-select all BUs
458
+ fixBuPreselected.push(...availableBuNames);
459
+ } else {
460
+ // pre-select BUs from comma-separated list
461
+ fixBuPreselected.push(
462
+ ...Util.OPTIONS.fixShared
463
+ .split(',')
464
+ .filter(Boolean)
465
+ .map((bu) => bu.trim())
466
+ .filter((bu) => availableBuNames.includes(bu))
467
+ );
468
+ }
469
+ }
470
+ if (Util.skipInteraction && fixBuPreselected.length) {
471
+ // assume programmatic use case or user that wants to skip the wizard. Use pre-selected BUs
472
+ return fixBuPreselected;
473
+ }
474
+
475
+ const buList = availableBuNames.map((name) => ({
476
+ name,
477
+ value: name,
478
+ checked: fixBuPreselected.includes(name),
479
+ }));
480
+ const questions = {
481
+ type: 'checkbox',
482
+ name: 'businessUnits',
483
+ message: 'Please select BUs that have access to the updated Shared Data Extensions:',
484
+ pageSize: 10,
485
+ choices: buList,
486
+ };
487
+ let responses = null;
488
+
489
+ try {
490
+ responses = await inquirer.prompt(questions);
491
+ } catch (ex) {
492
+ Util.logger.info(ex);
493
+ }
494
+ return responses.businessUnits;
495
+ }
496
+
497
+ /**
498
+ * helper for {@link DataExtension.#fixShared}
499
+ *
500
+ * @param {string} childBuName name of child BU to fix
501
+ * @param {TYPE.BuObject} buObjectParent bu object for parent BU
502
+ * @param {object} clientParent SDK for parent BU
503
+ * @param {Object.<string, string>} sharedDataExtensionMap ID-Key relationship of shared data extensions
504
+ * @returns {Promise.<string[]>} updated shared DE keys on BU
505
+ */
506
+ static async #fixShared_onBU(
507
+ childBuName,
508
+ buObjectParent,
509
+ clientParent,
510
+ sharedDataExtensionMap
511
+ ) {
512
+ /** @type {TYPE.BuObject} */
513
+ const buObjectChildBu = {
514
+ eid: this.properties.credentials[buObjectParent.credential].eid,
515
+ mid: this.properties.credentials[buObjectParent.credential].businessUnits[childBuName],
516
+ businessUnit: childBuName,
517
+ credential: this.buObject.credential,
518
+ };
519
+ const clientChildBu = auth.getSDK(buObjectChildBu);
520
+
521
+ try {
522
+ // check if shared data Extension is used in an attributeSet on current BU
523
+ AttributeSet.properties = this.properties;
524
+ AttributeSet.buObject = buObjectChildBu;
525
+ AttributeSet.client = clientChildBu;
526
+ const sharedDeIdsUsedOnBU = await AttributeSet.fixShared_retrieve(
527
+ sharedDataExtensionMap,
528
+ DataExtensionField.fixShared_fields
529
+ );
530
+ if (sharedDeIdsUsedOnBU.length) {
531
+ let sharedDataExtensionsKeys = sharedDeIdsUsedOnBU.map(
532
+ (deId) => sharedDataExtensionMap[deId]
533
+ );
534
+ Util.logger.info(
535
+ ` - Fixing dataExtensions on BU ${childBuName} ` +
536
+ Util.getKeysString(sharedDataExtensionsKeys)
537
+ );
538
+
539
+ for (const deId of sharedDeIdsUsedOnBU) {
540
+ // dont use Promise.all to ensure order of execution; otherwise, switched BU contexts in one step will affect the next
541
+ const fixed = await this.#fixShared_item(
542
+ deId,
543
+ sharedDataExtensionMap[deId],
544
+ buObjectChildBu,
545
+ clientChildBu,
546
+ buObjectParent,
547
+ clientParent
548
+ );
549
+ if (!fixed) {
550
+ // remove from list of shared DEs that were fixed
551
+ sharedDataExtensionsKeys = sharedDataExtensionsKeys.filter(
552
+ (key) => key !== sharedDataExtensionMap[deId]
553
+ );
554
+ }
555
+ }
556
+ if (sharedDataExtensionsKeys.length) {
557
+ Util.logger.debug(
558
+ ` - Fixed ${sharedDataExtensionsKeys.length}/${
559
+ sharedDeIdsUsedOnBU.length
560
+ }: ${sharedDataExtensionsKeys.join(', ')}`
561
+ );
562
+ }
563
+ return sharedDataExtensionsKeys;
564
+ } else {
565
+ Util.logger.info(
566
+ Util.getGrayMsg(
567
+ ` - No matching attributeSet found for given Shared Data Extensions keys found on BU ${childBuName}`
568
+ )
569
+ );
570
+ return [];
571
+ }
572
+ } catch (ex) {
573
+ Util.logger.error(ex.message);
574
+ return [];
575
+ }
576
+ }
577
+
578
+ /**
579
+ * method that actually takes care of triggering the update for a particular BU-sharedDe combo
580
+ * helper for {@link DataExtension.#fixShared_onBU}
581
+ *
582
+ * @param {string} deId data extension ObjectID
583
+ * @param {string} deKey dataExtension key
584
+ * @param {TYPE.BuObject} buObjectChildBu BU object for Child BU
585
+ * @param {object} clientChildBu SDK for child BU
586
+ * @param {TYPE.BuObject} buObjectParent BU object for Parent BU
587
+ * @param {object} clientParent SDK for parent BU
588
+ * @returns {Promise.<boolean>} flag that signals if the fix was successful
589
+ */
590
+ static async #fixShared_item(
591
+ deId,
592
+ deKey,
593
+ buObjectChildBu,
594
+ clientChildBu,
595
+ buObjectParent,
596
+ clientParent
597
+ ) {
598
+ try {
599
+ // add field via child BU
600
+ const randomSuffix = await DataExtension.#fixShared_item_addField(
601
+ buObjectChildBu,
602
+ clientChildBu,
603
+ deKey,
604
+ deId
605
+ );
606
+
607
+ // get field ID from parent BU (it is not returned on child BU)
608
+ const fieldObjectID = await DataExtension.#fixShared_item_getFieldId(
609
+ randomSuffix,
610
+ buObjectParent,
611
+ clientParent,
612
+ deKey
613
+ );
614
+
615
+ // delete field via child BU
616
+ await DataExtension.#fixShared_item_deleteField(
617
+ randomSuffix,
618
+ buObjectChildBu,
619
+ clientChildBu,
620
+ deKey,
621
+ fieldObjectID
622
+ );
623
+
624
+ Util.logger.info(
625
+ ` - Fixed dataExtension ${deKey} on BU ${buObjectChildBu.businessUnit}`
626
+ );
627
+
628
+ return true;
629
+ } catch (ex) {
630
+ Util.logger.error(
631
+ `- error fixing dataExtension ${deKey} on BU ${buObjectChildBu.businessUnit}: ${ex.message}`
632
+ );
633
+ return false;
634
+ }
635
+ }
636
+
637
+ /**
638
+ * add a new field to the shared DE to trigger an update to the data model
639
+ * helper for {@link DataExtension.#fixShared_item}
640
+ *
641
+ * @param {TYPE.BuObject} buObjectChildBu BU object for Child BU
642
+ * @param {object} clientChildBu SDK for child BU
643
+ * @param {string} deKey dataExtension key
644
+ * @param {string} deId dataExtension ObjectID
645
+ * @returns {Promise.<string>} randomSuffix
646
+ */
647
+ static async #fixShared_item_addField(buObjectChildBu, clientChildBu, deKey, deId) {
648
+ this.buObject = buObjectChildBu;
649
+ this.client = clientChildBu;
650
+ const randomSuffix = Util.OPTIONS._runningTest
651
+ ? '_randomNumber_'
652
+ : Math.floor(Math.random() * 9999999999);
653
+ // add a new field to the shared DE to trigger an update to the data model
654
+ const soapType = this.definition.soapType || this.definition.type;
655
+ await this.client.soap.update(
656
+ Util.capitalizeFirstLetter(soapType),
657
+ {
658
+ CustomerKey: deKey,
659
+ ObjectID: deId,
660
+ Fields: {
661
+ Field: [
662
+ {
663
+ Name: 'TriggerUpdate' + randomSuffix,
664
+ IsRequired: false,
665
+ IsPrimaryKey: false,
666
+ FieldType: 'Boolean',
667
+ ObjectID: null,
668
+ },
669
+ ],
670
+ },
671
+ },
672
+ null
673
+ );
674
+ return randomSuffix;
675
+ }
676
+
677
+ /**
678
+ * get ID of the field added by {@link DataExtension.#fixShared_item_addField} on the shared DE via parent BU
679
+ * helper for {@link DataExtension.#fixShared_item}
680
+ *
681
+ * @param {string} randomSuffix -
682
+ * @param {TYPE.BuObject} buObjectParent BU object for Parent BU
683
+ * @param {object} clientParent SDK for parent BU
684
+ * @param {string} deKey dataExtension key
685
+ * @returns {Promise.<string>} fieldObjectID
686
+ */
687
+ static async #fixShared_item_getFieldId(randomSuffix, buObjectParent, clientParent, deKey) {
688
+ DataExtensionField.buObject = buObjectParent;
689
+ DataExtensionField.client = clientParent;
690
+ const fieldKey = `[${deKey}].[TriggerUpdate${randomSuffix}]`;
691
+ const fieldResponse = await DataExtensionField.retrieveForCache(
692
+ {
693
+ filter: {
694
+ leftOperand: 'CustomerKey',
695
+ operator: 'equals',
696
+ rightOperand: fieldKey,
697
+ },
698
+ },
699
+ ['Name', 'ObjectID']
700
+ );
701
+ const fieldObjectID = fieldResponse.metadata[fieldKey]?.ObjectID;
702
+ return fieldObjectID;
703
+ }
704
+
705
+ /**
706
+ * delete the field added by {@link DataExtension.#fixShared_item_addField}
707
+ * helper for {@link DataExtension.#fixShared_item}
708
+ *
709
+ * @param {string} randomSuffix -
710
+ * @param {TYPE.BuObject} buObjectChildBu BU object for Child BU
711
+ * @param {object} clientChildBu SDK for child BU
712
+ * @param {string} deKey dataExtension key
713
+ * @param {string} fieldObjectID field ObjectID
714
+ * @returns {Promise} -
715
+ */
716
+ static async #fixShared_item_deleteField(
717
+ randomSuffix,
718
+ buObjectChildBu,
719
+ clientChildBu,
720
+ deKey,
721
+ fieldObjectID
722
+ ) {
723
+ DataExtensionField.buObject = buObjectChildBu;
724
+ DataExtensionField.client = clientChildBu;
725
+ await DataExtensionField.deleteByKeySOAP(
726
+ deKey + '.TriggerUpdate' + randomSuffix,
727
+ fieldObjectID
728
+ );
357
729
  }
358
730
 
359
731
  /**
@@ -390,70 +762,10 @@ class DataExtension extends MetadataType {
390
762
  // in case of cache dont get fields
391
763
  if (metadata && retrieveDir) {
392
764
  // get fields from API
393
- await this._attachFields(metadata, fieldOptions, additionalFields);
765
+ await this.#attachFields(metadata, fieldOptions, additionalFields);
394
766
  }
395
767
  if (!retrieveDir && this.buObject.eid !== this.buObject.mid) {
396
- // for caching, we want to retrieve shared DEs as well from the instance parent BU
397
- Util.logger.info(
398
- ' - Caching dependent Metadata: dataExtension (shared via _ParentBU_)'
399
- );
400
- /** @type {TYPE.BuObject} */
401
- const buObjectParentBu = {
402
- eid: this.properties.credentials[this.buObject.credential].eid,
403
- mid: this.properties.credentials[this.buObject.credential].eid,
404
- businessUnit: Util.parentBuName,
405
- credential: this.buObject.credential,
406
- };
407
- try {
408
- this.client = auth.getSDK(buObjectParentBu);
409
- } catch (ex) {
410
- Util.logger.error(ex.message);
411
- return;
412
- }
413
- const metadataParentBu = await this._retrieveAll(additionalFields);
414
-
415
- // get shared folders to match our shared / synched Data Extensions
416
- const subTypeArr = this.definition.dependencies
417
- .filter((item) => item.startsWith('folder-'))
418
- .map((item) => item.slice(7));
419
- Util.logger.info(' - Caching dependent Metadata: folder (shared via _ParentBU_)');
420
- Util.logSubtypes(subTypeArr);
421
- Folder.client = this.client;
422
- Folder.buObject = buObjectParentBu;
423
- Folder.properties = this.properties;
424
- const result = await Folder.retrieveForCache(null, subTypeArr);
425
- cache.mergeMetadata('folder', result.metadata, this.buObject.eid);
426
-
427
- // get the types and clean out non-shared ones
428
- const folderTypesFromParent = require('../MetadataTypeDefinitions').folder
429
- .folderTypesFromParent;
430
- for (const metadataEntry in metadataParentBu) {
431
- try {
432
- // get the data extension type from the folder
433
- const folderContentType = cache.searchForField(
434
- 'folder',
435
- metadataParentBu[metadataEntry].CategoryID,
436
- 'ID',
437
- 'ContentType',
438
- this.buObject.eid
439
- );
440
- if (!folderTypesFromParent.includes(folderContentType)) {
441
- Util.logger.verbose(
442
- `removing ${metadataEntry} because r__folder_ContentType '${folderContentType}' identifies this DE as not being shared`
443
- );
444
- delete metadataParentBu[metadataEntry];
445
- }
446
- } catch (ex) {
447
- Util.logger.debug(
448
- `removing ${metadataEntry} because of error while retrieving r__folder_ContentType: ${ex.message}`
449
- );
450
- delete metadataParentBu[metadataEntry];
451
- }
452
- }
453
-
454
- // revert client to current default
455
- this.client = auth.getSDK(this.buObject);
456
- Folder.client = auth.getSDK(this.buObject);
768
+ const metadataParentBu = await this.retrieveSharedForCache(additionalFields);
457
769
 
458
770
  // make sure to overwrite parent bu DEs with local ones
459
771
  metadata = { ...metadataParentBu, ...metadata };
@@ -469,6 +781,81 @@ class DataExtension extends MetadataType {
469
781
  return { metadata: metadata, type: 'dataExtension' };
470
782
  }
471
783
 
784
+ /**
785
+ * get shared dataExtensions from parent BU and merge them into the cache
786
+ * helper for {@link DataExtension.retrieve} and for AttributeSet.fixShared_retrieve
787
+ *
788
+ * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
789
+ * @returns {Promise.<TYPE.DataExtensionMap>} keyField => metadata map
790
+ */
791
+ static async retrieveSharedForCache(additionalFields = []) {
792
+ // for caching, we want to retrieve shared DEs as well from the instance parent BU
793
+ Util.logger.info(' - Caching dependent Metadata: dataExtension (shared via _ParentBU_)');
794
+ const buObjectBak = this.buObject;
795
+ const clientBak = this.client;
796
+ /** @type {TYPE.BuObject} */
797
+ const buObjectParentBu = {
798
+ eid: this.properties.credentials[this.buObject.credential].eid,
799
+ mid: this.properties.credentials[this.buObject.credential].eid,
800
+ businessUnit: Util.parentBuName,
801
+ credential: this.buObject.credential,
802
+ };
803
+ try {
804
+ this.buObject = buObjectParentBu;
805
+ this.client = auth.getSDK(buObjectParentBu);
806
+ } catch (ex) {
807
+ Util.logger.error(ex.message);
808
+ return;
809
+ }
810
+ const metadataParentBu = await this._retrieveAll(additionalFields);
811
+
812
+ // get shared folders to match our shared / synched Data Extensions
813
+ const subTypeArr = this.definition.dependencies
814
+ .filter((item) => item.startsWith('folder-'))
815
+ .map((item) => item.slice(7));
816
+ Util.logger.info(' - Caching dependent Metadata: folder (shared via _ParentBU_)');
817
+ Util.logSubtypes(subTypeArr);
818
+ Folder.client = this.client;
819
+ Folder.buObject = this.buObject;
820
+ Folder.properties = this.properties;
821
+ const result = await Folder.retrieveForCache(null, subTypeArr);
822
+ cache.mergeMetadata('folder', result.metadata, this.buObject.eid);
823
+
824
+ // get the types and clean out non-shared ones
825
+ const folderTypesFromParent = MetadataTypeDefinitions.folder.folderTypesFromParent;
826
+ for (const metadataEntry in metadataParentBu) {
827
+ try {
828
+ // get the data extension type from the folder
829
+ const folderContentType = cache.searchForField(
830
+ 'folder',
831
+ metadataParentBu[metadataEntry].CategoryID,
832
+ 'ID',
833
+ 'ContentType',
834
+ this.buObject.eid
835
+ );
836
+ if (!folderTypesFromParent.includes(folderContentType)) {
837
+ // Util.logger.verbose(
838
+ // `removing ${metadataEntry} because r__folder_ContentType '${folderContentType}' identifies this DE as not being shared`
839
+ // );
840
+ delete metadataParentBu[metadataEntry];
841
+ }
842
+ } catch (ex) {
843
+ Util.logger.debug(
844
+ `removing ${metadataEntry} because of error while retrieving r__folder_ContentType: ${ex.message}`
845
+ );
846
+ delete metadataParentBu[metadataEntry];
847
+ }
848
+ }
849
+
850
+ // revert client to current default
851
+ this.client = clientBak;
852
+ this.buObject = buObjectBak;
853
+ Folder.client = clientBak;
854
+ Folder.buObject = buObjectBak;
855
+
856
+ return metadataParentBu;
857
+ }
858
+
472
859
  /**
473
860
  * helper to retrieve all dataExtension fields and attach them to the dataExtension metadata
474
861
  *
@@ -478,7 +865,7 @@ class DataExtension extends MetadataType {
478
865
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
479
866
  * @returns {Promise.<void>} -
480
867
  */
481
- static async _attachFields(metadata, fieldOptions, additionalFields) {
868
+ static async #attachFields(metadata, fieldOptions, additionalFields) {
482
869
  const fieldsObj = await this._retrieveFields(fieldOptions, additionalFields);
483
870
  const fieldKeys = Object.keys(fieldsObj);
484
871
  // add fields to corresponding DE
@@ -628,11 +1015,16 @@ class DataExtension extends MetadataType {
628
1015
  throw new Error(`Cannot Upsert Strongly Typed Data Extensions`);
629
1016
  }
630
1017
  if (
1018
+ !Util.OPTIONS._fixSharedOnBu &&
631
1019
  this.buObject.eid !== this.buObject.mid &&
632
1020
  metadata.r__folder_Path?.startsWith('Shared Items')
633
1021
  ) {
634
1022
  throw new Error(`Cannot Create/Update a Shared Data Extension from the Child BU`);
635
1023
  }
1024
+ if (metadata.r__folder_ContentType === 'shared_dataextension') {
1025
+ this.deployedSharedKeys ||= [];
1026
+ this.deployedSharedKeys.push(metadata.CustomerKey);
1027
+ }
636
1028
  if (metadata.r__folder_Path?.startsWith('Synchronized Data Extensions')) {
637
1029
  throw new Error(
638
1030
  `Cannot Create/Update a Synchronized Data Extension. Please use Contact Builder to maintain these`
@@ -822,7 +1214,11 @@ class DataExtension extends MetadataType {
822
1214
  for (const element of fieldsJson) {
823
1215
  const newJsonElement = {};
824
1216
  for (const field of fieldsToKeep) {
825
- newJsonElement[field] = element[field];
1217
+ if (field === 'MaxLength' && element.FieldType === 'Decimal') {
1218
+ newJsonElement.MaxLength = `${element.MaxLength},${element.Scale}`;
1219
+ } else {
1220
+ newJsonElement[field] = element[field];
1221
+ }
826
1222
  }
827
1223
  newJson.push(newJsonElement);
828
1224
  }
@@ -1113,6 +1509,7 @@ class DataExtension extends MetadataType {
1113
1509
  }
1114
1510
 
1115
1511
  // Assign definition to static attributes
1116
- DataExtension.definition = require('../MetadataTypeDefinitions').dataExtension;
1512
+ import MetadataTypeDefinitions from '../MetadataTypeDefinitions.js';
1513
+ DataExtension.definition = MetadataTypeDefinitions.dataExtension;
1117
1514
 
1118
- module.exports = DataExtension;
1515
+ export default DataExtension;