Utilities

Transformer

SXL Token Transformer: YAML-first CLI для детерминированной конвертации JSON дизайн-токенов в CSS, SCSS, Swift, UIKit, Kotlin, Android XML и JSON manifest.

Overview

SXL Token Transformer (@sxl-studio/token-transformer) — CLI-утилита, которая преобразует JSON-токены в готовые платформенные артефакты:

  • CSS custom properties
  • SCSS variables
  • Swift константы и типизированные спецификации для Xcode/SwiftUI
  • UIKit константы и типизированные спецификации для iOS UIKit
  • Kotlin константы и типизированные спецификации для Android Compose
  • Android XML ресурсы (colors.xml, dimens.xml, strings.xml, bools.xml, integers.xml)
  • JSON manifest metadata для документации, Storybook token viewer, devtools, аудита и миграций

Transformer предназначен для детерминированной сборки, где выбор токенов и структура файлов задаются в конфиге.

Что Поддерживается

  • YAML-only конфиг (version: 1) со строгой валидацией схемы
  • Выбор по collection/mode из config.json токен-проекта
  • Опциональный авто-поиск config.json в source.tokenDir, если source.configFile не указан
  • Inline-настройка наследования через top-level projectConfig (без config.json)
  • Компоновка конфига из нескольких YAML-файлов через extends
  • Выбор по файлам/glob и смешанные селекторы в одном token set
  • Упорядоченный merge источников
  • Стратегии коллизий: error | suffix | namespace-by-file | namespace-by-mode
  • Политика unresolved aliases на token set: error | warn | ignore
  • Резолв алиасов с защитой от циклов/переполнения глубины
  • Математические выражения (+ - * / %, round, floor, ceil, clamp и др.) — вычисляются только для числовых типов токенов и числовых полей композитов; строковые значения (text, fontFamily, …) никогда не вычисляются
  • Цветовые модификаторы figma.modify (включая chain)
  • Инлайн-мутации цвета ("rgba({color}, {alpha})") — на CSS/SCSS всегда запекаются в вычисленный литерал: rgba(var(--hex), var(--amount)) невалиден на этапе вычисления значений CSS
  • Smart incremental generation со state v2 content hashes, проверкой generated-файлов, stale cleanup, state lock, explain output и watch mode
  • Интерактивная обработка проблем с генерацией debug-отчёта
  • Автогенерация компонентных файлов через splitBySourceFile (включая prefixPattern)
  • First-class manifest output (platform: manifest) из того же resolved token graph, что CSS/native outputs
  • Swift, SwiftUI и UIKit support types генерируются с Sendable conformance для проектов на Swift 6 strict concurrency

Типы токенов template, composition, grid и custom намеренно исключены из трансформации.

Матрица Поддержки Типов (CSS / SCSS / Swift / UIKit / Kotlin)

Поддерживаются:

  • color, gradient, img, fill, opacity
  • dimension, number, spacing, sizing
  • border, borderWidth, borderRadius, strokeStyle
  • typography, fontFamily, fontWeight, fontStyle, fontSize, lineHeight, letterSpacing, paragraphSpacing, paragraphIndent, textCase, textDecoration
  • shadow, backdrop-blur, blur, glass, effects
  • boolean, text
  • transition, duration, cubicBezier (как value/spec токены, без auto-apply хелперов к UI-элементам)

Исключены:

  • template, composition, grid, custom

Manifest поддерживает те же разрешённые типы, что CSS/SCSS/Swift/UIKit/Kotlin, и намеренно не эмитит запрещённые template, composition, grid, custom.

Поведение без галлюцинаций:

  • Невалидные значения не “додумываются”: токен получает error и не эмитится.
  • Поведение по неразрешимым алиасам задается unresolvedAliases (error | warn | ignore).
  • Для прод-профиля рекомендуется unsupportedTypes.default: error.

Warning (Android XML): XML-выгрузка намеренно ограничена нативными Android-ресурсами и не дает полного parity по всем token types относительно CSS/SCSS/Swift/UIKit/Kotlin. Transformer генерирует только валидные примитивы (color, dimen, string, bool, integer) и поддерживаемые drawable (для gradients/fill/img). Сложные token-объекты без нативного представления в XML не сериализуются и попадают в диагностику.

