diff --git a/.github/workflows/ai-engine.yml b/.github/workflows/ai-engine.yml index f9e3e2d3b4..3a6ff6701a 100644 --- a/.github/workflows/ai-engine.yml +++ b/.github/workflows/ai-engine.yml @@ -49,7 +49,7 @@ jobs: if: steps.engine-check.outcome == 'failure' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Fail if engine check failed if: steps.engine-check.outcome == 'failure' @@ -79,4 +79,4 @@ jobs: if: steps.engine-check.outcome == 'success' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" diff --git a/.github/workflows/backend-build.yml b/.github/workflows/backend-build.yml index fb3eedc842..e8ec07d1fa 100644 --- a/.github/workflows/backend-build.yml +++ b/.github/workflows/backend-build.yml @@ -69,7 +69,7 @@ jobs: if: steps.spotless-check.outcome == 'failure' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Fail if backend format check failed if: steps.spotless-check.outcome == 'failure' @@ -91,7 +91,7 @@ jobs: if: steps.spotless-check.outcome == 'success' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + 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 diff --git a/.github/workflows/check-generated-models.yml b/.github/workflows/check-generated-models.yml index 30613ea88f..4c071e3592 100644 --- a/.github/workflows/check-generated-models.yml +++ b/.github/workflows/check-generated-models.yml @@ -88,7 +88,7 @@ jobs: if: steps.models-check.outcome == 'failure' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Fail if generated models check failed if: steps.models-check.outcome == 'failure' @@ -109,4 +109,4 @@ jobs: if: steps.models-check.outcome == 'success' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" diff --git a/.github/workflows/check_toml.yml b/.github/workflows/check_toml.yml index 29dfd64114..2dcf743ccf 100644 --- a/.github/workflows/check_toml.yml +++ b/.github/workflows/check_toml.yml @@ -2,7 +2,7 @@ name: Check TOML Translation Files on PR # 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 report-pr-check.yml via workflow_run. +# permissions. PR comments are handled by pr-validation-comments.yml via workflow_run. on: workflow_call: diff --git a/.github/workflows/frontend-backend-licenses-update.yml b/.github/workflows/frontend-backend-licenses-update.yml index 27f4015646..3acb8f92a9 100644 --- a/.github/workflows/frontend-backend-licenses-update.yml +++ b/.github/workflows/frontend-backend-licenses-update.yml @@ -157,7 +157,7 @@ jobs: if: github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + 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]' @@ -183,7 +183,7 @@ jobs: if: github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + 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' @@ -341,13 +341,13 @@ jobs: if: github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Comment on PR - Backend License Check Results if: github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + 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' diff --git a/.github/workflows/frontend-validation.yml b/.github/workflows/frontend-validation.yml index 7430ca24e9..7b26e83bbc 100644 --- a/.github/workflows/frontend-validation.yml +++ b/.github/workflows/frontend-validation.yml @@ -37,7 +37,7 @@ jobs: if: steps.frontend-check.outcome == 'failure' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Fail if frontend check failed if: steps.frontend-check.outcome == 'failure' run: | @@ -57,7 +57,7 @@ jobs: if: steps.frontend-check.outcome == 'success' && github.event_name == 'pull_request' continue-on-error: true run: | - echo "Comments are handled by report-pr-check.yml" + echo "Comments are handled by pr-validation-comments.yml" - name: Install uv if: always() uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 diff --git a/.github/workflows/pr-validation-comments.yml b/.github/workflows/pr-validation-comments.yml new file mode 100644 index 0000000000..3e165ae329 --- /dev/null +++ b/.github/workflows/pr-validation-comments.yml @@ -0,0 +1,424 @@ +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 == matrix.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: + title: Frontend Check Failed + type: standard + - label: engine + workflow: Build and Test Workflow + failed_step: Fail if engine check failed + marker: + title: Engine Check Failed + type: standard + - label: backend-format + workflow: Build and Test Workflow + failed_step: Fail if backend format check failed + marker: + title: Backend Format Check Failed + type: standard + - label: generated-models + workflow: Build and Test Workflow + failed_step: Fail if generated models check failed + marker: + title: Generated Models Check Failed + type: standard + - label: toml + workflow: Build and Test Workflow + failed_step: Fail job if errors found + marker: + 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 = { + '': + "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.", + '': + "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.", + '': + "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.", + '': + "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 == matrix.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: + 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: + 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 = { + '': + "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.", + '': + "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.", + '': + "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.", + '': + "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, + }); + } + + + diff --git a/.github/workflows/report-pr-check.yml b/.github/workflows/report-pr-check.yml deleted file mode 100644 index 47bea0812a..0000000000 --- a/.github/workflows/report-pr-check.yml +++ /dev/null @@ -1,209 +0,0 @@ -name: PR Check Reporter - -# Reports failed PR checks back to the pull request with actionable comments. -# Runs separately so fork PRs can receive comments without granting write -# access to workflows that execute pull request code. -on: - workflow_run: - workflows: - - "Build and Test Workflow" - - "License Report Workflow" - types: [completed] - -permissions: - contents: read - actions: read - -jobs: - report: - if: github.event.workflow_run.event == 'pull_request' - permissions: - contents: read - actions: read - issues: write - pull-requests: write - runs-on: ubuntu-latest - steps: - - name: Download frontend license warnings - if: github.event.workflow_run.name == 'License Report Workflow' - 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: github.event.workflow_run.name == 'License Report Workflow' - 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 check result - if: github.event.workflow_run.name == 'Build and Test Workflow' - 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 PR check comments - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108d7 # v9.0.0 - 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 workflow run ${run.id}.`); - return; - } - - const jobs = await github.paginate( - github.rest.actions.listJobsForWorkflowRun, - { owner, repo, run_id: run.id, per_page: 100 }, - ); - - const checks = [ - { - workflow: 'Build and Test Workflow', - marker: '', - failedStep: 'Fail if engine check failed', - title: 'Engine Check Failed', - instructions: "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.", - removeWhenFixed: true, - }, - { - workflow: 'Build and Test Workflow', - marker: '', - failedStep: 'Fail if backend format check failed', - title: 'Backend Format Check Failed', - instructions: "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.", - removeWhenFixed: true, - }, - { - workflow: 'Build and Test Workflow', - marker: '', - failedStep: 'Fail if frontend check failed', - title: 'Frontend Check Failed', - instructions: "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.", - removeWhenFixed: true, - }, - { - workflow: 'Build and Test Workflow', - marker: '', - failedStep: 'Fail job if errors found', - title: 'TOML Translation Check Failed', - instructions: "The TOML translation check found errors. Review the TOML translation files and make them consistent with the en-US reference file.", - type: 'toml-translation', - removeWhenFixed: true, - }, - { - workflow: 'Build and Test Workflow', - marker: '', - failedStep: 'Fail if generated models check failed', - title: 'Generated Models Check Failed', - instructions: "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.", - removeWhenFixed: true, - }, - { - workflow: 'License Report Workflow', - marker: '', - failedStep: 'Fail workflow if license warnings exist (PR only)', - jobName: 'Generate Frontend License Report', - title: 'Frontend License Check Failed', - instructions: 'Review the frontend license warnings reported on the pull request and resolve them before merging.', - type: 'frontend-license', - removeWhenFixed: false, - }, - { - workflow: 'License Report Workflow', - marker: '', - failedStep: 'Fail workflow if license warnings exist (PR only)', - jobName: 'Generate Backend License Report', - title: 'Backend License Check Failed', - instructions: 'Review the backend license warnings reported on the pull request and resolve them before merging.', - type: 'backend-license', - removeWhenFixed: false, - }, - ]; - - const comments = await github.paginate( - github.rest.issues.listComments, - { owner, repo, issue_number: pullRequest.number, per_page: 100 }, - ); - - const licenseCommentBody = (type, failed, marker) => { - if (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 (_) {} - return failed - ? `${marker}\n\n## ❌ Frontend License Check Failed\n\nThe frontend license check has detected compatibility warnings that require review:\n\n${warningDetails}\n\n**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.\n\n_This check will fail the PR until license issues are resolved._` - : `${marker}\n\n## ✅ Frontend License Check Passed\n\nAll frontend licenses have been validated and no compatibility warnings were detected.\n\nThe frontend license report has been updated successfully.`; - } - - if (type === 'backend-license') { - 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 (_) {} - return failed - ? `${marker}\n\n## ❌ Backend License Check Failed\n\nThe backend license check has detected dependencies with incompatible or unallowed licenses:\n\n${warningDetails || 'See uploaded artifact for details.'}\n\n**Action Required:** Please review these licenses and resolve before merging.\n\n_This check will fail the PR until license issues are resolved._` - : `${marker}\n\n## ✅ Backend License Check Passed\n\nAll backend dependencies have valid and allowed licenses.\n\nThe backend license report has been updated successfully.`; - } - - if (type === 'toml-translation') { - let details = 'See the TOML translation check logs for details.'; - try { - details = require('fs').readFileSync('toml-artifacts/result.txt', 'utf8'); - } catch (_) {} - return failed - ? `${marker}\n\n## ❌ TOML Translation Check Failed\n\n${details}\n\nPlease correct the translation files and push the changes.` - : `${marker}\n\n## ✅ TOML Translation Check Passed\n\nAll changed TOML translation files are consistent with the reference file.`; - } - - throw new Error(`Unsupported license comment type: ${type}`); - }; - - for (const check of checks) { - if (run.name !== check.workflow) continue; - - const failed = jobs.some(job => - (!check.jobName || job.name === check.jobName) && - job.steps?.some(step => step.name === check.failedStep && step.conclusion === 'failure')); - const existing = comments.find(comment => comment.body?.includes(check.marker)); - const body = check.type - ? licenseCommentBody(check.type, failed, check.marker) - : [check.marker, `## ❌ ${check.title}`, '', check.instructions].join('\n'); - - 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 (check.type && existing) { - await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); - } else if (check.removeWhenFixed && existing) { - await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id }); - } - }