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
152 lines (145 loc) • 8.76 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags, optionalOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
import c from "chalk";
import * as path from "path";
import fs from "fs-extra";
import { Messages } from '@salesforce/core';
import { WebSocketClient } from '../../../common/websocketClient.js';
import { isCI, uxLog } from '../../../common/utils/index.js';
import { MetadataUtils } from '../../../common/metadata-utils/index.js';
import { generateFlowMarkdownFile, generateHistoryDiffMarkdown, generateMarkdownFileWithMermaid } from '../../../common/utils/mermaidUtils.js';
import { CONSTANTS } from '../../../config/index.js';
import { setConnectionVariables } from '../../../common/utils/orgUtils.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class Flow2Markdown extends SfCommand {
static title = 'Flow to Markdown';
static description = `
## Command Behavior
**Generates comprehensive Markdown documentation from a Salesforce Flow metadata file.**
This command automates the creation of human-readable documentation for Salesforce Flows, making it easier to understand their logic and behavior. It can process a single Flow file or multiple Flow files, generating a Markdown file for each.
Key features include:
- **Detailed Flow Description:** Extracts and presents the Flow's structure, elements, and decision logic in a clear, organized Markdown format.
- **AI-Powered Summarization (Optional):** If [AI integration](${CONSTANTS.DOC_URL_ROOT}/salesforce-ai-setup/) is configured, the documentation will include an AI-generated summary of the Flow's purpose and functionality.
- **Mermaid Diagram Generation:** Integrates with Mermaid to visualize the Flow's structure, providing a graphical representation alongside the textual description.
- **History Diff (Optional):** Can generate a Markdown file showing the historical differences of the Flow, useful for tracking changes over time.
- **PDF Export (Optional):** Allows for the generation of the documentation in PDF format for easy sharing and archiving.
- **Interactive File Selection:** If no input file is specified, the command interactively prompts the user to select Flow files.
<details markdown="1">
<summary>Technical explanations</summary>
The command leverages several internal utilities and external libraries to achieve its functionality:
- **Flow Metadata Parsing:** Reads and parses the XML content of Salesforce Flow metadata files (.flow-meta.xml).
- **Markdown Generation:** Utilizes exttt{generateFlowMarkdownFile} to transform the parsed Flow data into a structured Markdown format.
- **Mermaid Integration:** Employs exttt{generateMarkdownFileWithMermaid} to embed Mermaid diagrams within the Markdown output, which are then rendered by compatible Markdown viewers.
- **AI Integration:** If enabled, it interacts with an AI service (via exttt{describeWithAi} option) to generate a high-level summary of the Flow.
- **Git History Analysis:** For the \
--with-history\
flag, it uses exttt{generateHistoryDiffMarkdown} to analyze Git history and present changes to the Flow.
- **File System Operations:** Uses exttt{fs-extra} for file system operations like reading input files, creating output directories (e.g., exttt{docs/flows/}), and writing Markdown and PDF files.
- **Salesforce CLI Integration:** Uses exttt{@salesforce/sf-plugins-core} for command-line parsing and exttt{setConnectionVariables} for Salesforce organization context.
- **WebSocket Communication:** Interacts with a WebSocket client ( exttt{WebSocketClient.requestOpenFile}) to open the generated Markdown file in a VS Code tab, enhancing user experience.
</details>
`;
static examples = [
'$ sf hardis:doc:flow2markdown',
'$ sf hardis:doc:flow2markdown --inputfile force-app/main/default/flows/MyFlow.flow-meta.xml',
'$ sf hardis:doc:flow2markdown --pdf',
'$ sf hardis:doc:flow2markdown --inputfile force-app/main/default/flows/MyFlow.flow-meta.xml --pdf',
];
static flags = {
inputfile: Flags.string({
char: 'x',
description: 'Path to Flow metadata file. If not specified, the command will prompt the user',
}),
outputfile: Flags.string({
char: 'f',
description: 'Force the path and name of output markdown file. Must end with .md',
}),
"with-history": Flags.boolean({
default: false,
description: "Generate a markdown file with the history diff of the Flow",
}),
pdf: Flags.boolean({
description: 'Also generate the documentation in PDF format',
}),
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": optionalOrgFlagWithDeprecations
};
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
static requiresProject = false;
withHistory = false;
withPdf = false;
singleFileMode = false;
inputFiles;
outputFile;
debugMode = false;
/* jscpd:ignore-end */
async run() {
const { flags } = await this.parse(Flow2Markdown);
const outputFiles = [];
this.inputFiles = flags.inputfile ? [flags.inputfile] : null;
this.outputFile = flags.outputfile || null;
this.withHistory = flags["with-history"] === true ? true : false;
this.withPdf = flags.pdf === true ? true : false;
this.singleFileMode = this.inputFiles != null && this.inputFiles.length == 1;
this.debugMode = flags.debug || false;
await setConnectionVariables(flags['target-org']?.getConnection(), true); // Required for some notifications providers like Email, or for Agentforce
if (this.inputFiles === null && !isCI) {
this.inputFiles = await MetadataUtils.promptMultipleFlows();
}
if (this.singleFileMode && !this.outputFile) {
await fs.ensureDir(path.join("docs", "flows"));
this.outputFile = path.join("docs", "flows", path.basename(this.inputFiles[0]).replace(".flow-meta.xml", ".md"));
}
for (const inputFile of this.inputFiles) {
let outputFile = this.outputFile;
if (!this.singleFileMode) {
await fs.ensureDir(path.join("docs", "flows"));
outputFile = path.join("docs", "flows", path.basename(inputFile).replace(".flow-meta.xml", ".md"));
}
const flowName = path.basename(inputFile, ".flow-meta.xml");
uxLog("log", this, c.grey(`Generating Markdown for Flow ${inputFile}.`));
const flowXml = (await fs.readFile(inputFile, "utf8")).toString();
const genRes = await generateFlowMarkdownFile(flowName, flowXml, outputFile, { collapsedDetails: false, describeWithAi: true, flowDependencies: {} });
if (!genRes) {
throw new Error("Error generating markdown file");
}
if (this.debugMode) {
await fs.copyFile(outputFile, outputFile.replace(".md", ".mermaid.md"));
}
const gen2res = await generateMarkdownFileWithMermaid(outputFile, outputFile, null, this.withPdf);
if (!gen2res) {
throw new Error("Error generating mermaid markdown file");
}
if (this.withHistory) {
try {
await generateHistoryDiffMarkdown(inputFile, this.debugMode);
}
catch (e) {
uxLog("warning", this, c.yellow(`Error generating history-diff Markdown: ${e.message}.`));
}
}
// Open file in a new VS Code tab if available
WebSocketClient.requestOpenFile(outputFile);
WebSocketClient.sendReportFileMessage(outputFile, path.basename(outputFile).replace(".md", "") + " Documentation", 'report');
outputFiles.push(outputFile);
}
uxLog("action", this, c.green(`Markdown documentation generated for ${this.inputFiles.length} Flow(s).`));
for (const outputFile of outputFiles) {
uxLog("log", this, c.grey(`- ${outputFile}`));
}
// Return an object to be displayed with --json
return { outputFiles: outputFiles };
}
}
//# sourceMappingURL=flow2markdown.js.map