Compare commits

...
Author SHA1 Message Date
Ludy 2447fce497 Merge branch 'main' into move_pr_check 2026-08-23 15:21:17 +02:00
Ludy87 ca8464b6d7 Update pr-validation-comments.yml 2026-08-23 13:55:43 +02:00
Ludy 1d39ad6e99 Merge branch 'main' into move_pr_check 2026-08-23 13:21:01 +02:00
Ludy87 f8bd4cd0b0 Consolidate PR validation comments workflow
This change replaces the old PR reporter workflow with a new pr-validation-comments workflow and updates the validation jobs to reference it. The new workflow centralizes PR comment handling for build and license checks using workflow_run, so forked PRs can still receive validation feedback without granting write access to code-executing jobs.
2026-08-21 10:49:45 +02:00
Ludy 13958d3b30 Merge branch 'main' into move_pr_check 2026-08-18 20:27:47 +02:00
Ludy c5395ff5e7 Merge branch 'main' into move_pr_check 2026-08-18 09:22:16 +02:00
Ludy 7835bffe3c Merge branch 'main' into move_pr_check 2026-08-17 08:22:44 +02:00
Ludy 87cde59851 Merge branch 'main' into move_pr_check 2026-08-16 15:32:24 +02:00
Ludy87 5e999f68e7 Update check_toml.yml 2026-08-16 15:30:03 +02:00
Ludy87 1de354606b Add TOML translation check workflow
Validate TOML translation files on PRs and surface results in PR checks.

Changes:
- .github/config/.files.yaml: add a toml files group for change detection.
- .github/workflows/build.yml: wire toml into changed-files detection and add a toml-check job to run the reusable workflow.
- .github/workflows/check_toml.yml: convert to a reusable workflow_call, check frontend/editor locales path, checkout PR base SHA, and upload a result artifact instead of posting comments.
- .github/workflows/report-pr-check.yml: download the toml artifact and add a toml-translation marker so PR comment updater can include pass/fail details.

Reason: ensure TOML translation files stay consistent with the reference and present clear PR feedback.
2026-08-16 14:23:27 +02:00
Ludy87 29adabbabe Update report-pr-check.yml 2026-08-16 14:01:07 +02:00
Ludy87 c08d2b4de4 Consolidate PR check reporting workflow
Replace the old report-pr-check-failures.yml with a new centralized report-pr-check.yml and update workflows to delegate PR commenting to it. Updated ai-engine.yml, backend-build.yml, check-generated-models.yml, frontend-validation.yml and frontend-backend-licenses-update.yml to reference the new reporter. The new workflow runs on workflow_run (Build and Test + License Report), downloads license artifacts, aggregates job failures, and posts or updates actionable PR comments (including support for fork PRs). The license workflow now uploads warning artifacts and defers all comment logic to the reporter.
2026-08-16 13:51:51 +02:00
Ludy87 00528ed4cf Move PR check comments to dedicated workflow
Remove in-workflow PR comment creation/deletion and drop pull-requests write perms from several reusable workflows (ai-engine, backend-build, build, check-generated-models, frontend-validation). Add a new report-pr-check-failures.yml workflow that runs on workflow_run with limited write permissions to create/update/delete PR comments for failed checks. This centralises comment handling, follows least-privilege for forks, and avoids duplicate github-script blocks across workflows.
2026-08-16 12:51:17 +02:00
9 changed files with 493 additions and 420 deletions
+6
View File
@@ -105,6 +105,12 @@ frontend: &frontend
- .github/workflows/e2e-stubbed.yml
- .github/workflows/e2e-live.yml
toml: &toml
- *ci
- frontend/editor/public/locales/*/translation.toml
- .github/scripts/check_language_toml.py
- .github/workflows/check_toml.yml
# Files that affect the Tauri desktop bundle. Changes to any of these files
# trigger the multi-OS Tauri build job.
tauri: &tauri
+4 -51
View File
@@ -17,7 +17,6 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
@@ -49,39 +48,8 @@ jobs:
# issue/PR number, which doesn't exist on merge_group runs.
if: steps.engine-check.outcome == 'failure' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- engine-check -->';
const body = [
marker,
'### Engine Check Failed',
'',
'There are issues with your Python code that will need to be fixed before they can be merged in.',
'',
'Run `task engine:fix` to auto-fix what can be fixed automatically, then run `task engine:check` to see what still needs fixing manually.',
].join('\n');
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(c => c.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,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail if engine check failed
if: steps.engine-check.outcome == 'failure'
@@ -110,20 +78,5 @@ jobs:
- name: Remove engine check comment on success
if: steps.engine-check.outcome == 'success' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- engine-check -->';
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(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
+4 -51
View File
@@ -16,7 +16,6 @@ permissions:
contents: read
actions: read
security-events: write
pull-requests: write
jobs:
build:
@@ -71,39 +70,8 @@ jobs:
# issue/PR number, which doesn't exist on merge_group runs.
if: steps.spotless-check.outcome == 'failure' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- java-formatting-check -->';
const body = [
marker,
'### Backend Format Check Failed',
'',
'There are formatting issues in your Java code that will need to be fixed before they can be merged in.',
'',
'Run `task backend:format` to auto-fix, then commit and push the changes.',
].join('\n');
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(c => c.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,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail if backend format check failed
if: steps.spotless-check.outcome == 'failure'
@@ -124,23 +92,8 @@ jobs:
- name: Remove backend format check comment on success
if: steps.spotless-check.outcome == 'success' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- java-formatting-check -->';
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(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Build with Gradle (flavor=${{ matrix.flavor }})
# STIRLING_FLAVOR is read by settings.gradle and expands into the
+12 -4
View File
@@ -41,6 +41,7 @@ jobs:
project: ${{ steps.changes.outputs.project }}
openapi: ${{ steps.changes.outputs.openapi }}
frontend: ${{ steps.changes.outputs.frontend }}
toml: ${{ steps.changes.outputs.toml }}
docker-base: ${{ steps.changes.outputs.docker-base }}
dockerfiles: ${{ steps.changes.outputs.dockerfiles }}
tauri: ${{ steps.changes.outputs.tauri }}
@@ -73,7 +74,6 @@ jobs:
actions: read
contents: read
security-events: write
pull-requests: write
uses: ./.github/workflows/backend-build.yml
secrets: inherit
@@ -102,10 +102,18 @@ jobs:
needs: [files-changed]
permissions:
contents: read
pull-requests: write
uses: ./.github/workflows/frontend-validation.yml
secrets: inherit
toml-check:
name: TOML translation check
if: needs.files-changed.outputs.toml == 'true'
needs: [files-changed]
permissions:
contents: read
uses: ./.github/workflows/check_toml.yml
secrets: inherit
# Required (in all-checks-passed). Scans the stories a branch touches in both
# light and dark; an axe violation in either theme blocks the merge. The
# whole-suite sweep (nightly.yml) still covers stories a change affects without
@@ -213,7 +221,6 @@ jobs:
needs: [files-changed]
permissions:
contents: read
pull-requests: write
uses: ./.github/workflows/ai-engine.yml
secrets: inherit
@@ -227,7 +234,6 @@ jobs:
needs: [files-changed, gradle-cache-prime]
permissions:
contents: read
pull-requests: write
uses: ./.github/workflows/check-generated-models.yml
secrets: inherit
with:
@@ -283,6 +289,7 @@ jobs:
- db-migration-test
- check-generateOpenApiDocs
- frontend-validation
- toml-check
- frontend-a11y
- playwright-e2e
- playwright-e2e-live
@@ -311,6 +318,7 @@ jobs:
db-migration-test=${{ needs.db-migration-test.result }}
check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }}
frontend-validation=${{ needs.frontend-validation.result }}
toml-check=${{ needs.toml-check.result }}
frontend-a11y=${{ needs.frontend-a11y.result }}
playwright-e2e=${{ needs.playwright-e2e.result }}
playwright-e2e-live=${{ needs.playwright-e2e-live.result }}
+4 -51
View File
@@ -25,7 +25,6 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
@@ -88,39 +87,8 @@ jobs:
# issue/PR number, which doesn't exist on merge_group runs.
if: steps.models-check.outcome == 'failure' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- generated-models-check -->';
const body = [
marker,
'### Generated Models Check Failed',
'',
'One or more generated files are out of date with the Java OpenAPI spec and will need to be regenerated before they can be merged in.',
'',
'Run `task tool-models` to regenerate them, then commit the updated files.',
].join('\n');
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(c => c.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,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail if generated models check failed
if: steps.models-check.outcome == 'failure'
@@ -140,20 +108,5 @@ jobs:
- name: Remove generated models check comment on success
if: steps.models-check.outcome == 'success' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- generated-models-check -->';
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(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
+14 -59
View File
@@ -1,39 +1,30 @@
name: Check TOML Translation Files on PR
# This workflow validates TOML translation files
# This reusable workflow validates TOML translation files.
# It is called by the pull_request workflow and deliberately has no write
# permissions. PR comments are handled by pr-validation-comments.yml via workflow_run.
on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- "frontend/editor/public/locales/*/translation.toml"
- ".github/scripts/check_language_toml.py"
- ".github/workflows/check_toml.yml"
# cancel in-progress jobs if a new job is triggered
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true
workflow_call:
permissions:
contents: read # Allow read access to repository content
jobs:
check-files:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read # Checkout, and read translation files via the contents API
issues: write # Allow posting comments on issues/PRs
pull-requests: write # Allow writing to pull requests
contents: read # Read the base repository and PR files via the API
steps:
- name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout main branch first
- name: Checkout PR base first
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: Get PR data
id: get-pr-data
@@ -242,49 +233,13 @@ jobs:
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi
- name: Post comment on PR
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- name: Upload TOML check result
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
github-token: ${{ github.token }}
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const issueNumber = context.issue.number;
// Find existing comment
const comments = await github.rest.issues.listComments({
owner: repoOwner,
repo: repoName,
issue_number: issueNumber
});
const comment = comments.data.find(c => c.body.includes("## 🌐 TOML Translation Verification Summary"));
// Only update or create comments by the action user
const expectedActor = "github-actions[bot]";
if (comment && comment.user.login === expectedActor) {
// Update existing comment
await github.rest.issues.updateComment({
owner: repoOwner,
repo: repoName,
comment_id: comment.id,
body: `## 🌐 TOML Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
});
console.log("Updated existing comment.");
} else if (!comment) {
// Create new comment if no existing comment is found
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: issueNumber,
body: `## 🌐 TOML Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
});
console.log("Created new comment.");
} else {
console.log("Comment update attempt denied. Actor does not match.");
}
name: toml-translation-check-result
path: result.txt
if-no-files-found: ignore
- name: Fail job if errors found
if: env.FAIL_JOB == 'true'
@@ -147,39 +147,19 @@ jobs:
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
fi
- name: Upload frontend license warnings
if: env.LICENSE_WARNINGS_EXIST == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend-license-warnings
path: frontend/editor/src/assets/license-warnings.json
# PR Event: Check licenses and comment on PR
- name: Delete previous license check comments
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) && github.actor != 'dependabot[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = context.issue.number;
// Get all comments on the PR
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100
});
// Filter for license check comments
const licenseComments = comments.filter(comment =>
comment.body.includes('## ✅ Frontend License Check Passed') ||
comment.body.includes('## ❌ Frontend License Check Failed')
);
// Delete old license check comments
for (const comment of licenseComments) {
console.log(`Deleting old license check comment: ${comment.id}`);
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: comment.id
});
}
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Summarize results (fork PRs)
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) || github.actor == 'dependabot[bot]'
@@ -202,51 +182,10 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
- name: Comment on PR - License Check Results
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) && github.actor != 'dependabot[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = context.issue.number;
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
let commentBody;
if (hasWarnings) {
// Read warnings file to get specific issues
const fs = require('fs');
let warningDetails = '';
try {
const warnings = JSON.parse(fs.readFileSync('frontend/editor/src/assets/license-warnings.json', 'utf8'));
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
} catch (e) {
warningDetails = 'Unable to read warning details';
}
commentBody = `## ❌ Frontend License Check Failed
The frontend license check has detected compatibility warnings that require review:
${warningDetails}
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
_This check will fail the PR until license issues are resolved._`;
} else {
commentBody = `## ✅ Frontend License Check Passed
All frontend licenses have been validated and no compatibility warnings were detected.
The frontend license report has been updated successfully.`;
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail workflow if license warnings exist (PR only)
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
@@ -403,84 +342,16 @@ jobs:
cp build/reports/dependency-license/index.json app/core/src/main/resources/static/3rdPartyLicenses.json
- name: Delete previous backend license check comments
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) && github.actor != 'dependabot[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = context.issue.number;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100
});
const backendLicenseComments = comments.filter(comment =>
comment.body.includes('## ✅ Backend License Check Passed') ||
comment.body.includes('## ❌ Backend License Check Failed')
);
for (const comment of backendLicenseComments) {
console.log(`Deleting old backend license comment: ${comment.id}`);
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: comment.id
});
}
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Comment on PR - Backend License Check Results
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) && github.actor != 'dependabot[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
const fs = require('fs');
let warningDetails = '';
if (hasWarnings) {
try {
const warningsFile = 'build/reports/dependency-license/dependencies-without-allowed-license.json';
if (fs.existsSync(warningsFile)) {
const data = JSON.parse(fs.readFileSync(warningsFile, 'utf8'));
if (data.length > 0) {
warningDetails = data.map(dep => `- **${dep.moduleName}@${dep.moduleVersion}** ${dep.moduleLicenses.map(l => l.licenseName).join(', ')}`).join('\n');
}
}
} catch (e) {
warningDetails = 'Unable to parse warning details.';
}
}
let commentBody;
if (hasWarnings) {
commentBody = `## ❌ Backend License Check Failed
The backend license check has detected dependencies with incompatible or unallowed licenses:
${warningDetails || 'See uploaded artifact for details.'}
**Action Required:** Please review these licenses and resolve before merging.
_This check will fail the PR until license issues are resolved._`;
} else {
commentBody = `## ✅ Backend License Check Passed
All backend dependencies have valid and allowed licenses.
The backend license report has been updated successfully.`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail workflow if license warnings exist (PR only)
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
+5 -52
View File
@@ -1,14 +1,13 @@
name: Frontend lint, type-check, and build
# Reusable workflow called from build.yml when frontend / testing sources
# change. Runs `task frontend:check:all` and uploads the
# change. Runs `task frontend:check:all` and uploads the
# coverage + dist artifacts for downstream jobs.
on:
workflow_call:
permissions:
contents: read
pull-requests: write
jobs:
frontend-validation:
@@ -37,39 +36,8 @@ jobs:
# issue/PR number, which doesn't exist on merge_group runs.
if: steps.frontend-check.outcome == 'failure' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- frontend-check -->';
const body = [
marker,
'### Frontend Check Failed',
'',
'There are issues with your frontend code that will need to be fixed before they can be merged in.',
'',
'Run `task frontend:fix` to auto-fix what can be fixed automatically, then run `task frontend:check:all` to see what still needs fixing manually.',
].join('\n');
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(c => c.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,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Fail if frontend check failed
if: steps.frontend-check.outcome == 'failure'
run: |
@@ -88,23 +56,8 @@ jobs:
- name: Remove frontend check comment on success
if: steps.frontend-check.outcome == 'success' && github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const marker = '<!-- frontend-check -->';
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(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
});
}
run: |
echo "Comments are handled by pr-validation-comments.yml"
- name: Install uv
if: always()
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
@@ -0,0 +1,421 @@
name: PR validation comments
# Reports validation results from the default branch. It never checks out or
# executes code from the pull request.
on:
workflow_run:
workflows:
- "Build and Test Workflow"
- "License Report Workflow"
types: [completed]
permissions:
contents: read
actions: read
jobs:
build-validation-comments:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.name == 'Build and Test Workflow'
name: Build validation comment (${{ matrix.label }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: frontend
workflow: Build and Test Workflow
failed_step: Fail if frontend check failed
marker: <!-- frontend-check -->
title: Frontend Check Failed
type: standard
- label: engine
workflow: Build and Test Workflow
failed_step: Fail if engine check failed
marker: <!-- engine-check -->
title: Engine Check Failed
type: standard
- label: backend-format
workflow: Build and Test Workflow
failed_step: Fail if backend format check failed
marker: <!-- java-formatting-check -->
title: Backend Format Check Failed
type: standard
- label: generated-models
workflow: Build and Test Workflow
failed_step: Fail if generated models check failed
marker: <!-- generated-models-check -->
title: Generated Models Check Failed
type: standard
- label: toml
workflow: Build and Test Workflow
failed_step: Fail job if errors found
marker: <!-- toml-translation-check -->
title: TOML Translation Check Failed
type: toml
permissions:
actions: read
contents: read
issues: write
pull-requests: write
steps:
- name: Download frontend license warnings
if: matrix.type == 'frontend-license'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: frontend-license-warnings
path: license-artifacts/frontend
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Download backend license warnings
if: matrix.type == 'backend-license'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: backend-dependencies-without-allowed-license.json
path: license-artifacts/backend
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Download TOML translation result
if: matrix.type == 'toml'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: toml-translation-check-result
path: toml-artifacts
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Update ${{ matrix.label }} comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108d7 # v9.0.0
env:
CHECK_MARKER: ${{ matrix.marker }}
FAILED_STEP: ${{ matrix.failed_step }}
CHECK_TITLE: ${{ matrix.title }}
CHECK_TYPE: ${{ matrix.type }}
CHECK_JOB_NAME: ${{ matrix.job_name }}
with:
script: |
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
let pullRequest = run.pull_requests?.[0];
if (!pullRequest) {
const { data: associatedPullRequests } =
await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: run.head_sha,
});
pullRequest = associatedPullRequests[0];
}
if (!pullRequest) {
core.warning('No pull request found for this workflow run.');
return;
}
const jobs = await github.paginate(
github.rest.actions.listJobsForWorkflowRun,
{ owner, repo, run_id: run.id, per_page: 100 },
);
const failed = jobs.some(job =>
(!process.env.CHECK_JOB_NAME || job.name === process.env.CHECK_JOB_NAME) &&
job.steps?.some(step =>
step.name === process.env.FAILED_STEP &&
step.conclusion === 'failure'));
const comments = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number: pullRequest.number, per_page: 100 },
);
const existing = comments.find(comment =>
comment.body?.includes(process.env.CHECK_MARKER));
const standardInstructions = {
'<!-- frontend-check -->':
"There are issues with your frontend code that will need to be fixed before they can be merged in.\n\nRun 'task frontend:fix' to auto-fix what can be fixed automatically, then run 'task frontend:check:all' to see what still needs fixing manually.",
'<!-- engine-check -->':
"There are issues with your Python code that will need to be fixed before they can be merged in.\n\nRun 'task engine:fix' to auto-fix what can be fixed automatically, then run 'task engine:check' to see what still needs fixing manually.",
'<!-- java-formatting-check -->':
"There are formatting issues in your Java code that will need to be fixed before they can be merged in.\n\nRun 'task backend:format' to auto-fix, then commit and push the changes.",
'<!-- generated-models-check -->':
"One or more generated files are out of date with the Java OpenAPI spec and will need to be regenerated before merging.\n\nRun 'task tool-models' to regenerate them, then commit the updated files.",
};
let body;
if (process.env.CHECK_TYPE === 'standard') {
body = [
process.env.CHECK_MARKER,
'## ' + (failed ? '❌ ' : '✅ ') + process.env.CHECK_TITLE,
'',
standardInstructions[process.env.CHECK_MARKER],
].join('\n');
} else if (process.env.CHECK_TYPE === 'toml') {
let details = 'See the TOML translation check logs for details.';
try {
details = require('fs').readFileSync('toml-artifacts/result.txt', 'utf8');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ TOML Translation Check Failed\n\n' +
details + '\n\nPlease correct the translation files and push the changes.'
: process.env.CHECK_MARKER +
'\n\n## ✅ TOML Translation Check Passed\n\nAll changed TOML translation files are consistent with the reference file.';
} else if (process.env.CHECK_TYPE === 'frontend-license') {
let warningDetails = 'Unable to read warning details.';
try {
const data = JSON.parse(require('fs').readFileSync(
'license-artifacts/frontend/license-warnings.json',
'utf8',
));
warningDetails = data.warnings.map(w => '- ' + w.message).join('\n');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ Frontend License Check Failed\n\nThe frontend license check detected compatibility warnings that require review:\n\n' +
warningDetails + '\n\n**Action Required:** Please review these licenses before merging.'
: process.env.CHECK_MARKER +
'\n\n## ✅ Frontend License Check Passed\n\nAll frontend licenses have been validated and no compatibility warnings were detected.';
} else {
let warningDetails = '';
try {
const data = JSON.parse(require('fs').readFileSync(
'license-artifacts/backend/dependencies-without-allowed-license.json',
'utf8',
));
warningDetails = data.map(dep =>
'- **' + dep.moduleName + '@' + dep.moduleVersion +
'** ' + dep.moduleLicenses.map(l => l.licenseName).join(', '),
).join('\n');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ Backend License Check Failed\n\nThe backend license check detected dependencies with incompatible or unallowed licenses:\n\n' +
(warningDetails || 'See uploaded artifact for details.') +
'\n\n**Action Required:** Please review these licenses before merging.'
: process.env.CHECK_MARKER +
'\n\n## ✅ Backend License Check Passed\n\nAll backend dependencies have valid and allowed licenses.';
}
if (failed && existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else if (failed) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequest.number,
body,
});
} else if (existing && process.env.CHECK_TYPE !== 'standard') {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else if (existing) {
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: existing.id,
});
}
license-validation-comments:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.name == 'License Report Workflow'
name: License validation comment (${{ matrix.label }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: frontend-license
workflow: License Report Workflow
job_name: Generate Frontend License Report
failed_step: Fail workflow if license warnings exist (PR only)
marker: <!-- frontend-license-check -->
title: Frontend License Check
type: frontend-license
- label: backend-license
workflow: License Report Workflow
job_name: Generate Backend License Report
failed_step: Fail workflow if license warnings exist (PR only)
marker: <!-- backend-license-check -->
title: Backend License Check
type: backend-license
permissions:
actions: read
contents: read
issues: write
pull-requests: write
steps:
- name: Download frontend license warnings
if: matrix.type == 'frontend-license'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: frontend-license-warnings
path: license-artifacts/frontend
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Download backend license warnings
if: matrix.type == 'backend-license'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: backend-dependencies-without-allowed-license.json
path: license-artifacts/backend
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Download TOML translation result
if: matrix.type == 'toml'
continue-on-error: true
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: toml-translation-check-result
path: toml-artifacts
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Update ${{ matrix.label }} comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108d7 # v9.0.0
env:
CHECK_MARKER: ${{ matrix.marker }}
FAILED_STEP: ${{ matrix.failed_step }}
CHECK_TITLE: ${{ matrix.title }}
CHECK_TYPE: ${{ matrix.type }}
CHECK_JOB_NAME: ${{ matrix.job_name }}
with:
script: |
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
let pullRequest = run.pull_requests?.[0];
if (!pullRequest) {
const { data: associatedPullRequests } =
await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: run.head_sha,
});
pullRequest = associatedPullRequests[0];
}
if (!pullRequest) {
core.warning('No pull request found for this workflow run.');
return;
}
const jobs = await github.paginate(
github.rest.actions.listJobsForWorkflowRun,
{ owner, repo, run_id: run.id, per_page: 100 },
);
const failed = jobs.some(job =>
(!process.env.CHECK_JOB_NAME || job.name === process.env.CHECK_JOB_NAME) &&
job.steps?.some(step =>
step.name === process.env.FAILED_STEP &&
step.conclusion === 'failure'));
const comments = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number: pullRequest.number, per_page: 100 },
);
const existing = comments.find(comment =>
comment.body?.includes(process.env.CHECK_MARKER));
const standardInstructions = {
'<!-- frontend-check -->':
"There are issues with your frontend code that will need to be fixed before they can be merged in.\n\nRun 'task frontend:fix' to auto-fix what can be fixed automatically, then run 'task frontend:check:all' to see what still needs fixing manually.",
'<!-- engine-check -->':
"There are issues with your Python code that will need to be fixed before they can be merged in.\n\nRun 'task engine:fix' to auto-fix what can be fixed automatically, then run 'task engine:check' to see what still needs fixing manually.",
'<!-- java-formatting-check -->':
"There are formatting issues in your Java code that will need to be fixed before they can be merged in.\n\nRun 'task backend:format' to auto-fix, then commit and push the changes.",
'<!-- generated-models-check -->':
"One or more generated files are out of date with the Java OpenAPI spec and will need to be regenerated before merging.\n\nRun 'task tool-models' to regenerate them, then commit the updated files.",
};
let body;
if (process.env.CHECK_TYPE === 'standard') {
body = [
process.env.CHECK_MARKER,
'## ' + (failed ? '❌ ' : '✅ ') + process.env.CHECK_TITLE,
'',
standardInstructions[process.env.CHECK_MARKER],
].join('\n');
} else if (process.env.CHECK_TYPE === 'toml') {
let details = 'See the TOML translation check logs for details.';
try {
details = require('fs').readFileSync('toml-artifacts/result.txt', 'utf8');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ TOML Translation Check Failed\n\n' +
details + '\n\nPlease correct the translation files and push the changes.'
: process.env.CHECK_MARKER +
'\n\n## ✅ TOML Translation Check Passed\n\nAll changed TOML translation files are consistent with the reference file.';
} else if (process.env.CHECK_TYPE === 'frontend-license') {
let warningDetails = 'Unable to read warning details.';
try {
const data = JSON.parse(require('fs').readFileSync(
'license-artifacts/frontend/license-warnings.json',
'utf8',
));
warningDetails = data.warnings.map(w => '- ' + w.message).join('\n');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ Frontend License Check Failed\n\nThe frontend license check detected compatibility warnings that require review:\n\n' +
warningDetails + '\n\n**Action Required:** Please review these licenses before merging.'
: process.env.CHECK_MARKER +
'\n\n## ✅ Frontend License Check Passed\n\nAll frontend licenses have been validated and no compatibility warnings were detected.';
} else {
let warningDetails = '';
try {
const data = JSON.parse(require('fs').readFileSync(
'license-artifacts/backend/dependencies-without-allowed-license.json',
'utf8',
));
warningDetails = data.map(dep =>
'- **' + dep.moduleName + '@' + dep.moduleVersion +
'** ' + dep.moduleLicenses.map(l => l.licenseName).join(', '),
).join('\n');
} catch (_) {}
body = failed
? process.env.CHECK_MARKER + '\n\n## ❌ Backend License Check Failed\n\nThe backend license check detected dependencies with incompatible or unallowed licenses:\n\n' +
(warningDetails || 'See uploaded artifact for details.') +
'\n\n**Action Required:** Please review these licenses before merging.'
: process.env.CHECK_MARKER +
'\n\n## ✅ Backend License Check Passed\n\nAll backend dependencies have valid and allowed licenses.';
}
if (failed && existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else if (failed) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequest.number,
body,
});
} else if (existing && process.env.CHECK_TYPE !== 'standard') {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else if (existing) {
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: existing.id,
});
}