mcdev 8.0.0 → 8.0.2

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 (65) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/coverage-base-update.yml +2 -2
  3. package/.github/workflows/coverage.yml +1 -1
  4. package/@types/lib/index.d.ts +1 -0
  5. package/@types/lib/index.d.ts.map +1 -1
  6. package/@types/lib/metadataTypes/Asset.d.ts +1 -0
  7. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  8. package/@types/lib/metadataTypes/Automation.d.ts +1 -0
  9. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  10. package/@types/lib/metadataTypes/DataExtension.d.ts +1 -0
  11. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/DataExtract.d.ts +1 -0
  13. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  14. package/@types/lib/metadataTypes/Event.d.ts +2 -0
  15. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/FileTransfer.d.ts +1 -0
  17. package/@types/lib/metadataTypes/FileTransfer.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  19. package/@types/lib/metadataTypes/ImportFile.d.ts +1 -0
  20. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  22. package/@types/lib/metadataTypes/MetadataType.d.ts +3 -1
  23. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  24. package/@types/lib/metadataTypes/MobileKeyword.d.ts +1 -0
  25. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  26. package/@types/lib/metadataTypes/Query.d.ts +1 -0
  27. package/@types/lib/metadataTypes/Query.d.ts.map +1 -1
  28. package/@types/lib/metadataTypes/Script.d.ts +1 -0
  29. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  30. package/@types/lib/util/cache.d.ts +9 -0
  31. package/@types/lib/util/cache.d.ts.map +1 -1
  32. package/@types/lib/util/file.d.ts.map +1 -1
  33. package/@types/lib/util/util.d.ts +8 -1
  34. package/@types/lib/util/util.d.ts.map +1 -1
  35. package/lib/index.js +2 -3
  36. package/lib/metadataTypes/Asset.js +29 -7
  37. package/lib/metadataTypes/Automation.js +2 -0
  38. package/lib/metadataTypes/DataExtension.js +2 -0
  39. package/lib/metadataTypes/DataExtract.js +2 -0
  40. package/lib/metadataTypes/Event.js +21 -13
  41. package/lib/metadataTypes/FileTransfer.js +2 -0
  42. package/lib/metadataTypes/Folder.js +39 -37
  43. package/lib/metadataTypes/ImportFile.js +2 -0
  44. package/lib/metadataTypes/Journey.js +2 -1
  45. package/lib/metadataTypes/MetadataType.js +20 -15
  46. package/lib/metadataTypes/MobileKeyword.js +2 -0
  47. package/lib/metadataTypes/Query.js +2 -0
  48. package/lib/metadataTypes/Script.js +2 -0
  49. package/lib/util/cache.js +22 -3
  50. package/lib/util/file.js +10 -1
  51. package/lib/util/util.js +26 -12
  52. package/package.json +10 -10
  53. package/test/mockRoot/.mcdevrc.json +1 -1
  54. package/test/resources/9999999/asset/test_coderesource_js-retrieve-expected.json +2 -4
  55. package/test/resources/9999999/asset/test_coderesource_json-retrieve-expected.json +2 -4
  56. package/test/resources/9999999/asset/test_coderesource_xml-retrieve-expected.json +2 -4
  57. package/test/resources/9999999/asset/test_interactivecontent-retrieve-expected.json +2 -4
  58. package/test/resources/9999999/asset/test_landingpage-retrieve-expected.json +2 -4
  59. package/test/resources/9999999/asset/test_microsite-retrieve-expected.json +2 -4
  60. package/test/resources/9999999/asset/v1/content/assets/9451/get-response.json +3 -3
  61. package/test/resources/9999999/asset/v1/content/assets/9456/get-response.json +3 -3
  62. package/test/resources/9999999/asset/v1/content/assets/9458/get-response.json +3 -3
  63. package/test/resources/9999999/asset/v1/content/assets/9460/get-response.json +3 -3
  64. package/test/resources/9999999/asset/v1/content/assets/9463/get-response.json +3 -3
  65. package/test/resources/9999999/asset/v1/content/assets/9465/get-response.json +3 -3
