diff --git a/.github/workflows/bump-version.yaml b/.github/workflows/bump-version.yaml new file mode 100644 index 0000000..ec16baf --- /dev/null +++ b/.github/workflows/bump-version.yaml @@ -0,0 +1,90 @@ +# (C) 2026 GoodData Corporation +name: Bump version & trigger release + +on: + workflow_dispatch: + inputs: + bump_type: + description: 'Type of version bump to perform (following semver).' + type: choice + required: true + default: 'patch' + options: + - major + - minor + - patch + +permissions: + contents: write + pull-requests: write + +jobs: + bump-version: + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.bump.outputs.new_version }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.TOKEN_GITHUB_YENKINS_ADMIN }} + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: latest + + - name: Install dependencies + run: uv sync --only-group release --locked + + - name: Bump version + id: bump + run: | + NEW_VERSION=$(uv run python ./scripts/bump_version.py ${{ github.event.inputs.bump_type }}) + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Bump version in codebase + run: make release-ci VERSION=${{ steps.bump.outputs.new_version }} + + - name: Specify release branch + id: branch + run: | + if [ "${{ github.event.inputs.bump_type }}" == "patch" ]; then + RELEASE_BRANCH="patch/${{ steps.bump.outputs.new_version }}" + else + RELEASE_BRANCH="rel/${{ steps.bump.outputs.new_version }}" + fi + echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + + - name: Create and push release ${{ steps.bump.outputs.new_version }} + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git checkout -b ${{ steps.branch.outputs.release_branch }} + git add -A + git commit -m "Release ${{ steps.bump.outputs.new_version }}" + git push origin ${{ steps.branch.outputs.release_branch }} --force-with-lease + git checkout master + git merge ${{ steps.branch.outputs.release_branch }} + git push origin master + + - name: Push tag v${{ steps.bump.outputs.new_version }} + run: | + git tag v${{ steps.bump.outputs.new_version }} + git push origin v${{ steps.bump.outputs.new_version }} + + create-release: + needs: bump-version + permissions: + contents: write + uses: ./.github/workflows/release-github.yaml + with: + tag: v${{ needs.bump-version.outputs.new_version }} + + publish: + needs: [bump-version, create-release] + permissions: + id-token: write + uses: ./.github/workflows/release-pypi.yaml + with: + tag: v${{ needs.bump-version.outputs.new_version }} diff --git a/.github/workflows/release-github.yaml b/.github/workflows/release-github.yaml new file mode 100644 index 0000000..20c2562 --- /dev/null +++ b/.github/workflows/release-github.yaml @@ -0,0 +1,35 @@ +# (C) 2026 GoodData Corporation +name: Create GitHub Release + +on: + workflow_call: + inputs: + tag: + type: string + required: true + workflow_dispatch: + inputs: + tag: + description: 'Tag to release (e.g. v0.1.0)' + type: string + required: true + +permissions: + contents: write + +jobs: + github_release: + name: Create GitHub release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.tag }} + token: ${{ secrets.GITHUB_TOKEN }} + generate_release_notes: true + draft: false + prerelease: false + make_latest: true diff --git a/.github/workflows/release-pypi.yaml b/.github/workflows/release-pypi.yaml new file mode 100644 index 0000000..127bc71 --- /dev/null +++ b/.github/workflows/release-pypi.yaml @@ -0,0 +1,40 @@ +# (C) 2026 GoodData Corporation +name: Publish to PyPI + +on: + workflow_call: + inputs: + tag: + type: string + required: true + workflow_dispatch: + inputs: + tag: + description: 'Tag to publish (e.g. v0.1.0)' + type: string + required: true + +permissions: + id-token: write + +jobs: + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + environment: release + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Build + run: uv build --out-dir dist + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + verbose: true diff --git a/Makefile b/Makefile index 09d0c3e..fa1023a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # (C) 2026 GoodData Corporation .PHONY: dev dev: - uv sync + uv sync --all-groups uv run pre-commit install @echo "\n\nRun 'source .venv/bin/activate' to activate the virtual environment" @@ -29,3 +29,8 @@ test: .PHONY: check check: format lint test type + +.PHONY: release-ci +release-ci: + if [ -z "$(VERSION)" ]; then echo "Usage: 'make release-ci VERSION=X.Y.Z'"; false; else \ + uv run tbump $(VERSION) --only-patch --non-interactive && uv lock ; fi diff --git a/pyproject.toml b/pyproject.toml index 110da60..778b02a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,10 @@ dev = [ "pytest==9.0.0", "pytest-mock==3.15.1", ] +release = [ + "tbump==6.11.0", + "tomlkit==0.11.8", +] [tool.ty.src] include = ["src", "tests"] @@ -53,3 +57,21 @@ gooddata-legacy2cloud = "gooddata_legacy2cloud.arg_parsing.cli_commands:main" [build-system] requires = ["uv_build>=0.10.9,<0.11.0"] build-backend = "uv_build" + +[tool.tbump.version] +current = "0.1.0" +regex = ''' + (?P\d+) + \. + (?P\d+) + \. + (?P\d+) + ''' + +[tool.tbump.git] +message_template = "Bump to {new_version}" +tag_template = "v{new_version}" + +[[tool.tbump.file]] +src = "pyproject.toml" +search = 'version = "{current_version}"' diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100644 index 0000000..9c0ae41 --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,36 @@ +# (C) 2026 GoodData Corporation +import sys +from pathlib import Path + +import tomllib + +_ROOT_DIR = Path(__file__).resolve().parent.parent + + +def get_version(content: dict) -> list[int]: + current_version = ( + content.get("tool", {}).get("tbump", {}).get("version", {}).get("current") + ) + if current_version is None: + raise ValueError("No current version found while reading from pyproject.toml") + return list(map(int, current_version.split("."))) + + +if __name__ == "__main__": + bump_type = sys.argv[1] + with open(_ROOT_DIR / "pyproject.toml", "rb") as f: + data = tomllib.load(f) + version = get_version(data) + match bump_type: + case "patch": + version[2] += 1 + case "minor": + version[1] += 1 + version[2] = 0 + case "major": + version[0] += 1 + version[1] = 0 + version[2] = 0 + case _: + raise ValueError("Invalid bump type") + print(".".join(list(map(str, version)))) diff --git a/uv.lock b/uv.lock index b8d29ec..c01ccae 100644 --- a/uv.lock +++ b/uv.lock @@ -109,6 +109,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] +[[package]] +name = "cli-ui" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "tabulate" }, + { name = "unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/63/70d8fefa7b4140367c45287b94fb5df535b6ba6f77464087b18fdae2bb47/cli_ui-0.19.0.tar.gz", hash = "sha256:59cdab0c6a2a6703c61b31cb75a1943076888907f015fffe15c5a8eb41a933aa", size = 12808, upload-time = "2025-03-29T20:24:37.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/a9/09591cff626f71bd11f88d8eedb81619c17422b4a5e778614a91e83c4b6a/cli_ui-0.19.0-py3-none-any.whl", hash = "sha256:1cf1b93328f7377730db29507e10bcb29ccc1427ceef45714b522d1f2055e7cd", size = 13492, upload-time = "2025-03-29T20:24:35.841Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -127,6 +141,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + [[package]] name = "filelock" version = "3.29.0" @@ -185,6 +205,10 @@ dev = [ { name = "ruff" }, { name = "ty" }, ] +release = [ + { name = "tbump" }, + { name = "tomlkit" }, +] [package.metadata] requires-dist = [ @@ -207,6 +231,10 @@ dev = [ { name = "ruff", specifier = "==0.15.5" }, { name = "ty", specifier = "==0.0.20" }, ] +release = [ + { name = "tbump", specifier = "==6.11.0" }, + { name = "tomlkit", specifier = "==0.11.8" }, +] [[package]] name = "gooddata-sdk" @@ -525,6 +553,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, ] +[[package]] +name = "schema" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/8da627b65577a8f130fe9dfa88ce94fcb24b1f8b59e0fc763ee61abef8b8/schema-0.7.8.tar.gz", hash = "sha256:e86cc08edd6fe6e2522648f4e47e3a31920a76e82cce8937535422e310862ab5", size = 45540, upload-time = "2025-10-11T13:15:40.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/75/aad85817266ac5285c93391711d231ca63e9ae7d42cd3ca37549e24ebe52/schema-0.7.8-py2.py3-none-any.whl", hash = "sha256:00bd977fadc7d9521bf289850cd8a8aa5f4948f575476b8daaa5c1b57af2dce1", size = 19108, upload-time = "2025-10-11T17:13:07.323Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -534,6 +571,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tbump" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cli-ui" }, + { name = "docopt" }, + { name = "schema" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/1f/d02379532311192521a20b3597dc0f01bd37596e950a6cb40795ae9acb94/tbump-6.11.0.tar.gz", hash = "sha256:385e710eedf0a8a6ff959cf1e9f3cfd17c873617132fc0ec5f629af0c355c870", size = 28642, upload-time = "2023-09-09T11:22:59.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/41/c21994a64efe86ed81c1a0935aeec840548839a37a7f3716f74a75e54fc2/tbump-6.11.0-py3-none-any.whl", hash = "sha256:6b181fe6f3ae84ce0b9af8cc2009a8bca41ded34e73f623a7413b9684f1b4526", size = 35607, upload-time = "2023-09-09T11:22:56.581Z" }, +] + [[package]] name = "tenacity" version = "9.0.0" @@ -543,6 +604,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169, upload-time = "2024-07-29T12:12:25.825Z" }, ] +[[package]] +name = "tomlkit" +version = "0.11.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/37/dd53019ccb72ef7d73fff0bee9e20b16faff9658b47913a35d79e89978af/tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3", size = 188825, upload-time = "2023-04-27T10:39:21.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a8/b1c193be753c02e2a94af6e37ee45d3378a74d44fe778c2434a63af92731/tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171", size = 35807, upload-time = "2023-04-27T10:39:19.629Z" }, +] + [[package]] name = "ty" version = "0.0.20" @@ -600,6 +670,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "unidecode" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/7d/a8a765761bbc0c836e397a2e48d498305a865b70a8600fd7a942e85dcf63/Unidecode-1.4.0.tar.gz", hash = "sha256:ce35985008338b676573023acc382d62c264f307c8f7963733405add37ea2b23", size = 200149, upload-time = "2025-04-24T08:45:03.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/b7/559f59d57d18b44c6d1250d2eeaa676e028b9c527431f5d0736478a73ba1/Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021", size = 235837, upload-time = "2025-04-24T08:45:01.609Z" }, +] + [[package]] name = "urllib3" version = "2.6.3"