Tip
If you are new to Marp, check out this article that covers the basics: Marp: Create Presentations using Markdown
If you did not use just before, you can refer to this article: My Favorite FOSS Tools: Just
TL;DR
- Prerequisites
- You version your Marp presentations on GitHub
- You have Actions enabled in your repository
- You are somewhat familiar with the just command runner and GitHub workflows
- Setup
- version your presentation markdown files in a GitHub repository
- use the marp CLI tooling with in a just recipe to build presentations
- add a GitHub workflow with
paths-ignoresetting to only build presentations you are working on - include to read the
paths-ignoreconfig from the github.ci.yaml into your build recipe
Project Structure
.
├── dist
│ └── .gitkeep
├── .editorconfig
│ └── workflows
│ └── github.ci.yaml
├── .gitignore
├── justfile
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── README.md
└── src
├── marp-structure
│ └── index.md
└── testing-strategy
└── index.md
Warning
The CI settings as well as the just recipes depend on the directory structure. The following code and explanations are based on the setup above. If your setup differs, you need to adjust the paths used in the github.ci.yaml and the justfile.
Relevant folders and files
dist
This is the target directory for built presentations. The .gitkeep was added to have the empty folder versioned so I could test my CI builds.
src
Inside source, I chose to version every presentation in its own folder, which is named descriptively. The index.md file in each folder inside src holds the actual marp markdown presentation.
justfile
I like utilizing the flexibility of just as a command runner. In the justfile, I defined recipes to build my presentations like “build-all”, to build all presentations or “build-active”, to only build the ones I am currently working on.
github.ci.yaml
on:
push:
branches:
- main
paths-ignore:
- README.md
- .github/workflows/github.ci.yaml
- justfile
- package.json
- .gitignore
- .editorconfig
- src/marp-structure/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Install just
uses: extractions/setup-just@v3
- name: Install Fish Shell
run: sudo apt-add-repository ppa:fish-shell/release-3;sudo apt install fish
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install project dependencies
run: just install
- name: build active PDFs
run: just build-active
- name: Commit Artifacts
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Build PDF
Click to view detailed explanations about the workflow file
Section 1: Triggers
on:
push:
branches:
- main
paths-ignore:
- README.md
- .github/workflows/github.ci.yaml
- justfile
- package.json
- .gitignore
- .editorconfig
- src/marp-structure/**
# ... (more presentation directories)
The workflow runs whenever code is pushed to the main branch. paths-ignore however disables the
trigger for the specified paths. This comes in handy to save resources and time. When the README or configuration changes, there is no need to build the presentations. I also use this setting to stop building presentations that are already finished.
Section 2: Concurrency Control
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
This configuration controls how multiple workflow runs are handled. The group directive groups workflows by name and branch reference. cancel-in-progress: true leads to the cancellation of workflows that are still running, if a new workflow is triggered. This makes sense, as the newer status quo will “win” anyways regarding generated presentations.
Section 3: The Job Definition
jobs:
build:
runs-on: ubuntu-latest
This section defines the job(s) that are carried out by the workflow. You can chose a name you see fit. Since we are building presentations (PDFs) I chose “build”. And this job will run on a free GitHub hosted Ubuntu runner.
Section 4: The Steps
Each step runs sequentially to prepare the environment and build the PDFs:
steps:
- name: Checkout Code
uses: actions/checkout@v6
Downloads your repository code. The uses keyword references a pre-built action from the GitHub Marketplace.
- name: Install just
uses: extractions/setup-just@v3
Installs the just command runner used to execute build commands from the justfile.
- name: Install Fish Shell
run: sudo apt-add-repository ppa:fish-shell/release-3;sudo apt install fish
Installs Fish shell because the justfile recipes use Fish syntax. The run keyword executes shell commands directly.
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '24'
Installs Node.js v24, needed for the Marp and Mermaid tools. The with keyword passes parameters to actions.
- name: Install pnpm
uses: pnpm/action-setup@v4
Installs pnpm, the package manager this project uses.
- name: Install project dependencies
run: just install
Runs pnpm install to install the npm packages (marp-cli, mermaid-cli) needed to build presentations.
- name: build active PDFs
run: just build-active
Builds PDFs for non-ignored presentations, converting Markdown files to PDFs in the dist/ folder.
- name: Commit Artifacts
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Build PDF
Automatically commits the rebuilt PDFs back to the repository, keeping them up-to-date.
Info
This workflow is based on the tooling I use. You do not have to use pnpm, fish shell or just. Node comes with npm and you can write shell or bash-scripts in your justfile, too. If you do not want to use just, you can also write scripts and run them directly in your github workflow by specifying run: myscript.sh.
justfile
# run pnpm install
install:
pnpm install
# build only active presentation PDFs (not in CI paths-ignore)
build-active:
#!/usr/bin/env fish
# Extract ignored presentation paths from GitHub CI config
set -l ignored_presentations (awk -F'/' '/src\/.*\/\*\*/ {print $2}' .github/workflows/github.ci.yaml | grep -v assets)
for slide in src/*/index.md
set -l dirname (path dirname "$slide")
set -l presentation (path basename "$dirname")
# Skip if presentation is in ignored list
if contains $presentation $ignored_presentations
echo "Skipping $presentation (ignored in CI config)"
continue
end
pushd "$dirname"
just build &
popd
end
wait
Click to view detailed explanations about the justfile
Recipe: install
# run pnpm install
install:
pnpm install
Installs the project dependencies defined in the package.json.
Recipe: build-active
# build only active presentation PDFs (not in CI paths-ignore)
build-active:
#!/usr/bin/env fish
# Extract ignored presentation paths from GitHub CI config
set -l ignored_presentations (awk -F'/' '/src\/.*\/\*\*/ {print $2}' .github/workflows/github.ci.yaml | grep -v assets)
for slide in src/*/index.md
set -l dirname (path dirname "$slide")
set -l presentation (path basename "$dirname")
# Skip if presentation is in ignored list
if contains $presentation $ignored_presentations
echo "Skipping $presentation (ignored in CI config)"
continue
end
pushd "$dirname"
just build &
popd
end
wait
Builds PDF files for presentations that aren’t marked as ignored in the CI configuration.
The process:
- Reads CI config - Extracts presentation names from the
paths-ignorelist in.github/workflows/github.ci.yaml - Finds all presentations - Looks for
index.mdfiles insrc/*/directories - Filters - Skips presentations that are in the ignored list
- Builds in parallel - Runs
just buildfor each active presentation (the&runs them simultaneously) - Waits - Uses
waitto ensure all builds complete before finishing