|
| 1 | +name: Enhanced Strapi Documentation Style Review |
| 2 | + |
| 3 | +on: |
| 4 | + # Enable for real PRs when ready |
| 5 | + # pull_request: |
| 6 | + # types: [opened, synchronize, reopened] |
| 7 | + # paths: |
| 8 | + # - 'docusaurus/docs/**/*.md' |
| 9 | + # - 'docusaurus/docs/**/*.mdx' |
| 10 | + |
| 11 | + # Manual trigger for testing |
| 12 | + workflow_dispatch: |
| 13 | + inputs: |
| 14 | + pr_number: |
| 15 | + description: 'PR number to validate' |
| 16 | + required: true |
| 17 | + type: string |
| 18 | + target_files: |
| 19 | + description: 'Specific files to validate (optional - leave empty to analyze entire PR)' |
| 20 | + required: false |
| 21 | + type: string |
| 22 | + enable_inline_comments: |
| 23 | + description: 'Enable inline comments (recommended)' |
| 24 | + required: false |
| 25 | + type: boolean |
| 26 | + default: true |
| 27 | + max_inline_comments: |
| 28 | + description: 'Maximum number of inline comments (default: 10)' |
| 29 | + required: false |
| 30 | + type: string |
| 31 | + default: '10' |
| 32 | + |
| 33 | +jobs: |
| 34 | + validate-documentation: |
| 35 | + runs-on: ubuntu-latest |
| 36 | + permissions: |
| 37 | + contents: read |
| 38 | + issues: write |
| 39 | + pull-requests: write |
| 40 | + steps: |
| 41 | + - name: Checkout repository |
| 42 | + uses: actions/checkout@v4 |
| 43 | + with: |
| 44 | + fetch-depth: 0 |
| 45 | + |
| 46 | + - name: Setup Node.js |
| 47 | + uses: actions/setup-node@v4 |
| 48 | + with: |
| 49 | + node-version: '20' |
| 50 | + cache: 'yarn' |
| 51 | + cache-dependency-path: 'docusaurus/yarn.lock' |
| 52 | + |
| 53 | + - name: Install dependencies |
| 54 | + run: | |
| 55 | + cd docusaurus |
| 56 | + yarn install |
| 57 | +
|
| 58 | + - name: Run Enhanced Documentation Validation |
| 59 | + id: validation |
| 60 | + run: | |
| 61 | + cd docusaurus |
| 62 | + |
| 63 | + echo "🎯 Starting enhanced documentation validation..." |
| 64 | + |
| 65 | + # Determine validation strategy |
| 66 | + if [ -n "${{ github.event.inputs.target_files }}" ]; then |
| 67 | + echo "📄 Validating specific files: ${{ github.event.inputs.target_files }}" |
| 68 | + |
| 69 | + # Manual file validation (standard mode) |
| 70 | + node scripts/style-validation/validate-content-style.js \ |
| 71 | + --verbose \ |
| 72 | + ${{ github.event.inputs.target_files }} |
| 73 | + |
| 74 | + elif [ -n "${{ github.event.inputs.pr_number }}" ]; then |
| 75 | + echo "🔍 Validating PR #${{ github.event.inputs.pr_number }} with enhanced diff analysis" |
| 76 | + |
| 77 | + # Use the new enhanced GitHub validation with inline comments |
| 78 | + node scripts/style-validation/validate-content-style-github.js \ |
| 79 | + --pr ${{ github.event.inputs.pr_number }} \ |
| 80 | + --repo strapi/documentation \ |
| 81 | + --verbose |
| 82 | + |
| 83 | + elif [ "${{ github.event_name }}" = "pull_request" ]; then |
| 84 | + echo "🔍 Auto-validating PR #${{ github.event.number }}" |
| 85 | + |
| 86 | + # Auto-validation for real PRs |
| 87 | + node scripts/style-validation/validate-content-style-github.js \ |
| 88 | + --pr ${{ github.event.number }} \ |
| 89 | + --repo strapi/documentation \ |
| 90 | + --verbose |
| 91 | + |
| 92 | + else |
| 93 | + echo "❌ No validation target specified" |
| 94 | + echo '{"summary":{"filesProcessed":0,"totalIssues":0,"criticalIssues":0},"issues":{"errors":[],"warnings":[],"suggestions":[]}}' > style-check-results.json |
| 95 | + fi |
| 96 | + continue-on-error: true |
| 97 | + |
| 98 | + - name: Post Enhanced PR Review with Inline Comments |
| 99 | + uses: actions/github-script@v7 |
| 100 | + with: |
| 101 | + script: | |
| 102 | + const fs = require('fs'); |
| 103 | + |
| 104 | + // Get context information |
| 105 | + const prNumber = '${{ github.event.inputs.pr_number }}' || '${{ github.event.number }}' || ''; |
| 106 | + const eventName = '${{ github.event_name }}'; |
| 107 | + const enableInlineComments = '${{ github.event.inputs.enable_inline_comments }}' !== 'false'; |
| 108 | + const maxInlineComments = parseInt('${{ github.event.inputs.max_inline_comments }}') || 10; |
| 109 | + |
| 110 | + try { |
| 111 | + // Read validation results |
| 112 | + const resultsPath = 'docusaurus/style-check-results.json'; |
| 113 | + |
| 114 | + if (!fs.existsSync(resultsPath)) { |
| 115 | + console.log('❌ No results file found, validation may have failed'); |
| 116 | + |
| 117 | + const errorComment = '## ❌ Enhanced Documentation Review Failed\n\n' + |
| 118 | + 'The enhanced validation system failed to run. Please check the Action logs.\n\n' + |
| 119 | + '**This might indicate:**\n' + |
| 120 | + '- Missing validation scripts\n' + |
| 121 | + '- Network issues fetching PR diff\n' + |
| 122 | + '- Configuration problems\n\n' + |
| 123 | + '*Please contact the documentation team for assistance.*'; |
| 124 | + |
| 125 | + if (prNumber) { |
| 126 | + await github.rest.issues.createComment({ |
| 127 | + issue_number: parseInt(prNumber), |
| 128 | + owner: context.repo.owner, |
| 129 | + repo: context.repo.repo, |
| 130 | + body: errorComment |
| 131 | + }); |
| 132 | + } |
| 133 | + return; |
| 134 | + } |
| 135 | +
|
| 136 | + const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8')); |
| 137 | + |
| 138 | + // Check for previous bot comments to determine if this is an update |
| 139 | + let isFollowUp = false; |
| 140 | + let previousResults = null; |
| 141 | + |
| 142 | + try { |
| 143 | + if (prNumber) { |
| 144 | + const commentsResponse = await github.rest.issues.listComments({ |
| 145 | + issue_number: parseInt(prNumber), |
| 146 | + owner: context.repo.owner, |
| 147 | + repo: context.repo.repo |
| 148 | + }); |
| 149 | + |
| 150 | + const botComments = commentsResponse.data.filter(comment => |
| 151 | + comment.user.type === 'Bot' && |
| 152 | + comment.body.includes('Thanks for contributing to Strapi\'s documentation') |
| 153 | + ); |
| 154 | + |
| 155 | + isFollowUp = botComments.length > 0; |
| 156 | + |
| 157 | + // Try to extract previous results for comparison |
| 158 | + if (isFollowUp) { |
| 159 | + const lastComment = botComments[botComments.length - 1].body; |
| 160 | + const prevIssuesMatch = lastComment.match(/spotted (\d+) (?:small )?improvement/); |
| 161 | + if (prevIssuesMatch) { |
| 162 | + previousResults = { totalIssues: parseInt(prevIssuesMatch[1]) }; |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + } catch (e) { |
| 167 | + console.log('Could not check for previous comments:', e.message); |
| 168 | + } |
| 169 | + |
| 170 | + // Use the friendly comment from our enhanced validator |
| 171 | + let comment = results.githubComment || '## 👋 Thanks for contributing to Strapi\'s documentation!\n\nSomething went wrong generating the review comment.'; |
| 172 | + |
| 173 | + // Add progress indication if this is a follow-up |
| 174 | + if (isFollowUp && previousResults) { |
| 175 | + const change = results.summary.totalIssues - previousResults.totalIssues; |
| 176 | + let progressNote = ''; |
| 177 | + |
| 178 | + if (change < 0) { |
| 179 | + progressNote = `\n\n🎉 **Great progress!** You've resolved ${Math.abs(change)} issue${Math.abs(change) > 1 ? 's' : ''} since the last review!\n`; |
| 180 | + } else if (change > 0) { |
| 181 | + progressNote = `\n\n📝 **Update:** Found ${change} additional item${change > 1 ? 's' : ''} to review.\n`; |
| 182 | + } else if (results.summary.totalIssues === 0) { |
| 183 | + progressNote = `\n\n🎊 **Perfect!** All issues have been resolved!\n`; |
| 184 | + } else { |
| 185 | + progressNote = `\n\n🔄 **Updated review** with your latest changes.\n`; |
| 186 | + } |
| 187 | + |
| 188 | + // Insert progress note before the resources section |
| 189 | + comment = comment.replace(/\*\*📚 Helpful Resources:\*\*/, progressNote + '**📚 Helpful Resources:**'); |
| 190 | + } |
| 191 | + |
| 192 | + // Add workflow context |
| 193 | + if (eventName === 'workflow_dispatch') { |
| 194 | + comment += `\n\n*🔧 Manually triggered validation for PR #${prNumber}*`; |
| 195 | + } |
| 196 | + |
| 197 | + // Post the main comment |
| 198 | + if (prNumber) { |
| 199 | + await github.rest.issues.createComment({ |
| 200 | + issue_number: parseInt(prNumber), |
| 201 | + owner: context.repo.owner, |
| 202 | + repo: context.repo.repo, |
| 203 | + body: comment |
| 204 | + }); |
| 205 | + |
| 206 | + console.log(`✅ Main comment posted to PR #${prNumber}`); |
| 207 | + } |
| 208 | + |
| 209 | + // Post inline comments if enabled and available |
| 210 | + if (enableInlineComments && results.inlineComments && results.inlineComments.length > 0 && prNumber) { |
| 211 | + console.log(`💬 Posting ${results.inlineComments.length} inline comments...`); |
| 212 | + |
| 213 | + try { |
| 214 | + // Get the latest commit SHA for the PR |
| 215 | + const prData = await github.rest.pulls.get({ |
| 216 | + owner: context.repo.owner, |
| 217 | + repo: context.repo.repo, |
| 218 | + pull_number: parseInt(prNumber) |
| 219 | + }); |
| 220 | + |
| 221 | + const commitSha = prData.data.head.sha; |
| 222 | + |
| 223 | + // Create a review with inline comments |
| 224 | + const review = await github.rest.pulls.createReview({ |
| 225 | + owner: context.repo.owner, |
| 226 | + repo: context.repo.repo, |
| 227 | + pull_number: parseInt(prNumber), |
| 228 | + commit_id: commitSha, |
| 229 | + body: `## 📝 Detailed Style Review\n\nI've added specific suggestions directly on the lines that need attention. Each comment includes:\n- 🎯 The specific rule being applied\n- 💡 A suggested improvement\n- ✨ An example of the recommended approach\n\nThese suggestions will help make your documentation even more welcoming and clear for our global developer community!`, |
| 230 | + event: results.summary.criticalIssues > 0 ? 'REQUEST_CHANGES' : 'COMMENT', |
| 231 | + comments: results.inlineComments.map(comment => ({ |
| 232 | + path: comment.path, |
| 233 | + line: comment.line, |
| 234 | + body: comment.body |
| 235 | + })) |
| 236 | + }); |
| 237 | + |
| 238 | + console.log(`✅ Posted review with ${results.inlineComments.length} inline comments`); |
| 239 | + |
| 240 | + // If we had to truncate comments, add a note |
| 241 | + if (results.hasMoreIssues) { |
| 242 | + const truncationComment = `## 💙 More suggestions available!\n\n` + |
| 243 | + `I found ${results.remainingIssues} additional item${results.remainingIssues > 1 ? 's' : ''} to review. ` + |
| 244 | + `Once you address the current suggestions, I'll be happy to review again and share the remaining feedback!\n\n` + |
| 245 | + `This approach helps keep the review manageable and focused. Thanks for your patience! 😊`; |
| 246 | + |
| 247 | + await github.rest.issues.createComment({ |
| 248 | + issue_number: parseInt(prNumber), |
| 249 | + owner: context.repo.owner, |
| 250 | + repo: context.repo.repo, |
| 251 | + body: truncationComment |
| 252 | + }); |
| 253 | + } |
| 254 | + |
| 255 | + } catch (error) { |
| 256 | + console.error('Failed to post inline comments:', error); |
| 257 | + |
| 258 | + // Fallback: post inline comments as a single comment |
| 259 | + let fallbackComment = '## 📝 Detailed Suggestions\n\n*Unable to post inline comments, here are the specific suggestions:*\n\n'; |
| 260 | + |
| 261 | + results.inlineComments.forEach((comment, index) => { |
| 262 | + fallbackComment += `### ${comment.path}:${comment.line}\n\n${comment.body}\n\n---\n\n`; |
| 263 | + }); |
| 264 | + |
| 265 | + await github.rest.issues.createComment({ |
| 266 | + issue_number: parseInt(prNumber), |
| 267 | + owner: context.repo.owner, |
| 268 | + repo: context.repo.repo, |
| 269 | + body: fallbackComment |
| 270 | + }); |
| 271 | + |
| 272 | + console.log('✅ Posted fallback comment with suggestions'); |
| 273 | + } |
| 274 | + } |
| 275 | + |
| 276 | + // Set appropriate exit code |
| 277 | + if (results.issues.errors.length > 0) { |
| 278 | + core.setFailed(`Found ${results.issues.errors.length} critical errors that must be fixed before merging.`); |
| 279 | + } else { |
| 280 | + console.log(`✅ Enhanced validation completed successfully`); |
| 281 | + console.log(`📊 Results: ${results.summary.totalIssues} total issues, ${results.inlineComments?.length || 0} inline comments`); |
| 282 | + } |
| 283 | + |
| 284 | + } catch (error) { |
| 285 | + console.error('❌ Error processing validation results:', error); |
| 286 | + |
| 287 | + const errorMessage = '## ❌ Enhanced Documentation Review Error\n\n' + |
| 288 | + 'There was an error processing the validation results.\n\n' + |
| 289 | + `**Error:** ${error.message}\n\n` + |
| 290 | + 'Please check the GitHub Action logs for more details or contact the documentation team.'; |
| 291 | + |
| 292 | + if (prNumber) { |
| 293 | + await github.rest.issues.createComment({ |
| 294 | + issue_number: parseInt(prNumber), |
| 295 | + owner: context.repo.owner, |
| 296 | + repo: context.repo.repo, |
| 297 | + body: errorMessage |
| 298 | + }); |
| 299 | + } |
| 300 | + |
| 301 | + core.setFailed(`Enhanced validation processing failed: ${error.message}`); |
| 302 | + } |
| 303 | +
|
| 304 | + - name: Upload Validation Results |
| 305 | + uses: actions/upload-artifact@v4 |
| 306 | + if: always() |
| 307 | + with: |
| 308 | + name: style-validation-results |
| 309 | + path: docusaurus/style-check-results.json |
| 310 | + retention-days: 30 |
0 commit comments