Покрытие Android XML (Warning)

Генерация Android XML в первую очередь рассчитана на нативную совместимость Android resources, а не на 1:1 поддержку всех типов токенов.

Поддерживается в XML:

  • примитивные ресурсы: color, dimension/spacing/sizing, number (integer), text, boolean
  • декомпозиция typography в примитивные ресурсы (fontFamily, fontWeight, fontSize, lineHeight, letterSpacing и т.д.)
  • drawable ресурсы для поддерживаемых структур gradient/fill/img

Не представимо в XML полностью (попадает в диагностику):

  • сложные композитные эффекты (effects, сложный glass, сложные цепочки blur/backdrop)
  • семантика transition/easing (transition, duration, cubicBezier) как runtime-анимация
  • token-объекты, которым нужны runtime API, а не статический resource XML

Совместимость Swift / UIKit с concurrency

Swift и UIKit output включают support structs/enums с Sendable conformance, поэтому сгенерированные token specs можно использовать в кодовых базах Swift 6 strict concurrency без локальных wrapper-типов. Это относится к типизированным спецификациям fills, gradients, typography, shadows/effects, borders, stroke styles, images и transitions.

Установка

BASH
npm install --save-dev @sxl-studio/token-transformer
# или
pnpm add -D @sxl-studio/token-transformer
# или
yarn add -D @sxl-studio/token-transformer

Команды CLI

BASH
# smart incremental sync (режим по умолчанию)
npx sxl-transform sync --config ./sxl-transform.config.yaml
# или через pnpm
pnpm exec sxl-transform sync --config ./sxl-transform.config.yaml

# принудительный полный rebuild
npx sxl-transform sync --config ./sxl-transform.config.yaml --force

# только валидация конфига
npx sxl-transform validate-config --config ./sxl-transform.config.yaml

# генерация стартового YAML-конфига
npx sxl-transform init --path ./sxl-transform.config.yaml

# help по конкретной команде
npx sxl-transform help
npx sxl-transform help sync
npx sxl-transform help validate-config
npx sxl-transform help init
npx sxl-transform help version
npx sxl-transform validate-config --help

# версия
npx sxl-transform version
npx sxl-transform --version

CLI help

CLI поддерживает общий help и help по конкретной команде:

BASH
sxl-transform help
sxl-transform help sync
sxl-transform help validate-config
sxl-transform help init
sxl-transform help version

Эквивалентные сокращения:

BASH
sxl-transform --help
sxl-transform -h
sxl-transform sync --help
sxl-transform validate-config --help
sxl-transform init --help
sxl-transform --version
sxl-transform -v

Поведение help:

  • sxl-transform help показывает список команд, issue modes, state-поведение и scope flags.
  • sxl-transform help <command> показывает options и поведение конкретной команды.
  • sxl-transform <command> --help эквивалентен help <command>.
  • sync является командой по умолчанию, поэтому sxl-transform --config ./sxl-transform.config.yaml валиден.
  • version, --version и -v выводят установленную версию пакета.

Режимы сборки

  • smart (по умолчанию): использует state прошлого запуска и пересобирает только изменившиеся output-файлы.
  • force: пересобирает все output-файлы из конфига.
BASH
npx sxl-transform sync --config ./sxl-transform.config.yaml --mode smart
npx sxl-transform sync --config ./sxl-transform.config.yaml --mode force

Примеры выборочного запуска:

BASH
# scope по output id (можно повторять)
npx sxl-transform sync --config ./sxl-transform.config.yaml --only-output css-app --only-output swift-app

# scope по output-файлам (glob)
npx sxl-transform sync --config ./sxl-transform.config.yaml --only-file "modes/dark.css"
npx sxl-transform sync --config ./sxl-transform.config.yaml --only-file "css-app:modes/dark.css"

State-файл:

  • путь по умолчанию: <config-name>.state.json (рядом с конфигом)
  • переопределение: --state-file ./custom/path/transform.state.json

