mcdev 7.6.3 → 7.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/ISSUE_TEMPLATE/task.md +1 -1
  3. package/.github/workflows/coverage-base-update.yml +2 -2
  4. package/.github/workflows/coverage-develop-branch.yml +3 -1
  5. package/.github/workflows/coverage-main-branch.yml +3 -1
  6. package/.github/workflows/coverage.yml +5 -3
  7. package/.mcdev-validations.js +0 -0
  8. package/@types/lib/Builder.d.ts +14 -0
  9. package/@types/lib/Builder.d.ts.map +1 -1
  10. package/@types/lib/MetadataTypeDefinitions.d.ts +2 -0
  11. package/@types/lib/MetadataTypeDefinitions.d.ts.map +1 -1
  12. package/@types/lib/MetadataTypeInfo.d.ts +2 -0
  13. package/@types/lib/MetadataTypeInfo.d.ts.map +1 -1
  14. package/@types/lib/index.d.ts +17 -8
  15. package/@types/lib/index.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/Asset.d.ts +11 -3
  17. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  19. package/@types/lib/metadataTypes/DomainVerification.d.ts +180 -0
  20. package/@types/lib/metadataTypes/DomainVerification.d.ts.map +1 -0
  21. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  22. package/@types/lib/metadataTypes/Journey.d.ts +7 -4
  23. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  24. package/@types/lib/metadataTypes/MetadataType.d.ts +15 -7
  25. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  26. package/@types/lib/metadataTypes/MobileKeyword.d.ts +2 -10
  27. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  28. package/@types/lib/metadataTypes/MobileMessage.d.ts +2 -10
  29. package/@types/lib/metadataTypes/MobileMessage.d.ts.map +1 -1
  30. package/@types/lib/metadataTypes/SendClassification.d.ts.map +1 -1
  31. package/@types/lib/metadataTypes/SenderProfile.d.ts +7 -0
  32. package/@types/lib/metadataTypes/SenderProfile.d.ts.map +1 -1
  33. package/@types/lib/metadataTypes/TransactionalEmail.d.ts +2 -2
  34. package/@types/lib/metadataTypes/TransactionalEmail.d.ts.map +1 -1
  35. package/@types/lib/metadataTypes/TriggeredSend.d.ts +8 -0
  36. package/@types/lib/metadataTypes/TriggeredSend.d.ts.map +1 -1
  37. package/@types/lib/metadataTypes/Verification.d.ts +0 -9
  38. package/@types/lib/metadataTypes/Verification.d.ts.map +1 -1
  39. package/@types/lib/metadataTypes/definitions/DomainVerification.definition.d.ts +100 -0
  40. package/@types/lib/metadataTypes/definitions/DomainVerification.definition.d.ts.map +1 -0
  41. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +1 -1
  42. package/@types/lib/util/devops.d.ts.map +1 -1
  43. package/@types/lib/util/replaceContentBlockReference.d.ts +2 -1
  44. package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
  45. package/@types/lib/util/util.d.ts +42 -1
  46. package/@types/lib/util/util.d.ts.map +1 -1
  47. package/@types/lib/util/validations.d.ts.map +1 -1
  48. package/@types/types/mcdev.d.d.ts +34 -0
  49. package/@types/types/mcdev.d.d.ts.map +1 -1
  50. package/boilerplate/config.json +11 -0
  51. package/boilerplate/files/eslint.config.js +98 -3
  52. package/boilerplate/forcedUpdates.json +4 -0
  53. package/boilerplate/gitignore-template +1 -1
  54. package/boilerplate/npm-dependencies.json +1 -0
  55. package/eslint.config.js +4 -3
  56. package/lib/Builder.js +114 -54
  57. package/lib/Deployer.js +2 -2
  58. package/lib/MetadataTypeDefinitions.js +2 -0
  59. package/lib/MetadataTypeInfo.js +2 -0
  60. package/lib/cli.js +127 -3
  61. package/lib/index.js +217 -164
  62. package/lib/metadataTypes/Asset.js +76 -22
  63. package/lib/metadataTypes/DataExtension.js +10 -2
  64. package/lib/metadataTypes/DomainVerification.js +246 -0
  65. package/lib/metadataTypes/Event.js +21 -9
  66. package/lib/metadataTypes/Journey.js +339 -223
  67. package/lib/metadataTypes/MetadataType.js +153 -106
  68. package/lib/metadataTypes/MobileKeyword.js +5 -2
  69. package/lib/metadataTypes/MobileMessage.js +5 -2
  70. package/lib/metadataTypes/SendClassification.js +5 -0
  71. package/lib/metadataTypes/SenderProfile.js +102 -3
  72. package/lib/metadataTypes/TransactionalEmail.js +3 -1
  73. package/lib/metadataTypes/Verification.js +3 -1
  74. package/lib/metadataTypes/definitions/DomainVerification.definition.js +71 -0
  75. package/lib/metadataTypes/definitions/Journey.definition.js +7 -1
  76. package/lib/metadataTypes/definitions/SendClassification.definition.js +2 -2
  77. package/lib/metadataTypes/definitions/SenderProfile.definition.js +1 -1
  78. package/lib/util/config.js +6 -0
  79. package/lib/util/devops.js +130 -154
  80. package/lib/util/file.js +3 -3
  81. package/lib/util/replaceContentBlockReference.js +10 -3
  82. package/lib/util/util.js +96 -14
  83. package/lib/util/validations.js +34 -14
  84. package/package.json +10 -10
  85. package/test/general.test.js +339 -96
  86. package/test/mockRoot/.mcdev-validations.js +66 -0
  87. package/test/mockRoot/.mcdevrc.json +30 -2
  88. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_htmlblock.asset-block-meta.html +1 -0
  89. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_htmlblock.asset-block-meta.json +39 -0
  90. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_withCBBK_notexisting.asset-block-meta.html +4 -0
  91. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_withCBBK_notexisting.asset-block-meta.json +39 -0
  92. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_withCBBK_preexisting.asset-block-meta.html +4 -0
  93. package/test/mockRoot/deploy/testInstance/testBU/asset/block/testNew_asset_withCBBK_preexisting.asset-block-meta.json +39 -0
  94. package/test/mockRoot/deploy/testInstance/testBU/asset/message/testNew_assetMessage/testNew_assetMessage.asset-message-meta.json +435 -0
  95. package/test/mockRoot/deploy/testInstance/testBU/asset/message/testNew_assetMessage/views.html.content.asset-message-meta.html +150 -0
  96. package/test/mockRoot/deploy/testInstance/testBU/asset/message/testNew_asset_templatebasedemail/testNew_asset_templatebasedemail.asset-message-meta.json +305 -0
  97. package/test/mockRoot/deploy/testInstance/testBU/asset/message/testNew_asset_templatebasedemail/views.html.content.asset-message-meta.html +150 -0
  98. package/test/mockRoot/deploy/testInstance/testBU/asset/template/testNew_asset_template/content.asset-template-meta.html +150 -0
  99. package/test/mockRoot/deploy/testInstance/testBU/asset/template/testNew_asset_template/testNew_asset_template.asset-template-meta.json +116 -0
  100. package/test/mockRoot/deploy/testInstance/testBU/domainVerification/joern.berkefeld.New@accenture.com.domainVerification-meta.json +6 -0
  101. package/test/mockRoot/deploy/testInstance/testBU/domainVerification/joern.berkefeld@accenture.com.domainVerification-meta.json +6 -0
  102. package/test/mockRoot/deploy/testInstance/testBU/domainVerification/mcdev.accenture.com.domainVerification-meta.json +6 -0
  103. package/test/mockRoot/deploy/testInstance/testBU/journey/testNew_temail_notPublished.journey-meta.json +213 -0
  104. package/test/mockRoot/deploy/testInstance/testBU/senderProfile/testExisting_senderProfile.senderProfile-meta.json +1 -0
  105. package/test/mockRoot/deploy/testInstance/testBU/senderProfile/testNew_senderProfile.senderProfile-meta.json +1 -0
  106. package/test/resourceFactory.js +7 -24
  107. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_assetMessage.json +441 -0
  108. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_asset_htmlblock.json +59 -0
  109. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_asset_template.json +147 -0
  110. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_asset_templatebasedemail.json +322 -0
  111. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_asset_withCBBK_notexisting.json +59 -0
  112. package/test/resources/9999999/asset/v1/content/assets/post-response-key=testNew_asset_withCBBK_preexisting.json +59 -0
  113. package/test/resources/9999999/asset-deploy2/block/testBlacklist_asset_htmlblock.asset-block-meta.html +1 -0
  114. package/test/resources/9999999/asset-deploy2/block/testBlacklist_asset_htmlblock.asset-block-meta.json +39 -0
  115. package/test/resources/9999999/automation/clone-expected.json +61 -0
  116. package/test/resources/9999999/dataExtension/retrieve-CustomerKey=testExisting_dataExtension-response.xml +52 -0
  117. package/test/resources/9999999/dataExtension-deploy/testBlacklist_dataExtension.dataExtension-meta.json +20 -0
  118. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-shared,dataextension,hidden,salesforcedataextension,shared_data,shared_dataextension,shared_salesforcedataextension,synchronizeddataextension-response.xml +137 -0
  119. package/test/resources/9999999/domainVerification/create-expected.json +3 -0
  120. package/test/resources/9999999/domainVerification/get-sap-expected.json +6 -0
  121. package/test/resources/9999999/domainVerification/update-expected.json +6 -0
  122. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/put-response-paused.json +219 -0
  123. package/test/resources/9999999/interaction/v1/interactions/key_testNew_temail_notPublished/get-response.json +218 -0
  124. package/test/resources/9999999/interaction/v1/interactions/post-response.json +216 -0
  125. package/test/resources/9999999/journey/create-transactionaEmail-publish-expected.json +217 -0
  126. package/test/resources/9999999/messaging/v1/domainverification/delete/post-response.txt +1 -0
  127. package/test/resources/9999999/messaging/v1/domainverification/get-response.json +43 -0
  128. package/test/resources/9999999/messaging/v1/domainverification/post-response.txt +1 -0
  129. package/test/resources/9999999/messaging/v1/domainverification/update/post-response.txt +1 -0
  130. package/test/resources/9999999/messaging/v1/email/definitions/get-response.json +7 -0
  131. package/test/resources/9999999/messaging/v1/email/definitions/testNew_temail_notPublished/get-response.json +26 -0
  132. package/test/resources/9999999/query/clone-expected.json +8 -0
  133. package/test/resources/9999999/query/clone-expected.sql +7 -0
  134. package/test/resources/9999999/senderProfile/create-response.xml +1 -1
  135. package/test/resources/9999999/senderProfile/post-expected.json +1 -1
  136. package/test/resources/9999999/transactionalEmail/create-publish-expected.json +20 -0
  137. package/test/type.asset.test.js +216 -9
  138. package/test/type.automation.test.js +1 -1
  139. package/test/type.domainVerification.test.js +169 -0
  140. package/test/type.journey.test.js +107 -21
  141. package/test/type.script.test.js +1 -1
  142. package/test/type.sendClassification.test.js +3 -3
  143. package/test/type.senderProfile.test.js +26 -6
  144. package/test/type.transactionalEmail.test.js +5 -5
  145. package/test/type.triggeredSend.test.js +1 -1
  146. package/test/utils.js +8 -0
  147. package/types/mcdev.d.js +12 -0
@@ -142,8 +142,8 @@ class MetadataType {
142
142
  * Deploys metadata
143
143
  *
144
144
  * @param {MetadataTypeMap} metadataMap metadata mapped by their keyField
145
- * @param {string} deployDir directory where deploy metadata are saved
146
- * @param {string} retrieveDir directory where metadata after deploy should be saved
145
+ * @param {string} deployDir directory where deploy metadata are saved, ending on cred/bu
146
+ * @param {string} retrieveDir directory where metadata after deploy should be saved, ending on cred/bu
147
147
  * @returns {Promise.<MetadataTypeMap>} Promise of keyField => metadata map
148
148
  */
149
149
  static async deploy(metadataMap, deployDir, retrieveDir) {
@@ -173,24 +173,36 @@ class MetadataType {
173
173
  */
174
174
  static async postDeployTasks(upsertResults, originalMetadata, createdUpdated) {}
175
175
 
176
+ /**
177
+ * Gets executed before deleting a list of keys for the current type
178
+ *
179
+ * @param {string} keyArr metadata keys
180
+ * @returns {Promise.<void>} -
181
+ */
182
+ static async preDeleteTasks(keyArr) {}
183
+
176
184
  /**
177
185
  * helper for {@link MetadataType.createREST}
178
186
  *
179
187
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
180
188
  * @param {object} apiResponse varies depending on the API call
181
189
  * @param {MetadataTypeItem} metadataEntryWithAllFields like metadataEntry but before non-creatable fields were stripped
182
- * @returns {void}
190
+ * @returns {Promise.<object>} apiResponse, potentially modified
183
191
  */
184
- static postCreateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {}
192
+ static async postCreateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {
193
+ return apiResponse;
194
+ }
185
195
 
186
196
  /**
187
197
  * helper for {@link MetadataType.updateREST}
188
198
  *
189
199
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
190
200
  * @param {object} apiResponse varies depending on the API call
191
- * @returns {void}
201
+ * @returns {Promise.<object>} apiResponse, potentially modified
192
202
  */
193
- static postUpdateTasks(metadataEntry, apiResponse) {}
203
+ static postUpdateTasks(metadataEntry, apiResponse) {
204
+ return apiResponse;
205
+ }
194
206
 
195
207
  /**
196
208
  * helper for {@link MetadataType.createREST} when legacy API endpoints as these do not return the created item but only their new id
@@ -252,9 +264,8 @@ class MetadataType {
252
264
  delete metadata[this.definition.folderIdField];
253
265
  } catch (ex) {
254
266
  Util.logger.warn(
255
- ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${
256
- metadata[this.definition.keyField]
257
- }): Could not find folder (${ex.message})`
267
+ Util.getMsgPrefix(this.definition, metadata) +
268
+ `: Could not find folder (${ex.message})`
258
269
  );
259
270
  }
260
271
  }
@@ -437,7 +448,7 @@ class MetadataType {
437
448
  // write to file
438
449
  await File.writeJSONToFile([templateDir, ...typeDirArr], fileName, metadata);
439
450
  Util.logger.info(
440
- `- templated ${this.definition.type}: ${key} (${
451
+ ` - templated ${this.definition.type}: ${key} (${
441
452
  metadata[this.definition.nameField]
442
453
  })`
443
454
  );
@@ -495,9 +506,7 @@ class MetadataType {
495
506
  */
496
507
  static async create(metadata, deployDir) {
497
508
  Util.logger.error(
498
- ` ☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
499
- metadata[this.definition.nameField]
500
- }: create is not supported yet for ${this.definition.type}`
509
+ ` ☇ skipping ${Util.getTypeKeyName(this.definition, metadata)}: create is not supported yet for ${this.definition.type}`
501
510
  );
502
511
  return;
503
512
  }
@@ -511,9 +520,7 @@ class MetadataType {
511
520
  */
512
521
  static async update(metadata, metadataBefore) {
513
522
  Util.logger.error(
514
- ` ☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
515
- metadata[this.definition.nameField]
516
- }: update is not supported yet for ${this.definition.type}`
523
+ ` ☇ skipping ${Util.getTypeKeyName(this.definition, metadata)}: update is not supported yet for ${this.definition.type}`
517
524
  );
518
525
  return;
519
526
  }
@@ -523,9 +530,10 @@ class MetadataType {
523
530
  *
524
531
  * @param {string[]} [keyArr] metadata keys
525
532
  * @param {boolean} [checkKey] whether to check if the key is valid
533
+ * @param {MetadataTypeMap} [upsertResults] metadata mapped by their keyField as returned by update/create; needs to be refreshed after publish
526
534
  * @returns {Promise.<string[]>} Returns list of keys that were refreshed
527
535
  */
528
- static async refresh(keyArr, checkKey = true) {
536
+ static async refresh(keyArr, checkKey = true, upsertResults) {
529
537
  Util.logger.error(
530
538
  ` ☇ skipping ${this.definition.type}: refresh is not supported yet for ${this.definition.type}`
531
539
  );
@@ -610,9 +618,7 @@ class MetadataType {
610
618
  if (!findAssetKeys) {
611
619
  Util.logger.info(
612
620
  Util.getGrayMsg(
613
- ` ☇ skipping ${this.definition.type} ${
614
- item[this.definition.keyField]
615
- }: no ${fromDescription} found`
621
+ ` ☇ skipping ${Util.getTypeKeyName(this.definition, item)}: no ${fromDescription} found`
616
622
  )
617
623
  );
618
624
  }
@@ -774,24 +780,38 @@ class MetadataType {
774
780
  // preDeployTasks parsing
775
781
  let deployableMetadata;
776
782
  try {
777
- await this.validation('deploy', metadataMap[metadataKey], deployDir);
778
- deployableMetadata = await this.preDeployTasks(
783
+ metadataMap[metadataKey] = await this.validation(
784
+ 'deploy',
779
785
  metadataMap[metadataKey],
780
786
  deployDir
781
787
  );
788
+ if (metadataMap[metadataKey]) {
789
+ // only run unless we encountered a situation in our validation that made us want to filter this record
790
+ deployableMetadata = await this.preDeployTasks(
791
+ metadataMap[metadataKey],
792
+ deployDir
793
+ );
794
+ }
782
795
  } catch (ex) {
783
796
  // do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
784
797
  hasError = true;
785
798
  deployableMetadata = metadataMap[metadataKey];
786
-
787
- // * include ": ${ex.message}" in the error if this is ever turned back into Util.logger.error()
788
- Util.logger.errorStack(
789
- ex,
790
- ` ☇ skipping ${this.definition.type} ${
791
- deployableMetadata[this.definition.keyField]
792
- } / ${deployableMetadata[this.definition.nameField]}`
793
- );
799
+ if (deployableMetadata) {
800
+ // * include ": ${ex.message}" in the error if this is ever turned back into Util.logger.error()
801
+ Util.logger.errorStack(
802
+ ex,
803
+ ` ☇ skipping ${this.definition.type} ${
804
+ deployableMetadata[this.definition.keyField]
805
+ } / ${deployableMetadata[this.definition.nameField]}`
806
+ );
807
+ } else {
808
+ Util.logger.errorStack(
809
+ ex,
810
+ ` ☇ skipping ${this.definition.type} ${metadataKey}`
811
+ );
812
+ }
794
813
  }
814
+
795
815
  // if preDeploy returns nothing then it cannot be deployed so skip deployment
796
816
  if (deployableMetadata) {
797
817
  metadataMap[metadataKey] = deployableMetadata;
@@ -809,8 +829,14 @@ class MetadataType {
809
829
  const result = await this.create(metadataMap[metadataKey], deployDir);
810
830
  if (result) {
811
831
  createResults.push(result);
832
+ if (result[this.definition.idField]) {
833
+ // ensure we have the ID in the cache
834
+ metadataMap[metadataKey][this.definition.idField] =
835
+ result[this.definition.idField];
836
+ }
812
837
 
813
838
  // make this newly created item available in cache for other itmes that might reference it
839
+ /** @type {MetadataTypeMap} */
814
840
  const newObject = {};
815
841
  newObject[metadataKey] = metadataMap[metadataKey];
816
842
  cache.mergeMetadata(this.definition.type, newObject);
@@ -1174,24 +1200,18 @@ class MetadataType {
1174
1200
  this.removeNotCreateableFields(metadataEntry);
1175
1201
  try {
1176
1202
  // set to empty object in case API returned nothing to be able to update it in helper classes
1177
- const response = (await this.client.rest.post(uri, metadataEntry)) || {};
1178
- await this.postCreateTasks(metadataEntry, response, metadataClone);
1203
+ let response = (await this.client.rest.post(uri, metadataEntry)) || {};
1204
+ response = await this.postCreateTasks(metadataEntry, response, metadataClone);
1179
1205
  if (!handleOutside) {
1180
1206
  Util.logger.info(
1181
- ` - created ${this.definition.type}: ${
1182
- metadataEntry[this.definition.keyField] ||
1183
- metadataEntry[this.definition.nameField]
1184
- } / ${metadataEntry[this.definition.nameField]}`
1207
+ ` - created ${Util.getTypeKeyName(this.definition, metadataEntry)}`
1185
1208
  );
1186
1209
  }
1187
1210
  return response;
1188
1211
  } catch (ex) {
1189
1212
  const parsedErrors = this.getErrorsREST(ex);
1190
1213
  Util.logger.error(
1191
- ` ☇ error creating ${this.definition.type} ${
1192
- metadataEntry[this.definition.keyField] ||
1193
- metadataEntry[this.definition.nameField]
1194
- } / ${metadataEntry[this.definition.nameField]}:`
1214
+ ` ☇ error creating ${Util.getTypeKeyName(this.definition, metadataEntry)}:`
1195
1215
  );
1196
1216
  if (parsedErrors.length) {
1197
1217
  for (const msg of parsedErrors) {
@@ -1223,9 +1243,7 @@ class MetadataType {
1223
1243
 
1224
1244
  if (!handleOutside) {
1225
1245
  Util.logger.info(
1226
- ` - created ${this.definition.type}: ${
1227
- metadataEntry[this.definition.keyField]
1228
- } / ${metadataEntry[this.definition.nameField]}`
1246
+ ` - created ${Util.getTypeKeyName(this.definition, metadataEntry)}`
1229
1247
  );
1230
1248
  }
1231
1249
  return response;
@@ -1247,25 +1265,17 @@ class MetadataType {
1247
1265
  this.removeNotUpdateableFields(metadataEntry);
1248
1266
  try {
1249
1267
  // set to empty object in case API returned nothing to be able to update it in helper classes
1250
- const response = (await this.client.rest[httpMethod](uri, metadataEntry)) || {};
1268
+ let response = (await this.client.rest[httpMethod](uri, metadataEntry)) || {};
1251
1269
  await this._postChangeKeyTasks(metadataEntry);
1252
1270
  this.getErrorsREST(response);
1253
- await this.postUpdateTasks(metadataEntry, response);
1271
+ response = await this.postUpdateTasks(metadataEntry, response);
1254
1272
  // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
1255
- Util.logger.info(
1256
- ` - updated ${this.definition.type}: ${
1257
- metadataEntry[this.definition.keyField] ||
1258
- metadataEntry[this.definition.nameField]
1259
- } / ${metadataEntry[this.definition.nameField]}`
1260
- );
1273
+ Util.logger.info(` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`);
1261
1274
  return response;
1262
1275
  } catch (ex) {
1263
1276
  const parsedErrors = this.getErrorsREST(ex);
1264
1277
  Util.logger.error(
1265
- ` ☇ error updating ${this.definition.type} ${
1266
- metadataEntry[this.definition.keyField] ||
1267
- metadataEntry[this.definition.nameField]
1268
- } / ${metadataEntry[this.definition.nameField]}:`
1278
+ ` ☇ error updating ${Util.getTypeKeyName(this.definition, metadataEntry)}:`
1269
1279
  );
1270
1280
  for (const msg of parsedErrors) {
1271
1281
  Util.logger.error(' • ' + msg);
@@ -1326,9 +1336,7 @@ class MetadataType {
1326
1336
  );
1327
1337
  if (!handleOutside) {
1328
1338
  Util.logger.info(
1329
- ` - updated ${this.definition.type}: ${
1330
- metadataEntry[this.definition.keyField]
1331
- } / ${metadataEntry[this.definition.nameField]}`
1339
+ ` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`
1332
1340
  );
1333
1341
  }
1334
1342
  await this._postChangeKeyTasks(metadataEntry);
@@ -1559,9 +1567,7 @@ class MetadataType {
1559
1567
  );
1560
1568
  if (response?.OverallStatus === 'OK') {
1561
1569
  Util.logger.info(
1562
- ` - executed ${this.definition.type}: ${
1563
- metadataEntry[this.definition.keyField]
1564
- }`
1570
+ ` - executed ${Util.getTypeKeyName(this.definition, metadataEntry)}`
1565
1571
  );
1566
1572
  } else {
1567
1573
  throw new Error(response?.OverallStatus);
@@ -1799,9 +1805,7 @@ class MetadataType {
1799
1805
  );
1800
1806
  if (includeByDefinition === false || includeByConfig === false) {
1801
1807
  Util.logger.debug(
1802
- `Filtered ${this.definition.type} '${
1803
- metadataEntry[this.definition.nameField]
1804
- }' (${metadataEntry[this.definition.keyField]}): not matching include filter`
1808
+ `Filtered ${Util.getTypeKeyName(this.definition, metadataEntry)}: not matching include filter`
1805
1809
  );
1806
1810
 
1807
1811
  return true;
@@ -1815,9 +1819,7 @@ class MetadataType {
1815
1819
  );
1816
1820
  if (excludeByDefinition || excludeByConfig) {
1817
1821
  Util.logger.debug(
1818
- `Filtered ${this.definition.type} '${
1819
- metadataEntry[this.definition.nameField]
1820
- }' (${metadataEntry[this.definition.keyField]}): matching exclude filter`
1822
+ `Filtered ${Util.getTypeKeyName(this.definition, metadataEntry)}: matching exclude filter`
1821
1823
  );
1822
1824
  return true;
1823
1825
  }
@@ -1846,11 +1848,10 @@ class MetadataType {
1846
1848
  // r__folder_Path found
1847
1849
 
1848
1850
  if (include) {
1849
- const errorMsg = `Filtered ${this.definition.type} '${
1850
- metadataEntry[this.definition.nameField]
1851
- }' (${
1852
- metadataEntry[this.definition.keyField]
1853
- }): not matching include filter for folder`;
1851
+ const errorMsg = `Filtered ${Util.getTypeKeyName(
1852
+ this.definition,
1853
+ metadataEntry
1854
+ )}: not matching include filter for folder`;
1854
1855
  // check include-only filters (== discard rest)
1855
1856
  const includeByDefinition = this._filterFolder(
1856
1857
  this.definition.include,
@@ -1870,9 +1871,7 @@ class MetadataType {
1870
1871
  return true;
1871
1872
  }
1872
1873
  } else {
1873
- const errorMsg = `Filtered ${this.definition.type} '${
1874
- metadataEntry[this.definition.nameField]
1875
- }' (${metadataEntry[this.definition.keyField]}): matching exclude filter for folder`;
1874
+ const errorMsg = `Filtered ${Util.getTypeKeyName(this.definition, metadataEntry)}: matching exclude filter for folder`;
1876
1875
  // check exclude-only filters (== keep rest)
1877
1876
  const excludeByDefinition = this._filterFolder(
1878
1877
  this.definition.filter,
@@ -2017,8 +2016,17 @@ class MetadataType {
2017
2016
  filterCounter++;
2018
2017
  continue;
2019
2018
  }
2020
- await this.validation('retrieve', results[originalKey], retrieveDir);
2021
-
2019
+ results[originalKey] = await this.validation(
2020
+ 'retrieve',
2021
+ results[originalKey],
2022
+ retrieveDir
2023
+ );
2024
+ if (!results[originalKey]) {
2025
+ // we encountered a situation in our validation that made us want to filter this record
2026
+ delete results[originalKey];
2027
+ filterCounter++;
2028
+ continue;
2029
+ }
2022
2030
  savedResults[originalKey] = await this.saveToDisk(
2023
2031
  results,
2024
2032
  originalKey,
@@ -2321,11 +2329,16 @@ class MetadataType {
2321
2329
  );
2322
2330
 
2323
2331
  try {
2324
- await this.validation(
2332
+ metadata = await this.validation(
2325
2333
  'buildDefinition',
2326
2334
  metadata,
2327
2335
  Array.isArray(targetDir) ? targetDir[0] : targetDir
2328
2336
  );
2337
+ if (!metadata) {
2338
+ // we encountered a situation in our validation that made us want to filter this record
2339
+ return;
2340
+ }
2341
+
2329
2342
  // write to file
2330
2343
  const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
2331
2344
  for (const targetDir of targetDirArr) {
@@ -2336,9 +2349,10 @@ class MetadataType {
2336
2349
  );
2337
2350
  }
2338
2351
  Util.logger.info(
2339
- `- prepared deployment definition of ${this.definition.type}: ${
2340
- metadata[this.definition.keyField]
2341
- }`
2352
+ ` - prepared deployment definition of ${Util.getTypeKeyName(
2353
+ this.definition,
2354
+ metadata
2355
+ )}`
2342
2356
  );
2343
2357
  if (
2344
2358
  metadata.r__folder_Path &&
@@ -2346,16 +2360,15 @@ class MetadataType {
2346
2360
  metadata.r__folder_Path.startsWith('Shared Items/'))
2347
2361
  ) {
2348
2362
  Util.logger.warn(
2349
- `- ${this.definition.type} ${
2350
- metadata[this.definition.keyField]
2351
- } is in a shared folder: ${metadata.r__folder_Path}`
2363
+ Util.getMsgPrefix(this.definition, metadata) +
2364
+ ` is in a shared folder: ${metadata.r__folder_Path}`
2352
2365
  );
2353
2366
  }
2354
2367
 
2355
2368
  return { metadata: metadata, type: this.definition.type };
2356
2369
  } catch (ex) {
2357
2370
  Util.logger.error(
2358
- ` ☇ skipped ${this.definition.type} ${metadata[this.definition.keyField]}${metadata[this.definition.nameField] ? ' / ' + metadata[this.definition.nameField] : ''}: ${ex.message}`
2371
+ ` ☇ skipped ${Util.getTypeKeyName(this.definition, metadata)}: ${ex.message}`
2359
2372
  );
2360
2373
  }
2361
2374
  }
@@ -2775,9 +2788,10 @@ class MetadataType {
2775
2788
  maxKeyLength)
2776
2789
  ) {
2777
2790
  Util.logger.warn(
2778
- `Name of the item ${item[this.definition.keyField]} (${
2779
- item[this.definition.nameField]
2780
- }) is too long for a key${Util.OPTIONS.keySuffix.length ? ' (including the suffix ' + Util.OPTIONS.keySuffix + ')' : ''}. Consider renaming your item. Key will be equal first ${maxKeyLength} characters of the name`
2791
+ `Name of the item ${Util.getKeyName(
2792
+ this.definition,
2793
+ item
2794
+ )} is too long for a key${Util.OPTIONS.keySuffix.length ? ' (including the suffix ' + Util.OPTIONS.keySuffix + ')' : ''}. Consider renaming your item. Key will be equal first ${maxKeyLength} characters of the name`
2781
2795
  );
2782
2796
  }
2783
2797
  const newKey = this.getNewKey(this.definition.nameField, item, maxKeyLength);
@@ -2785,16 +2799,18 @@ class MetadataType {
2785
2799
  // add key but make sure to turn it into string or else numeric keys will be filtered later
2786
2800
  keysForDeploy.push(item[this.definition.keyField] + '');
2787
2801
  Util.logger.info(
2788
- ` - added ${this.definition.type} to fixKey queue: ${
2789
- item[this.definition.keyField]
2790
- } >> ${newKey}`
2802
+ ` - added ${this.definition.type} to fixKey queue: ${Util.getKeyName(
2803
+ this.definition,
2804
+ item
2805
+ )} >> ${newKey}`
2791
2806
  );
2792
2807
  } else {
2793
2808
  Util.logger.info(
2794
2809
  Util.getGrayMsg(
2795
- ` ☇ skipping ${this.definition.type} ${
2796
- item[this.definition.keyField]
2797
- }: key does not need to be updated`
2810
+ ` ☇ skipping ${Util.getTypeKeyName(
2811
+ this.definition,
2812
+ item
2813
+ )}: key does not need to be updated`
2798
2814
  )
2799
2815
  );
2800
2816
  }
@@ -2841,15 +2857,17 @@ class MetadataType {
2841
2857
  * Gets executed before deploying metadata
2842
2858
  *
2843
2859
  * @param {'retrieve'|'buildDefinition'|'deploy'} method used to select the right config
2844
- * @param {MetadataTypeItem | CodeExtractItem} item a single metadata item
2860
+ * @param {MetadataTypeItem | CodeExtractItem} originalItem a single metadata item
2845
2861
  * @param {string} targetDir folder where files for deployment are stored
2846
2862
  * @returns {Promise.<MetadataTypeItem | CodeExtractItem>} Promise of a single metadata item
2847
2863
  */
2848
- static async validation(method, item, targetDir) {
2849
- item = structuredClone(item);
2864
+ static async validation(method, originalItem, targetDir) {
2850
2865
  if (!this.properties.options?.validation?.[method]) {
2851
- return item;
2866
+ return originalItem;
2852
2867
  }
2868
+ // if the fix parameter was set, allow validation rules to override the metadata
2869
+ /** @type {MetadataTypeItem | CodeExtractItem} */
2870
+ let item = Util.OPTIONS.fix ? originalItem : structuredClone(originalItem);
2853
2871
  /** @type {MetadataTypeItem} */
2854
2872
  const metadataItem = item.json && item.codeArr ? item.json : item;
2855
2873
  const codeArr = item.codeArr || null;
@@ -2884,26 +2902,55 @@ class MetadataType {
2884
2902
 
2885
2903
  // run validation rules
2886
2904
  const warnings = [];
2905
+ let fixed = false;
2887
2906
  for (const rule of Object.keys(validationRules)) {
2888
2907
  if (
2889
2908
  validationConfig[rule] &&
2890
2909
  validationConfig[rule] !== 'off' &&
2891
- !this.definition.skipValidation?.[rule] &&
2892
- !(await validationRules[rule].passed())
2910
+ !this.definition.skipValidation?.[rule]
2893
2911
  ) {
2894
- if (!Util.OPTIONS.skipValidation && validationConfig[rule] === 'error') {
2895
- throw new Error(validationRules[rule].failedMsg);
2896
- } else if (Util.OPTIONS.skipValidation || validationConfig[rule] === 'warn') {
2897
- warnings.push(validationRules[rule].failedMsg);
2912
+ const mode = Util.OPTIONS.fix ? 'fix' : validationConfig[rule];
2913
+ const passed = await validationRules[rule].passed(mode);
2914
+ if (mode === 'fix') {
2915
+ // potentially fixed really
2916
+ fixed = true;
2917
+ }
2918
+ if (!passed) {
2919
+ if (mode === 'fix') {
2920
+ if (passed === false) {
2921
+ warnings.push(validationRules[rule].failedMsg);
2922
+ } else if (passed === null) {
2923
+ // ensure this item no longer exists outside of the validation rule
2924
+ item = null;
2925
+ warnings.push(
2926
+ ` ☇ filtered item via validation rule fix: ` +
2927
+ validationRules[rule].failedMsg
2928
+ );
2929
+ break;
2930
+ }
2931
+ } else if (
2932
+ !Util.OPTIONS.skipValidation &&
2933
+ validationConfig[rule] === 'error' &&
2934
+ passed === false
2935
+ ) {
2936
+ throw new Error(validationRules[rule].failedMsg);
2937
+ } else if (
2938
+ (Util.OPTIONS.skipValidation || validationConfig[rule] === 'warn') &&
2939
+ passed === false
2940
+ ) {
2941
+ warnings.push(validationRules[rule].failedMsg);
2942
+ }
2898
2943
  }
2899
2944
  }
2900
2945
  }
2901
2946
  if (warnings.length) {
2902
2947
  Util.logger.warn(
2903
- ` - ${this.definition.type} ${metadataItem[this.definition.keyField]} / ${metadataItem[this.definition.nameField]}:${warnings.length > 1 ? '\n ·' : ''} ${warnings.join('\n · ')}`
2948
+ Util.getMsgPrefix(this.definition, metadataItem) +
2949
+ `:${warnings.length > 1 ? '\n ·' : ''} ${warnings.join('\n · ')}`
2904
2950
  );
2905
2951
  }
2906
- return item;
2952
+ // only return "fixed" item if --fix is set, otherwise return the original item
2953
+ return Util.OPTIONS.fix || fixed ? item : originalItem;
2907
2954
  }
2908
2955
  }
2909
2956
 
@@ -481,10 +481,12 @@ class MobileKeyword extends MetadataType {
481
481
  *
482
482
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
483
483
  * @param {object} apiResponse varies depending on the API call
484
- * @returns {Promise.<void>} -
484
+ * @returns {Promise.<object>} apiResponse
485
485
  */
486
486
  static async postCreateTasks(metadataEntry, apiResponse) {
487
487
  await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
488
+
489
+ return apiResponse;
488
490
  }
489
491
 
490
492
  /**
@@ -492,10 +494,11 @@ class MobileKeyword extends MetadataType {
492
494
  *
493
495
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
494
496
  * @param {object} apiResponse varies depending on the API call
495
- * @returns {Promise.<void>} -
497
+ * @returns {Promise.<object>} apiResponse, potentially modified
496
498
  */
497
499
  static async postUpdateTasks(metadataEntry, apiResponse) {
498
500
  await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
501
+ return apiResponse;
499
502
  }
500
503
 
501
504
  /**
@@ -338,10 +338,12 @@ class MobileMessage extends MetadataType {
338
338
  *
339
339
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
340
340
  * @param {object} apiResponse varies depending on the API call
341
- * @returns {Promise.<void>} -
341
+ * @returns {Promise.<object>} apiResponse
342
342
  */
343
343
  static async postCreateTasks(metadataEntry, apiResponse) {
344
344
  await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
345
+
346
+ return apiResponse;
345
347
  }
346
348
 
347
349
  /**
@@ -349,10 +351,11 @@ class MobileMessage extends MetadataType {
349
351
  *
350
352
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
351
353
  * @param {object} apiResponse varies depending on the API call
352
- * @returns {Promise.<void>} -
354
+ * @returns {Promise.<object>} apiResponse, potentially modified
353
355
  */
354
356
  static async postUpdateTasks(metadataEntry, apiResponse) {
355
357
  await super.postDeployTasks_legacyApi(metadataEntry, apiResponse);
358
+ return apiResponse;
356
359
  }
357
360
 
358
361
  /**
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  import MetadataType from './MetadataType.js';
4
+ import SenderProfile from './SenderProfile.js';
4
5
  import { Util } from '../util/util.js';
5
6
  import cache from '../util/cache.js';
6
7
 
@@ -110,6 +111,8 @@ class SendClassification extends MetadataType {
110
111
  'CustomerKey',
111
112
  'ObjectID'
112
113
  );
114
+ const sp = cache.getByKey('senderProfile', metadata.r__senderProfile_key);
115
+ SenderProfile.verifySenderEmailAddresses(sp, metadata, this.definition);
113
116
  metadata.SenderProfile = {
114
117
  ObjectID: spId,
115
118
  CustomerKey: metadata.r__senderProfile_key,
@@ -173,6 +176,8 @@ class SendClassification extends MetadataType {
173
176
  'CustomerKey'
174
177
  );
175
178
  delete metadata.SenderProfile;
179
+ const sp = cache.getByKey('senderProfile', metadata.r__senderProfile_key);
180
+ SenderProfile.verifySenderEmailAddresses(sp, metadata, this.definition);
176
181
  } catch (ex) {
177
182
  Util.logger.warn(` - ${this.definition.type} ${metadata.CustomerKey}: ${ex.message}`);
178
183
  }