@ethlete/components 0.1.0-next.8 ā 1.0.0-next.17
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 +121 -0
- package/fesm2022/ethlete-components.mjs +11282 -4282
- package/fesm2022/ethlete-components.mjs.map +1 -1
- package/generators/generators.json +9 -0
- package/generators/icons/generator.js +191 -0
- package/generators/icons/schema.json +35 -0
- package/package.json +15 -8
- package/types/ethlete-components.d.ts +2122 -282
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { formatFiles, logger, workspaceRoot } from '@nx/devkit';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region Constants
|
|
6
|
+
// Known SVG sources, tried in order during auto-detection. All use a `svgs/<variant>/<name>.svg` layout.
|
|
7
|
+
const KNOWN_SOURCES = ['@fortawesome/fontawesome-pro', '@fortawesome/fontawesome-free'];
|
|
8
|
+
const DEFAULT_CONFIG_PATH = 'src/icons.json';
|
|
9
|
+
const DEFAULT_OUTPUT_PATH = 'src/generated/et-icons.ts';
|
|
10
|
+
const DEFAULT_VARIANTS = ['solid'];
|
|
11
|
+
//#endregion
|
|
12
|
+
export default async function generate(tree, schema) {
|
|
13
|
+
logger.log('\nšØ Starting Ethlete icon generator...\n');
|
|
14
|
+
const configPath = schema.configPath || DEFAULT_CONFIG_PATH;
|
|
15
|
+
const outputPath = schema.outputPath || DEFAULT_OUTPUT_PATH;
|
|
16
|
+
const typesOutputPath = schema.typesOutputPath || join(dirname(outputPath), 'et-icon-registry.d.ts');
|
|
17
|
+
// Step 1: Read the icons config.
|
|
18
|
+
if (!tree.exists(configPath)) {
|
|
19
|
+
logger.error(`ā Icons config not found at: ${configPath}`);
|
|
20
|
+
logger.log(`\nCreate one, or point to it with --configPath. Example ${configPath}:`);
|
|
21
|
+
logger.log(` { "variants": ["solid"], "icons": ["plus", { "name": "shield", "variants": ["light", "solid"] }] }\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let config;
|
|
25
|
+
try {
|
|
26
|
+
config = JSON.parse(tree.read(configPath, 'utf-8') || '');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger.error(`ā Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(config.icons) || config.icons.length === 0) {
|
|
33
|
+
logger.error(`ā ${configPath} must contain a non-empty "icons" array.`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Prefer the `variants` list; fall back to `['solid']`.
|
|
37
|
+
const defaultVariants = config.variants?.length ? config.variants : DEFAULT_VARIANTS;
|
|
38
|
+
// Step 2: Resolve the SVG source package. `'auto'` (the default, from the option or the
|
|
39
|
+
// config) means auto-detect; an explicit package name overrides it.
|
|
40
|
+
const configuredSource = schema.source && schema.source !== 'auto' ? schema.source : config.source;
|
|
41
|
+
const requestedSource = configuredSource && configuredSource !== 'auto' ? configuredSource : undefined;
|
|
42
|
+
const source = resolveSource(requestedSource);
|
|
43
|
+
if (!source) {
|
|
44
|
+
logger.error('ā Could not find an icon source. Install @fortawesome/fontawesome-pro (or -free), or set "source".');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
logger.log(`š¦ Using icon source: ${source.package}`);
|
|
48
|
+
// Step 3: Read + transform each icon's SVG.
|
|
49
|
+
const requested = normalizeIcons(config.icons, defaultVariants);
|
|
50
|
+
const resolved = [];
|
|
51
|
+
const missing = [];
|
|
52
|
+
for (const icon of requested) {
|
|
53
|
+
const svgPath = join(source.svgsDir, icon.variant, `${icon.name}.svg`);
|
|
54
|
+
if (!existsSync(svgPath)) {
|
|
55
|
+
missing.push(`${icon.name} (${icon.variant})`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
resolved.push({ ...icon, data: toIconData(readFileSync(svgPath, 'utf-8')) });
|
|
59
|
+
}
|
|
60
|
+
if (missing.length) {
|
|
61
|
+
logger.warn(`ā ļø ${missing.length} icon(s) not found in ${source.package} and skipped:\n - ${missing.join('\n - ')}`);
|
|
62
|
+
}
|
|
63
|
+
if (resolved.length === 0) {
|
|
64
|
+
logger.error('ā No icons could be resolved. Nothing was written.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Step 4: Write the IconDefinition constants.
|
|
68
|
+
tree.write(outputPath, generateIconsFile(resolved, schema, source.package));
|
|
69
|
+
logger.log(`ā
Generated ${resolved.length} icon(s) at: ${outputPath}`);
|
|
70
|
+
// Step 5: Write the type augmentation so `etIcon`/`variant` are checked against these names.
|
|
71
|
+
tree.write(typesOutputPath, generateTypesFile(resolved, schema));
|
|
72
|
+
logger.log(`ā
Generated icon name types at: ${typesOutputPath}`);
|
|
73
|
+
if (!schema.skipFormat) {
|
|
74
|
+
await formatFiles(tree);
|
|
75
|
+
}
|
|
76
|
+
logger.log('\nā
Icon generation completed successfully!\n');
|
|
77
|
+
}
|
|
78
|
+
//#region Helpers
|
|
79
|
+
function resolveSource(requested) {
|
|
80
|
+
const candidates = requested ? [requested] : KNOWN_SOURCES;
|
|
81
|
+
for (const pkg of candidates) {
|
|
82
|
+
const svgsDir = join(workspaceRoot, 'node_modules', pkg, 'svgs');
|
|
83
|
+
if (existsSync(svgsDir)) {
|
|
84
|
+
return { package: pkg, svgsDir };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function normalizeIcons(entries, defaultVariants) {
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
const result = [];
|
|
92
|
+
const add = (name, variant) => {
|
|
93
|
+
const key = `${name}::${variant}`;
|
|
94
|
+
if (!seen.has(key)) {
|
|
95
|
+
seen.add(key);
|
|
96
|
+
result.push({ name, variant });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
// A bare string uses the config's default variants. An object may override with its own
|
|
101
|
+
// `variants` list (or the singular `variant`); otherwise it also gets the defaults.
|
|
102
|
+
const variants = typeof entry === 'string'
|
|
103
|
+
? defaultVariants
|
|
104
|
+
: (entry.variants ?? (entry.variant ? [entry.variant] : defaultVariants));
|
|
105
|
+
const name = typeof entry === 'string' ? entry : entry.name;
|
|
106
|
+
for (const variant of variants) {
|
|
107
|
+
add(name, variant);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Stable ordering keeps regeneration diffs minimal.
|
|
111
|
+
return result.sort((a, b) => `${a.name}::${a.variant}`.localeCompare(`${b.name}::${b.variant}`));
|
|
112
|
+
}
|
|
113
|
+
/** Make a raw SVG etIcon-compatible: fill its host, inherit currentColor, drop the license comment. */
|
|
114
|
+
function toIconData(rawSvg) {
|
|
115
|
+
return rawSvg
|
|
116
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
117
|
+
.replace('<svg ', '<svg width="100%" height="100%" fill="currentColor" ')
|
|
118
|
+
.replace(/\s+/g, ' ')
|
|
119
|
+
.trim();
|
|
120
|
+
}
|
|
121
|
+
/** `shield` + `light` -> `SHIELD_LIGHT` */
|
|
122
|
+
function constName(name, variant) {
|
|
123
|
+
return `${name}_${variant}`.replace(/[^a-z0-9]+/gi, '_').toUpperCase();
|
|
124
|
+
}
|
|
125
|
+
function regenerateCommand(schema) {
|
|
126
|
+
const parts = ['nx g @ethlete/components:icons'];
|
|
127
|
+
if (schema.configPath)
|
|
128
|
+
parts.push(`--configPath=${schema.configPath}`);
|
|
129
|
+
if (schema.outputPath)
|
|
130
|
+
parts.push(`--outputPath=${schema.outputPath}`);
|
|
131
|
+
if (schema.typesOutputPath)
|
|
132
|
+
parts.push(`--typesOutputPath=${schema.typesOutputPath}`);
|
|
133
|
+
if (schema.source && schema.source !== 'auto')
|
|
134
|
+
parts.push(`--source=${schema.source}`);
|
|
135
|
+
return parts.join(' ');
|
|
136
|
+
}
|
|
137
|
+
function generateIconsFile(icons, schema, source) {
|
|
138
|
+
const header = `/* eslint-disable */
|
|
139
|
+
/*
|
|
140
|
+
* Auto-generated by @ethlete/components:icons from "${source}"
|
|
141
|
+
* DO NOT EDIT THIS FILE MANUALLY
|
|
142
|
+
*
|
|
143
|
+
* Regenerate by running:
|
|
144
|
+
* ${regenerateCommand(schema)}
|
|
145
|
+
*/
|
|
146
|
+
import type { IconDefinition } from '@ethlete/components';
|
|
147
|
+
`;
|
|
148
|
+
const consts = icons
|
|
149
|
+
.map((icon) => `export const ${constName(icon.name, icon.variant)}: IconDefinition = {\n` +
|
|
150
|
+
` name: '${icon.name}',\n` +
|
|
151
|
+
` variant: '${icon.variant}',\n` +
|
|
152
|
+
` data: \`${icon.data}\`,\n` +
|
|
153
|
+
`};`)
|
|
154
|
+
.join('\n\n');
|
|
155
|
+
const aggregate = `export const GENERATED_ICONS = [\n${icons
|
|
156
|
+
.map((icon) => ` ${constName(icon.name, icon.variant)},`)
|
|
157
|
+
.join('\n')}\n] as const;`;
|
|
158
|
+
return `${header}\n${consts}\n\n${aggregate}\n`;
|
|
159
|
+
}
|
|
160
|
+
function generateTypesFile(icons, schema) {
|
|
161
|
+
const names = [...new Set(icons.map((i) => i.name))]
|
|
162
|
+
.sort()
|
|
163
|
+
.map((n) => `'${n}'`)
|
|
164
|
+
.join(' | ');
|
|
165
|
+
const variants = [...new Set(icons.map((i) => i.variant))]
|
|
166
|
+
.sort()
|
|
167
|
+
.map((v) => `'${v}'`)
|
|
168
|
+
.join(' | ');
|
|
169
|
+
return `/*
|
|
170
|
+
* Auto-generated by @ethlete/components:icons
|
|
171
|
+
* DO NOT EDIT THIS FILE MANUALLY
|
|
172
|
+
*
|
|
173
|
+
* Regenerate by running:
|
|
174
|
+
* ${regenerateCommand(schema)}
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
declare module '@ethlete/components' {
|
|
178
|
+
interface EthleteIconNameRegistry {
|
|
179
|
+
name: ${names};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface EthleteIconVariantRegistry {
|
|
183
|
+
name: ${variants};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {};
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "et-icons",
|
|
4
|
+
"title": "Ethlete Icon Generator",
|
|
5
|
+
"description": "Generates etIcon IconDefinitions and typed names from an installed SVG icon source. Auto-detects Font Awesome (pro, then free).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"configPath": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path to the icons config JSON (relative to workspace root). Lists the icons/variants to generate.",
|
|
11
|
+
"default": "src/icons.json",
|
|
12
|
+
"x-prompt": "Where is your icons config file?"
|
|
13
|
+
},
|
|
14
|
+
"outputPath": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Where to write the generated IconDefinition constants (.ts).",
|
|
17
|
+
"default": "src/generated/et-icons.ts"
|
|
18
|
+
},
|
|
19
|
+
"typesOutputPath": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Where to write the generated name/variant type augmentation (.d.ts). Defaults to 'et-icon-registry.d.ts' next to outputPath."
|
|
22
|
+
},
|
|
23
|
+
"source": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Icon source package to read SVGs from (expects a `svgs/<variant>/<name>.svg` layout). 'auto' detects Font Awesome pro then free.",
|
|
26
|
+
"default": "auto"
|
|
27
|
+
},
|
|
28
|
+
"skipFormat": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"description": "Skip formatting files after generation.",
|
|
31
|
+
"default": false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": []
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0-next.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@analogjs/vitest-angular": "2.
|
|
8
|
-
"@angular/common": "
|
|
9
|
-
"@angular/compiler": "
|
|
10
|
-
"@angular/core": "
|
|
11
|
-
"@angular/forms": "
|
|
12
|
-
"@angular/platform-browser": "
|
|
13
|
-
"@angular/router": "
|
|
7
|
+
"@analogjs/vitest-angular": "2.6.2",
|
|
8
|
+
"@angular/common": "22.0.5",
|
|
9
|
+
"@angular/compiler": "22.0.5",
|
|
10
|
+
"@angular/core": "22.0.5",
|
|
11
|
+
"@angular/forms": "22.0.5",
|
|
12
|
+
"@angular/platform-browser": "22.0.5",
|
|
13
|
+
"@angular/router": "22.0.5",
|
|
14
14
|
"@ethlete/core": "^5.0.0-beta.11",
|
|
15
15
|
"@floating-ui/dom": "1.7.6",
|
|
16
|
+
"@nx/devkit": "23.1.0-beta.7",
|
|
16
17
|
"rxjs": "7.8.2"
|
|
17
18
|
},
|
|
19
|
+
"peerDependenciesMeta": {
|
|
20
|
+
"@nx/devkit": {
|
|
21
|
+
"optional": true
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"generators": "./generators/generators.json",
|
|
18
25
|
"module": "fesm2022/ethlete-components.mjs",
|
|
19
26
|
"typings": "types/ethlete-components.d.ts",
|
|
20
27
|
"exports": {
|