mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-10 13:18:25 +00:00
feat(metadata|docs): reusable tag groups with $group: (#5037)
* feat(metadata): reusable tag groups with `$group:` A `$group:<group-name>` marker in `tags` is replaced in place with the tags of `groups/<group-name>.json`: a small set of tags for what a variant suffix means, such as `-minus` → remove, delete. - Read groups in `readGroups` and resolve markers next to `$extends:`, failing on unknown groups and on `$group:` outside `tags` - Add `group.schema.json` and lint, spell-check and format `groups/*.json` - `pnpm checkIcons` also fails on literals a group already provides, and warns about icons that list a group's tags by hand and unused groups - Add the `minus`, `plus` and `x` groups and use them in `alarm-clock-minus`, `file-minus`, `file-plus` and `file-x` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix group checker * Apply batched suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * feat(metadata): reusable tag groups with `$group:` A `$group:<group-name>` marker in `tags` is replaced in place with the tags of `groups/<group-name>.json`: a small set of tags for what a variant suffix means, such as `-minus` → remove, delete. - Read groups in `readGroups` and resolve markers next to `$extends:`, failing on unknown groups and on `$group:` outside `tags` - Add `group.schema.json` and lint, spell-check and format `groups/*.json` - `pnpm checkIcons` also fails on literals a group already provides, and warns about icons that list a group's tags by hand and unused groups - Add the `minus`, `plus` and `x` groups and use them in `alarm-clock-minus`, `file-minus`, `file-plus` and `file-x` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix group checker * Apply batched suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * feat(metadata): reusable tag groups with `$group:` A `$group:<group-name>` marker in `tags` is replaced in place with the tags of `groups/<group-name>.json`: a small set of tags for what a variant suffix means, such as `-minus` → remove, delete. - Read groups in `readGroups` and resolve markers next to `$extends:`, failing on unknown groups and on `$group:` outside `tags` - Add `group.schema.json` and lint, spell-check and format `groups/*.json` - `pnpm checkIcons` also fails on literals a group already provides, and warns about icons that list a group's tags by hand and unused groups - Add the `minus`, `plus` and `x` groups and use them in `alarm-clock-minus`, `file-minus`, `file-plus` and `file-x` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix group checker * Apply batched suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * feat(metadata): reusable tag groups with `$group:` A `$group:<group-name>` marker in `tags` is replaced in place with the tags of `groups/<group-name>.json`: a small set of tags for what a variant suffix means, such as `-minus` → remove, delete. - Read groups in `readGroups` and resolve markers next to `$extends:`, failing on unknown groups and on `$group:` outside `tags` - Add `group.schema.json` and lint, spell-check and format `groups/*.json` - `pnpm checkIcons` also fails on literals a group already provides, and warns about icons that list a group's tags by hand and unused groups - Add the `minus`, `plus` and `x` groups and use them in `alarm-clock-minus`, `file-minus`, `file-plus` and `file-x` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix group checker * Apply batched suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
parent
7bf67d3b1c
commit
831740411f
@@ -4,6 +4,7 @@ import {
|
||||
getCurrentDirPath,
|
||||
readAllMetadata,
|
||||
readMetadata,
|
||||
readGroups,
|
||||
} from '../tools/build-helpers/helpers.ts';
|
||||
import { type IconMetadata } from '../tools/build-icons/types.ts';
|
||||
|
||||
@@ -15,7 +16,7 @@ try {
|
||||
icons = await readAllMetadata(ICONS_DIR) as Record<string, IconMetadata>;
|
||||
} catch (error) {
|
||||
console.error(`Failed to read icon metadata: ${error instanceof Error ? error.message : String(error)}`);
|
||||
console.error('This may be due to invalid extends references or circular dependencies.');
|
||||
console.error('This may be due to invalid extends or group references, or circular dependencies.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -54,6 +55,7 @@ Object.keys(icons).forEach((iconName) => {
|
||||
});
|
||||
|
||||
const EXTENDS_PREFIX = '$extends:';
|
||||
const GROUP_PREFIX = '$group:';
|
||||
|
||||
const MARKED_FIELDS = ['tags', 'categories', 'contributors'] as const;
|
||||
type MarkedField = (typeof MARKED_FIELDS)[number];
|
||||
@@ -64,7 +66,11 @@ const FIELD_LABELS: Record<MarkedField, string> = {
|
||||
contributors: 'contributor',
|
||||
};
|
||||
|
||||
const isMarker = (entry: string) => entry.startsWith(EXTENDS_PREFIX);
|
||||
const isMarker = (entry: string) =>
|
||||
entry.startsWith(EXTENDS_PREFIX) || entry.startsWith(GROUP_PREFIX);
|
||||
|
||||
const groups = await readGroups();
|
||||
const usedGroups = new Set<string>();
|
||||
|
||||
// `readAllMetadata` expands the markers, so the duplicate checks below read the
|
||||
// JSON as it is written on disk to see which entries are literals.
|
||||
@@ -80,6 +86,13 @@ const rawIcons = Object.fromEntries(
|
||||
|
||||
/** The values a single marker contributes to `field`. */
|
||||
const resolveMarker = (marker: string, field: MarkedField): string[] => {
|
||||
if (marker.startsWith(GROUP_PREFIX)) {
|
||||
const groupName = marker.slice(GROUP_PREFIX.length);
|
||||
usedGroups.add(groupName);
|
||||
|
||||
return groups[groupName] ?? [];
|
||||
}
|
||||
|
||||
const parentName = marker.slice(EXTENDS_PREFIX.length);
|
||||
|
||||
return (icons[parentName]?.[field] as string[] | undefined) ?? [];
|
||||
@@ -113,6 +126,28 @@ Object.entries(rawIcons).forEach(([iconName, rawMetadata]) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Check if the icon has literal tags that could be replaced by group references.
|
||||
Object.entries(rawIcons).forEach(([iconName, rawMetadata]) => {
|
||||
const tags = rawMetadata.tags ?? [];
|
||||
const unreferencedGroups = Object.entries(groups).filter(
|
||||
([groupName]) => !tags.includes(`${GROUP_PREFIX}${groupName}`),
|
||||
);
|
||||
|
||||
tags
|
||||
.filter((entry) => !isMarker(entry))
|
||||
.forEach((tag) => {
|
||||
const matchingMarkers = unreferencedGroups
|
||||
.filter(([, groupTags]) => groupTags.includes(tag))
|
||||
.map(([groupName]) => `"${GROUP_PREFIX}${groupName}"`);
|
||||
|
||||
if (matchingMarkers.length > 0) {
|
||||
console.error(
|
||||
`Icon '${iconName}': tag '${tag}' is in ${matchingMarkers.join(', ')} — consider using the group instead.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(categories).forEach((categoryName) => {
|
||||
const category = categories[categoryName];
|
||||
if (!category?.icon) {
|
||||
@@ -124,6 +159,12 @@ Object.keys(categories).forEach((categoryName) => {
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(groups)
|
||||
.filter((groupName) => !usedGroups.has(groupName))
|
||||
.forEach((groupName) => {
|
||||
console.warn(`Group '${groupName}' is not used by any icon.`);
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('At least one error in icon JSONs prevents from committing changes.');
|
||||
process.exit(1);
|
||||
|
||||
@@ -8,7 +8,7 @@ import process from 'process';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const PATTERNS = ['icons/*.json', 'categories/*.json'];
|
||||
const PATTERNS = ['icons/*.json', 'categories/*.json', 'groups/*.json'];
|
||||
|
||||
// cspell only exports its bin for `import`, so resolve it the ESM way.
|
||||
const cspellBin = fileURLToPath(import.meta.resolve('cspell/bin.mjs'));
|
||||
|
||||
Reference in New Issue
Block a user