Поведение smart state:

  • Формат state-файла — v2, с content hashes для source/output. Старый v1 state игнорируется, поэтому первый запуск после обновления делает один полный rebuild.
  • Изменения source-файлов определяются по content hash, а не только по размеру или времени изменения. Правка значения или алиаса ловится даже при неизменной длине файла.
  • Generated-файлы перед ранним выходом smart mode сверяются со state. Удалённые или изменённые извне файлы пересобираются автоматически.
  • Scoped-прогоны (--only-output / --only-file) не поглощают изменения вне scope: они остаются pending до следующего полного smart-прогона.
  • Устаревшие generated-файлы по умолчанию удаляются в smart и force. options.removeStaleOutputs: false оставляет на диске outputs, source-токены которых исчезли.

Дополнительные sync-флаги:

  • --explain: выводит по каждому output причину rebuild или skip.
  • --watch: оставляет процесс запущенным, следит за token/config изменениями и пересобирает в smart mode. Требует Node.js 20 или новее.
  • --no-state-lock: отключает кооперативный <state>.lock, который защищает от одновременной записи в один state-файл.

Важно Smart mode считает generated-файлы артефактами под управлением Transformer. Если отдельный postprocess-скрипт переписывает эти же файлы после sxl-transform sync, следующий smart-прогон пересоберёт их, потому что содержимое больше не совпадает со state. Держите postprocess вне managed output paths, переносите его в config/output hooks Transformer или запускайте до финального sync, который сохраняет state.

Режимы обработки проблем (issue-action):

  • ask (интерактивный режим по умолчанию)
  • debug-stop
  • debug-continue
  • autofix
  • skip

Совместимость:

  • --issues <mode> работает как alias для --issue-action <mode>

Модель Конфига (YAML)

YAML
version: 1
source:
  tokenDir: ./tokens
  # опционально: автоматически возьмется ./tokens/config.json, если файл существует
  # configFile: config.json
  include: ["**/*.json"]
  exclude: ["config.json", "**/diff-id*.json"]

projectConfig:
  collections:
    - name: Core
      modes:
        - name: Default
          files:
            core/palettes.json: enabled

options:
  remBase: 16
  collisionStrategy: error
  maxAliasDepth: 20
  removeStaleOutputs: true # false оставляет output-файлы исчезнувших токенов на диске
  unsupportedTypes:
    default: error
    types:
      template: skip
      composition: skip
      grid: skip

tokenSets:
  - id: app-root
    unresolvedAliases: error
    selectors:
      - collection: Core
        mode: Default
      - collection: Product
        mode: Default
      - collection: Themes
        mode: Light
        refModeMap:
          Product: Default
          Core: Default

outputs:
  - id: css-root
    platform: css
    outputDir: ./design-system/css/customer-app
    prefix: ds
    suffix: v2
    codeSyntax:
      source: extension-first
      template: "{var(--css-variable)}"
    resolveAliases: false
    splitEffects: true
    showDescriptions: true
    files:
      - tokenSet: app-root
        output: root.css

  - id: tokens-manifest
    platform: manifest
    outputDir: ./design-system/manifest/customer-app
    files:
      - tokenSet: app-root
        output: tokens-manifest.json
        options:
          schemaVersion: "1.0"
          includeResolvedValue: true
          includeOriginalValue: true
          includeReferences: true
          includeSource: true
          includeExtensions: false
          includePrivate: true
          groupBy: flat

Настройка Наследования (Два Поддерживаемых Способа)

collection + mode selectors корректно работают, если настроен хотя бы один источник наследования:

  1. Через файл конфигурации token-проекта:
    • source.configFile: config.json (или source.configFile можно не указывать, если config.json лежит в source.tokenDir)
  2. Через inline-настройку в YAML:
    • top-level projectConfig с collections -> modes -> files

Если не настроено ни одно из двух, пайплайн завершится ошибкой PROJECT_CONFIG_REQUIRED, а debug-файл покажет готовые примеры исправления и ссылку на документацию.

Разделение Конфига По Проектам / Платформам

Один большой конфиг можно разбить на несколько целевых и запускать каждый отдельно через --config.

