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

217 lines 10.1 kB
import c from 'chalk'; import * as path from 'path'; import fs from 'fs-extra'; import { SfError } from '@salesforce/core'; import { sortCrossPlatform, uxLog } from '../utils/index.js'; import { countPackageXmlItems, parsePackageXmlFile } from '../utils/xmlUtils.js'; import { SalesforceSetupUrlBuilder } from './docUtils.js'; import { CONSTANTS } from '../../config/index.js'; import { prettifyFieldName } from '../utils/flowVisualiser/nodeFormatUtils.js'; export class DocBuilderPackageXML { static async buildIndexTable(outputPackageXmlMarkdownFiles) { const packageLines = []; const packagesForMenu = { "All manifests": "manifests.md" }; packageLines.push(...[ "## Package XML files", "", "| Package name | Description |", "| :----------- | :---------- |" ]); for (const outputPackageXmlDef of outputPackageXmlMarkdownFiles) { const metadataNb = await countPackageXmlItems(outputPackageXmlDef.path); const packageMdFile = path.basename(outputPackageXmlDef.path) + ".md"; const label = outputPackageXmlDef.name ? `Package folder: ${outputPackageXmlDef.name}` : path.basename(outputPackageXmlDef.path); const packageTableLine = `| [${label}](${packageMdFile}) (${metadataNb}) | ${outputPackageXmlDef.description} |`; packageLines.push(packageTableLine); packagesForMenu[label] = packageMdFile; } packageLines.push(""); packageLines.push("___"); packageLines.push(""); return { packageLines, packagesForMenu }; } static async generatePackageXmlMarkdown(inputFile, outputFile = null, packageXmlDefinition = null, rootSalesforceUrl = "") { // Find packageXml to parse if not defined if (inputFile == null) { inputFile = path.join(process.cwd(), "manifest", "package.xml"); if (!fs.existsSync(inputFile)) { throw new SfError("No package.xml found. You need to send the path to a package.xml file in --inputfile option"); } } // Build output file if not defined if (outputFile == null) { const packageXmlFileName = path.basename(inputFile); outputFile = path.join(process.cwd(), "docs", `${packageXmlFileName}.md`); } await fs.ensureDir(path.dirname(outputFile)); uxLog(this, `Generating markdown doc from ${inputFile} to ${outputFile}...`); // Read content const packageXmlContent = await parsePackageXmlFile(inputFile); const metadataTypes = Object.keys(packageXmlContent); metadataTypes.sort(); const nbItems = await countPackageXmlItems(inputFile); const mdLines = []; if (packageXmlDefinition && packageXmlDefinition.description) { // Header mdLines.push(...[ `## Content of ${path.basename(inputFile)}`, '', packageXmlDefinition.description, '', '<div id="jstree-container"></div>', '', `Metadatas: ${nbItems}`, '' ]); } else { // Header mdLines.push(...[ `## Content of ${path.basename(inputFile)}`, '', '<div id="jstree-container"></div>', '', `Metadatas: ${nbItems}`, '' ]); } // Generate package.xml markdown for (const metadataType of metadataTypes) { const members = packageXmlContent[metadataType]; sortCrossPlatform(members); const memberLengthLabel = members.length === 1 && members[0] === "*" ? "*" : members.length; mdLines.push(`<details><summary>${metadataType} (${memberLengthLabel})</summary>\n\n`); for (const member of members) { const memberLabel = member === "*" ? "ALL (wildcard *)" : member; const setupUrl = SalesforceSetupUrlBuilder.getSetupUrl(metadataType, member); if (setupUrl && rootSalesforceUrl) { mdLines.push(` • <a href="${rootSalesforceUrl}${setupUrl}" target="_blank">${memberLabel}</a><br/>`); } else { mdLines.push(` • ${memberLabel}<br/>`); } } mdLines.push(""); mdLines.push("</details>"); mdLines.push(""); } mdLines.push(""); // Footer mdLines.push(`_Documentation generated with [sfdx-hardis](${CONSTANTS.DOC_URL_ROOT})_`); // Write output file await fs.writeFile(outputFile, mdLines.join("\n") + "\n"); uxLog(this, c.green(`Successfully generated ${path.basename(inputFile)} documentation into ${outputFile}`)); const jsonTree = await this.generateJsonTree(metadataTypes, packageXmlContent); if (jsonTree) { const packageXmlFileName = path.basename(outputFile, ".md"); const jsonFile = `./docs/json/root-${packageXmlFileName}.json`; await fs.ensureDir(path.dirname(jsonFile)); await fs.writeFile(jsonFile, JSON.stringify(jsonTree, null, 2)); uxLog(this, c.green(`Successfully generated ${packageXmlFileName} JSON into ${jsonFile}`)); } return outputFile; } static listPackageXmlCandidates() { return [ // CI/CD package files { path: "manifest/package.xml", description: "Contains all deployable metadatas of the SFDX project" }, { path: "manifest/packageDeployOnce.xml", description: "Contains all metadatas that will never be overwritten during deployment if they are already existing in the target org" }, { path: "manifest/package-no-overwrite.xml", description: "Contains all metadatas that will never be overwritten during deployment if they are already existing in the target org" }, { path: "manifest/destructiveChanges.xml", description: "Contains all metadatas that will be deleted during deployment, in case they are existing in the target org" }, // Monitoring package files { path: "manifest/package-all-org-items.xml", description: "Contains the entire list of metadatas that are present in the monitored org (not all of them are in the git backup)" }, { path: "manifest/package-backup-items.xml", description: "Contains the list of metadatas that are in the git backup" }, { path: "manifest/package-skip-items.xml", description: "Contains the list of metadatas that are excluded from the backup.<br/>Other metadata types might be skipped using environment variable MONITORING_BACKUP_SKIP_METADATA_TYPES" }, ]; } // Generate json for display with jsTree npm library static async generateJsonTree(metadataTypes, packageXmlContent) { if (metadataTypes === "all") { metadataTypes = Object.keys(packageXmlContent); metadataTypes.sort(); } const treeElements = []; for (const metadataType of metadataTypes) { const members = packageXmlContent[metadataType] || []; sortCrossPlatform(members); const memberLengthLabel = members.length === 1 && members[0] === "*" ? "all" : members.length; const typeRoot = { text: prettifyFieldName(metadataType) + " (" + memberLengthLabel + ")", icon: memberLengthLabel !== "all" ? "fa-solid fa-folder icon-blue" : "fa-solid fa-folder icon-warning", a_attr: { href: null }, children: [], }; if (memberLengthLabel !== "all") { if (metadataType === "CustomField") { // Sort custom fields by object name DocBuilderPackageXML.createCustomFieldsTree(members, typeRoot); } else { DocBuilderPackageXML.createMembersTree(members, typeRoot); } } treeElements.push(typeRoot); } return treeElements; } static createCustomFieldsTree(members, typeRoot) { const elementsByObject = []; for (const element of members) { const objectName = element.split('.')[0]; if (!elementsByObject[objectName]) { elementsByObject[objectName] = []; } elementsByObject[objectName].push(element); } // Create object nodes and fields as children for (const objectName of Object.keys(elementsByObject)) { const objectNode = { text: objectName + " (" + elementsByObject[objectName].length + ")", icon: "fa-solid fa-folder icon-blue", a_attr: { href: null }, children: [], }; for (const element of elementsByObject[objectName]) { const subElement = { text: element, icon: "fa-solid fa-circle-check icon-success", a_attr: { href: null }, }; objectNode.children.push(subElement); } typeRoot.children.push(objectNode); } } static createMembersTree(members, typeRoot) { for (const member of members) { const subElement = { text: member, icon: "fa-solid fa-circle-check icon-success", a_attr: { href: null }, }; typeRoot.children.push(subElement); } } } //# sourceMappingURL=docBuilderPackageXml.js.map