From ecc5057a1b2d6b7b780c6ad5fea25a5569382330 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 29 Jan 2026 02:52:33 +0500 Subject: [PATCH 1/7] feat: add firmware stats extraction and PR commenting --- .github/workflows/ci.yml | 53 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 286f14aa..05060400 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,10 @@ name: CI branches: [master] pull_request: +permissions: + contents: read + pull-requests: write + jobs: build: runs-on: ubuntu-latest @@ -34,4 +38,51 @@ jobs: run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1) - name: Build CrossPoint - run: pio run + run: | + set -euo pipefail + pio run | tee pio.log + + - name: Extract firmware stats + id: fw_stats + run: | + set -euo pipefail + ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)" + flash_line="$(grep -E "Flash:\\s" -m1 pio.log || true)" + echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" + echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" + { + echo "## Firmware build stats" + if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi + if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Comment PR with firmware stats + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + uses: actions/github-script@v7 + with: + script: | + const marker = ''; + const ram = `${{ steps.fw_stats.outputs.ram_line }}`.trim(); + const flash = `${{ steps.fw_stats.outputs.flash_line }}`.trim(); + const body = `${marker}\n**Firmware build stats**\n\n- ${ram || 'RAM: not found'}\n- ${flash || 'Flash: not found'}`; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } From baebb3b2e2da28d23fa37ce4cf9f86fd6af99239 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 29 Jan 2026 03:08:02 +0500 Subject: [PATCH 2/7] Adjust condition for commenting PR with firmware stats --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05060400..57c7c2de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: } >> "$GITHUB_STEP_SUMMARY" - name: Comment PR with firmware stats - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | From 873611b98996e30c14f744d70693aa2b73aaa2c5 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 29 Jan 2026 03:21:36 +0500 Subject: [PATCH 3/7] chore: update CI workflows and add labeler configuration --- .github/labeler.yml | 38 +++++++++++++++++ .github/workflows/ci.yml | 52 ++++++++++++++++++----- .github/workflows/pr-formatting-check.yml | 39 +++++++++++++++++ 3 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..eae3f875 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,38 @@ +firmware: + - changed-files: + - any-glob-to-any-file: + - src/** + - lib/** + - open-x4-sdk/** + +ui: + - changed-files: + - any-glob-to-any-file: + - src/activities/** + - src/network/html/** + - docs/images/** + +epub: + - changed-files: + - any-glob-to-any-file: + - lib/Epub/** + +network: + - changed-files: + - any-glob-to-any-file: + - src/network/** + - src/util/UrlUtils.* + - lib/OpdsParser/** + +docs: + - changed-files: + - any-glob-to-any-file: + - docs/** + - README* + - CHANGELOG* + +tests: + - changed-files: + - any-glob-to-any-file: + - test/** + - scripts/** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57c7c2de..e12c26a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,34 @@ permissions: pull-requests: write jobs: - build: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + + clang-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: '3.14' + + - name: Install clang-format-21 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 21 + sudo apt-get update + sudo apt-get install -y clang-format-21 + + - name: Run clang-format + run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1) + + cppcheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -23,19 +50,22 @@ jobs: - name: Install PlatformIO Core run: pip install --upgrade platformio - - name: Install clang-format-21 - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 21 - sudo apt-get update - sudo apt-get install -y clang-format-21 - - name: Run cppcheck run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high - - name: Run clang-format - run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1) + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: '3.14' + + - name: Install PlatformIO Core + run: pip install --upgrade platformio - name: Build CrossPoint run: | diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index 044b7b64..fd9c4d02 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -9,6 +9,7 @@ on: permissions: statuses: write + pull-requests: write jobs: title-check: @@ -21,6 +22,44 @@ jobs: egress-policy: audit - name: Check PR Title + id: title_check uses: amannn/action-semantic-pull-request@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Comment with changelog hint on failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + const marker = ''; + const error = + `${{ steps.title_check.outputs.error_message || steps.title_check.outputs.error || '' }}`.trim(); + const details = error ? `\n\n**Error:** ${error}` : '\n\n**Error:** See workflow logs.'; + const body = `${marker} + **PR title check failed** + + Please use a Conventional Commit-style prefix (e.g., \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`). + If this change should appear in release notes, ensure the title reflects the correct category.${details}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } From 826ccb2d255a1dbc4b2e38dc3e27e13bb49255c5 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 29 Jan 2026 03:22:09 +0500 Subject: [PATCH 4/7] fix: format firmware stats in PR comment as code block --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e12c26a1..344bc945 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: const marker = ''; const ram = `${{ steps.fw_stats.outputs.ram_line }}`.trim(); const flash = `${{ steps.fw_stats.outputs.flash_line }}`.trim(); - const body = `${marker}\n**Firmware build stats**\n\n- ${ram || 'RAM: not found'}\n- ${flash || 'Flash: not found'}`; + const body = `${marker}\n**Firmware build stats**\n\n\`\`\`\n${ram || 'RAM: not found'}\n${flash || 'Flash: not found'}\n\`\`\``; const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, From cb95ddbd04161633faf4d5e816efc2a827f5470e Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Fri, 30 Jan 2026 22:24:51 +0500 Subject: [PATCH 5/7] feat: enhance PR commenting with firmware stats and upload firmware artifact --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 344bc945..514bc98c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,14 @@ jobs: if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi } >> "$GITHUB_STEP_SUMMARY" - - name: Comment PR with firmware stats + - name: Upload firmware.bin artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event_name == 'pull_request' && format('firmware-bin-pr-{0}-{1}', github.event.pull_request.number, github.sha) || format('firmware-bin-{0}', github.sha) }} + path: .pio/build/default/firmware.bin + if-no-files-found: error + + - name: Comment PR with firmware stats and firmware.bin if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: @@ -94,7 +101,27 @@ jobs: const marker = ''; const ram = `${{ steps.fw_stats.outputs.ram_line }}`.trim(); const flash = `${{ steps.fw_stats.outputs.flash_line }}`.trim(); - const body = `${marker}\n**Firmware build stats**\n\n\`\`\`\n${ram || 'RAM: not found'}\n${flash || 'Flash: not found'}\n\`\`\``; + const prNumber = context.payload.pull_request?.number; + const artifactName = prNumber + ? `firmware-bin-pr-${prNumber}-${context.sha}` + : `firmware-bin-${context.sha}`; + let firmwareLine = 'Firmware: artifact not found'; + try { + const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + const firmwareArtifact = artifacts.artifacts.find((artifact) => artifact.name === artifactName); + if (firmwareArtifact) { + firmwareLine = `Firmware: [firmware.bin artifact](${firmwareArtifact.archive_download_url})`; + } else { + firmwareLine = `Firmware: artifact not found (${artifactName})`; + } + } catch (error) { + firmwareLine = `Firmware: artifact lookup failed (${error.message})`; + } + const body = `${marker}\n**Firmware build stats**\n\n\`\`\`\n${ram || 'RAM: not found'}\n${flash || 'Flash: not found'}\n\`\`\`\n\n**Firmware binary**\n${firmwareLine}`; const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, From c008d238d5f42891beefe7afd09c23cc28c49b8c Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Fri, 30 Jan 2026 22:32:27 +0500 Subject: [PATCH 6/7] fix: update firmware artifact link in PR comment to point to the UI --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 514bc98c..1a34f9c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,8 @@ jobs: }); const firmwareArtifact = artifacts.artifacts.find((artifact) => artifact.name === artifactName); if (firmwareArtifact) { - firmwareLine = `Firmware: [firmware.bin artifact](${firmwareArtifact.archive_download_url})`; + const artifactUiUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts/${firmwareArtifact.id}`; + firmwareLine = `Firmware: [firmware.bin artifact](${artifactUiUrl})`; } else { firmwareLine = `Firmware: artifact not found (${artifactName})`; } From a540edd437b0346fe90a84cf1a15032a4a2e572f Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Fri, 30 Jan 2026 22:51:28 +0500 Subject: [PATCH 7/7] feat: capture short SHA for firmware artifact naming in PR comments --- .github/workflows/ci.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a34f9c1..20b4ec74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,10 @@ jobs: set -euo pipefail pio run | tee pio.log + - name: Capture short SHA + id: short_sha + run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + - name: Extract firmware stats id: fw_stats run: | @@ -89,7 +93,7 @@ jobs: - name: Upload firmware.bin artifact uses: actions/upload-artifact@v4 with: - name: ${{ github.event_name == 'pull_request' && format('firmware-bin-pr-{0}-{1}', github.event.pull_request.number, github.sha) || format('firmware-bin-{0}', github.sha) }} + name: ${{ github.event_name == 'pull_request' && format('firmware-bin-pr-{0}-{1}', github.event.pull_request.number, steps.short_sha.outputs.short) || format('firmware-bin-{0}', steps.short_sha.outputs.short) }} path: .pio/build/default/firmware.bin if-no-files-found: error @@ -102,9 +106,10 @@ jobs: const ram = `${{ steps.fw_stats.outputs.ram_line }}`.trim(); const flash = `${{ steps.fw_stats.outputs.flash_line }}`.trim(); const prNumber = context.payload.pull_request?.number; + const shortSha = `${{ steps.short_sha.outputs.short }}`.trim() || context.sha.substring(0, 7); const artifactName = prNumber - ? `firmware-bin-pr-${prNumber}-${context.sha}` - : `firmware-bin-${context.sha}`; + ? `firmware-bin-pr-${prNumber}-${shortSha}` + : `firmware-bin-${shortSha}`; let firmwareLine = 'Firmware: artifact not found'; try { const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ @@ -115,7 +120,7 @@ jobs: const firmwareArtifact = artifacts.artifacts.find((artifact) => artifact.name === artifactName); if (firmwareArtifact) { const artifactUiUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts/${firmwareArtifact.id}`; - firmwareLine = `Firmware: [firmware.bin artifact](${artifactUiUrl})`; + firmwareLine = `Firmware: [${artifactName}](${artifactUiUrl})`; } else { firmwareLine = `Firmware: artifact not found (${artifactName})`; }