@sxl-studio/token-transformer 1.0.0 → 2.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.
Files changed (93) hide show
  1. package/README.en.md +77 -608
  2. package/README.md +77 -685
  3. package/config/sxl-transform.config.yaml +120 -0
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +411 -141
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config/loader.d.ts +6 -0
  8. package/dist/config/loader.js +160 -0
  9. package/dist/config/loader.js.map +1 -0
  10. package/dist/config/schema.d.ts +847 -0
  11. package/dist/config/schema.js +123 -0
  12. package/dist/config/schema.js.map +1 -0
  13. package/dist/core/color-modifiers.d.ts +31 -0
  14. package/dist/core/color-modifiers.js +289 -0
  15. package/dist/core/color-modifiers.js.map +1 -0
  16. package/dist/core/color-parser.d.ts +24 -0
  17. package/dist/core/color-parser.js +281 -0
  18. package/dist/core/color-parser.js.map +1 -0
  19. package/dist/core/debug-report.d.ts +11 -0
  20. package/dist/core/debug-report.js +161 -0
  21. package/dist/core/debug-report.js.map +1 -0
  22. package/dist/core/incremental.d.ts +18 -0
  23. package/dist/core/incremental.js +105 -0
  24. package/dist/core/incremental.js.map +1 -0
  25. package/dist/core/math.d.ts +3 -0
  26. package/dist/core/math.js +261 -0
  27. package/dist/core/math.js.map +1 -0
  28. package/dist/core/parser.d.ts +4 -3
  29. package/dist/core/parser.js +68 -172
  30. package/dist/core/parser.js.map +1 -1
  31. package/dist/core/resolver.d.ts +26 -0
  32. package/dist/core/resolver.js +431 -0
  33. package/dist/core/resolver.js.map +1 -0
  34. package/dist/core/token-loader.d.ts +11 -0
  35. package/dist/core/token-loader.js +380 -0
  36. package/dist/core/token-loader.js.map +1 -0
  37. package/dist/core/token-parser.d.ts +9 -0
  38. package/dist/core/token-parser.js +138 -0
  39. package/dist/core/token-parser.js.map +1 -0
  40. package/dist/core/token-types.d.ts +7 -0
  41. package/dist/core/token-types.js +132 -0
  42. package/dist/core/token-types.js.map +1 -0
  43. package/dist/core/types.d.ts +154 -63
  44. package/dist/core/writer.d.ts +18 -5
  45. package/dist/core/writer.js +545 -91
  46. package/dist/core/writer.js.map +1 -1
  47. package/dist/emit/css.d.ts +2 -0
  48. package/dist/emit/css.js +538 -0
  49. package/dist/emit/css.js.map +1 -0
  50. package/dist/emit/kotlin.d.ts +2 -0
  51. package/dist/emit/kotlin.js +406 -0
  52. package/dist/emit/kotlin.js.map +1 -0
  53. package/dist/emit/shared.d.ts +13 -0
  54. package/dist/emit/shared.js +127 -0
  55. package/dist/emit/shared.js.map +1 -0
  56. package/dist/emit/swift.d.ts +2 -0
  57. package/dist/emit/swift.js +432 -0
  58. package/dist/emit/swift.js.map +1 -0
  59. package/dist/emit/typography.d.ts +17 -0
  60. package/dist/emit/typography.js +132 -0
  61. package/dist/emit/typography.js.map +1 -0
  62. package/dist/emit/xml.d.ts +2 -0
  63. package/dist/emit/xml.js +311 -0
  64. package/dist/emit/xml.js.map +1 -0
  65. package/dist/index.d.ts +15 -6
  66. package/dist/index.js +13 -5
  67. package/dist/index.js.map +1 -1
  68. package/dist/transformers/css.d.ts +1 -1
  69. package/dist/transformers/css.js +13 -482
  70. package/dist/transformers/css.js.map +1 -1
  71. package/dist/transformers/kotlin.d.ts +2 -2
  72. package/dist/transformers/kotlin.js +14 -442
  73. package/dist/transformers/kotlin.js.map +1 -1
  74. package/dist/transformers/swiftui.d.ts +2 -2
  75. package/dist/transformers/swiftui.js +14 -433
  76. package/dist/transformers/swiftui.js.map +1 -1
  77. package/dist/utils/color.d.ts +7 -5
  78. package/dist/utils/color.js +90 -86
  79. package/dist/utils/color.js.map +1 -1
  80. package/dist/utils/dimension.d.ts +8 -5
  81. package/dist/utils/dimension.js +54 -52
  82. package/dist/utils/dimension.js.map +1 -1
  83. package/dist/utils/naming.d.ts +10 -12
  84. package/dist/utils/naming.js +102 -44
  85. package/dist/utils/naming.js.map +1 -1
  86. package/package.json +30 -10
  87. package/config.example.json +0 -45
  88. package/dist/core/loader.d.ts +0 -8
  89. package/dist/core/loader.js +0 -105
  90. package/dist/core/loader.js.map +0 -1
  91. package/dist/transformers/vue3.d.ts +0 -28
  92. package/dist/transformers/vue3.js +0 -534
  93. package/dist/transformers/vue3.js.map +0 -1