Пример структуры:

  • sxl-transform.customer-app.config.yaml
  • sxl-transform.admin-panel.config.yaml
  • sxl-transform.marketing-site.config.yaml
  • sxl-transform-components.config.yaml
  • опционально платформенные конфиги (*-css.config.yaml, *-scss.config.yaml, *-swift.config.yaml, *-kotlin.config.yaml, *-manifest.config.yaml)

Для общих частей используйте extends:

YAML
# sxl-transform.customer-app.css.config.yaml
extends:
  - ./configs/sxl-transform.base.config.yaml
  - ./configs/sxl-transform.customer-app.tokensets.config.yaml
outputs:
  - id: css-customer-app
    platform: css
    outputDir: ./design-system/css/customer-app
    files:
      - tokenSet: customer-app-root
        output: root.css

Поведение selectors и resolver scope

Selector должен задавать одно из двух:

  • collection + mode (выбор из token-project config), или
  • files / include (прямой выбор файлов по glob).

В одном token set можно комбинировать несколько selectors; merge идёт в порядке описания.

YAML
tokenSets:
  - id: semantic-root
    unresolvedAliases: warn
    selectors:
      - collection: Themes
        mode: Light
        includeRefs: true
        refModeMap:
          Product: Default
          Core: Default
      - include:
          - components/**/*.style.json
        exclude:
          - components/**/__draft__/*.json

Ключи selector:

  • includeRefs -> добавляет referenced collection/mode файлы в resolver scope (true по умолчанию)
  • refModeMap -> фиксирует mode для связанных коллекций, чтобы сборка была детерминированной
  • exclude -> исключает файлы из emit и из resolver scope

Политика unresolvedAliases на token set:

  • error -> unresolved aliases валят сборку
  • warn -> показывают диагностику, но сборка продолжается
  • ignore -> диагностика unresolved aliases для набора подавляется

Поведение outputs и дефолты

Для каждого output:

  • resolveAliases по умолчанию false для css и scss;
  • resolveAliases по умолчанию true для swift, uikit, kotlin, xml, manifest;
  • splitEffects по умолчанию true;
  • showDescriptions по умолчанию true.
  • SCSS-выгрузка ориентирована на token-first подключение: подключайте root-файлы раньше component-файлов в entrypoint, чтобы межфайловые $token-ссылки резолвились корректно.
  • Manifest output генерирует JSON и не зависит от Style Dictionary или postprocess-скриптов.
  • можно задать fallback codeSyntax на уровне output:
    • source: extension-first | config-first | extension-only | config-only
    • template: один из
      • {var(--css-variable)}
      • {$sass-variable}
      • {@less-variable}
      • {UpperCamelCase}
      • {lowerCamelCase}
      • {UPPER_SNAKE_CASE}
      • {lower_snake_case}

Правила приоритета codeSyntax.source:

  • extension-first (по умолчанию): приоритет у токенового $extensions.figma.codeSyntax, шаблон output — fallback.
  • config-first: приоритет у шаблона output, токеновый extension — fallback.
  • extension-only: используется только токеновый extension.
  • config-only: используется только шаблон output.

Каждый output должен содержать минимум один блок: files, bundles, bundlesFromCollections или indexes.

Каждый элемент outputs[].files[] должен содержать:

  • либо output (статичный путь файла),
  • либо splitBySourceFile (динамическая генерация по source-файлам).

Manifest Output

Используйте platform: manifest, когда потребителям нужны token metadata для документации, Storybook foundations, token viewer, devtools, аудита или миграций.

Manifest строится из того же resolved token graph, что и остальные outputs для выбранного tokenSet. Каждая запись токена содержит name, cssVar, path, type, sourceFile, references и эффективное value. По умолчанию также включаются resolvedValue и originalValue.

level и weight не выводятся из path или названия collection. Transformer читает их из token extensions (level, sxl.level, sxl.weight), если они есть; иначе значение будет null.

Manifest options:

  • schemaVersion: строка версии schema на верхнем уровне, default "1.0"
  • includeResolvedValue: включить resolvedValue, default true
  • includeOriginalValue: включить исходное JSON $value, default true
  • includeReferences: извлекать references из исходного value до resolve, default true
  • includeSource: включить collection, mode, tokenSet, level, default true
  • includeExtensions: включить token $extensions, default false
  • includePrivate: включить токены, скрытые через $extensions.figma.hide, default true
  • cssVarPrefix / cssVarSuffix: переопределить CSS variable affixes только для manifest metadata
  • groupBy: flat | collection | mode | file, default flat

Пример нескольких manifest-файлов:

YAML
outputs:
  - id: manifest-customer-app
    platform: manifest
    outputDir: ./design-system/manifest/customer-app
    files:
      - tokenSet: customer-app-root
        output: tokens-manifest.json
      - tokenSet: customer-app-core
        output: manifest/core.json
      - tokenSet: customer-app-components
        output: manifest/components.json
        options:
          groupBy: file

Выборочный scope в CLI:

  • --only-output <id>: запуск только для указанных output id (ключ можно повторять).
  • --only-file <glob>: запуск только для указанных output-файлов (ключ можно повторять).
  • В режиме --only-file stale-cleanup вне выбранных файлов не выполняется.

Кастомные Форматтеры (По Типу / По Платформе)

Transformer поддерживает модульные output hooks, чтобы пользователь мог переопределять трансформацию без форка пакета.

Подключение модуля делается на уровне output через outputs[].options.customFormatters:

YAML
outputs:
  - id: css-main
    platform: css
    outputDir: ./out/css
    files:
      - tokenSet: root
        output: root.css
    options:
      customFormatters:
        module: ./transform-hooks.mjs
        exportName: plugin

Форма hook-модуля:

JS
/** @type {import("@sxl-studio/token-transformer").CustomFormatterPlugin} */
export const plugin = {
  tokenTypeFormatters: {
    css: {
      color: ({ token }) => ({ ...token, resolvedValue: "#00FF00", value: "#00FF00" }),
    },
  },
  platformEmitters: {
    css: ({ emitDefault, input, relativeOutput }) => {
      const base = emitDefault(input, relativeOutput);
      return { ...base, content: `${base.content}\n/* custom footer */\n` };
    },
  },
};

