Skip to content

Commit f9ce9bc

Browse files
authored
AUI Figma: Added components CLI option and fixed processing issues (#295)
1 parent 5bf0946 commit f9ce9bc

4 files changed

Lines changed: 124 additions & 42 deletions

File tree

.changeset/silent-trains-spend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@adaptive-web/adaptive-ui-designer-figma": patch
3+
---
4+
5+
Added `components` CLI option and fixed processing issues

package-lock.json

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/adaptive-ui-designer-figma/src/cli/main.ts

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,60 @@ program
1717
.name(programName)
1818
.description('A CLI tool for generating CSS stylesheets from Figma Library Components')
1919
.requiredOption('-l, --library <path>', 'Path to the library configuration file.')
20+
.option('-c, --components <names>', 'Comma-delimited list or wildcard pattern of components to generate (e.g., "anchor,button" or "content navigation *")')
2021
.action(main);
2122

2223
interface ProgramOptions {
2324
library: string;
25+
components?: string;
2426
}
2527

26-
async function main({ library }: ProgramOptions) {
28+
/**
29+
* Matches a component name against a pattern that may include wildcards.
30+
* @param componentName - The component name to test
31+
* @param pattern - The pattern to match against (supports * wildcard)
32+
* @returns true if the component name matches the pattern
33+
*/
34+
function matchesPattern(componentName: string, pattern: string): boolean {
35+
const normalizedComponent = componentName.toLowerCase().trim();
36+
const normalizedPattern = pattern.toLowerCase().trim();
37+
38+
// Convert wildcard pattern to regex
39+
const regexPattern = normalizedPattern
40+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except *
41+
.replace(/\*/g, '.*'); // Convert * to .*
42+
43+
const regex = new RegExp(`^${regexPattern}$`);
44+
return regex.test(normalizedComponent);
45+
}
46+
47+
/**
48+
* Filters component names based on comma-delimited patterns or wildcards.
49+
* @param componentNames - Array of all available component names
50+
* @param filterString - Comma-delimited patterns (e.g., "button,card" or "content navigation *")
51+
* @returns Array of matching component names
52+
*/
53+
function filterComponentNames(componentNames: string[], filterString: string): string[] {
54+
const patterns = filterString.split(',').map(p => p.trim()).filter(p => p.length > 0);
55+
56+
if (patterns.length === 0) {
57+
return componentNames;
58+
}
59+
60+
const matchedNames = new Set<string>();
61+
62+
for (const pattern of patterns) {
63+
for (const componentName of componentNames) {
64+
if (matchesPattern(componentName, pattern)) {
65+
matchedNames.add(componentName);
66+
}
67+
}
68+
}
69+
70+
return Array.from(matchedNames).sort(alphabetize);
71+
}
72+
73+
async function main({ library, components }: ProgramOptions) {
2774
const configPath = path.resolve(process.cwd(), library);
2875
logger.neutral('Validating library config file: ' + configPath);
2976
const libraryConfig = await LibraryConfig.create(configPath);
@@ -60,14 +107,14 @@ async function main({ library }: ProgramOptions) {
60107
const libraryComponentSetsResponse = await client.getFileComponentSets(libraryConfig.file);
61108

62109
if (libraryComponentSetsResponse.error || libraryComponentSetsResponse.status !== 200) {
63-
logger.fail(`Accessing Figma library component sets failed with status code ${libraryComponentSetsResponse.status}`);
110+
logger.fail(`Accessing Figma library component sets failed with status code ${libraryComponentSetsResponse.status}: ${(libraryComponentSetsResponse as any).err}`);
64111
process.exit(1);
65112
}
66113

67114
const libraryComponentsResponse = await client.getFileComponents(libraryConfig.file);
68115

69116
if (libraryComponentsResponse.error || libraryComponentsResponse.status !== 200) {
70-
logger.fail(`Accessing Figma library components failed with status code ${libraryComponentsResponse.status}`);
117+
logger.fail(`Accessing Figma library components failed with status code ${libraryComponentsResponse.status}: ${(libraryComponentsResponse as any).err}`);
71118
process.exit(1);
72119
}
73120

@@ -84,32 +131,48 @@ async function main({ library }: ProgramOptions) {
84131
// Also filter out components which aren't in a container frame (assume they are helper/utility for now)
85132
const hasContainingFrame = component.containing_frame !== undefined;
86133

87-
return !hasComponentSet && !hasContainingFrame;
134+
return !hasComponentSet && hasContainingFrame;
88135
});
89136
const allComponents = libraryComponentSets.concat(uniqueComponents);
90137

91138
const componentNames = allComponents.map((value) => value.name).sort(alphabetize);
92-
const pickComponentsRequest = {
93-
type: 'list',
94-
name: 'all',
95-
message: 'Which component stylesheets would you like to generate?',
96-
choices: ['All', 'Choose which'],
97-
};
98-
99-
const pickComponentsResponse = await inquirer.prompt([pickComponentsRequest]);
100-
const componentNamesToRender: string[] = [];
101-
102-
if (pickComponentsResponse.all !== 'All') {
103-
const chooseComponentsRequest = {
104-
type: 'checkbox',
105-
name: 'which',
106-
message: 'Choose components:',
107-
choices: componentNames,
108-
};
109-
const components = await inquirer.prompt([chooseComponentsRequest]);
110-
componentNamesToRender.push(...components.which);
139+
let componentNamesToRender: string[] = [];
140+
141+
// If components filter is provided via CLI, use it directly
142+
if (components) {
143+
componentNamesToRender = filterComponentNames(componentNames, components);
144+
145+
if (componentNamesToRender.length === 0) {
146+
logger.fail(`No components matched the pattern: "${components}"`);
147+
logger.neutral(`Available components:\n${componentNames.join(', ')}`);
148+
process.exit(1);
149+
}
150+
151+
logger.success(`Found ${componentNamesToRender.length} component${componentNamesToRender.length === 1 ? '' : 's'} matching "${components}":`);
152+
logger.neutral(componentNamesToRender.join(', '));
111153
} else {
112-
componentNamesToRender.push(...componentNames);
154+
// Interactive mode when no -c parameter is provided
155+
const pickComponentsRequest = {
156+
type: 'list',
157+
name: 'all',
158+
message: 'Which component stylesheets would you like to generate?',
159+
choices: ['All', 'Choose which'],
160+
};
161+
162+
const pickComponentsResponse = await inquirer.prompt([pickComponentsRequest]);
163+
164+
if (pickComponentsResponse.all !== 'All') {
165+
const chooseComponentsRequest = {
166+
type: 'checkbox',
167+
name: 'which',
168+
message: 'Choose components:',
169+
choices: componentNames,
170+
};
171+
const chosenComponents = await inquirer.prompt([chooseComponentsRequest]);
172+
componentNamesToRender.push(...chosenComponents.which);
173+
} else {
174+
componentNamesToRender.push(...componentNames);
175+
}
113176
}
114177

115178
if (componentNamesToRender.length === 0) {
@@ -123,10 +186,17 @@ async function main({ library }: ProgramOptions) {
123186
}\n${componentNamesToRender.join(', ')}`
124187
);
125188

126-
const confirm = await inquirer.prompt({ type: 'confirm', message: 'Would you like to continue?', name: 'confirm' });
189+
// Only prompt for confirmation in interactive mode
190+
if (!components) {
191+
const confirm = await inquirer.prompt({ type: 'confirm', message: 'Would you like to continue?', name: 'confirm' });
192+
193+
if (!confirm.confirm) {
194+
logger.neutral(`Exiting ${programName}`);
195+
process.exit(0);
196+
}
197+
}
127198

128-
if (confirm.confirm) {
129-
logger.success('Generating component stylesheets. This may take a moment.');
199+
logger.success('Generating component stylesheets. This may take a moment.');
130200

131201
const nameLookup = new Set(componentNamesToRender);
132202
const componentsToRender = allComponents.filter((value) => {
@@ -159,11 +229,6 @@ async function main({ library }: ProgramOptions) {
159229
})
160230
);
161231

162-
// process components
163-
} else {
164-
logger.neutral(`Exiting ${programName}`);
165-
}
166-
167232
process.exit(0);
168233
}
169234

packages/adaptive-ui-designer-figma/src/lib/node-parser.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,20 @@ export function parseNode(node: FigmaRestAPI.Node): PluginUINodeData {
4848
const appliedDesignTokens: AppliedDesignTokens = appliedTokensPluginData
4949
? deserializeMap(appliedTokensPluginData)
5050
: new AppliedDesignTokens();
51-
const appliedStyleModules: AppliedStyleModules = appliedStylesPluginData
52-
? JSON.parse(appliedStylesPluginData)
53-
: new AppliedStyleModules();
51+
52+
// Parse appliedStyleModules and ensure it's an array
53+
let appliedStyleModules: AppliedStyleModules = new AppliedStyleModules();
54+
if (appliedStylesPluginData) {
55+
try {
56+
const parsed = JSON.parse(appliedStylesPluginData);
57+
// Ensure we have an array - if parsed data is not an array, ignore it
58+
if (Array.isArray(parsed)) {
59+
appliedStyleModules = new AppliedStyleModules(...parsed);
60+
}
61+
} catch (e) {
62+
console.warn('Failed to parse appliedStyleModules:', e);
63+
}
64+
}
5465

5566
return {
5667
id: node.id,

0 commit comments

Comments
 (0)