Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Contribute

Hiii! Thanks for thinking about contributing to this project.

## How the Action Works

The action does the following:

1. Checks for mermaid definition files

2. Checks for old generated diagrams (svg/png/pdf)

3. If there are diagrams that need to be generated, it:
- Downloads @mermaid-js/mermaid-cli
- Generates mermaid diagrams to the desired output

A key detail here is that it only generates mermaid diagrams if the existing, generated diagrams (png/pdf/svg) have older timestamps than their respective mermaid definition files.

As such, a technical challenge arises when checking out a repo, since all timestamps are reset to when the repo was checked out.

## How the Repo is Structured

I wanted to write my action in Python, and I wanted it to run as fast as possible. I also wanted to unit-test it.

So, I used composite actions, with Python run commands...

But, how do I unit-test them?

I take an unconventional approach by defining standalone, python files that contain the code I want to execute. They're named with the step's ID.

Then, I "bundle" these python files into the action. I take all the python files and (using a YAML JS library) integrate them into the `action.yml`.

This means I can unit-test my Python run commands because they're regular files. I also get to use composite actions (instead of container actions) for speed and readability. Finally, and best of all, I get to use Python to write the logic that I want.

I also added a workflow to check that the files were bundled.

Is it over-engineered? Yes!

But is it cool? You tell me...

## Next Steps

- I want this action to handle caching generated mermaid files for the user to remove complexity, and to only regenerate the mermaid diagrams that need to be regenerated.

- Support for md can be added

- Support for Yarn berry can be added

- A minimal JS package can be created to preview how the mermaid diagrams will look in production.
157 changes: 131 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,117 @@
# Mermaid-Maker

