1+ name : Generate PDFs
2+
3+ # Triggered by:
4+ # - push to master that changes lecture markdown files
5+ # - manual dispatch (regenerates PDFs for ALL lecture files)
6+ on :
7+ push :
8+ branches :
9+ - master
10+ paths :
11+ - ' [0-9][0-9]_*.md'
12+ workflow_dispatch :
13+
14+ concurrency :
15+ group : pdf-generation
16+ cancel-in-progress : false
17+
18+ permissions :
19+ contents : write
20+
21+ jobs :
22+ # -------------------------------------------------------------------------
23+ # Job 1 – build PDFs and publish GitHub Releases
24+ # -------------------------------------------------------------------------
25+ generate_pdfs :
26+ name : Generate PDFs and create releases
27+ runs-on : ubuntu-latest
28+ steps :
29+ - name : Checkout repository
30+ uses : actions/checkout@v4
31+ with :
32+ fetch-depth : 2
33+ token : ${{ secrets.GITHUB_TOKEN }}
34+
35+ - name : Detect changed lecture files (push) or select all (dispatch)
36+ id : changes
37+ run : |
38+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
39+ # Regenerate PDFs for every numbered lecture file
40+ files=$(ls [0-9][0-9]_*.md 2>/dev/null | tr '\n' ' ')
41+ else
42+ files=$(git diff --name-only HEAD^ HEAD \
43+ | grep -E '^[0-9]{2}_.*\.md$' || true)
44+ files="${files//$'\n'/ }"
45+ fi
46+ echo "changed_files=${files}" >> "$GITHUB_OUTPUT"
47+ echo "Files to process: ${files}"
48+
49+ # -----------------------------------------------------------------------
50+ # Validate that every changed file has a version tag and auto-increment
51+ # the patch component when the corresponding release tag already exists.
52+ #
53+ # Logic:
54+ # 1. Extract version from the <!-- ... --> frontmatter.
55+ # → Fail hard if the tag is absent.
56+ # 2. Compute the release tag (same algorithm as the PDF step below).
57+ # → If the release tag already exists on GitHub, the user forgot to
58+ # bump the version. We increment the patch component in the file.
59+ # 3. Commit & push any modified files with "[skip ci]" so the PDF step
60+ # reads the correct (bumped) version from the working tree.
61+ # -----------------------------------------------------------------------
62+ - name : Check versions and auto-increment patch if tag already exists
63+ if : steps.changes.outputs.changed_files != ''
64+ run : |
65+ set -euo pipefail
66+
67+ git config user.name "github-actions[bot]"
68+ git config user.email "github-actions[bot]@users.noreply.github.com"
69+
70+ bumped_files=""
71+ error_files=""
72+
73+ for file in ${{ steps.changes.outputs.changed_files }}; do
74+ [ -f "$file" ] || continue
75+
76+ filename=$(basename "$file" .md)
77+
78+ # ── 1. Require a version tag ───────────────────────────────────
79+ version=$(grep -oP '(?m)^version:\s*\K[\d.]+' "$file" | head -1 || true)
80+ if [ -z "$version" ]; then
81+ echo "ERROR: $file has no 'version:' tag in its frontmatter." >&2
82+ error_files="$error_files $file"
83+ continue
84+ fi
85+
86+ # ── 2. Compute safe release tag ───────────────────────────────
87+ safe_tag=$(echo "$filename" \
88+ | tr '[:upper:]' '[:lower:]' \
89+ | sed -e 's/ä/ae/g' -e 's/ö/oe/g' -e 's/ü/ue/g' -e 's/ß/ss/g' \
90+ -e 's/Ä/ae/g' -e 's/Ö/oe/g' -e 's/Ü/ue/g' \
91+ -e 's/[^a-z0-9_-]//g')
92+ release_tag="${safe_tag}_v${version}"
93+
94+ # ── 3. Auto-increment patch if release tag already exists ─────
95+ if gh release view "$release_tag" > /dev/null 2>&1; then
96+ echo "Tag '$release_tag' already exists for $file – auto-incrementing patch."
97+
98+ # Increment last numeric component: e.g. 1.0.10 → 1.0.11
99+ new_version=$(echo "$version" | awk -F. '{$NF = $NF + 1; print}' OFS='.')
100+
101+ # Replace the version line inside the HTML comment frontmatter.
102+ # The sed pattern handles optional spaces around the colon.
103+ sed -i -E "s/^(version:[[:space:]]*)[0-9]+(\.[0-9]+)*/\1${new_version}/" "$file"
104+
105+ echo " $file: $version → $new_version"
106+ bumped_files="$bumped_files $file"
107+ else
108+ echo "Tag '$release_tag' is new – no version bump needed for $file."
109+ fi
110+ done
111+
112+ # ── Fail if any file was missing a version tag ───────────────────
113+ if [ -n "$error_files" ]; then
114+ echo ""
115+ echo "The following files are missing a 'version:' tag:" >&2
116+ for f in $error_files; do echo " $f" >&2; done
117+ echo "Please add 'version: X.Y.Z' to the HTML comment frontmatter." >&2
118+ exit 1
119+ fi
120+
121+ # ── Commit & push version bumps ───────────────────────────────────
122+ if [ -n "$bumped_files" ]; then
123+ git add $bumped_files
124+ git commit -m "ci: auto-increment patch version for changed courses [skip ci]"
125+ git pull --rebase origin master || true
126+ git push
127+ echo "Version bumps pushed to master."
128+ fi
129+ env :
130+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
131+
132+ - name : Set up Node.js
133+ if : steps.changes.outputs.changed_files != ''
134+ uses : actions/setup-node@v4
135+ with :
136+ node-version : ' 22'
137+
138+ - name : Install LiaScript exporter
139+ if : steps.changes.outputs.changed_files != ''
140+ run : npm install -g @liascript/exporter
141+
142+ - name : Generate PDFs and publish GitHub Releases
143+ if : steps.changes.outputs.changed_files != ''
144+ run : |
145+ set -euo pipefail
146+ mkdir -p /tmp/pdf_build
147+
148+ for file in ${{ steps.changes.outputs.changed_files }}; do
149+ [ -f "$file" ] || continue
150+
151+ filename=$(basename "$file" .md)
152+
153+ # Safe tag: lowercase, transliterate umlauts, remove non-ASCII
154+ safe_tag=$(echo "$filename" \
155+ | tr '[:upper:]' '[:lower:]' \
156+ | sed -e 's/ä/ae/g' -e 's/ö/oe/g' -e 's/ü/ue/g' -e 's/ß/ss/g' \
157+ -e 's/Ä/ae/g' -e 's/Ö/oe/g' -e 's/Ü/ue/g' \
158+ -e 's/[^a-z0-9_-]//g')
159+
160+ # Re-read version from the file (may have been bumped in the
161+ # previous step; the version tag is guaranteed to exist by now).
162+ version=$(grep -oP '(?m)^version:\s*\K[\d.]+' "$file" | head -1)
163+
164+ asset_name="${filename}_v${version}_Documentation"
165+ release_tag="${safe_tag}_v${version}"
166+
167+ echo "==> $file (tag: $release_tag)"
168+
169+ # Generate PDF
170+ liaex \
171+ --input "$file" \
172+ --format pdf \
173+ --output "/tmp/pdf_build/${asset_name}" \
174+ --pdf-timeout 60000
175+
176+ pdf_path="/tmp/pdf_build/${asset_name}.pdf"
177+
178+ if [ ! -f "$pdf_path" ]; then
179+ echo "ERROR: PDF not created for $file" >&2
180+ exit 1
181+ fi
182+
183+ # The version-check step guarantees the tag is new at this point.
184+ gh release create "$release_tag" \
185+ --title "PDF – ${filename} (v${version})" \
186+ --notes "Automated PDF export for \`${file}\` – version ${version}" \
187+ "$pdf_path"
188+ done
189+ env :
190+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
191+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments