feat(action): add Markdown to HTML publishing workflow

- Introduce a GitHub Action to convert Markdown files to HTML using Pandoc
- Configure steps for repository checkout, git setup, and branch management
- Include scripts for Markdown conversion, Pandoc installation, and git setup
- Automate the publishing of HTML output to a specified branch
This commit is contained in:
2025-07-09 21:28:30 +02:00
parent 93dda9a578
commit 4c489fe5f2
4 changed files with 360 additions and 0 deletions

85
action.yml Normal file
View File

@@ -0,0 +1,85 @@
name: "Markdown → HTML Publisher"
description: >
Converts all *.md files in the given path (recursively) to HTML via Pandoc
and commits the result into the specified branch as an orphan branch,
replacing the entire branch content and removing history.
inputs:
path:
description: "Directory to scan for Markdown files (default: $GITHUB_WORKSPACE)"
required: false
branch:
description: "Target branch that will receive the generated HTML"
required: false
default: pages
runs:
using: composite
steps:
# 0) Checkout repository with full history so we can delete/reset branches
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
# 1) Configure git author (uses default values if none supplied)
- name: Setup git author
shell: bash
run: |
bash "${{ github.action_path }}/scripts/setup-git.sh"
# 2) Install or ensure pandoc is present
- name: Install pandoc
shell: bash
run: |
bash "${{ github.action_path }}/scripts/install-pandoc.sh"
# 3) Convert Markdown files to HTML into a temporary directory
- name: Convert Markdown tree
shell: bash
run: |
SRC_DIR="${{ inputs.path }}"
[[ -z "$SRC_DIR" ]] && SRC_DIR="$GITHUB_WORKSPACE"
TMP_DIR="$(mktemp -d)"
bash "${{ github.action_path }}/scripts/convert-md-tree.sh" "$SRC_DIR" "$TMP_DIR"
echo "TMP_DIR=$TMP_DIR" >> "$GITHUB_ENV"
# 4) Delete target branch (if it exists) and create new orphan branch
- name: Recreate orphan target branch
shell: bash
run: |
TARGET_BRANCH="${{ inputs.branch }}"
[[ -z "$TARGET_BRANCH" ]] && TARGET_BRANCH="pages"
if git show-ref --quiet "refs/heads/$TARGET_BRANCH"; then
git branch -D "$TARGET_BRANCH"
fi
git switch --orphan "$TARGET_BRANCH"
git rm -rf . || true
echo "TARGET_BRANCH=$TARGET_BRANCH" >> "$GITHUB_ENV"
# 5) Replace contents of the branch with generated HTML
- name: Replace branch contents
shell: bash
run: |
shopt -s dotglob
rm -rf ./* || true
cp -r "$TMP_DIR"/. .
rm -rf "$TMP_DIR"
# 6) Commit and push changes if there are any
- name: Commit and push changes
shell: bash
run: |
TARGET_BRANCH="${TARGET_BRANCH:-${{ inputs.branch }}}"
[[ -z "$TARGET_BRANCH" ]] && TARGET_BRANCH="pages"
if git status --porcelain | grep -q .
then
git add .
git commit -m "Automatic HTML publish: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
git push --force origin "$TARGET_BRANCH"
else
echo "ℹ️ No changes to commit."
fi

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env bash
#=== HELP START ===
# convert-md-tree.sh – Recursive Markdown → HTML conversion (Pandoc)
#
# Usage:
# ./convert-md-tree.sh [options] <source_dir> <dest_dir>
#
# Options:
# -n, --dry-run Show what would be done without converting
# -p, --pandoc-args <string> Extra arguments passed to pandoc (e.g. "-t html5")
# -h, --help Show this help message
#
# Arguments:
# <source_dir> Root directory containing .md files
# <dest_dir> Destination directory for the HTML output
#
# Behaviour:
# • Preserves the directory structure in the destination.
# • Creates required sub-directories automatically.
# • Emits clear errors for missing programs or invalid parameters.
#=== HELP END ===
set -euo pipefail
# ───────────────────────────────────────────────
# Defaults
DRY_RUN=false
PANDOC_ARGS=""
# ───────────────────────────────────────────────
# 0 Help function -------------------------------------------------------------
show_help() {
sed -n '/^#=== HELP START ===/,/^#=== HELP END ===/ {
/^#=== HELP START ===/d
/^#=== HELP END ===/d
s/^#//
p
}' "$0"
}
# 1 Parse options -------------------------------------------------------------
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-p|--pandoc-args)
[[ $# -lt 2 ]] && { echo "❌ Option $1 requires an argument"; exit 1; }
PANDOC_ARGS="$2"; shift 2 ;;
--) shift; break ;;
-*)
echo "❌ Unknown option: $1"; exit 1 ;;
*) POSITIONAL+=("$1"); shift ;;
esac
done
set -- "${POSITIONAL[@]}"
[[ $# -lt 2 ]] && { echo "❌ Missing <source_dir> and/or <dest_dir>"; echo; show_help; exit 1; }
SRC="$1"
DEST="$2"
# 2 Validate arguments --------------------------------------------------------
[[ ! -d "$SRC" ]] && { echo "❌ Source directory does not exist: $SRC"; exit 1; }
mkdir -p "$DEST" || { echo "❌ Cannot create/access dest dir: $DEST"; exit 1; }
# Remove trailing slash for path manipulation
SRC="${SRC%/}"
# 3 Check required tools ------------------------------------------------------
need() { command -v "$1" >/dev/null || { echo "$1 is missing"; exit 1; }; }
need pandoc; need find; need mkdir
# 4 Process files -------------------------------------------------------------
echo "🔍 Scanning for .md files in $SRC"
COUNT_TOTAL=$(find "$SRC" -type f -name '*.md' | wc -l | tr -d ' ')
[[ "$COUNT_TOTAL" -eq 0 ]] && { echo "ℹ️ No Markdown files found – nothing to do."; exit 0; }
echo "📄 Found $COUNT_TOTAL Markdown file(s)."
CONVERTED=0
while IFS= read -r -d '' FILE; do
REL_PATH="${FILE#$SRC/}"
OUT_FILE="$DEST/${REL_PATH%.md}.html"
mkdir -p "$(dirname "$OUT_FILE")"
if $DRY_RUN; then
echo "🧪 Would convert: $FILE -> $OUT_FILE"
else
pandoc $PANDOC_ARGS "$FILE" -s -o "$OUT_FILE"
echo "✅ Converted: $FILE -> $OUT_FILE"
fi
((CONVERTED++))
done < <(find "$SRC" -type f -name '*.md' -print0)
if $DRY_RUN; then
echo "🧪 Dry-run complete – $CONVERTED file(s) would be processed."
else
echo "🏁 Finished – $CONVERTED file(s) converted."
fi

147
scripts/install-pandoc.sh Executable file
View File

@@ -0,0 +1,147 @@
#!/usr/bin/env bash
#=== HELP START ===
# install-pandoc.sh – Installs the Pandoc binary
#
# Usage:
# sudo ./install-pandoc.sh [options] [<version>]
#
# Options:
# -a, --arch <amd64|arm64> Target architecture (default: auto-detected)
# -d, --dir <path> Installation directory (default: /usr/local/bin)
# -n, --dry-run Download and verify, but DO NOT install
# -h, --help Show this help message
#
# Arguments:
# <version> Pandoc version to install (default: latest)
#
# Behaviour:
# • If pandoc is already in PATH and no version was requested, the script exits.
# • If a specific version was requested, the installed version is only upgraded if it differs.
#=== HELP END ===
set -euo pipefail
REPO="jgm/pandoc"
# ───────────────────────────────────────────────
# Defaults
INSTALL_DIR_DEFAULT="/usr/local/bin"
INSTALL_DIR="$INSTALL_DIR_DEFAULT"
ARCH_INPUT=""
VERSION="latest"
DRY_RUN=false
USER_VERSION_SPECIFIED=false
# ───────────────────────────────────────────────
# 0 Help function
show_help() {
sed -n '/^#=== HELP START ===/,/^#=== HELP END ===/ {
/^#=== HELP START ===/d
/^#=== HELP END ===/d
s/^#//
p
}' "$0"
}
# 1 Parse options
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-a|--arch)
[[ $# -lt 2 ]] && { echo "❌ Option $1 requires an argument"; exit 1; }
ARCH_INPUT="$2"; shift 2 ;;
-d|--dir)
[[ $# -lt 2 ]] && { echo "❌ Option $1 requires an argument"; exit 1; }
INSTALL_DIR="$2"; shift 2 ;;
*) POSITIONAL+=("$1"); shift ;;
esac
done
if [[ ${#POSITIONAL[@]} -gt 0 ]]; then
VERSION="${POSITIONAL[0]}"
USER_VERSION_SPECIFIED=true
fi
# 2 Detect / normalize architecture
detect_arch() {
case "$(uname -m)" in
x86_64) echo "linux-amd64" ;;
aarch64|arm64) echo "linux-arm64" ;;
*) echo "unsupported" ;;
esac
}
ARCH_TAG=""
if [[ -n "$ARCH_INPUT" ]]; then
case "$ARCH_INPUT" in
amd64|linux-amd64) ARCH_TAG="linux-amd64" ;;
arm64|aarch64|linux-arm64) ARCH_TAG="linux-arm64" ;;
*) echo "❌ Unknown architecture: $ARCH_INPUT"; exit 1 ;;
esac
else
ARCH_TAG=$(detect_arch)
if [[ "$ARCH_TAG" == "unsupported" ]]; then
echo "❌ Automatic architecture detection failed. Please use -a."
exit 1
fi
fi
# 3 Pre-check existing installation
if command -v pandoc >/dev/null; then
INSTALLED_PATH=$(command -v pandoc)
INSTALLED_VER=$(pandoc -v | head -n1 | awk '{print $2}')
if ! $USER_VERSION_SPECIFIED; then
echo "✅ pandoc already present at ${INSTALLED_PATH} (version ${INSTALLED_VER}) – nothing to do."
exit 0
else
if [[ "$INSTALLED_VER" == "$VERSION" ]]; then
echo "✅ pandoc ${INSTALLED_VER} already installed – requested version matches."
exit 0
else
echo "ℹ️ pandoc ${INSTALLED_VER} installed, upgrading to ${VERSION}..."
fi
fi
fi
# 4 Check required tools
need() { command -v "$1" >/dev/null || { echo "$1 is missing"; exit 1; }; }
need curl; need tar; need grep; need sed; need awk; need jq
# 5 Fetch release info
if [[ "$VERSION" == "latest" ]]; then
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
else
API_URL="https://api.github.com/repos/${REPO}/releases/tags/${VERSION}"
fi
echo "🔍 Fetching release info (${API_URL})…"
JSON=$(curl -fsSL "$API_URL") || { echo "❌ Failed to fetch release info"; exit 1; }
VERSION=$(jq -r '.tag_name' <<< "$JSON") || { echo "❌ Could not extract version"; exit 1; }
ASSET_URL=$(jq -r '.assets[]?.browser_download_url' <<< "$JSON" |
grep -E "pandoc-.*-${ARCH_TAG}\.tar\.gz$" | head -n1)
[[ -z "$ASSET_URL" ]] && { echo "❌ Matching asset not found for architecture ${ARCH_TAG}"; exit 1; }
ASSET_FILE=$(basename "$ASSET_URL")
echo "📦 Downloading pandoc ${VERSION} (${ASSET_FILE}) …"
TMP=$(mktemp -d)
curl -#L -o "${TMP}/${ASSET_FILE}" "$ASSET_URL"
# 6 Extract
tar -C "$TMP" -xzf "${TMP}/${ASSET_FILE}"
# 7 Locate binary
BIN_PATH=$(find "$TMP" -type f -name pandoc -perm -u+x | head -n1)
[[ -z "$BIN_PATH" ]] && { echo "❌ Binary not found"; exit 1; }
# 8 Install / Dry-run
if $DRY_RUN; then
echo "🧪 Dry-run: verified pandoc binary at ${BIN_PATH}"
echo "ℹ️ Would install to: ${INSTALL_DIR}/pandoc (sudo install -m755 …)"
echo "✅ pandoc $("$BIN_PATH" -v | head -n1) would be installed successfully"
else
sudo install -m755 "$BIN_PATH" "${INSTALL_DIR}/pandoc"
echo "✅ pandoc $(pandoc -v | head -n1) installed in ${INSTALL_DIR}"
fi

29
scripts/setup-git.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
# Optional inputs (positionals) or fallback to environment variables
AUTHOR_NAME="${1:-${CI_COMMIT_AUTHOR_NAME:-CI Bot}}"
AUTHOR_EMAIL="${2:-${CI_COMMIT_AUTHOR_EMAIL:-ci@bot.none}}"
echo "🔧 Setting up git author:"
echo " Name : $AUTHOR_NAME"
echo " Email: $AUTHOR_EMAIL"
git config --global user.name "$AUTHOR_NAME"
git config --global user.email "$AUTHOR_EMAIL"
# Check if the values were set correctly
CONFIGURED_NAME=$(git config --global user.name)
CONFIGURED_EMAIL=$(git config --global user.email)
if [[ "$CONFIGURED_NAME" != "$AUTHOR_NAME" ]]; then
echo "❌ Error: Git username was not set correctly!" >&2
exit 1
fi
if [[ "$CONFIGURED_EMAIL" != "$AUTHOR_EMAIL" ]]; then
echo "❌ Error: Git email was not set correctly!" >&2
exit 1
fi
echo "✅ Git configuration completed successfully."