@gtk-js/icon-helpers 0.0.1 → 0.1.1

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 ADDED
@@ -0,0 +1,15 @@
1
+ # @gtk-js/icon-helpers
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0982123: Bump all packages past burned npm versions
8
+
9
+ ## 0.2.0
10
+
11
+ ## 0.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - f9da446: Testing CI publishing
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@gtk-js/icon-helpers",
3
- "version": "0.0.1",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.1",
4
7
  "type": "module",
5
8
  "main": "src/index.ts",
6
9
  "exports": {
@@ -1,18 +1,28 @@
1
1
  /**
2
2
  * Shared helpers for building GTK icon packages.
3
- * Used by both @gtk-js/gtk4-icons and @gtk-js/adwaita-icons build scripts.
3
+ * Used by all icon packages under icons/.
4
4
  */
5
5
 
6
- import { existsSync, mkdirSync, readdirSync, statSync } from "fs";
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
- const STRIP_ATTRS = new Set(["id", "class", "xmlns"]);
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, "-") // replace non-alphanumeric chars with hyphens
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, "-") // replace non-alphanumeric chars with hyphens
26
- .replace(/-+/g, "-") // collapse multiple hyphens
27
- .replace(/^-|-$/g, ""); // trim leading/trailing hyphens
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
- if (!existsSync(outDir)) {
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 fileName = filePath.split("/").pop()!;
119
- const iconName = fileName.replace(".svg", "");
126
+ const iconName = filePath.split("/").pop()!.replace(".svg", "");
120
127
  const kebabName = toKebabCase(iconName);
121
- const pascalName = toPascalCase(iconName);
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
- const svg = await Bun.file(filePath).text();
128
- const children = parseSvgChildren(svg);
133
+ // Skip icons whose names start with a digit (invalid JS identifiers)
134
+ if (/^[0-9]/.test(pascalName)) continue;
129
135
 
130
- if (children.length === 0) {
131
- continue;
132
- }
136
+ toProcess.push({ filePath, kebabName, pascalName });
137
+ }
133
138
 
134
- const childrenLiteral = JSON.stringify(children.map((c) => [c.tag, c.attrs]));
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
- const code = `import { createGtkIcon } from "${createGtkIconImport}";
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
- export const ${pascalName} = createGtkIcon("${kebabName}", ${childrenLiteral});
139
- `;
149
+ await Bun.write(`${outDir}/${kebabName}.ts`, code);
150
+ return `export { ${pascalName} } from "./icons/${kebabName}.ts";`;
151
+ }),
152
+ );
140
153
 
141
- await Bun.write(`${outDir}/${kebabName}.ts`, code);
142
- exports.push(`export { ${pascalName} } from "./icons/${kebabName}.ts";`);
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
- return exports;
175
+ console.log(`Built ${exports.length} icon components`);
146
176
  }