• Shell 50.6%
  • Ruby 35.3%
  • Dockerfile 14.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-24 16:44:41 +02:00
.github/workflows Update actions/checkout action to v7 2026-06-22 00:56:15 +00:00
extensions Asciidoc-ify strong and italic 2026-07-21 17:00:21 +02:00
.dockerignore Add .dockerignore to keep the build small 2026-06-02 13:41:45 +02:00
.gitignore Add .gitignore file 2026-06-02 08:49:03 +02:00
build.config Add support for asciidoctor options 2026-06-12 11:30:25 +02:00
build.sh Remove embedding of images 2026-07-24 16:19:56 +02:00
Dockerfile Update HTML to allow for dark mode images 2026-07-24 16:17:14 +02:00
LICENSE Add default CC-BY-SA license 2026-06-02 13:41:45 +02:00
README.md Add support for asciidoctor options 2026-06-12 11:30:25 +02:00
renovate.json Pin version of theme repositories 2026-06-02 18:50:49 +02:00

iSAQB curriculum builder (Ruby toolchain)

A self-contained, native-Ruby toolchain for rendering iSAQB Advanced Level curricula to PDF and HTML. It replaces the JVM/Gradle (asciidoctorj) build with native MRI asciidoctor + asciidoctor-pdf — significantly faster, and a single pinned Docker image instead of a JDK, Gradle, and theme submodules in every curriculum repo.

The repo lives at isaqb-org/curriculum-builder. It produces a multi-arch Docker image published to the GitHub Container Registry (GHCR); curriculum repos pull a pinned digest of that image and need nothing else installed locally.

Migration status: the legacy Gradle build remains authoritative. This image is being rolled out and validated first; the Gradle tooling will be retired step by step once the image is released and tested in real curriculum repos.

What's in the image

  • asciidoctor + asciidoctor-pdf — pinned to explicit versions in the Dockerfile; Renovate keeps them current. No source highlighter is bundled (the curriculum sets no :source-highlighter:).
  • extensions/ — the two render extensions, baked into /opt/isaqb/extensions:
    • learning-goals-overview.rb — builds the "Learning Goals Overview" section and tags the first chapter arabic-start. Ruby port of the legacy SpecialTocTreeprocessor.groovy, verified functionally equivalent.
    • robust-page-numbering.rb — roman front matter up to the first chapter, arabic after. It reads the arabic-start role set by the treeprocessor, so the two extensions must stay in sync.
  • pdf-theme and html-theme — cloned from their main branch into /opt/isaqb, so curriculum repos no longer need the theme submodules.
  • build.sh — the render matrix (format × language × suffix); the in-image ENTRYPOINT.
  • build.config — baked as the default config; a curriculum's own build.config overrides it.

The image is built for both linux/amd64 and linux/arm64, so it runs natively on Intel/AMD hosts and on Apple Silicon / ARM CI alike.

Using it from a curriculum repo

A curriculum repo consumes the published image directly: bind-mount the repo at /project, pass the per-curriculum keys as environment, and let the image entrypoint render into ./build. Curriculum repos typically wrap this in a small build script of their own (which lives there, not in this repo).

docker run --rm -u "$(id -u):$(id -g)" \
  -v "$PWD:/project" -w /project \
  -e CURRICULUM_FILE=curriculum-template -e LANGUAGES="DE EN" \
  ghcr.io/isaqb-org/curriculum-builder:2026              # all languages × pdf+html -> ./build

Positional arguments select a subset of the matrix:

# single format + language
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/project" -w /project \
  -e CURRICULUM_FILE=curriculum-template \
  ghcr.io/isaqb-org/curriculum-builder:2026 pdf DE

# + suffix tag
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/project" -w /project \
  -e CURRICULUM_FILE=curriculum-template \
  ghcr.io/isaqb-org/curriculum-builder:2026 pdf DE REMARKS

No JDK, Gradle, Ruby, or theme submodules required on the host.

build.config

The few per-curriculum keys (the native equivalent of the old build.gradle ext{} block):

Key Meaning Default
CURRICULUM_FILE AsciiDoc root in docs/ (no .adoc); also the output name required (build fails if unset)
LANGUAGES Space-separated languages to build DE EN
SUFFIX_TAGS Space-separated extra variants exposed as {suffix} (empty)
PREPRESS PDF print/book layout (recto starts + binding margins) false
ASCIIDOCTOR_COMMON_OPTS Extra CLI flags passed to both asciidoctor and asciidoctor-pdf (e.g. --trace) (empty)
ASCIIDOCTOR_PDF_OPTS Extra CLI flags passed to asciidoctor-pdf only (empty)
ASCIIDOCTOR_HTML_OPTS Extra CLI flags passed to asciidoctor (HTML) only (empty)

Precedence (highest first): docker run -e environment > repo build.config > baked default. CI sets the version separately via RELEASE_VERSION.

Pinning the image

Curriculum repos reference the image by tag plus an explicit digest:

IMAGE="ghcr.io/isaqb-org/curriculum-builder:2026"
DIGEST="sha256:<digest from the latest release>"

The tag is the release major (the year — see Versioning). Pin DIGEST to a published digest, found on the release page or in the workflow run summary; the digest is what guarantees a reproducible, immutable image. A Renovate custom manager in the curriculum repo keeps both current — match the IMAGE/DIGEST lines of that repo's build script and set "datasourceTemplate": "docker".

Versioning and publishing

Releases use iSAQB calendar versioning: v<year>.<minor>-rev<patch>, e.g. v2026.1-rev0 (major 2026, minor 1, patch rev0). A release tag publishes these image tags: 2026.1-rev0, 2026.1, 2026, and latest. Downstream pins the major (:2026) plus a digest.

.github/workflows/build-image.yml runs on every pull request, on push to main, and on tags (plus manual workflow_dispatch). The build/publish split is deliberate:

  • PR / main / dispatch — builds the image for both architectures (the arm64 image is cross-built under QEMU) to validate that it compiles. Nothing is pushed.
  • tag — builds, pushes the multi-arch manifest list to ghcr.io/<owner>/<repo>, and creates a matching GitHub release. The release notes and the run summary both carry the digest to pin downstream. Because the pushed digest is the manifest-list (index) digest, a single pinned digest resolves to the right CPU architecture automatically.

Renovate (renovate.json, extending the isaqb-org/renovate-config preset) keeps the pinned base image and gems current. Merge a bump, cut a release tag, and a fresh digest is published. Themes are not pinned — they track main and refresh on each rebuild.

Local image build (development)

docker build -t ghcr.io/isaqb-org/curriculum-builder:dev .

# build for a specific platform:
docker build --platform linux/arm64 -t ghcr.io/isaqb-org/curriculum-builder:dev .

# override a theme ref or repo while developing:
docker build --build-arg PDF_THEME_REF=some-branch -t ghcr.io/isaqb-org/curriculum-builder:dev .

Parity with the legacy Gradle build

build.sh reproduces the build.gradle attribute map and task matrix exactly (icons, version labels, include-configuration tag filter, data-uri, compress, prepress, theme/font dirs, HTML stylesheet), and the two extensions are ported verbatim in behaviour. PDF output has been confirmed equivalent to the legacy build.

This checklist documents what the toolchain must reproduce — useful when validating a migration step or investigating a rendering regression after a gem bump:

  1. "Learning Goals Overview" / "Lernziele im Überblick" lists every LG-*/LZ-* goal, each linking to the right anchor.
  2. Roman page numbers on copyright/TOC/overview, switching to arabic at the first chapter.
  3. PDF outline/bookmarks and in-TOC page numbers match.
  4. Fonts, theme colours, and embedded images render identically.