specweave 1.0.274 → 1.0.276

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.
Files changed (38) hide show
  1. package/.claude-plugin/README.md +1 -2
  2. package/.claude-plugin/marketplace.json +0 -11
  3. package/dist/src/adapters/agents-md-generator.d.ts.map +1 -1
  4. package/dist/src/adapters/agents-md-generator.js +1 -4
  5. package/dist/src/adapters/agents-md-generator.js.map +1 -1
  6. package/dist/src/adapters/claude-md-generator.js +1 -1
  7. package/dist/src/adapters/claude-md-generator.js.map +1 -1
  8. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
  9. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  10. package/dist/src/core/lazy-loading/llm-plugin-detector.js +0 -1
  11. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  12. package/dist/src/sync/github-reconciler.d.ts +9 -0
  13. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  14. package/dist/src/sync/github-reconciler.js +41 -9
  15. package/dist/src/sync/github-reconciler.js.map +1 -1
  16. package/dist/src/utils/auto-install.js +1 -1
  17. package/dist/src/utils/auto-install.js.map +1 -1
  18. package/dist/src/utils/generate-skills-index.d.ts.map +1 -1
  19. package/dist/src/utils/generate-skills-index.js +1 -3
  20. package/dist/src/utils/generate-skills-index.js.map +1 -1
  21. package/package.json +1 -1
  22. package/plugins/PLUGINS-INDEX.md +0 -1
  23. package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +9 -0
  24. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +41 -9
  25. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  26. package/plugins/specweave/skills/npm/SKILL.md +155 -34
  27. package/plugins/specweave-frontend/PLUGIN.md +2 -1
  28. package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +1 -6
  29. package/plugins/specweave-frontend/skills/figma/SKILL.md +287 -0
  30. package/plugins/specweave-frontend/skills/frontend/SKILL.md +4 -0
  31. package/plugins/specweave-frontend/skills/frontend-architect/SKILL.md +1 -0
  32. package/plugins/specweave-frontend/skills/frontend-design/SKILL.md +1 -1
  33. package/plugins/specweave-release/commands/npm.md +158 -38
  34. package/plugins/specweave-figma/.claude-plugin/plugin.json +0 -23
  35. package/plugins/specweave-figma/PLUGIN.md +0 -34
  36. package/plugins/specweave-figma/commands/figma-import.md +0 -694
  37. package/plugins/specweave-figma/commands/figma-to-react.md +0 -838
  38. package/plugins/specweave-figma/commands/figma-tokens.md +0 -819
