Repository: Accenture/sfmc-devtools
Pull request #361 (2022-07-04 22:08:33)

Task/final checks 4.0.0
# PR details

## What is the purpose of this pull request? (put an "X" next to an item)




- [x] Bug fix


## What changes did you make? (Give an overview)

...

## Is there anything you'd like reviewers to focus on?

pls check commit messages

## Checklist

- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] ESLint & Prettier are not outputting errors or warnings
- [ ] README.md updated (if applicable)
- [ ] CHANGELOG.md updated
- [ ] ran `npm run docs` to update developer docs



Patch:
diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md
index d956cf58..af67e15f 100644
--- a/docs/dist/documentation.md
+++ b/docs/dist/documentation.md
@@ -2989,7 +2989,7 @@ Provides default functionality that can be overwritten by child metadata type cl
     * [.findSubType(templateDir, templateName)](#MetadataType.findSubType) ⇒ <code>Promise.&lt;string&gt;</code>
     * [.readSecondaryFolder(templateDir, typeDirArr, templateName, fileName, ex)](#MetadataType.readSecondaryFolder) ⇒ <code>object</code>
     * [.buildDefinition(templateDir, targetDir, templateName, variables)](#MetadataType.buildDefinition) ⇒ <code>Promise.&lt;TYPE.MetadataTypeMapObj&gt;</code>
-    * [.checkForErrors(ex)](#MetadataType.checkForErrors) ⇒ <code>string</code>
+    * [.checkForErrors(ex)](#MetadataType.checkForErrors) ⇒ <code>Array.&lt;string&gt;</code>
     * [.document([buObject], [metadata], [isDeploy])](#MetadataType.document) ⇒ <code>void</code>
     * [.deleteByKey(buObject, customerKey)](#MetadataType.deleteByKey) ⇒ <code>boolean</code>
     * [.postDeleteTasks(buObject, customerKey)](#MetadataType.postDeleteTasks) ⇒ <code>void</code>
@@ -3545,11 +3545,11 @@ parsing is required (for example scripts & queries)
 
 <a name="MetadataType.checkForErrors"></a>
 
-### MetadataType.checkForErrors(ex) ⇒ <code>string</code>
+### MetadataType.checkForErrors(ex) ⇒ <code>Array.&lt;string&gt;</code>
 Standardizes a check for multiple messages
 
 **Kind**: static method of [<code>MetadataType</code>](#MetadataType)  
-**Returns**: <code>string</code> - formatted Error Message  
+**Returns**: <code>Array.&lt;string&gt;</code> - formatted Error Message  
 
 | Param | Type | Description |
 | --- | --- | --- |
@@ -3768,6 +3768,7 @@ Query MetadataType
     * [.buildTemplateForNested(templateDir, targetDir, metadata, templateVariables, templateName)](#Query.buildTemplateForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
     * [.parseMetadata(metadata)](#Query.parseMetadata) ⇒ <code>TYPE.CodeExtractItem</code>
     * [.getFilesToCommit(keyArr)](#Query.getFilesToCommit) ⇒ <code>Array.&lt;string&gt;</code>
+    * [.checkForErrors(ex)](#Query.checkForErrors) ⇒ <code>Array.&lt;string&gt;</code>
 
 <a name="Query.retrieve"></a>
 
@@ -3932,6 +3933,18 @@ additionally, the documentation for dataExtension and automation should be retur
 | --- | --- | --- |
 | keyArr | <code>Array.&lt;string&gt;</code> | customerkey of the metadata |
 
+<a name="Query.checkForErrors"></a>
+
+### Query.checkForErrors(ex) ⇒ <code>Array.&lt;string&gt;</code>
+Standardizes a check for multiple messages but adds query specific filters to error texts
+
+**Kind**: static method of [<code>Query</code>](#Query)  
+**Returns**: <code>Array.&lt;string&gt;</code> - formatted Error Message  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| ex | <code>object</code> | response payload from REST API |
+
 <a name="Role"></a>
 
 ## Role ⇐ [<code>MetadataType</code>](#MetadataType)
@@ -4429,7 +4442,7 @@ CLI entry for SFMC DevTools
     * [.isTrue(attrValue)](#Util.isTrue) ⇒ <code>boolean</code>
     * [.isFalse(attrValue)](#Util.isFalse) ⇒ <code>boolean</code>
     * [._isValidType(selectedType)](#Util._isValidType) ⇒ <code>boolean</code>
-    * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ <code>Array.&lt;string&gt;</code>
+    * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code>
     * [.metadataLogger(level, type, method, payload, [source])](#Util.metadataLogger) ⇒ <code>void</code>
     * [.replaceByObject(str, obj)](#Util.replaceByObject) ⇒ <code>string</code> \| <code>object</code>
     * [.inverseGet(objs, val)](#Util.inverseGet) ⇒ <code>string</code>
@@ -4554,15 +4567,15 @@ helper for retrieve, retrieveAsTemplate and deploy
 
 | Param | Type | Description |
 | --- | --- | --- |
-| selectedType | <code>string</code> | type or type-subtype |
+| selectedType | <code>TYPE.SupportedMetadataTypes</code> | type or type-subtype |
 
 <a name="Util.getRetrieveTypeChoices"></a>
 
-### Util.getRetrieveTypeChoices() ⇒ <code>Array.&lt;string&gt;</code>
+### Util.getRetrieveTypeChoices() ⇒ <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code>
 helper for getDefaultProperties()
 
 **Kind**: static method of [<code>Util</code>](#Util)  
-**Returns**: <code>Array.&lt;string&gt;</code> - type choices  
+**Returns**: <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> - type choices  
 <a name="Util.metadataLogger"></a>
 
 ### Util.metadataLogger(level, type, method, payload, [source]) ⇒ <code>void</code>
@@ -6181,7 +6194,7 @@ Util that contains logger and simple util methods
     * [.isTrue(attrValue)](#Util.isTrue) ⇒ <code>boolean</code>
     * [.isFalse(attrValue)](#Util.isFalse) ⇒ <code>boolean</code>
     * [._isValidType(selectedType)](#Util._isValidType) ⇒ <code>boolean</code>
-    * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ <code>Array.&lt;string&gt;</code>
+    * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code>
     * [.metadataLogger(level, type, method, payload, [source])](#Util.metadataLogger) ⇒ <code>void</code>
     * [.replaceByObject(str, obj)](#Util.replaceByObject) ⇒ <code>string</code> \| <code>object</code>
     * [.inverseGet(objs, val)](#Util.inverseGet) ⇒ <code>string</code>
@@ -6306,15 +6319,15 @@ helper for retrieve, retrieveAsTemplate and deploy
 
 | Param | Type | Description |
 | --- | --- | --- |
-| selectedType | <code>string</code> | type or type-subtype |
+| selectedType | <code>TYPE.SupportedMetadataTypes</code> | type or type-subtype |
 
 <a name="Util.getRetrieveTypeChoices"></a>
 
-### Util.getRetrieveTypeChoices() ⇒ <code>Array.&lt;string&gt;</code>
+### Util.getRetrieveTypeChoices() ⇒ <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code>
 helper for getDefaultProperties()
 
 **Kind**: static method of [<code>Util</code>](#Util)  
-**Returns**: <code>Array.&lt;string&gt;</code> - type choices  
+**Returns**: <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> - type choices  
 <a name="Util.metadataLogger"></a>
 
 ### Util.metadataLogger(level, type, method, payload, [source]) ⇒ <code>void</code>
diff --git a/lib/Builder.js b/lib/Builder.js
index 885a6d17..2ebd10ec 100644
--- a/lib/Builder.js
+++ b/lib/Builder.js
@@ -103,7 +103,6 @@ saved
      * @returns {Promise.<TYPE.MultiMetadataTypeList>} -
      */
     static async buildTemplate(businessUnit, selectedType, keyArr, market) {
-        Util.logger.info('mcdev:: Build Definition from Template');
         const properties = await config.getProperties();
         if (!Util._isValidType(selectedType)) {
             return;
diff --git a/lib/Deployer.js b/lib/Deployer.js
index d5cafb4e..54ec7759 100644
--- a/lib/Deployer.js
+++ b/lib/Deployer.js
@@ -317,13 +317,11 @@ class Deployer {
         const folderMetadata = {};
         metadataTypeArr.forEach((metadataType) => {
             if (!MetadataTypeInfo[metadataType].definition.dependencies.includes('folder')) {
-                Util.logger.debug(
-                    `_createFolderDefinitions(${metadataType}) - folder not a dependency`
-                );
+                Util.logger.debug(`☇ skipping ${metadataType}: folder not a dependency`);
                 return;
             }
             if (!MetadataTypeInfo[metadataType].definition.folderType) {
-                Util.logger.debug(`_createFolderDefinitions(${metadataType}) - folderType not set`);
+                Util.logger.debug(`☇ skipping ${metadataType}: folderType not set`);
                 return;
             }
             if (
@@ -332,7 +330,7 @@ class Deployer {
                 )
             ) {
                 Util.logger.warn(
-                    `_createFolderDefinitions(${metadataType}: ${MetadataTypeInfo[metadataType].definition.folderType}) - folderType not supported for deployment`
+                    `☇ skipping ${metadataType}: folderType ${MetadataTypeInfo[metadataType].definition.folderType} not supported for deployment`
                 );
                 return;
             }
@@ -386,7 +384,7 @@ class Deployer {
         });
 
         if (i > 0) {
-            Util.logger.info(`Saving folders to deploy dir (${i}) - please wait`);
+            Util.logger.info(`- Saving folders to deploy dir (${i}) - please wait`);
 
             // await results to allow us to re-read it right after
             await MetadataTypeInfo.folder.saveResults(folderMetadata, deployDir, null);
diff --git a/lib/Retriever.js b/lib/Retriever.js
index 3322f941..67f5a964 100644
--- a/lib/Retriever.js
+++ b/lib/Retriever.js
@@ -100,10 +100,12 @@ class Retriever {
                     if (Array.isArray(result)) {
                         cache.setMetadata(
                             type,
-                            result.map((element) => element.metadata)
+                            result.filter((el) => !!el).map((element) => element.metadata)
                         );
                         if (metadataTypes.includes(type) || metadataTypes.includes(metadataType)) {
-                            retrieveChangelog[type] = result.map((element) => element.metadata);
+                            retrieveChangelog[type] = result
+                                .filter((el) => !!el)
+                                .map((element) => element.metadata);
                         }
                     } else {
                         cache.setMetadata(type, result.metadata);
diff --git a/lib/index.js b/lib/index.js
index b79fe758..901b8b3e 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -53,6 +53,7 @@ class Mcdev {
      * @returns {Promise.<TYPE.DeltaPkgItem[]>} list of changed items
      */
     static async createDeltaPkg(argv) {
+        Util.logger.info('Create Delta Package ::');
         Mcdev.setSkipInteraction(argv.skipInteraction);
         const properties = await config.getProperties();
         if (!(await config.checkProperties(properties))) {
@@ -488,6 +489,7 @@ class Mcdev {
      * @returns {Promise.<TYPE.MultiMetadataTypeList>} -
      */
     static async buildTemplate(businessUnit, selectedType, keyArr, market) {
+        Util.logger.info('mcdev:: Build Template from retrieved files');
         return Builder.buildTemplate(businessUnit, selectedType, keyArr, market);
     }
     /**
diff --git a/lib/metadataTypes/AccountUser.js b/lib/metadataTypes/AccountUser.js
index d5adbc63..2f92999d 100644
--- a/lib/metadataTypes/AccountUser.js
+++ b/lib/metadataTypes/AccountUser.js
@@ -23,7 +23,7 @@ class AccountUser extends MetadataType {
      */
     static async retrieve(retrieveDir, _, buObject, ___, key) {
         if (buObject.eid !== buObject.mid) {
-            Util.logger.info('Skipping User retrieval on non-parent BU');
+            Util.logger.info('- Skipping User retrieval on non-parent BU');
             return;
         }
         return this._retrieve(retrieveDir, buObject, key);
diff --git a/lib/metadataTypes/Automation.js b/lib/metadataTypes/Automation.js
index e188a48c..60617542 100644
--- a/lib/metadataTypes/Automation.js
+++ b/lib/metadataTypes/Automation.js
@@ -347,6 +347,7 @@ class Automation extends MetadataType {
      */
     static validateDeployMetadata(metadata) {
         let deployable = true;
+        const errors = [];
         if (metadata.steps) {
             let stepNumber = 0;
             for (const step of metadata.steps) {
@@ -358,19 +359,27 @@ class Automation extends MetadataType {
                     // check if manual deploy required. if so then log warning
                     if (this.definition.manualDeployTypes.includes(activity.r__type)) {
                         Util.logger.warn(
-                            `Automation '${metadata.name}' requires additional manual configuration: '${activity.name}' in step ${stepNumber}.${displayOrder}`
+                            `- ${this.definition.type} '${metadata.name}' requires additional manual configuration: '${activity.name}' in step ${stepNumber}.${displayOrder}`
                         );
                     }
                     // cannot deploy because it is not supported
                     else if (!this.definition.dependencies.includes(activity.r__type)) {
-                        Util.logger.error(
-                            `Automation '${metadata.name}' cannot be deployed as the following activity is not supported: '${activity.name}' in step ${stepNumber}.${displayOrder}`
+                        errors.push(
+                            `  • not supported ${activity.r__type} activity '${activity.name}' in step ${stepNumber}.${displayOrder}`
                         );
                         deployable = false;
                     }
                 }
             }
         }
+        if (!deployable) {
+            Util.logger.error(
+                `☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / '${
+                    metadata[this.definition.nameField]
+                }':`
+            );
+            errors.forEach((error) => Util.logger.error(error));
+        }
         return deployable;
     }
 
@@ -393,7 +402,7 @@ class Automation extends MetadataType {
                         schedule = this._buildSchedule(originalMetadata[key].schedule);
                     } catch (ex) {
                         Util.logger.error(
-                            `Could not create schedule for automation ${originalMetadata[key].name} to start it: ${ex.message}`
+                            `- Could not create schedule for automation ${originalMetadata[key].name} to start it: ${ex.message}`
                         );
                     }
                     if (schedule !== null) {
@@ -416,7 +425,7 @@ class Automation extends MetadataType {
                                     : schedule.RecurrenceType.slice(0, -2) +
                                       (schedule._interval > 1 ? 's' : ''));
                             Util.logger.warn(
-                                `Automation '${
+                                `- Automation '${
                                     originalMetadata[key].name
                                 }' deployed Active: runs every ${intervalString} starting ${
                                     schedule._StartDateTime.split('T').join(' ').split('.')[0]
@@ -424,12 +433,14 @@ class Automation extends MetadataType {
                             );
                         } catch (ex) {
                             Util.logger.error(
-                                `Could not start automation '${originalMetadata[key].name}': ${ex.message}`
+                                `- Could not start automation '${originalMetadata[key].name}': ${ex.message}`
                             );
                         }
                     }
                 } else {
-                    Util.logger.warn(`Automation '${originalMetadata[key].name}' deployed Paused`);
+                    Util.logger.warn(
+                        `- Automation '${originalMetadata[key].name}' deployed Paused`
+                    );
                 }
             }
             if (metadata[key].startSource) {
@@ -495,11 +506,8 @@ class Automation extends MetadataType {
                         metadata.status = 'Scheduled';
                     }
                 } catch (ex) {
-                    Util.metadataLogger(
-                        'error',
-                        this.definition.type,
-                        'parseMetadata',
-                        `${metadata.name} does not have a valid schedule setting. `
+                    Util.logger.error(
+                        `- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
                     );
                     return;
                 }
diff --git a/lib/metadataTypes/DataExtension.js b/lib/metadataTypes/DataExtension.js
index c2c888e8..517991b0 100644
--- a/lib/metadataTypes/DataExtension.js
+++ b/lib/metadataTypes/DataExtension.js
@@ -36,12 +36,13 @@ class DataExtension extends MetadataType {
         const deCreatePromises = [];
         /** @type {Promise[]} */
         const deUpdatePromises = [];
+        let filteredByPreDeploy = 0;
         for (const dataExtension in desToDeploy) {
             if (desToDeploy[dataExtension].Name.startsWith('_')) {
                 Util.logger.warn(
-                    '- Cannot Upsert Strongly Typed Data Extensions - skipping ' +
-                        desToDeploy[dataExtension].Name
+                    `☇ skipping dataExtension ${desToDeploy[dataExtension].Name}: Cannot Upsert Strongly Typed Data Extensions`
                 );
+                filteredByPreDeploy++;
                 continue;
             }
             if (
@@ -50,8 +51,9 @@ class DataExtension extends MetadataType {
             ) {
                 // this needs to be run before executing preDeployTasks()
                 Util.logger.warn(
-                    `- Cannot Create/Update a Shared Data Extension from the Child BU - skipping ${desToDeploy[dataExtension].Name}`
+                    `☇ skipping dataExtension ${desToDeploy[dataExtension].Name}: Cannot Create/Update a Shared Data Extension from the Child BU`
                 );
+                filteredByPreDeploy++;
                 continue;
             }
             if (
@@ -59,8 +61,9 @@ class DataExtension extends MetadataType {
             ) {
                 // this needs to be run before executing preDeployTasks()
                 Util.logger.warn(
-                    `- Cannot Create/Update a Synchronized Data Extension. Please use Contact Builder to maintain these - skipping ${desToDeploy[dataExtension].Name}`
+                    `☇ skipping dataExtension ${desToDeploy[dataExtension].Name}:Cannot Create/Update a Synchronized Data Extension. Please use Contact Builder to maintain these`
                 );
+                filteredByPreDeploy++;
                 continue;
             }
             try {
@@ -97,11 +100,9 @@ class DataExtension extends MetadataType {
         );
 
         const successfulResults = [...createResults, ...updateResults];
-        Util.metadataLogger(
-            'info',
-            this.definition.type,
-            'upsert',
-            `${createResults.length} of ${deCreatePromises.length} created / ${updateResults.length} of ${deUpdatePromises.length} updated`
+        Util.logger.info(
+            `${this.definition.type} upsert: ${createResults.length} of ${deCreatePromises.length} created / ${updateResults.length} of ${deUpdatePromises.length} updated` +
+                (filteredByPreDeploy > 0 ? ` / ${filteredByPreDeploy} filtered` : '')
         );
         if (successfulResults.length > 0) {
             const metadataResults = successfulResults
diff --git a/lib/metadataTypes/MetadataType.js b/lib/metadataTypes/MetadataType.js
index 6c8ab2ea..ebde3081 100644
--- a/lib/metadataTypes/MetadataType.js
+++ b/lib/metadataTypes/MetadataType.js
@@ -296,7 +296,11 @@ class MetadataType {
      * @returns {void}
      */
     static create(metadata, deployDir) {
-        Util.metadataLogger('error', this.definition.type, 'create', 'create not supported');
+        Util.logger.error(
+            `☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
+                metadata[this.definition.nameField]
+            }: create is not supported yet for ${this.definition.type}`
+        );
         return;
     }
 
@@ -308,7 +312,11 @@ class MetadataType {
      * @returns {void}
      */
     static update(metadata, metadataBefore) {
-        Util.metadataLogger('error', this.definition.type, 'update', 'update not supported');
+        Util.logger.error(
+            `☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
+                metadata[this.definition.nameField]
+            }: update is not supported yet for ${this.definition.type}`
+        );
         return;
     }
 
@@ -371,8 +379,8 @@ class MetadataType {
         }
         if (!silent) {
             Util.logger.verbose(
-                ` - skipping ${this.definition.type} '${
-                    clonedMetada[fieldName || this.definition.keyField]
+                `☇ skipping ${this.definition.type} ${clonedMetada[this.definition.keyField]} / '${
+                    clonedMetada[fieldName || this.definition.nameField]
                 }': no change detected`
             );
         }
@@ -389,6 +397,7 @@ class MetadataType {
     static async upsert(metadata, deployDir, buObject) {
         const metadataToUpdate = [];
         const metadataToCreate = [];
+        let filteredByPreDeploy = 0;
         for (const metadataKey in metadata) {
             let hasError = false;
             try {
@@ -405,9 +414,9 @@ class MetadataType {
                     hasError = true;
                     deployableMetadata = metadata[metadataKey];
                     Util.logger.error(
-                        `- skipping ${this.definition.type}: ${
-                            deployableMetadata[this.definition.nameField]
-                        } (${ex.message})`
+                        `☇ skipping ${this.definition.type} ${
+                            deployableMetadata[this.definition.keyField]
+                        } / '${deployableMetadata[this.definition.nameField]}': ${ex.message}`
                     );
                 }
                 // if preDeploy returns nothing then it cannot be deployed so skip deployment
@@ -465,6 +474,8 @@ class MetadataType {
                             metadataToCreate.push(metadata[metadataKey]);
                         }
                     }
+                } else {
+                    filteredByPreDeploy++;
                 }
             } catch (ex) {
                 Util.logger.errorStack(
@@ -494,11 +505,9 @@ class MetadataType {
             )
         ).filter((r) => r !== undefined && r !== null);
         // Logging
-        Util.metadataLogger(
-            'info',
-            this.definition.type,
-            'upsert',
-            `${createResults.length} of ${metadataToCreate.length} created / ${updateResults.length} of ${metadataToUpdate.length} updated`
+        Util.logger.info(
+            `${this.definition.type} upsert: ${createResults.length} of ${metadataToCreate.length} created / ${updateResults.length} of ${metadataToUpdate.length} updated` +
+                (filteredByPreDeploy > 0 ? ` / ${filteredByPreDeploy} filtered` : '')
         );
 
         // if Results then parse as SOAP
@@ -535,16 +544,21 @@ class MetadataType {
         try {
             const response = await this.client.rest.post(uri, metadataEntry);
             Util.logger.info(
-                `- created ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
+                `- created ${this.definition.type}: ${
+                    metadataEntry[this.definition.keyField] ||
+                    metadataEntry[this.definition.nameField]
+                } / ${metadataEntry[this.definition.nameField]}`
             );
             return response;
         } catch (ex) {
             const parsedErrors = this.checkForErrors(ex);
             Util.logger.error(
-                `- error creating ${this.definition.type}: ${
-                    metadataEntry[this.definition.keyField]
-                } (${parsedErrors})`
+                `☇ error creating ${this.definition.type} ${
+                    metadataEntry[this.definition.keyField] ||
+                    metadataEntry[this.definition.nameField]
+                } / ${metadataEntry[this.definition.nameField]}:`
             );
+            parsedErrors.forEach((msg) => Util.logger.error('  • ' + msg));
             return null;
         }
     }
@@ -569,7 +583,9 @@ class MetadataType {
 
             if (!handleOutside) {
                 Util.logger.info(
-                    `- created ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
+                    `- created ${this.definition.type}: ${
+                        metadataEntry[this.definition.keyField]
+                    } / ${metadataEntry[this.definition.nameField]}`
                 );
             }
             return response;
@@ -582,7 +598,7 @@ class MetadataType {
                     errorMsg = ex.message;
                 }
                 Util.logger.error(
-                    `- error creating ${this.definition.type} '${
+                    `☇ error creating ${this.definition.type} '${
                         metadataEntry[this.definition.keyField]
                     }': ${errorMsg}`
                 );
@@ -611,16 +627,18 @@ class MetadataType {
                 `- updated ${this.definition.type}: ${
                     metadataEntry[this.definition.keyField] ||
                     metadataEntry[this.definition.nameField]
-                }`
+                } / ${metadataEntry[this.definition.nameField]}`
             );
             return response;
         } catch (ex) {
             const parsedErrors = this.checkForErrors(ex);
             Util.logger.error(
-                `- error updating ${this.definition.type}: ${
-                    metadataEntry[this.definition.keyField]
-                } (${parsedErrors})`
+                `☇ error updating ${this.definition.type} ${
+                    metadataEntry[this.definition.keyField] ||
+                    metadataEntry[this.definition.nameField]
+                } / ${metadataEntry[this.definition.nameField]}:`
             );
+            parsedErrors.forEach((msg) => Util.logger.error('  • ' + msg));
             return null;
         }
     }
@@ -644,7 +662,9 @@ class MetadataType {
             );
             if (!handleOutside) {
                 Util.logger.info(
-                    `- updated ${this.definition.type}: ${metadataEntry[this.definition.keyField]}`
+                    `- updated ${this.definition.type}: ${
+                        metadataEntry[this.definition.keyField]
+                    } /  ${metadataEntry[this.definition.nameField]}`
                 );
             }
             return response;
@@ -657,7 +677,7 @@ class MetadataType {
                     errorMsg = ex.message;
                 }
                 Util.logger.error(
-                    `- error updating ${this.definition.type} '${
+                    `☇ error updating ${this.definition.type} '${
                         metadataEntry[this.definition.keyField]
                     }': ${errorMsg}`
                 );
@@ -1440,18 +1460,28 @@ class MetadataType {
      *  Standardizes a check for multiple messages
      *
      * @param {object} ex response payload from REST API
-     * @returns {string} formatted Error Message
+     * @returns {string[]} formatted Error Message
      */
     static checkForErrors(ex) {
         if (ex?.response?.status >= 400 && ex?.response?.status < 600) {
             const errors = [];
             if (ex.response.data.errors) {
                 for (const errMsg of ex.response.data.errors) {
-                    errors.push(errMsg.message.split('<br />').join(''));
+                    errors.push(
+                        ...errMsg.message
+                            .split('<br />')
+                            .map((el) => el.trim())
+                            .filter((el) => el)
+                    );
                 }
             } else if (ex.response.data.validationErrors) {
                 for (const errMsg of ex.response.data.validationErrors) {
-                    errors.push(errMsg.message.split('<br />').join(''));
+                    errors.push(
+                        ...errMsg.message
+                            .split('<br />')
+                            .map((el) => el.trim())
+                            .filter((el) => el)
+                    );
                 }
             } else if (ex.response.data.message) {
                 errors.push(ex.response.data.message);
@@ -1460,7 +1490,7 @@ class MetadataType {
             }
             Util.logger.debug(JSON.stringify(ex.config));
             Util.logger.debug(JSON.stringify(ex.response.data));
-            return `Errors on upserting metadata at ${ex.request.path}: ${errors.join('<br />')}`;
+            return errors;
         }
     }
 
diff --git a/lib/metadataTypes/Query.js b/lib/metadataTypes/Query.js
index 44a6a145..3f6291e1 100644
--- a/lib/metadataTypes/Query.js
+++ b/lib/metadataTypes/Query.js
@@ -332,6 +332,19 @@ class Query extends MetadataType {
             .flat();
         return fileList;
     }
+
+    /**
+     *  Standardizes a check for multiple messages but adds query specific filters to error texts
+     *
+     * @param {object} ex response payload from REST API
+     * @returns {string[]} formatted Error Message
+     */
+    static checkForErrors(ex) {
+        const errors = super.checkForErrors(ex);
+        if (errors?.length > 0) {
+            return errors.map((msg) => msg.split('Error saving the Query field.').join(''));
+        }
+    }
 }
 
 // Assign definition & cache to static attributes
diff --git a/lib/metadataTypes/Role.js b/lib/metadataTypes/Role.js
index 78e7613e..b6211288 100644
--- a/lib/metadataTypes/Role.js
+++ b/lib/metadataTypes/Role.js
@@ -26,7 +26,7 @@ class Role extends MetadataType {
             // don't run for BUs other than Parent BU
             // this check does not work during caching
             if (buObject.eid !== buObject.mid) {
-                Util.logger.info('Skipping Role retrieval on non-parent BU');
+                Util.logger.info('- Skipping Role retrieval on non-parent BU');
                 return;
             }
         }
diff --git a/lib/metadataTypes/definitions/DataExtract.definition.js b/lib/metadataTypes/definitions/DataExtract.definition.js
index 58cace1b..bf9b6989 100644
--- a/lib/metadataTypes/definitions/DataExtract.definition.js
+++ b/lib/metadataTypes/definitions/DataExtract.definition.js
@@ -34,8 +34,8 @@ module.exports = {
             template: false,
         },
         dataExtractTypeId: {
-            isCreateable: null,
-            isUpdateable: null,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
@@ -43,20 +43,20 @@ module.exports = {
             skipValidation: true,
         },
         description: {
-            isCreateable: null,
-            isUpdateable: null,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
         endDate: {
-            isCreateable: null,
-            isUpdateable: null,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
         fileSpec: {
-            isCreateable: null,
-            isUpdateable: null,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
@@ -67,8 +67,8 @@ module.exports = {
             template: true,
         },
         key: {
-            isCreateable: null,
-            isUpdateable: false,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
@@ -85,14 +85,14 @@ module.exports = {
             template: false,
         },
         name: {
-            isCreateable: null,
-            isUpdateable: false,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
         startDate: {
-            isCreateable: null,
-            isUpdateable: null,
+            isCreateable: true,
+            isUpdateable: true,
             retrieving: true,
             template: true,
         },
diff --git a/lib/util/auth.js b/lib/util/auth.js
index 46e61dd8..71fa18bd 100644
--- a/lib/util/auth.js
+++ b/lib/util/auth.js
@@ -8,7 +8,7 @@ const credentialStore = new Conf({ configName: 'sessions', clearInvalidConfig: t
 const initializedSDKs = {};
 let authfile;
 
-module.exports = {
+const Auth = {
     /**
      * For each business unit, set up base credentials to be used.
      *
@@ -39,7 +39,7 @@ module.exports = {
                     ? await File.readJson(Util.authFileName)
                     : {};
                 existingAuth[credential] = authObject;
-                await File.writeJSONToFile('./', Util.authFileName, existingAuth);
+                await File.writeJSONToFile('./', Util.authFileName.split('.json')[0], existingAuth);
                 authfile = existingAuth;
             }
         } catch (ex) {
@@ -88,9 +88,8 @@ module.exports = {
  * @param {TYPE.AuthObject} authObject credentials for specific BU
  * @returns {SDK} auth object
  */
-
-const setupSDK = (sessionKey, authObject) =>
-    new SDK(authObject, {
+function setupSDK(sessionKey, authObject) {
+    return new SDK(authObject, {
         eventHandlers: {
             onLoop: (type, req) => {
                 Util.logger.info(
@@ -113,3 +112,6 @@ const setupSDK = (sessionKey, authObject) =>
         requestAttempts: 4,
         retryOnConnectionError: true,
     });
+}
+
+module.exports = Auth;
diff --git a/lib/util/devops.js b/lib/util/devops.js
index 8a8a9ab3..80da447f 100644
--- a/lib/util/devops.js
+++ b/lib/util/devops.js
@@ -24,7 +24,6 @@ const DevOps = {
      */
     async getDeltaList(properties, range, saveToDeployDir, filterPaths) {
         const rangeUserInput = range;
-        Util.logger.info('Create Delta Package ::');
         if (filterPaths) {
             filterPaths = filterPaths.split(',').map((filePath) =>
                 path
diff --git a/lib/util/init.js b/lib/util/init.js
index 0487d06b..7c27fc15 100644
--- a/lib/util/init.js
+++ b/lib/util/init.js
@@ -264,13 +264,24 @@ const Init = {
      */
     _getMissingCredentials(properties) {
         let missingCredentials;
-        if (properties && properties.credentials) {
+        if (properties?.credentials) {
+            // reload auth file because for some reason we didnt want that in our main properties object
+            let auth;
+            try {
+                auth = File.readJsonSync(Util.authFileName);
+            } catch (ex) {
+                Util.logger.error(`${ex.code}: ${ex.message}`);
+                return;
+            }
+            // walk through config credentials and check if the matching credential in the auth file is missing something
             missingCredentials = Object.keys(properties.credentials).filter(
                 (cred) =>
-                    !properties.credentials[cred].client_id ||
-                    !properties.credentials[cred].client_secret ||
-                    !properties.credentials[cred].auth_url ||
-                    !properties.credentials[cred].account_id
+                    !auth[cred] ||
+                    !auth[cred].account_id ||
+                    properties.credentials[cred].eid != auth[cred].account_id ||
+                    !auth[cred].client_id ||
+                    !auth[cred].client_secret ||
+                    !auth[cred].auth_url
             );
         }
         return missingCredentials || [];
diff --git a/lib/util/util.js b/lib/util/util.js
index 7b62c7cb..9901f943 100644
--- a/lib/util/util.js
+++ b/lib/util/util.js
@@ -152,7 +152,7 @@ const Util = {
     /**
      * helper for retrieve, retrieveAsTemplate and deploy
      *
-     * @param {string} selectedType type or type-subtype
+     * @param {TYPE.SupportedMetadataTypes} selectedType type or type-subtype
      * @returns {boolean} type ok or not
      */
     _isValidType(selectedType) {
@@ -174,7 +174,7 @@ const Util = {
     /**
      * helper for getDefaultProperties()
      *
-     * @returns {string[]} type choices
+     * @returns {TYPE.SupportedMetadataTypes[]} type choices
      */
     getRetrieveTypeChoices() {
         const typeChoices = [];
diff --git a/package-lock.json b/package-lock.json
index 73db4f03..debc7ee4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "mcdev",
-    "version": "3.1.3",
+    "version": "4.0.0",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "mcdev",
-            "version": "3.1.3",
+            "version": "4.0.0",
             "license": "MIT",
             "dependencies": {
                 "cli-progress": "3.10.0",
diff --git a/package.json b/package.json
index eb81a9ad..2ae194c9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "mcdev",
-    "version": "3.1.3",
+    "version": "4.0.0",
     "description": "Accenture Salesforce Marketing Cloud DevTools",
     "author": "joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
     "license": "MIT",
@@ -77,6 +77,8 @@
         "prettier-eslint": "14.0.2"
     },
     "lint-staged": {
-        "*.{js,jsx,ts,tsx}": ["eslint --fix"]
+        "*.{js,jsx,ts,tsx}": [
+            "eslint --fix"
+        ]
     }
 }


Test patch:
diff --git a/test/mockRoot/.mcdevrc.json b/test/mockRoot/.mcdevrc.json
index d0e3578c..0d823781 100644
--- a/test/mockRoot/.mcdevrc.json
+++ b/test/mockRoot/.mcdevrc.json
@@ -63,5 +63,5 @@
             "triggeredSendDefinition"
         ]
     },
-    "version": "3.1.3"
+    "version": "4.0.0"
 }