@@ -1493,12 +1493,14 @@ class DataExtension extends MetadataType {
1493
1493
  /**
1494
1494
  * Retrieves dataExtension metadata in template format.
1495
1495
  *
1496
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
1496
1497
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
1497
1498
  * @param {string} name name of the metadata item
1498
1499
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
1499
1500
  * @returns {Promise.<{metadata: DataExtensionItem, type: string}>} Promise of items
1500
1501
  */
1501
1502
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
1503
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
1502
1504
  /** @type {SoapRequestParams} */
1503
1505
  const options = {
1504
1506
  filter: {
@@ -50,12 +50,14 @@ class DataExtract extends MetadataType {
50
50
  /**
51
51
  * Retrieve a specific dataExtract Definition by Name
52
52
  *
53
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
53
54
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
54
55
  * @param {string} name name of the metadata file
55
56
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
56
57
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
57
58
  */
58
59
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
60
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
59
61
  const res = await this.client.rest.get(
60
62
  '/automation/v1/dataextracts/?$filter=name%20eq%20' + encodeURIComponent(name)
61
63
  );
@@ -33,6 +33,7 @@ import pLimit from 'p-limit';
33
33
  */
34
34
  class Event extends MetadataType {
35
35
  static reCacheDataExtensions = [];
36
+ static createdKeys = [];
36
37
 
37
38
  /**
38
39
  * Retrieves Metadata of Event Definition.
@@ -81,12 +82,14 @@ class Event extends MetadataType {
81
82
  /**
82
83
  * Retrieve a specific Event Definition by Name
83
84
  *
85
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
84
86
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
85
87
  * @param {string} name name of the metadata file
86
88
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
87
89
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
88
90
  */
89
91
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
92
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
90
93
  const res = await this.client.rest.get(
91
94
  '/interaction/v1/eventDefinitions?name=' + encodeURIComponent(name)
92
95
  );
@@ -158,7 +161,10 @@ class Event extends MetadataType {
158
161
  */
159
162
  static async deploy(metadata, deployDir, retrieveDir) {
160
163
  Util.logBeta(this.definition.type);
161
- return super.deploy(metadata, deployDir, retrieveDir);
164
+ const metadataMap = await super.deploy(metadata, deployDir, retrieveDir);
165
+
166
+ this.createdKeys.length = 0; // reset createdKeys after deploy to ensure it's not used in future retrieves
167
+ return metadataMap;
162
168
  }
163
169
 
164
170
  /**
@@ -168,6 +174,7 @@ class Event extends MetadataType {
168
174
  * @returns {Promise} Promise
169
175
  */
170
176
  static create(metadata) {
177
+ this.createdKeys.push(metadata[this.definition.keyField]);
171
178
  return super.createREST(metadata, '/interaction/v1/eventDefinitions/');
172
179
  }
173
180
 
@@ -215,8 +222,7 @@ class Event extends MetadataType {
215
222
  break;
216
223
  }
217
224
  case 'SalesforceObjectTriggerV2': {
218
- metadata.iconUrl ||=
219
- '/events/js/salesforce-event/events/NetworkMember/images/SF-Event-Icon.svg';
225
+ metadata.iconUrl ||= '/events/js/salesforce-event/images/SF-Event-Icon.svg';
220
226
  break;
221
227
  }
222
228
  case 'AutomationAudience': {
@@ -601,16 +607,18 @@ class Event extends MetadataType {
601
607
  if (metadata.mode === 'Production') {
602
608
  delete metadata.mode;
603
609
  }
604
- if (metadata.interactionCount === 0 && metadata.publishedInteractionCount === 0) {
605
- Util.logger.warn(
606
- ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is no longer used and could therefore be deleted. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
607
- );
608
- } else if (metadata.publishedInteractionCount === 0) {
609
- Util.logger.info(
610
- Util.getGrayMsg(
611
- ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is currently inactive. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
612
- )
613
- );
610
+ if (!this.createdKeys.includes(metadata[this.definition.keyField])) {
611
+ if (metadata.interactionCount === 0 && metadata.publishedInteractionCount === 0) {
612
+ Util.logger.warn(
613
+ ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is not used and could therefore be deleted. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
614
+ );
615
+ } else if (metadata.publishedInteractionCount === 0) {
616
+ Util.logger.info(
617
+ Util.getGrayMsg(
618
+ ` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${metadata[this.definition.keyField]}): is currently inactive. Associated Journeys: ${metadata.interactionCount}. Active Journeys: ${metadata.publishedInteractionCount}.`
619
+ )
620
+ );
621
+ }
614
622
  }
615
623
 
616
624
  try {
@@ -50,12 +50,14 @@ class FileTransfer extends MetadataType {
50
50
  /**
51
51
  * Retrieve a specific File Transfer Definition by Name
52
52
  *
53
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
53
54
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
54
55
  * @param {string} name name of the metadata file
55
56
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
56
57
  * @returns {Promise.<MetadataTypeItemObj>} Promise
57
58
  */
58
59
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
60
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
59
61
  const res = await this.client.rest.get(
60
62
  '/automation/v1/filetransfers/?$filter=name%20eq%20' + encodeURIComponent(name)
61
63
  );
@@ -101,17 +101,21 @@ class Folder extends MetadataType {
101
101
  idMap[id].Path = idMap[id].Name;
102
102
  }
103
103
  } else {
104
- Util.logger.warn(
105
- ` - Skipping folder ${idMap[id].Name} (${id}, type: ${idMap[id].ContentType}): Cannot find parent folder (${idMap[id].ParentFolder.ID})`
104
+ Util.logger.info(
105
+ Util.getGrayMsg(
106
+ ` ☇ skipping folder ${idMap[id].Name} (${id}, type: ${idMap[id].ContentType}): Cannot find parent folder (${idMap[id].ParentFolder.ID})`
107
+ )
106
108
  );
109
+ delete idMap[id];
107
110
  }
108
111
  }
109
112
  // All folders except the artificial root have ParentFolder attribute. If they dont something else is wrong
110
113
  else if (idMap[id].Name !== '<ROOT>') {
111
114
  idMap[id].Path = '';
112
115
  Util.logger.warn(
113
- ` - Skipping folder ${idMap[id].Name} (${id}, type: ${idMap[id].ContentType}): Does not have a parent folder`
116
+ ` skipping folder ${idMap[id].Name} (${id}, type: ${idMap[id].ContentType}): Does not have a parent folder`
114
117
  );
118
+ delete idMap[id];
115
119
  }
116
120
  }
117
121
 
@@ -220,25 +224,11 @@ class Folder extends MetadataType {
220
224
  let existingId;
221
225
  try {
222
226
  // perform a secondary check based on path
223
- existingId = cache.searchForField(
224
- 'folder',
225
- deployableMetadata.Path,
226
- 'Path',
227
- 'ID',
228
- undefined,
229
- true
230
- );
231
- const cachedVersion = cache.getByKey(
232
- 'folder',
233
- cache.searchForField(
234
- 'folder',
235
- deployableMetadata.Path,
236
- 'Path',
237
- this.definition.keyField,
238
- undefined,
239
- true
240
- )
227
+
228
+ const cachedVersion = cache.getFolderByPath(
229
+ deployableMetadata.Path
241
230
  );
231
+ existingId = cachedVersion?.ID;
242
232
  if (
243
233
  existingId &&
244
234
  !this.hasChangedGeneric(
@@ -374,6 +364,7 @@ class Folder extends MetadataType {
374
364
  return {};
375
365
  }
376
366
  const path = metadataEntry.Path;
367
+ let apiTypeSoap = false;
377
368
  try {
378
369
  if (this.definition.deployFolderTypesEmailRest.includes(metadataEntry.ContentType)) {
379
370
  // * The SOAP endpoint for creating folders does not support folders for automations nor journeys. The Rest endpoint on the other hand errors out on certain characters in the folder names that are actually valid. We therefore only use Rest for the folder types that are not supported by SOAP.
@@ -448,6 +439,7 @@ class Folder extends MetadataType {
448
439
  throw new Error(JSON.stringify(response));
449
440
  }
450
441
  } else {
442
+ apiTypeSoap = true;
451
443
  const response = await super.createSOAP(metadataEntry, true);
452
444
  if (response) {
453
445
  // set the client ID to ensure we can find the newly created folders as parents for folders created afterwards inside of it
@@ -464,14 +456,18 @@ class Folder extends MetadataType {
464
456
  }
465
457
  }
466
458
  } catch (ex) {
467
- if (ex?.results) {
468
- Util.logger.error(
469
- `${this.definition.type}-${metadataEntry.ContentType}.create:: error creating: '${path}'. ${ex.results[0].StatusMessage}`
470
- );
471
- } else if (ex) {
472
- Util.logger.error(
473
- `${this.definition.type}-${metadataEntry.ContentType}.create:: error creating: '${path}'. ${ex.message}`
474
- );
459
+ if (apiTypeSoap) {
460
+ this._handleSOAPErrors(ex, 'creating', metadataEntry, false, 'Name');
461
+ } else {
462
+ if (ex?.results) {
463
+ Util.logger.error(
464
+ `${this.definition.type}-${metadataEntry.ContentType}.create:: error creating: '${path}'. ${ex.results[0].StatusMessage}`
465
+ );
466
+ } else if (ex) {
467
+ Util.logger.error(
468
+ `${this.definition.type}-${metadataEntry.ContentType}.create:: error creating: '${path}'. ${ex.message}`
469
+ );
470
+ }
475
471
  }
476
472
  }
477
473
  }
@@ -490,6 +486,7 @@ class Folder extends MetadataType {
490
486
  return {};
491
487
  }
492
488
  const path = metadataEntry.Path;
489
+ let apiTypeSoap = false;
493
490
  try {
494
491
  let response;
495
492
  if (this.definition.deployFolderTypesAssetRest.includes(metadataEntry.ContentType)) {
@@ -531,6 +528,7 @@ class Folder extends MetadataType {
531
528
  throw new Error(JSON.stringify(response));
532
529
  }
533
530
  } else {
531
+ apiTypeSoap = true;
534
532
  response = await super.updateSOAP(metadataEntry, true);
535
533
  if (response.Results?.[0]?.StatusCode === 'OK') {
536
534
  response.Results[0].Object = metadataEntry;
@@ -541,14 +539,18 @@ class Folder extends MetadataType {
541
539
  }
542
540
  }
543
541
  } catch (ex) {
544
- if (ex?.results) {
545
- Util.logger.error(
546
- `${this.definition.type}-${metadataEntry.ContentType}.update:: error updating: '${path}'. ${ex.results[0].StatusMessage}`
547
- );
548
- } else if (ex) {
549
- Util.logger.error(
550
- `${this.definition.type}-${metadataEntry.ContentType}.update:: error updating: '${path}'. ${ex.message}`
551
- );
542
+ if (apiTypeSoap) {
543
+ this._handleSOAPErrors(ex, 'updating', metadataEntry, false, 'Name');
544
+ } else {
545
+ if (ex?.results) {
546
+ Util.logger.error(
547
+ `${this.definition.type}-${metadataEntry.ContentType}.update:: error updating: '${path}'. ${ex.results[0].StatusMessage}`
548
+ );
549
+ } else if (ex) {
550
+ Util.logger.error(
551
+ `${this.definition.type}-${metadataEntry.ContentType}.update:: error updating: '${path}'. ${ex.message}`
552
+ );
553
+ }
552
554
  }
553
555
  }
554
556
  }
@@ -133,12 +133,14 @@ class ImportFile extends MetadataType {
133
133
  /**
134
134
  * Retrieve a specific Import Definition by Name
135
135
  *
136
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
136
137
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
137
138
  * @param {string} name name of the metadata file
138
139
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
139
140
  * @returns {Promise.<MetadataTypeItemObj>} Promise
140
141
  */
141
142
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
143
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
142
144
  // using '?$filter=name%20eq%20' + encodeURIComponent(name) would also work but that just retrieves more data for no reason
143
145
  const cache = await this.retrieveForCache(null, null, 'name:' + name);
144
146
  const metadataArr = Object.values(cache?.metadata);
@@ -182,7 +182,8 @@ class Journey extends MetadataType {
182
182
  // if the interaction does not exist, the API returns an error code which would otherwise bring execution to a hold
183
183
  if (
184
184
  [
185
- 'Interaction matching key not found.',
185
+ 'Interaction matching key not found.', // might be outdated
186
+ 'Interaction matching criteria not found.', // seen in 2025-05
186
187
  'Must provide a valid ID or Key parameter',
187
188
  ].includes(ex.message) ||
188
189
  (key && ex.code === 'ERR_BAD_REQUEST')
@@ -299,7 +299,7 @@ class MetadataType {
299
299
  * @returns {Promise.<MetadataTypeMapObj>} metadata
300
300
  */
301
301
  static async retrieve(retrieveDir, additionalFields, subTypeArr, key) {
302
- Util.notSupportedError(this.definition, 'retrieve');
302
+ Util.logNotSupported(this.definition, 'retrieve');
303
303
  return { metadata: {}, type: this.definition.type };
304
304
  }
305
305
 
@@ -329,6 +329,7 @@ class MetadataType {
329
329
  /**
330
330
  * Gets metadata cache with limited fields and does not store value to disk
331
331
  *
332
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
332
333
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
333
334
  * @param {string} name name of the metadata file
334
335
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
@@ -336,7 +337,8 @@ class MetadataType {
336
337
  * @returns {Promise.<MetadataTypeItemObj>} metadata
337
338
  */
338
339
  static async retrieveAsTemplate(templateDir, name, templateVariables, subType) {
339
- Util.notSupportedError(this.definition, 'retrieveAsTemplate');
340
+ Util.logNotSupported(this.definition, 'retrieveAsTemplate');
341
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
340
342
  return { metadata: null, type: this.definition.type };
341
343
  }
342
344
 
@@ -499,7 +501,7 @@ class MetadataType {
499
501
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
500
502
  */
501
503
  static async create(metadata, deployDir) {
502
- Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
504
+ Util.logNotSupported(this.definition, 'create', metadata);
503
505
  return;
504
506
  }
505
507
 
@@ -511,7 +513,7 @@ class MetadataType {
511
513
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
512
514
  */
513
515
  static async update(metadata, metadataBefore) {
514
- Util.notSupportedError(this.definition, 'retrieveAsTemplate', metadata);
516
+ Util.logNotSupported(this.definition, 'update', metadata);
515
517
  return;
516
518
  }
517
519
 
@@ -524,7 +526,7 @@ class MetadataType {
524
526
  * @returns {Promise.<string[]>} Returns list of keys that were refreshed
525
527
  */
526
528
  static async refresh(keyArr, checkKey = true, upsertResults) {
527
- Util.notSupportedError(this.definition, 'refresh');
529
+ Util.logNotSupported(this.definition, 'refresh');
528
530
  return [];
529
531
  }
530
532
 
@@ -630,7 +632,7 @@ class MetadataType {
630
632
  * @returns {Promise.<MetadataTypeItem | CodeExtractItem>} key of the item that was updated
631
633
  */
632
634
  static async replaceCbReference(item, retrieveDir, findAssetKeys) {
633
- Util.notSupportedError(this.definition, 'replaceCbReference');
635
+ Util.logNotSupported(this.definition, 'replaceCbReference');
634
636
  return [];
635
637
  }
636
638
 
@@ -642,7 +644,7 @@ class MetadataType {
642
644
  * @returns {Promise.<string[]>} Returns list of keys that were executed
643
645
  */
644
646
  static async execute(keyArr, cache) {
645
- Util.notSupportedError(this.definition, 'execute');
647
+ Util.logNotSupported(this.definition, 'execute');
646
648
  return [];
647
649
  }
648
650
 
@@ -654,7 +656,7 @@ class MetadataType {
654
656
  * @returns {Promise.<string[]>} Returns list of keys that were executed
655
657
  */
656
658
  static async schedule(keyArr, cache) {
657
- Util.notSupportedError(this.definition, 'schedule');
659
+ Util.logNotSupported(this.definition, 'schedule');
658
660
  return [];
659
661
  }
660
662
 
@@ -666,7 +668,7 @@ class MetadataType {
666
668
  * @returns {Promise.<string[]>} Returns list of keys that were paused
667
669
  */
668
670
  static async pause(keyArr, cache) {
669
- Util.notSupportedError(this.definition, 'pause');
671
+ Util.logNotSupported(this.definition, 'pause');
670
672
  return [];
671
673
  }
672
674
 
@@ -677,7 +679,7 @@ class MetadataType {
677
679
  * @returns {Promise.<string[]>} Returns list of keys that were stopped
678
680
  */
679
681
  static async stop(keyArr) {
680
- Util.notSupportedError(this.definition, 'stop');
682
+ Util.logNotSupported(this.definition, 'stop');
681
683
  return [];
682
684
  }
683
685
 
@@ -1354,13 +1356,16 @@ class MetadataType {
1354
1356
  * @param {'creating'|'updating'|'retrieving'|'executing'|'pausing'} msg what to print in the log
1355
1357
  * @param {MetadataTypeItem} [metadataEntry] single metadata entry
1356
1358
  * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
1359
+ * @param {string} [nameAttribute] name attribute to use in the error message instead of keyField
1357
1360
  */
1358
- static _handleSOAPErrors(ex, msg, metadataEntry, handleOutside) {
1361
+ static _handleSOAPErrors(ex, msg, metadataEntry, handleOutside, nameAttribute) {
1359
1362
  if (handleOutside) {
1360
1363
  throw ex;
1361
1364
  } else {
1362
1365
  const errorMsg = this.getSOAPErrorMsg(ex);
1363
- const name = metadataEntry ? ` '${metadataEntry[this.definition.keyField]}'` : '';
1366
+ const name = metadataEntry
1367
+ ? ` '${metadataEntry[nameAttribute || this.definition.keyField]}'`
1368
+ : '';
1364
1369
  Util.logger.error(` ☇ error ${msg} ${this.definition.type}${name}: ${errorMsg}`);
1365
1370
  }
1366
1371
  }
@@ -2464,7 +2469,7 @@ class MetadataType {
2464
2469
  */
2465
2470
  static document(metadata, isDeploy) {
2466
2471
  if (!isDeploy) {
2467
- Util.notSupportedError(this.definition, 'document');
2472
+ Util.logNotSupported(this.definition, 'document');
2468
2473
  }
2469
2474
  }
2470
2475
 
@@ -2475,7 +2480,7 @@ class MetadataType {
2475
2480
  * @returns {Promise.<{key:string, name:string}>} key, name and path of metadata; null if not found
2476
2481
  */
2477
2482
  static async resolveId(id) {
2478
- Util.notSupportedError(this.definition, 'resolveId');
2483
+ Util.logNotSupported(this.definition, 'resolveId');
2479
2484
  return;
2480
2485
  }
2481
2486
 
@@ -2486,7 +2491,7 @@ class MetadataType {
2486
2491
  * @returns {Promise.<boolean>} deletion success status
2487
2492
  */
2488
2493
  static async deleteByKey(customerKey) {
2489
- Util.notSupportedError(this.definition, 'delete');
2494
+ Util.logNotSupported(this.definition, 'delete');
2490
2495
  return false;
2491
2496
  }
2492
2497
 
@@ -137,12 +137,14 @@ class MobileKeyword extends MetadataType {
137
137
  /**
138
138
  * retrieve an item and create a template from it
139
139
  *
140
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
140
141
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
141
142
  * @param {string} key name of the metadata file
142
143
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
143
144
  * @returns {Promise.<MetadataTypeItemObj>} Promise of metadata
144
145
  */
145
146
  static async retrieveAsTemplate(templateDir, key, templateVariables) {
147
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
146
148
  try {
147
149
  let queryParams;
148
150
  [key, queryParams] = this.#getRetrieveKeyAndUrl(key);
@@ -127,12 +127,14 @@ class Query extends MetadataType {
127
127
  /**
128
128
  * Retrieve a specific Query by Name
129
129
  *
130
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
130
131
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
131
132
  * @param {string} name name of the metadata file
132
133
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
133
134
  * @returns {Promise.<{metadata: Query, type: string}>} Promise of metadata
134
135
  */
135
136
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
137
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
136
138
  await File.initPrettier('sql');
137
139
  return super.retrieveREST(
138
140
  templateDir,
@@ -57,12 +57,14 @@ class Script extends MetadataType {
57
57
  /**
58
58
  * Retrieve a specific Script by Name
59
59
  *
60
+ * @deprecated Use `retrieve` followed by `build` instead. `retrieveAsTemplate` will be removed in a future version.
60
61
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
61
62
  * @param {string} name name of the metadata file
62
63
  * @param {TemplateMap} templateVariables variables to be replaced in the metadata
63
64
  * @returns {Promise.<{metadata: ScriptItem, type: string}>} Promise
64
65
  */
65
66
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
67
+ Util.logDeprecated('retrieveAsTemplate', `'retrieve' followed by 'build'`);
66
68
  await File.initPrettier('ssjs');
67
69
  return super.retrieveREST(
68
70
  templateDir,
package/lib/util/cache.js CHANGED
@@ -186,26 +186,45 @@ export default {
186
186
  * @returns {number} folder ID
187
187
  */
188
188
  getFolderId(r__folder_Path, overrideMID, allowOtherBu = true) {
189
+ const folder = this.getFolderByPath(r__folder_Path, overrideMID, allowOtherBu);
190
+ return folder?.ID;
191
+ },
192
+
193
+ /**
194
+ * helper for setFolderId
195
+ *
196
+ * @param {string} r__folder_Path folder path value
197
+ * @param {number} [overrideMID] ignore currentMID and use alternative (for example parent MID)
198
+ * @param {boolean} allowOtherBu getting folder from other BU; FALSE for folder parent search
199
+ * @returns {object} folder item
200
+ */
201
+ getFolderByPath(r__folder_Path, overrideMID, allowOtherBu = true) {
189
202
  if (!r__folder_Path) {
190
203
  throw new Error('r__folder_Path not set');
191
204
  }
205
+
192
206
  /** @type {ListMap} */
193
207
  const folderMap = dataStore[overrideMID || currentMID]?.['folder'];
194
208
  if (!folderMap) {
195
209
  throw new Error('No folders found in cache');
196
210
  }
211
+ const folderPath_lower = r__folder_Path.toLowerCase();
197
212
  const potentialFolders = [];
198
213
  for (const folder of Object.values(folderMap)) {
199
- if (folder.Path === r__folder_Path) {
214
+ if (!folder?.Path) {
215
+ continue; // skip folders without Path
216
+ }
217
+ if (folder.Path.toLowerCase() === folderPath_lower) {
200
218
  if (folder?.Client?.ID === (overrideMID || currentMID)) {
201
- return folder.ID;
219
+ return folder;
202
220
  } else if (allowOtherBu) {
203
221
  potentialFolders.push(folder);
204
222
  }
205
223
  }
206
224
  }
225
+
207
226
  if (potentialFolders.length >= 1) {
208
- return potentialFolders[0].ID;
227
+ return potentialFolders[0];
209
228
  } else {
210
229
  throw new Error(`No folders found with path ${r__folder_Path}`);
211
230
  }
package/lib/util/file.js CHANGED
@@ -571,6 +571,15 @@ const File = {
571
571
  * @returns {Promise.<boolean>} success of config load
572
572
  */
573
573
  async initPrettier(filetype = 'html') {
574
+ if (Util.OPTIONS.format === false) {
575
+ // auto-formatting was disabled via CLI option
576
+ return;
577
+ }
578
+ const properties = await config.getProperties();
579
+ if (Util.OPTIONS.format !== true && properties.options.formatOnSave === false) {
580
+ // auto-formatting was disabled via config file
581
+ return;
582
+ }
574
583
  if (FileFs.prettierConfig === null || FileFs.prettierConfigFileType !== filetype) {
575
584
  // run this if no config was yet found or if the filetype previously used to initialize it differs (because it results in a potentially different config!)
576
585
  FileFs.prettierConfigFileType = filetype;
@@ -591,7 +600,7 @@ const File = {
591
600
  return true;
592
601
  } catch (ex) {
593
602
  FileFs.prettierConfig = false;
594
- Util.logger.error('Cannot apply auto-formatting to your code:' + ex.message);
603
+ Util.logger.error('Cannot apply auto-formatting to your code: ' + ex.message);
595
604
  return false;
596
605
  }
597
606
  } else {
package/lib/util/util.js CHANGED
@@ -904,6 +904,32 @@ export const Util = {
904
904
  ` - 🚧 ${type} support is currently still in beta. Please report any issues here: https://github.com/Accenture/sfmc-devtools/issues/new/choose`
905
905
  );
906
906
  },
907
+
908
+ /**
909
+ * outputs a warning that the given method is deprecated
910
+ *
911
+ * @param {string} method name of the method
912
+ * @param {string} [useInstead] optionally specify which method to use instead
913
+ */
914
+ logDeprecated(method, useInstead = '') {
915
+ Util.logger.warn(
916
+ ` 💥 '${method}' is deprecated and will be sunset in a future version. ${useInstead ? `Please use ${useInstead} instead.` : ''}`
917
+ );
918
+ },
919
+
920
+ /**
921
+ * helper for MetadataType class to issue a similar error message for unsupported methods
922
+ *
923
+ * @param {any} definition type definition object
924
+ * @param {string} method name of the method thats not supported
925
+ * @param {MetadataTypeItem} [item] metadata item
926
+ */
927
+ logNotSupported: function (definition, method, item) {
928
+ Util.logger.error(
929
+ ` ☇ skipping ${item ? Util.getTypeKeyName(definition, item) : definition.type}: ${method} is not supported for ${definition.type}`
930
+ );
931
+ },
932
+
907
933
  // defined colors for logging things in different colors
908
934
  color: {
909
935
  reset: '\x1B[0m',
@@ -1317,18 +1343,6 @@ export const Util = {
1317
1343
  return acc;
1318
1344
  }, {});
1319
1345
  },
1320
- /**
1321
- * helper for MetadataType class to issue a similar error message for unsupported methods
1322
- *
1323
- * @param {any} definition type definition object
1324
- * @param {string} method name of the method thats not supported
1325
- * @param {MetadataTypeItem} [item] metadata item
1326
- */
1327
- notSupportedError: function (definition, method, item) {
1328
- Util.logger.error(
1329
- ` ☇ skipping ${item ? Util.getTypeKeyName(definition, item) : definition.type}: ${method} is not supported yet for ${definition.type}`
1330
- );
1331
- },
1332
1346
  };
1333
1347
 
1334
1348
  Util.startLogger(false, true);