From a3f594e7f0cf42cd70de615798045e8e5e237295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Sat, 30 May 2026 11:58:50 +0100 Subject: [PATCH 1/6] feat: rework release workflows - Created release-metadata.json and release.test.json to store release information for libs and apps. - Implemented release_helper.py to manage release metadata and version upgrades with enhanced argument parsing. - Added release_save.py to merge release data with input data and save it back to the release file. - Updated pyproject.toml to include new script entry points for release-save and release-helper. --- .github/codeql/codeql-config.yml | 9 + .github/workflows/_scratchpad.yml | 2 +- .github/workflows/codeql.yml | 8 +- .github/workflows/itch-publish.yml | 2 +- .github/workflows/notify-social.yml | 162 ++++++++++----- .github/workflows/python-publish.yml | 8 +- .github/workflows/release-doc.yml | 2 +- .github/workflows/release-sandbox-bin.yml | 2 +- .github/workflows/release-sandbox.yml | 2 +- .github/workflows/release.yml | 212 ++++++++++++-------- .github/workflows/testing.yml | 2 +- .github/workflows/workflow-health-check.yml | 92 +++++++++ tools/ci/ci_tools/release_helper.py | 51 +++-- tools/ci/ci_tools/release_save.py | 74 +++++++ tools/ci/pyproject.toml | 1 + 15 files changed, 468 insertions(+), 161 deletions(-) create mode 100644 .github/codeql/codeql-config.yml create mode 100644 .github/workflows/workflow-health-check.yml create mode 100644 tools/ci/ci_tools/release_save.py diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000..7fbb361a4 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,9 @@ +name: "pyTermTk CodeQL config" + +# Keep analysis focused on source code and avoid generated artifacts. +paths-ignore: + - build/** + - tmp/** + - '**/*.egg-info/**' + - '**/__pycache__/**' + - docs/source/_build/** diff --git a/.github/workflows/_scratchpad.yml b/.github/workflows/_scratchpad.yml index 47d9a479a..db1dda458 100644 --- a/.github/workflows/_scratchpad.yml +++ b/.github/workflows/_scratchpad.yml @@ -25,6 +25,6 @@ jobs: GHE: ${{ toJson(github.event) }} GHEH: ${{ toJson(github.event.head_commit) }} GHEHM: ${{ toJson(github.event.head_commit.modified) }} - # - uses: actions/checkout@v4 + # - uses: actions/checkout@v6 # with: # ref: ${{ github.sha }} \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8788d174c..21ee46490 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -11,6 +11,10 @@ # name: "CodeQL Advanced" +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + on: push: branches: [ "main" ] @@ -59,7 +63,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` @@ -73,6 +77,8 @@ jobs: with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql/codeql-config.yml + queries: security-extended,security-and-quality # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. diff --git a/.github/workflows/itch-publish.yml b/.github/workflows/itch-publish.yml index befd47b9e..e54377012 100644 --- a/.github/workflows/itch-publish.yml +++ b/.github/workflows/itch-publish.yml @@ -23,7 +23,7 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Butler shell: bash run: | diff --git a/.github/workflows/notify-social.yml b/.github/workflows/notify-social.yml index 24009decf..ce1557edd 100644 --- a/.github/workflows/notify-social.yml +++ b/.github/workflows/notify-social.yml @@ -1,7 +1,8 @@ -name: Release Sandbox +name: Notify Social permissions: contents: read + actions: read on: workflow_dispatch: @@ -10,75 +11,140 @@ on: description: The changed app type: string default: pyTermTk - version: - description: The app version + run_id: + description: Workflow run ID to download artifacts from type: string - default: v0.0.0 - discord-message: - description: The release message - type: string - default: pyTermTk released - github-discussion-message: - description: The release message - type: string - default: pyTermTk released + required: true + notify_discord: + description: Notify on Discord + type: boolean + default: false + notify_github_discussion: + description: Notify on GitHub Discussion + type: boolean + default: false + notify_bluesky: + description: Notify on Bluesky + type: boolean + default: false + notify_twitter: + description: Notify on Twitter + type: boolean + default: false 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 + run_id: + description: Workflow run ID to download artifacts from type: string - default: pyTermTk released - github-discussion-message: - description: The release message - type: string - default: pyTermTk released + required: true + notify_discord: + description: Notify on Discord + type: boolean + default: false + notify_github_discussion: + description: Notify on GitHub Discussion + type: boolean + default: false + notify_bluesky: + description: Notify on Bluesky + type: boolean + default: false + notify_twitter: + description: Notify on Twitter + type: boolean + default: false jobs: - notify-discord: - name: Notify Discord + notify: + name: Notify ${{ inputs.app }} to the socials + continue-on-error: true runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.x' + - name: Install dependencies + shell: bash + run: + pip install -e 'tools/ci[social]' + + - uses: actions/download-artifact@v8 with: - python-version: "3.x" - - name: Instrall deps + name: release-metadata.json + run-id: ${{ inputs.run_id }} + + - name: Extract ${{ inputs.app }} params + id: app-params run: | - python -m pip install discord.py - - name: Deploy Discord message + echo "params<<_EOF" >> $GITHUB_OUTPUT + release-helper \ + --config .release-please-config.json \ + --manifest .release-please-manifest.json \ + --release release-metadata.json \ + --app-filter ${{ inputs.app }} \ + matrix all >> $GITHUB_OUTPUT + echo "_EOF" >> $GITHUB_OUTPUT + + - name: Validate extracted params + shell: bash + run: | + PARAMS='${{ steps.app-params.outputs.params }}' + if [[ -z "$PARAMS" || "$PARAMS" == "{}" ]]; then + echo "No release metadata found for app=${{ inputs.app }} in run_id=${{ inputs.run_id }}" + exit 1 + fi + _NAME="$(jq -r '.name // empty' <<< "$PARAMS")" + _VERSION="$(jq -r '.version // empty' <<< "$PARAMS")" + if [[ -z "$_NAME" || -z "$_VERSION" ]]; then + echo "Invalid params payload: $PARAMS" + exit 1 + fi + + - name: Notify ${{ fromJson(steps.app-params.outputs.params).name }} on Discord + if: ${{ inputs.notify_discord }} + continue-on-error: true env: - MESSAGE: ${{ inputs.discord-message }} + RN: ${{ fromJson(steps.app-params.outputs.params).release_notes }} + MESSAGE: ${{ fromJson(steps.app-params.outputs.params).release_notes }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} run: | - python tools/ci/social/notify_discord.py ${{ inputs.app }} ${{ inputs.version }} + notify-discord ${{ fromJson(steps.app-params.outputs.params).name }} ${{ fromJson(steps.app-params.outputs.params).version }} - notify-github-discussion: - name: Notify Github Discussion - runs-on: self-hosted - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.sha }} - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Deploy Github Discussion + - name: Notify ${{ fromJson(steps.app-params.outputs.params).name }} on Github Discussion + if: ${{ inputs.notify_github_discussion }} + continue-on-error: true env: - DISCUSSION_BODY: ${{ inputs.github-discussion-message}} - GH_DISCUSSION_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + RN: ${{ fromJson(steps.app-params.outputs.params).release_notes }} + MESSAGE: ${{ fromJson(steps.app-params.outputs.params).release_notes }} + GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + GH_DISCUSSION_TOKEN: ${{ secrets.GH_DISCUSSION_TOKEN }} run: | - export DISCUSSION_TITLE="${{ inputs.app }} ${{ inputs.version }} Released!!!" - python tools/ci/social/notify_github_discussion.py ${{ inputs.app }} ${{ inputs.version }} + notify-gh-discussion ${{ fromJson(steps.app-params.outputs.params).name }} ${{ fromJson(steps.app-params.outputs.params).version }} + - name: Notify ${{ fromJson(steps.app-params.outputs.params).name }} on Bluesky + if: ${{ inputs.notify_bluesky }} + continue-on-error: true + env: + BLUESKY_APP_PWD: ${{ secrets.BLUESKY_APP_PWD }} + BLUESKY_APP_IDENTIFIER: ${{ secrets.BLUESKY_APP_IDENTIFIER }} + run: | + notify-bluesky ${{ fromJson(steps.app-params.outputs.params).name }} ${{ fromJson(steps.app-params.outputs.params).version }} + - name: Notify ${{ fromJson(steps.app-params.outputs.params).name }} on Twitter + if: ${{ inputs.notify_twitter }} + continue-on-error: true + env: + X_API_KEY: ${{ secrets.X_API_KEY }} + X_API_SECRET: ${{ secrets.X_API_SECRET }} + X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }} + X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }} + run: | + notify-twitter ${{ fromJson(steps.app-params.outputs.params).name }} ${{ fromJson(steps.app-params.outputs.params).version }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 9c2942019..6309aab4c 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -45,8 +45,8 @@ jobs: working-directory: ${{ inputs.pkg_folder }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: "3.x" - name: Build release distributions @@ -60,7 +60,7 @@ jobs: python -m build echo '::endgroup::' - name: Upload distributions - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: release-dist-${{ inputs.pkg_name }} path: ${{ inputs.pkg_folder }}/dist/ @@ -74,7 +74,7 @@ jobs: id-token: write steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" - name: Retrieve release distributions diff --git a/.github/workflows/release-doc.yml b/.github/workflows/release-doc.yml index 23368b342..96cf036e9 100644 --- a/.github/workflows/release-doc.yml +++ b/.github/workflows/release-doc.yml @@ -28,7 +28,7 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} - name: Gen Docs diff --git a/.github/workflows/release-sandbox-bin.yml b/.github/workflows/release-sandbox-bin.yml index e27912227..1b87e6164 100644 --- a/.github/workflows/release-sandbox-bin.yml +++ b/.github/workflows/release-sandbox-bin.yml @@ -28,7 +28,7 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} diff --git a/.github/workflows/release-sandbox.yml b/.github/workflows/release-sandbox.yml index b96a7fd6a..45d32c21a 100644 --- a/.github/workflows/release-sandbox.yml +++ b/.github/workflows/release-sandbox.yml @@ -28,7 +28,7 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce839ac2a..d282bf60c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,11 +34,13 @@ jobs: matrix-pypi: ${{ steps.set-matrix.outputs.matrix_pypi }} matrix-itch: ${{ steps.set-matrix.outputs.matrix_itch }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} + - name: Trust git directory run: git config --global --add safe.directory $GITHUB_WORKSPACE + - uses: googleapis/release-please-action@v4 timeout-minutes: 15 id: release-please @@ -46,20 +48,40 @@ jobs: token: ${{ secrets.GH_PAT_TOKEN }} config-file: .release-please-config.json manifest-file: .release-please-manifest.json + - name: Print outputs shell: bash env: OUTPUTS: ${{ toJSON(steps.release-please.outputs) }} run: | echo OUTPUTS: "$OUTPUTS" - - name: Set up Python 3.11 - uses: actions/setup-python@v5 + + - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: '3.x' + - name: Install dependencies shell: bash run: pip install -e 'tools/ci[social]' + + - name: Save Release + if: ${{ steps.release-please.outputs.releases_created == 'true'}} + shell: bash + env: + RP_OUT: ${{ toJson(steps.release-please.outputs) }} + run: | + release-save \ + --release ${{ github.workspace }}/release-metadata.json \ + <<< ${RP_OUT} + + - name: Upload Artifact + if: ${{ steps.release-please.outputs.releases_created == 'true'}} + uses: actions/upload-artifact@v7 + with: + name: release-metadata.json + path: ${{ github.workspace }}/release-metadata.json + - name: Update Version if: ${{ steps.release-please.outputs.prs_created == 'true'}} shell: bash @@ -67,6 +89,19 @@ jobs: GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} RP_OUT: ${{ toJson(steps.release-please.outputs) }} run: | + _CLONE_DIR=$(mktemp -d) + + echo '::group::Setup Git' + git config --global user.name 'Eugenio Parodi - Action' + git config --global user.email 'ceccopierangioliegenio@googlemail.com' + git clone \ + -b ${{ fromJson(steps.release-please.outputs.pr).headBranchName }} \ + https://${GITHUB_TOKEN}@github.com/ceccopierangiolieugenio/pyTermTk.git \ + ${_CLONE_DIR} + echo '::endgroup::' + + cd ${_CLONE_DIR} + _get_name(){ _ITEM=$1 jq -r ".packages[\"${_ITEM}\"][\"package-name\"]" .release-please-config.json @@ -76,22 +111,15 @@ jobs: 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' - git clone \ - -b ${{ fromJson(steps.release-please.outputs.pr).headBranchName }} \ - https://${GITHUB_TOKEN}@github.com/ceccopierangiolieugenio/pyTermTk.git \ - pyTermTk.new - echo '::endgroup::' - cd pyTermTk.new + echo '::group::🍧 Print the Versions' release-helper \ --config .release-please-config.json \ --manifest .release-please-manifest.json \ - info <<< '{}' + --release ${{ github.workspace }}/release-metadata.json \ + info echo '::endgroup::' echo '::group::🍓 Update the Versions' @@ -105,7 +133,8 @@ jobs: release-helper \ --config .release-please-config.json \ --manifest .release-please-manifest.json \ - upgrade <<< ${RP_OUT} + --release ${{ github.workspace }}/release-metadata.json \ + upgrade cp libs/pyTermTk/CHANGELOG.md CHANGELOG.md @@ -122,7 +151,9 @@ jobs: git push fi echo '::endgroup::' - - name: Define the Matrix strategy + + - name: Define the Matrix strategy1 + if: ${{ steps.release-please.outputs.releases_created == 'true'}} id: set-matrix env: RP_OUT: ${{ toJson(steps.release-please.outputs) }} @@ -131,23 +162,27 @@ jobs: release-helper \ --config .release-please-config.json \ --manifest .release-please-manifest.json \ - matrix all <<< ${RP_OUT} >> $GITHUB_OUTPUT + --release ${{ github.workspace }}/release-metadata.json \ + matrix all >> $GITHUB_OUTPUT echo "_EOF" >> $GITHUB_OUTPUT echo "matrix_itch<<_EOF" >> $GITHUB_OUTPUT release-helper \ --config .release-please-config.json \ --manifest .release-please-manifest.json \ - matrix itch <<< ${RP_OUT} >> $GITHUB_OUTPUT + --release ${{ github.workspace }}/release-metadata.json \ + matrix itch >> $GITHUB_OUTPUT echo "_EOF" >> $GITHUB_OUTPUT echo "matrix_pypi<<_EOF" >> $GITHUB_OUTPUT release-helper \ --config .release-please-config.json \ --manifest .release-please-manifest.json \ - matrix pypi <<< ${RP_OUT} >> $GITHUB_OUTPUT + --release ${{ github.workspace }}/release-metadata.json \ + matrix pypi >> $GITHUB_OUTPUT echo "_EOF" >> $GITHUB_OUTPUT + pyTermTk-deploy-artifacts: if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }} # runs-on: ubuntu-latest @@ -156,7 +191,7 @@ jobs: name: Deploy pyTermTk to github release needs: release-please steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} - name: Create Artifacts @@ -190,18 +225,20 @@ jobs: TAG_NAME: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--tag_name'] }} run: | _TMP=${{ steps.create-artifacts.outputs.artifacts_dir }} - gh release upload ${TAG_NAME} ${_TMP}/TermTk.tgz - gh release upload ${TAG_NAME} ${_TMP}/tutorial.tgz - gh release upload ${TAG_NAME} ${_TMP}/tests.tgz - gh release upload ${TAG_NAME} ${_TMP}/demo.tgz + gh release upload ${TAG_NAME} ${_TMP}/TermTk.tgz --clobber + gh release upload ${TAG_NAME} ${_TMP}/tutorial.tgz --clobber + gh release upload ${TAG_NAME} ${_TMP}/tests.tgz --clobber + gh release upload ${TAG_NAME} ${_TMP}/demo.tgz --clobber rm -rf ${_TMP} + pyTermTk-deploy-sandbox: name: Deploy pyTermTk Sandbox uses: ./.github/workflows/release-sandbox.yml needs: release-please secrets: inherit + pyTermTk-deploy-sandbox-bin: if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }} name: Deploy pyTermTk Sandbox Binaries @@ -211,6 +248,7 @@ jobs: - pyTermTk-deploy-artifacts secrets: inherit + pyTermTk-deploy-docs: if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }} name: Deploy pyTermTk Docs @@ -220,12 +258,14 @@ jobs: - pyTermTk-deploy-artifacts secrets: inherit + publish-pypi: if: ${{ needs.release-please.outputs.matrix-pypi != '[]' }} name: Publish pypi ${{ matrix.name }} needs: - release-please strategy: + fail-fast: false matrix: include: ${{ fromJson(needs.release-please.outputs.matrix-pypi) }} uses: ./.github/workflows/python-publish.yml @@ -234,22 +274,6 @@ jobs: pkg_folder: ${{ matrix.path }} 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'] }} @@ -266,48 +290,72 @@ jobs: name: Notify ${{ matrix.name }} to the socials needs: - release-please - runs-on: self-hosted strategy: + fail-fast: false 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 }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - run: | - notify-discord ${{ matrix.name }} v${{ matrix.version }} - - name: Notify ${{ matrix.name }} on Github Discussion - env: - RN: ${{ matrix.release-notes }} - MESSAGE: ${{ matrix.release-notes }} - GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} - GH_DISCUSSION_TOKEN: ${{ secrets.GH_DISCUSSION_TOKEN }} - run: | - notify-gh-discussion ${{ matrix.name }} v${{ matrix.version }} - - name: Notify ${{ matrix.name }} on Bluesky - env: - BLUESKY_APP_PWD: ${{ secrets.BLUESKY_APP_PWD }} - BLUESKY_APP_IDENTIFIER: ${{ secrets.BLUESKY_APP_IDENTIFIER }} - run: | - notify-bluesky ${{ matrix.name }} v${{ matrix.version }} - - name: Notify ${{ matrix.name }} on Twitter - env: - X_API_KEY: ${{ secrets.X_API_KEY }} - X_API_SECRET: ${{ secrets.X_API_SECRET }} - X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }} - X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }} - run: | - notify-twitter ${{ matrix.name }} ${{ matrix.version }} \ No newline at end of file + uses: ./.github/workflows/notify-social.yml + with: + app: ${{ matrix.name }} + run_id: ${{ github.run_id }} + notify_discord: true + notify_github_discussion: true + notify_bluesky: true + notify_twitter: true + secrets: inherit + + # notify: + # if: ${{ needs.release-please.outputs.matrix != '[]' }} + # name: Notify ${{ matrix.name }} to the socials + # needs: + # - release-please + # continue-on-error: true + # runs-on: self-hosted + # strategy: + # fail-fast: false + # matrix: + # include: ${{ fromJson(needs.release-please.outputs.matrix) }} + # steps: + # - uses: actions/checkout@v6 + # with: + # ref: ${{ github.sha }} + # - uses: actions/setup-python@v6 + # with: + # python-version: '3.x' + # - name: Install dependencies + # shell: bash + # run: + # pip install -e 'tools/ci[social]' + # - name: Notify ${{ matrix.name }} on Discord + # continue-on-error: true + # env: + # RN: ${{ matrix.release-notes }} + # MESSAGE: ${{ matrix.release-notes }} + # DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + # run: | + # notify-discord ${{ matrix.name }} v${{ matrix.version }} + # - name: Notify ${{ matrix.name }} on Github Discussion + # continue-on-error: true + # env: + # RN: ${{ matrix.release-notes }} + # MESSAGE: ${{ matrix.release-notes }} + # GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + # GH_DISCUSSION_TOKEN: ${{ secrets.GH_DISCUSSION_TOKEN }} + # run: | + # notify-gh-discussion ${{ matrix.name }} v${{ matrix.version }} + # - name: Notify ${{ matrix.name }} on Bluesky + # continue-on-error: true + # env: + # BLUESKY_APP_PWD: ${{ secrets.BLUESKY_APP_PWD }} + # BLUESKY_APP_IDENTIFIER: ${{ secrets.BLUESKY_APP_IDENTIFIER }} + # run: | + # notify-bluesky ${{ matrix.name }} v${{ matrix.version }} + # - name: Notify ${{ matrix.name }} on Twitter + # continue-on-error: true + # env: + # X_API_KEY: ${{ secrets.X_API_KEY }} + # X_API_SECRET: ${{ secrets.X_API_SECRET }} + # X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }} + # X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }} + # run: | + # notify-twitter ${{ matrix.name }} ${{ matrix.version }} \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8f38fe455..518488853 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -18,7 +18,7 @@ jobs: name: Test AutoGen Code runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.sha }} - name: Check Autogen Code is up to dated diff --git a/.github/workflows/workflow-health-check.yml b/.github/workflows/workflow-health-check.yml new file mode 100644 index 000000000..82f60ec13 --- /dev/null +++ b/.github/workflows/workflow-health-check.yml @@ -0,0 +1,92 @@ +name: Workflow Health Check + +on: + workflow_dispatch: + pull_request: + paths: + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + push: + branches: + - main + paths: + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + schedule: + - cron: '17 5 * * 1' + +permissions: + contents: read + +jobs: + actionlint: + name: Validate GitHub Actions workflows + runs-on: self-hosted + steps: + - uses: actions/checkout@v6 + + - name: Install actionlint + shell: bash + env: + # Safe bump process: + # 1) Set this to an exact released tag (vX.Y.Z), never 'latest'. + # 2) Keep checksum verification enabled below. + # 3) Optional sanity check: compare with release checksums file in the same tag. + ACTIONLINT_TAG_VERSION: v1.7.12 + run: | + set -euo pipefail + + version="${ACTIONLINT_TAG_VERSION#v}" + case "$OSTYPE" in + linux-*) os="linux" ;; + darwin*) os="darwin" ;; + freebsd*) os="freebsd" ;; + *) + echo "Unsupported OSTYPE: $OSTYPE" >&2 + exit 1 + ;; + esac + + machine="$(uname -m)" + case "$machine" in + x86_64) arch="amd64" ;; + i?86) arch="386" ;; + aarch64|arm64) arch="arm64" ;; + arm*) arch="armv6" ;; + *) + echo "Unsupported architecture: $machine" >&2 + exit 1 + ;; + esac + + file="actionlint_${version}_${os}_${arch}.tar.gz" + checksums="actionlint_${version}_checksums.txt" + base_url="https://github.com/rhysd/actionlint/releases/download/v${version}" + + curl -fsSLo "$file" "${base_url}/${file}" + curl -fsSLo "$checksums" "${base_url}/${checksums}" + + expected="$(awk -v f="$file" '$2 == f { print $1 }' "$checksums")" + if [ -z "$expected" ]; then + echo "Checksum not found for $file" >&2 + exit 1 + fi + + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - + else + actual="$(shasum -a 256 "$file" | awk '{print $1}')" + if [ "$actual" != "$expected" ]; then + echo "Checksum mismatch for $file" >&2 + exit 1 + fi + fi + + tar -xzf "$file" actionlint + chmod +x ./actionlint + rm -f "$file" "$checksums" + + - name: Run actionlint + shell: bash + run: | + ./actionlint -color \ No newline at end of file diff --git a/tools/ci/ci_tools/release_helper.py b/tools/ci/ci_tools/release_helper.py index 776650259..40719514a 100644 --- a/tools/ci/ci_tools/release_helper.py +++ b/tools/ci/ci_tools/release_helper.py @@ -30,7 +30,7 @@ from dataclasses import dataclass from enum import Enum -from typing import List, Dict, Union +from typing import Any, List, Dict, Union class MatrixType(Enum): ALL = "all" @@ -55,7 +55,7 @@ def to_dict(self) -> Dict[str, Union[str,bool]]: "pypi" : self.pypi, "itch" : self.itch, "tag" : self.tag, - "release-notes" : self.release_notes + "release_notes" : self.release_notes } def _print_info(apps_data:List[_AppData]) -> None: @@ -76,7 +76,7 @@ def _print_info(apps_data:List[_AppData]) -> None: # echo 🆗 No new release found for ${_NAME} # fi # done -def _upgrade_files(apps_data:List[_AppData], rp_data:Dict, dry_run:bool) -> None: +def _upgrade_files(apps_data:List[_AppData], rp_data:Dict[str, Any], 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}") @@ -106,7 +106,7 @@ def _upgrade_files(apps_data:List[_AppData], rp_data:Dict, dry_run:bool) -> None print(pattern.sub(replacement, line), end="") -def _gen_matrix(matrix_type: MatrixType, rp_data:Dict, apps_data:List[_AppData]) -> List[_AppData]: +def _gen_matrix(matrix_type: MatrixType, rp_data:Dict[str, Any], 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: @@ -133,10 +133,12 @@ def _gen_matrix(matrix_type: MatrixType, rp_data:Dict, apps_data:List[_AppData]) return apps def main(): - parser = argparse.ArgumentParser(description="Release Helper Script") + parser = argparse.ArgumentParser(description="Utilities for release metadata and matrix generation") # 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") + parser.add_argument("--config", metavar="config_file", help="Path to the release configuration JSON") + parser.add_argument("--manifest", metavar="manifest_file", help="Path to the manifest versions JSON") + parser.add_argument("--release", metavar="release_file", help="Path to the release-please outputs JSON") + parser.add_argument("--app-filter", metavar="app_filter", help="Filter output to a single app name") subparsers = parser.add_subparsers(title="Features", dest="feature") @@ -144,7 +146,7 @@ def main(): 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") + upgrade_parser.add_argument("--dry-run", action="store_true", help="Show planned changes without applying them") # Apps Feature apps_parser = subparsers.add_parser("apps", help="Apps related operations") @@ -161,29 +163,31 @@ def main(): config = {} if args.config: try: - config = json.load(args.config) # Parse the JSON file + with open(args.config, 'r') as f: + config = json.load(f) # 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.") + print(f"Error: Configuration file '{args.config}' 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 + with open(args.manifest, 'r') as f: + manifest = json.load(f) # 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.") + print(f"Error: Configuration file '{args.manifest}' is not valid JSON.") sys.exit(1) - input_data = {} - if not sys.stdin.isatty(): # or sys.stdin.peek(1): + release = {} + if args.release: try: - read = sys.stdin.read() - input_data = json.loads(read) + with open(args.release, 'r') as f: + release = json.load(f) # Parse the JSON file except json.JSONDecodeError: - print("Error: Invalid JSON input.") + print(f"Error: Release file '{args.release}' is not valid JSON.") sys.exit(1) apps_data = [ @@ -201,7 +205,7 @@ def main(): _print_info(apps_data) elif args.feature == "upgrade": print(args) - _upgrade_files(apps_data, input_data, args.dry_run) + _upgrade_files(apps_data, release, args.dry_run) elif args.feature == "apps": if args.list: print("Available Apps:") @@ -214,14 +218,21 @@ def main(): apps_parser.print_help() elif args.feature == "matrix": matrix_type = MatrixType(args.type) - matrix = _gen_matrix(matrix_type, input_data, apps_data) + matrix = _gen_matrix(matrix_type, release, 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)) + if args.app_filter: + apps_filtered = [app.to_dict() for app in matrix if app.name == args.app_filter] + if apps_filtered: + print(json.dumps(apps_filtered[0], indent=2)) + else: + print('{}') + else: + print(json.dumps([app.to_dict() for app in matrix], indent=2)) else: parser.print_help() diff --git a/tools/ci/ci_tools/release_save.py b/tools/ci/ci_tools/release_save.py new file mode 100644 index 000000000..d9ccf3673 --- /dev/null +++ b/tools/ci/ci_tools/release_save.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2026 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 + +def main(): + parser = argparse.ArgumentParser(description="Release Helper Script") + # Configuration File Argument + parser.add_argument("--release", metavar="release_file", help="Path to the release file") + + args = parser.parse_args() + + # Load and parse configuration file if provided + release = {} + if args.release: + try: + with open(args.release, 'r') as f: + release = json.load(f) # Parse the JSON file + except json.JSONDecodeError: + print(f"Error: Release file '{args.release}' is not valid JSON.") + sys.exit(1) + + input_data = {} + if not sys.stdin.isatty(): + try: + read = sys.stdin.read() + input_data = json.loads(read) + except json.JSONDecodeError: + print("Error: Invalid JSON input.") + sys.exit(1) + + # Merge release and input_data, with input_data taking priority + merged = {**release, **input_data} + + # Save merged data back to release file + if args.release: + try: + with open(args.release, 'w') as f: + json.dump(merged, f, indent=2) + except IOError as e: + print(f"Error: Could not write to release file '{args.release}': {e}") + sys.exit(1) + + \ No newline at end of file diff --git a/tools/ci/pyproject.toml b/tools/ci/pyproject.toml index 7f86854bf..c3f2fa2d1 100644 --- a/tools/ci/pyproject.toml +++ b/tools/ci/pyproject.toml @@ -22,6 +22,7 @@ ] [project.scripts] + release-save = "ci_tools.release_save:main" release-helper = "ci_tools.release_helper:main" notify-discord = "ci_tools.social.notify_discord:main" notify-bluesky = "ci_tools.social.notify_bluesky:main" From 7842778442dd4fe8f880118b4675a558c9e15309 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Sat, 30 May 2026 23:30:01 +0100 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- tools/ci/ci_tools/release_save.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tools/ci/ci_tools/release_save.py b/tools/ci/ci_tools/release_save.py index d9ccf3673..c1725aaa3 100644 --- a/tools/ci/ci_tools/release_save.py +++ b/tools/ci/ci_tools/release_save.py @@ -22,16 +22,9 @@ # 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 def main(): parser = argparse.ArgumentParser(description="Release Helper Script") From c8431a40028f1e8863f67bd0a75adcdb9d94d21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Sat, 30 May 2026 23:38:33 +0100 Subject: [PATCH 3/6] feat: add PR title semantic check workflow --- .github/workflows/pr-title-semantic-check.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/pr-title-semantic-check.yml diff --git a/.github/workflows/pr-title-semantic-check.yml b/.github/workflows/pr-title-semantic-check.yml new file mode 100644 index 000000000..c36abc7c0 --- /dev/null +++ b/.github/workflows/pr-title-semantic-check.yml @@ -0,0 +1,44 @@ +name: PR Title Semantic Check + +on: + pull_request: + branches: [ main ] + types: [opened, edited, reopened, synchronize, ready_for_review] + +permissions: + contents: read + +jobs: + semantic-pr-title: + name: Validate PR title (Conventional Commits) + runs-on: self-hosted + steps: + - name: Check PR title format + shell: bash + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + set -euo pipefail + + # Allowed: type(scope): subject + # Also allowed: type!: subject or type(scope)!: subject + pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._/-]+\))?(!)?: .+$' + + if [[ "$PR_TITLE" =~ $pattern ]]; then + echo "✅ PR title is semantic: $PR_TITLE" + exit 0 + fi + + echo "❌ PR title is not semantic: $PR_TITLE" + echo "" + echo "Expected Conventional Commits format:" + echo " type(scope): short description" + echo " type: short description" + echo " type!: breaking change description" + echo "" + echo "Allowed types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test" + echo "Examples:" + echo " feat(workflow): validate PR title format" + echo " fix: handle empty release metadata" + echo " refactor(ci)!: drop legacy publish path" + exit 1 From d7985794531b41ceca44b655762f4d9d60499663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Sat, 30 May 2026 23:45:05 +0100 Subject: [PATCH 4/6] fix: update PR title semantic check pattern to ensure proper formatting --- .github/workflows/pr-title-semantic-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title-semantic-check.yml b/.github/workflows/pr-title-semantic-check.yml index c36abc7c0..af8143e1d 100644 --- a/.github/workflows/pr-title-semantic-check.yml +++ b/.github/workflows/pr-title-semantic-check.yml @@ -22,7 +22,7 @@ jobs: # Allowed: type(scope): subject # Also allowed: type!: subject or type(scope)!: subject - pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._/-]+\))?(!)?: .+$' + pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._/-]+\))?(!)?: [^[:space:]].*$' if [[ "$PR_TITLE" =~ $pattern ]]; then echo "✅ PR title is semantic: $PR_TITLE" From e34b6232197b3ee60f3e030cc70032e66c74b517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Sun, 31 May 2026 00:11:49 +0100 Subject: [PATCH 5/6] refactor: remove commented-out notification steps from release workflow --- .github/workflows/release.yml | 56 ----------------------------------- 1 file changed, 56 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d282bf60c..e9374b57f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -303,59 +303,3 @@ jobs: notify_bluesky: true notify_twitter: true secrets: inherit - - # notify: - # if: ${{ needs.release-please.outputs.matrix != '[]' }} - # name: Notify ${{ matrix.name }} to the socials - # needs: - # - release-please - # continue-on-error: true - # runs-on: self-hosted - # strategy: - # fail-fast: false - # matrix: - # include: ${{ fromJson(needs.release-please.outputs.matrix) }} - # steps: - # - uses: actions/checkout@v6 - # with: - # ref: ${{ github.sha }} - # - uses: actions/setup-python@v6 - # with: - # python-version: '3.x' - # - name: Install dependencies - # shell: bash - # run: - # pip install -e 'tools/ci[social]' - # - name: Notify ${{ matrix.name }} on Discord - # continue-on-error: true - # env: - # RN: ${{ matrix.release-notes }} - # MESSAGE: ${{ matrix.release-notes }} - # DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - # run: | - # notify-discord ${{ matrix.name }} v${{ matrix.version }} - # - name: Notify ${{ matrix.name }} on Github Discussion - # continue-on-error: true - # env: - # RN: ${{ matrix.release-notes }} - # MESSAGE: ${{ matrix.release-notes }} - # GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} - # GH_DISCUSSION_TOKEN: ${{ secrets.GH_DISCUSSION_TOKEN }} - # run: | - # notify-gh-discussion ${{ matrix.name }} v${{ matrix.version }} - # - name: Notify ${{ matrix.name }} on Bluesky - # continue-on-error: true - # env: - # BLUESKY_APP_PWD: ${{ secrets.BLUESKY_APP_PWD }} - # BLUESKY_APP_IDENTIFIER: ${{ secrets.BLUESKY_APP_IDENTIFIER }} - # run: | - # notify-bluesky ${{ matrix.name }} v${{ matrix.version }} - # - name: Notify ${{ matrix.name }} on Twitter - # continue-on-error: true - # env: - # X_API_KEY: ${{ secrets.X_API_KEY }} - # X_API_SECRET: ${{ secrets.X_API_SECRET }} - # X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }} - # X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }} - # run: | - # notify-twitter ${{ matrix.name }} ${{ matrix.version }} \ No newline at end of file From 55d0b03a6ca4514f29f21612cf70cb638f9f451a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Sun, 31 May 2026 00:17:12 +0100 Subject: [PATCH 6/6] chore: update actions/checkout and actions/setup-python to v6 in testing workflow --- .github/workflows/testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 518488853..5efba7b11 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -45,10 +45,10 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }}