Metanorma
On this page

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.

`.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</code></td><td>GitHubtokenforcreatingreleases</td></tr></tbody></table></div></section><sectionid="actionreferenceaggregateactioninputsactionsmnaggregate"><h3id="actionreferenceaggregateactioninputsactionsmnaggregate">Aggregateactioninputs(actionsmn/aggregate)</h3><divclass="tablewrapper"><table><thead><tr><td>Input</td><td>Default</td><td>Description</td></tr></thead><tbody><tr><td><code>organizations</code></td><td>’‘</td><td>CommaseparatedGitHuborganizationstoscanforrepos</td></tr><tr><td><code>topic</code></td><td><code>metanormarelease</code></td><td>Repositorytopicforautodiscovery</td></tr><tr><td><code>repos</code></td><td>’‘</td><td>Explicitrepolist(<code>owner/repo</code>),skipstopicdiscovery</td></tr><tr><td><code>channels</code></td><td>’‘</td><td>Commaseparatedchannelstoinclude.Empty=all.</td></tr><tr><td><code>stages</code></td><td>’‘</td><td>Commaseparatedstagestoinclude.Empty=all.</td></tr><tr><td><code>outputdir</code></td><td><code>site/documents</code></td><td>Directoryforextracteddocumentfiles</td></tr><tr><td><code>canonicalize</code></td><td><code>true</code></td><td>Stripeditionsuffixesfromfilenames</td></tr><tr><td><code>concurrency</code></td><td><code>4</code></td><td>Maxparallelrepoprocessing</td></tr><tr><td><code>cachedir</code></td><td>’‘</td><td>Directoryforpersistentcache(ETags,deltastate)</td></tr><tr><td><code>token</code></td><td><code>{{ github.token }}</code> </td><td> GitHub token for creating releases</td></tr></tbody></table></div></section><section id="_action_reference_aggregate_action_inputs_actionsmnaggregate"><h3 id="_action_reference_aggregate_action_inputs_actionsmnaggregate">Aggregate action inputs (`actions-mn/aggregate`)</h3><div class="table-wrapper"><table><thead><tr><td> Input </td><td> Default </td><td> Description</td></tr></thead><tbody><tr><td> <code>organizations</code> </td><td> ’‘ </td><td> Comma-separated GitHub organizations to scan for repos</td></tr><tr><td> <code>topic</code> </td><td> <code>metanorma-release</code> </td><td> Repository topic for auto-discovery</td></tr><tr><td> <code>repos</code> </td><td> ’‘ </td><td> Explicit repo list (<code>owner/repo</code>), skips topic discovery</td></tr><tr><td> <code>channels</code> </td><td> ’‘ </td><td> Comma-separated channels to include. Empty = all.</td></tr><tr><td> <code>stages</code> </td><td> ’‘ </td><td> Comma-separated stages to include. Empty = all.</td></tr><tr><td> <code>output-dir</code> </td><td> <code>_site/documents</code> </td><td> Directory for extracted document files</td></tr><tr><td> <code>canonicalize</code> </td><td> <code>true</code> </td><td> Strip edition suffixes from filenames</td></tr><tr><td> <code>concurrency</code> </td><td> <code>4</code> </td><td> Max parallel repo processing</td></tr><tr><td> <code>cache-dir</code> </td><td> ’‘ </td><td> Directory for persistent cache (ETags, delta state)</td></tr><tr><td> <code>token</code> </td><td> <code>{{ 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.