UNPKG

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

228 lines (223 loc) • 13 kB
import c from 'chalk'; import fs from 'fs-extra'; import * as yaml from 'js-yaml'; import { SfError } from "@salesforce/core"; import { UtilsAi } from "../aiProvider/utils.js"; import { AiProvider } from "../aiProvider/index.js"; import { uxLog, execCommand } from "../utils/index.js"; export function readMkDocsFile(mkdocsYmlFile) { const mkdocsYml = yaml.load(fs .readFileSync(mkdocsYmlFile, 'utf-8') .replace('!!python/name:materialx.emoji.twemoji', "!!python/name:material.extensions.emoji.twemoji") .replace('!!python/name:materialx.emoji.to_svg', "!!python/name:material.extensions.emoji.to_svg") .replace('!!python/name:material.extensions.emoji.twemoji', "'!!python/name:material.extensions.emoji.twemoji'") .replace('!!python/name:material.extensions.emoji.to_svg', "'!!python/name:material.extensions.emoji.to_svg'") .replace('!!python/name:pymdownx.superfences.fence_code_format', "'!!python/name:pymdownx.superfences.fence_code_format'")); if (!mkdocsYml.nav) { mkdocsYml.nav = {}; } return mkdocsYml; } export async function writeMkDocsFile(mkdocsYmlFile, mkdocsYml) { const mkdocsYmlStr = yaml .dump(mkdocsYml, { lineWidth: -1 }) .replace("!!python/name:materialx.emoji.twemoji", '!!python/name:material.extensions.emoji.twemoji') .replace("!!python/name:materialx.emoji.to_svg", '!!python/name:material.extensions.emoji.to_svg') .replace("'!!python/name:material.extensions.emoji.twemoji'", '!!python/name:material.extensions.emoji.twemoji') .replace("'!!python/name:material.extensions.emoji.to_svg'", '!!python/name:material.extensions.emoji.to_svg') .replace("'!!python/name:pymdownx.superfences.fence_code_format'", '!!python/name:pymdownx.superfences.fence_code_format'); await fs.writeFile(mkdocsYmlFile, mkdocsYmlStr); uxLog(this, c.cyan(`Updated mkdocs-material config file at ${c.green(mkdocsYmlFile)}`)); } const alreadySaid = []; export class SalesforceSetupUrlBuilder { /** * Map of metadata types to their Lightning Experience setup paths. */ static setupAreaMap = { 'ActionLinkGroupTemplate': '/lightning/setup/ActionLinkTemplates/home', 'AppMenu': '/lightning/setup/NavigationMenus/home', 'ApprovalProcess': '/lightning/setup/ApprovalProcesses/home', 'AssignmentRules': '/lightning/setup/AssignmentRules/home', 'AuthProvider': '/lightning/setup/AuthProviders/home', 'AutoResponseRules': '/lightning/setup/AutoResponseRules/home', 'ApexClass': '/lightning/setup/ApexClasses/home', 'ApexPage': '/lightning/setup/VisualforcePages/home', 'ApexTrigger': '/lightning/setup/ApexTriggers/home', 'BusinessProcess': '/lightning/setup/ObjectManager/{objectName}/BusinessProcesses/view', 'CompactLayout': '/lightning/setup/ObjectManager/{objectName}/CompactLayouts/view', 'ConnectedApp': '/lightning/setup/ConnectedApps/home', 'ContentAsset': '/lightning/setup/ContentAssets/home', 'CustomApplication': '/lightning/setup/NavigationMenus/home', 'CustomField': '/lightning/setup/ObjectManager/{objectName}/FieldsAndRelationships/{apiName}/view', 'CustomHelpMenu': '/lightning/setup/CustomHelpMenu/home', 'CustomLabel': '/lightning/setup/CustomLabels/home', 'CustomMetadata': '/lightning/setup/CustomMetadataTypes/home', 'CustomNotificationType': '/lightning/setup/CustomNotifications/home', 'CustomObject': '/lightning/setup/ObjectManager/{objectName}/Details/view', 'CustomPermission': '/lightning/setup/CustomPermissions/home', 'CustomSetting': '/lightning/setup/ObjectManager/{objectName}/Details/view', 'CustomSite': '/lightning/setup/Sites/home', 'CustomTab': '/lightning/setup/Tabs/home', 'Dashboard': '/lightning/setup/Dashboards/home', 'DashboardFolder': '/lightning/setup/DashboardFolders/home', 'DataCategoryGroup': '/lightning/setup/DataCategories/home', 'EmailServicesFunction': '/lightning/setup/EmailServices/home', 'EmailTemplate': '/lightning/setup/EmailTemplates/home', 'EntitlementTemplate': '/lightning/setup/EntitlementTemplates/home', 'EscalationRules': '/lightning/setup/EscalationRules/home', 'EventSubscription': '/lightning/setup/PlatformEvents/home', 'ExternalDataSource': '/lightning/setup/ExternalDataSources/home', 'ExternalService': '/lightning/setup/ExternalServices/home', 'FieldSet': '/lightning/setup/ObjectManager/{objectName}/FieldSets/view', 'Flexipage': '/lightning/setup/FlexiPageList/home', 'Flow': '/lightning/setup/Flows/home', 'GlobalPicklist': '/lightning/setup/Picklists/home', 'Group': '/lightning/setup/PublicGroups/home', 'HomePageLayout': '/lightning/setup/HomePageLayouts/home', 'Layout': '/lightning/setup/ObjectManager/{objectName}/PageLayouts/view', 'LightningComponentBundle': '/lightning/setup/LightningComponents/home', 'MilestoneType': '/lightning/setup/Milestones/home', 'NamedCredential': '/lightning/setup/NamedCredentials/home', 'OmniChannelSettings': '/lightning/setup/OmniChannelSettings/home', 'PermissionSet': '/lightning/setup/PermissionSets/home', 'PermissionSetGroup': '/lightning/setup/PermissionSetGroups/home', 'PlatformEvent': '/lightning/setup/PlatformEvents/home', 'Profile': '/lightning/setup/Profiles/home', 'Queue': '/lightning/setup/Queues/home', 'RecordType': '/lightning/setup/ObjectManager/{objectName}/RecordTypes/view', 'RemoteSiteSetting': '/lightning/setup/RemoteSites/home', 'Report': '/lightning/setup/Reports/home', 'ReportFolder': '/lightning/setup/ReportFolders/home', 'Role': '/lightning/setup/Roles/home', 'ServiceChannel': '/lightning/setup/ServiceChannels/home', 'SharingRules': '/lightning/setup/SharingRules/home', 'StaticResource': '/lightning/setup/StaticResources/home', 'Territory': '/lightning/setup/Territories/home', 'TerritoryModel': '/lightning/setup/TerritoryManagement/home', 'Translation': '/lightning/setup/Translations/home', 'ValidationRule': '/lightning/setup/ObjectManager/{objectName}/ValidationRules/view', 'VisualforcePage': '/lightning/setup/VisualforcePages/home', 'Workflow': '/lightning/setup/Workflow/home', // Add more metadata types if needed }; /** * Generates the setup URL for a given metadata type and API name (if required). * @param metadataType The metadata type (e.g., "CustomObject", "ApexClass"). * @param apiName The API name of the metadata (optional, e.g., "Account"). * @returns The constructed setup URL. * @throws Error if the metadata type is unsupported or the API name is missing for required types. */ static getSetupUrl(metadataType, apiName) { const pathTemplate = this.setupAreaMap[metadataType]; if (!pathTemplate) { if (!alreadySaid.includes(metadataType)) { uxLog(this, c.grey(`Unsupported metadata type for doc quick link: ${metadataType}`)); alreadySaid.push(metadataType); } return null; } let apiNameFinal = apiName + ""; let objectName = ""; if (apiName.includes(".") && apiName.split(".").length === 2) { [objectName, apiNameFinal] = apiName.split(".")[1]; } // Replace placeholders in the path template with the provided API name const urlPath = pathTemplate .replace(/\{objectName\}/g, objectName || '') .replace(/\{apiName\}/g, apiNameFinal || ''); if (urlPath.includes('{apiName}') || urlPath.includes('{objectName}')) { uxLog(this, c.grey(`Wrong replacement in ${urlPath} with values apiName:${apiNameFinal} and objectName:${objectName}`)); } return urlPath; } } export async function completeAttributesDescriptionWithAi(attributesMarkdown, objectName) { if (!attributesMarkdown) { return attributesMarkdown; } const aiCache = await UtilsAi.findAiCache("PROMPT_COMPLETE_OBJECT_ATTRIBUTES_MD", [attributesMarkdown], objectName); if (aiCache.success === true) { uxLog(this, c.grey("Used AI cache for attributes completion (set IGNORE_AI_CACHE=true to force call to AI)")); return aiCache.cacheText ? includeFromFile(aiCache.aiCacheDirFile, aiCache.cacheText) : attributesMarkdown; } if (AiProvider.isAiAvailable()) { // Invoke AI Service const prompt = AiProvider.buildPrompt("PROMPT_COMPLETE_OBJECT_ATTRIBUTES_MD", { "MARKDOWN": attributesMarkdown, "OBJECT_NAME": objectName }); const aiResponse = await AiProvider.promptAi(prompt, "PROMPT_COMPLETE_OBJECT_ATTRIBUTES_MD"); // Replace description in markdown if (aiResponse?.success) { const responseText = aiResponse.promptResponse || "No AI description available"; await UtilsAi.writeAiCache("PROMPT_COMPLETE_OBJECT_ATTRIBUTES_MD", [attributesMarkdown], objectName, responseText); attributesMarkdown = includeFromFile(aiCache.aiCacheDirFile, responseText); } } return attributesMarkdown; } export async function replaceInFile(filePath, stringToReplace, replaceWith) { const fileContent = await fs.readFile(filePath, 'utf8'); const newContent = fileContent.replaceAll(stringToReplace, replaceWith); await fs.writeFile(filePath, newContent); } export async function generateMkDocsHTML() { const mkdocsLocalOk = await installMkDocs(); if (mkdocsLocalOk) { // Generate MkDocs HTML pages with local MkDocs uxLog(this, c.cyan("Generating HTML pages with mkdocs...")); const mkdocsBuildRes = await execCommand("mkdocs build -v || python -m mkdocs build -v || py -m mkdocs build -v", this, { fail: false, output: true, debug: false }); if (mkdocsBuildRes.status !== 0) { throw new SfError('MkDocs build failed:\n' + mkdocsBuildRes.stderr + "\n" + mkdocsBuildRes.stdout); } } else { // Generate MkDocs HTML pages with Docker uxLog(this, c.cyan("Generating HTML pages with Docker...")); const mkdocsBuildRes = await execCommand("docker run --rm -v $(pwd):/docs squidfunk/mkdocs-material build -v", this, { fail: false, output: true, debug: false }); if (mkdocsBuildRes.status !== 0) { throw new SfError('MkDocs build with docker failed:\n' + mkdocsBuildRes.stderr + "\n" + mkdocsBuildRes.stdout); } } } export async function installMkDocs() { uxLog(this, c.cyan("Managing mkdocs-material local installation...")); let mkdocsLocalOk = false; const installMkDocsRes = await execCommand("pip install mkdocs-material mkdocs-exclude-search mdx_truly_sane_lists || python -m pip install mkdocs-material mkdocs-exclude-search mdx_truly_sane_lists || py -m pip install mkdocs-material mkdocs-exclude-search mdx_truly_sane_lists || python3 -m pip install mkdocs-material mkdocs-exclude-search mdx_truly_sane_lists || py3 -m pip install mkdocs-material mkdocs-exclude-search mdx_truly_sane_lists", this, { fail: false, output: true, debug: false }); if (installMkDocsRes.status === 0) { mkdocsLocalOk = true; } return mkdocsLocalOk; } export function getMetaHideLines() { return `--- hide: - path --- `; } export function includeFromFile(cacheFilePath, content) { // Detect if cacheFilePath contains a fingerprint at the end after the last "-" const fileNameWithoutExtension = cacheFilePath.substring(0, cacheFilePath.lastIndexOf(".")); const fileExtensionWithDot = cacheFilePath.substring(cacheFilePath.lastIndexOf(".")); const lastDashIndex = fileNameWithoutExtension.lastIndexOf("-"); const cacheFileFingerprint = lastDashIndex !== -1 ? fileNameWithoutExtension.substring(lastDashIndex + 1) : ""; // Check if the fingerprint is a valid number const isValidFingerprint = /^\d+$/.test(cacheFileFingerprint); if (isValidFingerprint) { // Remove the fingerprint from the cacheFilePath const cacheFilePathOverridden = fileNameWithoutExtension.substring(0, lastDashIndex) + fileExtensionWithDot; return `<!-- The following part has been generated by AI. --> <!-- If you want to override it manually, rename the cache file into ${cacheFilePathOverridden} then update it with the content you want. --> <!-- Cache file start: ${cacheFilePath} --> ${content} <!-- Cache file end: ${cacheFilePath} --> `; } else { return `<!-- The following part has been generated by AI then manually updated --> <!-- If you want AI to recalculate it again, you can delete file ${cacheFilePath} --> <!-- Cache file: ${cacheFilePath} --> ${content} <!-- Cache file end: ${cacheFilePath} -->`; } } //# sourceMappingURL=docUtils.js.map