Skip to content
Merged
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
223 changes: 144 additions & 79 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# Release workflow — triggered by version tags (v*).
# Builds artifacts, generates changelog via git-cliff, creates a GitHub Release,
# and produces SLSA provenance attestations.
# Release workflow — triggered by version tags (v*), or run by hand for an
# existing tag. Tests the package, packages its source with a CycloneDX SBOM,
# generates the changelog via git-cliff, attests build provenance and creates
# a GitHub Release.
name: Release

on:
push:
tags:
- 'v*'
# A change to this file runs the build and changelog jobs on its PR, so a
# broken release path shows up at review rather than at the next tag. The
# release job is skipped on PRs.
pull_request:
paths:
- '.github/workflows/release.yml'
workflow_dispatch:
inputs:
version:
description: 'Existing tag to release (e.g., v0.1.1). The build checks out this tag.'
required: true

permissions:
contents: read
Expand All @@ -18,115 +30,168 @@ jobs:
build:
name: Build Artifacts
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
# A cold install of the dependency closure takes more than 15 minutes on
# the hosted runners; 45 matches ci.yml.
timeout-minutes: 45
steps:
# On a manual run, build the tag being released, not the default branch,
# so the artefacts match the tagged and registered tree. The ref is
# qualified as refs/tags/: checkout resolves a bare name to a branch of
# that name before a tag (src/ref-helper.ts). On other events
# inputs.version is empty, ref is empty, and checkout uses the triggering ref.
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ inputs.version && format('refs/tags/{0}', inputs.version) || '' }}

- uses: julia-actions/setup-julia@fa02766e078afaaf09b14210362cee14137e6a32 # v3.0.2
with:
version: '1.10'

- uses: julia-actions/cache@9353db09a0f2bbbdec6f41791918c75d37391b36 # v3.5.0

# A library release is tested the way users install it: resolved fresh
# from Project.toml [compat].
- name: Ignore any committed manifest
run: rm -f Manifest.toml

# Causals, ZeroProb, Cliodynamics and their dependency AcceleratorGate
# are registered in the hyperpolymath registry, not in General.
- name: Add registries
run: julia -e 'using Pkg; Pkg.Registry.add("General"); Pkg.Registry.add(Pkg.RegistrySpec(url="https://github.com/hyperpolymath/julia-professional-registry"))'

- name: Build and test
run: julia --project -e 'using Pkg; Pkg.instantiate(); Pkg.build(); Pkg.test()'

- name: Build
# Pkg.dependencies() needs the resolved environment from the step above.
# JSON is not a dependency of this package, so it is loaded from a
# throwaway environment after the component list has been read.
- name: Generate SBOM
run: |
echo "Build your artifacts here"
# TODO: Replace with your build commands
# Examples:
# cargo build --release
# zig build -Doptimize=ReleaseFast
# gleam build
# mix release

# TODO: Upload build artifacts if needed
# - uses: actions/upload-artifact@ea165f8d65b6db9a8b71b5c2d1a090c0daf9c8bb # v4
# with:
# name: release-artifacts
# path: target/release/
julia --project -e '
using Pkg
pkg_info = Pkg.project()
components = [
Dict(
"type" => "library",
"name" => info.name,
"version" => info.version === nothing ? "stdlib" : string(info.version)
) for (uuid, info) in Pkg.dependencies() if info.is_direct_dep
]
Pkg.activate(temp=true)
Pkg.add("JSON")
using JSON
sbom = Dict(
"bomFormat" => "CycloneDX",
"specVersion" => "1.4",
"version" => 1,
"metadata" => Dict(
"component" => Dict(
"type" => "library",
"name" => pkg_info.name,
"version" => string(pkg_info.version)
)
),
"components" => components
)
open("sbom.json", "w") do f
JSON.print(f, sbom, 2)
end
'

- name: Package artifacts
run: |
mkdir -p dist
tar -czf dist/InvestigativeJournalism.jl-src.tar.gz src/ Project.toml README.adoc LICENSE
mv sbom.json dist/

- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-artifacts
path: dist/

changelog:
name: Generate Changelog
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
outputs:
changelog: ${{ steps.cliff.outputs.content }}
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ inputs.version && format('refs/tags/{0}', inputs.version) || '' }}
fetch-depth: 0

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Install git-cliff
run: |
curl -sSfL https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-$(uname -m)-unknown-linux-gnu.tar.gz \
| tar -xz --strip-components=1 -C /usr/local/bin/ git-cliff-*/git-cliff

- name: Generate changelog for this release
id: cliff
run: |
# Generate changelog for the current tag only
CHANGELOG=$(git cliff --latest --strip header)
# Write to output using delimiter to handle multiline
{
echo "content<<CLIFF_EOF"
echo "$CHANGELOG"
echo "CLIFF_EOF"
} >> "$GITHUB_OUTPUT"

- name: Update full CHANGELOG.md
run: |
git cliff --output CHANGELOG.md

- name: Upload updated CHANGELOG.md
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
with:
name: changelog
path: CHANGELOG.md
retention-days: 5
version: v2.14.2
config: cliff.toml
args: --latest --strip header

release:
name: Create GitHub Release
if: github.event_name != 'pull_request'
needs: [build, changelog]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
# Refuse anything but an existing vX.Y.Z[-pre] tag, so a mistyped manual
# run cannot make action-gh-release create a new tag on the default branch.
- name: Extract version
id: version
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
version="$INPUT_VERSION"
else
version="$GITHUB_REF_NAME"
fi
if ! printf '%s\n' "$version" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then
echo "::error::the version to release is not of the form vX.Y.Z or vX.Y.Z-pre"
exit 1
fi
if ! git ls-remote --exit-code --tags "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" "refs/tags/$version" > /dev/null; then
echo "::error::tag $version does not exist; push the tag first"
exit 1
fi
case "$version" in
*-*) prerelease=true ;;
*) prerelease=false ;;
esac
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"

- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-artifacts
path: release/

# TODO: Download build artifacts if uploading to the release
# - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
# with:
# name: release-artifacts
# path: artifacts/
- name: Checksums
run: |
cd release
sha256sum -- * > checksums.txt

- name: Attest build provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: 'release/*'

- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: InvestigativeJournalism.jl ${{ steps.version.outputs.version }}
body: ${{ needs.changelog.outputs.changelog }}
draft: false
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
prerelease: ${{ steps.version.outputs.prerelease }}
generate_release_notes: false
# TODO: Add artifact files to the release
# files: |
# artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

provenance:
name: SLSA Provenance
needs: [build]
permissions:
actions: read
id-token: write
contents: write
# SLSA generator must run in a separate, isolated workflow
# See: https://slsa.dev/spec/v1.0/requirements#build-l3
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
with:
base64-subjects: ""
# TODO: Replace with actual artifact hashes
# Generate with: sha256sum artifact | base64 -w0
# base64-subjects: "${{ needs.build.outputs.hashes }}"
files: release/*
Loading