Metanorma: Aequitate Verum

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 + release in each per-document repository, and aggregate in 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.

Example 1. .github/workflows/generate.yml
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@v1

For 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.

Tip

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, or internal

  • 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 portal

The 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@v1 to compile documents, then runs actions-mn/release@v1 to publish changed documents as GitHub Releases.

actions-mn/.github/.github/workflows/metanorma-generate.yml

Checks out the repo inside the metanorma/metanorma Docker container, runs actions-mn/cache@v1 and actions-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: inherit

A 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: inherit

The 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 creating releases

Aggregate action inputs (actions-mn/aggregate)

Input Default Description

organizations

''

Comma-separated GitHub organizations to scan for repos

topic

metanorma-release

Repository topic for auto-discovery

repos

''

Explicit repo list (owner/repo), skips topic discovery

channels

''

Comma-separated channels to include. Empty = all.

stages

''

Comma-separated stages to include. Empty = all.

output-dir

_site/documents

Directory for extracted document files

canonicalize

true

Strip edition suffixes from filenames

concurrency

4

Max parallel repo processing

cache-dir

''

Directory for persistent cache (ETags, delta state)

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.