diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md
index 8a8030911..00debb980 100644
--- a/docs/dist/documentation.md
+++ b/docs/dist/documentation.md
@@ -6150,7 +6150,8 @@ used to ensure the program tells surrounding software that an unrecoverable erro
### Util.isTrue(attrValue) ⇒ boolean
-SFMC accepts multiple true values for Boolean attributes for which we are checking here
+SFMC accepts multiple true values for Boolean attributes for which we are checking here.
+The same problem occurs when evaluating boolean CLI flags
**Kind**: static method of [Util
](#Util)
**Returns**: boolean
- attribute value == true ? true : false
@@ -6162,7 +6163,8 @@ SFMC accepts multiple true values for Boolean attributes for which we are checki
### Util.isFalse(attrValue) ⇒ boolean
-SFMC accepts multiple false values for Boolean attributes for which we are checking here
+SFMC accepts multiple false values for Boolean attributes for which we are checking here.
+The same problem occurs when evaluating boolean CLI flags
**Kind**: static method of [Util
](#Util)
**Returns**: boolean
- attribute value == false ? true : false
@@ -6535,6 +6537,7 @@ CLI helper class
* [Cli](#Cli)
* [.initMcdevConfig()](#Cli.initMcdevConfig) ⇒ Promise.<boolean>
* [.addExtraCredential(properties)](#Cli.addExtraCredential) ⇒ Promise.<(boolean\|string)>
+ * [.postFixKeysReretrieve(type, dependentTypes)](#Cli.postFixKeysReretrieve) ⇒ Promise.<boolean>
* [.logExistingCredentials(properties)](#Cli.logExistingCredentials) ⇒ void
* [.updateCredential(properties, credName)](#Cli.updateCredential) ⇒ Promise.<boolean>
* [.getCredentialObject(properties, target, [isCredentialOnly], [allowAll])](#Cli.getCredentialObject) ⇒ Promise.<TYPE.BuObject>
@@ -6565,6 +6568,17 @@ Extends template file for properties.json
| --- | --- | --- |
| properties | TYPE.Mcdevrc
| config file's json |
+
+
+### Cli.postFixKeysReretrieve(type, dependentTypes) ⇒ Promise.<boolean>
+**Kind**: static method of [Cli
](#Cli)
+**Returns**: Promise.<boolean>
- true if user wants to continue with retrieve
+
+| Param | Type | Description |
+| --- | --- | --- |
+| type | TYPE.SupportedMetadataTypes
| limit execution to given metadata type |
+| dependentTypes | Array.<TYPE.SupportedMetadataTypes>
| types that depent on type |
+
### Cli.logExistingCredentials(properties) ⇒ void
@@ -8042,7 +8056,8 @@ used to ensure the program tells surrounding software that an unrecoverable erro
### Util.isTrue(attrValue) ⇒ boolean
-SFMC accepts multiple true values for Boolean attributes for which we are checking here
+SFMC accepts multiple true values for Boolean attributes for which we are checking here.
+The same problem occurs when evaluating boolean CLI flags
**Kind**: static method of [Util
](#Util)
**Returns**: boolean
- attribute value == true ? true : false
@@ -8054,7 +8069,8 @@ SFMC accepts multiple true values for Boolean attributes for which we are checki
### Util.isFalse(attrValue) ⇒ boolean
-SFMC accepts multiple false values for Boolean attributes for which we are checking here
+SFMC accepts multiple false values for Boolean attributes for which we are checking here.
+The same problem occurs when evaluating boolean CLI flags
**Kind**: static method of [Util
](#Util)
**Returns**: boolean
- attribute value == false ? true : false
diff --git a/lib/index.js b/lib/index.js
index 80d9b80f4..cd927a092 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -865,6 +865,7 @@ class Mcdev {
}
if (businessUnit === '*') {
+ Util.OPTIONS._multiBuExecution = true;
Util.logger.info(
`:: ${lang_present} the ${selectedType} on all BUs for all credentials`
);
@@ -916,6 +917,7 @@ class Mcdev {
}
}
if (bu === '*' && properties.credentials && properties.credentials[cred]) {
+ Util.OPTIONS._multiBuExecution = true;
Util.logger.info(`:: ${lang_present} ${selectedType} on all BUs for ${cred}`);
for (const bu in properties.credentials[cred].businessUnits) {
resultObj[cred + '/' + bu] = await this.#runOnBU(
@@ -1123,11 +1125,25 @@ class Mcdev {
actuallyFixedKeys.length === 1 ? '' : 's'
} of type ${type}`
);
- Util.logger.warn(
- ` Please manually re-retrieve the following types as your local copies might now be outdated: ${Util.getGrayMsg(
- dependentTypes.join(', ')
- )}`
- );
+ if (dependentTypes.length) {
+ Util.logger.warn(
+ `Please re-retrieve the following types as your local copies might now be outdated: ${Util.getGrayMsg(
+ dependentTypes.join(', ')
+ )}`
+ );
+ const reRetrieve = await Cli.postFixKeysReretrieve(type, dependentTypes);
+ if (reRetrieve) {
+ Util.logger.info(
+ `Retrieving latest versions of ${dependentTypes.join(', ')} from server`
+ );
+ const retriever = new Retriever(properties, buObject);
+ await retriever.retrieve(dependentTypes, null, null, false);
+ }
+ } else {
+ Util.logger.info(
+ `No dependent types found that need to be re-retrieved after fixing keys of type ${type}.`
+ );
+ }
} else {
Util.logger.warn(`No keys of type ${type} updated.`);
}
diff --git a/lib/metadataTypes/MetadataType.js b/lib/metadataTypes/MetadataType.js
index 646a30350..36f5f52bc 100644
--- a/lib/metadataTypes/MetadataType.js
+++ b/lib/metadataTypes/MetadataType.js
@@ -2054,41 +2054,46 @@ class MetadataType {
*/
static getKeysForFixing(metadataMap) {
const keysForDeploy = [];
-
- for (const item of Object.values(metadataMap)) {
- if (item[this.definition.nameField].length > this.definition.maxKeyLength) {
- Util.logger.warn(
- `Name of the item ${
- item[this.definition.keyField]
- } is too long for a key. Consider renaming your item. Key will be equal first ${
+ if (Object.keys(metadataMap).length) {
+ Util.logger.info(
+ `Searching for ${this.definition.type} keys among downloaded items that need fixing:`
+ );
+ for (const item of Object.values(metadataMap)) {
+ if (item[this.definition.nameField].length > this.definition.maxKeyLength) {
+ Util.logger.warn(
+ `Name of the item ${
+ item[this.definition.keyField]
+ } is too long for a key. Consider renaming your item. Key will be equal first ${
+ this.definition.maxKeyLength
+ } characters of the name`
+ );
+ item[this.definition.nameField] = item[this.definition.nameField].slice(
+ 0,
this.definition.maxKeyLength
- } characters of the name`
- );
- item[this.definition.nameField] = item[this.definition.nameField].slice(
- 0,
- this.definition.maxKeyLength
- );
- }
+ );
+ }
- if (
- item[this.definition.nameField] != item[this.definition.keyField] &&
- !this.definition.keyIsFixed
- ) {
- keysForDeploy.push(item[this.definition.keyField]);
- Util.logger.info(
- ` - added ${this.definition.type} to fixKey queue: ${
- item[this.definition.keyField]
- }`
- );
- } else {
- Util.logger.info(
- Util.getGrayMsg(
- ` ☇ skipping ${this.definition.type} ${
+ if (
+ item[this.definition.nameField] != item[this.definition.keyField] &&
+ !this.definition.keyIsFixed
+ ) {
+ keysForDeploy.push(item[this.definition.keyField]);
+ Util.logger.info(
+ ` - added ${this.definition.type} to fixKey queue: ${
item[this.definition.keyField]
- }: key does not need to be updated`
- )
- );
+ }`
+ );
+ } else {
+ Util.logger.info(
+ Util.getGrayMsg(
+ ` ☇ skipping ${this.definition.type} ${
+ item[this.definition.keyField]
+ }: key does not need to be updated`
+ )
+ );
+ }
}
+ Util.logger.info(`Found ${keysForDeploy.length} ${this.definition.type} keys to fix`);
}
return keysForDeploy;
}
diff --git a/lib/util/cli.js b/lib/util/cli.js
index a18927798..d16c4fc62 100644
--- a/lib/util/cli.js
+++ b/lib/util/cli.js
@@ -53,6 +53,46 @@ const Cli = {
return null;
}
},
+
+ /**
+ *
+ * @param {TYPE.SupportedMetadataTypes} type limit execution to given metadata type
+ * @param {TYPE.SupportedMetadataTypes[]} dependentTypes types that depent on type
+ * @returns {Promise.} true if user wants to continue with retrieve
+ */
+ async postFixKeysReretrieve(type, dependentTypes) {
+ if (Util.isTrue(Util.skipInteraction?.fixKeysReretrieve)) {
+ return true;
+ } else if (Util.isFalse(Util.skipInteraction?.fixKeysReretrieve)) {
+ return false;
+ } else {
+ const now = await inquirer.prompt([
+ {
+ type: 'confirm',
+ name: 'fixKeysReretrieve',
+ message: `Do you want to re-retrieve dependent types (${dependentTypes.join(
+ ', '
+ )}) now?`,
+ default: true,
+ },
+ ]);
+ if (Util.OPTIONS._multiBuExecution) {
+ const remember = await inquirer.prompt([
+ {
+ type: 'confirm',
+ name: 'rememberFixKeysReretrieve',
+ message: `Remember answer for other BUs?`,
+ default: true,
+ },
+ ]);
+ if (remember.rememberFixKeysReretrieve) {
+ Util.skipInteraction ||= {};
+ Util.skipInteraction.fixKeysReretrieve = now.fixKeysReretrieve;
+ }
+ }
+ return now.fixKeysReretrieve;
+ }
+ },
/**
* helper that logs to cli which credentials are already existing in our config file
*
diff --git a/lib/util/util.js b/lib/util/util.js
index 1e7f75501..5dc6cc1fe 100644
--- a/lib/util/util.js
+++ b/lib/util/util.js
@@ -135,22 +135,24 @@ const Util = {
process.exitCode = 1;
},
/**
- * SFMC accepts multiple true values for Boolean attributes for which we are checking here
+ * SFMC accepts multiple true values for Boolean attributes for which we are checking here.
+ * The same problem occurs when evaluating boolean CLI flags
*
* @param {*} attrValue value
* @returns {boolean} attribute value == true ? true : false
*/
isTrue(attrValue) {
- return ['true', 'TRUE', 'True', '1', 1, 'Y', true].includes(attrValue);
+ return ['true', 'TRUE', 'True', '1', 1, 'Y', 'y', true].includes(attrValue);
},
/**
- * SFMC accepts multiple false values for Boolean attributes for which we are checking here
+ * SFMC accepts multiple false values for Boolean attributes for which we are checking here.
+ * The same problem occurs when evaluating boolean CLI flags
*
* @param {*} attrValue value
* @returns {boolean} attribute value == false ? true : false
*/
isFalse(attrValue) {
- return ['false', 'FALSE', 'False', '0', 0, 'N', false].includes(attrValue);
+ return ['false', 'FALSE', 'False', '0', 0, 'N', 'n', false].includes(attrValue);
},
/**
diff --git a/test/type.automation.test.js b/test/type.automation.test.js
index 4057c62bf..c36ba3b64 100644
--- a/test/type.automation.test.js
+++ b/test/type.automation.test.js
@@ -240,7 +240,6 @@ describe('type: automation', () => {
);
return;
});
- it('Should change the key during update via --changeKeyValue');
});
describe('Templating ================', () => {
it('Should create a automation template via retrieveAsTemplate and build it', async () => {
diff --git a/test/type.dataExtension.test.js b/test/type.dataExtension.test.js
index d1a068379..e9a8a6d93 100644
--- a/test/type.dataExtension.test.js
+++ b/test/type.dataExtension.test.js
@@ -87,7 +87,6 @@ describe('type: dataExtension', () => {
);
return;
});
- it('Should change the key during update via --changeKeyValue');
it('Should rename fields');
});
describe('Templating ================', () => {
diff --git a/test/type.fileTransfer.test.js b/test/type.fileTransfer.test.js
index 63102b96e..2d33c05b7 100644
--- a/test/type.fileTransfer.test.js
+++ b/test/type.fileTransfer.test.js
@@ -76,7 +76,6 @@ describe('type: fileTransfer', () => {
);
return;
});
- it('Should change the key during update via --changeKeyValue ');
});
describe('Templating ================', () => {
it('Should create a fileTransfer template via retrieveAsTemplate and build it', async () => {
diff --git a/test/type.importFile.test.js b/test/type.importFile.test.js
index 40a62c1b9..76e9c3642 100644
--- a/test/type.importFile.test.js
+++ b/test/type.importFile.test.js
@@ -77,7 +77,6 @@ describe('type: importFile', () => {
);
return;
});
- it('Should change the key during update via --changeKeyValue ');
});
describe('Templating ================', () => {
it('Should create a importFile template via retrieveAsTemplate and build it', async () => {
diff --git a/test/type.mobileKeyword.test.js b/test/type.mobileKeyword.test.js
index 9f37bffc7..9f15adaf0 100644
--- a/test/type.mobileKeyword.test.js
+++ b/test/type.mobileKeyword.test.js
@@ -120,7 +120,6 @@ describe('type: mobileKeyword', () => {
);
return;
});
- it('Should change the key during update via --changeKeyValue');
});
describe('Templating ================', () => {
it('Should create a mobileKeyword template via retrieveAsTemplate and build it', async () => {
diff --git a/test/type.query.test.js b/test/type.query.test.js
index 5e8bcb4d8..45ca9088b 100644
--- a/test/type.query.test.js
+++ b/test/type.query.test.js
@@ -323,6 +323,7 @@ describe('type: query', () => {
});
it('Should NOT fixKeys and deploy', async () => {
// WHEN
+ handler.setOptions({ skipInteraction: { fixKeysReretrieve: false } });
const resultFixKeys = await handler.fixKeys('testInstance/testBU', 'query', [
'testExisting_query',
]);
@@ -354,8 +355,9 @@ describe('type: query', () => {
);
return;
});
- it('Should fixKeys and deploy by key', async () => {
+ it('Should fixKeys and deploy by key WITHOUT re-retrieving dependent types', async () => {
// WHEN
+ handler.setOptions({ skipInteraction: { fixKeysReretrieve: false } });
const resultFixKeys = await handler.fixKeys('testInstance/testBU', 'query', [
'testExisting_query_fixKeys',
'testExisting_query',
@@ -389,9 +391,48 @@ describe('type: query', () => {
);
return;
});
- it('Should fixKeys and deploy via --like', async () => {
+ it('Should fixKeys and deploy by key AND re-retrieve dependent types', async () => {
// WHEN
- handler.setOptions({ like: { key: 'testExisting_query_f%' } });
+ handler.setOptions({ skipInteraction: { fixKeysReretrieve: true } });
+ const resultFixKeys = await handler.fixKeys('testInstance/testBU', 'query', [
+ 'testExisting_query_fixKeys',
+ 'testExisting_query',
+ ]);
+ assert.equal(
+ resultFixKeys['testInstance/testBU'].length,
+ 1,
+ 'returned number of keys does not correspond to number of expected fixed keys'
+ );
+ assert.equal(
+ resultFixKeys['testInstance/testBU'][0],
+ 'testExisting_query_fixedKeys',
+ 'returned keys do not correspond to expected fixed keys'
+ );
+ // THEN
+ assert.equal(process.exitCode, false, 'fixKeys should not have thrown an error');
+ // confirm updated item
+ assert.deepEqual(
+ await testUtils.getActualJson('testExisting_query_fixedKeys', 'query'),
+ await testUtils.getExpectedJson('9999999', 'query', 'patch_fixKeys'),
+ 'returned metadata was not equal expected for update query'
+ );
+ expect(
+ file(testUtils.getActualFile('testExisting_query_fixedKeys', 'query', 'sql'))
+ ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'patch_fixKeys', 'sql')));
+ // check number of API calls
+ assert.equal(
+ testUtils.getAPIHistoryLength(),
+ 31,
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+ );
+ return;
+ });
+ it('Should fixKeys and deploy via --like WITHOUT re-retrieving dependent types', async () => {
+ // WHEN
+ handler.setOptions({
+ like: { key: 'testExisting_query_f%' },
+ skipInteraction: { fixKeysReretrieve: false },
+ });
const resultFixKeys = await handler.fixKeys('testInstance/testBU', 'query');
assert.equal(
resultFixKeys['testInstance/testBU'].length,
diff --git a/test/type.user.test.js b/test/type.user.test.js
index 18fc252ba..34dda9f2a 100644
--- a/test/type.user.test.js
+++ b/test/type.user.test.js
@@ -161,7 +161,6 @@ describe('type: user', () => {
);
return;
});
- it('Should change the key during update with --changeKeyValue');
});
describe('Templating ================', () => {
// it('Should create a user template via retrieveAsTemplate and build it', async () => {});