@auto-engineer/design-system-importer 0.1.1 → 0.2.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-test.log +12 -0
- package/.turbo/turbo-type-check.log +4 -0
- package/CHANGELOG.md +22 -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 -0
- package/dist/commands/import-design-system.d.ts.map +1 -1
- package/dist/commands/import-design-system.js +20 -3
- 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 +91 -3
- 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 +3 -2
- package/src/FigmaComponentsBuilder.ts +207 -0
- package/src/commands/import-design-system.ts +21 -3
- package/src/index.ts +110 -3
- package/src/utils/FilterLoader.ts +76 -0
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auto-engineer/design-system-importer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
21
|
+
"figma-api": "2.0.2-beta",
|
|
22
|
+
"@auto-engineer/message-bus": "^0.1.0"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
25
|
"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,12 +1,15 @@
|
|
|
1
1
|
import { type CommandHandler, type Command, type Event } from '@auto-engineer/message-bus';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
-
import {
|
|
3
|
+
import { importDesignSystemComponentsFromFigma, ImportStrategy, type FilterFunctionType } from '../index';
|
|
4
|
+
import { FilterLoader } from '../utils/FilterLoader';
|
|
4
5
|
|
|
5
6
|
export type ImportDesignSystemCommand = Command<
|
|
6
7
|
'ImportDesignSystem',
|
|
7
8
|
{
|
|
8
9
|
inputDir: string;
|
|
9
10
|
outputDir: string;
|
|
11
|
+
strategy?: keyof typeof ImportStrategy;
|
|
12
|
+
filterPath?: string;
|
|
10
13
|
}
|
|
11
14
|
>;
|
|
12
15
|
|
|
@@ -31,7 +34,7 @@ export type DesignSystemImportFailedEvent = Event<
|
|
|
31
34
|
export async function handleImportDesignSystemCommand(
|
|
32
35
|
command: ImportDesignSystemCommand,
|
|
33
36
|
): Promise<DesignSystemImportedEvent | DesignSystemImportFailedEvent> {
|
|
34
|
-
const { inputDir, outputDir } = command.data;
|
|
37
|
+
const { inputDir, outputDir, strategy, filterPath } = command.data;
|
|
35
38
|
|
|
36
39
|
try {
|
|
37
40
|
// Check if input directory exists
|
|
@@ -55,7 +58,22 @@ export async function handleImportDesignSystemCommand(
|
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
const resolvedStrategy = strategy ? ImportStrategy[strategy] : ImportStrategy.WITH_COMPONENT_SETS;
|
|
62
|
+
|
|
63
|
+
let filterFn: FilterFunctionType | undefined;
|
|
64
|
+
let loader: FilterLoader | undefined;
|
|
65
|
+
if (typeof filterPath === 'string' && filterPath.trim().length > 0) {
|
|
66
|
+
try {
|
|
67
|
+
loader = new FilterLoader();
|
|
68
|
+
filterFn = await loader.loadFilter(filterPath);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.warn(`Could not import filter from ${filterPath}. Skipping custom filter.`, e);
|
|
71
|
+
} finally {
|
|
72
|
+
if (loader) loader.cleanup();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await importDesignSystemComponentsFromFigma(outputDir, resolvedStrategy, filterFn);
|
|
59
77
|
console.log(`Design system files processed from ${inputDir} to ${outputDir}`);
|
|
60
78
|
|
|
61
79
|
return {
|
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,6 +100,97 @@ 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
|
+
// await copyFile("../../../examples/design-system/design-system-principles.md", outputDir, 'design-system-principles.md');
|
|
192
|
+
}
|
|
193
|
+
|
|
95
194
|
// Check if this file is being run directly
|
|
96
195
|
if (process.argv[1] === __filename) {
|
|
97
196
|
const [, , inputDir, outputDir] = process.argv;
|
|
@@ -107,12 +206,20 @@ if (process.argv[1] === __filename) {
|
|
|
107
206
|
// console.error('Error generating design-system.md:', err);
|
|
108
207
|
// process.exit(1);
|
|
109
208
|
// });
|
|
110
|
-
copyDesignSystemDocsAndUserPreferences(inputDir, outputDir)
|
|
209
|
+
// copyDesignSystemDocsAndUserPreferences(inputDir, outputDir)
|
|
210
|
+
// .then(() => {
|
|
211
|
+
// console.log(`design-system.md copied to ${outputDir}`);
|
|
212
|
+
// })
|
|
213
|
+
// .catch((err) => {
|
|
214
|
+
// console.error('Error copying design-system.md:', err);
|
|
215
|
+
// process.exit(1);
|
|
216
|
+
// });
|
|
217
|
+
importDesignSystemComponentsFromFigma(outputDir)
|
|
111
218
|
.then(() => {
|
|
112
|
-
console.log(`design-system.md
|
|
219
|
+
console.log(`design-system.md generated to ${outputDir}`);
|
|
113
220
|
})
|
|
114
221
|
.catch((err) => {
|
|
115
|
-
console.error('Error
|
|
222
|
+
console.error('Error generating design-system.md:', err);
|
|
116
223
|
process.exit(1);
|
|
117
224
|
});
|
|
118
225
|
}
|
|
@@ -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
|
+
}
|