GitHub Actions Integration
Overview
Metanorma provides a set of composable GitHub Actions at `actions-mn` that handle compilation, site deployment, per-document releases, and multi-repo aggregation.
GitHub is the only platform that supports the full feature set: compilation, site deployment, organization-scale publishing (per-document releases, channel-based routing, and multi-repo aggregation).
For the conceptual architecture and config file schemas, see Organization-Scale Publishing Setup. For other CI/CD platforms, see CI/CD integration.
The actions-mn ecosystem
| Action | Role | Used in |
| `actions-mn/setup`:: | Installs the `metanorma` CLI for Ubuntu, MacOS, or Windows. Skip if using the `metanorma/metanorma` Docker image (Metanorma is already installed). | Any workflow |
| `actions-mn/cache`:: | Caches files used by and generated by Metanorma during compilation (temporary fonts, Relaton data, site output) to speed up subsequent runs. | Any build workflow |
| `actions-mn/compile`:: | Compiles a single Metanorma document to HTML/PDF/XML/etc. | Single-document repos |
| `actions-mn/site-gen`:: | Compiles **all** documents from a `metanorma.yml` manifest using `metanorma site generate`. | Any repo with multiple documents |
| `actions-mn/build-and-publish`:: | Meta-action: calls `cache` then `site-gen` then uploads a Pages artifact. Combines three steps into one. | Simple site deployments |
| `actions-mn/deploy-pages`:: | Deploys to GitHub Pages with PR preview support. Handles three scenarios: push to main (deploy), PR open/update (preview), PR close (cleanup). | Any Pages deployment |
| `actions-mn/release`:: | Discovers compiled documents, detects changes, and publishes them as per-document GitHub Releases with channel-based routing and stage gating. | Organization-scale publishing |
| `actions-mn/aggregate`:: | Aggregates released Metanorma documents from multiple GitHub repositories with channel-based filtering. Discovers repos by topic, downloads artifacts, and generates a structured index. | Portal repositories |
| `actions-mn/setup-flavors`:: | Installs extra or private Metanorma flavor gems (e.g., BSI, NIST, Plateau). | Repos using non-standard flavors |
Pipeline patterns
Two pipeline patterns cover all use cases:
- Simple site (single repo)
- Use
build-and-publish+deploy-pages. On every push, compile the site and deploy it. This is what most single-team or single-document projects use. - Organization-scale (multi-repo)
- Use
site-gen+releasein each per-document repository, andaggregatein the portal repository. Each repo publishes its own documents as GitHub Releases. The portal discovers and collects them.
Simple site workflow
The following workflow compiles a Metanorma site and deploys it to GitHub Pages.
name: generate
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
container:
image: metanorma/metanorma:latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache Metanorma assets
uses: actions-mn/cache@v1
- name: Metanorma generate site
uses: actions-mn/build-and-publish@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
agree-to-terms: true
deploy:
if: ${{ github.ref == 'refs/heads/main' }}
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1For more examples, see the `actions-mn/build-and-publish` action documentation.
Organization-scale publishing
The release + aggregate pipeline is for organizations that publish many documents across many repositories into a unified portal.
For a step-by-step tutorial, see the blog post. For the full config schemas and setup checklist, see Organization-Scale Publishing Setup.
When to use
Use this pipeline when you have:
- Multiple document repositories that each produce one or more documents
- A portal site that aggregates documents from all repositories
- Different document types (standards, reports, directives) that should appear on different portals or in different sections
- A need to ensure that internal or draft documents never appear on public portals
For single-document sites, actions-mn/build-and-publish is simpler and sufficient.
Architecture
The publication pipeline has three stages:
Author Per-document repo GitHub Portal repo
sources/ .github/workflows Releases .github/workflows
*.adoc → site-gen + release → per-document → aggregate → site
releases + index.json| Component | Role | Where |
metanorma site generate | Compiles AsciiDoc sources to HTML/PDF/XML/RXL | Per-document repo (CI) |
actions-mn/release | Discovers compiled documents, detects changes, publishes per-document GitHub Releases | Per-document repo (CI) |
| GitHub Releases | Stores release artifacts (zip) + metadata (channels, stage, edition) | GitHub |
actions-mn/aggregate | Discovers repos by topic, filters by channel, downloads and indexes released documents | Portal repo (CI) |
Channels
A channel is an audience/category pair that determines where a document appears:
- audience:
public,members, orinternal - category: free-form identifier
public/standards ← published standards, visible to everyone
public/reports ← conference and technical reports
members/internal-review ← only visible to organization members
internal/working-draft ← never aggregated by any external portalThe publisher sets the channel. The aggregator filters by it. A portal cannot override or discover channels the publisher didn't assign.
For the channel routing config, see Release manifest.
Per-document repository structure
A repository participating in the organization-scale pipeline follows this structure:
my-documents/
.github/workflows/
release.yml ← compiles and releases on push to main
generate.yml ← builds preview site on push and PRs
sources/
cc-s-51015.adoc ← AsciiDoc authoring sources
metanorma.yml ← compilation manifest (lists source files)
metanorma.release.yml ← release manifest (channel routing + stage gating)
Gemfile ← typically just "metanorma-cli"For the contents of metanorma.release.yml, see Release manifest.
Reusable workflows
Per-document repositories can use reusable workflows from actions-mn/.github to avoid defining their own build logic:
actions-mn/.github/.github/workflows/metanorma-release.yml- Checks out the repo, runs
actions-mn/site-gen@v1to compile documents, then runsactions-mn/release@v1to publish changed documents as GitHub Releases. actions-mn/.github/.github/workflows/metanorma-generate.yml- Checks out the repo inside the
metanorma/metanormaDocker container, runsactions-mn/cache@v1andactions-mn/build-and-publish@v1, then deploys the preview to GitHub Pages.
A per-document repository's entire release workflow is:
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
paths: ['sources/**', 'metanorma.yml', 'metanorma.release.yml']
workflow_dispatch:
permissions:
contents: write
jobs:
release:
uses: actions-mn/.github/.github/workflows/metanorma-release.yml@main
with:
default-visibility: private
secrets: inheritA preview workflow builds a site on every push and PR:
# .github/workflows/generate.yml
name: Generate
on:
push:
pull_request:
jobs:
build:
uses: actions-mn/.github/.github/workflows/metanorma-generate.yml@main
secrets: inheritThe `metanorma-release` topic
The portal discovers document repositories via the metanorma-release GitHub topic. Add it to each participating repository:
gh api repos/{owner}/{repo}/topics -X PUT --field names='["metanorma-release"]'If topic-based discovery is not suitable (e.g., aggregating from multiple organizations), use the repos input on the aggregate action:
- uses: actions-mn/aggregate@v1
with:
repos: 'my-org/repo-a,other-org/repo-b'
channels: 'public/standards'Document stages in CI
Stage filtering can be applied at the CI level to narrow the manifest policy for a specific run:
- uses: actions-mn/release@v1
with:
stages: 'published,final-draft'For the full stage codes table, see Document stages.
Published releases are immutable — the tag is created once and never overwritten. Draft releases are rolling — the same tag is updated in-place as the draft evolves.
Action reference
Release action inputs (`actions-mn/release`)
| Input | Default | Description |
source-path | . | Source path containing the metanorma configuration |
output-dir | _site | Output directory containing compiled documents |
release-config | metanorma.release.yml | Release manifest file |
default-visibility | public | Default visibility for unlisted documents (public, private, or members) |
force | false | Force release even if content hash matches |
force-replace | ’‘ | Comma-separated doc IDs or glob patterns to force-replace (deletes and recreates specific releases) |
include-pattern | * | Glob pattern to filter documents for release |
stages | ’‘ | Comma-separated stages to release. Empty = all. |
channels | ’‘ | Override channels for all documents. Empty = use manifest. |
concurrency | 4 | Max parallel document processing |
token | {{ github.token }} | GitHub token for API access |
Release metadata protocol
Each GitHub Release published by actions-mn/release carries structured metadata:
content-hash:abc123...
<!-- mn-release-metadata
{"version":1,"id":"cc-s-51015","channels":["public/standards"],
"stage":"published","edition":"1","title":"My Standard"}
-->
## CC/S 51015
| Field | Value |
|---|---|
| Document | cc-s-51015 |
| Edition | 1 |
| Status | published |
| Channels | public/standards |The content-hash on the first line enables incremental aggregation — unchanged releases are skipped. The mn-release-metadata JSON block (inside an HTML comment) is parsed by actions-mn/aggregate for channel filtering and indexing.
Force-replacing releases
Published releases are immutable by default. To selectively re-release a specific document (e.g. to fix bad metadata), use the force-replace input:
- uses: actions-mn/release@v1
with:
force-replace: 'cc-s-51015' # exact doc ID
# or: force-replace: 'cc-s-*' # glob pattern
token: ${{ secrets.GITHUB_TOKEN }}Only matched documents are deleted and recreated. Other documents in the same repo are completely unaffected.