package/README.md CHANGED
@@ -1,715 +1,107 @@
1
- # SXL Studio — Token Transformer
1
+ # SXL Token Transformer
2
2
 
3
- Инструмент для трансформации дизайн-токенов из JSON (DTCG-формат) в валидный код для трёх платформ:
4
- - **Web** — CSS Custom Properties
5
- - **iOS** SwiftUI
6
- - **Android** — Kotlin Compose
7
- - **Vue 3** Composition JSON Vue 3 SFC (`.vue`)
3
+ YAML-first утилита для преобразования дизайн-токенов в:
4
+ - CSS custom properties
5
+ - Swift-константы (SwiftUI)
6
+ - Kotlin-константы (Compose)
7
+ - Android XML ресурсы (`colors.xml`, `dimens.xml`, `strings.xml`, `bools.xml`, `integers.xml`)
8
8
 
9
- ---
10
-
11
- ## Быстрый старт
9
+ ## Установка
12
10
 
13
11
  ```bash
14
- # Установка зависимостей
15
12
  npm install
16
-
17
- # Запуск с конфигом по умолчанию
18
- npx tsx src/cli.ts
19
-
20
- # Запуск с указанием конфига
21
- npx tsx src/cli.ts --config=config.example.json
22
-
23
- # Генерация конфига по умолчанию
24
- npx tsx src/cli.ts --init
25
-
26
- # Трансформация composition → Vue 3
27
- npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
28
- ```
29
-
30
- ---
31
-
32
- ## Структура проекта
33
-
34
- ```
35
- Transformer/
36
- ├── src/
37
- │ ├── cli.ts # Точка входа CLI
38
- │ ├── core/
39
- │ │ ├── types.ts # Типы и интерфейсы
40
- │ │ ├── parser.ts # Парсинг JSON, резолв алиасов, мат. выражения
41
- │ │ ├── loader.ts # Загрузка токенов из файлов
42
- │ │ └── writer.ts # Трансформация и запись файлов
43
- │ ├── transformers/
44
- │ │ ├── css.ts # Генерация CSS Custom Properties
45
- │ │ ├── swiftui.ts # Генерация SwiftUI-кода
46
- │ │ ├── kotlin.ts # Генерация Kotlin Compose-кода
47
- │ │ └── vue3.ts # Composition JSON → Vue 3 SFC
48
- │ └── utils/
49
- │ ├── naming.ts # Утилиты именования (kebab, camel, pascal)
50
- │ ├── color.ts # Парсинг и форматирование цветов
51
- │ └── dimension.ts # Парсинг и форматирование размеров
52
- ├── config.example.json # Конфиг для тестовых токенов
53
- ├── config.admin-ui.json # Конфиг для admin-ui проекта
54
- ├── config.site-ui.json # Конфиг для site-ui проекта
55
- ├── project-style/ # Выходные файлы (генерируется)
56
- └── package.json
57
- ```
58
-
59
- ---
60
-
61
- ## Конфигурация
62
-
63
- Конфиг — JSON-файл со следующей структурой:
64
-
65
- ### Полная схема
66
-
67
- ```json
68
- {
69
- "source": {
70
- "tokenDir": "../Plugin/tokens",
71
- "configFile": "config.json",
72
- "include": ["example/*.json", "core/*.json"],
73
- "exclude": ["config.json", "**/diff-id*.json", "**/composition*"]
74
- },
75
- "platforms": {
76
- "css": {
77
- "outputDir": "./project-style/example",
78
- "prefix": "ds",
79
- "resolveAliases": false,
80
- "splitEffects": true,
81
- "showDescriptions": true,
82
- "codeSyntax": {
83
- "colorFormat": "hex",
84
- "prefix": "ds"
85
- },
86
- "fileMapping": [
87
- {
88
- "sources": ["example/*.json"],
89
- "output": "all-tokens.css",
90
- "filter": {
91
- "types": ["color", "dimension"],
92
- "paths": ["color.", "spacing."],
93
- "excludePaths": ["color.internal."]
94
- }
95
- }
96
- ]
97
- },
98
- "swiftui": {
99
- "outputDir": "./project-style/example",
100
- "prefix": "DS",
101
- "resolveAliases": true,
102
- "showDescriptions": true,
103
- "fileMapping": [
104
- {
105
- "sources": ["example/*.json"],
106
- "output": "AllTokens.swift"
107
- }
108
- ]
109
- },
110
- "kotlin": {
111
- "outputDir": "./project-style/example",
112
- "prefix": "DS",
113
- "resolveAliases": true,
114
- "showDescriptions": true,
115
- "fileMapping": [
116
- {
117
- "sources": ["example/*.json"],
118
- "output": "AllTokens.kt"
119
- }
120
- ]
121
- }
122
- },
123
- "settings": {
124
- "remBase": 16,
125
- "verbose": false
126
- }
127
- }
128
- ```
129
-
130
- ---
131
-
132
- ### Описание полей конфига
133
-
134
- #### `source` — Источник токенов
135
-
136
- | Поле | Тип | По умолчанию | Описание |
137
- |------|-----|-------------|----------|
138
- | `tokenDir` | `string` | обязательный | Путь к папке с JSON-токенами |
139
- | `configFile` | `string` | `"config.json"` | Путь к config.json плагина (внутри tokenDir) |
140
- | `include` | `string[]` | все `**/*.json` | Glob-паттерны файлов для включения |
141
- | `exclude` | `string[]` | `["config.json"]` | Glob-паттерны файлов для исключения |
142
-
143
- #### `platforms` — Настройки платформ
144
-
145
- Каждая платформа (`css`, `swiftui`, `kotlin`) настраивается независимо:
146
-
147
- | Поле | Тип | По умолчанию | Описание |
148
- |------|-----|-------------|----------|
149
- | `outputDir` | `string` | обязательный | Папка для выходных файлов |
150
- | `prefix` | `string` | `""` | Префикс для имён переменных |
151
- | `resolveAliases` | `boolean` | CSS: `false`, Swift/Kotlin: `true` | Резолвить ли алиасы в финальные значения |
152
- | `splitEffects` | `boolean` | `true` | Разделять ли эффекты на отдельные переменные (только CSS) |
153
- | `showDescriptions` | `boolean` | `true` | Включать ли `$description` как комментарии в код |
154
- | `codeSyntax` | `object` | — | Настройки формата кода |
155
- | `fileMapping` | `array` | — | Правила объединения токенов в файлы |
156
-
157
- #### `resolveAliases` — Управление алиасами
158
-
159
- Ключевая настройка, определяющая как обрабатываются алиасные ссылки (`{token.path}`) в составных токенах.
160
-
161
- **CSS (`resolveAliases: false` — по умолчанию):**
162
- ```css
163
- /* Алиасы сохраняются как var() */
164
- --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
165
- --shadow-sm: var(--number-fx-offset-xs) var(--number-fx-offset-none) var(--number-fx-blur-xs) var(--number-fx-offset-none) var(--color-hex-alpha);
166
- ```
167
-
168
- **CSS (`resolveAliases: true`):**
169
- ```css
170
- /* Все значения полностью разрешены */
171
- --ds-typography-heading-xl: 700 30px/24px Inter;
172
- --ds-shadow-sm: 2px 0px 8px 0px rgba(255, 85, 0, 0.502);
173
- ```
174
-
175
- **SwiftUI/Kotlin (`resolveAliases: false`):**
176
- - Чистые алиасы (весь токен = ссылка) → ссылка на константу в том же scope
177
- - Составные токены с алиасными полями → комментарий `/// @ref` / `/** @ref */` с путями
178
-
179
- ```swift
180
- /// @ref {color.hex-full}
181
- static let primary = hexFull // ← прямая ссылка
182
-
183
- /// @ref {fontFamily.sans}, {fontWeight.bold}, {fontSize.3xl}, {lineHeight.relaxed}
184
- static let headingXl: Font = .system(size: 30, weight: .bold) // ← значения + ref-комментарий
185
- ```
186
-
187
- **SwiftUI/Kotlin (`resolveAliases: true` — по умолчанию):**
188
- ```swift
189
- static let primary = Color(red: 1, green: 0.3333, blue: 0, opacity: 1) // ← полностью разрешено
190
- ```
191
-
192
- #### `splitEffects` — Разделение эффектов (только CSS)
193
-
194
- Управляет поведением смешанных эффектов (shadow + blur + backdrop-blur).
195
-
196
- **`splitEffects: true` (по умолчанию):**
197
-
198
- Смешанные эффекты разделяются на отдельные переменные с суффиксами. Каждая переменная соответствует конкретному CSS-свойству:
199
-
200
- ```css
201
- /* box-shadow */
202
- --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
203
- /* filter */
204
- --effects-full-mix-blur: blur(2px);
205
- /* backdrop-filter */
206
- --effects-full-mix-backdrop-blur: blur(20px);
207
- ```
208
-
209
- Использование:
210
- ```css
211
- .card {
212
- box-shadow: var(--effects-full-mix);
213
- filter: var(--effects-full-mix-blur);
214
- backdrop-filter: var(--effects-full-mix-backdrop-blur);
215
- }
216
- ```
217
-
218
- **`splitEffects: false`:**
219
-
220
- Все эффекты выводятся в одну переменную, только shadow-часть:
221
-
222
- ```css
223
- --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
224
- ```
225
-
226
- #### `showDescriptions` — Описания токенов
227
-
228
- Управляет выводом `$description` из JSON в качестве комментариев. Работает на всех платформах.
229
-
230
- **`showDescriptions: true` (по умолчанию):**
231
- ```css
232
- /* Heading XL — all values raw */
233
- --typography-heading-xl: 700 30px/24px Inter;
234
- ```
235
-
236
- ```swift
237
- /// Heading XL — all values raw
238
- static let headingXl: Font = .system(size: 30, weight: .bold)
239
- ```
240
-
241
- ```kotlin
242
- /** Heading XL — all values raw */
243
- val HeadingXl = TextStyle(...)
244
- ```
245
-
246
- **`showDescriptions: false`:**
247
- ```css
248
- --typography-heading-xl: 700 30px/24px Inter;
249
- ```
250
-
251
- ```swift
252
- static let headingXl: Font = .system(size: 30, weight: .bold)
253
- ```
254
-
255
- ```kotlin
256
- val HeadingXl = TextStyle(...)
257
- ```
258
-
259
- #### `fileMapping` — Правила объединения файлов
260
-
261
- Позволяет собрать токены из нескольких JSON-файлов в один выходной файл.
262
-
263
- ```json
264
- {
265
- "sources": ["core/palette.json", "projects/aui/colors.json", "projects/modes/themes/light.json"],
266
- "output": "core.css",
267
- "filter": {
268
- "types": ["color", "dimension"],
269
- "paths": ["color.primary"],
270
- "excludePaths": ["color.internal"]
271
- }
272
- }
273
- ```
274
-
275
- | Поле | Описание |
276
- |------|----------|
277
- | `sources` | Массив путей к JSON-файлам (поддерживает `*` wildcards) |
278
- | `output` | Имя выходного файла (относительно `outputDir`) |
279
- | `filter.types` | Фильтр по типу токена |
280
- | `filter.paths` | Включить только пути начинающиеся с указанных |
281
- | `filter.excludePaths` | Исключить пути начинающиеся с указанных |
282
-
283
- #### `settings` — Глобальные настройки
284
-
285
- | Поле | Тип | По умолчанию | Описание |
286
- |------|-----|-------------|----------|
287
- | `remBase` | `number` | `16` | Базовое значение для вычисления rem |
288
- | `verbose` | `boolean` | `false` | Подробный вывод логов |
289
-
290
- ---
291
-
292
- ## Поддерживаемые типы токенов
293
-
294
- ### Простые типы
295
-
296
- | Тип | CSS | SwiftUI | Kotlin |
297
- |-----|-----|---------|--------|
298
- | `color` | `#hex` / `rgba()` | `Color(red:green:blue:opacity:)` | `Color(0xAARRGGBB)` |
299
- | `dimension` | `16px` / `1rem` | `CGFloat` | `Dp` |
300
- | `spacing`, `sizing` | `16px` | `CGFloat` | `Dp` |
301
- | `borderRadius`, `borderWidth` | `4px` | `CGFloat` | `Dp` |
302
- | `opacity` | `0.5` | `Double` | `Float` |
303
- | `number` | `42` | числовое значение | числовое значение |
304
- | `fontFamily` | `"Inter"` | `String` | `String` |
305
- | `fontWeight` | `700` | `Font.Weight` | `FontWeight` |
306
- | `fontSize`, `lineHeight` | `16px` | `CGFloat` | `TextUnit (sp)` |
307
- | `letterSpacing` | `0.5px` | `CGFloat` | `TextUnit (sp)` |
308
- | `duration` | `200ms` | `String` | `String` |
309
- | `cubicBezier` | `cubic-bezier(...)` | `String` | `String` |
310
- | `boolean` | `true` | `Bool` | `Boolean` |
311
- | `text`, `string` | `"value"` | `String` | `String` |
312
- | `textCase` | `uppercase` | `String` | `String` |
313
- | `textDecoration` | `underline` | `String` | `String` |
314
- | `strokeStyle` | `dashed /* ... */` | `String` | `String` |
315
-
316
- ### Составные типы
317
-
318
- | Тип | CSS | SwiftUI | Kotlin |
319
- |-----|-----|---------|--------|
320
- | `typography` | `700 16px/24px Inter` | `Font.system(size:weight:)` | `TextStyle(...)` |
321
- | `shadow` | `0 4px 8px rgba(...)` | `.shadow(color:radius:x:y:)` | `elevation (Dp)` |
322
- | `border` | `1px solid #000` | `(color:width:style:)` | `BorderStroke(...)` |
323
- | `fill` | `#hex` / `gradient(...)` | `Color(...)` / `Gradient(...)` | `Color(...)` / `Brush(...)` |
324
- | `gradient` | `linear-gradient(...)` | `LinearGradient(...)` | `Brush.linearGradient(...)` |
325
- | `effects` | см. ниже | `.shadow(...)` | `elevation (Dp)` |
326
- | `blur` | `blur(4px)` | `.blur(radius:)` | `Modifier.blur(...)` |
327
- | `backdrop-blur` | `blur(16px)` | `.blur(radius:)` | `Modifier.blur(...)` |
328
- | `transition` | `all 200ms ease` | `String` | `String` |
329
- | `grid` | `repeat(12, 1fr)` | `String` | `String` |
330
-
331
- ---
332
-
333
- ## Обработка Effects (CSS)
334
-
335
- В CSS разные типы эффектов используют разные CSS-свойства. Трансформер автоматически создаёт отдельные переменные для каждого типа:
336
-
337
- ### Один тип эффекта
338
-
339
- ```css
340
- /* box-shadow */
341
- --effects-card: 0 4px 8px rgba(0,0,0,0.1);
342
-
343
- /* filter */
344
- --blur-sm: blur(4px);
345
-
346
- /* backdrop-filter */
347
- --backdrop-blur-md: blur(12px);
348
- ```
349
-
350
- ### Смешанные эффекты (shadow + blur + backdrop-blur)
351
-
352
- Когда токен содержит несколько типов эффектов, они разделяются на отдельные переменные с суффиксами:
353
-
354
- ```css
355
- /* box-shadow */
356
- --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
357
- /* filter */
358
- --effects-full-mix-blur: blur(2px);
359
- /* backdrop-filter */
360
- --effects-full-mix-backdrop-blur: blur(20px);
361
- ```
362
-
363
- Использование в CSS:
364
- ```css
365
- .card {
366
- box-shadow: var(--effects-full-mix);
367
- filter: var(--effects-full-mix-blur);
368
- backdrop-filter: var(--effects-full-mix-backdrop-blur);
369
- }
370
- ```
371
-
372
- ---
373
-
374
- ## Алиасы в составных токенах
375
-
376
- Все составные токены (typography, shadow, border, fill, gradient, effects) поддерживают алиасные ссылки `{token.path}` в любом подсвойстве.
377
-
378
- ### Пример JSON
379
-
380
- ```json
381
- {
382
- "heading": {
383
- "xl": {
384
- "$type": "typography",
385
- "$value": {
386
- "fontFamily": "{fontFamily.sans}",
387
- "fontWeight": "{fontWeight.bold}",
388
- "fontSize": "{fontSize.3xl}",
389
- "lineHeight": "{lineHeight.relaxed}"
390
- }
391
- }
392
- }
393
- }
394
- ```
395
-
396
- ### Результат CSS (resolveAliases: false)
397
-
398
- ```css
399
- --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
400
- ```
401
-
402
- ### Результат CSS (resolveAliases: true)
403
-
404
- ```css
405
- --typography-heading-xl: 700 30px/24px Inter;
406
- ```
407
-
408
- ---
409
-
410
- ## codeSyntax (Figma)
411
-
412
- Если токен содержит `$extensions.figma.codeSyntax`, трансформер использует указанные имена переменных:
413
-
414
- ```json
415
- {
416
- "primary": {
417
- "$value": "#0066FF",
418
- "$extensions": {
419
- "figma.codeSyntax": {
420
- "Web": "var(--color-primary)",
421
- "iOS": "Color.primary",
422
- "Android": "@color/primary"
423
- }
424
- }
425
- }
426
- }
427
13
  ```
