diff --git a/README.md b/README.md
index da3383e..0b8407e 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ available:
| Option | Description | Type | Default | Required |
| ------------------- | ------------------------------------------------------------------------------------------------------ | -------- | ---------- | ----------------------------------- |
-| `--mode` | The output mode ('json' or 'github_action'). | string | `json` | No |
+| `--mode` | The output mode ('json', 'markdown', or 'github_action'). | string | `json` | No |
| `--diagramTool` | The diagram tool to use ('plantuml', 'graphviz', or 'mermaid'). | string | `graphviz` | No |
| `--filePath` | Path(s) to the Salesforce Flow XML file(s). Specify multiple files using space separated values. | string[] | | No (Git diff or file path required) |
| `--gitDiffFromHash` | The starting commit hash for the Git diff. | string | | No (Only with Git diff) |
@@ -41,16 +41,6 @@ available:
| `--outputDirectory` | The directory to save the output file. | string | | Yes (Only in json mode) |
| `--outputFileName` | The name of the output file (without extension). | string | | Yes (Only in json mode) |
-### Output Modes
-
-Flow Lens supports two output modes:
-
-1. **json mode (default):** Generates a JSON file containing the UML diagram(s)
- that can be used for further processing.
-2. **github_action mode:** Automatically posts comments with flow diagrams on
- pull requests when used in a GitHub Actions workflow. When using this mode,
- you must specify `mermaid` as the diagram tool.
-
**Example using file path (json mode):**
```shell
@@ -83,10 +73,86 @@ deno run \
--outputFileName="test"
```
-### Setting up a GitHub Action
+## Output
+
+Flow Lens supports three output modes:
+
+1. **json mode (default):** Generates a JSON file containing the UML diagram(s)
+ that can be used for further processing.
+2. **markdown mode:** Generates individual `.md` files for each flow in the
+ specified output directory. Each markdown file contains Mermaid diagrams
+ wrapped in code blocks.
+3. **github_action mode:** Automatically posts comments with flow diagrams on
+ pull requests when used in a GitHub Actions workflow. When using this mode,
+ you must specify `mermaid` as the diagram tool.
+
+### JSON Mode (default)
+
+When using the `json` mode, the output is a JSON file containing the generated
+UML diagram(s). The structure will contain the file paths and their associated
+old (if applicable) and new UML strings.
+
+```json
+[
+ {
+ "path": "force-app/main/default/flows/ArticleSubmissionStatus.flow-meta.xml",
+ "difference": {
+ "old": "UML_STRING_HERE",
+ "new": "UML_STRING_HERE"
+ }
+ },
+ {
+ "path": "force-app/main/default/flows/LeadConversionScreen.flow-meta.xml",
+ "difference": {
+ "old": "UML_STRING_HERE",
+ "new": "UML_STRING_HERE"
+ }
+ }
+]
+```
+
+### Markdown Mode
+
+When using the `markdown` mode, Flow Lens generates individual `.md` files for
+each flow in the specified output directory. Each markdown file is named after
+the flow's API name and contains the UML diagram wrapped in Mermaid code blocks.
+
+**File Structure:**
+
+```
+outputDirectory/
+├── FlowName1.md
+├── FlowName2.md
+└── FlowName3.md
+```
+
+**Markdown Content Format:**
+
+- If there's only a new version (no diff), the file contains a single Mermaid
+ diagram
+- If there are both old and new versions (diff mode), the file contains:
+ - `## Old Version` section with the previous diagram
+ - `## New Version` section with the current diagram
+- Each diagram is wrapped in triple backticks with the `mermaid` language
+ identifier
+
+**Note:** Markdown mode only works with the `mermaid` diagram tool and requires
+an `outputDirectory` to be specified.
+
+### GitHub Action Mode
+
+When using the `github_action` mode, Flow Lens automatically posts flow diagrams
+as comments on pull requests. This mode is designed for use in GitHub Actions
+workflows and requires the `mermaid` diagram tool.
+
+**Requirements:**
-You can set up a GitHub Action to automatically generate and post flow diagrams
-as comments on pull requests. Here's an example workflow configuration:
+- Must be run in a GitHub Actions workflow
+- Requires `mermaid` as the diagram tool
+- Needs appropriate GitHub permissions to post comments
+- Requires `GITHUB_TOKEN` environment variable
+
+**Example GitHub Actions Workflow:**
```yaml
name: Generate Flow Preview
@@ -128,54 +194,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
-When using the GitHub Action mode, Flow Lens will automatically post a comment
-on the pull request with the old (if applicable) and new versions of the flow
-whenever a pull request is created or updated. This makes it easy to visualize
-flow changes directly in the pull request review process.
-
-## Output
-
-When using the json mode, the output is a JSON file containing the generated UML
-diagram(s). The structure will contain the file paths and their associated old
-(if applicable) and new UML strings.
-
-```json
-[
- {
- "path": "force-app/main/default/flows/ArticleSubmissionStatus.flow-meta.xml",
- "difference": {
- "old": "UML_STRING_HERE",
- "new": "UML_STRING_HERE"
- }
- },
- {
- "path": "force-app/main/default/flows/LeadConversionScreen.flow-meta.xml",
- "difference": {
- "old": "UML_STRING_HERE",
- "new": "UML_STRING_HERE"
- }
- }
-]
-```
-
-## Frequently Asked Questions
+**What Happens:**
-### Why is this built using Deno?
-
-Porting the project from Google's internal Blaze build system to Deno was easier
-than setting it up in Node.js, as there is no transpilation step from TypeScript
-to JavaScript. Deno's built-in TypeScript support made the migration process
-much smoother.
-
-### How is this different than Todd Halfpenny's flow visualizer?
-
-While
-[Todd's project](https://github.com/toddhalfpenny/salesforce-flow-visualiser) is
-excellent, Flow Lens was built and used internally at Google before Todd's
-project was available for commercial use. The key differentiator is that Flow
-Lens represents flow differences structurally, making it ideal for assistance
-with code reviews. This structural diff visualization is not available in other
-flow visualization tools.
+- Flow Lens detects the pull request context automatically
+- Generates Mermaid diagrams for changed flows
+- Posts a comment with both old and new versions (if applicable)
+- Updates existing comments when the PR is modified
+- Requires no manual file management or output directory setup
## Example
@@ -320,7 +345,10 @@ flow visualization tools.
deno run \
--allow-read \
--allow-write \
+ --allow-run \
+ --allow-env \
jsr:@goog/flow-lens \
+ --mode="markdown" \
--diagramTool="mermaid" \
--gitRepo="/path/to/salesforce_project/" \
--gitDiffFromHash="HEAD~1" \
@@ -329,30 +357,66 @@ deno run \
--outputFileName="test"
```
-`test.json`
-
-```json
-[
- {
- "path": "force-app/main/default/flows/Demo.flow-meta.xml",
- "difference": {
- "old": "---\ntitle: \"Demo\"\n---\nstateDiagram-v2\n\n classDef pink fill:#F9548A, color:white\n classDef orange fill:#DD7A00, color:white\n classDef navy fill:#344568, color:white\n classDef blue fill:#1B96FF, color:white\n classDef modified stroke-width: 5px, stroke: orange\n classDef added stroke-width: 5px, stroke: green\n classDef deleted stroke-width: 5px, stroke: red\n\n state \"-Assignment 📝
Set the Description
Get_the_Acme_Account.Description = This is a Demonstration!\" as Set_the_Description\n class Set_the_Description orange deleted\n state \"ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only\" as Get_the_Acme_Account\n class Get_the_Acme_Account pink modified\n state \"ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
\" as Update_the_Acme_Account\n class Update_the_Acme_Account pink modified\n FLOW_START --> Get_the_Acme_Account\n Get_the_Acme_Account --> Set_the_Description\n Set_the_Description --> Update_the_Acme_Account",
- "new": "---\ntitle: \"Demo\"\n---\nstateDiagram-v2\n\n classDef pink fill:#F9548A, color:white\n classDef orange fill:#DD7A00, color:white\n classDef navy fill:#344568, color:white\n classDef blue fill:#1B96FF, color:white\n classDef modified stroke-width: 5px, stroke: orange\n classDef added stroke-width: 5px, stroke: green\n classDef deleted stroke-width: 5px, stroke: red\n\n state \"+Assignment 📝
Set the Type
Get_the_Acme_Account.Type = Other\" as Set_the_Type\n class Set_the_Type orange added\n state \"ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only\" as Get_the_Acme_Account\n class Get_the_Acme_Account pink modified\n state \"ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
\" as Update_the_Acme_Account\n class Update_the_Acme_Account pink modified\n state \"+Action Call ⚡
Log Error\" as Log_Error\n class Log_Error navy added\n state \"+Action Call ⚡
Log Error\" as Log_Error2\n class Log_Error2 navy added\n FLOW_START --> Get_the_Acme_Account\n Get_the_Acme_Account --> Set_the_Type\n Get_the_Acme_Account --> Log_Error : ❌ Fault ❌\n Set_the_Type --> Update_the_Acme_Account\n Update_the_Acme_Account --> Log_Error2 : ❌ Fault ❌"
- }
- }
-]
+# `./Demo.md`
+
+## Old Version
+
+```mermaid
+---
+title: "Demo"
+---
+stateDiagram-v2
+
+ classDef pink fill:#F9548A, color:white
+ classDef orange fill:#DD7A00, color:white
+ classDef navy fill:#344568, color:white
+ classDef blue fill:#1B96FF, color:white
+ classDef modified stroke-width: 5px, stroke: orange
+ classDef added stroke-width: 5px, stroke: green
+ classDef deleted stroke-width: 5px, stroke: red
+
+ state "Flow Start
Flow Start
Flow Details
Process Type: AutoLaunchedFlow" as FLOW_START
+ state "-Assignment 📝
Set the Description
Get_the_Acme_Account.Description = This is a Demonstration!" as Set_the_Description
+ class Set_the_Description orange deleted
+ state "ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only" as Get_the_Acme_Account
+ class Get_the_Acme_Account pink modified
+ state "ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
" as Update_the_Acme_Account
+ class Update_the_Acme_Account pink modified
+ FLOW_START --> Get_the_Acme_Account
+ Get_the_Acme_Account --> Set_the_Description
+ Set_the_Description --> Update_the_Acme_Account
```
-
-
- | Old | New |
-
-
-
-
- |
-
-
- |
-
-
+## New Version
+
+```mermaid
+---
+title: "Demo"
+---
+stateDiagram-v2
+
+ classDef pink fill:#F9548A, color:white
+ classDef orange fill:#DD7A00, color:white
+ classDef navy fill:#344568, color:white
+ classDef blue fill:#1B96FF, color:white
+ classDef modified stroke-width: 5px, stroke: orange
+ classDef added stroke-width: 5px, stroke: green
+ classDef deleted stroke-width: 5px, stroke: red
+
+ state "Flow Start
Flow Start
Flow Details
Process Type: AutoLaunchedFlow" as FLOW_START
+ state "+Assignment 📝
Set the Type
Get_the_Acme_Account.Type = Other" as Set_the_Type
+ class Set_the_Type orange added
+ state "ΔRecord Lookup 🔍
Get the 'Acme' Account
sObject: Account
Fields Queried: all
Filter Logic: and
1. Name EqualTo Acme
Limit: First Record Only" as Get_the_Acme_Account
+ class Get_the_Acme_Account pink modified
+ state "ΔRecord Update ✏️
Update the 'Acme' Account
Reference Update
Get_the_Acme_Account
" as Update_the_Acme_Account
+ class Update_the_Acme_Account pink modified
+ state "+Action Call ⚡
Log Error" as Log_Error
+ class Log_Error navy added
+ state "+Action Call ⚡
Log Error" as Log_Error2
+ class Log_Error2 navy added
+ FLOW_START --> Get_the_Acme_Account
+ Get_the_Acme_Account --> Set_the_Type
+ Get_the_Acme_Account --> Log_Error : ❌ Fault ❌
+ Set_the_Type --> Update_the_Acme_Account
+ Update_the_Acme_Account --> Log_Error2 : ❌ Fault ❌
+```
diff --git a/deno.json b/deno.json
index 5ec8342..54feb3b 100644
--- a/deno.json
+++ b/deno.json
@@ -1,6 +1,6 @@
{
"name": "@goog/flow-lens",
- "version": "0.1.12",
+ "version": "0.1.14",
"license": "Apache",
"exports": "./src/main/main.ts",
"imports": {
diff --git a/docs/img/Diff_New.png b/docs/img/Diff_New.png
deleted file mode 100644
index f3155d3..0000000
Binary files a/docs/img/Diff_New.png and /dev/null differ
diff --git a/docs/img/Diff_Old.png b/docs/img/Diff_Old.png
deleted file mode 100644
index 6df0363..0000000
Binary files a/docs/img/Diff_Old.png and /dev/null differ
diff --git a/docs/img/Initial_Diagram.png b/docs/img/Initial_Diagram.png
deleted file mode 100644
index cfbf1e9..0000000
Binary files a/docs/img/Initial_Diagram.png and /dev/null differ
diff --git a/src/main/argument_processor.ts b/src/main/argument_processor.ts
index bf7f719..6280559 100644
--- a/src/main/argument_processor.ts
+++ b/src/main/argument_processor.ts
@@ -67,6 +67,7 @@ export enum DiagramTool {
export enum Mode {
JSON = "json",
GITHUB_ACTION = "github_action",
+ MARKDOWN = "markdown",
}
/**
@@ -93,6 +94,7 @@ export const ERROR_MESSAGES = {
"gitDiffFromHash and gitDiffToHash must be specified together",
outputFileNameRequired: "outputFileName is required for JSON mode",
outputDirectoryRequired: "outputDirectory is required for JSON mode",
+
unsupportedMode: (mode: string) =>
`Unsupported mode: ${mode}. Valid options are: ${
Object.values(Mode).join(
@@ -106,6 +108,7 @@ export const ERROR_MESSAGES = {
"GitHub Action mode requires gitDiffToHash to be 'HEAD'",
githubActionRequiresHeadMinusOne:
"GitHub Action mode requires gitDiffFromHash to be 'HEAD^1'",
+ markdownRequiresMermaid: "Markdown mode requires diagramTool to be 'mermaid'",
};
/**
@@ -168,11 +171,21 @@ export class ArgumentProcessor {
this.validateOutputFileName();
}
+ // Validate output directory for markdown mode
+ if (this.config.mode?.toLowerCase() === Mode.MARKDOWN) {
+ this.validateOutputDirectory();
+ }
+
// Validate GitHub Action specific requirements
if (this.config.mode?.toLowerCase() === Mode.GITHUB_ACTION) {
this.validateGitHubActionMode();
}
+ // Validate Markdown specific requirements
+ if (this.config.mode?.toLowerCase() === Mode.MARKDOWN) {
+ this.validateMarkdownMode();
+ }
+
this.validateRequiredArguments();
this.validateMutuallyExclusiveArguments();
this.validateConditionalArguments();
@@ -254,6 +267,12 @@ export class ArgumentProcessor {
}
}
+ private validateMarkdownMode() {
+ if (this.config.diagramTool?.toLowerCase() !== DiagramTool.MERMAID) {
+ this.errorsEncountered.push(ERROR_MESSAGES.markdownRequiresMermaid);
+ }
+ }
+
private validateRequiredArguments() {
if (
(!this.config.filePath || this.config?.filePath?.length === 0) &&
diff --git a/src/main/flow_file_change_detector.ts b/src/main/flow_file_change_detector.ts
index bd49985..b139eb4 100644
--- a/src/main/flow_file_change_detector.ts
+++ b/src/main/flow_file_change_detector.ts
@@ -137,8 +137,7 @@ export class FlowFileChangeDetector {
*/
private executeGitCommand(args: string[]): Uint8Array {
const repo = Configuration.getInstance().gitRepo;
- const commandArgs = [repo ? `-C ${repo}` : "", ...args].filter(Boolean);
-
+ const commandArgs = repo ? ["-C", repo, ...args] : args;
return new Deno.Command("git", { args: commandArgs }).outputSync().stdout;
}
}
diff --git a/src/main/flow_parser.ts b/src/main/flow_parser.ts
index fdbd4cf..c173b94 100644
--- a/src/main/flow_parser.ts
+++ b/src/main/flow_parser.ts
@@ -67,6 +67,7 @@ export interface Transition {
*/
export interface ParsedFlow {
label?: string;
+ fullName?: string;
processType?: flowTypes.FlowProcessType;
start?: flowTypes.FlowStart;
apexPluginCalls?: flowTypes.FlowApexPluginCall[];
@@ -132,6 +133,7 @@ export class FlowParser {
private populateFlowNodes(flow: flowTypes.Flow) {
this.beingParsed.label = flow.label;
+ this.beingParsed.fullName = flow.fullName;
this.beingParsed.processType = flow.processType;
this.beingParsed.start = flow.start;
this.validateFlowStart();
@@ -228,6 +230,7 @@ export class FlowParser {
if (!start) {
return result;
}
+
const queue: flowTypes.FlowNode[] = [start];
const visitedNodes = new Set();
while (queue.length > 0) {
@@ -237,6 +240,7 @@ export class FlowParser {
}
visitedNodes.add(node.name);
const transitions = this.getTransitionsForNode(node);
+
for (const transition of transitions) {
const toNode = this.beingParsed.nameToNode?.get(transition.to);
if (toNode) {
@@ -245,6 +249,7 @@ export class FlowParser {
}
result.push(...transitions);
}
+
return result;
}
@@ -290,6 +295,7 @@ export class FlowParser {
) {
transitions.push(...this.getTransitionsFromConnector(node));
}
+
return transitions;
}
@@ -361,6 +367,7 @@ export class FlowParser {
| flowTypes.FlowActionCall,
): Transition[] {
const result: Transition[] = [];
+
if (node.connector) {
result.push(
this.createTransition(node, node.connector, false, undefined),
@@ -371,6 +378,7 @@ export class FlowParser {
this.createTransition(node, node.faultConnector, true, FAULT),
);
}
+
return result;
}
@@ -405,6 +413,7 @@ export class FlowParser {
| flowTypes.FlowCustomError,
): Transition[] {
const result: Transition[] = [];
+
if (node.connector) {
for (
const connector of Array.isArray(node.connector)
@@ -414,6 +423,7 @@ export class FlowParser {
result.push(this.createTransition(node, connector, false, undefined));
}
}
+
return result;
}
@@ -727,5 +737,6 @@ function isCustomError(
function isFlowStart(
node: flowTypes.FlowNode,
): node is flowTypes.FlowStart {
- return (node as flowTypes.FlowStart).connector !== undefined;
+ return (node as flowTypes.FlowStart).name === START &&
+ (node as flowTypes.FlowStart).label === undefined;
}
diff --git a/src/main/uml_writer.ts b/src/main/uml_writer.ts
index 5f1c65f..1553ae4 100644
--- a/src/main/uml_writer.ts
+++ b/src/main/uml_writer.ts
@@ -23,6 +23,8 @@ import { Configuration, Mode, RuntimeConfig } from "./argument_processor.ts";
import { FlowDifference } from "./flow_to_uml_transformer.ts";
import { GithubClient } from "./github_client.ts";
+const EOL = Deno.build.os === "windows" ? "\r\n" : "\n";
+
const FILE_EXTENSION = ".json";
const HIDDEN_COMMENT_PREFIX = "";
const MERMAID_OPEN_TAG = "```mermaid";
@@ -48,10 +50,13 @@ export class UmlWriter {
*/
async writeUmlDiagrams() {
const config = Configuration.getInstance();
+
if (config.mode === Mode.JSON) {
this.writeJsonFile(config);
} else if (config.mode === Mode.GITHUB_ACTION) {
await this.writeGithubComment(config);
+ } else if (config.mode === Mode.MARKDOWN) {
+ this.writeMarkdownFiles(config);
}
}
@@ -94,6 +99,44 @@ export class UmlWriter {
throw error;
}
}
+
+ private writeMarkdownFiles(config: RuntimeConfig) {
+ for (const [filePath, flowDifference] of this.filePathToFlowDifference) {
+ const flowApiName = this.extractFlowApiName(filePath);
+
+ const outputPath = join(
+ config.outputDirectory!,
+ `${flowApiName}.md`,
+ );
+
+ let markdownContent = "";
+ const tripleBackticks = "```";
+
+ if (flowDifference.old) {
+ markdownContent +=
+ `## Old Version${EOL}${EOL}${tripleBackticks}mermaid${EOL}${flowDifference.old}${EOL}${tripleBackticks}${EOL}${EOL}`;
+ markdownContent +=
+ `## New Version${EOL}${EOL}${tripleBackticks}mermaid${EOL}${flowDifference.new}${EOL}${tripleBackticks}${EOL}`;
+ } else {
+ markdownContent +=
+ `${tripleBackticks}mermaid${EOL}${flowDifference.new}${EOL}${tripleBackticks}${EOL}`;
+ }
+
+ Deno.writeTextFileSync(outputPath, markdownContent);
+ }
+ }
+
+ private extractFlowApiName(filePath: string): string {
+ // Extract the flow API name from the file path
+ // The file path should contain the flow API name
+ const fileName = filePath.split("/").pop() || "";
+ // Remove common flow file extensions
+ const flowApiName = fileName
+ .replace(/\.flow-meta\.xml$/, "")
+ .replace(/\.flow$/, "")
+ .replace(/\.xml$/, "");
+ return flowApiName || "flow";
+ }
}
interface DefaultFormat {
diff --git a/src/test/argument_processor_test.ts b/src/test/argument_processor_test.ts
index 456ed29..2cf5db0 100644
--- a/src/test/argument_processor_test.ts
+++ b/src/test/argument_processor_test.ts
@@ -98,6 +98,74 @@ Deno.test("ArgumentProcessor", async (t) => {
},
);
+ await t.step(
+ "should validate when mode is MARKDOWN and outputDirectory is provided",
+ () => {
+ const { argumentProcessor, config } = setupTest((config) => {
+ config.mode = Mode.MARKDOWN;
+ config.diagramTool = DiagramTool.MERMAID;
+ config.outputDirectory = ".";
+ config.outputFileName = undefined;
+ });
+ const result = argumentProcessor.getConfig();
+ assertEquals(result, config);
+ },
+ );
+
+ await t.step(
+ "should reject markdown mode with non-mermaid diagram tool",
+ () => {
+ assertThrows(
+ () => {
+ const { argumentProcessor } = setupTest((config) => {
+ config.mode = Mode.MARKDOWN;
+ config.diagramTool = DiagramTool.GRAPH_VIZ;
+ config.outputDirectory = "test/output";
+ });
+ argumentProcessor.getConfig();
+ },
+ Error,
+ ERROR_MESSAGES.markdownRequiresMermaid,
+ );
+ },
+ );
+
+ await t.step(
+ "should reject markdown mode without outputDirectory",
+ () => {
+ assertThrows(
+ () => {
+ const { argumentProcessor } = setupTest((config) => {
+ config.mode = Mode.MARKDOWN;
+ config.diagramTool = DiagramTool.MERMAID;
+ config.outputDirectory = undefined;
+ });
+ argumentProcessor.getConfig();
+ },
+ Error,
+ ERROR_MESSAGES.outputDirectoryRequired,
+ );
+ },
+ );
+
+ await t.step(
+ "should reject markdown mode with non-existent outputDirectory",
+ () => {
+ assertThrows(
+ () => {
+ const { argumentProcessor } = setupTest((config) => {
+ config.mode = Mode.MARKDOWN;
+ config.diagramTool = DiagramTool.MERMAID;
+ config.outputDirectory = "non/existent/directory";
+ });
+ argumentProcessor.getConfig();
+ },
+ Error,
+ ERROR_MESSAGES.invalidOutputDirectory("non/existent/directory"),
+ );
+ },
+ );
+
await t.step(
"should throw an exception when outputDirectory is not provided in JSON mode",
() => {
diff --git a/src/test/uml_writer_test.ts b/src/test/uml_writer_test.ts
index 963d22b..5efaf78 100644
--- a/src/test/uml_writer_test.ts
+++ b/src/test/uml_writer_test.ts
@@ -175,4 +175,49 @@ Deno.test("UmlWriter", async (t) => {
Deno.env.get = originalEnvGet;
}
});
+
+ await t.step("should write UML diagrams as markdown files", async () => {
+ // Mock the Configuration.getInstance to return our test config
+ const originalGetInstance = Configuration.getInstance;
+ const testConfig = getRuntimeConfig(DiagramTool.MERMAID, Mode.MARKDOWN);
+
+ Configuration.getInstance = () => testConfig;
+
+ try {
+ writer = new UmlWriter(FILE_PATH_TO_FLOW_DIFFERENCE);
+
+ writer.writeUmlDiagrams();
+
+ // Check that markdown files were created
+ const expectedFile1Path = join(TEST_UNDECLARED_OUTPUTS_DIR, "file1.md");
+ const expectedFile2Path = join(TEST_UNDECLARED_OUTPUTS_DIR, "file2.md");
+
+ assertExists(existsSync(expectedFile1Path));
+ assertExists(existsSync(expectedFile2Path));
+
+ // Use OS-appropriate newlines for test expectations
+ const eol = Deno.build.os === "windows" ? "\r\n" : "\n";
+
+ // Check the content of the first file (no old version)
+ fileContent = Deno.readTextFileSync(expectedFile1Path);
+ assertEquals(
+ fileContent,
+ `\`\`\`mermaid${eol}uml1${eol}\`\`\`${eol}`,
+ );
+
+ // Check the content of the second file (with old version)
+ fileContent = Deno.readTextFileSync(expectedFile2Path);
+ assertEquals(
+ fileContent,
+ `## Old Version${eol}${eol}\`\`\`mermaid${eol}uml1${eol}\`\`\`${eol}${eol}## New Version${eol}${eol}\`\`\`mermaid${eol}uml2${eol}\`\`\`${eol}`,
+ );
+
+ // Clean up
+ await Deno.remove(expectedFile1Path);
+ await Deno.remove(expectedFile2Path);
+ } finally {
+ // Restore original Configuration.getInstance
+ Configuration.getInstance = originalGetInstance;
+ }
+ });
});