sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
171 lines (164 loc) • 8.81 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import c from 'chalk';
import { glob } from 'glob';
import sortArray from 'sort-array';
import { uxLog } from '../../../../common/utils/index.js';
import { soqlQueryTooling } from '../../../../common/utils/apiUtils.js';
import { prompts } from '../../../../common/utils/prompts.js';
import { parseXmlFile, writeXmlFile } from '../../../../common/utils/xmlUtils.js';
import { GLOB_IGNORE_PATTERNS } from '../../../../common/utils/projectUtils.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class FixV53Flexipages extends SfCommand {
static title = 'Fix profiles to add tabs that are not retrieved by SF CLI';
static description = `
## Command Behavior
**Interactively updates tab visibility settings in Salesforce profiles, addressing a common issue where tab visibilities are not correctly retrieved by \`sf project retrieve start\`.**
This command provides a user-friendly interface to manage tab settings within your profile XML files, ensuring that your local project accurately reflects the intended tab configurations in your Salesforce org.
Key functionalities:
- **Interactive Tab Selection:** Displays a multi-select menu of all available tabs in your org, allowing you to choose which tabs to update.
- **Visibility Control:** Lets you set the visibility for the selected tabs to either \`DefaultOn\` (Visible) or \`Hidden\`.
- **Profile Selection:** Presents a multi-select menu of all .profile-meta.xml files in your project, allowing you to apply the tab visibility changes to specific profiles.
- **XML Updates:** Modifies the <tabVisibilities> section of the selected profile XML files to reflect the chosen tab settings. If a tab visibility setting already exists for a selected tab, it will be updated; otherwise, a new one will be added.
- **Sorted Output:** The <tabVisibilities> in the updated profile XML files are sorted alphabetically for consistency and readability.
<details markdown="1">
<summary>Technical explanations</summary>
The command's technical implementation involves:
- **SOQL Queries (Tooling API):** It queries the \`TabDefinition\` object using \`soqlQueryTooling\` to retrieve a list of all available tabs in the target org.
- **File Discovery:** Uses \`glob\` to find all .profile-meta.xml files within the specified project path.
- **Interactive Prompts:** Leverages the \`prompts\` library to create interactive menus for selecting tabs, visibility settings, and profiles.
- **XML Parsing and Manipulation:** Uses \`parseXmlFile\` to read the content of profile XML files and \`writeXmlFile\` to write the modified content back. It manipulates the \`tabVisibilities\` array within the parsed XML to add or update tab settings.
- **Array Sorting:** Employs the \`sort-array\` library to sort the \`tabVisibilities\` alphabetically by tab name.
- **Logging:** Provides feedback to the user about which profiles have been updated and a summary of the changes.
</details>
`;
static examples = ['$ sf hardis:project:fix:profiletabs'];
static flags = {
path: Flags.string({
char: 'p',
default: process.cwd(),
description: 'Root folder',
}),
debug: Flags.boolean({
char: 'd',
default: false,
description: messages.getMessage('debugMode'),
}),
websocket: Flags.string({
description: messages.getMessage('websocket'),
}),
skipauth: Flags.boolean({
description: 'Skip authentication check when a default username is required',
}),
'target-org': requiredOrgFlagWithDeprecations,
}; // Set this to true if your command requires a project workspace; 'requiresProject' is false by default
static requiresProject = true;
pathToBrowse;
debugMode = false;
async run() {
const { flags } = await this.parse(FixV53Flexipages);
this.pathToBrowse = flags.path || process.cwd();
this.debugMode = flags.debug || false;
/* jscpd:ignore-end */
// List available tabs in org
const tabsRequest = 'SELECT Label,DurableId,Name,SobjectName FROM TabDefinition ORDER BY Label';
const tabsResult = await soqlQueryTooling(tabsRequest, flags['target-org'].getConnection());
const choices = tabsResult.records.map((tab) => {
return {
title: `${tab.Label} (${tab.Name} on SObject ${tab.SobjectName})}`,
value: tab.Name,
};
});
// Prompt tabs to add to Profiles
const promptTabsToAdd = await prompts([
{
type: 'multiselect',
name: 'tabs',
message: 'Please select the tabs you want to display or hide in Profile(s)',
description: 'Choose which tabs should be configured for profiles',
choices: choices,
},
{
type: 'select',
name: 'visibility',
message: 'Please select the flag you want the tabs to be applied on profiles you will select',
description: 'Choose the visibility setting for the selected tabs',
placeholder: 'Select visibility',
choices: [
{
title: 'Visible (DefaultOn)',
value: 'DefaultOn',
},
{
title: 'Hidden',
value: 'Hidden',
},
],
},
]);
const tabsToUpdate = promptTabsToAdd.tabs;
const visibility = promptTabsToAdd.visibility;
// Prompt profiles to user
const globPattern = this.pathToBrowse + `/**/*.profile-meta.xml`;
const profileSourceFiles = await glob(globPattern, { cwd: this.pathToBrowse, ignore: GLOB_IGNORE_PATTERNS });
const promptProfilesToUpdate = await prompts({
type: 'multiselect',
name: 'profiles',
message: 'Please select the profiles you want to update to apply tabs [' +
tabsToUpdate.join(', ') +
'] with visibility ' +
visibility,
description: 'Choose which profiles should receive the tab visibility updates',
choices: profileSourceFiles.map((profileFile) => {
return {
title: (profileFile.replace(/\\/g, '/').split('/').pop() || '').replace('.profile-meta.xml', ''),
value: profileFile,
};
}),
});
// Apply updates on Profiles
for (const profileFile of promptProfilesToUpdate.profiles) {
const profile = await parseXmlFile(profileFile);
let tabVisibilities = profile.Profile['tabVisibilities'] || [];
for (const tabName of tabsToUpdate) {
// Update existing tabVisibility
if (tabVisibilities.filter((tabVisibility) => tabVisibility.tab[0] === tabName).length > 0) {
tabVisibilities = tabVisibilities.map((tabVisibility) => {
if (tabVisibility.tab[0] === tabName) {
tabVisibility.visibility = [visibility];
}
return tabVisibility;
});
continue;
}
//Add new visibility
tabVisibilities.push({
tab: [tabName],
visibility: [visibility],
});
}
// Sort items by name
const sortedTabVisibility = sortArray(tabVisibilities.map((tabVisibility) => {
return {
key: tabVisibility.tab[0],
value: tabVisibility,
};
}), {
by: ['key'],
order: ['asc'],
}).map((sorted) => sorted.value);
profile.Profile['tabVisibilities'] = sortedTabVisibility;
// Update Profile XML File
await writeXmlFile(profileFile, profile);
uxLog("log", this, c.grey('Updated ' + profileFile));
}
// Summary
const msg = `Updated ${c.green(c.bold(promptProfilesToUpdate.profiles.length))} profiles.`;
uxLog("action", this, c.cyan(msg));
// Return an object to be displayed with --json
return { outputString: msg, updatedNumber: promptProfilesToUpdate.profiles.length };
}
}
//# sourceMappingURL=profiletabs.js.map