428
14
 
429
- ---
430
-
431
- ## Математические выражения
432
-
433
- Токены поддерживают математические выражения, которые вычисляются при трансформации:
434
-
435
- ```json
436
- {
437
- "spacing": {
438
- "sm": { "$value": "{spacing.base} * 2" },
439
- "md": { "$value": "{spacing.base} * 4" }
440
- }
441
- }
442
- ```
443
-
444
- Поддерживаемые операции: `+`, `-`, `*`, `/`, `round()`.
445
-
446
- Выражения вычисляются рекурсивно, включая подсвойства составных токенов.
447
-
448
- ---
449
-
450
- ## Примеры запуска
451
-
452
- ### Трансформация всех проектов
15
+ ## Команды
453
16
 
454
17
  ```bash
455
- # Тестовые токены (example)
456
- npx tsx src/cli.ts --config=config.example.json
18
+ # трансформация в smart incremental режиме (по умолчанию)
19
+ npx tsx src/cli.ts sync --config ./config/sxl-transform.config.yaml
457
20
 
458
- # Боевые проекты
459
- npx tsx src/cli.ts --config=config.admin-ui.json
460
- npx tsx src/cli.ts --config=config.site-ui.json
461
- ```
21
+ # принудительный полный rebuild
22
+ npx tsx src/cli.ts sync --config ./config/sxl-transform.config.yaml --force
462
23
 
