diff --git a/.github/scripts/create-failure-issue.js b/.github/scripts/create-failure-issue.js new file mode 100644 index 0000000000..2b9bd6071e --- /dev/null +++ b/.github/scripts/create-failure-issue.js @@ -0,0 +1,256 @@ +/** + * Create or update GitHub issue when i18n workflow fails + * Usage: node create-failure-issue.js + */ + +module.exports = async ({ github, context, core }) => { + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const timestamp = new Date().toISOString(); + const date = timestamp.split('T')[0]; + + // Get error details from environment variables + const errorDetails = { + validateEnv: process.env.ERROR_VALIDATE_ENV || '', + rebaseAttempt: process.env.ERROR_REBASE_ATTEMPT || '', + createBranch: process.env.ERROR_CREATE_BRANCH || '', + installDeps: process.env.ERROR_INSTALL_DEPS || '', + runI18n: process.env.ERROR_RUN_I18N || '', + commitPush: process.env.ERROR_COMMIT_PUSH || '', + }; + + // Get step conclusions from environment variables + const stepStatus = { + validateEnv: process.env.STEP_VALIDATE_ENV || 'Not run', + checkBranch: process.env.STEP_CHECK_BRANCH || 'Not run', + rebaseAttempt: process.env.STEP_REBASE_ATTEMPT || 'Not run', + createBranch: process.env.STEP_CREATE_BRANCH || 'Not run', + installDeps: process.env.STEP_INSTALL_DEPS || 'Not run', + runI18n: process.env.STEP_RUN_I18N || 'Not run', + commitPush: process.env.STEP_COMMIT_PUSH || 'Not run', + createPr: process.env.STEP_CREATE_PR || 'Not run', + }; + + // Find the first non-empty error + const mainError = + Object.values(errorDetails).find((error) => error && error.trim()) || 'Unknown error occurred'; + + // Determine error category for better troubleshooting + const getErrorCategory = (error) => { + if (error.includes('API') || error.includes('authentication')) return 'API/Authentication'; + if (error.includes('network') || error.includes('timeout')) return 'Network/Connectivity'; + if (error.includes('dependencies') || error.includes('bun')) return 'Dependencies'; + if (error.includes('git') || error.includes('branch') || error.includes('rebase')) + return 'Git Operations'; + if (error.includes('permission') || error.includes('token')) return 'Permissions'; + return 'General'; + }; + + const errorCategory = getErrorCategory(mainError); + + const issueTitle = `🚨 Daily i18n Update Failed - ${date}`; + + const issueBody = `## 🚨 Automated i18n Update Failure + +**Timestamp:** ${timestamp} +**Workflow Run:** [#${context.runNumber}](${runUrl}) +**Repository:** ${context.repo.owner}/${context.repo.repo} +**Branch:** ${context.ref} +**Commit:** ${context.sha} + +## ❌ Error Details +**Primary Error:** ${mainError} +**Category:** ${errorCategory} + +## 🔍 Step Status +| Step | Status | +|------|--------| +| Environment Validation | ${stepStatus.validateEnv} | +| Branch Check | ${stepStatus.checkBranch} | +| Rebase Attempt | ${stepStatus.rebaseAttempt} | +| Branch Creation | ${stepStatus.createBranch} | +| Dependencies | ${stepStatus.installDeps} | +| i18n Update | ${stepStatus.runI18n} | +| Git Operations | ${stepStatus.commitPush} | +| PR Creation | ${stepStatus.createPr} | + +## 🔧 Environment Info +- **Runner OS:** ${process.env.RUNNER_OS || 'Unknown'} +- **Bun Version:** ${process.env.BUN_VERSION || 'Default'} +- **Workflow:** \`${context.workflow}\` + +## 📋 Debug Information +Debug logs have been uploaded as artifacts and will be available for 7 days. + +${getErrorCategoryHelp(errorCategory)} + +## đŸ› ī¸ General Troubleshooting Steps +1. Check if all required secrets are properly configured +2. Verify OpenAI API quota and billing status +3. Review the workflow run logs for detailed error messages +4. Check if there are any ongoing GitHub API issues +5. Manually trigger the workflow to retry + +## 📊 Workflow Statistics +- **Run Number:** ${context.runNumber} +- **Run ID:** ${context.runId} +- **Event:** ${context.eventName} + +--- +**Auto-generated by:** [\`${context.workflow}\`](${runUrl}) +**Labels:** automated, bug, i18n, workflow-failure, ${errorCategory.toLowerCase().replace(/[^a-z0-9]/g, '-')}`; + + try { + // Search for existing open issues with similar title + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'automated,workflow-failure', + state: 'open', + per_page: 50, + }); + + const todayPrefix = `🚨 Daily i18n Update Failed - ${date}`; + const existingIssue = existingIssues.data.find((issue) => issue.title.startsWith(todayPrefix)); + + if (existingIssue) { + // Update existing issue with comment + const commentBody = `## 🔄 Additional Failure + +**Timestamp:** ${timestamp} +**Workflow Run:** [#${context.runNumber}](${runUrl}) +**Error Category:** ${errorCategory} +**Error:** ${mainError} + +Same issue occurred again. Please investigate the recurring problem. + +### Quick Actions +- [ ] Check API quotas and billing +- [ ] Verify network connectivity +- [ ] Review recent changes that might cause conflicts +- [ ] Consider manual intervention + +--- +*This is failure #${(await getFailureCount(github, context, existingIssue.number)) + 1} for today.*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + body: commentBody, + }); + + // Add priority label if this is a recurring issue + const failureCount = await getFailureCount(github, context, existingIssue.number); + if (failureCount >= 2) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + labels: ['priority-high', 'recurring'], + }); + } + + core.info(`✅ Updated existing issue #${existingIssue.number}`); + core.setOutput('issue-number', existingIssue.number); + core.setOutput('issue-url', existingIssue.html_url); + core.setOutput('action', 'updated'); + } else { + // Create new issue + const labels = [ + 'automated', + 'bug', + 'i18n', + 'workflow-failure', + errorCategory.toLowerCase().replace(/[^a-z0-9]/g, '-'), + ]; + + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body: issueBody, + labels: labels, + }); + + core.info(`✅ Created new issue #${issue.data.number}`); + core.setOutput('issue-number', issue.data.number); + core.setOutput('issue-url', issue.data.html_url); + core.setOutput('action', 'created'); + } + } catch (error) { + core.setFailed(`Failed to create or update issue: ${error.message}`); + throw error; + } +}; + +/** + * Get category-specific help text + */ +function getErrorCategoryHelp(category) { + const helpTexts = { + 'API/Authentication': ` +### 🔑 API/Authentication Issues +- Verify \`OPENAI_API_KEY\` is correctly set and valid +- Check if API key has sufficient quota/credits +- Ensure \`GH_TOKEN\` has necessary permissions +- Test API connectivity manually`, + + 'Network/Connectivity': ` +### 🌐 Network/Connectivity Issues +- Check if OpenAI API is experiencing outages +- Verify proxy settings if using \`OPENAI_PROXY_URL\` +- Retry the workflow as this might be temporary +- Check GitHub Actions service status`, + + 'Dependencies': ` +### đŸ“Ļ Dependencies Issues +- Verify \`bun\` version compatibility +- Check for package.json changes that might affect dependencies +- Clear cache and retry installation +- Review recent dependency updates`, + + 'Git Operations': ` +### 🔧 Git Operations Issues +- Check for conflicting changes in target branch +- Verify repository permissions +- Review recent commits that might cause conflicts +- Manual branch cleanup might be required`, + + 'Permissions': ` +### 🔐 Permissions Issues +- Verify \`GH_TOKEN\` has \`repo\` and \`issues\` permissions +- Check if token can create/update PRs and branches +- Ensure token hasn't expired +- Review repository settings and branch protection rules`, + + 'General': ` +### 🔍 General Issues +- Review detailed error logs in workflow run +- Check for recent changes in codebase +- Verify all environment variables are set +- Consider running workflow manually with debug enabled`, + }; + + return helpTexts[category] || helpTexts['General']; +} + +/** + * Count how many times this issue has failed today + */ +async function getFailureCount(github, context, issueNumber) { + try { + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + }); + + const today = new Date().toISOString().split('T')[0]; + return comments.data.filter( + (comment) => + comment.body.includes('Additional Failure') && comment.created_at.startsWith(today), + ).length; + } catch (error) { + return 0; + } +} diff --git a/.github/workflows/auto-i18n.yml b/.github/workflows/auto-i18n.yml index 332fd9c7ae..fb2010c9b9 100644 --- a/.github/workflows/auto-i18n.yml +++ b/.github/workflows/auto-i18n.yml @@ -8,58 +8,317 @@ on: jobs: update-i18n: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GH_TOKEN }} + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" - name: Install bun uses: oven-sh/setup-bun@v1 with: bun-version: ${{ secrets.BUN_VERSION }} + - name: Validate environment + id: validate_env + run: | + echo "🔍 Validating environment..." + + # Check required secrets + if [ -z "${{ secrets.OPENAI_API_KEY }}" ]; then + echo "❌ OPENAI_API_KEY is missing" + echo "env_valid=false" >> $GITHUB_OUTPUT + echo "ERROR_VALIDATE_ENV=OPENAI_API_KEY secret is not configured" >> $GITHUB_ENV + exit 1 + fi + + if [ -z "${{ secrets.GH_TOKEN }}" ]; then + echo "❌ GH_TOKEN is missing" + echo "env_valid=false" >> $GITHUB_OUTPUT + echo "ERROR_VALIDATE_ENV=GH_TOKEN secret is not configured" >> $GITHUB_ENV + exit 1 + fi + + # Test OpenAI API connectivity (optional, with timeout) + echo "🌐 Testing OpenAI API connectivity..." + if timeout 30s curl -s -H "Authorization: Bearer ${{ secrets.OPENAI_API_KEY }}" \ + ${{ secrets.OPENAI_PROXY_URL || 'https://api.openai.com' }}/v1/models >/dev/null 2>&1; then + echo "✅ OpenAI API accessible" + else + echo "âš ī¸ OpenAI API test failed (but continuing...)" + fi + + echo "env_valid=true" >> $GITHUB_OUTPUT + - name: Check if branch exists id: check_branch run: | - git ls-remote --exit-code --heads origin style/auto-i18n - echo "::set-output name=branch_exists::$?" + echo "🔍 Checking for existing branch..." + + # Retry mechanism for network issues + for i in {1..3}; do + if git ls-remote --exit-code --heads origin style/auto-i18n >/dev/null 2>&1; then + echo "branch_exists=true" >> $GITHUB_OUTPUT + echo "🔍 Found existing branch: style/auto-i18n" + exit 0 + elif [ $i -eq 3 ]; then + echo "branch_exists=false" >> $GITHUB_OUTPUT + echo "â„šī¸ Branch style/auto-i18n does not exist" + exit 0 + else + echo "âš ī¸ Network issue, retrying... ($i/3)" + sleep 5 + fi + done + + - name: Handle existing branch with rebase + if: steps.check_branch.outputs.branch_exists == 'true' + id: rebase_attempt + run: | + echo "🔄 Attempting to rebase existing branch..." + + # Fetch the existing branch with error handling + if ! git fetch origin style/auto-i18n; then + echo "❌ Failed to fetch existing branch" + echo "rebase_success=false" >> $GITHUB_OUTPUT + echo "ERROR_REBASE_ATTEMPT=Failed to fetch existing branch from origin" >> $GITHUB_ENV + exit 1 + fi + + if ! git checkout -b style/auto-i18n origin/style/auto-i18n; then + echo "❌ Failed to checkout existing branch" + echo "rebase_success=false" >> $GITHUB_OUTPUT + echo "ERROR_REBASE_ATTEMPT=Failed to checkout existing branch" >> $GITHUB_ENV + exit 1 + fi + + # Try to rebase onto latest main + if git rebase origin/main; then + echo "✅ Rebase successful" + echo "rebase_success=true" >> $GITHUB_OUTPUT + else + echo "❌ Rebase failed due to conflicts" + echo "rebase_success=false" >> $GITHUB_OUTPUT + + # Abort the failed rebase + git rebase --abort || echo "âš ī¸ Failed to abort rebase cleanly" + + # Go back to main and delete the problematic branch + git checkout main + git branch -D style/auto-i18n || echo "âš ī¸ Failed to delete local branch" + + # Try to delete remote branch, handle if it fails + if git push origin --delete style/auto-i18n 2>/dev/null; then + echo "đŸ—‘ī¸ Deleted old branch due to rebase conflicts" + else + echo "âš ī¸ Could not delete remote branch (may not exist or permission issue)" + fi + fi + + - name: Create clean branch if needed + if: steps.check_branch.outputs.branch_exists == 'false' || steps.rebase_attempt.outputs.rebase_success == 'false' + id: create_branch + run: | + echo "đŸŒŋ Creating fresh branch from main..." + + # Ensure we're on main and it's up to date + git checkout main + git pull origin main + + # Create new branch + if git checkout -b style/auto-i18n; then + echo "✅ Successfully created new branch" + echo "branch_created=true" >> $GITHUB_OUTPUT + else + echo "❌ Failed to create new branch" + echo "branch_created=false" >> $GITHUB_OUTPUT + echo "ERROR_CREATE_BRANCH=Failed to create new branch style/auto-i18n" >> $GITHUB_ENV + exit 1 + fi - name: Install deps - run: bun i + id: install_deps + run: | + echo "đŸ“Ļ Installing dependencies..." + + # Retry mechanism for dependency installation + for i in {1..3}; do + if bun i; then + echo "✅ Dependencies installed successfully" + echo "deps_installed=true" >> $GITHUB_OUTPUT + exit 0 + elif [ $i -eq 3 ]; then + echo "❌ Failed to install dependencies after 3 attempts" + echo "deps_installed=false" >> $GITHUB_OUTPUT + echo "ERROR_INSTALL_DEPS=Failed to install dependencies with bun after 3 retries" >> $GITHUB_ENV + exit 1 + else + echo "âš ī¸ Dependency installation failed, retrying... ($i/3)" + sleep 10 + fi + done - name: Run i18n update - if: steps.check_branch.outputs.branch_exists != 0 - run: bun run i18n + id: run_i18n + run: | + echo "🌐 Running i18n update..." + + # Set timeout and capture output + if timeout 900s bun run i18n 2>&1 | tee i18n_output.log; then + echo "✅ i18n update completed successfully" + echo "i18n_success=true" >> $GITHUB_OUTPUT + else + exit_code=$? + echo "❌ i18n update failed with exit code: $exit_code" + echo "i18n_success=false" >> $GITHUB_OUTPUT + + # Capture error details + if [ $exit_code -eq 124 ]; then + echo "ERROR_RUN_I18N=i18n update timed out after 15 minutes" >> $GITHUB_ENV + else + echo "ERROR_RUN_I18N=i18n update failed with exit code $exit_code" >> $GITHUB_ENV + fi + + # Save output for debugging + echo "📋 Saving debug output..." + exit 1 + fi env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_PROXY_URL: ${{ secrets.OPENAI_PROXY_URL }} - name: Check for changes id: git_status + if: steps.run_i18n.outputs.i18n_success == 'true' run: | - git diff --exit-code || echo "::set-output name=changes_exist::true" + echo "🔍 Checking for changes..." - - name: Create Pull Request - if: steps.check_branch.outputs.branch_exists != 0 && steps.git_status.outputs.changes_exist == 'true' + if git diff --exit-code >/dev/null 2>&1; then + echo "changes_exist=false" >> $GITHUB_OUTPUT + echo "â„šī¸ No changes detected" + else + echo "changes_exist=true" >> $GITHUB_OUTPUT + echo "✨ Changes detected" + + # Show what changed + echo "📝 Changed files:" + git diff --name-only | head -20 + fi + + - name: Commit and push changes + if: steps.git_status.outputs.changes_exist == 'true' + id: commit_push + run: | + echo "💾 Committing and pushing changes..." + + git add . + git commit -m "🤖 style: update i18n + + - Auto-generated i18n updates + - Generated at: $(date -u '+%Y-%m-%d %H:%M:%S UTC') + - Workflow run: ${{ github.run_number }}" + + # Push with retry mechanism + for i in {1..3}; do + if git push origin style/auto-i18n --force-with-lease; then + echo "✅ Successfully pushed changes" + echo "push_success=true" >> $GITHUB_OUTPUT + exit 0 + elif [ $i -eq 3 ]; then + echo "❌ Failed to push changes after 3 attempts" + echo "push_success=false" >> $GITHUB_OUTPUT + echo "ERROR_COMMIT_PUSH=Failed to push changes to remote repository after 3 retries" >> $GITHUB_ENV + exit 1 + else + echo "âš ī¸ Push failed, retrying... ($i/3)" + sleep 5 + fi + done + + - name: Create or Update Pull Request + if: steps.git_status.outputs.changes_exist == 'true' && steps.commit_push.outputs.push_success == 'true' + id: create_pr uses: peter-evans/create-pull-request@v4 with: token: ${{ secrets.GH_TOKEN }} - commit-message: '🤖 style: update i18n' branch: 'style/auto-i18n' - delete-branch: true title: '🤖 style: update i18n' - body: 'This PR updates the i18n files.' + body: | + This PR updates the i18n files. + + ## 🔄 Update Strategy + ${{ steps.check_branch.outputs.branch_exists == 'true' && steps.rebase_attempt.outputs.rebase_success == 'true' && '✅ Successfully rebased existing branch onto latest main' || '' }} + ${{ steps.check_branch.outputs.branch_exists == 'true' && steps.rebase_attempt.outputs.rebase_success == 'false' && '🔄 Recreated branch due to rebase conflicts' || '' }} + ${{ steps.check_branch.outputs.branch_exists == 'false' && 'đŸŒŋ Created fresh branch from main' || '' }} + + ## 📝 Changes + - Auto-generated i18n updates + - Generated at: ${{ github.run_number }} + - Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC') + + ## 🤖 Automation Info + - Workflow: `${{ github.workflow }}` + - Run ID: `${{ github.run_id }}` + - Commit: `${{ github.sha }}` + + > This PR is automatically generated by GitHub Actions and kept up-to-date with the latest main branch. base: main + labels: | + i18n + automated + style - name: Check Pull Request Status - if: steps.check_branch.outputs.branch_exists != 0 && steps.git_status.outputs.changes_exist == 'true' + if: steps.git_status.outputs.changes_exist == 'true' run: | - if [ ${{ steps.create_pull_request.outputs.pull-request-number }} ]; then - echo "Pull request created successfully." + if [ "${{ steps.create_pr.outputs.pull-request-number }}" ]; then + echo "✅ Pull request #${{ steps.create_pr.outputs.pull-request-number }} created/updated successfully." + echo "🔗 PR URL: ${{ steps.create_pr.outputs.pull-request-url }}" else - echo "Failed to create pull request." + echo "❌ Failed to create/update pull request." exit 1 fi - name: No changes - if: steps.git_status.outputs.changes_exist != 'true' - run: echo "No changes to commit. Skipping PR creation." + if: steps.git_status.outputs.changes_exist != 'true' && steps.run_i18n.outputs.i18n_success == 'true' + run: echo "â„šī¸ No changes to commit. Skipping PR creation." + + # Set step status for issue creation + - name: Set step conclusions + if: always() + run: | + echo "STEP_VALIDATE_ENV=${{ steps.validate_env.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_CHECK_BRANCH=${{ steps.check_branch.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_REBASE_ATTEMPT=${{ steps.rebase_attempt.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_CREATE_BRANCH=${{ steps.create_branch.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_INSTALL_DEPS=${{ steps.install_deps.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_RUN_I18N=${{ steps.run_i18n.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_COMMIT_PUSH=${{ steps.commit_push.conclusion || 'Not run' }}" >> $GITHUB_ENV + echo "STEP_CREATE_PR=${{ steps.create_pr.conclusion || 'Not run' }}" >> $GITHUB_ENV + + # Error handling and issue creation + - name: Upload debug artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: debug-logs-${{ github.run_number }} + path: | + i18n_output.log + /tmp/*.log + retention-days: 7 + + - name: Create issue on failure + if: failure() + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const createFailureIssue = require('./.github/scripts/create-failure-issue.js'); + return await createFailureIssue({ github, context, core });