Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/csharp-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ name: CSharp CI

on:
workflow_call:
outputs:
coverage:
description: >-
Integer line-coverage percentage (e.g. '85') produced by the
coverage matrix shard, or an empty string when no shard set
`coverage: true`. Feed it as the `percent` field of an entry in the
`update-badges` reusable workflow's `coverage-data` input.
value: ${{ jobs.coverage-output.outputs.coverage }}
matrix-status:
description: >-
JSON array of per-shard build results
`[{ "name", "os", "arch", "passed" }]`, one entry per matrix shard,
suitable to feed (after tagging each entry with a `lang`) into the
`update-badges` reusable workflow's `matrix-data` input.
value: ${{ jobs.matrix-output.outputs.matrix-status }}
secrets:
BOT_GITHUB_TOKEN:
description: >-
Expand Down Expand Up @@ -154,6 +169,16 @@ on:
required: false
type: string
default: 'nupkg'
artifact-prefix:
description: >-
Prefix applied to per-run artifact names (coverage-percent,
ci-result-<shard>) to namespace them by language. Set a distinct
value per language when multiple CI workflows (e.g. csharp-ci and
scala-ci) run as sibling jobs in the same caller run, so their
run-level artifacts don't collide.
required: false
type: string
default: 'csharp'

concurrency:
group: ${{ inputs.concurrency-group != '' && inputs.concurrency-group || format('{0}-{1}', github.workflow, github.ref) }}
Expand Down Expand Up @@ -294,6 +319,23 @@ jobs:
-f cobertura \
"${cobertura_files[@]}"

- name: Extract coverage percentage
if: ${{ matrix.coverage }}
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
mkdir -p coverage
.github-actions-helpers/scripts/extract_coverage.py "$GITHUB_WORKSPACE/coverage/merged.cobertura.xml" > coverage/coverage-percent.txt

- name: Upload coverage value
if: ${{ matrix.coverage }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage/coverage-percent.txt
if-no-files-found: error
retention-days: 1

- name: Code Coverage Summary Report
if: ${{ matrix.coverage }}
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
Expand Down Expand Up @@ -340,6 +382,83 @@ jobs:
fi
cat code-coverage-results.md >> "$GITHUB_STEP_SUMMARY"

- name: Record shard result
if: ${{ !cancelled() }}
env:
SHARD_NAME: ${{ matrix.name }}
SHARD_STATUS: ${{ job.status }}
run: |
set -euo pipefail
mkdir -p ci-result
printf '%s %s\n' "$SHARD_NAME" "$SHARD_STATUS" > "ci-result/${SHARD_NAME}.txt"

- name: Upload shard result
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact-prefix }}-ci-result-${{ matrix.name }}
path: ci-result/${{ matrix.name }}.txt
if-no-files-found: error
retention-days: 1