463
- ### Пример конфига для нового проекта
24
+ # валидация конфига
25
+ npx tsx src/cli.ts validate-config --config ./config/sxl-transform.config.yaml
464
26
 
465
- ```json
466
- {
467
- "source": {
468
- "tokenDir": "./path/to/tokens",
469
- "exclude": ["config.json"]
470
- },
471
- "platforms": {
472
- "css": {
473
- "outputDir": "./dist/css",
474
- "prefix": "my",
475
- "resolveAliases": false,
476
- "splitEffects": true,
477
- "showDescriptions": true,
478
- "fileMapping": [
479
- {
480
- "sources": ["core/*.json", "themes/light.json"],
481
- "output": "core.css"
482
- },
483
- {
484
- "sources": ["themes/dark.json"],
485
- "output": "themes/dark.css"
486
- }
487
- ]
488
- },
489
- "swiftui": {
490
- "outputDir": "./dist/ios",
491
- "prefix": "My",
492
- "resolveAliases": false,
493
- "showDescriptions": true,
494
- "fileMapping": [
495
- {
496
- "sources": ["core/*.json", "themes/light.json"],
497
- "output": "MyTokens.swift"
498
- }
499
- ]
500
- },
501
- "kotlin": {
502
- "outputDir": "./dist/android",
503
- "prefix": "My",
504
- "resolveAliases": true,
505
- "showDescriptions": false,
506
- "fileMapping": [
507
- {
508
- "sources": ["core/*.json", "themes/light.json"],
509
- "output": "MyTokens.kt"
510
- }
511
- ]
512
- }
513
- },
514
- "settings": {
515
- "remBase": 16
516
- }
517
- }
27
+ # генерация стартового конфига
28
+ npx tsx src/cli.ts init --path ./sxl-transform.config.yaml
518
29
  ```