Поведение:

  • tokenTypeFormatters[platform][type] переопределяет форматтер конкретного типа токена.
  • undefined -> оставить дефолтную обработку токена.
  • null -> удалить токен из output.
  • FlatToken или FlatToken[] -> заменить/расширить результат по токену.
  • platformEmitters[platform] позволяет переопределить весь emitter платформы для конкретного output.

Диагностика:

  • невалидный/отсутствующий hook-модуль дает явные CUSTOM_FORMATTER_* диагностические события (error / warn) без тихого fallback.

Пример Для Нескольких Режимов (Root + Modes)

YAML
tokenSets:
  - id: customer-app-root
    selectors:
      - collection: Core
        mode: Default
      - collection: Product
        mode: Default
      - collection: Themes
        mode: Light
      - collection: Breakpoints
        mode: Desktop
      - collection: Surface
        mode: Low

  - id: customer-app-dark
    selectors:
      - collection: Themes
        mode: Dark

outputs:
  - id: css-customer-app
    platform: css
    outputDir: ./design-system/css/customer-app
    resolveAliases: false
    files:
      - tokenSet: customer-app-root
        output: root.css
      - tokenSet: customer-app-dark
        output: modes/dark.css

Автогенерация Компонентных CSS Файлов

Чтобы для каждого *.style.json автоматически создавать отдельный CSS-файл (без ручного добавления новых блоков в конфиг):

YAML
tokenSets:
  - id: components-style
    unresolvedAliases: warn
    selectors:
      - files:
          - components/**/*.style.json

