diff --git a/.github/workflows/fix-completions-on-comment.yml b/.github/workflows/fix-completions-on-comment.yml new file mode 100644 index 000000000000..d8c0a403c168 --- /dev/null +++ b/.github/workflows/fix-completions-on-comment.yml @@ -0,0 +1,193 @@ +name: Fix completion snapshots on command + +on: + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + fix-completions: + # Only run on PR comments that start with /fixcompletions or /completions + if: github.event.issue.pull_request && + (startsWith(github.event.comment.body, '/fixcompletions') || startsWith(github.event.comment.body, '/completions')) + runs-on: ubuntu-latest + + steps: + - name: React to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Comment on PR - Started + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `▶️ CLI completion snapshot update started. Track progress in [this workflow run](${runUrl}).` + }); + + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Checkout PR branch + run: | + gh pr checkout ${{ github.event.issue.number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build repository + id: build + run: | + chmod +x ./build.sh + ./build.sh + continue-on-error: true + timeout-minutes: 15 + + - name: Run completion tests + id: test + if: steps.build.outcome == 'success' + run: | + # Use the repo-local dotnet that was built + ./.dotnet/dotnet test test/dotnet.Tests/dotnet.Tests.csproj --filter "FullyQualifiedName~VerifyCompletions" + continue-on-error: true + timeout-minutes: 10 + + - name: Compare snapshots + id: compare + if: steps.test.outcome != 'skipped' + run: | + # Use repo-local dotnet so we only restore the project we need on the runner + ./.dotnet/dotnet msbuild test/dotnet.Tests/dotnet.Tests.csproj -restore -t:CompareCliSnapshots + continue-on-error: true + + - name: Check for snapshot changes + id: check-changes + if: steps.compare.outcome == 'success' + run: | + # Detect new received files (ignored by git) and diff-ed verified files + shopt -s nullglob + received_files=(test/dotnet.Tests/CompletionTests/snapshots/**/*.received.*) + shopt -u nullglob + + diff_output=$(git diff --name-only -- test/dotnet.Tests/CompletionTests/snapshots/ | grep -E '\.verified\.' || true) + + if [ ${#received_files[@]} -gt 0 ] || [ -n "$diff_output" ]; then + echo "changes=true" >> $GITHUB_OUTPUT + echo "Changed snapshot files:" + printf '%s\n' "${received_files[@]}" + if [ -n "$diff_output" ]; then + echo "$diff_output" + fi + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Update verified snapshots + id: update + if: steps.check-changes.outputs.changes == 'true' + run: | + # This renames .received.* files to .verified.* + ./.dotnet/dotnet msbuild test/dotnet.Tests/dotnet.Tests.csproj -restore -t:UpdateCliSnapshots + continue-on-error: true + + - name: Commit and push snapshot changes + id: commit + if: steps.update.outcome == 'success' + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + # Add snapshot files + git add test/dotnet.Tests/CompletionTests/snapshots/ + + # Create commit + COMMIT_DATE=$(date -u +"%Y-%m-%d") + git commit -m "Update CLI completion snapshots - $COMMIT_DATE" + + # Push to the PR branch + git push + continue-on-error: true + + - name: Comment on PR - Success + if: steps.commit.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `✅ CLI completion snapshots have been updated and committed to this PR. See [workflow details](${runUrl}).` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + - name: Comment on PR - No changes + if: steps.compare.outcome == 'success' && steps.check-changes.outputs.changes == 'false' + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `ℹ️ No completion snapshot files needed to be updated. Review [the workflow run](${runUrl}).` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + - name: Comment on PR - Failure + if: steps.build.outcome == 'failure' || (steps.test.outcome == 'failure' && steps.compare.outcome != 'success') || (steps.check-changes.outputs.changes == 'true' && (steps.update.outcome == 'failure' || steps.commit.outcome == 'failure')) + uses: actions/github-script@v7 + with: + script: | + let errorMsg = '❌ Failed to update completion snapshots.'; + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + + if ('${{ steps.build.outcome }}' === 'failure') { + errorMsg += ' The build failed.'; + } else if ('${{ steps.update.outcome }}' === 'failure') { + errorMsg += ' Could not update snapshot files.'; + } else if ('${{ steps.commit.outcome }}' === 'failure') { + errorMsg += ' Could not commit changes.'; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `${errorMsg} Please check [the workflow run](${runUrl}) for details.` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused' + }); diff --git a/.github/workflows/update-xlf-on-comment.yml b/.github/workflows/update-xlf-on-comment.yml new file mode 100644 index 000000000000..a1dcb139345d --- /dev/null +++ b/.github/workflows/update-xlf-on-comment.yml @@ -0,0 +1,168 @@ +name: Update XLF files on command + +on: + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + update-xlf: + # Only run on PR comments that start with /updatexlf or /xlf + if: github.event.issue.pull_request && + (startsWith(github.event.comment.body, '/updatexlf') || startsWith(github.event.comment.body, '/xlf')) + runs-on: ubuntu-latest + + steps: + - name: React to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Comment on PR - Started + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `▶️ XLF update workflow started. Track progress in [this workflow run](${runUrl}).` + }); + + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Checkout PR branch + run: | + gh pr checkout ${{ github.event.issue.number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run UpdateXlf target + id: update + run: | + chmod +x ./build.sh + # Try the fast UpdateXlf target first + if ./build.sh /t:UpdateXlf; then + echo "result=success" >> $GITHUB_OUTPUT + else + # If that fails, try a full build + if ./build.sh; then + echo "result=success" >> $GITHUB_OUTPUT + else + echo "result=failure" >> $GITHUB_OUTPUT + exit 1 + fi + fi + continue-on-error: true + timeout-minutes: 15 + + - name: Check for XLF changes + id: check-changes + if: steps.update.outcome == 'success' + run: | + # Check if there are changes to .xlf files + if git diff --name-only | grep -E '\.xlf$'; then + echo "changes=true" >> $GITHUB_OUTPUT + echo "Changed XLF files:" + git diff --name-only | grep -E '\.xlf$' + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Commit and push XLF changes + id: commit + if: steps.update.outcome == 'success' && steps.check-changes.outputs.changes == 'true' + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + # Add all .xlf files + git add **/*.xlf + + # Create commit + COMMIT_DATE=$(date -u +"%Y-%m-%d") + git commit -m "Update XLF translation files - $COMMIT_DATE" + + # Push to the PR branch + git push + continue-on-error: true + + - name: Comment on PR - Success + if: steps.commit.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `✅ XLF translation files have been updated and committed to this PR. See [workflow details](${runUrl}).` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + - name: Comment on PR - No changes + if: steps.update.outcome == 'success' && steps.check-changes.outputs.changes == 'false' + uses: actions/github-script@v7 + with: + script: | + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `ℹ️ No XLF files needed to be updated. Review [the workflow run](${runUrl}).` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + - name: Comment on PR - Failure + if: steps.update.outcome == 'failure' || (steps.check-changes.outputs.changes == 'true' && steps.commit.outcome == 'failure') + uses: actions/github-script@v7 + with: + script: | + let errorMsg = '❌ Failed to update XLF files.'; + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + + if ('${{ steps.update.outcome }}' === 'failure') { + errorMsg += ' The build failed.'; + } else if ('${{ steps.commit.outcome }}' === 'failure') { + errorMsg += ' Could not commit changes.'; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `${errorMsg} Please check [the workflow run](${runUrl}) for details.` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused' + }); diff --git a/documentation/project-docs/Localization.md b/documentation/project-docs/Localization.md index 884c9fa075a3..51fde9703637 100644 --- a/documentation/project-docs/Localization.md +++ b/documentation/project-docs/Localization.md @@ -7,6 +7,17 @@ The local dev build automatically generates updates to the xlf files that contai When making string changes, update the resx, build, and check in all xlf file changes. Developers should never need to update the xlf files directly and should always rely on the local build for updates to those files. This will leave the files in english initially and they will get translated eventually. +#### Automated XLF Updates via GitHub Actions + +If you've modified `.resx` files in a pull request and need to update the corresponding `.xlf` files but don't want to clone the branch locally, you can use the automated GitHub Action: + +**Comment `/updatexlf` on your pull request** and the workflow will: +1. Check out your PR branch +2. Run the UpdateXlf build target +3. Commit any updated `.xlf` files directly to your PR branch + +This is particularly useful when CI is failing due to outdated XLF files. + For internal folks, see https://aka.ms/allaboutloc ### Loc issues diff --git a/documentation/project-docs/developer-guide.md b/documentation/project-docs/developer-guide.md index b423df5b4dbd..e1bd2e70a080 100644 --- a/documentation/project-docs/developer-guide.md +++ b/documentation/project-docs/developer-guide.md @@ -176,6 +176,42 @@ taskkill /F /IM VSTest.Console.exe /T || taskkill /F /IM msbuild.exe /T ``` +## Automated PR Maintenance Commands + +The SDK repository includes GitHub Actions workflows that automate common maintenance tasks directly from pull requests. + +### `/xlf or /updatexlf` - Update Translation Files + +When you modify `.resx` resource files, the corresponding `.xlf` translation files need to be updated. Instead of manually running the build locally, comment `/updatexlf` on the PR and the GitHub Action will: + +1. Check out the PR branch +2. Run `./build.sh /t:UpdateXlf` (or full build if needed) +3. Commit any updated `.xlf` files directly to the PR branch + +This is useful when you've changed localized strings and the CI build is failing due to outdated XLF files. + +See also: [Localization documentation](Localization.md) + +### `/completions or /fixcompletions` - Update CLI Completion Snapshots + +The CLI includes snapshot-based tests for shell completions (bash, zsh, pwsh, etc.). When you add or modify CLI commands, these snapshots need to be updated. Comment `/fixcompletions` on the PR and the GitHub Action will: + +1. Build the repository +2. Run the completion tests +3. Update the verified snapshot files +4. Commit the changes directly to the PR branch + +This is useful when you've added new commands or options and the completion snapshot tests are failing. + +See also: [Snapshot-based testing documentation](snapshot-based-testing.md) + + +### `/backport to {branch}` + +The SDK team manages many branches. +You may use /backport to {branch_name} to mimic the process of cherry-picking a PR onto another branch. + + ## Adding a Command The dotnet CLI supports several models for adding new commands: diff --git a/documentation/project-docs/snapshot-based-testing.md b/documentation/project-docs/snapshot-based-testing.md index eb282a9cb174..c423941cca30 100644 --- a/documentation/project-docs/snapshot-based-testing.md +++ b/documentation/project-docs/snapshot-based-testing.md @@ -13,6 +13,18 @@ To fix these tests, you need to diff the two files and visually inspect the chan * [CompareCliSnapshots][compare] - this Target copies the .received. files from the artifacts directory, where they are created due to the way we run tests, to the [snapshots][snapshots] directory in the dotnet.Tests project. This makes it much easier to diff the two. * [UpdateCliSnapshots][update] - this Target renames the .received. files to .verified. in the local [snapshots][snapshots] directory, and so acts as a giant 'I accept these changes' button. Only use this if you've diffed the snapshots and are sure they match your expectations. +### Automated Snapshot Updates via GitHub Actions + +If you've modified CLI commands in a pull request and the completion snapshot tests are failing, but you don't want to clone the branch locally to update the snapshots, you can use the automated GitHub Action: + +**Comment `/fixcompletions` on your pull request** and the workflow will: +1. Build the repository +2. Run the completion tests to generate new snapshots +3. Run the `CompareCliSnapshots` and `UpdateCliSnapshots` targets +4. Commit the updated verified snapshot files directly to your PR branch + +This is particularly useful when you've added new commands, options, or modified existing CLI functionality. + [dotnet.Tests]: ../../test/dotnet.Tests/ [snapshot-tests]: ../../test/dotnet.Tests/CompletionTests/DotnetCliSnapshotTests.cs [snapshots]: ../../test/dotnet.Tests/CompletionTests/snapshots/