519
30
 
520
- ---
521
-
522
- ## Composition → Vue 3 (SFC)
523
-
524
- Трансформер поддерживает преобразование composition-токенов из JSON в готовые Vue 3 Single File Components (`.vue`).
31
+ ### Smart vs force
525
32
 
526
- ### Запуск
33
+ - `smart` (по умолчанию): пересобирает только затронутые output-файлы на основе изменённых/удалённых source-файлов и state предыдущего запуска.
34
+ - `force`: пересобирает все output-файлы из конфига безусловно.
527
35
 
528
- ```bash
529
- npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
530
- ```
531
-
532
- | Параметр | Описание |
533
- |----------|----------|
534
- | `--composition` | Путь к JSON-файлу с composition-токеном |
535
- | `--output` | Папка для выходного `.vue` файла (по умолчанию — рядом с исходником) |
536
-
537
- ### Формат входного JSON
538
-
539
- ```json
540
- {
541
- "$type": "composition",
542
- "name": "WButton",
543
- "props": {
544
- "state": ["default", "hover", "active", "disabled"],
545
- "style": ["accent", "secondary", "tertiary"],
546
- "size": ["sm", "md", "lg"]
547
- },
548
- "structure": {
549
- "tag": "FRAME", "class": "btn",
550
- "children": [
551
- { "tag": "INSTANCE", "class": "btn-icon-left", "ref": { "component": "icon-name" } },
552
- { "tag": "TEXT", "class": "btn-label", "content": "Button" }
553
- ]
554
- },
555
- "componentProperties": {
556
- "label": { "type": "TEXT", "layer": "btn-label", "defaultValue": "Button" },
557
- "isIconLeft": { "type": "BOOLEAN", "layer": "btn-icon-left", "defaultValue": false }
558
- },
559
- "styles": { "btn": { "layoutMode": "HORIZONTAL", "fills": ["{accent.medium}"], ... } },
560
- "adapters": {
561
- "style=accent": { "btn": { "fills": ["{info.medium}"] } },
562
- "size=sm": { "btn": { "paddingLeft": "12", ... } },
563
- "state=hover": { "btn": { "opacity": "0.88" } }
564
- }
565
- }
566
- ```
36
+ Дополнительные флаги:
567
37
 