outputs:
  - id: css-components
    platform: css
    outputDir: ./design-system/components
    files:
      - tokenSet: components-style
        splitBySourceFile:
          include:
            - components/**/*.style.json
          exclude:
            - components/**/__draft__/*.json
          outputPattern: "{component}/{component}.css"
          prefixPattern: "c-{component}-"
          suffixPattern: "-v2"

Если добавить новый components/WAccordion/.../WAccordion.style.json, следующая сборка автоматически создаст ds/components/WAccordion/WAccordion.css.

Placeholders для splitBySourceFile:

  • {sourceFile} -> полный относительный путь файла-источника
  • {sourceDir} -> директория source-файла
  • {sourceBase} -> имя файла с расширением
  • {sourceName} -> имя файла без расширения
  • {sourceStem} -> sourceName без суффикса .style
  • {component} -> имя компонента из components/<name>/... или fallback на stem
  • {fileName} -> алиас {sourceStem}

Аффиксы имен:

  • outputs[].prefix / outputs[].suffix применяются ко всем именам токенов в output.
  • outputs[].files[].prefix / outputs[].files[].suffix переопределяют аффиксы для конкретного mapping.
  • splitBySourceFile.prefixPattern / splitBySourceFile.suffixPattern задают динамические аффиксы для split-генерации.

CSS / SCSS Output Contract

CSS output по умолчанию использует :root. Задайте selector на уровне file mapping, если mode-файл или component-файл должен генерироваться под конкретным runtime selector:

YAML
outputs:
  - id: css-app
    platform: css
    outputDir: ./design-system/styles/css
    files:
      - tokenSet: app-root
        output: root.css
        selector: ":root"
      - tokenSet: app-dark
        output: themes/dark.css
        selector: "[data-theme='dark']"
      - tokenSet: components-style
        splitBySourceFile:
          include:
            - components/**/*.style.json
          outputPattern: "{component}/{component}.css"
        selector:
          - ":root"
          - "[data-component='{component}']"

Для split-файлов selector поддерживает те же placeholders, что и outputPattern, включая {component}, {sourceStem} и {sourceFile}. Массив selectors генерируется как comma-separated selector list.

Важное правило по платформам:

  • selector применяется только для platform: css.
  • Если selector указан для scss, swift, uikit, kotlin, xml или manifest, Transformer выдаст SELECTOR_UNSUPPORTED_PLATFORM и проигнорирует настройку.

CSS или SCSS entrypoints генерируются через indexes:

YAML
outputs:
  - id: css-app
    platform: css
    outputDir: ./design-system/styles/css
    files:
      - tokenSet: app-root
        output: root.css
      - tokenSet: app-dark
        output: themes/dark.css
    indexes:
      - output: index.css
        imports:
          - ./root.css
        includeGenerated:
          - themes/*.css
          - components/**/*.css
        exclude:
          - index.css
          - "**/*.internal.css"
        sort: generated-order # generated-order | alpha
        skipMissing: true
        strict: false

Поведение indexes:

  • CSS indexes генерируют @import "...";.
  • SCSS indexes генерируют @use "..." as *;.
  • indexes поддержаны только для css и scss; native и XML outputs получают INDEX_UNSUPPORTED_PLATFORM.
  • Явные imports резолвятся относительно index-файла. Отсутствующие explicit imports считаются ошибкой, если не указано skipMissing: true.
  • includeGenerated матчится по generated files того же output. Smart mode использует текущий run плюс previous state, поэтому пересборка только index не теряет imports на неизменённые файлы.
  • Index-файлы хранятся в state и удаляются stale cleanup, если они удалены из config или стали stale.
  • Index-файлы участвуют в --only-output и --only-file; для точечной пересборки используйте output-scoped patterns вроде css-app:index.css.
  • Self-imports и конфликтующие output paths фиксируются как ошибки.

bundles нужны, когда несколько source token files нужно сгруппировать в один output без отдельного tokenSet:

YAML
outputs:
  - id: css-components
    platform: css
    outputDir: ./design-system/components
    files:
      - tokenSet: components-style
        splitBySourceFile:
          include:
            - components/**/*.style.json
          exclude:
            - components/WBanner/**/*.style.json
          outputPattern: "{component}/{component}.css"
    bundles:
      - tokenSet: components-style
        output: WBanner/WBanner.css
        include:
          - components/WBanner/**/*.style.json
        exclude: []
        selector: ":root"

Bundles используют тот же token graph, resolver, alias policy, unsupported-type policy, token filters, custom formatters и platform emitter, что и обычные files. Это не конкатенация уже сгенерированных строк. Bundles доступны для всех output platforms, но selector внутри bundle всё равно применяется только к CSS.

