From d7a5ed25eac5be4a7bd1c71f2a26726a6c5485f4 Mon Sep 17 00:00:00 2001 From: andonly1348 Date: Wed, 6 May 2026 04:47:09 +0500 Subject: [PATCH] feat: implement semantic versioning and automated release orchestration (#709) - Added docs/VERSIONING_POLICY.md defining SemVer and Conventional Commit rules. - Implemented .github/workflows/changelog.yml for automated CHANGELOG.md updates. - Implemented .github/workflows/commit-check.yml to enforce PR title standards. - Updated .pre-commit-config.yaml with conventional-pre-commit hook. - Refined .github/workflows/release.yml with automated version bumping, file synchronization, and Docker tagging. - Updated CHANGELOG.md with proper template and [Unreleased] section. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: andonly1348 <279141437+andonly1348@users.noreply.github.com> --- .github/workflows/changelog.yml | 11 +++--- .github/workflows/commit-check.yml | 36 +++++++++++++++++++ .github/workflows/release.yml | 58 ++++++++++++++---------------- .pre-commit-config.yaml | 10 ++++++ docs/VERSIONING_POLICY.md | 18 +++++++--- 5 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/commit-check.yml diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 92a72830..40de880c 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -4,10 +4,11 @@ on: push: branches: - main - paths-ignore: - - 'CHANGELOG.md' - - 'docs/**' - - '*.md' + paths: + - 'src/**' + - 'main.py' + - 'pyproject.toml' + - 'requirements*.txt' permissions: contents: write @@ -27,8 +28,6 @@ jobs: uses: Conventional-Changelog/github-action-generate-changelog@v1 with: path-to-changelog: CHANGELOG.md - # This action by default updates the Unreleased section if no tag is provided - # or appends to the top. - name: Commit and Push Changelog run: | diff --git a/.github/workflows/commit-check.yml b/.github/workflows/commit-check.yml new file mode 100644 index 00000000..161707a2 --- /dev/null +++ b/.github/workflows/commit-check.yml @@ -0,0 +1,36 @@ +name: Commit Message Check + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + main: + name: Validate PR Title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + types: | + feat + fix + perf + docs + refactor + style + test + chore + ci + # Configure that a scope must be always lowercase. + subjectPattern: ^(?![A-Z]).+$ + # If the PR title contains a breaking change (!), it must be at the end of the type. + # e.g. "feat!: breaking change" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe9c8854..a1753c06 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: version: - description: 'Version to release (e.g. 1.0.0). If empty, will be calculated from commits.' + description: 'Version to release (e.g. 1.2.0). If empty, will be calculated from commits.' required: false prerelease: description: 'Is this a pre-release?' @@ -14,9 +14,6 @@ on: description: 'Dry run (do not tag or publish)' type: boolean default: false - push: - tags: - - 'v*' permissions: contents: write @@ -140,22 +137,26 @@ jobs: needs: validate steps: - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Calculate Version (for Docker tagging) + id: tag_calc + if: github.event.inputs.version == '' + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + dry_run: true - name: Determine Version id: version run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - if [[ "${{ github.event.inputs.version }}" != "" ]]; then - echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT - echo "TAG=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT - else - echo "VERSION=0.0.0-draft" >> $GITHUB_OUTPUT - echo "TAG=v0.0.0-draft" >> $GITHUB_OUTPUT - fi + if [[ "${{ github.event.inputs.version }}" != "" ]]; then + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + echo "TAG=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else - TAG="${GITHUB_REF#refs/tags/}" - echo "TAG=$TAG" >> $GITHUB_OUTPUT - echo "VERSION=${TAG#v}" >> $GITHUB_OUTPUT + echo "VERSION=${{ steps.tag_calc.outputs.new_version }}" >> $GITHUB_OUTPUT + echo "TAG=${{ steps.tag_calc.outputs.new_tag }}" >> $GITHUB_OUTPUT fi - name: Set up Docker Buildx @@ -189,44 +190,38 @@ jobs: name: Create Release runs-on: ubuntu-latest needs: build - if: (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') || startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false' steps: - uses: actions/checkout@v5 with: fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - name: Calculate Next Version id: tag_action - if: github.event_name == 'workflow_dispatch' && github.event.inputs.version == '' + if: github.event.inputs.version == '' uses: mathieudutour/github-tag-action@v6.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} dry_run: true + create_tag: false - name: Determine Version id: version run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - if [[ "${{ github.event.inputs.version }}" != "" ]]; then - echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT - echo "TAG=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT - else - echo "VERSION=${{ steps.tag_action.outputs.new_version }}" >> $GITHUB_OUTPUT - echo "TAG=${{ steps.tag_action.outputs.new_tag }}" >> $GITHUB_OUTPUT - fi + if [[ "${{ github.event.inputs.version }}" != "" ]]; then + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + echo "TAG=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else - TAG="${GITHUB_REF#refs/tags/}" - echo "TAG=$TAG" >> $GITHUB_OUTPUT - echo "VERSION=${TAG#v}" >> $GITHUB_OUTPUT + echo "VERSION=${{ steps.tag_action.outputs.new_version }}" >> $GITHUB_OUTPUT + echo "TAG=${{ steps.tag_action.outputs.new_tag }}" >> $GITHUB_OUTPUT fi - name: Update Version and Changelog - if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false' run: | VERSION="${{ steps.version.outputs.VERSION }}" DATE=$(date +%Y-%m-%d) - # Update version strings using a shared variable echo "Updating version to ${VERSION} in pyproject.toml and src/__init__.py" sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml @@ -247,8 +242,7 @@ jobs: git push origin main fi - - name: Create Git Tag (if manual) - if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false' + - name: Create Git Tag run: | TAG="${{ steps.version.outputs.TAG }}" git config user.name "github-actions[bot]" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2548c98..5ef7a796 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,3 +51,13 @@ repos: name: pip-audit (dependency safety) args: - --requirement=requirements-ci.txt + + # ----------------------------------------------------------- + # 4. Conventional Commits — enforce commit message format + # ----------------------------------------------------------- + - repo: https://github.com/compilerla/conventional-pre-commit + rev: v3.2.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] + args: [feat, fix, perf, docs, refactor, style, test, chore, ci] diff --git a/docs/VERSIONING_POLICY.md b/docs/VERSIONING_POLICY.md index a2063c4b..8145b1e0 100644 --- a/docs/VERSIONING_POLICY.md +++ b/docs/VERSIONING_POLICY.md @@ -45,7 +45,13 @@ To enable automated changelog generation and version bumping, all commits to the Indicate a breaking change by adding a `!` after the type/scope or by adding `BREAKING CHANGE:` in the footer of the commit message. This triggers a **MAJOR** version bump. *Example*: `feat!: overhaul risk management engine` -## 3. Tagging & Release Strategy +## 3. Pull Request Labels (Optional) +While Conventional Commits are the primary driver, PR labels can be used to override or supplement automation: +- `release:major`: Forces a MAJOR version bump. +- `release:minor`: Forces a MINOR version bump. +- `release:patch`: Forces a PATCH version bump. + +## 4. Tagging & Release Strategy ### Stable Releases - **Branch**: `main` @@ -61,16 +67,18 @@ Indicate a breaking change by adding a `!` after the type/scope or by adding `BR - **Tag Format**: `vMAJOR.MINOR.PATCH-alpha.N` or `vMAJOR.MINOR.PATCH-beta.N` - **Use Case**: Early testing of experimental features. -## 4. Automated Workflow +## 5. Automated Workflow 1. **Automated Changelog**: Every push to `main` triggers the `changelog.yml` workflow, which appends new conventional commits to the `CHANGELOG.md` file under the `[Unreleased]` section. -2. **Version Bump Automation**: The `Release Orchestration` (`release.yml`) workflow: - - Calculates the next version based on commit history if not manually provided. +2. **Commit Validation**: Every Pull Request is checked by the `commit-check.yml` workflow to ensure it meets Conventional Commit standards. +3. **Local Enforcement**: A pre-commit hook ensures that local commits adhere to the standard before they are even pushed. +4. **Version Bump Automation**: The `Release Orchestration` (`release.yml`) workflow: + - Calculates the next version based on commit history. - Updates `pyproject.toml` and `src/__init__.py`. - Transitions `[Unreleased]` content in `CHANGELOG.md` to a new versioned header. - Creates a new Git tag and GitHub Release. -## 5. Guidance for Version Bumping +## 6. Guidance for Version Bumping | Change Type | Version Component | Example Commit | | :--- | :--- | :--- |