diff --git a/.github/workflows/check-media-attachments.yml b/.github/workflows/check-media-attachments.yml new file mode 100644 index 000000000..ddea3904e --- /dev/null +++ b/.github/workflows/check-media-attachments.yml @@ -0,0 +1,117 @@ +name: Check PR for Screenshots when HTML changes + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '**/*.html' + +jobs: + check-for-screenshots: + runs-on: ubuntu-latest + name: Check for PR screenshots + permissions: + contents: read + pull-requests: read + issues: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check for screenshots in PR description or comments + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const prNumber = context.payload.pull_request.number; + const botCommentIdentifier = ''; + + console.log(`Checking PR #${prNumber} for screenshots because HTML files were changed.`); + + // --- 1. Get PR body + comments --- + const prBody = context.payload.pull_request.body || ''; + + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: prNumber } + ); + + const allCommentsText = comments.map(comment => comment.body || '').join('\n'); + const combinedText = prBody + '\n' + allCommentsText; + + // --- 2. Look for actual media (image/video uploads) --- + + // Extract URLs from Markdown image syntax (![alt](url)) + const mdImageUrls = [...combinedText.matchAll(/!\[[^\]]*\]\(([^)]+)\)/gi)].map(m => m[1]); + + // Extract all plain URLs in the text + const allUrls = [...combinedText.matchAll(/\bhttps?:\/\/[^\s)>"']+/gi)].map(m => m[0]); + + // Merge and deduplicate all URLs + const candidates = [...new Set([...mdImageUrls, ...allUrls])]; + + // Define file extensions that count as media + const mediaExt = /\.(?:png|jpe?g|gif|webp|svg|apng|mp4|mov|webm)$/i; + + // Define known GitHub hosts for attachments (including new user-attachments paths) + const ghAssetsHost = /(?:^|\/\/)(?:user-images\.githubusercontent\.com|private-user-images\.githubusercontent\.com|github\.com\/user-attachments\/assets|github-production-user-asset-[^/]+\.s3\.amazonaws\.com)\b/i; + + // Filter candidate URLs: either matches media extensions OR GitHub attachment hosts + const mediaUrls = candidates.filter(u => mediaExt.test(u) || ghAssetsHost.test(u)); + + // Final flag: true if at least one media URL found + const mediaFound = mediaUrls.length > 0; + + // --- 3. Find previous bot comment --- + const botComment = comments.find(comment => + comment.user?.login === 'github-actions[bot]' && + comment.body?.includes(botCommentIdentifier) + ); + + if (mediaFound) { + console.log('✅ Screenshot or media found in the PR.'); + if (botComment) { + try { + await github.rest.issues.deleteComment({ + owner, + repo, + comment_id: botComment.id, + }); + console.log('Removed previous warning comment.'); + } catch (e) { + console.warn(`Could not delete previous comment (likely fork perms): ${e.status || ''}`); + } + } + return; + } + + // --- 4. Post or update warning comment --- + const commentBody = `${botCommentIdentifier} + + ## Screenshot Missing + + You've modified **HTML files**, but no screenshots or recordings were found in the PR description or comments. + + *Please attach a screenshot or GIF demonstrating the change.*`; + + if (botComment) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: botComment.id, + body: commentBody, + }); + console.log('Updated existing warning comment.'); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: commentBody, + }); + console.log('Created a new warning comment.'); + } + + // --- 5. Fail workflow to block merging --- + core.setFailed('Missing screenshots for HTML changes. Please add one to continue.');