@@ -1,819 +0,0 @@
1
- ---
2
- description: Extract design tokens from Figma and generate token files for theme configuration (CSS variables, JavaScript, JSON, SCSS).
3
- ---
4
-
5
- # /sw-figma:tokens
6
-
7
- Extract design tokens from Figma and generate token files for theme configuration (CSS variables, JavaScript, JSON, SCSS).
8
-
9
- You are a design token expert who automates design system token extraction and synchronization from Figma.
10
-
11
- ## Your Task
12
-
13
- Extract design tokens (colors, typography, spacing, shadows, borders) from Figma and generate production-ready token files in multiple formats.
14
-
15
- ### 1. Design Token Standards
16
-
17
- **W3C Design Tokens Format** (Recommended):
18
- ```json
19
- {
20
- "colors": {
21
- "brand": {
22
- "primary": {
23
- "$type": "color",
24
- "$value": "#0066FF",
25
- "$description": "Primary brand color"
26
- },
27
- "secondary": {
28
- "$type": "color",
29
- "$value": "#00CC66"
30
- }
31
- }
32
- },
33
- "typography": {
34
- "heading": {
35
- "h1": {
36
- "$type": "typography",
37
- "$value": {
38
- "fontFamily": "Inter",
39
- "fontSize": "48px",
40
- "fontWeight": 700,
41
- "lineHeight": 1.2
42
- }
43
- }
44
- }
45
- }
46
- }
47
- ```
48
-
49
- **Token Categories**:
50
- - Colors (brand, semantic, grayscale)
51
- - Typography (font families, sizes, weights, line heights)
52
- - Spacing (margins, paddings, gaps)
53
- - Border radius (corners)
54
- - Shadows (elevations)
55
- - Borders (widths, styles)
56
- - Breakpoints (responsive)
57
- - Z-index (layering)
58
- - Transitions (animations)
59
-
60
- ### 2. Figma Token Extraction
61
-
62
- **Extract Color Tokens from Styles**:
63
-
64
- ```typescript
65
- import { FigmaImporter } from './figma-importer';
66
-
67
- interface ColorToken {
68
- name: string;
69
- value: string;
70
- rgba: { r: number; g: number; b: number; a: number };
71
- type: 'solid' | 'gradient';
72
- description?: string;
73
- }
74
-
75
- async function extractColorTokens(fileKey: string): Promise<ColorToken[]> {
76
- const importer = new FigmaImporter({
77
- accessToken: process.env.FIGMA_ACCESS_TOKEN!,
78
- fileKey,
79
- });
80
-
81
- const file = await importer.fetchFile();
82
- const styles = await importer.fetchStyles();
83
-
84
- const colorTokens: ColorToken[] = [];
85
-
86
- for (const style of styles) {
87
- if (style.style_type === 'FILL') {
88
- const node = findNodeById(file.document, style.node_id);
89
-
90
- if (node?.fills?.[0]) {
91
- const fill = node.fills[0];
92
-
93
- if (fill.type === 'SOLID') {
94
- colorTokens.push({
95
- name: style.name,
96
- value: rgbaToHex(fill.color, fill.opacity),
97
- rgba: {
98
- r: fill.color.r,
99
- g: fill.color.g,
100
- b: fill.color.b,
101
- a: fill.opacity ?? 1,
102
- },
103
- type: 'solid',
104
- description: style.description || undefined,
105
- });
106
- } else if (fill.type === 'GRADIENT_LINEAR') {
107
- colorTokens.push({
108
- name: style.name,
109
- value: convertGradientToCSS(fill),
110
- rgba: fill.gradientStops[0].color,
111
- type: 'gradient',
112
- description: style.description,
113
- });
114
- }
115
- }
116
- }
117
- }
118
-
119
- return colorTokens;
120
- }
121
-
122
- function rgbaToHex(color: { r: number; g: number; b: number }, opacity = 1): string {
123
- const r = Math.round(color.r * 255);
124
- const g = Math.round(color.g * 255);
125
- const b = Math.round(color.b * 255);
126
-
127
- if (opacity < 1) {
128
- const a = Math.round(opacity * 255);
129
- return `#${[r, g, b, a].map(x => x.toString(16).padStart(2, '0')).join('')}`;
130
- }
131
-
132
- return `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`;
133
- }
134
- ```
135
-
136
- **Extract Typography Tokens**:
137
-
138
- ```typescript
139
- interface TypographyToken {
140
- name: string;
141
- fontFamily: string;
142
- fontSize: number;
143
- fontWeight: number;
144
- lineHeight: number;
145
- letterSpacing?: number;
146
- textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
147
- description?: string;
148
- }
149
-
150
- async function extractTypographyTokens(fileKey: string): Promise<TypographyToken[]> {
151
- const importer = new FigmaImporter({
152
- accessToken: process.env.FIGMA_ACCESS_TOKEN!,
153
- fileKey,
154
- });
155
-
156
- const file = await importer.fetchFile();
157
- const styles = await importer.fetchStyles();
158
-
159
- const typographyTokens: TypographyToken[] = [];
160
-
161
- for (const style of styles) {
162
- if (style.style_type === 'TEXT') {
163
- const node = findNodeById(file.document, style.node_id);
164
-
165
- if (node?.style) {
166
- typographyTokens.push({
167
- name: style.name,
168
- fontFamily: node.style.fontFamily,
169
- fontSize: node.style.fontSize,
170
- fontWeight: node.style.fontWeight,
171
- lineHeight: node.style.lineHeightPx / node.style.fontSize,
172
- letterSpacing: node.style.letterSpacing || undefined,
173
- textTransform: node.style.textCase?.toLowerCase() as any,
174
- description: style.description,
175
- });
176
- }
177
- }
178
- }
179
-
180
- return typographyTokens;
181
- }
182
- ```
183
-
184
- **Extract Spacing Tokens from Auto Layout**:
185
-
186
- ```typescript
187
- interface SpacingToken {
188
- name: string;
189
- value: number;
190
- description?: string;
191
- }
192
-
193
- async function extractSpacingTokens(fileKey: string): Promise<SpacingToken[]> {
194
- const importer = new FigmaImporter({
195
- accessToken: process.env.FIGMA_ACCESS_TOKEN!,
196
- fileKey,
197
- });
198
-
199
- const file = await importer.fetchFile();
200
-
201
- const spacingValues = new Set<number>();
202
-
203
- // Traverse all frames with auto layout
204
- traverseNodes(file.document, (node) => {
205
- if (node.layoutMode) {
206
- // Item spacing (gap)
207
- if (node.itemSpacing) {
208
- spacingValues.add(node.itemSpacing);
209
- }
210
-
211
- // Padding
212
- if (node.paddingLeft) spacingValues.add(node.paddingLeft);
213
- if (node.paddingRight) spacingValues.add(node.paddingRight);
214
- if (node.paddingTop) spacingValues.add(node.paddingTop);
215
- if (node.paddingBottom) spacingValues.add(node.paddingBottom);
216
- }
217
- });
218
-
219
- // Generate semantic names (4px → xs, 8px → sm, etc.)
220
- const sortedSpacing = Array.from(spacingValues).sort((a, b) => a - b);
221
-
222
- const sizeMap = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];
223
-
224
- return sortedSpacing.map((value, index) => ({
225
- name: sizeMap[index] || `spacing-${index}`,
226
- value,
227
- description: `${value}px spacing`,
228
- }));
229
- }
230
- ```
231
-
232
- **Extract Shadow Tokens (Elevation)**:
233
-
234
- ```typescript
235
- interface ShadowToken {
236
- name: string;
237
- value: string; // CSS box-shadow value
238
- layers: Array<{
239
- x: number;
240
- y: number;
241
- blur: number;
242
- spread: number;
243
- color: string;
244
- }>;
245
- description?: string;
246
- }
247
-
248
- async function extractShadowTokens(fileKey: string): Promise<ShadowToken[]> {
249
- const importer = new FigmaImporter({
250
- accessToken: process.env.FIGMA_ACCESS_TOKEN!,
251
- fileKey,
252
- });
253
-
254
- const file = await importer.fetchFile();
255
- const styles = await importer.fetchStyles();
256
-
257
- const shadowTokens: ShadowToken[] = [];
258
-
259
- for (const style of styles) {
260
- if (style.style_type === 'EFFECT') {
261
- const node = findNodeById(file.document, style.node_id);
262
-
263
- if (node?.effects) {
264
- const shadows = node.effects.filter(
265
- (e: any) => e.type === 'DROP_SHADOW' || e.type === 'INNER_SHADOW'
266
- );
267
-
268
- if (shadows.length > 0) {
269
- const layers = shadows.map((shadow: any) => ({
270
- x: shadow.offset.x,
271
- y: shadow.offset.y,
272
- blur: shadow.radius,
273
- spread: shadow.spread || 0,
274
- color: rgbaToHex(shadow.color, shadow.color.a),
275
- }));
276
-
277
- const cssValue = layers
278
- .map(
279
- (layer) =>
280
- `${layer.x}px ${layer.y}px ${layer.blur}px ${layer.spread}px ${layer.color}`
281
- )
282
- .join(', ');
283
-
284
- shadowTokens.push({
285
- name: style.name,
286
- value: cssValue,
287
- layers,
288
- description: style.description,
289
- });
290
- }
291
- }
292
- }
293
- }
294
-
295
- return shadowTokens;
296
- }
297
- ```
298
-
299
- ### 3. Token Organization
300
-
301
- **Hierarchical Token Structure**:
302
-
303
- ```
304
- tokens/
305
- ├── global/
306
- │ ├── colors.json # Brand colors, primitives
307
- │ ├── typography.json # Font scales
308
- │ ├── spacing.json # Spacing scale
309
- │ └── shadows.json # Elevation scale
310
- ├── semantic/
311
- │ ├── colors.json # Semantic colors (success, error, warning)
312
- │ ├── components.json # Component-specific tokens
313
- │ └── themes.json # Light/dark theme mappings
314
- └── platform/
315
- ├── web.json # Web-specific tokens (CSS variables)
316
- ├── ios.json # iOS-specific (Swift)
317
- └── android.json # Android-specific (XML)
318
- ```
319
-
320
- **Naming Convention** (BEM-inspired):
321
- ```
322
- category-element-modifier-state
323
-
324
- Examples:
325
- - color-brand-primary
326
- - color-semantic-success
327
- - color-text-primary
328
- - typography-heading-h1
329
- - spacing-component-button-padding
330
- - shadow-elevation-1
331
- - border-radius-sm
332
- ```
333
-
334
- ### 4. Token File Formats
335
-
336
- **CSS Variables** (Most Common):
337
-
338
- ```css
339
- /* tokens/global/colors.css */
340
- :root {
341
- /* Brand Colors */
342
- --color-brand-primary: #0066FF;
343
- --color-brand-secondary: #00CC66;
344
- --color-brand-tertiary: #FF6600;
345
-
346
- /* Semantic Colors */
347
- --color-semantic-success: #00CC66;
348
- --color-semantic-error: #FF3333;
349
- --color-semantic-warning: #FFCC00;
350
- --color-semantic-info: #0066FF;
351
-
352
- /* Grayscale */
353
- --color-gray-50: #F9FAFB;
354
- --color-gray-100: #F3F4F6;
355
- --color-gray-200: #E5E7EB;
356
- --color-gray-300: #D1D5DB;
357
- --color-gray-400: #9CA3AF;
358
- --color-gray-500: #6B7280;
359
- --color-gray-600: #4B5563;
360
- --color-gray-700: #374151;
361
- --color-gray-800: #1F2937;
362
- --color-gray-900: #111827;
363
-
364
- /* Typography */
365
- --font-family-sans: 'Inter', -apple-system, sans-serif;
366
- --font-family-mono: 'JetBrains Mono', monospace;
367
-
368
- --font-size-xs: 0.75rem; /* 12px */
369
- --font-size-sm: 0.875rem; /* 14px */
370
- --font-size-base: 1rem; /* 16px */
371
- --font-size-lg: 1.125rem; /* 18px */
372
- --font-size-xl: 1.25rem; /* 20px */
373
- --font-size-2xl: 1.5rem; /* 24px */
374
- --font-size-3xl: 1.875rem; /* 30px */
375
- --font-size-4xl: 2.25rem; /* 36px */
376
-
377
- --font-weight-normal: 400;
378
- --font-weight-medium: 500;
379
- --font-weight-semibold: 600;
380
- --font-weight-bold: 700;
381
-
382
- --line-height-tight: 1.25;
383
- --line-height-normal: 1.5;
384
- --line-height-relaxed: 1.75;
385
-
386
- /* Spacing */
387
- --spacing-xs: 0.25rem; /* 4px */
388
- --spacing-sm: 0.5rem; /* 8px */
389
- --spacing-md: 1rem; /* 16px */
390
- --spacing-lg: 1.5rem; /* 24px */
391
- --spacing-xl: 2rem; /* 32px */
392
- --spacing-2xl: 3rem; /* 48px */
393
- --spacing-3xl: 4rem; /* 64px */
394
-
395
- /* Shadows */
396
- --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
397
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
398
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
399
- --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
400
-
401
- /* Border Radius */
402
- --radius-sm: 0.25rem; /* 4px */
403
- --radius-md: 0.5rem; /* 8px */
404
- --radius-lg: 0.75rem; /* 12px */
405
- --radius-xl: 1rem; /* 16px */
406
- --radius-full: 9999px;
407
-
408
- /* Transitions */
409
- --transition-fast: 150ms;
410
- --transition-base: 250ms;
411
- --transition-slow: 350ms;
412
- }
413
- ```
414
-
415
- **JavaScript/TypeScript**:
416
-
417
- ```typescript
418
- // tokens/global/colors.ts
419
- export const colors = {
420
- brand: {
421
- primary: '#0066FF',
422
- secondary: '#00CC66',
423
- tertiary: '#FF6600',
424
- },
425
- semantic: {
426
- success: '#00CC66',
427
- error: '#FF3333',
428
- warning: '#FFCC00',
429
- info: '#0066FF',
430
- },
431
- gray: {
432
- 50: '#F9FAFB',
433
- 100: '#F3F4F6',
434
- 200: '#E5E7EB',
435
- 300: '#D1D5DB',
436
- 400: '#9CA3AF',
437
- 500: '#6B7280',
438
- 600: '#4B5563',
439
- 700: '#374151',
440
- 800: '#1F2937',
441
- 900: '#111827',
442
- },
443
- } as const;
444
-
445
- // tokens/global/typography.ts
446
- export const typography = {
447
- fontFamily: {
448
- sans: "'Inter', -apple-system, sans-serif",
449
- mono: "'JetBrains Mono', monospace",
450
- },
451
- fontSize: {
452
- xs: '0.75rem',
453
- sm: '0.875rem',
454
- base: '1rem',
455
- lg: '1.125rem',
456
- xl: '1.25rem',
457
- '2xl': '1.5rem',
458
- '3xl': '1.875rem',
459
- '4xl': '2.25rem',
460
- },
461
- fontWeight: {
462
- normal: 400,
463
- medium: 500,
464
- semibold: 600,
465
- bold: 700,
466
- },
467
- lineHeight: {
468
- tight: 1.25,
469
- normal: 1.5,
470
- relaxed: 1.75,
471
- },
472
- } as const;
473
-
474
- // tokens/index.ts
475
- export { colors } from './global/colors';
476
- export { typography } from './global/typography';
477
- export { spacing } from './global/spacing';
478
- export { shadows } from './global/shadows';
479
-
480
- // Type-safe token access
481
- export type ColorToken = keyof typeof colors.brand | keyof typeof colors.semantic;
482
- ```
483
-
484
- **JSON** (W3C Design Tokens Format):
485
-
486
- ```json
487
- {
488
- "$schema": "https://design-tokens.org/schemas/v1.0.0/design-tokens.schema.json",
489
- "colors": {
490
- "brand": {
491
- "primary": {
492
- "$type": "color",
493
- "$value": "#0066FF",
494
- "$description": "Primary brand color used for main actions and links"
495
- },
496
- "secondary": {
497
- "$type": "color",
498
- "$value": "#00CC66",
499
- "$description": "Secondary brand color for accents"
500
- }
501
- },
502
- "semantic": {
503
- "success": {
504
- "$type": "color",
505
- "$value": "{colors.brand.secondary}",
506
- "$description": "Success state color (references brand.secondary)"
507
- }
508
- }
509
- },
510
- "typography": {
511
- "heading": {
512
- "h1": {
513
- "$type": "typography",
514
- "$value": {
515
- "fontFamily": "{typography.fontFamily.sans}",
516
- "fontSize": "{typography.fontSize.4xl}",
517
- "fontWeight": "{typography.fontWeight.bold}",
518
- "lineHeight": "{typography.lineHeight.tight}"
519
- }
520
- }
521
- }
522
- }
523
- }
524
- ```
525
-
526
- **SCSS Variables**:
527
-
528
- ```scss
529
- // tokens/global/_colors.scss
530
- $color-brand-primary: #0066FF;
531
- $color-brand-secondary: #00CC66;
532
-
533
- $color-gray-50: #F9FAFB;
534
- $color-gray-100: #F3F4F6;
535
- // ...
536
-
537
- // tokens/global/_typography.scss
538
- $font-family-sans: 'Inter', -apple-system, sans-serif;
539
- $font-size-base: 1rem;
540
- $font-weight-bold: 700;
541
-
542
- // tokens/_index.scss
543
- @forward 'global/colors';
544
- @forward 'global/typography';
545
- @forward 'global/spacing';
546
- ```
547
-
548
- **Tailwind CSS Config**:
549
-
550
- ```javascript
551
- // tailwind.config.js
552
- const { colors, typography, spacing, shadows } = require('./tokens');
553
-
554
- module.exports = {
555
- theme: {
556
- extend: {
557
- colors: {
558
- brand: colors.brand,
559
- semantic: colors.semantic,
560
- gray: colors.gray,
561
- },
562
- fontFamily: typography.fontFamily,
563
- fontSize: typography.fontSize,
564
- fontWeight: typography.fontWeight,
565
- lineHeight: typography.lineHeight,
566
- spacing: spacing,
567
- boxShadow: shadows,
568
- },
569
- },
570
- };
571
- ```
572
-
573
- ### 5. Token Generation Automation
574
-
575
- **Complete Pipeline**:
576
-
577
- ```typescript
578
- import fs from 'fs/promises';
579
- import path from 'path';
580
-
581
- interface TokenGeneratorConfig {
582
- fileKey: string;
583
- outputDir: string;
584
- formats: Array<'css' | 'scss' | 'js' | 'ts' | 'json' | 'tailwind'>;
585
- }
586
-
587
- async function generateTokens(config: TokenGeneratorConfig) {
588
- const { fileKey, outputDir, formats } = config;
589
-
590
- // 1. Extract tokens from Figma
591
- const colorTokens = await extractColorTokens(fileKey);
592
- const typographyTokens = await extractTypographyTokens(fileKey);
593
- const spacingTokens = await extractSpacingTokens(fileKey);
594
- const shadowTokens = await extractShadowTokens(fileKey);
595
-
596
- const allTokens = {
597
- colors: colorTokens,
598
- typography: typographyTokens,
599
- spacing: spacingTokens,
600
- shadows: shadowTokens,
601
- };
602
-
603
- // 2. Create output directory
604
- await fs.mkdir(outputDir, { recursive: true });
605
-
606
- // 3. Generate formats
607
- if (formats.includes('css')) {
608
- const css = generateCSSVariables(allTokens);
609
- await fs.writeFile(path.join(outputDir, 'tokens.css'), css);
610
- }
611
-
612
- if (formats.includes('scss')) {
613
- const scss = generateSCSSVariables(allTokens);
614
- await fs.writeFile(path.join(outputDir, '_tokens.scss'), scss);
615
- }
616
-
617
- if (formats.includes('js') || formats.includes('ts')) {
618
- const js = generateJavaScript(allTokens);
619
- const ext = formats.includes('ts') ? '.ts' : '.js';
620
- await fs.writeFile(path.join(outputDir, `tokens${ext}`), js);
621
- }
622
-
623
- if (formats.includes('json')) {
624
- const json = generateW3CTokens(allTokens);
625
- await fs.writeFile(path.join(outputDir, 'tokens.json'), json);
626
- }
627
-
628
- if (formats.includes('tailwind')) {
629
- const tailwind = generateTailwindConfig(allTokens);
630
- await fs.writeFile(path.join(outputDir, 'tailwind.tokens.js'), tailwind);
631
- }
632
-
633
- console.log(`✅ Generated design tokens in ${formats.join(', ')} format(s)`);
634
- }
635
-
636
- function generateCSSVariables(tokens: any): string {
637
- let css = ':root {\n';
638
-
639
- // Colors
640
- css += ' /* Colors */\n';
641
- tokens.colors.forEach((token: any) => {
642
- css += ` --color-${token.name.toLowerCase().replace(/\s+/g, '-')}: ${token.value};\n`;
643
- });
644
-
645
- // Typography
646
- css += '\n /* Typography */\n';
647
- tokens.typography.forEach((token: any) => {
648
- const name = token.name.toLowerCase().replace(/\s+/g, '-');
649
- css += ` --font-family-${name}: ${token.fontFamily};\n`;
650
- css += ` --font-size-${name}: ${token.fontSize}px;\n`;
651
- css += ` --font-weight-${name}: ${token.fontWeight};\n`;
652
- css += ` --line-height-${name}: ${token.lineHeight};\n`;
653
- });
654
-
655
- // Spacing
656
- css += '\n /* Spacing */\n';
657
- tokens.spacing.forEach((token: any) => {
658
- css += ` --spacing-${token.name}: ${token.value}px;\n`;
659
- });
660
-
661
- // Shadows
662
- css += '\n /* Shadows */\n';
663
- tokens.shadows.forEach((token: any) => {
664
- css += ` --shadow-${token.name.toLowerCase().replace(/\s+/g, '-')}: ${token.value};\n`;
665
- });
666
-
667
- css += '}\n';
668
-
669
- return css;
670
- }
671
-
672
- function generateJavaScript(tokens: any): string {
673
- const js = `
674
- export const colors = {
675
- ${tokens.colors.map((t: any) => ` '${t.name}': '${t.value}',`).join('\n')}
676
- };
677
-
678
- export const typography = {
679
- ${tokens.typography.map((t: any) => ` '${t.name}': {
680
- fontFamily: '${t.fontFamily}',
681
- fontSize: ${t.fontSize},
682
- fontWeight: ${t.fontWeight},
683
- lineHeight: ${t.lineHeight},
684
- },`).join('\n')}
685
- };
686
-
687
- export const spacing = {
688
- ${tokens.spacing.map((t: any) => ` '${t.name}': ${t.value},`).join('\n')}
689
- };
690
-
691
- export const shadows = {
692
- ${tokens.shadows.map((t: any) => ` '${t.name}': '${t.value}',`).join('\n')}
693
- };
694
- `;
695
-
696
- return js.trim();
697
- }
698
-
699
- // Usage
700
- generateTokens({
701
- fileKey: 'ABC123XYZ456',
702
- outputDir: './src/design-tokens',
703
- formats: ['css', 'ts', 'json', 'tailwind'],
704
- }).catch(console.error);
705
- ```
706
-
707
- ### 6. Token Synchronization (Watch Mode)
708
-
709
- **Auto-sync on Figma Changes**:
710
-
711
- ```typescript
712
- import { watch } from 'fs';
713
-
714
- async function watchFigmaTokens(fileKey: string, outputDir: string, pollInterval = 60000) {
715
- let lastVersion: string | null = null;
716
-
717
- setInterval(async () => {
718
- const importer = new FigmaImporter({
719
- accessToken: process.env.FIGMA_ACCESS_TOKEN!,
720
- fileKey,
721
- });
722
-
723
- const file = await importer.fetchFile();
724
-
725
- if (lastVersion && file.version !== lastVersion) {
726
- console.log(`🔄 Figma file updated (v${file.version}). Re-generating tokens...`);
727
-
728
- await generateTokens({
729
- fileKey,
730
- outputDir,
731
- formats: ['css', 'ts', 'json'],
732
- });
733
-
734
- console.log('✅ Tokens synchronized!');
735
- }
736
-
737
- lastVersion = file.version;
738
- }, pollInterval);
739
-
740
- console.log(`👀 Watching Figma file for changes (polling every ${pollInterval / 1000}s)...`);
741
- }
742
-
743
- // Usage
744
- watchFigmaTokens('ABC123XYZ456', './src/design-tokens', 60000); // Check every minute
745
- ```
746
-
747
- ### 7. Theme Support (Light/Dark Mode)
748
-
749
- **Generate Theme Tokens**:
750
-
751
- ```typescript
752
- // tokens/themes/light.ts
753
- export const lightTheme = {
754
- colors: {
755
- background: '#FFFFFF',
756
- foreground: '#111827',
757
- primary: '#0066FF',
758
- secondary: '#6B7280',
759
- border: '#E5E7EB',
760
- },
761
- };
762
-
763
- // tokens/themes/dark.ts
764
- export const darkTheme = {
765
- colors: {
766
- background: '#111827',
767
- foreground: '#F9FAFB',
768
- primary: '#3B82F6',
769
- secondary: '#9CA3AF',
770
- border: '#374151',
771
- },
772
- };
773
-
774
- // CSS Variables with theme support
775
- :root {
776
- --color-background: #FFFFFF;
777
- --color-foreground: #111827;
778
- }
779
-
780
- [data-theme="dark"] {
781
- --color-background: #111827;
782
- --color-foreground: #F9FAFB;
783
- }
784
- ```
785
-
786
- ## Workflow
787
-
788
- 1. Ask about Figma file and token extraction preferences
789
- 2. Fetch color, typography, spacing, and shadow styles from Figma
790
- 3. Analyze and categorize tokens (brand, semantic, primitives)
791
- 4. Ask about output formats (CSS, SCSS, JS, JSON, Tailwind)
792
- 5. Generate hierarchical token structure
793
- 6. Create token files in requested formats
794
- 7. Set up theme support if needed (light/dark modes)
795
- 8. Provide integration examples for each format
796
- 9. Optionally set up watch mode for auto-sync
797
- 10. Generate documentation with token usage examples
798
-
799
- ## When to Use
800
-
801
- - Setting up design systems from Figma
802
- - Synchronizing design tokens across platforms (web, iOS, Android)
803
- - Migrating from hardcoded values to token-based theming
804
- - Implementing light/dark mode support
805
- - Automating design-to-code token updates
806
- - Standardizing design values across teams
807
-
808
- ## Best Practices
809
-
810
- 1. **Organization**: Use hierarchical structure (global → semantic → component)
811
- 2. **Naming**: Follow consistent naming conventions (BEM or similar)
812
- 3. **Formats**: Generate multiple formats for different use cases
813
- 4. **Aliasing**: Use token references for semantic tokens (W3C format)
814
- 5. **Documentation**: Include descriptions from Figma styles
815
- 6. **Versioning**: Track Figma file versions in metadata
816
- 7. **Automation**: Set up CI/CD for token synchronization
817
- 8. **Validation**: Validate token values before deployment
818
-
819
- Extract and sync design tokens with production-ready automation!