Skip to content

Commit b66a5ba

Browse files
author
qdnx
committed
Add GitHub Actions to enforce and normalize image filenames
- check-pr-filenames.yml: flags PR-added images whose filenames don't match the Name_Name_Book_Book.ext convention and comments the expected rename, failing the check until fixed. - normalize-filenames.yml: on push to master (or manual dispatch), renames non-conforming image files in place and commits the fix.
1 parent c04a6e1 commit b66a5ba

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Check image filename format
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, synchronize, reopened]
6+
7+
permissions:
8+
pull-requests: write
9+
contents: read
10+
11+
jobs:
12+
check-filenames:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Check added/renamed image filenames
16+
uses: actions/github-script@v7
17+
with:
18+
script: |
19+
const path = require('path');
20+
21+
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
22+
const MARKER = '<!-- filename-check-bot -->';
23+
24+
function normalizeBase(base) {
25+
const tokens = base.split(/[\s_-]+/).filter(Boolean);
26+
const normalized = tokens.map(t =>
27+
/^[a-z]+$/.test(t) ? t[0].toUpperCase() + t.slice(1) : t
28+
);
29+
return normalized.join('_');
30+
}
31+
32+
function expectedPath(filename) {
33+
const ext = path.extname(filename);
34+
const dir = path.dirname(filename);
35+
const base = path.basename(filename, ext);
36+
const normalized = normalizeBase(base) + ext.toLowerCase();
37+
return dir === '.' ? normalized : `${dir}/${normalized}`;
38+
}
39+
40+
const files = await github.paginate(github.rest.pulls.listFiles, {
41+
owner: context.repo.owner,
42+
repo: context.repo.repo,
43+
pull_number: context.payload.pull_request.number,
44+
});
45+
46+
const violations = [];
47+
for (const f of files) {
48+
if (!['added', 'renamed'].includes(f.status)) continue;
49+
if (!IMAGE_EXT.test(f.filename)) continue;
50+
const dir = path.dirname(f.filename);
51+
if (dir === '.' || dir.startsWith('.github')) continue;
52+
53+
const expected = expectedPath(f.filename);
54+
if (expected !== f.filename) {
55+
violations.push({ actual: f.filename, expected });
56+
}
57+
}
58+
59+
const { data: comments } = await github.rest.issues.listComments({
60+
owner: context.repo.owner,
61+
repo: context.repo.repo,
62+
issue_number: context.payload.pull_request.number,
63+
});
64+
const existing = comments.find(c => c.body.includes(MARKER));
65+
66+
if (violations.length > 0) {
67+
const lines = violations.map(v => `- \`${v.actual}\` → \`${v.expected}\``);
68+
const body = `${MARKER}\n### Some image filenames don't match the expected format\n\nExpected format: \`Name_Name_Book_Book.ext\` (words separated by underscores, each word capitalized).\n\nPlease rename:\n\n${lines.join('\n')}\n`;
69+
70+
if (existing) {
71+
await github.rest.issues.updateComment({
72+
owner: context.repo.owner,
73+
repo: context.repo.repo,
74+
comment_id: existing.id,
75+
body,
76+
});
77+
} else {
78+
await github.rest.issues.createComment({
79+
owner: context.repo.owner,
80+
repo: context.repo.repo,
81+
issue_number: context.payload.pull_request.number,
82+
body,
83+
});
84+
}
85+
86+
core.setFailed(`${violations.length} filename(s) don't match the expected format.`);
87+
} else if (existing) {
88+
await github.rest.issues.updateComment({
89+
owner: context.repo.owner,
90+
repo: context.repo.repo,
91+
comment_id: existing.id,
92+
body: `${MARKER}\n### All image filenames now match the expected format.\n`,
93+
});
94+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Normalize image filenames
2+
3+
on:
4+
push:
5+
branches: [master]
6+
paths:
7+
- '**/*.png'
8+
- '**/*.jpg'
9+
- '**/*.jpeg'
10+
- '**/*.gif'
11+
- '**/*.webp'
12+
- '**/*.bmp'
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: write
17+
18+
jobs:
19+
normalize:
20+
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip normalize]') }}
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Rename non-conforming image files
26+
run: |
27+
node <<'EOF'
28+
const fs = require('fs');
29+
const path = require('path');
30+
31+
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp)$/i;
32+
const EXCLUDED_DIRS = new Set(['.git', '.github', 'node_modules']);
33+
34+
function normalizeBase(base) {
35+
const tokens = base.split(/[\s_-]+/).filter(Boolean);
36+
const normalized = tokens.map(t =>
37+
/^[a-z]+$/.test(t) ? t[0].toUpperCase() + t.slice(1) : t
38+
);
39+
return normalized.join('_');
40+
}
41+
42+
const topLevel = fs.readdirSync('.', { withFileTypes: true })
43+
.filter(e => e.isDirectory() && !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name));
44+
45+
const renamed = [];
46+
for (const dirEntry of topLevel) {
47+
const dir = dirEntry.name;
48+
const entries = fs.readdirSync(dir, { withFileTypes: true });
49+
for (const entry of entries) {
50+
if (!entry.isFile()) continue;
51+
if (!IMAGE_EXT.test(entry.name)) continue;
52+
53+
const ext = path.extname(entry.name);
54+
const base = path.basename(entry.name, ext);
55+
const normalizedName = normalizeBase(base) + ext.toLowerCase();
56+
57+
if (normalizedName === entry.name) continue;
58+
59+
const oldPath = path.join(dir, entry.name);
60+
const newPath = path.join(dir, normalizedName);
61+
62+
if (fs.existsSync(newPath)) {
63+
console.warn(`Skipping ${oldPath}: target ${newPath} already exists`);
64+
continue;
65+
}
66+
67+
fs.renameSync(oldPath, newPath);
68+
renamed.push(`${oldPath} -> ${newPath}`);
69+
}
70+
}
71+
72+
if (renamed.length > 0) {
73+
console.log('Renamed files:');
74+
renamed.forEach(r => console.log(` ${r}`));
75+
} else {
76+
console.log('No files needed renaming.');
77+
}
78+
EOF
79+
80+
- name: Commit and push if changed
81+
run: |
82+
git config user.name "github-actions[bot]"
83+
git config user.email "github-actions[bot]@users.noreply.github.com"
84+
git add -A
85+
if git diff --cached --quiet; then
86+
echo "Nothing to commit."
87+
else
88+
git commit -m "chore: normalize image filenames [skip normalize]"
89+
git push
90+
fi

0 commit comments

Comments
 (0)