diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml new file mode 100644 index 0000000..8b20ccd --- /dev/null +++ b/.github/workflows/conventional-commits.yml @@ -0,0 +1,135 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 The Contributors to Eclipse OpenSOVD (see CONTRIBUTORS) +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 + +name: Conventional Commits + +on: + workflow_call: + inputs: + validate-pr-title: + description: "Validate PR title against Conventional Commits format" + type: boolean + default: true + validate-commits: + description: > + Validate individual commit subjects in the PR using prek with the conventional-pre-commit hook from the caller's .pre-commit-config.yaml + type: boolean + default: true + types: + description: > + Newline-separated list of allowed conventional commit types for PR title validation (via amannn/action-semantic-pull-request). Commit subject validation uses the types configured in the caller's .pre-commit-config.yaml. + type: string + default: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + scopes: + description: "Newline-separated list of allowed scopes for PR title validation (empty = any scope allowed)" + type: string + default: "" + require-scope: + description: "Require a scope in PR title" + type: boolean + default: false + subject-pattern: + description: "Regex pattern the PR title subject (text after type/scope) must match" + type: string + default: "" + subject-pattern-error: + description: "Error message when subject-pattern fails" + type: string + default: "" + ignore-authors: + description: "Newline-separated list of commit author emails to ignore (e.g. bots)" + type: string + default: "" + prek-version: + description: "Version of prek to install (empty = latest)" + type: string + default: "" + +permissions: + contents: read + pull-requests: read + +jobs: + pr-title: + name: PR Title + if: ${{ inputs.validate-pr-title && github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: ${{ inputs.types }} + scopes: ${{ inputs.scopes }} + requireScope: ${{ inputs.require-scope }} + subjectPattern: ${{ inputs.subject-pattern }} + subjectPatternError: ${{ inputs.subject-pattern-error }} + + commit-subjects: + name: Commit Subjects + if: ${{ inputs.validate-commits && github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: astral-sh/setup-uv@v7 + + - name: Install prek + shell: bash + run: | + if [[ -n "${{ inputs.prek-version }}" ]]; then + uv tool install "prek==${{ inputs.prek-version }}" + else + uv tool install prek + fi + + - name: Validate commit subjects + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + IGNORE_AUTHORS: ${{ inputs.ignore-authors }} + shell: bash + run: | + set -euo pipefail + + declare -A ignore_map + while IFS= read -r email; do + email=$(echo "$email" | xargs) + [[ -n "$email" ]] && ignore_map["$email"]=1 + done <<< "$IGNORE_AUTHORS" + + fail=0 + for sha in $(git rev-list "$BASE_SHA..$HEAD_SHA"); do + author_email=$(git log -1 --format='%ae' "$sha") + if [[ -n "${ignore_map[$author_email]:-}" ]]; then + continue + fi + + git log -1 --format=%B "$sha" > /tmp/commit-msg + if ! prek run --hook-stage commit-msg \ + --commit-msg-filename /tmp/commit-msg conventional-pre-commit; then + echo "::error::Non-conventional commit: $(git log -1 --format='%h %s' "$sha")" + fail=1 + fi + done + exit "$fail" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9b1e79b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 The Contributors to Eclipse OpenSOVD (see CONTRIBUTORS) +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 + +name: Release + +on: + workflow_call: + inputs: + version: + description: > + Release version string. Typically the git tag (e.g. "v1.2.3") or a rolling identifier like "nightly" or "latest". Used for archive names and the GitHub Release title. + type: string + required: true + artifact-pattern: + description: > + Pattern for actions/download-artifact to match build artifacts. Each matching artifact directory is expected to contain the binary. Example: "my-app-*" matches "my-app-x86_64-unknown-linux-gnu", etc. + type: string + required: true + binary-name: + description: > + Base name of the binary inside each artifact directory (without extension). Windows artifacts are expected to have a .exe suffix automatically. Example: "opensovd-gateway" + type: string + required: true + git-cliff-version: + description: "Version of git-cliff to install" + type: string + default: "2.11.0" + git-cliff-args: + description: > + Extra arguments for git-cliff (e.g. "--config cliff.toml"). The workflow automatically uses --unreleased for nightly/latest and --current for versioned tags. + type: string + default: "" + attestation: + description: "Generate build provenance attestation for release artifacts" + type: boolean + default: true + prerelease: + description: > + Mark the release as a prerelease. When not explicitly set, nightly and latest releases are automatically marked as prereleases. + type: boolean + default: false + delete-existing: + description: > + Delete an existing release with the same tag before creating a new one. Useful for rolling releases (nightly, latest). + type: boolean + default: false + draft: + description: "Create the release as a draft" + type: boolean + default: false + archive-prefix: + description: > + Prefix for archive filenames. Defaults to the binary-name input. Archives are named: --. + type: string + default: "" + +permissions: + contents: write + attestations: write + id-token: write + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: taiki-e/install-action@v2 + with: + tool: git-cliff@${{ inputs.git-cliff-version }} + + - uses: actions/download-artifact@v8 + with: + pattern: ${{ inputs.artifact-pattern }} + path: artifacts + + - name: Generate changelog + shell: bash + run: | + VERSION="${{ inputs.version }}" + EXTRA_ARGS="${{ inputs.git-cliff-args }}" + if [[ "$VERSION" == "nightly" || "$VERSION" == "latest" ]]; then + git cliff --unreleased $EXTRA_ARGS -o CHANGELOG.md + else + git cliff --current $EXTRA_ARGS -o CHANGELOG.md + fi + + - name: Package artifacts + shell: bash + run: | + VERSION="${{ inputs.version }}" + BINARY="${{ inputs.binary-name }}" + PREFIX="${{ inputs.archive-prefix }}" + PREFIX="${PREFIX:-$BINARY}" + + mkdir -p release + + for dir in artifacts/${BINARY}-*/; do + [ -d "$dir" ] || continue + target=$(basename "$dir" | sed "s/${BINARY}-//") + + if [[ "$target" == *windows* ]]; then + zip "release/${PREFIX}-${VERSION}-${target}.zip" "${dir}${BINARY}.exe" + else + chmod +x "${dir}${BINARY}" + tar -czf "release/${PREFIX}-${VERSION}-${target}.tar.gz" -C "$dir" "$BINARY" + fi + done + + cd release + for file in *.tar.gz *.zip; do + [ -f "$file" ] || continue + sha512sum "$file" > "$file.sha512" + done + + - name: Attest build provenance + if: ${{ inputs.attestation }} + uses: actions/attest-build-provenance@v4 + with: + subject-path: 'release/*' + + - name: Delete existing release + if: ${{ inputs.delete-existing }} + env: + GH_TOKEN: ${{ github.token }} + run: gh release delete "${{ inputs.version }}" --yes --cleanup-tag || true + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + VERSION="${{ inputs.version }}" + + ARGS=() + ARGS+=(--title "$VERSION") + ARGS+=(--notes-file CHANGELOG.md) + + if [[ "${{ inputs.draft }}" == "true" ]]; then + ARGS+=(--draft) + fi + + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + ARGS+=(--prerelease) + elif [[ "$VERSION" == "nightly" || "$VERSION" == "latest" ]]; then + ARGS+=(--prerelease) + fi + + gh release create "$VERSION" "${ARGS[@]}" release/* diff --git a/README.md b/README.md index f5b2edb..926b041 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,98 @@ jobs: clippy-deny-warnings: 'true' ``` +### Using the Conventional Commits Workflow + +The `conventional-commits.yml` workflow validates PR titles and individual commit messages against the [Conventional Commits](https://www.conventionalcommits.org/) specification. + +Commit subject validation uses [prek](https://prek.j178.dev/) to run the `conventional-pre-commit` hook from the caller's `.pre-commit-config.yaml`. This ensures CI validates exactly what developers see locally. + +**Prerequisites**: The calling repository must have a `.pre-commit-config.yaml` with the `conventional-pre-commit` hook configured: + +```yaml +# .pre-commit-config.yaml (in your repository) +repos: + - repo: https://github.com/compilerla/conventional-pre-commit + rev: v4.4.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] +``` + +**Usage**: + +```yaml +name: CI + +on: + pull_request: + +jobs: + conventional-commits: + uses: eclipse-opensovd/cicd-workflows/.github/workflows/conventional-commits.yml@main + with: + validate-pr-title: true + validate-commits: true +``` + +#### Available Inputs + +- `validate-pr-title` (optional): Validate PR title against Conventional Commits format. Defaults to `true`. +- `validate-commits` (optional): Validate individual commit subjects using prek with the caller's `.pre-commit-config.yaml`. Defaults to `true`. +- `types` (optional): Newline-separated list of allowed conventional commit types for PR title validation. Commit subject validation uses types from the caller's `.pre-commit-config.yaml`. Defaults to `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. +- `scopes` (optional): Newline-separated list of allowed scopes for PR title validation. Empty means any scope is allowed. +- `require-scope` (optional): Require a scope in PR title. Defaults to `false`. +- `subject-pattern` (optional): Regex pattern the PR title subject must match. +- `subject-pattern-error` (optional): Error message when `subject-pattern` fails. +- `ignore-authors` (optional): Newline-separated list of commit author emails to skip (e.g. bot accounts). +- `prek-version` (optional): Version of prek to install. Defaults to latest. + +### Using the Release Workflow + +The `release.yml` workflow packages build artifacts into platform archives, generates a changelog with git-cliff, creates SHA-512 checksums, optionally attests build provenance, and publishes a GitHub Release. + +```yaml +name: Release + +on: + push: + tags: ['v*'] + +jobs: + build: + # Your build matrix that uploads artifacts named like: + # my-app-x86_64-unknown-linux-gnu + # my-app-aarch64-unknown-linux-gnu + # my-app-x86_64-pc-windows-msvc + # Each artifact contains the binary (e.g. "my-app" or "my-app.exe"). + ... + + release: + needs: build + uses: eclipse-opensovd/cicd-workflows/.github/workflows/release.yml@main + with: + version: ${{ github.ref_name }} + artifact-pattern: "my-app-*" + binary-name: "my-app" + permissions: + contents: write + attestations: write + id-token: write +``` + +#### Available Inputs + +- `version` (required): Release version string (e.g. `v1.2.3`, `nightly`, `latest`). Used for archive names and the GitHub Release title. +- `artifact-pattern` (required): Pattern for `actions/download-artifact` to match build artifacts (e.g. `my-app-*`). +- `binary-name` (required): Base name of the binary inside each artifact directory, without extension. Windows artifacts are expected to have a `.exe` suffix. +- `git-cliff-version` (optional): Version of git-cliff to install. Defaults to `2.11.0`. +- `git-cliff-args` (optional): Extra arguments for git-cliff (e.g. `--config cliff.toml`). The workflow automatically uses `--unreleased` for nightly/latest and `--current` for versioned tags. +- `attestation` (optional): Generate build provenance attestation for release artifacts. Defaults to `true`. +- `prerelease` (optional): Mark the release as a prerelease. Nightly and latest releases are automatically marked as prereleases. Defaults to `false`. +- `delete-existing` (optional): Delete an existing release with the same tag before creating a new one. Useful for rolling releases. Defaults to `false`. +- `draft` (optional): Create the release as a draft. Defaults to `false`. +- `archive-prefix` (optional): Prefix for archive filenames. Defaults to the `binary-name` value. Archives are named `--.`. + ## Actions in This Repository ### Pre-commit Action (`pre-commit-action/`)