11name :Format Files
22
3- # cpp
4- # csharp - https://github.com/dotnet/format ?
5- # java - prettier-plugin-java
6- # javascript - prettier
7- # python - black
8- # ruby - rufo
9- # swift - https://github.com/apple/swift-format ?
10- # typescript - prettier
11-
123on :
134workflow_dispatch :
14- pull_request_target :
5+ pull_request :
156types :[opened, synchronize, reopened]
167
8+ permissions :
9+ contents :write # write needed only for same-repo pushes; forks get read-only token automatically
10+
1711jobs :
18- Format :
12+ format :
1913runs-on :ubuntu-latest
2014
21- strategy :
22- matrix :
23- node-version :[20.x]
24-
2515steps :
26- -name :CheckoutRepository
16+ -name :CheckoutPR HEAD
2717uses :actions/checkout@v4
2818with :
29- ref :${{ github.head_ref }}
30- fetch-depth :1
19+ repository :${{ github.event.pull_request.head.repo.full_name }}
20+ ref :${{ github.event.pull_request.head.sha }}
21+ fetch-depth :0
22+
23+ -name :Detect fork vs same-repo
24+ id :ctx
25+ run :|
26+ if [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then
27+ echo "same_repo=true" >> $GITHUB_OUTPUT
28+ else
29+ echo "same_repo=false" >> $GITHUB_OUTPUT
30+ fi
3131
32- -name :Use Node.js (dependency)
32+ -name :Use Node.js
3333uses :actions/setup-node@v4
3434with :
35- node-version :${{ matrix.node-version }}
35+ node-version :20
3636
37- -name :InstallRufo (dependency)
37+ -name :Installformatters
3838run :|
39- sudo apt update
39+ sudo apt-get update
4040 sudo gem install rufo
41-
42- -name :Install Prettier
43- run :|
44- npm install -g prettier
45- npm install -g prettier-plugin-java
46-
41+ npm i -g prettier prettier-plugin-java
4742 -name :Install Black
4843uses :BSFishy/pip-action@v1
4944with :
5045packages :black
5146
52- -name :Format
47+ -name :Run formatters
48+ run :|
49+ set -e
50+ # Use globs; .prettierrc discovered from repo root
51+ prettier --write "javascript/*.js" ||true
52+ prettier --write "typescript/*.ts" ||true
53+ prettier --write "java/*.java" ||true
54+ rufo ruby ||true
55+ python -m black . ||true
56+
57+ -name :Check for changes
58+ id :diff
59+ run :|
60+ git status --porcelain
61+ if [ -n "$(git status --porcelain)" ]; then
62+ echo "changed=true" >> $GITHUB_OUTPUT
63+ else
64+ echo "changed=false" >> $GITHUB_OUTPUT
65+ fi
66+
67+ # Auto-push only for same-repo PRs
68+ -name :Push formatting changes (same-repo only)
69+ if :steps.ctx.outputs.same_repo == 'true' && steps.diff.outputs.changed == 'true'
70+ run :|
71+ git config user.email "71089234+Ahmad-A0@users.noreply.github.com"
72+ git config user.name "Bot-A0"
73+ git add -A
74+ git commit -m "🎨 Format files (🛠️ from GitHub Actions)"
75+ git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.event.pull_request.head.repo.full_name }}.git" \
76+ HEAD:refs/heads/${{ github.event.pull_request.head.ref }}
77+ env :
78+ GITHUB_TOKEN :${{ secrets.GITHUB_TOKEN }}
79+
80+ # For forks, upload a patch artifact the author can apply locally: `git apply formatting.patch`
81+ -name :Create patch (forks only)
82+ if :steps.ctx.outputs.same_repo == 'false' && steps.diff.outputs.changed == 'true'
5383run :|
54- prettier --config "$GITHUB_WORKSPACE/.prettierrc" --write "$GITHUB_WORKSPACE/javascript/*.js" 2>&1 ||true
55- prettier --config "$GITHUB_WORKSPACE/.prettierrc" --write "$GITHUB_WORKSPACE/typescript/*.ts" 2>&1 ||true
56- prettier --config "$GITHUB_WORKSPACE/.prettierrc" --write "$GITHUB_WORKSPACE/java/*.java" 2>&1 ||true
57- rufo "$GITHUB_WORKSPACE/ruby" 2>&1 ||true
58- python -m black "$GITHUB_WORKSPACE/" 2>&1 ||true
84+ git diff > formatting.patch
85+ ls -l formatting.patch
86+
87+ -name :Upload patch artifact (forks only)
88+ if :steps.ctx.outputs.same_repo == 'false' && steps.diff.outputs.changed == 'true'
89+ uses :actions/upload-artifact@v4
90+ with :
91+ name :formatting.patch
92+ path :formatting.patch
93+ if-no-files-found :error
94+ retention-days :7
95+
96+ # Leave a comment with instructions on fork PRs
97+ -name :Comment instructions (forks only)
98+ if :steps.ctx.outputs.same_repo == 'false' && steps.diff.outputs.changed == 'true'
99+ uses :actions/github-script@v7
100+ with :
101+ script :|
102+ const body = [
103+ "I ran the formatters and found changes. Since this is a forked PR, I can't push to your branch.",
104+ "",
105+ "**How to apply the patch:**",
106+ "```bash",
107+ "curl -L -o formatting.patch \"$ACTIONS_ARTIFACT_URL\" # Download from the job's Artifacts section",
108+ "git apply formatting.patch",
109+ "git commit -am \"Apply formatting patch\"",
110+ "git push",
111+ "```",
112+ "",
113+ "Alternatively, run the same formatters locally: `prettier`, `rufo`, and `black`."
114+ ].join("\n");
115+ github.rest.issues.createComment({
116+ owner: context.repo.owner,
117+ repo: context.repo.repo,
118+ issue_number: context.payload.pull_request.number,
119+ body
120+ });
59121
60- -name :Push
122+ # Optional: make CI fail if formatting changes were needed (forces author to accept fixes or patch)
123+ -name :Fail if formatting changes are needed
124+ if :steps.diff.outputs.changed == 'true' && steps.ctx.outputs.same_repo == 'false'
61125run :|
62- git config --global user.email "71089234+Ahmad-A0@users.noreply.github.com"
63- git config --global user.name "Bot-A0"
64- git add .
65- git commit -m "🎨 Format files (🛠️ from Github Actions)" ||true
66- git push ||true
126+ echo "Formatting changes required. Patch uploaded as an artifact."
127+ exit 1