This action converts [mermaid](https://github.com/mermaid-js/mermaid) files into one of the output formats: png, svg, pdf.
This action converts [mermaid](https://github.com/mermaid-js/mermaid) definition files into one of the output formats: PNG, SVG, PDf.

## What is Mermaid?
This allows you to create web applications without worrying about how to render static mermaid diagrams in production; this action helps you with that.

Mermaid is a popular diagramming tool written with JS. It uses a unique syntax to generate flow charts, class diagrams, quadrant charts, etc...
## Quickstart

Here's an example of mermaid syntax:
1. Find your deploy workflow, ex. `./.github/workflows/deploy.yml`.

2. Before you build your web-application, add the following lines:

```yml
- name: Generate mermaid diagrams
uses: "mermaid-maker/action@v1.0.0"
with:
pkg_manager: pnpm # replace with your node pkg manager (npm/pnpm/bun)
output_file_extension: svg # replace with your desired output file (svg/png/pdf)
```
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses

3. (Optional) If you don't want to regenerate mermaid diagrams on every deploy, you can cache them! Here's an example:

```yml
- uses: actions/cache@v6.1.0
with:

# add the paths to both your mermaid definition files and generated diagrams
# the path to mermaid definition files is needed for their (older) timestamps
path: |
public/*.svg
public/*.mmd

# Generate a new cache whenever packages or source files change.
# add mermaid definition files to the hash-key so they're regenerated when they change
key: ${{ runner.os }}-nextjs-${{ hashFiles('public/*.mmd')}}
```

Mermaid would then convert the previous syntax into the following diagram:
4. In your web-application, render simple client-side mermaid diagrams for dev, and refer to the svg/png/pdf files in prod. Here's an example from a Next app:

```js
let MermaidDiagram = null;

if (process.env.NODE_ENV == "development"){
// read mermaid definition file
const chart_content = await readFile(`${process.cwd()}/public/${chart}.mmd`, "utf-8")
// generate client-side mermaid diagrams using mermaid-js library
MermaidDiagram = <ClientMermaid chart={chart_content}/>
}

```mermaid
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
else if (process.env.NODE_ENV == "production") {
// refer to the static svg in prod
MermaidDiagram = <Image src={`${process.env.NEXT_PUBLIC_BASEPATH}${chart}.svg`} alt="mermaid_diagram" width={100} height={100}/>
}

return (
<div className="*:w-100">
{MermaidDiagram}
</div>
)
```

For a full, working example, check out this Next sample app: [mermaid-maker/next-sample-app](https://github.com/mermaid-maker/next-sample-app)

Also, if you want to see how it will look in prod, you can visit [mermaid.ai](https://mermaid.ai) and export PNG/SVG/PDF diagrams that you can test out on your machine.

## Usage

This action takes the following **input** variables:

```yml
input_dir:
description: |
The input directory for mermaid files. Defaults to "all",
i.e. all changed files with input_file_extension across the whole repo.
required: false
default: all
input_file_extension:
description: |
The extension of the mermaid files. Note: md (markdown) input files are
currently not supported, but they could be in the future.
required: false
default: mmd
output_dir:
description: |
The output directory for generated mermaid files. Defaults to "same",
i.e. converted files will be placed in the same directory as their source files.
required: false
default: same
output_file_extension:
description: |
The output format of the generated mermaid files. Only "svg", "png" and "pdf" are accepted.
required: false
default: svg
pkg_manager:
description: |
The node package manager to use. Make sure it's setup before using this action.
Currently, only the following pkg_managers are accepted:
- "npm"
- "pnpm"
- "bun"
Yarn is not supported due to non-native support for the auto-installation of peer-depenecies like puppeteer.
required: false
default: npm
```

It **outputs** the following variables:

```yml
input_files:
description: The input files that were used to generate mermaid diagrams
value: ${{steps.get_files_to_regen.outputs.input_files_to_regen}}
output_files:
description: The output files that were generate
value: ${{steps.get_files_to_regen.outputs.output_files_to_regen}}
```

## Why Use This Action?
Expand All @@ -31,29 +120,29 @@ One key aspect about Mermaid is that it's a client-side library. It uses the DOM

This means that mermaid diagrams are rendered on the client, adding additional latency to your app.

### So, What are the Existing Solutions?
So, when you want to render mermaid diagrams, you might come across the following, **possible solutions**:

#### A) Live the Easy Life
#### A. Live the Easy Life

Well, technically, the first solution is to not worry about it... However, a webpage will take longer to load on every rerender.

---

#### B) Async & Caching
#### B. Async & Caching

One solution is rendering mermaid diagrams asyncronously and caching them between rerenders. This allows you to render most of your webpage while using placeholders/lazy loading for mermaid diagrams. Then, caching the mermaid diagrams makes it load faster on rerenders.
One solution is rendering mermaid diagrams asyncronously and caching them between rerenders. This allows you to render most of your webpage while using placeholders/suspense/lazy loading for mermaid diagrams. Then, caching the mermaid diagrams makes it load faster on rerenders.

This is good enough for use-cases where:

1. Initial speed of loading diagrams isn't a priority

2. A given webpage only contains a few, simpler diagrams

3. You don't mind the added complexity (async, caching, placeholders/suspense)
3. You don't mind the added complexity (async, caching, placeholders/suspense). You can even use a library like: [https://github.com/lukilabs/beautiful-mermaid](https://github.com/lukilabs/beautiful-mermaid) to help you out here.

---

#### C) Diagrams as Static Assets
#### C. Diagrams as Static Assets

Another solution is using `mermaid-js/mermaid-cli` to render mermaid diagrams on your machine, before pushing them as static assets to your repo.

Expand All @@ -67,16 +156,32 @@ This is also good enough for use-cases where:

### This Solution

Or... **you can use this solution**. This Github action checks for any added or modified mermaid source files, and it renders them to your chosen output.
Or... **you can use this solution**. This Github action checks for any mermaid definition files, and it renders them to your chosen output.

This solution is:

1. Simpler. On your dev environment, you can load mermaid diagrams on the client without async/placeholders/caching. Then, in prod, your webapp can use the generated SVGs as static assets.
1. Simpler. On your dev environment, you can simply load mermaid diagrams on the client without async/placeholders/caching. Then, in prod, your webapp can use the generated SVGs as static assets.

2. You don't need to worry about Mermaid's cli, sandboxing puppeteer or downloading web browsers.
2. You don't need to worry about Mermaid's CLI, sandboxing puppeteer or downloading web browsers.

3. More secure because SVGs are generated by GitHub actions rather than individual collaborators.
3. More secure, since SVGs are generated by GitHub Actions rather than individual collaborators.

## Usage
4. Small overhead. This action only depends on one JS library: `mermaid-js/mermaid-cli`. It's also a composite action, meaning the `action.yml` at the repo's root contains details everything the action does.

### Drawbacks of This Solution

Here are some of the challenges you might face using this action:

1. Requires basic knowledge of Github Actions.

2. Can't easily simulate how the diagrams will look in prod. This means that styling mermaid diagrams can create a discrepancy between what you see on dev vs. what ends up on production.

3. Additional latency to deploy applications, and the need to use caching to speed that up (complex Github Actions operations).

## So, who's this for?

I'd say this is for hobby projects, and for folks with working knowledge of Github Actions. For more serious projects with larger teams, I'd use [Solution B (detailed above)](#b-async--caching)

## Contributions!! 😀

TBD...
Check out [CONTRIBUTING.md](./CONTRIBUTING.md) for details on how to contribute to this project!!
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ inputs:
default: npm
outputs:
input_files:
description: The input files that were used to regenerate mermaid diagrams
description: The input files that were used to generate mermaid diagrams
value: ${{steps.get_files_to_regen.outputs.input_files_to_regen}}
output_files:
description: The output files that were regenerated
description: The output files that were generated
value: ${{steps.get_files_to_regen.outputs.output_files_to_regen}}
branding:
icon: arrow-down-circle
Expand Down
Loading