@iconoma/studio 0.0.0
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/README.md +9 -0
- package/dist/api/actions.d.ts +7 -0
- package/dist/api/actions.d.ts.map +1 -0
- package/dist/api/actions.js +351 -0
- package/dist/api/db.d.ts +11 -0
- package/dist/api/db.d.ts.map +1 -0
- package/dist/api/db.js +20 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +201 -0
- package/dist/api/queue.d.ts +6 -0
- package/dist/api/queue.d.ts.map +1 -0
- package/dist/api/queue.js +4 -0
- package/dist/api/svgo-plugin-map-colors.d.ts +11 -0
- package/dist/api/svgo-plugin-map-colors.d.ts.map +1 -0
- package/dist/api/svgo-plugin-map-colors.js +199 -0
- package/dist/api/target-clients/interface.d.ts +6 -0
- package/dist/api/target-clients/interface.d.ts.map +1 -0
- package/dist/api/target-clients/interface.js +1 -0
- package/dist/api/target-clients/react-native.d.ts +7 -0
- package/dist/api/target-clients/react-native.d.ts.map +1 -0
- package/dist/api/target-clients/react-native.js +22 -0
- package/dist/api/target-clients/react.d.ts +9 -0
- package/dist/api/target-clients/react.d.ts.map +1 -0
- package/dist/api/target-clients/react.js +285 -0
- package/dist/api/types.d.ts +57 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +1 -0
- package/dist/api/utils.d.ts +28 -0
- package/dist/api/utils.d.ts.map +1 -0
- package/dist/api/utils.js +126 -0
- package/dist/client/assets/base-80a1f760-DXByDNOn.js +1 -0
- package/dist/client/assets/consoleHook-59e792cb-Cc-Wa9Dv.js +2 -0
- package/dist/client/assets/index-2gul9srt.js +810 -0
- package/dist/client/assets/index-599aeaf7-CuvDGLig.js +16 -0
- package/dist/client/assets/index-B8EuJHGY.css +1 -0
- package/dist/client/assets/node-C6_XYplI.js +4 -0
- package/dist/client/assets/runtime-BL9Nzh_-.js +1 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/icon.png +0 -0
- package/dist/client/index.html +21 -0
- package/dist/server/assets/base-80a1f760-qtF4btYl.js +31 -0
- package/dist/server/assets/consoleHook-59e792cb-DzCtq2z9.js +230 -0
- package/dist/server/assets/index-599aeaf7-CrZ9wybV.js +7378 -0
- package/dist/server/assets/node-Cq9Cey_i.js +1412 -0
- package/dist/server/assets/runtime-Cj4RK1K3.js +8158 -0
- package/dist/server/entry-server.js +38533 -0
- package/dist/server/favicon.ico +0 -0
- package/dist/server/icon.png +0 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +86 -0
- package/package.json +88 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const DEFAULT_ATTRS = [
|
|
2
|
+
"fill",
|
|
3
|
+
"stroke",
|
|
4
|
+
"stop-color",
|
|
5
|
+
"flood-color",
|
|
6
|
+
"lighting-color",
|
|
7
|
+
"color",
|
|
8
|
+
];
|
|
9
|
+
function clamp255(n) {
|
|
10
|
+
return Math.max(0, Math.min(255, n));
|
|
11
|
+
}
|
|
12
|
+
function to2hex(n) {
|
|
13
|
+
return clamp255(n).toString(16).padStart(2, "0");
|
|
14
|
+
}
|
|
15
|
+
function expandHex(hexNoHash) {
|
|
16
|
+
const h = hexNoHash.toLowerCase();
|
|
17
|
+
if (h.length === 3)
|
|
18
|
+
return h
|
|
19
|
+
.split("")
|
|
20
|
+
.map((c) => c + c)
|
|
21
|
+
.join(""); // rgb
|
|
22
|
+
if (h.length === 4)
|
|
23
|
+
return h
|
|
24
|
+
.split("")
|
|
25
|
+
.map((c) => c + c)
|
|
26
|
+
.join(""); // rgba
|
|
27
|
+
return h;
|
|
28
|
+
}
|
|
29
|
+
function rgbToHex(r, g, b) {
|
|
30
|
+
return `#${to2hex(r)}${to2hex(g)}${to2hex(b)}`;
|
|
31
|
+
}
|
|
32
|
+
function normalizeColor(input) {
|
|
33
|
+
if (input == null)
|
|
34
|
+
return null;
|
|
35
|
+
const v = String(input).trim();
|
|
36
|
+
if (!v)
|
|
37
|
+
return null;
|
|
38
|
+
const lowered = v.toLowerCase();
|
|
39
|
+
if (lowered === "none" ||
|
|
40
|
+
lowered === "transparent" ||
|
|
41
|
+
lowered === "currentcolor" ||
|
|
42
|
+
lowered === "inherit" ||
|
|
43
|
+
lowered === "initial" ||
|
|
44
|
+
lowered === "unset" ||
|
|
45
|
+
lowered.startsWith("url(")) {
|
|
46
|
+
return lowered === "currentcolor" ? "currentColor" : lowered;
|
|
47
|
+
}
|
|
48
|
+
const hex = v.match(/^#([0-9a-fA-F]{3,8})$/);
|
|
49
|
+
if (hex) {
|
|
50
|
+
const exp = expandHex(hex[1] ?? "");
|
|
51
|
+
return `#${exp}`;
|
|
52
|
+
}
|
|
53
|
+
let m = v.match(/^rgba?\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)(?:\s*,\s*([0-9]*\.?[0-9]+))?\s*\)$/i);
|
|
54
|
+
if (m) {
|
|
55
|
+
const r = Number(m[1]);
|
|
56
|
+
const g = Number(m[2]);
|
|
57
|
+
const b = Number(m[3]);
|
|
58
|
+
const a = m[4] != null ? Number(m[4]) : 1;
|
|
59
|
+
if (!Number.isFinite(a) || a === 1)
|
|
60
|
+
return rgbToHex(r, g, b);
|
|
61
|
+
return `rgba(${r},${g},${b},${a})`;
|
|
62
|
+
}
|
|
63
|
+
m = v.match(/^rgb\(\s*([0-9]+)\s+([0-9]+)\s+([0-9]+)(?:\s*\/\s*([0-9]*\.?[0-9]+))?\s*\)$/i);
|
|
64
|
+
if (m) {
|
|
65
|
+
const r = Number(m[1]);
|
|
66
|
+
const g = Number(m[2]);
|
|
67
|
+
const b = Number(m[3]);
|
|
68
|
+
const a = m[4] != null ? Number(m[4]) : 1;
|
|
69
|
+
if (!Number.isFinite(a) || a === 1)
|
|
70
|
+
return rgbToHex(r, g, b);
|
|
71
|
+
return `rgb(${r} ${g} ${b} / ${a})`;
|
|
72
|
+
}
|
|
73
|
+
return lowered;
|
|
74
|
+
}
|
|
75
|
+
function parseStyle(styleText) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const parts = String(styleText).split(";");
|
|
78
|
+
for (const part of parts) {
|
|
79
|
+
const p = part.trim();
|
|
80
|
+
if (!p)
|
|
81
|
+
continue;
|
|
82
|
+
const idx = p.indexOf(":");
|
|
83
|
+
if (idx === -1)
|
|
84
|
+
continue;
|
|
85
|
+
const key = p.slice(0, idx).trim();
|
|
86
|
+
const val = p.slice(idx + 1).trim();
|
|
87
|
+
out.push([key, val]);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function serializeStyle(decls) {
|
|
92
|
+
return decls.map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
93
|
+
}
|
|
94
|
+
function makeNormalizedMap(map) {
|
|
95
|
+
const norm = new Map();
|
|
96
|
+
for (const [from, to] of Object.entries(map || {})) {
|
|
97
|
+
const k = normalizeColor(from);
|
|
98
|
+
if (k)
|
|
99
|
+
norm.set(k, String(to));
|
|
100
|
+
}
|
|
101
|
+
return norm;
|
|
102
|
+
}
|
|
103
|
+
export const mapColorsPluginBase = {
|
|
104
|
+
name: "mapColors",
|
|
105
|
+
type: "visitor",
|
|
106
|
+
params: {
|
|
107
|
+
map: {},
|
|
108
|
+
attributes: [...DEFAULT_ATTRS],
|
|
109
|
+
replaceInlineStyle: true,
|
|
110
|
+
replaceStyleElementText: false,
|
|
111
|
+
},
|
|
112
|
+
fn(_root, params) {
|
|
113
|
+
const colorMap = makeNormalizedMap(params.map || {});
|
|
114
|
+
const attrs = (params.attributes?.length ? params.attributes : [...DEFAULT_ATTRS]);
|
|
115
|
+
const replaceIfMapped = (raw) => {
|
|
116
|
+
const n = normalizeColor(raw);
|
|
117
|
+
if (!n)
|
|
118
|
+
return null;
|
|
119
|
+
return colorMap.get(n) ?? null;
|
|
120
|
+
};
|
|
121
|
+
const replaceInlineStyle = (styleValue) => {
|
|
122
|
+
const decls = parseStyle(styleValue);
|
|
123
|
+
let changed = false;
|
|
124
|
+
for (let i = 0; i < decls.length; i++) {
|
|
125
|
+
const [prop, val] = decls[i] ?? [];
|
|
126
|
+
if (!/^(fill|stroke|stop-color|flood-color|lighting-color|color)$/i.test(prop ?? ""))
|
|
127
|
+
continue;
|
|
128
|
+
const mapped = replaceIfMapped(val);
|
|
129
|
+
if (mapped != null) {
|
|
130
|
+
decls[i] = [prop ?? "", mapped ?? ""];
|
|
131
|
+
changed = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return changed ? serializeStyle(decls) : null;
|
|
135
|
+
};
|
|
136
|
+
const replaceInCssText = (cssText) => {
|
|
137
|
+
let text = String(cssText);
|
|
138
|
+
for (const [fromNorm, to] of colorMap.entries()) {
|
|
139
|
+
if (!fromNorm.startsWith("#"))
|
|
140
|
+
continue;
|
|
141
|
+
const hex = fromNorm.slice(1);
|
|
142
|
+
const hex6 = hex.length >= 6 ? hex.slice(0, 6) : hex;
|
|
143
|
+
const canShort = hex6.length === 6 &&
|
|
144
|
+
hex6[0] === hex6[1] &&
|
|
145
|
+
hex6[2] === hex6[3] &&
|
|
146
|
+
hex6[4] === hex6[5];
|
|
147
|
+
const short = canShort ? `#${hex6[0]}${hex6[2]}${hex6[4]}` : null;
|
|
148
|
+
const patterns = [new RegExp(`#${hex6}`, "gi")];
|
|
149
|
+
if (short)
|
|
150
|
+
patterns.push(new RegExp(short, "gi"));
|
|
151
|
+
for (const re of patterns)
|
|
152
|
+
text = text.replace(re, to);
|
|
153
|
+
}
|
|
154
|
+
return text;
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
element: {
|
|
158
|
+
enter(node) {
|
|
159
|
+
if (!node.attributes)
|
|
160
|
+
return;
|
|
161
|
+
for (const a of attrs) {
|
|
162
|
+
if (!(a in node.attributes))
|
|
163
|
+
continue;
|
|
164
|
+
const mapped = replaceIfMapped(node.attributes[a]);
|
|
165
|
+
if (mapped != null)
|
|
166
|
+
node.attributes[a] = mapped;
|
|
167
|
+
}
|
|
168
|
+
if (params.replaceInlineStyle && node.attributes.style) {
|
|
169
|
+
const next = replaceInlineStyle(node.attributes.style);
|
|
170
|
+
if (next != null)
|
|
171
|
+
node.attributes.style = next;
|
|
172
|
+
}
|
|
173
|
+
if (params.replaceStyleElementText &&
|
|
174
|
+
node.name === "style" &&
|
|
175
|
+
node.children) {
|
|
176
|
+
for (const child of node.children) {
|
|
177
|
+
if (child &&
|
|
178
|
+
child.type === "text" &&
|
|
179
|
+
typeof child.value === "string") {
|
|
180
|
+
child.value = replaceInCssText(child.value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
export function mapColors(params) {
|
|
190
|
+
return {
|
|
191
|
+
...mapColorsPluginBase,
|
|
192
|
+
params: {
|
|
193
|
+
...mapColorsPluginBase.params,
|
|
194
|
+
...params,
|
|
195
|
+
map: params.map ?? {},
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export default mapColorsPluginBase;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LockFileIcon } from "../types.js";
|
|
2
|
+
export interface TargetClient {
|
|
3
|
+
addIcon(icon: LockFileIcon, iconKey: string, filePath: string): Promise<void>;
|
|
4
|
+
removeIcon(icon: LockFileIcon, iconKey: string, filePath: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../api/target-clients/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,UAAU,CACR,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LockFileIcon } from "../types.js";
|
|
2
|
+
import { TargetClient } from "./interface.js";
|
|
3
|
+
import { ReactTargetClient } from "./react.js";
|
|
4
|
+
export declare class ReactNativeTargetClient extends ReactTargetClient implements TargetClient {
|
|
5
|
+
addIcon(icon: LockFileIcon, iconKey: string, filePath: string): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=react-native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../../../api/target-clients/react-native.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,qBAAa,uBACX,SAAQ,iBACR,YAAW,YAAY;IAEjB,OAAO,CACX,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;CAyBjB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { transform } from "@svgr/core";
|
|
2
|
+
import { getConfig, getIconContent, toPascalFromSeparated } from "../utils.js";
|
|
3
|
+
import { ReactTargetClient } from "./react.js";
|
|
4
|
+
export class ReactNativeTargetClient extends ReactTargetClient {
|
|
5
|
+
async addIcon(icon, iconKey, filePath) {
|
|
6
|
+
const content = await getIconContent(icon);
|
|
7
|
+
const componentName = toPascalFromSeparated(iconKey);
|
|
8
|
+
const config = await getConfig();
|
|
9
|
+
const reactContent = await transform(content, {
|
|
10
|
+
plugins: [
|
|
11
|
+
"@svgr/plugin-svgo",
|
|
12
|
+
"@svgr/plugin-jsx",
|
|
13
|
+
"@svgr/plugin-prettier",
|
|
14
|
+
],
|
|
15
|
+
icon: true,
|
|
16
|
+
svgoConfig: config?.svgo,
|
|
17
|
+
typescript: filePath.endsWith(".tsx"),
|
|
18
|
+
native: true,
|
|
19
|
+
}, { componentName });
|
|
20
|
+
await this.writeComponent(iconKey, reactContent, filePath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LockFileIcon } from "../types.js";
|
|
2
|
+
import { TargetClient } from "./interface.js";
|
|
3
|
+
export declare class ReactTargetClient implements TargetClient {
|
|
4
|
+
addIcon(icon: LockFileIcon, iconKey: string, filePath: string): Promise<void>;
|
|
5
|
+
removeIcon(icon: LockFileIcon, iconKey: string, filePath: string): Promise<void>;
|
|
6
|
+
deleteFile(fullPath: string): Promise<void>;
|
|
7
|
+
writeComponent(iconKey: string, content: string, filePath: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../api/target-clients/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAOxC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoP3C,qBAAa,iBAAkB,YAAW,YAAY;IAC9C,OAAO,CACX,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAyBV,UAAU,CACd,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAkCV,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;CA6DjB"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { getConfig, getIconContent, getPwd, toPascalFromSeparated, } from "../utils.js";
|
|
2
|
+
import { transform } from "@svgr/core";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function splitSvgrComponentSource(source) {
|
|
6
|
+
const lines = String(source).replace(/\r\n?/g, "\n").split("\n");
|
|
7
|
+
const imports = [];
|
|
8
|
+
const exports = [];
|
|
9
|
+
const bodyLines = [];
|
|
10
|
+
const isImportStart = (line) => /^\s*import\b/.test(line);
|
|
11
|
+
const isExportStart = (line) => /^\s*export\b/.test(line);
|
|
12
|
+
const isImportEnd = (blockText) => {
|
|
13
|
+
const t = blockText.trim();
|
|
14
|
+
if (/^\s*import\s*["'][^"']+["']\s*;?\s*$/.test(t))
|
|
15
|
+
return true;
|
|
16
|
+
if (/\bfrom\s*["'][^"']+["']\s*;?\s*$/.test(t))
|
|
17
|
+
return true;
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
const isExportEnd = (blockText) => {
|
|
21
|
+
const t = blockText.trim();
|
|
22
|
+
if (/;\s*$/.test(t))
|
|
23
|
+
return true;
|
|
24
|
+
if (/^\s*export\s+default\b/.test(t))
|
|
25
|
+
return true;
|
|
26
|
+
if (/^\s*export\s*{\s*[\s\S]*}\s*;?\s*$/.test(t))
|
|
27
|
+
return true;
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
let mode = null;
|
|
31
|
+
let acc = [];
|
|
32
|
+
const flush = () => {
|
|
33
|
+
if (!mode)
|
|
34
|
+
return;
|
|
35
|
+
const block = acc.join("\n").trim();
|
|
36
|
+
if (block) {
|
|
37
|
+
if (mode === "import")
|
|
38
|
+
imports.push(block);
|
|
39
|
+
if (mode === "export")
|
|
40
|
+
exports.push(block);
|
|
41
|
+
}
|
|
42
|
+
mode = null;
|
|
43
|
+
acc = [];
|
|
44
|
+
};
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
if (!mode && isImportStart(line)) {
|
|
47
|
+
mode = "import";
|
|
48
|
+
acc.push(line);
|
|
49
|
+
if (isImportEnd(acc.join("\n")))
|
|
50
|
+
flush();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!mode && isExportStart(line)) {
|
|
54
|
+
mode = "export";
|
|
55
|
+
acc.push(line);
|
|
56
|
+
if (isExportEnd(acc.join("\n")))
|
|
57
|
+
flush();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (mode === "import") {
|
|
61
|
+
acc.push(line);
|
|
62
|
+
if (isImportEnd(acc.join("\n")))
|
|
63
|
+
flush();
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (mode === "export") {
|
|
67
|
+
acc.push(line);
|
|
68
|
+
if (isExportEnd(acc.join("\n")))
|
|
69
|
+
flush();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
bodyLines.push(line);
|
|
73
|
+
}
|
|
74
|
+
flush();
|
|
75
|
+
const body = bodyLines.join("\n");
|
|
76
|
+
const startMatch = body.match(/(^|\n)\s*(const|function)\s+\w+/);
|
|
77
|
+
let content = body.trim();
|
|
78
|
+
if (startMatch) {
|
|
79
|
+
const startIdx = body.indexOf(startMatch[0]) + (startMatch[1] ? 1 : 0);
|
|
80
|
+
content = body.slice(startIdx).trim();
|
|
81
|
+
}
|
|
82
|
+
return { imports, exports, content };
|
|
83
|
+
}
|
|
84
|
+
function getDefaultExportName(exports) {
|
|
85
|
+
for (const e of exports) {
|
|
86
|
+
const m = e.match(/export\s+default\s+([A-Za-z_$][\w$]*)/);
|
|
87
|
+
if (m?.[1])
|
|
88
|
+
return m[1];
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function toNamedExport(componentCode, name) {
|
|
93
|
+
const constRe = new RegExp(`^\\s*const\\s+${name}\\b`);
|
|
94
|
+
if (constRe.test(componentCode)) {
|
|
95
|
+
return componentCode.replace(constRe, `export const ${name}`);
|
|
96
|
+
}
|
|
97
|
+
const fnRe = new RegExp(`^\\s*function\\s+${name}\\b`);
|
|
98
|
+
if (fnRe.test(componentCode)) {
|
|
99
|
+
return componentCode.replace(fnRe, `export function ${name}`);
|
|
100
|
+
}
|
|
101
|
+
return `export ${componentCode.trimStart()}`;
|
|
102
|
+
}
|
|
103
|
+
function uniqStable(items) {
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const it of items) {
|
|
107
|
+
const key = it.trim();
|
|
108
|
+
if (!key)
|
|
109
|
+
continue;
|
|
110
|
+
if (seen.has(key))
|
|
111
|
+
continue;
|
|
112
|
+
seen.add(key);
|
|
113
|
+
out.push(it.trim());
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
function extractTopImports(fileText) {
|
|
118
|
+
const lines = fileText.replace(/\r\n?/g, "\n").split("\n");
|
|
119
|
+
const imports = [];
|
|
120
|
+
let i = 0;
|
|
121
|
+
while (i < lines.length && /^\s*\/\/.*/.test(lines[i] ?? ""))
|
|
122
|
+
i++;
|
|
123
|
+
while (i < lines.length && /^\s*$/.test(lines[i] ?? ""))
|
|
124
|
+
i++;
|
|
125
|
+
while (i < lines.length) {
|
|
126
|
+
const line = lines[i];
|
|
127
|
+
if (!/^\s*import\b/.test(line ?? ""))
|
|
128
|
+
break;
|
|
129
|
+
const block = [line ?? ""];
|
|
130
|
+
i++;
|
|
131
|
+
while (i < lines.length) {
|
|
132
|
+
const t = block.join("\n").trim();
|
|
133
|
+
if (/^\s*import\s*["'][^"']+["']\s*;?\s*$/.test(t) ||
|
|
134
|
+
/\bfrom\s*["'][^"']+["']\s*;?\s*$/.test(t)) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
block.push(lines[i] ?? "");
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
imports.push(block.join("\n").trim());
|
|
141
|
+
i++;
|
|
142
|
+
while (i < lines.length && /^\s*$/.test(lines[i] ?? ""))
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
const rest = lines.slice(i).join("\n").replace(/^\n+/, "");
|
|
146
|
+
return { imports, rest };
|
|
147
|
+
}
|
|
148
|
+
function ensureHeader(fileText) {
|
|
149
|
+
const header = `// DON'T EDIT THIS FILE RUN \`npx @iconoma/cli studio\` TO UPDATE IT.\n` +
|
|
150
|
+
`// IT IS GENERATED BY ICONOMA.\n`;
|
|
151
|
+
const trimmed = fileText.trimStart();
|
|
152
|
+
if (trimmed.startsWith("// DON'T EDIT THIS FILE"))
|
|
153
|
+
return fileText;
|
|
154
|
+
return header + fileText.replace(/^\n+/, "");
|
|
155
|
+
}
|
|
156
|
+
function buildIconBlock(iconKey, componentName, namedExportCode) {
|
|
157
|
+
return (`// <iconoma-icon key="${iconKey}" name="${componentName}">\n` +
|
|
158
|
+
`${namedExportCode.trim()}\n` +
|
|
159
|
+
`// </iconoma-icon>\n`);
|
|
160
|
+
}
|
|
161
|
+
function upsertIconBlock(fileRest, iconKey, componentName, block) {
|
|
162
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
163
|
+
const pattern = new RegExp(`//\\s*<iconoma-icon\\s+key="${esc(iconKey)}"\\s+name="${esc(componentName)}">[\\s\\S]*?//\\s*</iconoma-icon>\\s*\\n?`, "m");
|
|
164
|
+
if (pattern.test(fileRest)) {
|
|
165
|
+
return fileRest.replace(pattern, block + "\n");
|
|
166
|
+
}
|
|
167
|
+
const restTrimmed = fileRest.trim();
|
|
168
|
+
if (!restTrimmed)
|
|
169
|
+
return block + "\n";
|
|
170
|
+
return restTrimmed + "\n\n" + block + "\n";
|
|
171
|
+
}
|
|
172
|
+
function hasAnyIconBlocks(fileText) {
|
|
173
|
+
return /\/\/\s*<iconoma-icon\b/.test(fileText);
|
|
174
|
+
}
|
|
175
|
+
function removeIconBlockFromFile(fileText, iconKey) {
|
|
176
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
177
|
+
const key = esc(iconKey);
|
|
178
|
+
const pattern = new RegExp(`(^|\\n)\\s*//\\s*<iconoma-icon\\s+key="${key}"\\s+name="[^"]*">\\s*\\n[\\s\\S]*?\\n\\s*//\\s*</iconoma-icon>\\s*(\\n|$)`, "m");
|
|
179
|
+
if (!pattern.test(fileText)) {
|
|
180
|
+
return { next: fileText, removed: false };
|
|
181
|
+
}
|
|
182
|
+
const next = fileText
|
|
183
|
+
.replace(pattern, "\n")
|
|
184
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
185
|
+
.trimEnd()
|
|
186
|
+
.concat("\n");
|
|
187
|
+
return { next, removed: true };
|
|
188
|
+
}
|
|
189
|
+
export class ReactTargetClient {
|
|
190
|
+
async addIcon(icon, iconKey, filePath) {
|
|
191
|
+
const content = await getIconContent(icon);
|
|
192
|
+
const componentName = toPascalFromSeparated(iconKey);
|
|
193
|
+
const config = await getConfig();
|
|
194
|
+
const reactContent = await transform(content, {
|
|
195
|
+
plugins: [
|
|
196
|
+
"@svgr/plugin-svgo",
|
|
197
|
+
"@svgr/plugin-jsx",
|
|
198
|
+
"@svgr/plugin-prettier",
|
|
199
|
+
],
|
|
200
|
+
icon: true,
|
|
201
|
+
svgoConfig: config?.svgo,
|
|
202
|
+
typescript: filePath.endsWith(".tsx"),
|
|
203
|
+
}, { componentName });
|
|
204
|
+
await this.writeComponent(iconKey, reactContent, filePath);
|
|
205
|
+
}
|
|
206
|
+
async removeIcon(icon, iconKey, filePath) {
|
|
207
|
+
const pwd = await getPwd();
|
|
208
|
+
const fullPath = path.join(pwd, filePath);
|
|
209
|
+
const isUniqueFile = fullPath.endsWith(`${iconKey}.tsx`) ||
|
|
210
|
+
fullPath.endsWith(`${iconKey}.jsx`);
|
|
211
|
+
if (isUniqueFile) {
|
|
212
|
+
await this.deleteFile(fullPath);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const fileExists = await fs
|
|
216
|
+
.access(fullPath)
|
|
217
|
+
.then(() => true)
|
|
218
|
+
.catch(() => false);
|
|
219
|
+
if (!fileExists)
|
|
220
|
+
return;
|
|
221
|
+
const existing = await fs.readFile(fullPath, "utf-8");
|
|
222
|
+
const { next, removed } = removeIconBlockFromFile(existing, iconKey);
|
|
223
|
+
if (!removed)
|
|
224
|
+
return;
|
|
225
|
+
if (!hasAnyIconBlocks(next)) {
|
|
226
|
+
await this.deleteFile(fullPath);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await fs.writeFile(fullPath, next);
|
|
230
|
+
}
|
|
231
|
+
async deleteFile(fullPath) {
|
|
232
|
+
const exists = await fs
|
|
233
|
+
.access(fullPath)
|
|
234
|
+
.then(() => true)
|
|
235
|
+
.catch(() => false);
|
|
236
|
+
if (!exists)
|
|
237
|
+
return;
|
|
238
|
+
await fs.unlink(fullPath);
|
|
239
|
+
const folder = path.dirname(fullPath);
|
|
240
|
+
const files = await fs.readdir(folder);
|
|
241
|
+
if (files.length === 0) {
|
|
242
|
+
await fs.rmdir(folder);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async writeComponent(iconKey, content, filePath) {
|
|
246
|
+
const pwd = await getPwd();
|
|
247
|
+
const fullPath = path.join(pwd, filePath);
|
|
248
|
+
const folder = path.dirname(fullPath);
|
|
249
|
+
await fs.mkdir(folder, { recursive: true });
|
|
250
|
+
const isUniqueFile = fullPath.endsWith(`${iconKey}.tsx`) ||
|
|
251
|
+
fullPath.endsWith(`${iconKey}.jsx`);
|
|
252
|
+
if (isUniqueFile) {
|
|
253
|
+
await fs.writeFile(fullPath, `// DON'T EDIT THIS FILE RUN \`npx @iconoma/cli studio\` TO UPDATE IT.\n// IT IS GENERATED BY ICONOMA.\n${content}\n`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const componentName = toPascalFromSeparated(iconKey);
|
|
257
|
+
const parsed = splitSvgrComponentSource(content);
|
|
258
|
+
const defaultName = getDefaultExportName(parsed.exports) ?? componentName;
|
|
259
|
+
const namedExportCode = toNamedExport(parsed.content, defaultName);
|
|
260
|
+
const newBlock = buildIconBlock(iconKey, componentName, namedExportCode);
|
|
261
|
+
const fileExists = await fs
|
|
262
|
+
.access(fullPath)
|
|
263
|
+
.then(() => true)
|
|
264
|
+
.catch(() => false);
|
|
265
|
+
if (!fileExists) {
|
|
266
|
+
const imports = uniqStable(parsed.imports);
|
|
267
|
+
const initial = ensureHeader("") +
|
|
268
|
+
(imports.length ? imports.join("\n") + "\n\n" : "") +
|
|
269
|
+
newBlock +
|
|
270
|
+
"\n";
|
|
271
|
+
await fs.writeFile(fullPath, initial);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const existing = await fs.readFile(fullPath, "utf-8");
|
|
275
|
+
const existingWithHeader = ensureHeader(existing);
|
|
276
|
+
const { imports: existingImports, rest } = extractTopImports(existingWithHeader);
|
|
277
|
+
const mergedImports = uniqStable([...existingImports, ...parsed.imports]);
|
|
278
|
+
const updatedRest = upsertIconBlock(rest, iconKey, componentName, newBlock);
|
|
279
|
+
const finalText = ensureHeader("") +
|
|
280
|
+
(mergedImports.length ? mergedImports.join("\n") + "\n\n" : "") +
|
|
281
|
+
updatedRest.trim() +
|
|
282
|
+
"\n";
|
|
283
|
+
await fs.writeFile(fullPath, finalText);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type ExtraTarget = {
|
|
2
|
+
targetId: string;
|
|
3
|
+
outputPath: string;
|
|
4
|
+
};
|
|
5
|
+
export type SvgConfig = {
|
|
6
|
+
folder: string | null;
|
|
7
|
+
inLock: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type Config = {
|
|
10
|
+
svg: SvgConfig;
|
|
11
|
+
extraTargets: ExtraTarget[];
|
|
12
|
+
colorVariables: string[];
|
|
13
|
+
svgo: any;
|
|
14
|
+
};
|
|
15
|
+
export type LockFileIcon = {
|
|
16
|
+
name: string;
|
|
17
|
+
tags: string[];
|
|
18
|
+
svg: LockFileIconSvg;
|
|
19
|
+
targets: Record<string, LockFileIconTarget>;
|
|
20
|
+
colorVariableKeys: string[];
|
|
21
|
+
};
|
|
22
|
+
export type LockFileIconSvg = {
|
|
23
|
+
content: string;
|
|
24
|
+
hash: string;
|
|
25
|
+
};
|
|
26
|
+
export type LockFileIconTarget = {
|
|
27
|
+
path: string;
|
|
28
|
+
builtFrom: LockFileBuiltFrom;
|
|
29
|
+
};
|
|
30
|
+
export type LockFileBuiltFrom = {
|
|
31
|
+
svgHash: string;
|
|
32
|
+
configHash: string;
|
|
33
|
+
};
|
|
34
|
+
export type LockFile = {
|
|
35
|
+
configHash: string;
|
|
36
|
+
icons: Record<string, LockFileIcon>;
|
|
37
|
+
};
|
|
38
|
+
export type Change = {
|
|
39
|
+
type: "MIGRATE_SVG_TO_LOCK" | "MIGRATE_SVG_TO_FILE" | "ADD_EXTRA_TARGET" | "REMOVE_EXTRA_TARGET" | "CREATE_ICON" | "REMOVE_ICON" | "REGENERATE_ICON" | "REGENERATE_ALL";
|
|
40
|
+
filePath?: string;
|
|
41
|
+
iconKey?: string;
|
|
42
|
+
metadata?: {
|
|
43
|
+
iconKey: string;
|
|
44
|
+
} | {
|
|
45
|
+
name: string;
|
|
46
|
+
tags: string[];
|
|
47
|
+
content: string;
|
|
48
|
+
colorMap?: Record<string, string>;
|
|
49
|
+
};
|
|
50
|
+
targetId?: string;
|
|
51
|
+
};
|
|
52
|
+
export type ActionModel = Change & {
|
|
53
|
+
status: "pending" | "processing" | "completed" | "failed";
|
|
54
|
+
percentage: number;
|
|
55
|
+
error?: string;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../api/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,GAAG,EAAE,SAAS,CAAC;IACf,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,IAAI,EAAE,GAAG,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,eAAe,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC5C,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EACA,qBAAqB,GACrB,qBAAqB,GACrB,kBAAkB,GAClB,qBAAqB,GACrB,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,gBAAgB,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EACL;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GACnB;QACE,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACnC,CAAC;IACN,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IACjC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ActionModel, Change, Config, LockFile, LockFileIcon } from "./types.js";
|
|
2
|
+
export declare function getPwd(): Promise<string>;
|
|
3
|
+
export declare function getConfig(): Promise<Config | null>;
|
|
4
|
+
export declare function setConfig(config: Config): Promise<void>;
|
|
5
|
+
export declare function getLockFile(): Promise<LockFile | null>;
|
|
6
|
+
export declare function setLockFile(lockFile: LockFile): Promise<void>;
|
|
7
|
+
export declare function getSvgContent(icon: LockFileIcon): Promise<string | null>;
|
|
8
|
+
export declare function getIconContent(icon: LockFileIcon): Promise<string>;
|
|
9
|
+
export declare function toPascalFromSeparated(input: string): string;
|
|
10
|
+
export declare function setContent(config: Config, iconKey: string, content: string): Promise<{
|
|
11
|
+
content: string;
|
|
12
|
+
hash: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const actionsTable: {
|
|
15
|
+
create: (value: ActionModel) => number;
|
|
16
|
+
get: (id: number) => ActionModel | undefined;
|
|
17
|
+
update: (id: number, value: ActionModel) => Map<number, any>;
|
|
18
|
+
delete: (id: number) => boolean;
|
|
19
|
+
getAll: () => (Change & {
|
|
20
|
+
status: "pending" | "processing" | "completed" | "failed";
|
|
21
|
+
percentage: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
} & {
|
|
24
|
+
id: number;
|
|
25
|
+
})[];
|
|
26
|
+
};
|
|
27
|
+
export declare function createAction(action: Change): Promise<void | LockFileIcon>;
|
|
28
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../api/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK9E,wBAAsB,MAAM,oBAE3B;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxD;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB7D;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAc5D;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAInE;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAUxE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B5C;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;CAAgC,CAAC;AAE1D,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,gCAWhD"}
|