@gtk-js/icon-helpers 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +4 -1
- package/src/build-helpers.ts +62 -32
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/build-helpers.ts
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared helpers for building GTK icon packages.
|
|
3
|
-
* Used by
|
|
3
|
+
* Used by all icon packages under icons/.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { mkdirSync, readdirSync, statSync } from "fs";
|
|
7
7
|
|
|
8
|
-
// Attributes to strip (GTK/namespace-specific)
|
|
8
|
+
// Attributes to strip (GTK/namespace-specific or invalid as React props)
|
|
9
9
|
const STRIP_ATTR_PREFIXES = ["gpa:", "xmlns:", "xml:", "cc:", "dc:", "rdf:"];
|
|
10
|
-
|
|
10
|
+
// style: CSS strings aren't valid React style props (React requires objects)
|
|
11
|
+
// filter/enableBackground/color: reference SVG defs or are non-standard SVG attrs
|
|
12
|
+
const STRIP_ATTRS = new Set([
|
|
13
|
+
"id",
|
|
14
|
+
"class",
|
|
15
|
+
"xmlns",
|
|
16
|
+
"style",
|
|
17
|
+
"filter",
|
|
18
|
+
"enableBackground",
|
|
19
|
+
"color",
|
|
20
|
+
]);
|
|
11
21
|
|
|
12
22
|
export function toPascalCase(str: string): string {
|
|
13
23
|
return str
|
|
14
24
|
.replace(/-symbolic$/, "")
|
|
15
|
-
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
25
|
+
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
16
26
|
.split(/[-_]+/)
|
|
17
27
|
.filter(Boolean)
|
|
18
28
|
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
@@ -22,9 +32,10 @@ export function toPascalCase(str: string): string {
|
|
|
22
32
|
export function toKebabCase(str: string): string {
|
|
23
33
|
return str
|
|
24
34
|
.replace(/-symbolic$/, "")
|
|
25
|
-
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
26
|
-
.replace(/[-_]+/g, "-")
|
|
27
|
-
.replace(/^-|-$/g, "")
|
|
35
|
+
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
36
|
+
.replace(/[-_]+/g, "-")
|
|
37
|
+
.replace(/^-|-$/g, "")
|
|
38
|
+
.toLowerCase(); // normalize case so mixed-case duplicates are caught
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
interface SvgChild {
|
|
@@ -52,13 +63,11 @@ export function parseSvgChildren(svg: string): SvgChild[] {
|
|
|
52
63
|
if (STRIP_ATTRS.has(name)) continue;
|
|
53
64
|
if (STRIP_ATTR_PREFIXES.some((p) => name.startsWith(p))) continue;
|
|
54
65
|
|
|
55
|
-
// Normalize fill colors to currentColor
|
|
56
66
|
if (name === "fill" && value !== "none") {
|
|
57
67
|
attrs[name] = "currentColor";
|
|
58
68
|
continue;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
// Normalize stroke to currentColor
|
|
62
71
|
if (name === "stroke" && value !== "none") {
|
|
63
72
|
attrs[name] = "currentColor";
|
|
64
73
|
continue;
|
|
@@ -99,48 +108,69 @@ export function findSvgFiles(dir: string): string[] {
|
|
|
99
108
|
* @param iconsDir - directory containing SVG files (can be nested)
|
|
100
109
|
* @param outDir - output directory for generated .ts files
|
|
101
110
|
* @param createGtkIconImport - import path for createGtkIcon
|
|
102
|
-
* @returns array of export statements for index.ts
|
|
111
|
+
* @returns sorted array of export statements for index.ts
|
|
103
112
|
*/
|
|
104
113
|
export async function buildIconComponents(
|
|
105
114
|
iconsDir: string,
|
|
106
115
|
outDir: string,
|
|
107
116
|
createGtkIconImport: string,
|
|
108
117
|
): Promise<string[]> {
|
|
109
|
-
|
|
110
|
-
mkdirSync(outDir, { recursive: true });
|
|
111
|
-
}
|
|
118
|
+
mkdirSync(outDir, { recursive: true });
|
|
112
119
|
|
|
113
120
|
const svgFiles = findSvgFiles(iconsDir);
|
|
114
|
-
const exports: string[] = [];
|
|
115
121
|
const seenNames = new Set<string>();
|
|
116
122
|
|
|
123
|
+
// Phase 1: filter duplicates and invalid identifiers (no I/O)
|
|
124
|
+
const toProcess: { filePath: string; kebabName: string; pascalName: string }[] = [];
|
|
117
125
|
for (const filePath of svgFiles) {
|
|
118
|
-
const
|
|
119
|
-
const iconName = fileName.replace(".svg", "");
|
|
126
|
+
const iconName = filePath.split("/").pop()!.replace(".svg", "");
|
|
120
127
|
const kebabName = toKebabCase(iconName);
|
|
121
|
-
const pascalName = toPascalCase(
|
|
128
|
+
const pascalName = toPascalCase(kebabName);
|
|
122
129
|
|
|
123
|
-
// Skip duplicates (same icon name in different subdirectories)
|
|
124
130
|
if (seenNames.has(kebabName)) continue;
|
|
125
131
|
seenNames.add(kebabName);
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
// Skip icons whose names start with a digit (invalid JS identifiers)
|
|
134
|
+
if (/^[0-9]/.test(pascalName)) continue;
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
136
|
+
toProcess.push({ filePath, kebabName, pascalName });
|
|
137
|
+
}
|
|
133
138
|
|
|
134
|
-
|
|
139
|
+
// Phase 2: parallel read + write
|
|
140
|
+
const results = await Promise.all(
|
|
141
|
+
toProcess.map(async ({ filePath, kebabName, pascalName }) => {
|
|
142
|
+
const svg = await Bun.file(filePath).text();
|
|
143
|
+
const children = parseSvgChildren(svg);
|
|
144
|
+
if (children.length === 0) return null;
|
|
135
145
|
|
|
136
|
-
|
|
146
|
+
const childrenLiteral = JSON.stringify(children.map((c) => [c.tag, c.attrs]));
|
|
147
|
+
const code = `import { createGtkIcon } from "${createGtkIconImport}";\n\nexport const ${pascalName} = createGtkIcon("${kebabName}", ${childrenLiteral});\n`;
|
|
137
148
|
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
await Bun.write(`${outDir}/${kebabName}.ts`, code);
|
|
150
|
+
return `export { ${pascalName} } from "./icons/${kebabName}.ts";`;
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
return results.filter((e): e is string => e !== null).sort();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build a complete icon package from a directory of SVGs.
|
|
159
|
+
* Writes generated components to src/icons/ and an index to src/index.ts.
|
|
160
|
+
*
|
|
161
|
+
* @param iconsDir - directory containing SVG files (can be nested)
|
|
162
|
+
* @param pkgDir - root directory of the icon package (contains src/)
|
|
163
|
+
*/
|
|
164
|
+
export async function buildIconPackage(iconsDir: string, pkgDir: string): Promise<void> {
|
|
165
|
+
const outDir = `${pkgDir}/src/icons`;
|
|
166
|
+
const indexPath = `${pkgDir}/src/index.ts`;
|
|
167
|
+
|
|
168
|
+
const exports = await buildIconComponents(iconsDir, outDir, "@gtk-js/icon-helpers");
|
|
169
|
+
|
|
170
|
+
await Bun.write(
|
|
171
|
+
indexPath,
|
|
172
|
+
`export type { GtkIconProps, GtkIcon } from "@gtk-js/icon-helpers";\n\n${exports.join("\n")}\n`,
|
|
173
|
+
);
|
|
144
174
|
|
|
145
|
-
|
|
175
|
+
console.log(`Built ${exports.length} icon components`);
|
|
146
176
|
}
|