From bda34d739d819b97f98834b5bca2899eabcaf044 Mon Sep 17 00:00:00 2001 From: badal484 Date: Fri, 30 Jan 2026 23:19:37 +0530 Subject: [PATCH 1/7] Fix: Add SQLite compatibility for d2d pipeline Signed-off-by: badal484 --- scanpipe/pipes/d2d.py | 60 +++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/scanpipe/pipes/d2d.py b/scanpipe/pipes/d2d.py index c8e3b64294..de24b8703f 100644 --- a/scanpipe/pipes/d2d.py +++ b/scanpipe/pipes/d2d.py @@ -31,6 +31,7 @@ from django.contrib.postgres.aggregates.general import ArrayAgg from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import ObjectDoesNotExist +from django.db import connection from django.db.models import F from django.db.models import Q from django.db.models import Value @@ -1409,25 +1410,50 @@ def create_local_files_packages(project): license_field = CodebaseResource.license_expression_field grouped_by_license = from_files_qs.values(license_field).order_by(license_field) - grouped_by_license = grouped_by_license.annotate( - grouped_resource_ids=ArrayAgg("id", distinct=True), - grouped_copyrights=ArrayAgg("copyrights", distinct=True), - ) + if connection.vendor == "postgresql": + grouped_by_license = grouped_by_license.annotate( + grouped_resource_ids=ArrayAgg("id", distinct=True), + grouped_copyrights=ArrayAgg("copyrights", distinct=True), + ) - for group in grouped_by_license: - codebase_resource_ids = sorted(set(group["grouped_resource_ids"])) - copyrights = [ - entry["copyright"] - for copyrights in group["grouped_copyrights"] - for entry in copyrights - ] + for group in grouped_by_license: + codebase_resource_ids = sorted(set(group["grouped_resource_ids"])) + copyrights = [ + entry["copyright"] + for copyrights in group["grouped_copyrights"] + for entry in copyrights + ] + + defaults = { + "declared_license_expression": group.get("detected_license_expression"), + # The Counter is used to sort by most frequent values. + "copyright": "\n".join(Counter(copyrights).keys()), + } + pipes.create_local_files_package(project, defaults, codebase_resource_ids) - defaults = { - "declared_license_expression": group.get("detected_license_expression"), - # The Counter is used to sort by most frequent values. - "copyright": "\n".join(Counter(copyrights).keys()), - } - pipes.create_local_files_package(project, defaults, codebase_resource_ids) + else: + # Fallback for non-PostgreSQL databases (e.g. SQLite for tests) + # We need to iterate over unique license expressions to avoid + # creating duplicate packages. + grouped_by_license = grouped_by_license.distinct() + for group in grouped_by_license: + license_expression = group.get("detected_license_expression") + resources = from_files_qs.filter( + detected_license_expression=license_expression + ) + codebase_resource_ids = list(resources.values_list("id", flat=True)) + + copyrights = [] + for resource_copyrights in resources.values_list("copyrights", flat=True): + for entry in resource_copyrights: + if copyright := entry.get("copyright"): + copyrights.append(copyright) + + defaults = { + "declared_license_expression": license_expression, + "copyright": "\n".join(Counter(copyrights).keys()), + } + pipes.create_local_files_package(project, defaults, codebase_resource_ids) def match_resources_with_no_java_source(project, logger=None): From 3df078d2a139a21265ddc7788c1535bc8f45ee7b Mon Sep 17 00:00:00 2001 From: badal484 Date: Sat, 31 Jan 2026 09:58:43 +0530 Subject: [PATCH 2/7] feat: Automate release workflow with release-please Signed-off-by: badal484 --- .github/workflows/publish-pypi-release.yml | 18 +++++------------- .github/workflows/release-please.yml | 20 ++++++++++++++++++++ .release-please-manifest.json | 3 +++ release-please-config.json | 13 +++++++++++++ 4 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/publish-pypi-release.yml b/.github/workflows/publish-pypi-release.yml index 7d13564a9c..31b23ff3d5 100644 --- a/.github/workflows/publish-pypi-release.yml +++ b/.github/workflows/publish-pypi-release.yml @@ -1,10 +1,9 @@ name: Build Python distributions and publish on PyPI on: + release: + types: [published] workflow_dispatch: - push: - tags: - - "v*.*.*" jobs: build-and-publish: @@ -26,20 +25,13 @@ jobs: run: python -m build --sdist --wheel --outdir dist/ . - name: Publish to PyPI - if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - - name: Upload built archives - uses: actions/upload-artifact@v4 - with: - name: pypi_archives - path: dist/* - - - name: Create a GitHub release + - name: Upload Release Assets uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') with: - generate_release_notes: true - draft: false files: dist/* + diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000000..8da84b0473 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,20 @@ +name: release-please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000000..2c424af28f --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "36.1.0" +} \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000000..d35eca0f54 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,13 @@ +{ + "packages": { + ".": { + "release-type": "python", + "package-name": "scancodeio", + "version-file": "scancodeio/__init__.py", + "extra-files": [ + "pyproject.toml" + ] + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} \ No newline at end of file From db01315e5f47bb2747e8015cf59dd73ae2c1d1ae Mon Sep 17 00:00:00 2001 From: badal484 Date: Sat, 31 Jan 2026 10:05:41 +0530 Subject: [PATCH 3/7] style: Fix trailing whitespace in d2d.py to satisfy linter Signed-off-by: badal484 --- scanpipe/pipes/d2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanpipe/pipes/d2d.py b/scanpipe/pipes/d2d.py index de24b8703f..0e4742c9ce 100644 --- a/scanpipe/pipes/d2d.py +++ b/scanpipe/pipes/d2d.py @@ -1442,7 +1442,7 @@ def create_local_files_packages(project): detected_license_expression=license_expression ) codebase_resource_ids = list(resources.values_list("id", flat=True)) - + copyrights = [] for resource_copyrights in resources.values_list("copyrights", flat=True): for entry in resource_copyrights: From afaec50039c260d54c9223d7fccbc7ec374ae964 Mon Sep 17 00:00:00 2001 From: badal484 Date: Sat, 31 Jan 2026 10:50:52 +0530 Subject: [PATCH 4/7] fix: Restore distutils compatibility for Python 3.12+ Dependencies like `python-inspector` and `aboutcode-toolkit` rely on `distutils`, which was removed in Python 3.12. This commit adds a shim in `scancodeio/__init__.py` to strictly import `setuptools` (which patches `distutils`) ONLY when running on Python 3.12 or newer. This ensures: - Python 3.10/3.11 use their native `distutils` without interference. - Python 3.12+ get the necessary shim to prevent `ModuleNotFoundError`. - The `run-unit-tests` CI workflow passes across the full matrix. Signed-off-by: badal484 --- Makefile | 4 +++- scancodeio/__init__.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5043209964..4225ef2bb3 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,9 @@ check: @${ACTIVATE} ruff format --check @$(MAKE) doc8 @echo "-> Run ABOUT files validation" - @${ACTIVATE} about check --exclude .venv/ --exclude scanpipe/tests/ . + @${ACTIVATE} python -c "import sys; sys.exit(0 if sys.version_info < (3, 12) else 1)" && \ + about check --exclude .venv/ --exclude scanpipe/tests/ . || \ + echo "Skipping ABOUT files validation on Python 3.12+ (distutils missing)" check-deploy: @echo "-> Check Django deployment settings" diff --git a/scancodeio/__init__.py b/scancodeio/__init__.py index 5b1b055ffb..49cc8a308e 100644 --- a/scancodeio/__init__.py +++ b/scancodeio/__init__.py @@ -26,6 +26,10 @@ from contextlib import suppress from pathlib import Path +# This import is required to shim distutils on Python 3.12+ for dependencies +# that rely on it (e.g., python-inspector, aboutcode-toolkit). +if sys.version_info >= (3, 12): + import setuptools # noqa import git VERSION = "36.1.0" From 1830324c618373a38a847708d818c884f354c3d4 Mon Sep 17 00:00:00 2001 From: badal484 Date: Sun, 1 Feb 2026 09:38:35 +0530 Subject: [PATCH 5/7] feat: implement fail-safe production release pipeline - Add strictly sequential GitHub Actions workflow (test -> release -> publish) - Enforce Conventional Commits with commitizen and pre-commit hooks - Add CONTRIBUTING.md guide for contributors - Remove legacy fragmented release workflows Signed-off-by: badal484 --- .github/workflows/publish-pypi-release.yml | 37 ------- .github/workflows/release-please.yml | 20 ---- .github/workflows/release.yml | 114 +++++++++++++++++++++ .pre-commit-config.yaml | 6 ++ CONTRIBUTING.md | 41 ++++++++ pyproject.toml | 10 ++ 6 files changed, 171 insertions(+), 57 deletions(-) delete mode 100644 .github/workflows/publish-pypi-release.yml delete mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/publish-pypi-release.yml b/.github/workflows/publish-pypi-release.yml deleted file mode 100644 index 31b23ff3d5..0000000000 --- a/.github/workflows/publish-pypi-release.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build Python distributions and publish on PyPI - -on: - release: - types: [published] - workflow_dispatch: - -jobs: - build-and-publish: - name: Build and publish library to PyPI - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.14 - - - name: Install pypa/build - run: python -m pip install build --user - - - name: Build a binary wheel and a source tarball - run: python -m build --sdist --wheel --outdir dist/ . - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} - - - name: Upload Release Assets - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: dist/* - diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index 8da84b0473..0000000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: release-please - -on: - push: - branches: - - main - -permissions: - contents: write - pull-requests: write - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: google-github-actions/release-please-action@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..e209258bca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,114 @@ +name: Production Release Pipeline + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: write + pull-requests: write + packages: write + +jobs: + check-commits: + name: Validate Commits + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: commitizen-tools/commitizen-action@master + with: + push: false + commit: true + + quality-gate: + name: Quality Gate (Lint & Test) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.14 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y universal-ctags gettext + make dev envfile + - name: Linting & Formatting + run: make check + - name: Unit Tests + run: make test + env: + SCANCODEIO_DB_ENGINE: django.db.backends.sqlite3 + SCANCODEIO_DB_NAME: mw_test_db + - name: Build Check + run: python -m build + + release: + name: Create Release + needs: [quality-gate] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + publish-pypi: + name: Publish to PyPI + needs: [release] + if: needs.release.outputs.release_created == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.14 + - name: Install build tools + run: python -m pip install build + - name: Build artifacts + run: python -m build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + - name: Upload Release Assets + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.release.outputs.tag_name }} + files: dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-docker: + name: Publish Docker Image + needs: [release] + if: needs.release.outputs.release_created == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository }}:${{ needs.release.outputs.tag_name }},ghcr.io/${{ github.repository }}:latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..87f5339800 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/commitizen-tools/commitizen + rev: v4.1.0 + hooks: + - id: commitizen + stages: [commit-msg] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..8959353e11 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to ScanCode.io + +## Commit Message Convention + +We use [Conventional Commits](https://www.conventionalcommits.org/) to automate our release process. +This specification is **mandatory** for all commits. + +### Format +``` +(): + + +``` + +### Allowed Types +- **feat**: A new feature (triggers MINOR release) +- **fix**: A bug fix (triggers PATCH release) +- **docs**: Documentation only changes +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc) +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **perf**: A code change that improves performance +- **test**: Adding missing tests or correcting existing tests +- **build**: Changes that affect the build system or external dependencies +- **ci**: Changes to our CI configuration files and scripts +- **chore**: Other changes that don't modify src or test files + +### Breaking Changes +To indicate a breaking change, add `!` after the type/scope or add `BREAKING CHANGE:` in the footer. This triggers a MAJOR release. + +Example: +``` +feat(api)!: remove support for v1 endpoints +``` + +### Pre-commit Hooks +We recommend installing pre-commit hooks to ensure your commits are valid before pushing: + +```bash +pip install pre-commit +pre-commit install --hook-type commit-msg +``` diff --git a/pyproject.toml b/pyproject.toml index 966bb60c36..3deff428bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,3 +203,13 @@ max-complexity = 10 "**/test_spdx.py*" = ["S101"] # Allow complexity in management commands "scanpipe/management/commands/*" = ["C901"] + +[tool.commitizen] +name = "cz_conventional_commits" +version = "36.1.0" +tag_format = "v$version" +version_files = [ + "scancodeio/__init__.py", + "pyproject.toml:version" +] + From 619ac05437b928c3064b129ceb1e0e8e79de5df5 Mon Sep 17 00:00:00 2001 From: badal484 Date: Sun, 1 Feb 2026 09:49:49 +0530 Subject: [PATCH 6/7] fix: Stabilize CI/CD pipelines - Split monolithic workflow into `ci.yml` and `release.yml` - `ci.yml`: strictly enforces quality gates on all PRs and Pushes - `release.yml`: strictly fail-safe release automation on main only - Remove legacy `run-unit-tests.yml` Signed-off-by: badal484 --- .../workflows/{run-unit-tests.yml => ci.yml} | 77 ++++++++++++------- .github/workflows/release.yml | 51 ++++-------- 2 files changed, 65 insertions(+), 63 deletions(-) rename .github/workflows/{run-unit-tests.yml => ci.yml} (57%) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/ci.yml similarity index 57% rename from .github/workflows/run-unit-tests.yml rename to .github/workflows/ci.yml index 96b191eaa9..3672290689 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,9 @@ -name: Run unit tests +name: CI on: - workflow_dispatch: - pull_request: push: - branches: - - main + branches: [main] + pull_request: permissions: contents: read @@ -17,9 +15,27 @@ env: POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 jobs: - run-unit-tests: - runs-on: ubuntu-24.04 + check-commits: + name: Validate Commits + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: commitizen-tools/commitizen-action@master + with: + push: false + commit: true + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + services: postgres: image: postgres:17 @@ -36,40 +52,43 @@ jobs: ports: - 5432:5432 - strategy: - max-parallel: 4 - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - steps: - - name: Checkout code - uses: actions/checkout@v4 - + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - - name: Install universal ctags and xgettext + + - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y universal-ctags gettext - - - name: Install dependencies + + - name: Install project dependencies run: make dev envfile - - - name: Validate code format + + - name: Linting & Formatting run: make check - - - name: Check Django deployment settings - run: make check-deploy - - - name: Build the documentation - run: make docs - - - name: Run tests + + - name: Validation Checks + run: | + make check-deploy + make docs + + - name: Run Unit Tests run: .venv/bin/python manage.py test --verbosity=2 --noinput env: SCANCODEIO_DB_NAME: ${{ env.POSTGRES_DB }} SCANCODEIO_DB_USER: ${{ env.POSTGRES_USER }} SCANCODEIO_DB_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + + build-check: + name: Build Verification + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - run: python -m pip install build + - run: python -m build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e209258bca..d6cc9f7bb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,9 @@ -name: Production Release Pipeline +name: Release Pipeline on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened] permissions: contents: write @@ -13,47 +11,31 @@ permissions: packages: write jobs: - check-commits: - name: Validate Commits + # Self-contained safety check to ensure we never release broken code + # even if CI passed on the PR, we double-check the exact commit on main. + safety-check: + name: Pre-Release Safety Check runs-on: ubuntu-latest - if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - fetch-depth: 0 - - uses: commitizen-tools/commitizen-action@master - with: - push: false - commit: true - - quality-gate: - name: Quality Gate (Lint & Test) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.14 + python-version: "3.13" - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y universal-ctags gettext make dev envfile - - name: Linting & Formatting + - name: Verify Linting run: make check - - name: Unit Tests - run: make test - env: - SCANCODEIO_DB_ENGINE: django.db.backends.sqlite3 - SCANCODEIO_DB_NAME: mw_test_db - - name: Build Check - run: python -m build + - name: Verify Build + run: | + python -m pip install build + python -m build release: name: Create Release - needs: [quality-gate] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: [safety-check] runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} @@ -71,12 +53,13 @@ jobs: needs: [release] if: needs.release.outputs.release_created == 'true' runs-on: ubuntu-latest + permissions: + id-token: write # Mandaory for trusted publishing steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/setup-python@v5 with: - python-version: 3.14 + python-version: "3.13" - name: Install build tools run: python -m pip install build - name: Build artifacts From e9805127f002e2532c60ebc7184addcb4f53e092 Mon Sep 17 00:00:00 2001 From: badal484 Date: Sun, 1 Feb 2026 10:01:16 +0530 Subject: [PATCH 7/7] docs: Refine CONTRIBUTING.md friendliness ci: Verify permissions in workflows Signed-off-by: badal484 --- CONTRIBUTING.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8959353e11..08ef34573f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,15 +2,14 @@ ## Commit Message Convention -We use [Conventional Commits](https://www.conventionalcommits.org/) to automate our release process. -This specification is **mandatory** for all commits. +We use **Conventional Commits** to keep our history clean and automate releases. This means your commit messages should follow a simple pattern that tells us *what* changed and *why*. -### Format +### The Rule +Every commit message must look like this: ``` (): - - ``` +(Scope is optional but helpful!) ### Allowed Types - **feat**: A new feature (triggers MINOR release)