From ecc5057a1b2d6b7b780c6ad5fea25a5569382330 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 29 Jan 2026 02:52:33 +0500 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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})`; } From 328d504cb00a5d8d711a33680ebda3b36972b872 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 16:46:26 +0500 Subject: [PATCH 08/27] try repository-projects: write --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20b4ec74..6480c0f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ name: CI permissions: contents: read pull-requests: write + repository-projects: write + jobs: label: From e3c799e05cb0640359289a679f7eb56125629809 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 16:51:19 +0500 Subject: [PATCH 09/27] Update .github/workflows/pr-formatting-check.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/pr-formatting-check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index fd9c4d02..7808fd71 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -37,10 +37,10 @@ jobs: `${{ 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** +**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}`; +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, From eebf9a7c9fbc73d3bc4a79c5904180d735393463 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 16:52:59 +0500 Subject: [PATCH 10/27] revert repository-projects: write --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6480c0f3..730a4455 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,6 @@ name: CI permissions: contents: read pull-requests: write - repository-projects: write jobs: From ce204d1a8fb785d28e0384bcf774291c041e68b8 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 17:00:57 +0500 Subject: [PATCH 11/27] try issues: write --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 730a4455..43e29df9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ name: CI permissions: contents: read + issues: write pull-requests: write From 7f08cc36672700ffbb2b0f9d74e30357ad889058 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 17:07:10 +0500 Subject: [PATCH 12/27] try github-script v8 --- .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 43e29df9..b7976d66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: - name: Comment PR with firmware stats and firmware.bin if: github.event_name == 'pull_request' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const marker = ''; From e8e067da5548948c821f90e5ed69cd384ddb38a4 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 17:11:27 +0500 Subject: [PATCH 13/27] try permissions write-all --- .github/workflows/ci.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7976d66..643d1dba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,13 @@ name: CI branches: [master] pull_request: -permissions: - contents: read - issues: write - pull-requests: write +# permissions: +# contents: read +# issues: write +# pull-requests: write + +permissions: + write-all jobs: From c572f4fffeafd8916a6b127a1eaaf1e7d053eff8 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 17:15:31 +0500 Subject: [PATCH 14/27] try with no permissions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 643d1dba..5aae91ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,8 @@ name: CI # issues: write # pull-requests: write -permissions: - write-all +# permissions: +# write-all jobs: From 55d2c985c1a9ecb160cc438b0ccb6867fa477d6c Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 21:18:11 +0300 Subject: [PATCH 15/27] add pr-writer --- .github/workflows/ci.yml | 96 +++++---------------- .github/workflows/pr-writer.yml | 145 ++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/pr-writer.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5aae91ed..5a8cb085 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,32 +1,22 @@ -name: CI -'on': +name: CI (build) + +on: push: branches: [master] pull_request: -# permissions: -# contents: read -# issues: write -# pull-requests: write - -# permissions: -# write-all - +permissions: + contents: read jobs: - label: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v5 - clang-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: recursive - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v5 with: python-version: '3.14' @@ -39,16 +29,18 @@ jobs: 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) + 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 + - uses: actions/checkout@v4 with: submodules: recursive - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v5 with: python-version: '3.14' @@ -61,11 +53,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: recursive - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v5 with: python-version: '3.14' @@ -95,62 +87,18 @@ jobs: if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi } >> "$GITHUB_STEP_SUMMARY" + # Upload both the binary and the stats/log so Stage 2 can read them without checkout. - 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, steps.short_sha.outputs.short) || format('firmware-bin-{0}', steps.short_sha.outputs.short) }} + name: firmware-bin 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@v8 + - name: Upload build metadata artifact + uses: actions/upload-artifact@v4 with: - script: | - const marker = ''; - 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}-${shortSha}` - : `firmware-bin-${shortSha}`; - 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) { - const artifactUiUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts/${firmwareArtifact.id}`; - firmwareLine = `Firmware: [${artifactName}](${artifactUiUrl})`; - } 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, - 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, - }); - } + name: build-meta + path: | + pio.log + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml new file mode 100644 index 00000000..fec4f42b --- /dev/null +++ b/.github/workflows/pr-writer.yml @@ -0,0 +1,145 @@ +comment_firmware: + runs-on: ubuntu-latest + steps: + - name: Find the matching CI run for this PR SHA + id: find_run + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pr = context.payload.pull_request; + const headSha = pr.head.sha; + + const { data } = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + event: "pull_request", + per_page: 50, + }); + + const run = data.workflow_runs.find(r => r.head_sha === headSha && r.name === "CI (build)"); + if (!run) core.setFailed(`No matching CI (build) run found for head_sha=${headSha}.`); + + core.setOutput("run_id", String(run.id)); + core.setOutput("run_html_url", run.html_url); + + - name: Locate artifact IDs in the CI run + id: artifacts + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const runId = Number("${{ steps.find_run.outputs.run_id }}"); + + const { data } = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + per_page: 100, + }); + + const artifacts = data.artifacts || []; + const fw = artifacts.find(a => a.name === "firmware-bin"); + const meta = artifacts.find(a => a.name === "build-meta"); + + if (!meta) core.setFailed("build-meta artifact not found in CI run artifacts."); + // firmware-bin is nice-to-have for linking; fail if you want. + core.setOutput("fw_id", fw ? String(fw.id) : ""); + core.setOutput("meta_id", String(meta.id)); + + - name: Download build-meta artifact zip and extract pio.log + id: parse_log + shell: bash + env: + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + META_ID: ${{ steps.artifacts.outputs.meta_id }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) + api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" + curl -sSL \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -o build-meta.zip \ + "${api}" + + mkdir -p build-meta + unzip -q build-meta.zip -d build-meta + + if [[ ! -f build-meta/pio.log ]]; then + echo "pio.log not found inside build-meta artifact" + echo "ram_line=" >> "$GITHUB_OUTPUT" + echo "flash_line=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + ram_line="$(grep -E "RAM:\s" -m1 build-meta/pio.log || true)" + flash_line="$(grep -E "Flash:\s" -m1 build-meta/pio.log || true)" + + echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" + echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" + + - name: Post/update PR comment with RAM/Flash + artifact links + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.payload.pull_request.number; + + const runId = Number("${{ steps.find_run.outputs.run_id }}"); + const runUrl = "${{ steps.find_run.outputs.run_html_url }}"; + + const marker = ''; + const ram = `${{ steps.parse_log.outputs.ram_line }}`.trim(); + const flash = `${{ steps.parse_log.outputs.flash_line }}`.trim(); + + const fwId = `${{ steps.artifacts.outputs.fw_id }}`.trim(); + const metaId = `${{ steps.artifacts.outputs.meta_id }}`.trim(); + + const fwUrl = fwId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${fwId}` : null; + const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; + + const body = +`${marker} +**Firmware build stats** + +\`\`\` +${ram || "RAM: not found"} +${flash || "Flash: not found"} +\`\`\` + +**Artifacts** +- CI run: ${runUrl} +- Firmware binary: ${fwUrl ? `[firmware-bin](${fwUrl})` : "artifact not found"} +- Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"} +`; + + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = comments.find(c => (c.body || "").includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body, + }); + } \ No newline at end of file From 9db4408750ca2eb40c55079ed3806b72a4d4c27b Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 21:47:22 +0300 Subject: [PATCH 16/27] update versions --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a8cb085..7195867a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,11 +12,11 @@ jobs: clang-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.14' @@ -36,11 +36,11 @@ jobs: cppcheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.14' @@ -53,11 +53,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.14' @@ -89,14 +89,14 @@ jobs: # Upload both the binary and the stats/log so Stage 2 can read them without checkout. - name: Upload firmware.bin artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: firmware-bin path: .pio/build/default/firmware.bin if-no-files-found: error - name: Upload build metadata artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: build-meta path: | From 6297189e6f19a47577ff5e820cba97a28e4564d6 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 21:56:20 +0300 Subject: [PATCH 17/27] pr writer fixes --- .github/workflows/pr-formatting-check.yml | 40 +++- .github/workflows/pr-writer.yml | 241 ++++++++++++---------- 2 files changed, 167 insertions(+), 114 deletions(-) diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index 7808fd71..8827a055 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -6,10 +6,13 @@ on: - opened - reopened - edited + - synchronize permissions: + contents: read statuses: write pull-requests: write + issues: write jobs: title-check: @@ -25,11 +28,11 @@ jobs: id: title_check uses: amannn/action-semantic-pull-request@v6 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} - name: Comment with changelog hint on failure if: failure() - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const marker = ''; @@ -46,8 +49,11 @@ If this change should appear in release notes, ensure the title reflects the cor owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, + per_page: 100, }); - const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + + const existing = comments.find((comment) => (comment.body || "").includes(marker)); + if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, @@ -63,3 +69,31 @@ If this change should appear in release notes, ensure the title reflects the cor body, }); } + + - name: Remove failure comment on success + if: success() + uses: actions/github-script@v8 + with: + script: | + const marker = ''; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + + const existing = comments.find((comment) => (comment.body || "").includes(marker)); + if (!existing) { + core.info("No previous PR title failure comment found."); + return; + } + + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + + core.info(`Deleted previous failure comment id=${existing.id}`); \ No newline at end of file diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml index fec4f42b..56c0024d 100644 --- a/.github/workflows/pr-writer.yml +++ b/.github/workflows/pr-writer.yml @@ -1,111 +1,130 @@ -comment_firmware: - runs-on: ubuntu-latest - steps: - - name: Find the matching CI run for this PR SHA - id: find_run - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const pr = context.payload.pull_request; - const headSha = pr.head.sha; +name: PR writes (label + comment) - const { data } = await github.rest.actions.listWorkflowRunsForRepo({ - owner, - repo, - event: "pull_request", - per_page: 50, - }); +on: + pull_request_target: + types: [opened, synchronize, reopened] - const run = data.workflow_runs.find(r => r.head_sha === headSha && r.name === "CI (build)"); - if (!run) core.setFailed(`No matching CI (build) run found for head_sha=${headSha}.`); +permissions: + contents: read + pull-requests: write + issues: write - core.setOutput("run_id", String(run.id)); - core.setOutput("run_html_url", run.html_url); +jobs: + label: + runs-on: ubuntu-latest + steps: + # Safe: labeler reads files from the base repo by default in pull_request_target context. + # Do NOT checkout PR head. + - uses: actions/labeler@v5 - - name: Locate artifact IDs in the CI run - id: artifacts - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const runId = Number("${{ steps.find_run.outputs.run_id }}"); + comment_firmware: + runs-on: ubuntu-latest + steps: + - name: Find the matching CI run for this PR SHA + id: find_run + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pr = context.payload.pull_request; + const headSha = pr.head.sha; - const { data } = await github.rest.actions.listWorkflowRunArtifacts({ - owner, - repo, - run_id: runId, - per_page: 100, - }); + const { data } = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + event: "pull_request", + per_page: 50, + }); - const artifacts = data.artifacts || []; - const fw = artifacts.find(a => a.name === "firmware-bin"); - const meta = artifacts.find(a => a.name === "build-meta"); + const run = data.workflow_runs.find(r => r.head_sha === headSha && r.name === "CI (build)"); + if (!run) core.setFailed(`No matching CI (build) run found for head_sha=${headSha}.`); - if (!meta) core.setFailed("build-meta artifact not found in CI run artifacts."); - // firmware-bin is nice-to-have for linking; fail if you want. - core.setOutput("fw_id", fw ? String(fw.id) : ""); - core.setOutput("meta_id", String(meta.id)); + core.setOutput("run_id", String(run.id)); + core.setOutput("run_html_url", run.html_url); - - name: Download build-meta artifact zip and extract pio.log - id: parse_log - shell: bash - env: - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - META_ID: ${{ steps.artifacts.outputs.meta_id }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail + - name: Locate artifact IDs in the CI run + id: artifacts + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const runId = Number("${{ steps.find_run.outputs.run_id }}"); - # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) - api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" - curl -sSL \ - -H "Authorization: Bearer ${GH_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - -o build-meta.zip \ - "${api}" + const { data } = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + per_page: 100, + }); - mkdir -p build-meta - unzip -q build-meta.zip -d build-meta + const artifacts = data.artifacts || []; + const fw = artifacts.find(a => a.name === "firmware-bin"); + const meta = artifacts.find(a => a.name === "build-meta"); - if [[ ! -f build-meta/pio.log ]]; then - echo "pio.log not found inside build-meta artifact" - echo "ram_line=" >> "$GITHUB_OUTPUT" - echo "flash_line=" >> "$GITHUB_OUTPUT" - exit 0 - fi + if (!meta) core.setFailed("build-meta artifact not found in CI run artifacts."); + // firmware-bin is nice-to-have for linking; fail if you want. + core.setOutput("fw_id", fw ? String(fw.id) : ""); + core.setOutput("meta_id", String(meta.id)); - ram_line="$(grep -E "RAM:\s" -m1 build-meta/pio.log || true)" - flash_line="$(grep -E "Flash:\s" -m1 build-meta/pio.log || true)" + - name: Download build-meta artifact zip and extract pio.log + id: parse_log + shell: bash + env: + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + META_ID: ${{ steps.artifacts.outputs.meta_id }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail - echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" - echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" + # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) + api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" + curl -sSL \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -o build-meta.zip \ + "${api}" - - name: Post/update PR comment with RAM/Flash + artifact links - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const prNumber = context.payload.pull_request.number; + mkdir -p build-meta + unzip -q build-meta.zip -d build-meta - const runId = Number("${{ steps.find_run.outputs.run_id }}"); - const runUrl = "${{ steps.find_run.outputs.run_html_url }}"; + if [[ ! -f build-meta/pio.log ]]; then + echo "pio.log not found inside build-meta artifact" + echo "ram_line=" >> "$GITHUB_OUTPUT" + echo "flash_line=" >> "$GITHUB_OUTPUT" + exit 0 + fi - const marker = ''; - const ram = `${{ steps.parse_log.outputs.ram_line }}`.trim(); - const flash = `${{ steps.parse_log.outputs.flash_line }}`.trim(); + ram_line="$(grep -E "RAM:\s" -m1 build-meta/pio.log || true)" + flash_line="$(grep -E "Flash:\s" -m1 build-meta/pio.log || true)" - const fwId = `${{ steps.artifacts.outputs.fw_id }}`.trim(); - const metaId = `${{ steps.artifacts.outputs.meta_id }}`.trim(); + echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" + echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" - const fwUrl = fwId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${fwId}` : null; - const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; + - name: Post/update PR comment with RAM/Flash + artifact links + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.payload.pull_request.number; - const body = + const runId = Number("${{ steps.find_run.outputs.run_id }}"); + const runUrl = "${{ steps.find_run.outputs.run_html_url }}"; + + const marker = ''; + const ram = `${{ steps.parse_log.outputs.ram_line }}`.trim(); + const flash = `${{ steps.parse_log.outputs.flash_line }}`.trim(); + + const fwId = `${{ steps.artifacts.outputs.fw_id }}`.trim(); + const metaId = `${{ steps.artifacts.outputs.meta_id }}`.trim(); + + const fwUrl = fwId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${fwId}` : null; + const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; + + const body = `${marker} **Firmware build stats** @@ -120,26 +139,26 @@ ${flash || "Flash: not found"} - Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"} `; - const { data: comments } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: prNumber, - per_page: 100, - }); + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); - const existing = comments.find(c => (c.body || "").includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body, - }); - } \ No newline at end of file + const existing = comments.find(c => (c.body || "").includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body, + }); + } \ No newline at end of file From cb9c17b80a0b988cd6b5ee266a6ac79e8be52bde Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:07:51 +0300 Subject: [PATCH 18/27] format issues --- .github/workflows/pr-formatting-check.yml | 3 +- .github/workflows/pr-writer.yml | 64 +++++++++++------------ 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index 8827a055..a02c5fd5 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -36,8 +36,7 @@ jobs: with: script: | const marker = ''; - const error = - `${{ steps.title_check.outputs.error_message || steps.title_check.outputs.error || '' }}`.trim(); + 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** diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml index 56c0024d..115c0d23 100644 --- a/.github/workflows/pr-writer.yml +++ b/.github/workflows/pr-writer.yml @@ -17,14 +17,14 @@ jobs: # Do NOT checkout PR head. - uses: actions/labeler@v5 - comment_firmware: + comment_firmware: runs-on: ubuntu-latest steps: - - name: Find the matching CI run for this PR SHA + - name: Find the matching CI run for this PR SHA id: find_run uses: actions/github-script@v8 with: - script: | + script: | const owner = context.repo.owner; const repo = context.repo.repo; const pr = context.payload.pull_request; @@ -43,11 +43,11 @@ jobs: core.setOutput("run_id", String(run.id)); core.setOutput("run_html_url", run.html_url); - - name: Locate artifact IDs in the CI run + - name: Locate artifact IDs in the CI run id: artifacts uses: actions/github-script@v8 with: - script: | + script: | const owner = context.repo.owner; const repo = context.repo.repo; const runId = Number("${{ steps.find_run.outputs.run_id }}"); @@ -68,45 +68,45 @@ jobs: core.setOutput("fw_id", fw ? String(fw.id) : ""); core.setOutput("meta_id", String(meta.id)); - - name: Download build-meta artifact zip and extract pio.log + - name: Download build-meta artifact zip and extract pio.log id: parse_log shell: bash env: - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - META_ID: ${{ steps.artifacts.outputs.meta_id }} - GH_TOKEN: ${{ github.token }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + META_ID: ${{ steps.artifacts.outputs.meta_id }} + GH_TOKEN: ${{ github.token }} run: | - set -euo pipefail + set -euo pipefail - # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) - api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" - curl -sSL \ + # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) + api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" + curl -sSL \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -o build-meta.zip \ "${api}" - mkdir -p build-meta - unzip -q build-meta.zip -d build-meta + mkdir -p build-meta + unzip -q build-meta.zip -d build-meta - if [[ ! -f build-meta/pio.log ]]; then + if [[ ! -f build-meta/pio.log ]]; then echo "pio.log not found inside build-meta artifact" echo "ram_line=" >> "$GITHUB_OUTPUT" echo "flash_line=" >> "$GITHUB_OUTPUT" exit 0 - fi + fi - ram_line="$(grep -E "RAM:\s" -m1 build-meta/pio.log || true)" - flash_line="$(grep -E "Flash:\s" -m1 build-meta/pio.log || true)" + ram_line="$(grep -E "RAM:\\s" -m1 build-meta/pio.log || true)" + flash_line="$(grep -E "Flash:\\s" -m1 build-meta/pio.log || true)" - echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" - echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" + echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" + echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" - - name: Post/update PR comment with RAM/Flash + artifact links + - name: Post/update PR comment with RAM/Flash + artifact links uses: actions/github-script@v8 with: - script: | + script: | const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = context.payload.pull_request.number; @@ -140,25 +140,25 @@ ${flash || "Flash: not found"} `; const { data: comments } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: prNumber, - per_page: 100, + owner, + repo, + issue_number: prNumber, + per_page: 100, }); const existing = comments.find(c => (c.body || "").includes(marker)); if (existing) { - await github.rest.issues.updateComment({ + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body, - }); + }); } else { - await github.rest.issues.createComment({ + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body, - }); + }); } \ No newline at end of file From f75c0c5aee6f0565e9009ddc646f42008db8a66d Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:12:48 +0300 Subject: [PATCH 19/27] format fixes --- .github/workflows/pr-formatting-check.yml | 9 ++++----- .github/workflows/pr-writer.yml | 19 ++++++------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index a02c5fd5..c2b43f32 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -38,11 +38,10 @@ jobs: 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 body = + `${marker}\n**PR title check failed**\n\n` + + `Please use a Conventional Commit-style prefix (e.g., \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`).\n` + + `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, diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml index 115c0d23..0c5d8a56 100644 --- a/.github/workflows/pr-writer.yml +++ b/.github/workflows/pr-writer.yml @@ -125,19 +125,12 @@ jobs: const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; const body = -`${marker} -**Firmware build stats** - -\`\`\` -${ram || "RAM: not found"} -${flash || "Flash: not found"} -\`\`\` - -**Artifacts** -- CI run: ${runUrl} -- Firmware binary: ${fwUrl ? `[firmware-bin](${fwUrl})` : "artifact not found"} -- Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"} -`; + `${marker}\n**Firmware build stats**\n\n` + + `\`\`\`\n${ram || "RAM: not found"}\n${flash || "Flash: not found"}\n\`\`\`\n\n` + + `**Artifacts**\n` + + `- CI run: ${runUrl}\n` + + `- Firmware binary: ${fwUrl ? `[firmware-bin](${fwUrl})` : "artifact not found"}\n` + + `- Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"}`; const { data: comments } = await github.rest.issues.listComments({ owner, From 0c050ab33eb862454610622e70b0fcab9e31cf18 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:17:07 +0300 Subject: [PATCH 20/27] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 5 +---- .github/workflows/pr-writer.yml | 9 +++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7195867a..6ba1e834 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,12 +69,9 @@ 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: | set -euo pipefail ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)" diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml index 0c5d8a56..a60565f0 100644 --- a/.github/workflows/pr-writer.yml +++ b/.github/workflows/pr-writer.yml @@ -1,4 +1,4 @@ -name: PR writes (label + comment) +name: PR writer (label + comment) on: pull_request_target: @@ -16,6 +16,8 @@ jobs: # Safe: labeler reads files from the base repo by default in pull_request_target context. # Do NOT checkout PR head. - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml comment_firmware: runs-on: ubuntu-latest @@ -63,7 +65,10 @@ jobs: const fw = artifacts.find(a => a.name === "firmware-bin"); const meta = artifacts.find(a => a.name === "build-meta"); - if (!meta) core.setFailed("build-meta artifact not found in CI run artifacts."); + if (!meta) { + core.setFailed("build-meta artifact not found in CI run artifacts."); + return; + } // firmware-bin is nice-to-have for linking; fail if you want. core.setOutput("fw_id", fw ? String(fw.id) : ""); core.setOutput("meta_id", String(meta.id)); From 88c4181cb1390565ab85e0e2179fb926d6c2f18e Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:19:21 +0300 Subject: [PATCH 21/27] Add PR writer workflow for labeling and commenting This workflow automates PR labeling and commenting with firmware build stats and artifact links. --- .github/workflows/pr-writer.yml | 157 ++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 .github/workflows/pr-writer.yml diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml new file mode 100644 index 00000000..13207655 --- /dev/null +++ b/.github/workflows/pr-writer.yml @@ -0,0 +1,157 @@ +name: PR writes (label + comment) + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + label: + runs-on: ubuntu-latest + steps: + # Safe: labeler reads files from the base repo by default in pull_request_target context. + # Do NOT checkout PR head. + - uses: actions/labeler@v5 + + comment_firmware: + runs-on: ubuntu-latest + steps: + - name: Find the matching CI run for this PR SHA + id: find_run + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pr = context.payload.pull_request; + const headSha = pr.head.sha; + + const { data } = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + event: "pull_request", + per_page: 50, + }); + + const run = data.workflow_runs.find(r => r.head_sha === headSha && r.name === "CI (build)"); + if (!run) core.setFailed(`No matching CI (build) run found for head_sha=${headSha}.`); + + core.setOutput("run_id", String(run.id)); + core.setOutput("run_html_url", run.html_url); + + - name: Locate artifact IDs in the CI run + id: artifacts + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const runId = Number("${{ steps.find_run.outputs.run_id }}"); + + const { data } = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + per_page: 100, + }); + + const artifacts = data.artifacts || []; + const fw = artifacts.find(a => a.name === "firmware-bin"); + const meta = artifacts.find(a => a.name === "build-meta"); + + if (!meta) core.setFailed("build-meta artifact not found in CI run artifacts."); + // firmware-bin is nice-to-have for linking; fail if you want. + core.setOutput("fw_id", fw ? String(fw.id) : ""); + core.setOutput("meta_id", String(meta.id)); + + - name: Download build-meta artifact zip and extract pio.log + id: parse_log + shell: bash + env: + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + META_ID: ${{ steps.artifacts.outputs.meta_id }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) + api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" + curl -sSL \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -o build-meta.zip \ + "${api}" + + mkdir -p build-meta + unzip -q build-meta.zip -d build-meta + + if [[ ! -f build-meta/pio.log ]]; then + echo "pio.log not found inside build-meta artifact" + echo "ram_line=" >> "$GITHUB_OUTPUT" + echo "flash_line=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + ram_line="$(grep -E "RAM:\\s" -m1 build-meta/pio.log || true)" + flash_line="$(grep -E "Flash:\\s" -m1 build-meta/pio.log || true)" + + echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" + echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" + + - name: Post/update PR comment with RAM/Flash + artifact links + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.payload.pull_request.number; + + const runId = Number("${{ steps.find_run.outputs.run_id }}"); + const runUrl = "${{ steps.find_run.outputs.run_html_url }}"; + + const marker = ''; + const ram = `${{ steps.parse_log.outputs.ram_line }}`.trim(); + const flash = `${{ steps.parse_log.outputs.flash_line }}`.trim(); + + const fwId = `${{ steps.artifacts.outputs.fw_id }}`.trim(); + const metaId = `${{ steps.artifacts.outputs.meta_id }}`.trim(); + + const fwUrl = fwId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${fwId}` : null; + const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; + + const body = + `${marker}\n**Firmware build stats**\n\n` + + `\`\`\`\n${ram || "RAM: not found"}\n${flash || "Flash: not found"}\n\`\`\`\n\n` + + `**Artifacts**\n` + + `- CI run: ${runUrl}\n` + + `- Firmware binary: ${fwUrl ? `[firmware-bin](${fwUrl})` : "artifact not found"}\n` + + `- Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = comments.find(c => (c.body || "").includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body, + }); + } From b97224bc065a45b110f3ee0d1c77b12161e4b0f0 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:19:41 +0300 Subject: [PATCH 22/27] Enhance PR title check workflow with synchronization --- .github/workflows/pr-formatting-check.yml | 73 ++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index 044b7b64..894416aa 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -6,9 +6,13 @@ on: - opened - reopened - edited + - synchronize permissions: + contents: read statuses: write + pull-requests: write + issues: write jobs: title-check: @@ -21,6 +25,73 @@ jobs: egress-policy: audit - name: Check PR Title + id: title_check uses: amannn/action-semantic-pull-request@v6 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} + + - name: Comment with changelog hint on failure + if: failure() + uses: actions/github-script@v8 + 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}\n**PR title check failed**\n\n` + + `Please use a Conventional Commit-style prefix (e.g., \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`).\n` + + `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, + per_page: 100, + }); + + const existing = comments.find((comment) => (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, + }); + } + + - name: Remove failure comment on success + if: success() + uses: actions/github-script@v8 + with: + script: | + const marker = ''; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + + const existing = comments.find((comment) => (comment.body || "").includes(marker)); + if (!existing) { + core.info("No previous PR title failure comment found."); + return; + } + + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + + core.info(`Deleted previous failure comment id=${existing.id}`); From 7b7337a36675e1d4a0443502a552b845b39b16d4 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:36:34 +0300 Subject: [PATCH 23/27] remove pr writer --- .github/labeler.yml | 38 ------------ .github/workflows/ci.yml | 16 +---- .github/workflows/pr-formatting-check.yml | 73 +---------------------- 3 files changed, 3 insertions(+), 124 deletions(-) delete mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index eae3f875..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,38 +0,0 @@ -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 6ba1e834..edba9651 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,10 +65,7 @@ jobs: run: pip install --upgrade platformio - name: Build CrossPoint - run: | - set -euo pipefail - pio run | tee pio.log - + run: pio run - name: Extract firmware stats @@ -84,18 +81,9 @@ jobs: if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi } >> "$GITHUB_STEP_SUMMARY" - # Upload both the binary and the stats/log so Stage 2 can read them without checkout. - name: Upload firmware.bin artifact uses: actions/upload-artifact@v6 with: - name: firmware-bin + name: firmware.bin path: .pio/build/default/firmware.bin if-no-files-found: error - - - name: Upload build metadata artifact - uses: actions/upload-artifact@v6 - with: - name: build-meta - path: | - pio.log - if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/pr-formatting-check.yml b/.github/workflows/pr-formatting-check.yml index 894416aa..044b7b64 100644 --- a/.github/workflows/pr-formatting-check.yml +++ b/.github/workflows/pr-formatting-check.yml @@ -6,13 +6,9 @@ on: - opened - reopened - edited - - synchronize permissions: - contents: read statuses: write - pull-requests: write - issues: write jobs: title-check: @@ -25,73 +21,6 @@ jobs: egress-policy: audit - name: Check PR Title - id: title_check uses: amannn/action-semantic-pull-request@v6 env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Comment with changelog hint on failure - if: failure() - uses: actions/github-script@v8 - 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}\n**PR title check failed**\n\n` + - `Please use a Conventional Commit-style prefix (e.g., \`feat:\`, \`fix:\`, \`docs:\`, \`chore:\`).\n` + - `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, - per_page: 100, - }); - - const existing = comments.find((comment) => (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, - }); - } - - - name: Remove failure comment on success - if: success() - uses: actions/github-script@v8 - with: - script: | - const marker = ''; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 100, - }); - - const existing = comments.find((comment) => (comment.body || "").includes(marker)); - if (!existing) { - core.info("No previous PR title failure comment found."); - return; - } - - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - }); - - core.info(`Deleted previous failure comment id=${existing.id}`); + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b4cd58079d668d4c46e0363ed1002e4c6e5ac8c1 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:37:48 +0300 Subject: [PATCH 24/27] remove pr writer --- .github/workflows/pr-writer.yml | 162 -------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 .github/workflows/pr-writer.yml diff --git a/.github/workflows/pr-writer.yml b/.github/workflows/pr-writer.yml deleted file mode 100644 index 34cadc8f..00000000 --- a/.github/workflows/pr-writer.yml +++ /dev/null @@ -1,162 +0,0 @@ -name: PR writer (label + comment) - -on: - pull_request_target: - types: [opened, synchronize, reopened] - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - label: - runs-on: ubuntu-latest - steps: - # Safe: labeler reads files from the base repo by default in pull_request_target context. - # Do NOT checkout PR head. - - uses: actions/labeler@v5 - with: - configuration-path: .github/labeler.yml - - comment_firmware: - runs-on: ubuntu-latest - steps: - - name: Find the matching CI run for this PR SHA - id: find_run - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const pr = context.payload.pull_request; - const headSha = pr.head.sha; - - const { data } = await github.rest.actions.listWorkflowRunsForRepo({ - owner, - repo, - event: "pull_request", - per_page: 50, - }); - - const run = data.workflow_runs.find(r => r.head_sha === headSha && r.name === "CI (build)"); - if (!run) core.setFailed(`No matching CI (build) run found for head_sha=${headSha}.`); - - core.setOutput("run_id", String(run.id)); - core.setOutput("run_html_url", run.html_url); - - - name: Locate artifact IDs in the CI run - id: artifacts - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const runId = Number("${{ steps.find_run.outputs.run_id }}"); - - const { data } = await github.rest.actions.listWorkflowRunArtifacts({ - owner, - repo, - run_id: runId, - per_page: 100, - }); - - const artifacts = data.artifacts || []; - const fw = artifacts.find(a => a.name === "firmware-bin"); - const meta = artifacts.find(a => a.name === "build-meta"); - - if (!meta) { - core.setFailed("build-meta artifact not found in CI run artifacts."); - return; - } - // firmware-bin is nice-to-have for linking; fail if you want. - core.setOutput("fw_id", fw ? String(fw.id) : ""); - core.setOutput("meta_id", String(meta.id)); - - - name: Download build-meta artifact zip and extract pio.log - id: parse_log - shell: bash - env: - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - META_ID: ${{ steps.artifacts.outputs.meta_id }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - # Download artifact zip via GitHub API (will redirect to blob storage; -L follows) - api="https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${META_ID}/zip" - curl -sSL \ - -H "Authorization: Bearer ${GH_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - -o build-meta.zip \ - "${api}" - - mkdir -p build-meta - unzip -q build-meta.zip -d build-meta - - if [[ ! -f build-meta/pio.log ]]; then - echo "pio.log not found inside build-meta artifact" - echo "ram_line=" >> "$GITHUB_OUTPUT" - echo "flash_line=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - ram_line="$(grep -E "RAM:\\s" -m1 build-meta/pio.log || true)" - flash_line="$(grep -E "Flash:\\s" -m1 build-meta/pio.log || true)" - - echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT" - echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" - - - name: Post/update PR comment with RAM/Flash + artifact links - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const prNumber = context.payload.pull_request.number; - - const runId = Number("${{ steps.find_run.outputs.run_id }}"); - const runUrl = "${{ steps.find_run.outputs.run_html_url }}"; - - const marker = ''; - const ram = `${{ steps.parse_log.outputs.ram_line }}`.trim(); - const flash = `${{ steps.parse_log.outputs.flash_line }}`.trim(); - - const fwId = `${{ steps.artifacts.outputs.fw_id }}`.trim(); - const metaId = `${{ steps.artifacts.outputs.meta_id }}`.trim(); - - const fwUrl = fwId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${fwId}` : null; - const metaUrl = metaId ? `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${metaId}` : null; - - const body = - `${marker}\n**Firmware build stats**\n\n` + - `\`\`\`\n${ram || "RAM: not found"}\n${flash || "Flash: not found"}\n\`\`\`\n\n` + - `**Artifacts**\n` + - `- CI run: ${runUrl}\n` + - `- Firmware binary: ${fwUrl ? `[firmware-bin](${fwUrl})` : "artifact not found"}\n` + - `- Build logs: ${metaUrl ? `[build-meta](${metaUrl})` : "artifact not found"}`; - - const { data: comments } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: prNumber, - per_page: 100, - }); - - const existing = comments.find(c => (c.body || "").includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body, - }); - } From f5dace7bed5bea00e31434d6d3b12417354ec56a Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:42:32 +0300 Subject: [PATCH 25/27] return pio.log --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edba9651..262a19f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,9 @@ jobs: run: pip install --upgrade platformio - name: Build CrossPoint - run: pio run + run: | + set -euo pipefail + pio run | tee pio.log - name: Extract firmware stats From b213893644e867f8bbfb0e8712241d08ccb033b9 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:46:56 +0300 Subject: [PATCH 26/27] stats as code --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 262a19f8..e60f6105 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,8 +79,10 @@ jobs: 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 + echo "```text" + 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 + echo "```" } >> "$GITHUB_STEP_SUMMARY" - name: Upload firmware.bin artifact From 45c385098363ff53659b6567cdee2bc56f1fa7d4 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Sun, 1 Feb 2026 22:54:36 +0300 Subject: [PATCH 27/27] revert to old style --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e60f6105..262a19f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,10 +79,8 @@ jobs: echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT" { echo "## Firmware build stats" - echo "```text" - 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 - echo "```" + 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: Upload firmware.bin artifact