568
- ### Что генерируется
38
+ - `--mode smart|force`
39
+ - `--force` (сокращение для `--mode force`)
40
+ - `--state-file <path>` (переопределить путь state-файла, по умолчанию `<config-name>.state.json`)
569
41
 
570
- Готовый Vue 3 SFC с тремя секциями:
42
+ ## Формат конфига
571
43
 
572
- **`<template>`** HTML-структура из `structure`:
573
- - `FRAME` `<div>` / `<button>` (определяется по имени компонента)
574
- - `TEXT` → `<span>` с привязкой к props или слотам
575
- - `INSTANCE` → компонентный тег (`<WIcon>`, и т.д.)
576
- - `BOOLEAN` свойства → `v-if` директивы
577
- - `INSTANCE_SWAP` → динамический `:name` prop
44
+ Поддерживается только YAML.
45
+ JSON-конфиг умышленно не поддерживается.
578
46
 
579
- **`<script setup lang="ts">`** — логика:
580
- - `defineProps<Props>()` с типами из `props` и `componentProperties`
581
- - `withDefaults()` со значениями по умолчанию
582
- - `useCssModule()` для CSS Modules
583
- - `computed()` для variant/size классов с exhaustive `Record<>` маппингом
584
- - Prop `style` автоматически переименовывается в `variant` (конфликт с Vue)
585
- - `state=disabled` → отдельный `disabled` boolean prop
47
+ Минимальная структура:
586
48
 