Bundle-only outputs валидны. Это полезно, когда проекту нужны сгруппированные component artifacts без генерации отдельного файла на каждый source token file.

Component bundles из config.json / projectConfig

Используйте bundlesFromCollections, когда группы component bundle уже описаны в source.configFile или top-level projectConfig. Transformer читает enabled mode.files из matched collections и генерирует один bundle на collection.

YAML
outputs:
  - id: css-components
    platform: css
    outputDir: ./design-system/components
    resolveAliases: false
    files:
      - tokenSet: components-style
        splitBySourceFile:
          include:
            - components/**/*.style.json
          excludeBundledSources: true
          outputPattern: "{fileName}.css"
    bundlesFromCollections:
      - tokenSet: components-style
        mode: Default
        include:
          - WBanner
          - WInput
          - WTab
        fileInclude:
          - components/**/*.style.json
        fileExclude:
          - "**/__draft__/*.json"
        outputPattern: "{collection}.css"
        selector:
          - ":root"
          - "[data-theme='dark']"
        strict: true
    indexes:
      - output: index.css
        includeGenerated:
          - "**/*.css"
        exclude:
          - index.css
        sort: alpha

Поведение:

  • Поддерживаются и source.configFile, и top-level projectConfig.
  • Используются только файлы со статусом enabled в mode.files.
  • fileInclude / fileExclude фильтруют файлы collection до генерации bundle.
  • Токены всё равно берутся из tokenSet; содержимое bundle ограничивается токенами, чей sourceFile входит в matched collection files.
  • Collection bundles проходят тот же pipeline, что и ручные bundles: platform emitter, selector, filter, prefix/suffix, unsupported-token policy, alias policy, custom formatters, state/smart mode, stale cleanup, --only-output и --only-file.
  • indexes[].includeGenerated видит collection bundles так же, как обычные generated files.

Маппинг collections:

YAML
bundlesFromCollections:
  - tokenSet: components-style
    mode: Default
    collections:
      include: ["W*"]
      exclude: ["WLegacy*"]
    fileInclude: ["components/**/*.style.json"]
    outputPattern: "{collectionKebab}.css"

Также поддержана flat-форма:

YAML
bundlesFromCollections:
  - tokenSet: components-style
    mode: Default
    include: ["WBanner", "WInput"]
    exclude: []
    outputPattern: "{collection}.css"

Поддержанные placeholders для outputPattern:

  • {collection}, {mode}
  • {collectionKebab}, {collectionSnake}, {collectionLower}
  • {modeKebab}, {modeSnake}, {modeLower}

Как избежать двойной split-генерации:

  • splitBySourceFile.excludeBundledSources: true автоматически исключает source files, которые используются ручными bundles и bundlesFromCollections того же tokenSet.
  • splitBySourceFile.excludeFromCollections явно исключает файлы выбранных collections без обязательной генерации bundle:
YAML
splitBySourceFile:
  include:
    - components/**/*.style.json
  excludeFromCollections:
    mode: Default
    include:
      - WBanner
      - WInput
  outputPattern: "{fileName}.css"

Диагностика:

  • Отсутствующий project config даёт BUNDLE_FROM_COLLECTION_PROJECT_CONFIG_REQUIRED.
  • Отсутствующие collections дают BUNDLE_FROM_COLLECTION_NOT_FOUND.
  • Отсутствующий mode даёт BUNDLE_FROM_COLLECTION_MODE_NOT_FOUND.
  • Collection/mode без matched enabled files даёт BUNDLE_FROM_COLLECTION_EMPTY.
  • При strict: true отсутствие config/collection/mode становится ошибкой; иначе это warning и bundle пропускается.

Маппинг Типографики

Для typography-токенов Transformer экспортирует минимум:

  • fontFamily
  • fontSize
  • lineHeight
  • fontWeight

и дополнительно поддерживает:

  • letterSpacing
  • textCase
  • textDecoration
  • verticalTrim / leadingTrim

