-
Notifications
You must be signed in to change notification settings - Fork 825
Split commands.yml into running and saving #18688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2e5bbfd
Split commands.yml into running and saving
T-Gro d6e9850
Update commands.yml
T-Gro 0ac4d25
Also download .NET9 for test execution
T-Gro b42cb70
Update CONTRIBUTING.md
T-Gro 2bae71d
Update CONTRIBUTING.md
T-Gro 1a962fd
Update CONTRIBUTING.md
T-Gro f450e8d
Update .github/workflows/commands.yml
T-Gro f4df0c6
Update .github/workflows/commands.yml
T-Gro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,131 +1,182 @@ | ||
name: Commands on PR | ||
name: Run CLI Commands via PR Comment | ||
|
||
on: | ||
issue_comment: | ||
types: [created] | ||
schedule: | ||
# once a day at 13:00 UTC | ||
- cron: '0 13 * * *' | ||
|
||
permissions: | ||
contents: write | ||
issues: write | ||
pull-requests: read | ||
|
||
jobs: | ||
cleanup_old_runs: | ||
if: github.event.schedule == '0 13 * * *' | ||
# This first job by definiton runs user-supplied code - you must NOT elevate its permissions to `write` | ||
# Malicious code could change nuget source URL, build targets or even compiler itself to pass a GH token | ||
# And use it to create branches, spam issues etc. Any write-actions happen in the second job, which does not allow | ||
# user extension points (i.e. plain scripts, must NOT run scripts from within checked-out code) | ||
detect-and-run: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
actions: write | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
outputs: | ||
command: ${{ steps.parse.outputs.command }} | ||
arg: ${{ steps.parse.outputs.arguments }} | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if: github.event.issue.pull_request | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
steps: | ||
- name: Delete old workflow runs | ||
run: | | ||
_UrlPath="/repos/$GITHUB_REPOSITORY/actions/workflows" | ||
_CurrentWorkflowID="$(gh api -X GET "$_UrlPath" | jq '.workflows[] | select(.name == '\""$GITHUB_WORKFLOW"\"') | .id')" | ||
- name: Parse comment | ||
id: parse | ||
uses: dotnet/comment-pipeline@1 | ||
with: | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
comment: ${{ toJSON(github.event.comment) }} | ||
commands: | | ||
/run fantomas | ||
/run ilverify | ||
/run xlf | ||
/run test-baseline | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
# delete workitems which are 'completed'. (other candidate values of status field are: 'queued' and 'in_progress') | ||
- name: Checkout the repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Checkout PR branch | ||
if: ${{ steps.parse.outputs.command }} | ||
run: gh auth setup-git && gh pr checkout ${{ github.event.issue.number }} | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
gh api -X GET "$_UrlPath/$_CurrentWorkflowID/runs" --paginate \ | ||
| jq '.workflow_runs[] | select(.status == "completed") | .id' \ | ||
| xargs -I{} gh api -X DELETE "/repos/$GITHUB_REPOSITORY/actions/runs"/{} | ||
- name: Install dotnet | ||
uses: actions/setup-dotnet@v3 | ||
with: | ||
global-json-file: global.json | ||
|
||
- name: Install dotnet tools | ||
run: dotnet tool restore | ||
|
||
- name: Setup .NET 9.0.0 Runtime for test execution | ||
if: ${{ steps.parse.outputs.command == '/run test-baseline' }} | ||
uses: actions/setup-dotnet@v4 | ||
with: | ||
dotnet-version: '9.0.x' | ||
|
||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
run_command: | ||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/run') | ||
- name: Run command | ||
id: run-cmd | ||
env: | ||
TEST_UPDATE_BSL: 1 | ||
continue-on-error: true | ||
run: | | ||
case "${{ steps.parse.outputs.command }}" in | ||
"/run fantomas") dotnet fantomas . ;; | ||
"/run xlf") dotnet build src/Compiler /t:UpdateXlf ;; | ||
"/run ilverify") pwsh tests/ILVerify/ilverify.ps1 ;; | ||
"/run test-baseline") dotnet test ./FSharp.Compiler.Service.sln --filter "${{ steps.parse.outputs.arguments }}" -c Release || true ;; | ||
*) echo "Unknown command" && exit 1 ;; | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
esac | ||
|
||
- name: Create patch & metadata | ||
id: meta | ||
if: steps.parse.outputs.command | ||
run: | | ||
echo "run_step_outcome=${{ steps.run-cmd.outcome }}" > result | ||
if [[ "${{ steps.run-cmd.outcome }}" == "success" ]]; then | ||
git diff > repo.patch || true | ||
if [ -s repo.patch ]; then echo "hasPatch=true" >> result; else echo "hasPatch=false" >> result; fi | ||
else | ||
echo "hasPatch=false" >> result | ||
fi | ||
cat result | ||
|
||
- name: Upload artifacts | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: cli-results | ||
path: | | ||
repo.patch | ||
result | ||
|
||
apply-and-report: | ||
needs: detect-and-run | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: write | ||
pull-requests: write | ||
if: needs.detect-and-run.outputs.command != '' | ||
steps: | ||
- name: Extract command to run | ||
uses: actions/github-script@v3 | ||
id: command-extractor | ||
with: | ||
result-encoding: string | ||
script: | | ||
if (context.eventName !== "issue_comment") throw "Error: This action only works on issue_comment events."; | ||
|
||
// extract the command to run, allowed characters: a-z, A-Z, digits, hyphen, underscore | ||
const regex = /^\/run ([a-zA-Z\d\-\_]+)/; | ||
command = regex.exec(context.payload.comment.body); | ||
if (command == null) throw "Error: No command found in the trigger phrase."; | ||
|
||
return command[1]; | ||
- name: Get github ref | ||
uses: actions/github-script@v3 | ||
id: get-pr | ||
with: | ||
script: | | ||
const result = await github.pulls.get({ | ||
pull_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
}); | ||
return { "ref": result.data.head.ref, "repository": result.data.head.repo.full_name}; | ||
- name: Checkout repo | ||
uses: actions/checkout@v2 | ||
with: | ||
repository: ${{ fromJson(steps.get-pr.outputs.result).repository }} | ||
ref: ${{ fromJson(steps.get-pr.outputs.result).ref }} | ||
fetch-depth: 0 | ||
- name: Install dotnet | ||
uses: actions/setup-dotnet@v3 | ||
with: | ||
global-json-file: global.json | ||
- name: Install dotnet tools | ||
run: dotnet tool restore | ||
- name: Process fantomas command | ||
if: steps.command-extractor.outputs.result == 'fantomas' | ||
id: fantomas | ||
run: dotnet fantomas . | ||
- name: Process xlf command | ||
if: steps.command-extractor.outputs.result == 'xlf' | ||
id: xlf | ||
run: dotnet build src/Compiler /t:UpdateXlf | ||
|
||
- name: Commit and push changes | ||
if: steps.fantomas.outcome == 'success' || steps.xlf.outcome == 'success' || steps.ilverify.outcome == 'success' | ||
run: | | ||
# Only commit if there are actual changes | ||
if git diff --quiet; then | ||
echo "No changes to commit, skipping." | ||
exit 0 | ||
fi | ||
|
||
git config --local user.name "github-actions[bot]" | ||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
git commit -a -m 'Automated command ran: ${{ steps.command-extractor.outputs.result }} | ||
|
||
Co-authored-by: ${{ github.event.comment.user.login }} <${{ github.event.comment.user.id }}+${{ github.event.comment.user.login }}@users.noreply.github.com>' | ||
git push origin HEAD:"refs/heads/$PR_HEAD_REF"\ | ||
- name: Post command comment | ||
if: steps.fantomas.outcome == 'success' || steps.xlf.outcome == 'success' || steps.ilverify.outcome == 'success' | ||
uses: actions/github-script@v3 | ||
with: | ||
script: | | ||
// Probably, there's more universal way of getting outputs, but my gh-actions-fu is not that good. | ||
var output = "" | ||
if ("${{steps.command-extractor.outputs.result}}" == 'fantomas') { | ||
output = "${{steps.fantomas.outputs.result}}" | ||
} else if ("${{steps.command-extractor.outputs.result}}" == 'xlf') { | ||
output = "${{steps.xlf.outputs.result}}" | ||
} else if ("${{steps.command-extractor.outputs.result}}" == 'ilverify') { | ||
output = "${{steps.ilverify.outputs.result}}" | ||
} | ||
const body = `Ran ${{ steps.command-extractor.outputs.result }}: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}\n${output}`; | ||
await github.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: body | ||
}); | ||
- name: Post command failed comment | ||
if: failure() | ||
uses: actions/github-script@v3 | ||
with: | ||
script: | | ||
const body = `Failed to run ${{ steps.command-extractor.outputs.result }}: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; | ||
await github.issues.createComment({ | ||
issue_number: context.issue.number, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: body | ||
}); | ||
- name: Checkout the repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Checkout PR branch | ||
run: gh auth setup-git && gh pr checkout ${{ github.event.issue.number }} | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Download artifacts | ||
uses: actions/download-artifact@v4 | ||
with: | ||
name: cli-results | ||
|
||
- name: Read metadata | ||
id: read-meta | ||
run: | | ||
source result | ||
echo "run_step_outcome=$run_step_outcome" >> $GITHUB_OUTPUT | ||
echo "hasPatch=$hasPatch" >> $GITHUB_OUTPUT | ||
|
||
- name: Apply and push patch | ||
if: ${{ steps.read-meta.outputs.run_step_outcome == 'success' && steps.read-meta.outputs.hasPatch == 'true' }} | ||
run: | | ||
patch -p1 -s --force < repo.patch || true | ||
git config user.name "GH Actions" | ||
git config user.email "actions@github.com" | ||
git add -u | ||
git commit -m "Apply patch from ${{ needs.detect-and-run.outputs.command }}" | ||
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name @{u}) | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
remote=${upstream%%/*} | ||
branch=${upstream#*/} | ||
|
||
echo "Pushing to $remote $branch" | ||
git push "$remote" HEAD:"$branch" | ||
|
||
- name: Count stats | ||
id: stats | ||
if: ${{ steps.read-meta.outputs.run_step_outcome == 'success' && steps.read-meta.outputs.hasPatch == 'true' }} | ||
run: | | ||
files=$(git diff --name-only HEAD~1 HEAD | wc -l) | ||
lines=$(git diff HEAD~1 HEAD | wc -l) | ||
echo "files=$files" >> $GITHUB_OUTPUT | ||
echo "lines=$lines" >> $GITHUB_OUTPUT | ||
- name: Generate and publish report | ||
if: always() | ||
env: | ||
COMMAND: ${{ needs.detect-and-run.outputs.command }} | ||
OUTCOME: ${{ steps.read-meta.outputs.run_step_outcome }} | ||
PATCH: ${{ steps.read-meta.outputs.hasPatch }} | ||
run: | | ||
# Build the markdown report | ||
report=" | ||
# 🔧 CLI Command Report | ||
|
||
- **Command:** \`${COMMAND}\` | ||
- **Outcome:** ${OUTCOME} | ||
|
||
" | ||
|
||
if [[ "$OUTCOME" == "success" ]]; then | ||
if [[ "$PATCH" == "true" ]]; then | ||
report+="✅ Patch applied: | ||
- Files changed: ${{ steps.stats.outputs.files }} | ||
- Lines changed: ${{ steps.stats.outputs.lines }}" | ||
else | ||
report+="✅ Command succeeded, no changes needed." | ||
fi | ||
else | ||
report+="❌ Command **failed** — no patch applied." | ||
fi | ||
|
||
# Output to GitHub Actions UI | ||
echo "$report" >> "$GITHUB_STEP_SUMMARY" | ||
|
||
# Store for use in next step | ||
echo "$report" > pr_report.md | ||
|
||
- name: Comment on PR | ||
if: always() | ||
env: | ||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
PR_NUMBER: ${{ env.PR_NUMBER }} | ||
run: | | ||
# Use gh CLI to comment with multi-line markdown | ||
gh pr comment ${{ github.event.issue.number }} \ | ||
--body-file pr_report.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.