587
- **`<style module>`** — CSS:
588
- - Базовые стили из `styles` с маппингом Figma → CSS
589
- - Modifier-классы из `adapters` (`.btn--accent`, `.btn--sm`, и т.д.)
590
- - `state=hover/active/focus` → CSS псевдоклассы (`:hover`, `:active`, `:focus`)
591
- - `state=disabled` → `.btn--disabled` + `:disabled`
592
- - Токен-ссылки `{accent.medium}` → `var(--accent-medium)`
593
- - TEXT/INSTANCE слои: `fills` → `color` (не `background-color`)
49
+ ```yaml
50
+ version: 1
51
+ source:
52
+ tokenDir: ../../Plugin/test/tokens_project_new
53
+ configFile: config.json
54
+ include: ["**/*.json"]
55
+ exclude: ["config.json", "**/diff-id*.json"]
594
56
 
595
- ### Маппинг Figma-свойств → CSS
57
+ options:
58
+ remBase: 16
59
+ collisionStrategy: error # error | suffix | namespace-by-file | namespace-by-mode
60
+ unsupportedTypes:
61
+ default: warn
62
+ types:
63
+ template: skip
64
+ composition: skip
596
65
 
597
- | Figma | CSS |
598
- |-------|-----|
599
- | `layoutMode: "HORIZONTAL"` | `display: flex` |
600
- | `layoutMode: "VERTICAL"` | `display: flex; flex-direction: column` |
601
- | `primaryAxisAlignItems` | `justify-content` |
602
- | `counterAxisAlignItems` | `align-items` |
603
- | `layoutSizingHorizontal: "HUG"` | `width: fit-content` |
604
- | `layoutSizingHorizontal: "FILL"` | `flex: 1` |
605
- | `itemSpacing` | `gap` |
606
- | `padding*` | `padding` (shorthand) |
607
- | `cornerRadius` | `border-radius` |
608
- | `fills: ["{token}"]` | `background-color: var(--token)` / `color: var(--token)` |
609
- | `strokeWeight` + `strokes` | `border` |
610
- | `opacity` | `opacity` |
611
- | `clipsContent` | `overflow: hidden` |
612
- | `fontSize` | `font-size` |
613
- | `fontFamily` | `font-family` |
614
- | `fontWeight` | `font-weight` (с маппингом имён → числа) |
615
- | `textAlignHorizontal` | `text-align` |
66
+ tokenSets:
67
+ - id: root
68
+ selectors:
69
+ - collection: Core
70
+ mode: Default
71
+ - collection: Themes
72
+ mode: Dark
73
+ refModeMap:
74
+ Projects: AUI
75
+ Core: Default
76
+ - files: ["projects/aui/*.json"]
616
77
 
617
- ### Пример результата
618
-
619
- ```vue
620
- <template>
621
- <button :class="[$style['btn'], variantClass, sizeClass, { [$style['btn--disabled']]: disabled }]"
622
- :disabled="disabled || undefined">
623
- <WIcon v-if="isIconLeft" :class="$style['btn-icon-left']" :name="iconLeft" />
624
- <span :class="$style['btn-label']">{{ label }}</span>
625
- </button>
626
- </template>
627
-
628
- <script setup lang="ts">
629
- import { computed, useCssModule } from 'vue'
630
-
631
- type Props = {
632
- variant?: 'accent' | 'secondary' | 'tertiary'
633
- size?: 'sm' | 'md' | 'lg'
634
- label?: string
635
- isIconLeft?: boolean
636
- iconLeft?: string
637
- disabled?: boolean
638
- }
639
-
640
- const props = withDefaults(defineProps<Props>(), {
641
- variant: 'accent',
642
- size: 'sm',
643
- label: 'Button',
644
- isIconLeft: false,
645
- iconLeft: '',
646
- disabled: false,
647
- })
648
-
649
- const $style = useCssModule()
650
-
651
- const variantClass = computed(() => {
652
- const map: Record<NonNullable<Props['variant']>, string> = {
653
- 'accent': $style['btn--accent'],
654
- 'secondary': $style['btn--secondary'],
655
- 'tertiary': $style['btn--tertiary'],
656
- }
657
- return map[props.variant ?? 'accent']
658
- })
659
- </script>
660
-
661
- <style module>
662
- .btn {
663
- display: flex;
664
- justify-content: center;
665
- align-items: center;
666
- gap: 8px;
667
- padding: 8px 16px;
668
- border-radius: 8px;
669
- background-color: var(--accent-medium);
670
- }
671
-
672
- .btn--accent { background-color: var(--info-medium); }
673
- .btn:hover { opacity: 0.88; }
674
- .btn--disabled, .btn:disabled { opacity: 0.4; pointer-events: none; }
675
- </style>
78
+ outputs:
79
+ - id: css-root
80
+ platform: css # css | swift | kotlin | xml
81
+ outputDir: ./project-style/css/adminui
82
+ resolveAliases: false
83
+ splitEffects: true
84
+ showDescriptions: true
85
+ files:
86
+ - tokenSet: root
87
+ output: root.css
676
88
  ```
677
89
 
678
- ---
679
-
680
- ## Справочник по настройке prefix
90
+ ## Quality gates
681
91
 
682
- | Платформа | prefix | Результат именования |
683
- |-----------|--------|---------------------|
684
- | CSS | `""` | `--color-primary` |
685
- | CSS | `"ds"` | `--ds-color-primary` |
686
- | SwiftUI | `""` | `Color.colorPrimary`, `enum Spacing` |
687
- | SwiftUI | `"DS"` | `Color.dsColorPrimary`, `enum DSSpacing` |
688
- | Kotlin | `""` | `DSColors.ColorPrimary`, `DSSpacing.SpacingXs` |
689
- | Kotlin | `"App"` | `AppColors.ColorPrimary`, `AppSpacing.SpacingXs` |
690
-
691
- ---
692
-
693
- ## Troubleshooting
694
-
695
- ### Файл конфига не найден
696
- ```
697
- Config file not found: ...
698
- Run with --init to generate a default config.
92
+ ```bash
93
+ npm run eslint
94
+ npm run typecheck
95
+ npm run test
96
+ npm run build
97
+ npm run check
699
98
  ```
