From c9fdcf27a9eb112f2f421a4856fbc290bba4f30a Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Tue, 17 Jun 2025 23:18:23 +0100 Subject: [PATCH] ci: add discord notifier and move the deployment to a matrix (#419) Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/notify-social.yml | 57 +++++ .github/workflows/release.yml | 197 ++++++++++-------- tools/ci/ci_tools/__init__.py | 0 tools/ci/ci_tools/release_helper.py | 229 +++++++++++++++++++++ tools/ci/ci_tools/release_helper_test.py | 85 ++++++++ tools/ci/ci_tools/social/__init__.py | 0 tools/ci/ci_tools/social/notify_discord.py | 82 ++++++++ tools/ci/ci_tools/social/social_common.py | 68 ++++++ tools/ci/pyproject.toml | 27 +++ 9 files changed, 656 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/notify-social.yml create mode 100644 tools/ci/ci_tools/__init__.py create mode 100644 tools/ci/ci_tools/release_helper.py create mode 100644 tools/ci/ci_tools/release_helper_test.py create mode 100644 tools/ci/ci_tools/social/__init__.py create mode 100644 tools/ci/ci_tools/social/notify_discord.py create mode 100644 tools/ci/ci_tools/social/social_common.py create mode 100644 tools/ci/pyproject.toml diff --git a/.github/workflows/notify-social.yml b/.github/workflows/notify-social.yml new file mode 100644 index 00000000..8c892202 --- /dev/null +++ b/.github/workflows/notify-social.yml @@ -0,0 +1,57 @@ +name: Release Sandbox + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + app: + description: The changed app + type: string + default: pyTermTk + version: + description: The app version + type: string + default: v0.0.0 + discord-message: + description: The release message + type: string + default: pyTermTk released + workflow_call: + inputs: + app: + description: The changed app + type: string + default: pyTermTk + version: + description: The app version + type: string + default: v0.0.0 + discord-message: + description: The release message + type: string + default: pyTermTk released + +jobs: + notify-discord: + # runs-on: ubuntu-latest + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Instrall deps + run: | + python -m pip install discord.py + - name: Deploy Discord message + env: + MESSAGE: ${{ inputs.discord-message }} + run: | + python tools/ci/social/notify_discord.py ${{ inputs.app }} ${{ inputs.version }} + + + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d20f84ae..92201371 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,7 @@ permissions: env: APP_NAME: pyTermTk + APP_TTK: libs/pyTermTk jobs: release-please: @@ -29,6 +30,9 @@ jobs: outputs: rp_out: ${{ toJson(steps.release-please.outputs) }} + matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix-pypi: ${{ steps.set-matrix.outputs.matrix_pypi }} + matrix-itch: ${{ steps.set-matrix.outputs.matrix_itch }} steps: - uses: actions/checkout@v4 with: @@ -48,12 +52,26 @@ jobs: OUTPUTS: ${{ toJSON(steps.release-please.outputs) }} run: | echo OUTPUTS: "$OUTPUTS" + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' - name: Update Version if: ${{ steps.release-please.outputs.prs_created == 'true'}} shell: bash env: GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + RP_OUT: ${{ steps.release-please.outputs }} run: | + _get_name(){ + _ITEM=$1 + jq -r ".packages[\"${_ITEM}\"][\"package-name\"]" .release-please-config.json + } + _get_version(){ + _ITEM=$1 + jq -r ".[\"${_ITEM}\"]" .release-please-manifest.json + } + echo '::group::Setup Git' git config --global user.name 'Eugenio Parodi - Action' git config --global user.email 'ceccopierangioliegenio@googlemail.com' @@ -65,55 +83,27 @@ jobs: cd pyTermTk.new - echo '::group::Retrieve the Versions' - # Update version in the project - _VERSION_TTK=$(jq -r '.["libs/pyTermTk" ]' .release-please-manifest.json) - _VERSION_DPT=$(jq -r '.["apps/dumbPaintTool"]' .release-please-manifest.json) - _VERSION_T_D=$(jq -r '.["apps/ttkDesigner" ]' .release-please-manifest.json) - _VERSION_KOD=$(jq -r '.["apps/ttkode" ]' .release-please-manifest.json) - _VERSION_TLG=$(jq -r '.["apps/tlogg" ]' .release-please-manifest.json) - _NAME_TTK=$(jq -r '.packages["libs/pyTermTk" ]["package-name"]' .release-please-config.json) - _NAME_DPT=$(jq -r '.packages["apps/dumbPaintTool"]["package-name"]' .release-please-config.json) - _NAME_T_D=$(jq -r '.packages["apps/ttkDesigner" ]["package-name"]' .release-please-config.json) - _NAME_KOD=$(jq -r '.packages["apps/ttkode" ]["package-name"]' .release-please-config.json) - _NAME_TLG=$(jq -r '.packages["apps/tlogg" ]["package-name"]' .release-please-config.json) - - echo "Version ${_NAME_TTK}: ${_VERSION_TTK}" - echo "Version ${_NAME_DPT}: ${_VERSION_DPT}" - echo "Version ${_NAME_T_D}: ${_VERSION_T_D}" - echo "Version ${_NAME_KOD}: ${_VERSION_KOD}" - echo "Version ${_NAME_TLG}: ${_VERSION_TLG}" + echo '::group::🍧 Print the Versions' + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + info <<< '{}' echo '::endgroup::' - echo '::group::Update the Versions' + echo '::group::🍓 Update the Versions' + + _VERSION_TTK=$(_get_name ${APP_TTK}) + sed -i \ "s|__version__:str.*|__version__:str = '${_VERSION_TTK}'|" \ libs/pyTermTk/TermTk/__init__.py - if grep -q "${_NAME_DPT}: ${_VERSION_DPT}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then - sed -i \ - "s|__version__:str.*|__version__:str = '${_VERSION_T_D}'|" \ - apps/ttkDesigner/ttkDesigner/__init__.py - sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/dumbPaintTool/pyproject.toml - fi - if grep -q "${_NAME_T_D}: ${_VERSION_T_D}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then - sed -i \ - "s|__version__:str.*|__version__:str = '${_VERSION_DPT}'|" \ - apps/dumbPaintTool/dumbPaintTool/__init__.py - sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkDesigner/pyproject.toml - fi - if grep -q "${_NAME_KOD}: ${_VERSION_KOD}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then - sed -i \ - "s|__version__:str.*|__version__:str = '${_VERSION_KOD}'|" \ - apps/ttkode/ttkode/__init__.py - sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkode/pyproject.toml - fi - if grep -q "${_NAME_TLG}: ${_VERSION_TLG}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then - sed -i \ - "s|__version__:str.*|__version__:str = '${_VERSION_TLG}'|" \ - apps/tlogg/tlogg/__init__.py - sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/tlogg/pyproject.toml - fi + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + upgrade <<< ${RP_OUT} + + cp libs/pyTermTk/CHANGELOG.md CHANGELOG.md echo '::endgroup::' @@ -124,10 +114,35 @@ jobs: CHANGELOG.md find . -name pyproject.toml | xargs git add if [[ $(git status --porcelain) ]] ; then - git commit -m "chore: updated TermTk and apps to versions to ${_VERSION_TTK}, ${_VERSION_DPT}, ${_VERSION_T_D} ${_VERSION_KOD}" + git commit -m "chore: updated TermTk and apps to versions" git push fi echo '::endgroup::' + - name: Define the Matrix strategy + id: set-matrix + env: + RP_OUT: ${{ steps.release-please.outputs }} + run: | + echo "matrix<> $GITHUB_OUTPUT + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + matrix all <<< ${RP_OUR} >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "matrix_itch<> $GITHUB_OUTPUT + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + matrix itch <<< ${RP_OUR} >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "matrix_pypi<> $GITHUB_OUTPUT + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + matrix pypi <<< ${RP_OUR} >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT pyTermTk-deploy-artifacts: if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }} @@ -201,38 +216,36 @@ jobs: - pyTermTk-deploy-artifacts secrets: inherit - publish-pyTermTk: - if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }} - name: Publish pyTermTk - uses: ./.github/workflows/python-publish.yml - with: - pkg_name: pyTermTk - pkg_folder: libs/pyTermTk + publish-pypi: + if: ${{ needs.release-please.outputs.matrix-pypi != '[]' }} + name: Publish pypi ${{ matrix.name }} needs: - release-please - secrets: inherit - - publish-ttkDesigner: - if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/ttkDesigner--release_created'] }} - name: Publish ttkDesigner + strategy: + matrix: + include: ${{ fromJson(needs.release-please.outputs.matrix-pypi) }} uses: ./.github/workflows/python-publish.yml with: - pkg_name: ttkDesigner - pkg_folder: apps/ttkDesigner - needs: - - release-please + pkg_name: ${{ matrix.name }} + pkg_folder: ${{ matrix.path }} secrets: inherit - publish-dumbPaintTool: - if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/dumbPaintTool--release_created'] }} - name: Publish dumbPaintTool - uses: ./.github/workflows/python-publish.yml - with: - pkg_name: dumbPaintTool - pkg_folder: apps/dumbPaintTool - needs: - - release-please - secrets: inherit + # publish-itch: + # name: Publish Itch ${{ matrix.name }} + # needs: + # - release-please + # - generate-matrix + # runs-on: self-hosted + # strategy: + # matrix: + # include: ${{ fromJson(needs.generate-matrix.outputs.matrix-itch) }} + # steps: + # - name: Build ${{ matrix.name }} + # run: | + # echo "Building ${{ matrix.name }} at path: ${{ matrix.path }}" + # uses: ./.github/workflows/itch-publish.yml + # with: + # pkg_name: dumb-paint-tool publish-dumbPaintTool-itch: if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/dumbPaintTool--release_created'] }} @@ -244,24 +257,30 @@ jobs: - release-please secrets: inherit - publish-ttkode: - if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/ttkode--release_created'] }} - name: Publish ttkode - uses: ./.github/workflows/python-publish.yml - with: - pkg_name: ttkode - pkg_folder: apps/ttkode - needs: - - release-please - secrets: inherit - - publish-tlogg: - if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/tlogg--release_created'] }} - name: Publish tlogg - uses: ./.github/workflows/python-publish.yml - with: - pkg_name: tlogg - pkg_folder: apps/tlogg + notify: + if: ${{ needs.release-please.outputs.matrix != '[]' }} + name: Notify ${{ matrix.name }} to the socials needs: - release-please - secrets: inherit \ No newline at end of file + runs-on: self-hosted + strategy: + matrix: + include: ${{ fromJson(needs.release-please.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + shell: bash + run: + pip install -e 'tools/ci[social]' + - name: Notify ${{ matrix.name }} on Discord + env: + RN: ${{ matrix.release-notes }} + MESSAGE: ${{ matrix.release-notes }} + run: | + notify-discord ${{ matrix.name }} v${{ matrix.version }} \ No newline at end of file diff --git a/tools/ci/ci_tools/__init__.py b/tools/ci/ci_tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/ci/ci_tools/release_helper.py b/tools/ci/ci_tools/release_helper.py new file mode 100644 index 00000000..77665025 --- /dev/null +++ b/tools/ci/ci_tools/release_helper.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import re +import sys +import glob +import json +import argparse +import fileinput +from dataclasses import dataclass +from enum import Enum + +from typing import List, Dict, Union + +class MatrixType(Enum): + ALL = "all" + PYPI = "pypi" + ITCH = "itch" + +@dataclass +class _AppData(): + name: str + path: str + version: str + pypi: bool = False + itch: bool = False + tag: str = "" + release_notes: str = "" + + def to_dict(self) -> Dict[str, Union[str,bool]]: + return { + "name" : self.name, + "path" : self.path, + "version" : self.version, + "pypi" : self.pypi, + "itch" : self.itch, + "tag" : self.tag, + "release-notes" : self.release_notes + } + +def _print_info(apps_data:List[_AppData]) -> None: + for _a in apps_data: + print(f"{_a.name} : {_a.version}") + +# for item in $(jq -r '.[].path' <<< ${APPS_ARRAY}) ; do; do +# # Update version in the project +# _VERSION=$(_get_version ${item}) +# _NAME=$(_get_name ${item}) +# if grep -q "${_NAME}: ${_VERSION}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then +# sed -i \ +# "s|__version__:str.*|__version__:str = '${_VERSION}'|" \ +# ${item}/*/__init__.py +# sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i ${item}/pyproject.toml +# echo ✅ Bumped ${_NAME} to ${_VERSION} +# else +# echo 🆗 No new release found for ${_NAME} +# fi +# done +def _upgrade_files(apps_data:List[_AppData], rp_data:Dict, dry_run:bool) -> None: + _ttk = [_a for _a in apps_data if _a.name=='pyTermTk'][0] + for _a in apps_data: + print(f"{_a.name} : {_a.version}") + if f"{_a.name}: {_a.version}" not in rp_data.get('pr',''): + print(f"🆗 No new release found for ${_a.name}") + else: + print(f"✅ Bumped ${_a.name} to ${_a.version}") + print(f"sed {_a.path}/*/__init__.py <<< {_a.version}") + + pattern = re.compile(r"__version__:str.*") + replacement=f"__version__:str = '{_a.version}'" + files = glob.glob(f"{_a.path}/*/__init__.py") + if dry_run: + print(files, replacement) + else: + for line in fileinput.input(files, inplace=True): + print(pattern.sub(replacement, line), end="") + + pattern = re.compile(r"'pyTermTk *>=[^']*'") + replacement = f"'pyTermTk>={_ttk.version}'" + + files = glob.glob(f"{_a.path}/pyproject.toml") + if dry_run: + print(files, replacement) + else: + for line in fileinput.input(files, inplace=True): + print(pattern.sub(replacement, line), end="") + + +def _gen_matrix(matrix_type: MatrixType, rp_data:Dict, apps_data:List[_AppData]) -> List[_AppData]: + if matrix_type == MatrixType.PYPI: + apps = [app for app in apps_data if app.pypi] + elif matrix_type == MatrixType.ITCH: + apps = [app for app in apps_data if app.itch] + elif matrix_type == MatrixType.ALL: + apps = apps_data + else: + raise ValueError(f"Invalid matrix type: {matrix_type}") + + # if 'pr' not in rp_data: + # return []# + + # pr = json.loads(rp_data['pr']) + + # print(rp_data) + # for app in apps: + # print(f"{app.name}: [{app.path}--release_created]: ", rp_data.get(f"{app.path}--release_created",False)) + + apps = [app for app in apps if rp_data.get(f"{app.path}--release_created",False) in ('true',True)] + for app in apps: + app.tag = rp_data.get(f"{app.path}--tag_name",'') + app.release_notes = rp_data.get(f"{app.path}--body",'') + + return apps + +def main(): + parser = argparse.ArgumentParser(description="Release Helper Script") + # Configuration File Argument + parser.add_argument("--config", metavar="config_file", type=argparse.FileType("r"), help="Path to the configuration file") + parser.add_argument("--manifest", metavar="config_file", type=argparse.FileType("r"), help="Path to the configuration file") + + subparsers = parser.add_subparsers(title="Features", dest="feature") + + # Apps Feature + info_parser = subparsers.add_parser("info", help="Print release info") + + upgrade_parser = subparsers.add_parser("upgrade", help="update the app versions") + upgrade_parser.add_argument("--dry-run", action="store_true", help="Do not apply thw changes") + + # Apps Feature + apps_parser = subparsers.add_parser("apps", help="Apps related operations") + apps_parser.add_argument("--list", action="store_true", help="List available apps") + apps_parser.add_argument("--build", metavar="app_name", type=str, help="Build a specific app") + + # Matrix Feature + matrix_parser = subparsers.add_parser("matrix", help="Matrix related operations") + matrix_parser.add_argument("type", metavar="matrix_type", type=str, choices=[e.value for e in MatrixType], help="Specify the type of matrix to generate") + + args = parser.parse_args() + + # Load and parse configuration file if provided + config = {} + if args.config: + try: + config = json.load(args.config) # Parse the JSON file + # print(f"Loaded configuration: {json.dumps(config, indent=2)}") + except json.JSONDecodeError: + print(f"Error: Configuration file '{args.config.name}' is not valid JSON.") + sys.exit(1) + + # Load and parse configuration file if provided + manifest = {} + if args.manifest: + try: + manifest = json.load(args.manifest) # Parse the JSON file + # print(f"Loaded manifesturation: {json.dumps(manifest, indent=2)}") + except json.JSONDecodeError: + print(f"Error: Configuration file '{args.manifest.name}' is not valid JSON.") + sys.exit(1) + + input_data = {} + if not sys.stdin.isatty(): # or sys.stdin.peek(1): + try: + read = sys.stdin.read() + input_data = json.loads(read) + except json.JSONDecodeError: + print("Error: Invalid JSON input.") + sys.exit(1) + + apps_data = [ + _AppData( + name=_v.get('package-name',''), + path=_a, + version=manifest.get(_a,"0.0.0"), + itch=_v.get('itch',False), + pypi=_v.get('pypi',False)) + for _a,_v in config.get('packages',{}).items()] + + # print(apps_data) + + if args.feature == "info": + _print_info(apps_data) + elif args.feature == "upgrade": + print(args) + _upgrade_files(apps_data, input_data, args.dry_run) + elif args.feature == "apps": + if args.list: + print("Available Apps:") + for app in apps_data: + print(f" - {app.name}") + elif args.build: + print(f"Building app: {args.build}") + # Implement build logic here + else: + apps_parser.print_help() + elif args.feature == "matrix": + matrix_type = MatrixType(args.type) + matrix = _gen_matrix(matrix_type, input_data, apps_data) + # print(json.dumps( + # { + # 'has_matrix': bool(matrix), + # 'matrix':[app.to_dict() for app in matrix] + # } + # , indent=2)) + print(json.dumps([app.to_dict() for app in matrix], indent=2)) + else: + parser.print_help() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/ci/ci_tools/release_helper_test.py b/tools/ci/ci_tools/release_helper_test.py new file mode 100644 index 00000000..b5211902 --- /dev/null +++ b/tools/ci/ci_tools/release_helper_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from .release_helper import * + +rp_pr_1 = '''{ + "releases_created": "false", + "paths_released": "[]", + "prs_created": "true", + "pr": "{\"headBranchName\":\"release-please--branches--main\",\"baseBranchName\":\"main\",\"number\":397,\"title\":\"chore: release main\",\"body\":\":robot: I have created a release *beep* *boop*\\n---\\n\\n\\n
pyTermTk: 0.43.0-a.0\\n\\n## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **kodeTab:** reworked iterWidget in iterItems\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Fixes\\n\\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\\n\\n\\n### Chores\\n\\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\\n\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))\\n
\\n\\n
ttkode: 0.4.0-a.2\\n\\n## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n
\\n\\n
tlogg: 0.7.0-a.0\\n\\n## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n
\\n\\n---\\nThis PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).\",\"files\":[],\"labels\":[\"autorelease: pending\"]}", + "prs": "[{\"headBranchName\":\"release-please--branches--main\",\"baseBranchName\":\"main\",\"number\":397,\"title\":\"chore: release main\",\"body\":\":robot: I have created a release *beep* *boop*\\n---\\n\\n\\n
pyTermTk: 0.43.0-a.0\\n\\n## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **kodeTab:** reworked iterWidget in iterItems\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Fixes\\n\\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\\n\\n\\n### Chores\\n\\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\\n\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))\\n
\\n\\n
ttkode: 0.4.0-a.2\\n\\n## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n
\\n\\n
tlogg: 0.7.0-a.0\\n\\n## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n
\\n\\n---\\nThis PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).\",\"files\":[],\"labels\":[\"autorelease: pending\"]}]" + } +''' + +rp_release_1 = '''{ + "releases_created": "true", + "libs/pyTermTk--release_created": "true", + "libs/pyTermTk--id": "222844982", + "libs/pyTermTk--name": "pyTermTk: v0.43.0-a.0", + "libs/pyTermTk--tag_name": "pyTermTk-v0.43.0-a.0", + "libs/pyTermTk--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd", + "libs/pyTermTk--body": "## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **kodeTab:** reworked iterWidget in iterItems\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Fixes\n\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\n\n\n### Chores\n\n* autogen code for scrollarea classes ([#406](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/406)) ([fef1b0e](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/fef1b0ea5bd6ddc8f3e8f93a23ea156071e77493))\n* **Input:** add support for ctrl and other key comination ([#404](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/404)) ([5c2bb92](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/5c2bb9202cd819aa573e9f0d9ea966a4d0e5c485))\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\n* **spinbox:** fix return type ([ddc53a0](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/ddc53a07653a6f3aa958509d7d400cc6c6264d91))\n* **spinbox:** handle left/right wheel event ([ce961a6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/ce961a657573ee520b73fca7d4ae721a8837a1d0))\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\n\n\n### Refactors\n\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))", + "libs/pyTermTk--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/pyTermTk-v0.43.0-a.0", + "libs/pyTermTk--draft": "false", + "libs/pyTermTk--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844982/assets{?name,label}", + "libs/pyTermTk--path": "libs/pyTermTk", + "libs/pyTermTk--version": "0.43.0-a.0", + "libs/pyTermTk--major": "0", + "libs/pyTermTk--minor": "43", + "libs/pyTermTk--patch": "0", + "libs/pyTermTk--prNumber": "397", + "apps/ttkode--release_created": "true", + "apps/ttkode--id": "222844984", + "apps/ttkode--name": "ttkode: v0.4.0-a.2", + "apps/ttkode--tag_name": "ttkode-v0.4.0-a.2", + "apps/ttkode--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd", + "apps/ttkode--body": "## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Features\n\n* add save feature ([#407](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/407)) ([26ff9b2](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/26ff9b2f0a81bddadeb6849d5d560ae67406f973))\n\n\n### Refactors\n\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))", + "apps/ttkode--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/ttkode-v0.4.0-a.2", + "apps/ttkode--draft": "false", + "apps/ttkode--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844984/assets{?name,label}", + "apps/ttkode--path": "apps/ttkode", + "apps/ttkode--version": "0.4.0-a.2", + "apps/ttkode--major": "0", + "apps/ttkode--minor": "4", + "apps/ttkode--patch": "0", + "apps/ttkode--prNumber": "397", + "apps/tlogg--release_created": "true", + "apps/tlogg--id": "222844986", + "apps/tlogg--name": "tlogg: v0.7.0-a.0", + "apps/tlogg--tag_name": "tlogg-v0.7.0-a.0", + "apps/tlogg--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd", + "apps/tlogg--body": "## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Refactors\n\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))", + "apps/tlogg--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/tlogg-v0.7.0-a.0", + "apps/tlogg--draft": "false", + "apps/tlogg--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844986/assets{?name,label}", + "apps/tlogg--path": "apps/tlogg", + "apps/tlogg--version": "0.7.0-a.0", + "apps/tlogg--major": "0", + "apps/tlogg--minor": "7", + "apps/tlogg--patch": "0", + "apps/tlogg--prNumber": "397", + "paths_released": "[\"libs/pyTermTk\",\"apps/ttkode\",\"apps/tlogg\"]", + "prs_created": "false" + }''' \ No newline at end of file diff --git a/tools/ci/ci_tools/social/__init__.py b/tools/ci/ci_tools/social/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/ci/ci_tools/social/notify_discord.py b/tools/ci/ci_tools/social/notify_discord.py new file mode 100644 index 00000000..869b69da --- /dev/null +++ b/tools/ci/ci_tools/social/notify_discord.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os, sys +import re +import asyncio +import argparse +from typing import Dict,List,Any + +import discord + +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(current_dir) +from ci_tools.social.social_common import get_social_data, SocialData, get_env_var + +async def send_discord_message(version: str, data:SocialData): + token = get_env_var("DISCORD_TOKEN") + message = get_env_var("MESSAGE") + + intents = discord.Intents.default() + client = discord.Client(intents=intents) + + embed = discord.Embed( + title=f"{data.name} Released!!!", + url=data.link, + # description="Here's a new feature we added.", + color=0x00ff00, + ) + embed.add_field(name="Version", value=version, inline=True) + # embed.add_field(name="What's New:", value=message, inline=False) + + # embed.set_image(url="https://example.com/image.png") + # embed.add_field(name="Feature", value="Auto-messaging", inline=False) + embed.set_footer(text="Bot by Pier...") + message = re.sub(r'\((https?://[^\)]+)\)', r'(<\1>)', message).replace('\\n','\n')[:2000] + # print(message) + # exit(1) + + @client.event + async def on_ready(): + print(f'Logged in as {client.user}') + channel = client.get_channel(data.discord_channel_id) + await channel.send(embed=embed) + await channel.send(message) + await client.close() # Optional: close after sending + + await client.start(token) + +def main(): + parser = argparse.ArgumentParser(description="Send a Discord notification.") + parser.add_argument("app", type=str, help="The application name.") + parser.add_argument("version", type=str, help="The application version.") + args = parser.parse_args() + + data = get_social_data(args.app) + if not data: + raise ValueError(f"app: {args.app} is not recognised") + + asyncio.run(send_discord_message(args.version, data)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/ci/ci_tools/social/social_common.py b/tools/ci/ci_tools/social/social_common.py new file mode 100644 index 00000000..8cf338a0 --- /dev/null +++ b/tools/ci/ci_tools/social/social_common.py @@ -0,0 +1,68 @@ +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE.from dataclasses import dataclass + +__all__ = ['get_social_data','SocialData','get_env_var'] + +import os +from dataclasses import dataclass +from typing import List + +@dataclass +class SocialData(): + name: str + link: str + discord_channel_id: int + +_all_data:List[SocialData] = [ + SocialData( + name='pytermtk', + link='https://github.com/ceccopierangiolieugenio/pyTermTk', + discord_channel_id=1379381341145268305, + ), + SocialData( + name='ttkode', + link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkode', + discord_channel_id=1379381474783924295, + ), + SocialData( + name='dumbpainttool', + link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/dumbPaintTool', + discord_channel_id=1379381571412430931, + ), + SocialData( + name='tlogg', + link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/tlogg', + discord_channel_id=1379381593378000916, + ), +] + +def get_social_data(app:str) -> SocialData: + for _sd in _all_data: + if _sd.name.lower() == app.lower(): + return _sd + raise ValueError(f"app: {app} is not recognised") + +def get_env_var(name:str) -> str: + value = os.environ.get(name) + if value is None: + raise EnvironmentError(f"{name} environment variable is not available") + return value \ No newline at end of file diff --git a/tools/ci/pyproject.toml b/tools/ci/pyproject.toml new file mode 100644 index 00000000..aede014d --- /dev/null +++ b/tools/ci/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] + requires = ["setuptools>=45", "wheel"] + build-backend = "setuptools.build_meta" + +[project] + name = "ci_tools" + version = "0.1.0" + description = "ci helpers" + authors = [ + {name = "Eugenio Parodi", email = "ceccopierangiolieugenio@googlemail.com"}, + ] + requires-python = ">=3.9" + dependencies = [ + 'GitPython==3.1.44' + ] + +[project.optional-dependencies] + social = [ + 'discord.py==2.5.2' + ] + +[project.scripts] + release-helper = "ci_tools.release_helper:main" + notify-discord = "ci_tools.social.notify_discord:main" + +[tool.setuptools] + packages = ["ci_tools"] \ No newline at end of file