Add icon comment generation with categories and tags to PR validation workflow (#1444)

* Add 'generate-icons-comment' script to package.json and update PR validation workflow to include icon generation and commenting steps.

* Refactor icon comment generation to include inline SVGs in markdown tables and update workflow to handle output correctly.

* Refactor icon comment generation to use base64 data URIs for SVGs in markdown tables, improving compatibility and visual representation.

* Refactor icon comment generation to use GitHub raw file URLs instead of base64 data URIs, enhancing compatibility with GitHub comments.

* Update icon size in markdown table for improved visibility in GitHub comments.

* Enhance icon comment generation by adding category and tags to markdown table, improving organization and metadata visibility.

* Implement category validation for new icons and consolidate helper imports in icon generation scripts.

* Add workflow step to remove comment with added icons if no icons are present

* Remove unused SVG icon file 'a-b-2 copy 2.svg' to clean up the icon directory.

* Update PR validation workflow to change icon comment mode from 'recreate' to 'upsert', allowing for more efficient comment updates.

* Update icon name in markdown table to include file extension for clarity in generated comments.

* Remove unused SVG icon file 'a-b-2 copy.svg' to streamline the icon directory.
This commit is contained in:
Paweł Kuna 2025-12-23 01:59:33 +01:00 committed by GitHub
parent e7f40a1500
commit 6d5f43d993
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 192 additions and 3 deletions

View File

@ -0,0 +1,104 @@
import { execSync } from 'child_process'
import { basename, join } from 'path'
import { ICONS_SRC_DIR, parseMatter } from './helpers.mjs'
// Check icon files added relative to main branch (for PR)
function getAddedIconsFromMain() {
try {
const output = execSync('git diff origin/main...HEAD --name-status', { encoding: 'utf-8' })
const addedIcons = []
output.split('\n').forEach(line => {
if (line.startsWith('A\t')) {
const filePath = line.substring(2)
// Filter only SVG files from icons/outline/ or icons/filled/ directories
if (filePath.match(/^icons\/(outline|filled)\/.+\.svg$/)) {
const iconPath = filePath.replace(/^icons\//, '')
addedIcons.push(iconPath)
}
}
})
return addedIcons
} catch (error) {
// Fallback: check relative to HEAD if origin/main doesn't exist
try {
const output = execSync('git diff --diff-filter=A --name-only', { encoding: 'utf-8' })
const addedIcons = []
output.split('\n').forEach(filePath => {
if (filePath && filePath.match(/^icons\/(outline|filled)\/.+\.svg$/)) {
const iconPath = filePath.replace(/^icons\//, '')
addedIcons.push(iconPath)
}
})
return addedIcons
} catch {
return []
}
}
}
// Get GitHub raw file URL for icon
function getIconRawUrl(iconPath) {
const repo = process.env.GITHUB_REPOSITORY || 'tabler/tabler-icons'
const ref = process.env.GITHUB_HEAD_REF || process.env.GITHUB_SHA || 'main'
return `https://raw.githubusercontent.com/${repo}/${ref}/icons/${iconPath}`
}
// Generate markdown table for icons
function generateIconsTable(icons, type) {
if (icons.length === 0) {
return ''
}
const typeName = type === 'outline' ? 'Outline' : 'Filled'
let markdown = `### ${typeName} Icons (${icons.length})\n\n`
markdown += `| Icon | Name | Category | Tags |\n`
markdown += `|------|------|------|------|\n`
icons.forEach(iconPath => {
const iconName = basename(iconPath, '.svg')
const rawUrl = getIconRawUrl(iconPath)
const { data } = parseMatter(join(ICONS_SRC_DIR, iconPath))
const category = data.category || ''
const tags = data.tags || []
// Use GitHub raw file URL - GitHub Comments support external image URLs
markdown += `| <img src="${rawUrl}" width="240" height="240" alt="${iconName}" /> | \`${iconName}.svg\` | ${category || '❌ No category'} | ${tags.join(', ') || '❌ No tags' } |\n`
})
markdown += `\n`
return markdown
}
// Generate markdown comment with table of added icons
function generateIconsComment(icons) {
if (icons.length === 0) {
return ''
}
// Group icons by type (outline/filled) with full paths
const outlineIcons = icons.filter(icon => icon.startsWith('outline/'))
const filledIcons = icons.filter(icon => icon.startsWith('filled/'))
let markdown = `## 📦 Added Icons\n\n`
markdown += `This PR adds **${icons.length}** new icon${icons.length > 1 ? 's' : ''}.\n\n`
markdown += generateIconsTable(outlineIcons, 'outline')
markdown += generateIconsTable(filledIcons, 'filled')
return markdown
}
const addedIcons = getAddedIconsFromMain()
if (addedIcons.length > 0) {
const comment = generateIconsComment(addedIcons)
console.log(comment)
} else {
process.exit(0)
}

View File

@ -20,6 +20,50 @@ export const strokes = {
400: 2,
}
export const categories = [
'Animals',
'Arrows',
'Badges',
'Brand',
'Buildings',
'Charts',
'Communication',
'Computers',
'Currencies',
'Database',
'Design',
'Development',
'Devices',
'Document',
'E-commerce',
'Electrical',
'Extensions',
'Food',
'Games',
'Gender',
'Gestures',
'Health',
'Laundry',
'Letters',
'Logic',
'Map',
'Math',
'Media',
'Mood',
'Nature',
'Numbers',
'Photography',
'Shapes',
'Sport',
'Symbols',
'System',
'Text',
'Vehicles',
'Version control',
'Weather',
'Zodiac'
]
export const iconTemplate = (type) =>
type === 'outline'
? `<svg

View File

@ -1,7 +1,7 @@
import { globSync } from 'glob'
import fs from 'fs'
import { basename } from 'path'
import { HOME_DIR, ICONS_SRC_DIR, iconTemplate, parseMatter, types, getArgvs } from './helpers.mjs'
import { HOME_DIR, ICONS_SRC_DIR, iconTemplate, parseMatter, types, getArgvs, categories } from './helpers.mjs'
import { join } from 'path'
import { execSync } from 'child_process'
@ -216,6 +216,17 @@ for (const icon of addedIcons) {
error = true
}
// check if outline icon has category
if (icon.match(/^outline\//) && !data.category) {
console.log(`⛔️ New icon \`${icon}\` has no category`)
error = true
} else {
if (!categories.includes(data.category)) {
console.log(`⛔️ New icon \`${icon}\` has invalid category \`${data.category}\`. Valid categories are: ${categories.join(', ')}`)
error = true
}
}
// check if filled icon hasnt category
if (icon.match(/^filled\//) && data.category) {
console.log(`⛔️ New icon \`${icon}\` has category, but should not have it`)

View File

@ -61,4 +61,33 @@ jobs:
with:
file-path: ./comment-markup.md
comment-tag: validate
mode: recreate
mode: recreate
- name: Generate icons comment
id: generate-icons-comment
run: pnpm run --silent generate-icons-comment > ./comment-icons.md || true
continue-on-error: true
- name: Check if icons were added
id: check-icons
run: |
if [ -s ./comment-icons.md ]; then
echo "has_icons=true" >> $GITHUB_OUTPUT
else
echo "has_icons=false" >> $GITHUB_OUTPUT
fi
- name: Comment PR with added icons
if: steps.check-icons.outputs.has_icons == 'true'
uses: thollander/actions-comment-pull-request@v3
with:
file-path: ./comment-icons.md
comment-tag: added-icons
mode: upsert
- name: Remove comment with added icons
if: steps.check-icons.outputs.has_icons == 'false'
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: added-icons
mode: delete

0
comment-icons.md Normal file
View File

View File

@ -49,7 +49,8 @@
"build:webfont": "pnpm --filter @tabler/icons-webfont build",
"update-readme": "node ./.build/update-readme.mjs",
"zip": "node ./.build/zip-files.mjs",
"validate-pr": "node ./.build/validate-pr.mjs"
"validate-pr": "node ./.build/validate-pr.mjs",
"generate-icons-comment": "node ./.build/generate-icons-comment.mjs"
},
"devDependencies": {
"@11ty/eleventy": "^2.0.1",