700
- Используйте `--config=путь/к/файлу.json` или `--init` для генерации.
701
-
702
- ### Токены не попадают в выходной файл
703
- Проверьте `fileMapping.sources` — пути указываются **относительно tokenDir**.
704
-
705
- ### `var()` не появляются в CSS
706
- Убедитесь что `"resolveAliases": false` установлен для платформы `css`.
707
-
708
- ### Алиасы не разрешаются
709
- Убедитесь что токен-источник существует в подключённых файлах (`include` / `sources`).
710
99
 
711
- ### Эффекты не разделяются на отдельные переменные
712
- Убедитесь что `"splitEffects": true` установлен для платформы `css`. По умолчанию `true`.
100
+ ## Важно
713
101
 
714
- ### Описания ($description) не отображаются
715
- Убедитесь что `"showDescriptions": true` установлен для нужной платформы. По умолчанию `true`.
102
+ - Типы `template` и `composition` по умолчанию пропускаются.
103
+ - Поддержан `figma.modify`, включая chain и alias-based параметры модификаторов.
104
+ - Поддержана безопасная математика (`+ - * / %`, `round`, `floor`, `ceil`, `clamp`, и др.) без `eval`.
105
+ - Для selector по `collection/mode` можно управлять зависимостями:
106
+ - `includeRefs: true|false` — подключать ли `ref`-цепочки из token `config.json` в resolver scope.
107
+ - `refModeMap` — принудительно выбрать mode для зависимых коллекций (например `Projects: AUI`).