mirror of
https://github.com/Cute-Dress/Dress.git
synced 2026-04-15 04:57:19 +00:00
[Enhancement] 添加自动修复工作流 (#337)
All checks were successful
Mark stale issues and pull requests / stale (push) Successful in 4s
All checks were successful
Mark stale issues and pull requests / stale (push) Successful in 4s
* feat: add GitHub Action to automatically strip EXIF data and compress images via PR comments * fix: 提升尝试压缩后修复失败的用户体验 * feat: 现在修复结果会直接基于源 comment 做 update * fix: 修复工作流仍然报告失败的问题 * doc: 添加自动修复引导 * ci: 改用 `github.rest.repos.compareCommitsWithBasehead` * ci: 增强检查工作流
This commit is contained in:
225
.github/workflows/auto_fix.yml
vendored
Normal file
225
.github/workflows/auto_fix.yml
vendored
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
name: Auto-fix Image Files
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-fix:
|
||||||
|
name: Auto-fix Images (EXIF & Size)
|
||||||
|
if: >
|
||||||
|
github.event.issue.pull_request &&
|
||||||
|
startsWith(github.event.comment.body, '/auto-fix') &&
|
||||||
|
(github.event.comment.author_association == 'OWNER' ||
|
||||||
|
github.event.comment.author_association == 'MEMBER' ||
|
||||||
|
github.event.comment.author_association == 'COLLABORATOR' ||
|
||||||
|
github.event.comment.user.login == github.event.issue.user.login)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Acknowledge command and start processing
|
||||||
|
id: init
|
||||||
|
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: "rocket"
|
||||||
|
});
|
||||||
|
const response = await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
body: "🚀 自动修复工作中...(Auto-fix initiated! Processing images...)"
|
||||||
|
});
|
||||||
|
core.setOutput("comment_id", response.data.id);
|
||||||
|
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Git identity and auth
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
gh auth setup-git
|
||||||
|
|
||||||
|
- name: Checkout PR branch
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh pr checkout ${{ github.event.issue.number }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends libimage-exiftool-perl imagemagick jpegoptim optipng pngquant
|
||||||
|
|
||||||
|
- name: Get list of changed image files
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh pr diff ${{ github.event.issue.number }} --name-only | grep -iE '\.(jpe?g|png|webp|tiff?|hei[cf]|avif|gif)$' > changed_images.txt || true
|
||||||
|
|
||||||
|
if [ ! -s changed_images.txt ]; then
|
||||||
|
echo "No images changed in this PR."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Changed images to process:"
|
||||||
|
cat changed_images.txt
|
||||||
|
|
||||||
|
- name: Process Phase 1 - Remove EXIF Data
|
||||||
|
run: |
|
||||||
|
if [ ! -s changed_images.txt ]; then exit 0; fi
|
||||||
|
|
||||||
|
exif_files_changed=0
|
||||||
|
while IFS= read -r file; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
count=$(exiftool -EXIF:all -S -q -- "$file" 2>/dev/null | wc -l || echo 0)
|
||||||
|
if [ "$count" -gt 0 ]; then
|
||||||
|
echo "Removing EXIF: $file"
|
||||||
|
exiftool -all= -overwrite_original "$file"
|
||||||
|
exif_files_changed=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < changed_images.txt
|
||||||
|
|
||||||
|
if [ "$exif_files_changed" -eq 1 ]; then
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: auto-remove EXIF data from images" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Process Phase 2 - Compress Oversized Images
|
||||||
|
id: compress
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
if [ ! -s changed_images.txt ]; then exit 0; fi
|
||||||
|
|
||||||
|
size_files_changed=0
|
||||||
|
failed_files=()
|
||||||
|
MAX_SIZE=$((1024 * 1024)) # 1MB Limit
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
|
||||||
|
if [ "$size" -gt "$MAX_SIZE" ]; then
|
||||||
|
echo "Compressing: $file ($size bytes)"
|
||||||
|
|
||||||
|
ext="${file##*.}"
|
||||||
|
ext_lower=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
case "$ext_lower" in
|
||||||
|
jpg|jpeg)
|
||||||
|
jpegoptim --size=990k "$file" || mogrify -quality 75 "$file"
|
||||||
|
;;
|
||||||
|
png)
|
||||||
|
# Use pngquant for highly effective lossy PNG compression first
|
||||||
|
pngquant --quality=60-80 --ext .png --force "$file" || optipng -o2 "$file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
mogrify -quality 75 "$file"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if file size is successfully reduced under 1MB
|
||||||
|
new_size=$(stat -c%s "$file" 2>/dev/null || echo 0)
|
||||||
|
if [ "$new_size" -gt "$MAX_SIZE" ]; then
|
||||||
|
echo "Failed to compress $file below 1MB (Current: $new_size bytes)"
|
||||||
|
failed_files+=("$file")
|
||||||
|
else
|
||||||
|
size_files_changed=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < changed_images.txt
|
||||||
|
|
||||||
|
if [ ${#failed_files[@]} -gt 0 ]; then
|
||||||
|
echo "Some files failed to compress below 1MB."
|
||||||
|
{
|
||||||
|
echo "failed=true"
|
||||||
|
echo "failed_list<<EOF"
|
||||||
|
for f in "${failed_files[@]}"; do echo "- $f"; done
|
||||||
|
echo "EOF"
|
||||||
|
} >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Revert changes to prevent pushing conflicted or partial states
|
||||||
|
git reset --hard
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$size_files_changed" -eq 1 ]; then
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: auto-compress oversized images" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Push Changes
|
||||||
|
id: push
|
||||||
|
if: steps.compress.outcome != 'failure'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if ! git diff --quiet @{u}...HEAD; then
|
||||||
|
echo "Pushing changes back to the PR branch..."
|
||||||
|
git push
|
||||||
|
else
|
||||||
|
echo "No changes made during processing."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Report Completion
|
||||||
|
if: always()
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const compressOutcome = "${{ steps.compress.outcome }}";
|
||||||
|
const failedList = `${{ steps.compress.outputs.failed_list }}`;
|
||||||
|
const commentId = `${{ steps.init.outputs.comment_id }}`;
|
||||||
|
|
||||||
|
let message = "";
|
||||||
|
if (compressOutcome === 'failure' && failedList.trim().length > 0) {
|
||||||
|
// Compression failed because some files couldn't be brought under 1MB
|
||||||
|
message = `❌ **自动修复未完全成功 (Auto-fix failed to compress some files).**
|
||||||
|
|
||||||
|
自动修复算法无法在保持分辨率的前提下,将以下文件压缩至 1MB 以内:
|
||||||
|
(The automated algorithm could not compress the following files strictly under 1MB without reducing their resolution:)
|
||||||
|
|
||||||
|
${failedList}
|
||||||
|
|
||||||
|
**如何手动修复 (How to fix manually):**
|
||||||
|
为了满足通过条件,请您在本地手动将这些图片尺寸缩小(缩小分辨率/长宽),或者转换为更高压缩率的格式后再次提交。
|
||||||
|
(Please manually scale down the images' resolution or convert them to a more efficient format and commit again.)
|
||||||
|
|
||||||
|
*请放心,为了防止冲突,本次自动化修改已被拦截,没有发起代码推送。*
|
||||||
|
*(The auto-fix push was aborted to prevent conflicts.)*`;
|
||||||
|
} else if (compressOutcome === 'failure') {
|
||||||
|
// Unexpected failure in the compress step
|
||||||
|
message = "❌ **自动修复失败 (Auto-fix failed).**\n请查看 [Actions Log](" + context.serverUrl + "/" + context.repo.owner + "/" + context.repo.repo + "/actions/runs/" + context.runId + ") 获取详情。(Please check the Actions Log for details.)";
|
||||||
|
} else {
|
||||||
|
// success or skipped — everything went fine
|
||||||
|
message = "✅ **自动修复成功 (Auto-fix completed successfully)!**\nEXIF 信息已被移除且文件已被压缩(如需要)。(EXIF data removed and files compressed if needed. Check the latest commits.)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commentId && commentId !== "undefined" && commentId !== "") {
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: parseInt(commentId, 10),
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
}
|
||||||
6
.github/workflows/check_exif.yml
vendored
6
.github/workflows/check_exif.yml
vendored
@@ -128,7 +128,11 @@ jobs:
|
|||||||
'**仅高敏感字段(GPS / 地址 / 联系方式)需要移除,普通摄影参数(光圈、快门等)无需处理。**',
|
'**仅高敏感字段(GPS / 地址 / 联系方式)需要移除,普通摄影参数(光圈、快门等)无需处理。**',
|
||||||
'**Only high-sensitivity fields (GPS / address / contact info) need to be removed. Regular photography parameters (aperture, shutter speed, etc.) are fine to keep.**',
|
'**Only high-sensitivity fields (GPS / address / contact info) need to be removed. Regular photography parameters (aperture, shutter speed, etc.) are fine to keep.**',
|
||||||
'',
|
'',
|
||||||
`📖 [EXIF 说明 / EXIF Guide](https://github.com/${owner}/${repo}/blob/master/EXIF.md) · [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/master/CONTRIBUTING.md)`
|
`📖 [EXIF 说明 / EXIF Guide](https://github.com/${owner}/${repo}/blob/master/EXIF.md) · [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/master/CONTRIBUTING.md)`,
|
||||||
|
'---',
|
||||||
|
'',
|
||||||
|
'<small>温馨提示: 您可以直接使用 `/auto-fix` 命令来自动修复这些文件。</small>',
|
||||||
|
'<small>Warm reminder: You can use the `/auto-fix` command to automatically fix these files.</small>'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
try { await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body }); }
|
try { await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body }); }
|
||||||
catch (e) { core.warning(`Cannot comment: ${e.message}. Check repo Settings > Actions > Workflow permissions.`); }
|
catch (e) { core.warning(`Cannot comment: ${e.message}. Check repo Settings > Actions > Workflow permissions.`); }
|
||||||
|
|||||||
41
.github/workflows/check_file_size.yml
vendored
41
.github/workflows/check_file_size.yml
vendored
@@ -23,30 +23,44 @@ jobs:
|
|||||||
const repo = context.repo.repo;
|
const repo = context.repo.repo;
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
|
|
||||||
let page = 1;
|
|
||||||
let oversized = [];
|
let oversized = [];
|
||||||
|
let page = 1;
|
||||||
|
let files = [];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = await github.rest.pulls.listFiles({
|
const compare = await github.rest.repos.compareCommitsWithBasehead({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
pull_number: prNumber,
|
basehead: `${pr.base.sha}...${pr.head.sha}`,
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
page
|
page
|
||||||
});
|
});
|
||||||
|
|
||||||
const files = response.data;
|
const pageFiles = compare.data.files || [];
|
||||||
if (files.length === 0) break;
|
if (pageFiles.length === 0) break;
|
||||||
|
|
||||||
for (const file of files) {
|
files.push(...pageFiles);
|
||||||
|
|
||||||
|
if (pageFiles.length < 100) break;
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
// 只检查新增或修改文件
|
// 只检查新增或修改文件
|
||||||
if (!["added","modified","renamed","copied"].includes(file.status))
|
if (!['added','modified','renamed','copied'].includes(file.status))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// 先编码 %,再编码 # 和 ?,避免双重编码问题。
|
||||||
|
const encodedPath = file.filename
|
||||||
|
.replace(/%/g, '%25')
|
||||||
|
.replace(/#/g, '%23')
|
||||||
|
.replace(/\?/g, '%3F');
|
||||||
|
|
||||||
const content = await github.rest.repos.getContent({
|
const content = await github.rest.repos.getContent({
|
||||||
owner: pr.head.repo.owner.login,
|
owner: pr.head.repo.owner.login,
|
||||||
repo: pr.head.repo.name,
|
repo: pr.head.repo.name,
|
||||||
path: file.filename,
|
path: encodedPath,
|
||||||
ref: pr.head.sha
|
ref: pr.head.sha
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,10 +69,10 @@ jobs:
|
|||||||
if (size > MAX_SIZE) {
|
if (size > MAX_SIZE) {
|
||||||
oversized.push(`- ${file.filename} (${size} bytes)`);
|
oversized.push(`- ${file.filename} (${size} bytes)`);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`Skip file ${file.filename}: ${error.message}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length < 100) break;
|
|
||||||
page++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oversized.length > 0) {
|
if (oversized.length > 0) {
|
||||||
@@ -67,6 +81,11 @@ jobs:
|
|||||||
We have noted that the following files exceed 1MB in size. Please resubmit your pull request after compressing them:
|
We have noted that the following files exceed 1MB in size. Please resubmit your pull request after compressing them:
|
||||||
|
|
||||||
${oversized.join("\n")}
|
${oversized.join("\n")}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>温馨提示: 您可以直接使用 `/auto-fix` 命令来自动修复这些文件。</small>
|
||||||
|
<small>Warm reminder: You can use the `/auto-fix` command to automatically fix these files.</small>
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
|
|||||||
Reference in New Issue
Block a user