coverage-output:
name: coverage-output
needs: build-and-test
if: ${{ success() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
coverage: ${{ steps.read.outputs.coverage }}
steps:
- name: Download coverage value
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage

- name: Read coverage value
id: read
run: |
set -euo pipefail
coverage=""
if [ -f coverage/coverage-percent.txt ]; then
coverage=$(tr -d '[:space:]' < coverage/coverage-percent.txt)
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"

matrix-output:
name: matrix-output
needs: build-and-test
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers

- name: Download shard results
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: ${{ inputs.artifact-prefix }}-ci-result-*
path: ci-results
merge-multiple: true

- name: Aggregate shard results
id: aggregate
run: |
set -euo pipefail
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py ci-results)"
echo "matrix-status=$status" >> "$GITHUB_OUTPUT"

pack:
name: pack
if: ${{ inputs.pack }}
Expand Down
135 changes: 127 additions & 8 deletions .github/workflows/scala-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ name: Scala CI

on:
workflow_call:
outputs:
coverage:
description: >-
Integer line-coverage percentage produced by the coverage shard,
or an empty string when no shard set `coverage: true`. Suitable to
feed into the `update-badges` reusable workflow's `coverage-data`.
value: ${{ jobs.coverage-output.outputs.coverage }}
matrix-status:
description: >-
JSON array of per-shard build results
`[{ "name", "os", "arch", "passed" }]`, one entry per matrix shard.
value: ${{ jobs.matrix-output.outputs.matrix-status }}
inputs:
working-directory:
description: >-
Expand Down Expand Up @@ -94,6 +106,16 @@ on:
required: false
type: string
default: 'scala-coverage'
artifact-prefix:
description: >-
Prefix applied to per-run artifact names (coverage-percent,
ci-result-<shard>) to namespace them by language. Set a distinct
value per language when multiple CI workflows (e.g. csharp-ci and
scala-ci) run as sibling jobs in the same caller run, so their
run-level artifacts don't collide.
required: false
type: string
default: 'scala'
coverage-title:
description: >-
Heading prepended to the rendered coverage report so a reader
Expand Down Expand Up @@ -210,6 +232,33 @@ jobs:
mkdir -p coverage/merged
cp "$COBERTURA_PATH" coverage/merged/Cobertura.xml

- name: Checkout CI helpers
if: ${{ matrix.coverage }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers

- name: Extract coverage percentage
if: ${{ matrix.coverage }}
working-directory: ${{ github.workspace }}
env:
WORKING_DIRECTORY: ${{ inputs.working-directory }}
run: |
set -euo pipefail
mkdir -p coverage
.github-actions-helpers/scripts/extract_coverage.py "$WORKING_DIRECTORY/coverage/merged/Cobertura.xml" > coverage/coverage-percent.txt

- name: Upload coverage value
if: ${{ matrix.coverage }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage/coverage-percent.txt
if-no-files-found: error
retention-days: 1

- name: Code Coverage Summary Report
if: ${{ matrix.coverage }}
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
Expand All @@ -220,14 +269,6 @@ jobs:
hide_complexity: true
output: 'both'

- name: Checkout CI helpers
if: ${{ matrix.coverage }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers

- name: Sort coverage table alphabetically
if: ${{ matrix.coverage }}
uses: ./.github-actions-helpers/.github/actions/sort-coverage-table
Expand Down Expand Up @@ -272,3 +313,81 @@ jobs:
name: ${{ inputs.coverage-artifact-name }}
path: ${{ inputs.working-directory }}/${{ inputs.cobertura-path }}
retention-days: 14

- name: Record shard result
if: ${{ !cancelled() }}
working-directory: ${{ github.workspace }}
env:
SHARD_NAME: ${{ matrix.name }}
SHARD_STATUS: ${{ job.status }}
run: |
set -euo pipefail
mkdir -p ci-result
printf '%s %s\n' "$SHARD_NAME" "$SHARD_STATUS" > "ci-result/${SHARD_NAME}.txt"

- name: Upload shard result
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact-prefix }}-ci-result-${{ matrix.name }}
path: ci-result/${{ matrix.name }}.txt
if-no-files-found: error
retention-days: 1

coverage-output:
name: coverage-output
needs: build-and-test
if: ${{ success() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
coverage: ${{ steps.read.outputs.coverage }}
steps:
- name: Download coverage value
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage

- name: Read coverage value
id: read
run: |
set -euo pipefail
coverage=""
if [ -f coverage/coverage-percent.txt ]; then
coverage=$(tr -d '[:space:]' < coverage/coverage-percent.txt)
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"

matrix-output:
name: matrix-output
needs: build-and-test
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers

- name: Download shard results
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: ${{ inputs.artifact-prefix }}-ci-result-*
path: ci-results
merge-multiple: true

- name: Aggregate shard results
id: aggregate
run: |
set -euo pipefail
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py ci-results)"
echo "matrix-status=$status" >> "$GITHUB_OUTPUT"
64 changes: 64 additions & 0 deletions .github/workflows/update-badges.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright (c) 2026 Peaceful Studio OÜ
# SPDX-License-Identifier: Apache-2.0

name: Update Badges

on:
workflow_call:
inputs:
coverage-data:
description: >-
JSON array of per-language coverage entries, each
`{ "slug": <file-suffix>, "label": <badge-left-text>, "percent": <int|""> }`.
Entries with an empty or null `percent` are skipped (no badge for a
language that produced no coverage). Writes `coverage-<slug>.json`
per entry to the badge branch. Default `'[]'` writes no coverage badge.
required: false
type: string
default: '[]'
matrix-data:
description: >-
JSON array of per-shard CI results, each
`{ "lang": <file-prefix>, "os": <name>, "arch": <name>, "passed": <bool> }`.
Writes `ci-<lang>-<os>-<arch>.json` per entry. Default `'[]'` writes
no matrix badges.
required: false
type: string
default: '[]'
badge-branch:
description: >-
Orphan branch of the caller repo where the shields.io endpoint
JSON is written. The caller must grant `permissions: contents:
write` so the built-in GITHUB_TOKEN can push to it.
required: false
type: string
default: 'badges'

jobs:
update-badges:
name: update-badges
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers

- name: Write badges
if: ${{ inputs.coverage-data != '[]' || inputs.matrix-data != '[]' }}
env:
COVERAGE_DATA: ${{ inputs.coverage-data }}
MATRIX_DATA: ${{ inputs.matrix-data }}
BADGE_BRANCH: ${{ inputs.badge-branch }}
run: |
set -euo pipefail
.github-actions-helpers/scripts/write-badges.sh
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add live README status badges driven from CI: a new `update-badges.yaml` reusable workflow writes shields.io endpoint JSON to an orphan `badges` branch of the caller repo (built-in `GITHUB_TOKEN`, no gist or PAT), fed by new `coverage` and `matrix-status` outputs on `csharp-ci.yaml` and `scala-ci.yaml`, so a consumer README can show live coverage and per-platform (OS × arch) CI badges alongside the standard release/version badges. (#23)

## [2.1.0] - 2026-06-12

### Fixed
Expand Down
Loading