@auto-engineer/design-system-importer 0.1.2 → 0.4.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +12 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-type-check.log +5 -0
- package/CHANGELOG.md +37 -0
- package/README.md +48 -0
- package/dist/FigmaComponentsBuilder.d.ts +16 -0
- package/dist/FigmaComponentsBuilder.d.ts.map +1 -0
- package/dist/FigmaComponentsBuilder.js +175 -0
- package/dist/FigmaComponentsBuilder.js.map +1 -0
- package/dist/commands/import-design-system.d.ts +3 -3
- package/dist/commands/import-design-system.d.ts.map +1 -1
- package/dist/commands/import-design-system.js +20 -25
- package/dist/commands/import-design-system.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -6
- package/dist/index.js.map +1 -1
- package/dist/utils/FilterLoader.d.ts +9 -0
- package/dist/utils/FilterLoader.d.ts.map +1 -0
- package/dist/utils/FilterLoader.js +66 -0
- package/dist/utils/FilterLoader.js.map +1 -0
- package/package.json +4 -2
- package/src/FigmaComponentsBuilder.ts +207 -0
- package/src/commands/import-design-system.ts +19 -28
- package/src/index.ts +112 -6
- package/src/utils/FilterLoader.ts +76 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterLoader.d.ts","sourceRoot":"","sources":["../../src/utils/FilterLoader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,qBAAa,YAAY;IACvB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,aAAa,CAA6B;IAE5C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAmCjD,gBAAgB;IAqB9B,OAAO,IAAI,IAAI;CAUhB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { extname, resolve as resolvePath } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
export class FilterLoader {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.tsxRegistered = false;
|
|
7
|
+
this.tsxUnregister = null;
|
|
8
|
+
}
|
|
9
|
+
async loadFilter(filePath) {
|
|
10
|
+
if (typeof filePath !== 'string' || filePath.trim().length === 0) {
|
|
11
|
+
throw new Error('Filter file path is required');
|
|
12
|
+
}
|
|
13
|
+
const absolutePath = resolvePath(process.cwd(), filePath);
|
|
14
|
+
if (!existsSync(absolutePath)) {
|
|
15
|
+
throw new Error(`Filter file not found: ${absolutePath}`);
|
|
16
|
+
}
|
|
17
|
+
const ext = extname(absolutePath).toLowerCase();
|
|
18
|
+
// Enable tsx loader for TS files at runtime
|
|
19
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
20
|
+
await this.ensureTsxSupport();
|
|
21
|
+
}
|
|
22
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
23
|
+
const loadedUnknown = await import(fileUrl);
|
|
24
|
+
const loadedModule = loadedUnknown;
|
|
25
|
+
if (typeof loadedModule.filter === 'function') {
|
|
26
|
+
return loadedModule.filter;
|
|
27
|
+
}
|
|
28
|
+
if (typeof loadedModule.default === 'function') {
|
|
29
|
+
console.warn('Using default export from filter module. Prefer a named export "filter".');
|
|
30
|
+
return loadedModule.default;
|
|
31
|
+
}
|
|
32
|
+
throw new Error('No filter function found. Export a function named "filter" or as a default export from the file.');
|
|
33
|
+
}
|
|
34
|
+
async ensureTsxSupport() {
|
|
35
|
+
if (this.tsxRegistered)
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
const tsxUnknown = await import('tsx/esm/api');
|
|
39
|
+
const tsxApi = tsxUnknown;
|
|
40
|
+
if (typeof tsxApi.register === 'function') {
|
|
41
|
+
this.tsxUnregister = tsxApi.register();
|
|
42
|
+
this.tsxRegistered = true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
throw new Error('tsx/esm/api.register is not a function');
|
|
46
|
+
}
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error('TypeScript filter detected but tsx is not available.\n' +
|
|
51
|
+
'Install tsx (npm i -D tsx) or provide a JavaScript file (.js/.mjs).');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
cleanup() {
|
|
55
|
+
if (this.tsxUnregister) {
|
|
56
|
+
try {
|
|
57
|
+
this.tsxUnregister();
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.tsxUnregister = null;
|
|
61
|
+
this.tsxRegistered = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=FilterLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterLoader.js","sourceRoot":"","sources":["../../src/utils/FilterLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAGhC,MAAM,OAAO,YAAY;IAAzB;QACU,kBAAa,GAAG,KAAK,CAAC;QACtB,kBAAa,GAAwB,IAAI,CAAC;IAoEpD,CAAC;IAlEC,KAAK,CAAC,UAAU,CAAC,QAAgB;QAC/B,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;QAE1D,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,EAAE,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QAEhD,4CAA4C;QAC5C,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACpC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAChC,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC;QAEjD,MAAM,aAAa,GAAY,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,YAAY,GAAG,aAAwD,CAAC;QAE9E,IAAI,OAAO,YAAY,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC9C,OAAO,YAAY,CAAC,MAA4B,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,YAAY,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;YACzF,OAAO,YAAY,CAAC,OAA6B,CAAC;QACpD,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,kGAAkG,CAAC,CAAC;IACtH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO;QAE/B,IAAI,CAAC;YACH,MAAM,UAAU,GAAY,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;YACxD,MAAM,MAAM,GAAG,UAA4C,CAAC;YAC5D,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAC1C,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACvC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,CAAC;YACD,6DAA6D;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,wDAAwD;gBACtD,qEAAqE,CACxE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auto-engineer/design-system-importer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@auto-engineer/message-bus": "^0.0
|
|
21
|
+
"@auto-engineer/message-bus": "^0.3.0",
|
|
22
|
+
"figma-api": "2.0.2-beta",
|
|
23
|
+
"tsx": "^3.12.7"
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"start": "tsx src/index.ts",
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
import * as Figma from 'figma-api';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
const figmaApi = new Figma.Api({
|
|
7
|
+
personalAccessToken: process.env.FIGMA_PERSONAL_TOKEN as string,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export interface FigmaComponent {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
thumbnail: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type FilterFunctionType = (components: FigmaComponent[]) => FigmaComponent[];
|
|
17
|
+
|
|
18
|
+
interface FigmaNode {
|
|
19
|
+
type: string;
|
|
20
|
+
name: string;
|
|
21
|
+
children: FigmaNode[];
|
|
22
|
+
description: string;
|
|
23
|
+
thumbnail_url: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class FigmaComponentsBuilder {
|
|
27
|
+
components: FigmaComponent[] = [];
|
|
28
|
+
|
|
29
|
+
async withFigmaComponents() {
|
|
30
|
+
try {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
32
|
+
const response = await figmaApi.getFileComponents({ file_key: process.env.FIGMA_FILE_ID });
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
34
|
+
if (response.meta.components.length > 0) {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
36
|
+
console.log(JSON.stringify(response.meta.components.length, null, 2));
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
38
|
+
this.components = response.meta.components.map(
|
|
39
|
+
(component: { name: string; description: string; thumbnail_url: string }) => ({
|
|
40
|
+
name: component.name,
|
|
41
|
+
description: component.description,
|
|
42
|
+
thumbnail: component.thumbnail_url,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(e);
|
|
48
|
+
}
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async withFigmaComponentSets() {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
55
|
+
const response = await figmaApi.getFileComponentSets({ file_key: process.env.FIGMA_FILE_ID });
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
57
|
+
if (response.meta.component_sets.length > 0) {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
59
|
+
this.components = response.meta.component_sets.map(
|
|
60
|
+
(component: { name: string; description: string; thumbnail_url: string }) => ({
|
|
61
|
+
name: component.name,
|
|
62
|
+
description: component.description ?? 'N/A',
|
|
63
|
+
thumbnail: component.thumbnail_url ?? 'N/A',
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error(e);
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// extractBracketedComponents(item: FigmaComponent): FigmaComponent | null {
|
|
74
|
+
// const match = item.name.match(/<([^>]+)>/);
|
|
75
|
+
// return match ? { ...item, name: match[1].trim() } : null;
|
|
76
|
+
// }
|
|
77
|
+
//
|
|
78
|
+
// withFilteredNamesForMui() {
|
|
79
|
+
// // eslint-disable-next-line @typescript-eslint/unbound-method
|
|
80
|
+
// this.components = this.components.map(this.extractBracketedComponents).filter(Boolean) as FigmaComponent[];
|
|
81
|
+
// }
|
|
82
|
+
|
|
83
|
+
withFilteredNamesForShadcn(): void {
|
|
84
|
+
this.components = this.components
|
|
85
|
+
.map((comp: FigmaComponent): FigmaComponent | null => {
|
|
86
|
+
if (!comp?.name) return null;
|
|
87
|
+
|
|
88
|
+
let str = comp.name.trim();
|
|
89
|
+
|
|
90
|
+
if (str.includes('/')) {
|
|
91
|
+
str = str.split('/')[0].trim();
|
|
92
|
+
} else {
|
|
93
|
+
str = str.split(' ')[0].trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (str.length > 0) {
|
|
97
|
+
str = str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
98
|
+
} else {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...comp,
|
|
104
|
+
name: str.toLowerCase(),
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.filter((c: FigmaComponent | null): c is FigmaComponent => Boolean(c));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
withFilter(filter: FilterFunctionType): void {
|
|
111
|
+
try {
|
|
112
|
+
this.components = filter(this.components);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('Error applying custom filter:', e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async withAllFigmaInstanceNames() {
|
|
119
|
+
try {
|
|
120
|
+
/// ----
|
|
121
|
+
const usedComponentMap = new Map<string, { name: string; description: string; thumbnail: string }>();
|
|
122
|
+
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
124
|
+
const { document } = await figmaApi.getFile({
|
|
125
|
+
file_key: process.env.FIGMA_FILE_ID,
|
|
126
|
+
depth: 1,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function walkTree(node: FigmaNode) {
|
|
130
|
+
if (node.type === 'INSTANCE' && Boolean(node.name)) {
|
|
131
|
+
if (!usedComponentMap.has(node.name)) {
|
|
132
|
+
usedComponentMap.set(node.name, {
|
|
133
|
+
name: node.name,
|
|
134
|
+
description: node.description ?? '',
|
|
135
|
+
thumbnail: node.thumbnail_url ?? '',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
141
|
+
if (node.children) {
|
|
142
|
+
for (const child of node.children) {
|
|
143
|
+
walkTree(child);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
walkTree(document);
|
|
149
|
+
|
|
150
|
+
this.components = Array.from(usedComponentMap.values());
|
|
151
|
+
|
|
152
|
+
/// ----
|
|
153
|
+
|
|
154
|
+
// const components = []
|
|
155
|
+
// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
156
|
+
// const { meta } = await figmaApi.getFileComponentSets({ file_key: process.env.FIGMA_FILE_ID });
|
|
157
|
+
// // console.log(response);
|
|
158
|
+
//
|
|
159
|
+
// console.log('Component sets:', meta.component_sets); // ⬅️ Check this!
|
|
160
|
+
//
|
|
161
|
+
// const componentSetIds = meta.component_sets.map(componentSet => componentSet.node_id); // This must return valid Figma node IDs
|
|
162
|
+
//
|
|
163
|
+
// console.log('ComponentSet IDs:', componentSetIds);
|
|
164
|
+
//
|
|
165
|
+
// const fileNodes = await figmaApi.getFileNodes({
|
|
166
|
+
// file_key: process.env.FIGMA_FILE_ID,
|
|
167
|
+
// }, {
|
|
168
|
+
// ids: componentSetIds.join(','),
|
|
169
|
+
// });
|
|
170
|
+
//
|
|
171
|
+
// for (const node of Object.values(fileNodes.nodes)) {
|
|
172
|
+
// const componentSet = node.document;
|
|
173
|
+
//
|
|
174
|
+
// if (componentSet.type === 'COMPONENT_SET' && componentSet.children?.length) {
|
|
175
|
+
// const variants = componentSet.children;
|
|
176
|
+
// const firstVariant = variants[0]; // or apply filtering logic
|
|
177
|
+
//
|
|
178
|
+
// components.push({
|
|
179
|
+
// name: componentSet.name, // e.g. "Button"
|
|
180
|
+
// id: firstVariant.id, // this is the actual variant node
|
|
181
|
+
// variantName: firstVariant.name // e.g. "primary=true, size=md"
|
|
182
|
+
// });
|
|
183
|
+
// }
|
|
184
|
+
// }
|
|
185
|
+
//
|
|
186
|
+
// fs.writeFileSync('output.json', JSON.stringify(components, null, 2));
|
|
187
|
+
|
|
188
|
+
// if (Boolean(response)) {
|
|
189
|
+
// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
190
|
+
// this.components = response.meta.component_sets.map(
|
|
191
|
+
// (component: { name: string; description: string; thumbnail_url: string }) => ({
|
|
192
|
+
// name: component.name,
|
|
193
|
+
// description: component.description,
|
|
194
|
+
// thumbnail_url: component.thumbnail_url,
|
|
195
|
+
// }),
|
|
196
|
+
// );
|
|
197
|
+
// }
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.error(e);
|
|
200
|
+
}
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
build() {
|
|
205
|
+
return this.components;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { type CommandHandler, type Command, type Event } from '@auto-engineer/message-bus';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { importDesignSystemComponentsFromFigma, ImportStrategy, type FilterFunctionType } from '../index';
|
|
3
|
+
import { FilterLoader } from '../utils/FilterLoader';
|
|
4
4
|
|
|
5
5
|
export type ImportDesignSystemCommand = Command<
|
|
6
6
|
'ImportDesignSystem',
|
|
7
7
|
{
|
|
8
|
-
inputDir: string;
|
|
9
8
|
outputDir: string;
|
|
9
|
+
strategy?: keyof typeof ImportStrategy;
|
|
10
|
+
filterPath?: string;
|
|
10
11
|
}
|
|
11
12
|
>;
|
|
12
13
|
|
|
13
14
|
export type DesignSystemImportedEvent = Event<
|
|
14
15
|
'DesignSystemImported',
|
|
15
16
|
{
|
|
16
|
-
inputDir: string;
|
|
17
17
|
outputDir: string;
|
|
18
18
|
}
|
|
19
19
|
>;
|
|
@@ -22,7 +22,6 @@ export type DesignSystemImportFailedEvent = Event<
|
|
|
22
22
|
'DesignSystemImportFailed',
|
|
23
23
|
{
|
|
24
24
|
error: string;
|
|
25
|
-
inputDir: string;
|
|
26
25
|
outputDir: string;
|
|
27
26
|
}
|
|
28
27
|
>;
|
|
@@ -31,37 +30,30 @@ export type DesignSystemImportFailedEvent = Event<
|
|
|
31
30
|
export async function handleImportDesignSystemCommand(
|
|
32
31
|
command: ImportDesignSystemCommand,
|
|
33
32
|
): Promise<DesignSystemImportedEvent | DesignSystemImportFailedEvent> {
|
|
34
|
-
const {
|
|
33
|
+
const { outputDir, strategy, filterPath } = command.data;
|
|
35
34
|
|
|
36
35
|
try {
|
|
37
|
-
|
|
38
|
-
const inputExists = await fs
|
|
39
|
-
.access(inputDir)
|
|
40
|
-
.then(() => true)
|
|
41
|
-
.catch(() => false);
|
|
36
|
+
const resolvedStrategy = strategy ? ImportStrategy[strategy] : ImportStrategy.WITH_COMPONENT_SETS;
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
correlationId: command.correlationId,
|
|
55
|
-
};
|
|
38
|
+
let filterFn: FilterFunctionType | undefined;
|
|
39
|
+
let loader: FilterLoader | undefined;
|
|
40
|
+
if (typeof filterPath === 'string' && filterPath.trim().length > 0) {
|
|
41
|
+
try {
|
|
42
|
+
loader = new FilterLoader();
|
|
43
|
+
filterFn = await loader.loadFilter(filterPath);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn(`Could not import filter from ${filterPath}. Skipping custom filter.`, e);
|
|
46
|
+
} finally {
|
|
47
|
+
if (loader) loader.cleanup();
|
|
48
|
+
}
|
|
56
49
|
}
|
|
57
50
|
|
|
58
|
-
await
|
|
59
|
-
console.log(`Design system files processed
|
|
51
|
+
await importDesignSystemComponentsFromFigma(outputDir, resolvedStrategy, filterFn);
|
|
52
|
+
console.log(`Design system files processed to ${outputDir}`);
|
|
60
53
|
|
|
61
54
|
return {
|
|
62
55
|
type: 'DesignSystemImported',
|
|
63
56
|
data: {
|
|
64
|
-
inputDir,
|
|
65
57
|
outputDir,
|
|
66
58
|
},
|
|
67
59
|
timestamp: new Date(),
|
|
@@ -76,7 +68,6 @@ export async function handleImportDesignSystemCommand(
|
|
|
76
68
|
type: 'DesignSystemImportFailed',
|
|
77
69
|
data: {
|
|
78
70
|
error: errorMessage,
|
|
79
|
-
inputDir,
|
|
80
71
|
outputDir,
|
|
81
72
|
},
|
|
82
73
|
timestamp: new Date(),
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,18 @@ import * as path from 'path';
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import * as dotenv from 'dotenv';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import * as Figma from 'figma-api';
|
|
6
|
+
import { FigmaComponentsBuilder, type FilterFunctionType } from './FigmaComponentsBuilder';
|
|
7
|
+
// import { AIProvider, generateTextWithAI } from '@auto-engineer/ai-gateway';
|
|
5
8
|
|
|
6
9
|
dotenv.config();
|
|
7
10
|
|
|
8
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
12
|
|
|
13
|
+
const api = new Figma.Api({
|
|
14
|
+
personalAccessToken: process.env.FIGMA_PERSONAL_TOKEN as string,
|
|
15
|
+
});
|
|
16
|
+
|
|
10
17
|
async function getAllTsxFiles(dir: string): Promise<string[]> {
|
|
11
18
|
let results: string[] = [];
|
|
12
19
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -47,6 +54,7 @@ export async function generateDesignSystemMarkdown(inputDir: string, outputDir:
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
export * from './commands/import-design-system';
|
|
57
|
+
export type { FilterFunctionType } from './FigmaComponentsBuilder';
|
|
50
58
|
|
|
51
59
|
async function copyFile(inputDir: string, outputDir: string, file: string): Promise<void> {
|
|
52
60
|
const srcPath = path.join(inputDir, file);
|
|
@@ -92,11 +100,101 @@ export async function copyDesignSystemDocsAndUserPreferences(inputDir: string, o
|
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
104
|
+
async function getFigmaComponents(): Promise<{ name: string; description: string; thumbnail: string }[]> {
|
|
105
|
+
let components: { name: string; description: string; thumbnail: string }[] = [];
|
|
106
|
+
try {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
108
|
+
const response = await api.getFileComponentSets({ file_key: process.env.FIGMA_FILE_ID });
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
110
|
+
components = response.meta.component_sets.map(
|
|
111
|
+
(component: { name: string; description: string; thumbnail_url: string }) => ({
|
|
112
|
+
name: component.name,
|
|
113
|
+
description: component.description,
|
|
114
|
+
thumbnail: component.thumbnail_url,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
console.log('figma response: ', response);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error(e);
|
|
120
|
+
}
|
|
121
|
+
console.log(components.length);
|
|
122
|
+
return components;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function generateMarkdownFromComponents(
|
|
126
|
+
components: { name: string; description: string; thumbnail: string }[],
|
|
127
|
+
): string {
|
|
128
|
+
let md = '# Design System\n\n## Components\n\n';
|
|
129
|
+
|
|
130
|
+
if (components.length === 0) {
|
|
131
|
+
console.warn('No components found');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const component of components) {
|
|
135
|
+
md += `### ${component.name}\nDescription: ${component.description}\nImage: \n\n`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return md;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export enum ImportStrategy {
|
|
142
|
+
WITH_COMPONENTS = 'WITH_COMPONENTS',
|
|
143
|
+
WITH_COMPONENT_SETS = 'WITH_COMPONENT_SETS',
|
|
144
|
+
WITH_ALL_FIGMA_INSTANCES = 'WITH_ALL_FIGMA_INSTANCES',
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function importDesignSystemComponentsFromFigma(
|
|
148
|
+
outputDir: string,
|
|
149
|
+
strategy: ImportStrategy = ImportStrategy.WITH_COMPONENT_SETS,
|
|
150
|
+
filterFn?: FilterFunctionType,
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
const figmaComponentsBuilder = new FigmaComponentsBuilder();
|
|
153
|
+
|
|
154
|
+
if (strategy === ImportStrategy.WITH_COMPONENTS) {
|
|
155
|
+
await figmaComponentsBuilder.withFigmaComponents();
|
|
156
|
+
} else if (strategy === ImportStrategy.WITH_COMPONENT_SETS) {
|
|
157
|
+
await figmaComponentsBuilder.withFigmaComponentSets();
|
|
158
|
+
} else if (strategy === ImportStrategy.WITH_ALL_FIGMA_INSTANCES) {
|
|
159
|
+
await figmaComponentsBuilder.withAllFigmaInstanceNames();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// figmaComponentsBuilder.withFilteredNamesForMui();
|
|
163
|
+
// figmaComponentsBuilder.withFilteredNamesForShadcn();
|
|
164
|
+
|
|
165
|
+
if (filterFn) {
|
|
166
|
+
figmaComponentsBuilder.withFilter(filterFn);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const figmaComponents = figmaComponentsBuilder.build();
|
|
170
|
+
|
|
171
|
+
console.log(figmaComponents.length);
|
|
172
|
+
|
|
173
|
+
const generatedComponentsMDFile = generateMarkdownFromComponents(figmaComponents);
|
|
174
|
+
// const mdWithImageAnalysis = await generateTextWithAI(
|
|
175
|
+
// `
|
|
176
|
+
// Given this markdown file content:
|
|
177
|
+
// ${generatedComponentsMDFile}
|
|
178
|
+
//
|
|
179
|
+
// ------ INSTRUCTIONS -------
|
|
180
|
+
// !IMPORTANT: Only return with Markdown content, nothing else, I will be putting this straight in a .md file. Don't even start the file with \`\`\`markdown
|
|
181
|
+
// For every component Image: Analyze the given image and add to the given component.
|
|
182
|
+
// - add more content to the "Description:" part of the component.
|
|
183
|
+
// - add "Hierarchy:" part under the component, returning the parts a component is build of. like [Button, Input]
|
|
184
|
+
// `,
|
|
185
|
+
// AIProvider.OpenAI,
|
|
186
|
+
// { temperature: 0.2, maxTokens: 8000 },
|
|
187
|
+
// );
|
|
188
|
+
// await fs.mkdir(outputDir, { recursive: true });
|
|
189
|
+
const outPath = path.join(outputDir, 'design-system.md');
|
|
190
|
+
await fs.writeFile(outPath, generatedComponentsMDFile);
|
|
191
|
+
}
|
|
192
|
+
|
|
95
193
|
// Check if this file is being run directly
|
|
96
194
|
if (process.argv[1] === __filename) {
|
|
97
|
-
const [, ,
|
|
98
|
-
if (!
|
|
99
|
-
console.error('Usage: tsx src/index.ts <
|
|
195
|
+
const [, , outputDir] = process.argv;
|
|
196
|
+
if (!outputDir) {
|
|
197
|
+
console.error('Usage: tsx src/index.ts <outputDir>');
|
|
100
198
|
process.exit(1);
|
|
101
199
|
}
|
|
102
200
|
// generateDesignSystemMarkdown(inputDir, outputDir)
|
|
@@ -107,12 +205,20 @@ if (process.argv[1] === __filename) {
|
|
|
107
205
|
// console.error('Error generating design-system.md:', err);
|
|
108
206
|
// process.exit(1);
|
|
109
207
|
// });
|
|
110
|
-
copyDesignSystemDocsAndUserPreferences(inputDir, outputDir)
|
|
208
|
+
// copyDesignSystemDocsAndUserPreferences(inputDir, outputDir)
|
|
209
|
+
// .then(() => {
|
|
210
|
+
// console.log(`design-system.md copied to ${outputDir}`);
|
|
211
|
+
// })
|
|
212
|
+
// .catch((err) => {
|
|
213
|
+
// console.error('Error copying design-system.md:', err);
|
|
214
|
+
// process.exit(1);
|
|
215
|
+
// });
|
|
216
|
+
importDesignSystemComponentsFromFigma(outputDir)
|
|
111
217
|
.then(() => {
|
|
112
|
-
console.log(`design-system.md
|
|
218
|
+
console.log(`design-system.md generated to ${outputDir}`);
|
|
113
219
|
})
|
|
114
220
|
.catch((err) => {
|
|
115
|
-
console.error('Error
|
|
221
|
+
console.error('Error generating design-system.md:', err);
|
|
116
222
|
process.exit(1);
|
|
117
223
|
});
|
|
118
224
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { extname, resolve as resolvePath } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import type { FilterFunctionType } from '../FigmaComponentsBuilder';
|
|
5
|
+
|
|
6
|
+
export class FilterLoader {
|
|
7
|
+
private tsxRegistered = false;
|
|
8
|
+
private tsxUnregister: (() => void) | null = null;
|
|
9
|
+
|
|
10
|
+
async loadFilter(filePath: string): Promise<FilterFunctionType> {
|
|
11
|
+
if (typeof filePath !== 'string' || filePath.trim().length === 0) {
|
|
12
|
+
throw new Error('Filter file path is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const absolutePath = resolvePath(process.cwd(), filePath);
|
|
16
|
+
|
|
17
|
+
if (!existsSync(absolutePath)) {
|
|
18
|
+
throw new Error(`Filter file not found: ${absolutePath}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ext = extname(absolutePath).toLowerCase();
|
|
22
|
+
|
|
23
|
+
// Enable tsx loader for TS files at runtime
|
|
24
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
25
|
+
await this.ensureTsxSupport();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
29
|
+
|
|
30
|
+
const loadedUnknown: unknown = await import(fileUrl);
|
|
31
|
+
const loadedModule = loadedUnknown as { filter?: unknown; default?: unknown };
|
|
32
|
+
|
|
33
|
+
if (typeof loadedModule.filter === 'function') {
|
|
34
|
+
return loadedModule.filter as FilterFunctionType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof loadedModule.default === 'function') {
|
|
38
|
+
console.warn('Using default export from filter module. Prefer a named export "filter".');
|
|
39
|
+
return loadedModule.default as FilterFunctionType;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error('No filter function found. Export a function named "filter" or as a default export from the file.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async ensureTsxSupport(): Promise<void> {
|
|
46
|
+
if (this.tsxRegistered) return;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const tsxUnknown: unknown = await import('tsx/esm/api');
|
|
50
|
+
const tsxApi = tsxUnknown as { register: () => () => void };
|
|
51
|
+
if (typeof tsxApi.register === 'function') {
|
|
52
|
+
this.tsxUnregister = tsxApi.register();
|
|
53
|
+
this.tsxRegistered = true;
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error('tsx/esm/api.register is not a function');
|
|
56
|
+
}
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'TypeScript filter detected but tsx is not available.\n' +
|
|
61
|
+
'Install tsx (npm i -D tsx) or provide a JavaScript file (.js/.mjs).',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cleanup(): void {
|
|
67
|
+
if (this.tsxUnregister) {
|
|
68
|
+
try {
|
|
69
|
+
this.tsxUnregister();
|
|
70
|
+
} finally {
|
|
71
|
+
this.tsxUnregister = null;
|
|
72
|
+
this.tsxRegistered = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|