Поведение по платформам:

  • CSS:
    • всегда генерирует одну shorthand-переменную typography из базовых полей (fontWeight fontSize/lineHeight fontFamily, плюс опциональный fontStyle)
    • не генерирует дубли базовых листьев (-font-family, -font-weight, -font-size, -line-height)
    • добавляет отдельные переменные только если поля реально заданы в token JSON:
      • -letter-spacing
      • -paragraph-indent
      • -paragraph-spacing
      • -text-transform + -font-variant-caps (из textCase)
      • -text-decoration-line (из textDecoration)
      • -leading-trim (из verticalTrim / leadingTrim)
  • Swift/Kotlin: типизированные typography spec включают все поля выше
  • XML: typography декомпозируется в примитивные ресурсы (family/weight как string, size/line-height/letter-spacing как dimen)

Примечание: verticalTrim / leadingTrim экспортируется как семантические данные токена. Поддержка trim-свойств в браузерах пока неравномерная, поэтому проверяйте целевую матрицу браузеров перед production.

Меню Обработки Проблем И Debug Отчёт

Когда найдены проблемы (unresolved alias, ошибка синтаксиса alias и т.д.), sync показывает интерактивное меню:

  1. Создать debug-файл и остановиться
  2. Создать debug-файл и продолжить
  3. Попробовать auto-fix простых alias-ошибок и продолжить
  4. Пропустить проблемные токены и продолжить

Для неинтерактивного запуска доступны флаги:

BASH
npx sxl-transform sync --config ./sxl-transform.config.yaml --issue-action debug-stop
npx sxl-transform sync --config ./sxl-transform.config.yaml --issue-action debug-continue
npx sxl-transform sync --config ./sxl-transform.config.yaml --issue-action autofix
npx sxl-transform sync --config ./sxl-transform.config.yaml --issue-action skip

Опции debug-файла:

BASH
# имя по умолчанию: <config-name>.debug.md
npx sxl-transform sync --config ./sxl-transform.config.yaml --debug-report

# кастомный путь отдельным флагом
npx sxl-transform sync --config ./sxl-transform.config.yaml --debug-file ./reports/transform.debug.md

# кастомный путь через значение --debug-report
npx sxl-transform sync --config ./sxl-transform.config.yaml --debug-report ./reports/transform.debug.md

Если для collection+mode не настроено наследование, debug-файл содержит:

  • объяснение ошибки пайплайна (PROJECT_CONFIG_REQUIRED)
  • два способа исправления (через config.json или inline projectConfig)
  • ссылку на документацию: https://sxl-studio.com/docs/util-transformer

Рекомендуемые scripts в package.json

Пример скриптов на уровне проекта:

JSON
{
  "scripts": {
    "tokens:validate": "sxl-transform validate-config --config ./sxl-transform.config.yaml",
    "tokens:build": "sxl-transform sync --config ./sxl-transform.config.yaml --issue-action debug-stop",
    "tokens:build:ci": "sxl-transform sync --config ./sxl-transform.config.yaml --issue-action skip"
  }
}

Quality Gates

Для пользовательского проекта обычно достаточно:

BASH
npm run tokens:validate
npm run tokens:build:ci

FAQ

Почему появились unresolved alias warnings для mode/component набора?

Обычно это означает, что в resolver scope не попали нужные файлы из связанных collection/mode. Проверьте состав selectors, includeRefs и refModeMap.

Нормально ли, что CSS хранит alias, а Swift/Kotlin — конкретные значения?

Да. Для переключения режимов обычно оставляют алиасы в CSS (resolveAliases: false), а для Swift/Kotlin генерируют уже резолвленные значения (resolveAliases: true).

Почему часть токенов не попала в Android XML?

XML-выгрузка ограничена примитивными ресурсами. Сложные визуальные структуры (например, сложные effects) экспортируются в CSS/Swift/Kotlin и отмечаются как unsupported для XML.

Для color токенов со значением linear-gradient(...) transformer генерирует нативные Android gradient drawable файлы (drawable/*.xml) вместо невалидных записей в colors.xml.

Допустим ли [object Object] в результатах?

Нет. Transformer не должен сериализовать объекты в таком виде: вместо этого генерируется диагностика.

Ссылки На Спецификации