Compares JSON schemas between two platform-lsp releases and produces a
combined HTML report showing structural changes across FOLIO module
repositories — including changes inside git submodules (e.g. acq-models).
platform-lsp tag (e.g. R1-2025-csp-5)
└─ install-applications.json
└─ app-platform-minimal-2.0.49
└─ app-platform-minimal.template.json
└─ modules: [ { "name": "mod-users", "version": "19.5.4" } ]
↓
tag v19.5.4 in folio-org/mod-users
↓
diff ramls/**/*.json between base and head tags
+ diff schemas inside submodules (filtered per module)
The workflow:
- resolve_tags — clones
platform-lsp, reads two release tags, walksinstall-applications.json→ app templates → module versions. - schema-diff — calls
reusable-schema-changes.ymlfor each module in parallel (dynamic matrix frommodules.json). - collect-reports — downloads per-module Markdown reports, combines them into a single timestamped HTML artifact, then cleans up intermediate artifacts.
Some modules share submodules (e.g. ramls/acq-models) that contain
schemas for many different modules. The report builder traces $ref and
!include references from the parent repo's schemas into the submodule,
then performs a BFS to find all transitively referenced files. Only
changes relevant to the current module appear in its report section.
The module list is defined in a single config file —
.github/config/modules.json:
{
"modules": [
"mod-inventory-storage",
"mod-users",
"mod-inventory",
"mod-orders-storage",
"mod-invoice-storage",
"mod-finance-storage",
"mod-organizations-storage",
"mod-circulation-storage",
"mod-source-record-storage"
]
}Adding or removing a module is a one-line edit in this file — no YAML or script changes required.
- Go to Actions → Run Schema Changes Reporting → Run workflow.
- Leave both fields empty.
- Click Run workflow.
The two most recent platform-lsp tags (by creation date) are used
automatically.
- Go to Actions → Run Schema Changes Reporting → Run workflow.
- Fill in both fields:
| Field | Example | Description |
|---|---|---|
| base | R1-2025-csp-4 |
Earlier release (before) |
| head | R1-2025-csp-5 |
Later release (after) |
- Click Run workflow.
Both fields must be filled or both left empty. Partial input (only one field) will fail with an error.
- Open the completed workflow run.
- Scroll to Artifacts.
- Download
report_<timestamp>— contains a self-contained HTML file and the raw Markdown source.
The reusable workflow can also be triggered independently from a module repo, without going through central reporting.
The workflow runs automatically when ramls/**/*.json (or other
configured paths) change. On a pull request a sticky comment with the
diff report is posted. On push the report is available in the Actions
summary and as an artifact.
Triggered automatically when a release is published. Compares the new release tag against the previous one.
- Go to the module repo → Actions → Schema changes → Run workflow.
- Optionally fill in base and head (any tag, branch, or commit SHA).
- Leave both empty to compare the two most recent tags automatically.
- Click Run workflow.
central-reporting.yml
│
├─ resolve_tags (ubuntu-latest)
│ ├─ clone platform-lsp (blob-less)
│ ├─ list tags sorted by creation date
│ ├─ select HEAD/BASE releases (manual or auto)
│ ├─ parse install-applications.json for each release
│ ├─ fetch app-*.template.json (parallel, throttled)
│ ├─ extract module versions (e.g. mod-users v19.5.4 → tag v19.5.4)
│ └─ output: modules JSON matrix [{name, base, head, base_app, head_app}]
│
├─ schema-diff (matrix: each module in parallel)
│ ├─ checkout target module repo (full history + submodules, blob:none)
│ ├─ checkout workflow scripts (.schema-reporting/)
│ ├─ refs-resolver.sh → determine BASE/HEAD refs
│ ├─ report-builder.sh → diff schemas + submodule schemas (filtered)
│ └─ upload report.md artifact
│
└─ collect-reports (ubuntu-latest)
├─ download report-* artifacts
├─ reports-combiner.sh → merge into MD + HTML (pandoc)
├─ artifacts-cleaner.sh → delete per-module artifacts
└─ upload report_<timestamp>.{md,html}
folio-org/schema-changes-reporting
├─ .github/
│ ├─ config/
│ │ └─ modules.json ← module list (one-line edits)
│ ├─ scripts/
│ │ ├─ lib/
│ │ │ └─ helpers.sh ← shared functions
│ │ ├─ releases-resolver.sh ← resolve module versions from platform-lsp
│ │ ├─ refs-resolver.sh ← determine BASE/HEAD git refs
│ │ ├─ report-builder.sh ← build schema diff report (main logic)
│ │ ├─ reports-combiner.sh ← merge per-module reports into HTML
│ │ └─ artifacts-cleaner.sh ← delete intermediate artifacts
│ ├─ tests/
│ │ ├─ helpers.bats ← unit tests for helpers.sh
│ │ ├─ refs-resolver.bats ← unit tests for refs-resolver.sh
│ │ ├─ report-builder.bats ← unit tests for report-builder.sh
│ │ └─ reports-combiner.bats ← unit tests for reports-combiner.sh
│ └─ workflows/
│ ├─ central-reporting.yml ← orchestrator workflow
│ └─ reusable-schema-changes.yml ← reusable diff workflow
├─ run-tests.sh ← local test runner
└─ README.md
All diff logic lives in reusable-schema-changes.yml in this
repository. central-reporting.yml calls it via a dynamic matrix —
no workflow file is required in the module repo for central reporting.
Module repos only need a thin caller workflow for local triggers (push, pull request, release, manual dispatch):
folio-org/mod-users
└─ .github/workflows/schema-changes-reporting.yml ← thin caller
Add the module name to .github/config/modules.json:
{
"modules": [
"mod-inventory-storage",
"mod-users",
...
"mod-new-module"
]
}That's it — after merging, the next central reporting run will include the new module automatically.
Skip this step if you only need the module in combined reports.
Create .github/workflows/schema-changes-reporting.yml in the module repo:
name: Schema changes (mod-new-module)
on:
push:
branches: ["**"]
paths:
- "ramls/**/*.json"
- "!ramls/examples/**"
- "!ramls/raml-util/**"
pull_request:
branches: ["**"]
paths:
- "ramls/**/*.json"
- "!ramls/examples/**"
- "!ramls/raml-util/**"
release:
types: [published]
workflow_dispatch:
inputs:
base:
description: "Optional base ref (tag/commit)"
required: false
head:
description: "Optional head ref (tag/commit)"
required: false
permissions:
contents: read
pull-requests: write
jobs:
schema-changes:
uses: folio-org/schema-changes-reporting/.github/workflows/reusable-schema-changes.yml@master
with:
repository: ${{ github.repository }}
base: ${{ github.event.inputs.base || '' }}
head: ${{ github.event.inputs.head || '' }}
permissions:
contents: read
pull-requests: write| Script | Purpose |
|---|---|
releases-resolver.sh |
Clones platform-lsp, resolves module versions for two releases, outputs a JSON matrix |
refs-resolver.sh |
Determines BASE/HEAD git refs based on event type (push, PR, release, dispatch) |
report-builder.sh |
Diffs ramls/**/*.json between two refs, handles submodules with per-module filtering |
reports-combiner.sh |
Merges per-module report.md files into a single MD + self-contained HTML via pandoc |
artifacts-cleaner.sh |
Deletes intermediate report-* artifacts after the combined report is built |
lib/helpers.sh |
Shared functions: truncate_or_all, status_label, resolve_path, extract_json_refs, extract_raml_includes, is_path_in_submodule, strip_sm_prefix |
Unit tests use bats-core. Each
script has a corresponding .bats file in .github/tests/.
brew install bats-core bash jq python3# Run all tests
./run-tests.sh
# Run tests for a specific script
./run-tests.sh helpers
./run-tests.sh refs-resolver
./run-tests.sh report-builder
./run-tests.sh reports-combinerNote: bash 4+ is required (for
mapfile). On macOS the runner auto-detects Homebrew bash if the system bash is too old.
| Input | Required | Default | Description |
|---|---|---|---|
base |
No | (auto-detected) | Base ref (tag, branch, or SHA) |
head |
No | (auto-detected) | Head ref (tag, branch, or SHA) |
repository |
No | github.repository |
Target module repo (e.g. folio-org/mod-users) |
artifact_name |
No | auto-generated | Name for the uploaded report artifact |
schema_paths |
No | (empty) | Extra pathspec globs for JSON schemas (space-separated) |
| Symptom | Cause | Fix |
|---|---|---|
Release tag 'X' not found |
Typo in manual input | Check available tags in platform-lsp |
Specify both base and head |
Only one field filled | Fill both or leave both empty |
No report artifacts found |
All module workflows failed | Check individual module job logs |
| Module not in report | Module not in any platform-lsp app template |
Verify module name matches what appears in app templates |
| Submodule shows unrelated schemas | $ref tracing didn't find references |
Check that parent schemas properly reference submodule files |
bash 4+ required |
macOS ships bash 3.x | brew install bash |
Declared in workflow files — no manual configuration needed.
| Workflow | contents |
pull-requests |
actions |
|---|---|---|---|
central-reporting.yml |
read | write | write |
reusable-schema-changes.yml |
read | write | — |
Module jobs receive secrets: inherit so that GITHUB_TOKEN is
forwarded for repository checkout, artifact upload, and PR comments.