diff --git a/.github/release-please/.release-please-manifest.json b/.github/release-please/.release-please-manifest.json deleted file mode 100644 index e18ee077..00000000 --- a/.github/release-please/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.0.0" -} diff --git a/.github/release-please/release-please-config.json b/.github/release-please/release-please-config.json deleted file mode 100644 index 9ca39d5d..00000000 --- a/.github/release-please/release-please-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "packages": { - ".": { - "release-type": "simple", - "component": "Security Frameworks" - } - } -} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index 71ea4a82..00000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Release Please - -on: - push: - branches: [main] - -permissions: - contents: write - pull-requests: write - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Create or update release PR - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 - with: - target-branch: main - config-file: .github/release-please/release-please-config.json - manifest-file: .github/release-please/.release-please-manifest.json diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 00000000..f9366dc6 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,114 @@ +name: Release PR + +on: + push: + branches: [main] + workflow_dispatch: + inputs: + version: + description: "Override version (e.g. v0.0.3). Leave blank to auto-bump." + required: false + type: string + +permissions: {} + +concurrency: + group: release-pr + cancel-in-progress: false + +jobs: + release-pr: + if: ${{ github.event_name == 'workflow_dispatch' || !contains(github.event.head_commit.message, 'chore(release):') }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + fetch-depth: 0 + fetch-tags: true + persist-credentials: false + + - name: Compute next version + id: version + env: + OVERRIDE: ${{ inputs.version }} + run: | + set -euo pipefail + if [ -n "${OVERRIDE:-}" ]; then + if ! printf '%s' "$OVERRIDE" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid version override '$OVERRIDE' (expected vMAJOR.MINOR.PATCH)" + exit 1 + fi + next="$OVERRIDE" + echo "Using override: $next" + else + latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1 || true) + if [ -z "$latest" ]; then + next="v0.0.1" + else + v="${latest#v}" + major="${v%%.*}" + rest="${v#*.}" + minor="${rest%%.*}" + patch="${rest#*.}" + next="v${major}.${minor}.$((patch + 1))" + fi + echo "Latest tag: ${latest:-none}" + fi + echo "next=$next" >> "$GITHUB_OUTPUT" + echo "Next version: $next" + + - name: Ensure changelog exists + run: touch CHANGELOG.md + + - name: Generate changelog + uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0 + id: git-cliff + with: + config: cliff.toml + github_token: ${{ secrets.GITHUB_TOKEN }} + args: --unreleased --tag ${{ steps.version.outputs.next }} --prepend CHANGELOG.md + env: + OUTPUT: RELEASE_NOTES.md + + - name: Check for releasable changes + id: check + run: | + set -euo pipefail + if grep -qE '^- ' RELEASE_NOTES.md; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "Changes found:" + cat RELEASE_NOTES.md + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" + echo "Nothing to release since the last tag. Skipping." + fi + + - name: Write VERSION + if: steps.check.outputs.has_changes == 'true' + env: + NEXT: ${{ steps.version.outputs.next }} + run: printf '%s\n' "$NEXT" > VERSION + + - name: Open release PR + if: steps.check.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 + with: + token: ${{ secrets.GITHUB_TOKEN }} + sign-commits: true + base: main + branch: release/${{ steps.version.outputs.next }} + title: "chore(release): ${{ steps.version.outputs.next }}" + commit-message: "chore(release): ${{ steps.version.outputs.next }}" + labels: release + delete-branch: true + add-paths: | + CHANGELOG.md + VERSION + body: | + Automated release PR for **${{ steps.version.outputs.next }}**. + + Review the changelog, edit if needed, then merge. + Merging creates the tag and publishes the GitHub Release. \ No newline at end of file diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 00000000..34031293 --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,61 @@ +name: Publish Release + +on: + pull_request: + types: [closed] + +permissions: {} + +jobs: + publish: + if: > + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'main' && + startsWith(github.event.pull_request.head.ref, 'release/') && + contains(github.event.pull_request.labels.*.name, 'release') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + + - name: Read version + id: version + run: | + set -euo pipefail + tag="$(head -n1 VERSION | tr -d '[:space:]')" + if ! printf '%s' "$tag" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid VERSION content '$tag' (expected vMAJOR.MINOR.PATCH)" + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Extract latest changelog section + run: | + awk '/^## /{ if (found) exit; found=1; next } found' CHANGELOG.md > RELEASE_NOTES.md + cat RELEASE_NOTES.md + + - name: Create tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + run: | + set -euo pipefail + sha="$(git rev-parse HEAD)" + gh api "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f "ref=refs/tags/${TAG}" \ + -f "sha=${sha}" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + run: | + set -euo pipefail + gh release create "$TAG" \ + --title "$TAG" \ + --notes-file RELEASE_NOTES.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d3f5a12f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ + diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..3e6e86a5 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,49 @@ +[changelog] +header = """ +# Changelog + +All notable changes to the Security Frameworks are documented here. +""" + +body = """ +{% macro remote_url() %}https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}{% endmacro %} +{% if version %} +## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }}) +{% else %} +## Unreleased +{% endif %} +{% for group, commits in commits | filter(attribute="merge_commit", value=false) | group_by(attribute="group") %} +### {{ group | striptags | trim | upper_first }} +{% for commit in commits %} +{%- if commit.remote.pr_title -%} +{%- set entry = commit.remote.pr_title -%} +{%- else -%} +{%- set entry = commit.message | split(pat="\n") | first -%} +{%- endif %} +- {{ entry | trim }}{% if commit.remote.pr_number %} ([#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }})){% endif %}{% if commit.remote.username %} by @{{ commit.remote.username }}{% endif %} +{%- endfor %} +{% endfor %} +""" + +trim = true + +[git] +conventional_commits = false +filter_unconventional = false +filter_commits = false +sort_commits = "oldest" + +commit_parsers = [ + { message = "^Merge pull request", skip = true }, + { message = "^Merge branch", skip = true }, + { message = "^chore\\(release\\)", skip = true }, + + { field = "github.pr_labels", pattern = "dependencies", skip = true }, + + { field = "github.pr_labels", pattern = "content:add", group = " New content" }, + { field = "github.pr_labels", pattern = "content:update", group = " Updated content" }, + { field = "github.pr_labels", pattern = "enhancement", group = " Enhancements" }, + { field = "github.pr_labels", pattern = "certifications", group = " Certifications" }, + + { message = ".*", group = " Other" }, +] \ No newline at end of file