Marp: Automate presentations builds

Automate building Marp presentations with GitHub CI and just

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

  1. 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
  2. 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-ignore setting to only build presentations you are working on
    • include to read the paths-ignore config 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:

  1. Reads CI config - Extracts presentation names from the paths-ignore list in .github/workflows/github.ci.yaml
  2. Finds all presentations - Looks for index.md files in src/*/ directories
  3. Filters - Skips presentations that are in the ignored list
  4. Builds in parallel - Runs just build for each active presentation (the & runs them simultaneously)
  5. Waits - Uses wait to ensure all builds complete before finishing
Built with Hugo
Theme Stack designed by Jimmy