npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sxl-studio/token-transformer

v3.4.1

Published

Transform DTCG design tokens to CSS/SCSS, Swift, Kotlin, Android XML, and JSON manifests

Downloads

594

Readme

SXL Token Transformer

YAML-first утилита для преобразования дизайн-токенов в:

  • CSS custom properties
  • SCSS variables
  • Swift-константы (SwiftUI)
  • Kotlin-константы (Compose)
  • Android XML ресурсы (colors.xml, dimens.xml, strings.xml, bools.xml, integers.xml)

3.4.1: smart, force и preserve

  • smart добавляет, обновляет и удаляет токены по JSON; записывает только файлы, содержимое которых действительно изменилось.
  • force заново генерирует и записывает все выбранные outputs. Предварительное удаление папок не требуется.
  • --mode smart --preserve-existing добавляет и обновляет объявления, сохраняя отсутствующие в JSON. Это флаг команды, без настройки YAML.
  • Стабилизированы порядок glob-файлов и повторное XML-объединение; исправлено экранирование многострочного текста и нативных спецсимволов.

3.3.1: совместимость и ограничения

  • Поддержаны объектные DTCG dimension/duration, цвета srgb, display-p3, hsl, oklch, lch и авторский hex fallback. Это не полная реализация всех цветовых пространств DTCG; некорректные значения получают диагностику.
  • easing: CSS/SCSS и Kotlin поддерживают пресеты, Bézier и hold. Spring в CSS — приближение с предупреждением; SwiftUI/UIKit сохраняют easing как строковый spec, Kotlin сохраняет spring как строковый spec. cubicBezier остаётся нативной кривой.
  • Тип в реестре не означает поддержку каждой формы Figma. Нативные outputs не воспроизводят сложную геометрию radial/conic, repeating gradients, lineHeight: auto, video/pattern/glass fills. XML ограничен ресурсами Android.
  • Поля, отсутствующие в формате output (noise/texture, геометрия progressive blur, фильтры/трансформации изображений, оси шрифта и отдельные поля типографики), сопровождаются предупреждением; исходные данные сохраняет manifest. Typed output — значения/spec, а не автоматическое применение всех свойств к UI.
  • Первый smart-запуск после обновления пересобирает outputs один раз. При запуске с ограниченным scope остальные outputs остаются ожидающими полного прогона. Затем неизменённые outputs снова пропускаются; standalone SCSS больше не получает импорт отсутствующего root.scss.

Установка

npm install @sxl-studio/token-transformer

Команды

# трансформация в smart incremental режиме (по умолчанию)
npx sxl-transform sync --config ./sxl-transform.config.yaml

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

# только выбранные outputs
npx sxl-transform sync --config ./sxl-transform.config.yaml --only-output css-root --only-output swift-root

# только выбранные файлы (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-root:modes/dark.css"

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

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

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

Сохранение ранее сгенерированных токенов

npx sxl-transform sync --config ./sxl-transform.config.yaml --preserve-existing --issue-action debug-stop

Команда сохраняет токены, чьи JSON-файлы или значения удалены из источников, добавляет новые и обновляет совпавшие актуальными значениями. Поддерживаются CSS, SCSS, Swift, UIKit, Kotlin, Android XML и manifest. Перед запуском не удаляйте выходные файлы: именно из них читаются прежние значения.

Совпадение определяется по публичному имени и области объявления:

| Формат | Область совпадения | |---|---| | CSS | Имя с учётом регистра, селектор и окружающие at-правила | | SCSS | Имя Sass-переменной внутри одного файла-модуля; дефис и подчёркивание эквивалентны | | Swift / UIKit / Kotlin | Namespace и имя свойства; у Kotlin также package | | Android XML | Тип ресурса, имя и квалификатор ресурсов, например values-night; drawable обновляется целиком | | Manifest | Имя, коллекция и режим; перенос JSON между исходными файлами не создаёт дубликат |

Старые объявления, заменённые актуальными в другом файле той же области, не дублируются. Разные Sass-модули и native namespaces остаются независимыми: изменение NewTokens.size не изменяет OldTokens.size. Сохраняются устаревшие выходные файлы и их подключения через indexes.includeGenerated. Native namespace и общие вспомогательные типы получают одного владельца в пределах области компиляции.

Флаг задаётся только в команде, в YAML его нет. Он работает с --mode smart, --force, --watch, --only-output и --only-file. Запуск без флага возвращает обычную генерацию из JSON для выбранных outputs; сохранённые значения будут удалены при пересборке. options.removeStaleOutputs: false отдельно управляет сохранением целых устаревших файлов.

Если state отсутствует, прежние файлы восстанавливаются в учёте по настроенным путям генерации и split-шаблонам. Произвольные файлы вне этих шаблонов не подхватываются. Уже потерянные значения без прежнего output восстановить нельзя. Сохранённые outputs не заменяют JSON-источники при разрешении алиасов.

Повреждённый или неподдерживаемый файл останавливает запуск до записи outputs. Обработчики рассчитаны на генерируемые Transformer структуры; произвольный код custom formatter может потребовать адаптации. CSS с анонимными слоями @layer не объединяется, потому что у слоя нет устойчивого имени. Предупреждения об исходных токенах по-прежнему обрабатываются согласно --issue-action. Пустой glob после подтверждённого удаления прежних источников при сохранённых outputs становится информационным сообщением.

Smart, force и preserve

  • smart (по умолчанию): пересобирает только реально затронутые output-файлы. Изменения source-файлов определяются по хешу содержимого (state v2) — правки значений и алиасов ловятся даже при неизменных size/mtime, а touch/checkout без изменения содержимого не вызывают лишней перезаписи. Дополнительно перед ранним выходом сверяется содержимое сгенерированных файлов на диске с state: удалённые или изменённые извне (git restore, ручные правки, форматтеры) output-файлы пересобираются автоматически.
  • force: заново генерирует и записывает все выбранные output-файлы безусловно.
  • smart --preserve-existing: сохраняет отсутствующие в JSON объявления, обновляет совпавшие и добавляет новые. Переименование сохраняет прежнее имя и добавляет новое.
  • В smart пересчёт из-за изменения конфига или переключения preserve не означает перезапись: одинаковые байты остаются нетронутыми. Force записывает файлы даже при одинаковом результате.

Замечания по state:

  • Формат state-файла — v2 (fingerprints с contentHash). Старый v1-state отбрасывается: первый запуск после обновления делает один полный rebuild.
  • Scoped-прогоны (--only-output / --only-file) не «поглощают» изменения вне своего scope: они остаются pending и подхватываются следующим полным smart-прогоном.
  • Устаревшие output-файлы (source-токены которых больше не существуют) по умолчанию удаляются в smart и force. Это отключается опцией options.removeStaleOutputs: false — тогда такие файлы остаются на диске и продолжают отслеживаться в state (при включении опции обратно они будут удалены).
  • Если postprocess-скрипт переписывает output-файлы, которыми управляет Transformer, после sxl-transform sync, следующий smart-прогон пересоберёт эти outputs: их содержимое больше не совпадает со state. Держите postprocess вне managed output paths, переносите его в config/output hooks Transformer или запускайте до финального sync, который сохраняет state.

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

  • --mode smart|force
  • --force (сокращение для --mode force)
  • --state-file <path> (переопределить путь state-файла, по умолчанию <config-name>.state.json)
  • --only-output <id> (можно повторять, выбор output id)
  • --only-file <glob> (можно повторять, выбор output-файлов по шаблону)
  • --explain (печатает по каждому output, почему он пересобран или пропущен)
  • --watch (следит за token-каталогом и конфигами, пересобирает в smart-режиме с debounce; Ctrl+C — выход; требуется Node >= 20)
  • --no-state-lock (отключить кооперативный lock state-файла)

Параллельные прогоны и целостность state:

  • State пишется атомарно (tmp + rename) — прерванный прогон не оставляет битый файл.
  • На время прогона берётся lock <state>.lock; второй одновременный прогон завершится с понятной ошибкой. Устаревшие локи (упавший процесс, старше 10 минут) перехватываются автоматически.

Формат конфига

Поддерживается только YAML. JSON-конфиг умышленно не поддерживается.

Минимальная структура:

version: 1
source:
  tokenDir: ./tokens
  configFile: config.json
  include: ["**/*.json"]
  exclude: ["config.json", "**/diff-id*.json"]

options:
  remBase: 16
  collisionStrategy: error # error | suffix | namespace-by-file | namespace-by-mode
  removeStaleOutputs: true # false — не удалять output-файлы исчезнувших токенов
  unsupportedTypes:
    default: error
    types:
      template: skip
      composition: skip
      grid: skip

tokenSets:
  - id: root
    selectors:
      - collection: Core
        mode: Default
      - collection: Themes
        mode: Dark
        refModeMap:
          Projects: Customer
          Core: Default
      - files: ["projects/customer/*.json"]

outputs:
  - id: css-root
    platform: css # css | scss | swift | uikit | kotlin | xml | manifest
    outputDir: ./dist/css/adminui
    prefix: ds
    suffix: v2
    resolveAliases: false
    splitEffects: true
    showDescriptions: true
    files:
      - tokenSet: root
        output: root.css
        # prefix/suffix можно переопределить для конкретного mapping:
        # prefix: cmp
        # suffix: beta

  - id: tokens-manifest
    platform: manifest
    outputDir: ./dist/manifest/adminui
    files:
      - tokenSet: root
        output: tokens-manifest.json
        options:
          schemaVersion: "1.0"
          includeResolvedValue: true
          includeOriginalValue: true
          includeReferences: true
          includeSource: true
          includeExtensions: false
          includePrivate: true
          groupBy: flat # flat | collection | mode | file

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

CSS / SCSS Output Contract

CSS-файлы по умолчанию оборачиваются в :root. Wrapper selector можно переопределить для статичного файла, split-файла или bundle:

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

selector работает только для CSS. Если указать его для scss, swift, uikit, kotlin, xml или manifest, Transformer выдаст warning и проигнорирует настройку.

CSS/SCSS entrypoint-файлы генерируются через indexes:

outputs:
  - id: css-app
    platform: css
    outputDir: ./dist/css
    files:
      - tokenSet: root
        output: root.css
    indexes:
      - output: index.css
        imports:
          - ./root.css
        includeGenerated:
          - components/**/*.css
        exclude:
          - index.css
          - "**/*.internal.css"
        sort: generated-order # generated-order | alpha
        skipMissing: true
        strict: false
  • CSS indexes генерируют @import "...";.
  • SCSS indexes генерируют @use "..." as *;.
  • Indexes поддержаны только для css и scss.
  • Явные imports резолвятся относительно index-файла.
  • includeGenerated матчится по файлам того же output, включая файлы из state в smart-mode.
  • Index-файлы хранятся в state и участвуют в stale cleanup как обычные generated files.

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

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

Bundles используют тот же token graph, filtering, unsupported-type policy, aliases, custom formatters и platform emitters, что и обычные files. selector внутри bundle всё равно применяется только к CSS.

bundlesFromCollections нужен, когда группы bundle уже описаны в source.configFile или top-level projectConfig:

outputs:
  - id: css-components
    platform: css
    outputDir: ./dist/components
    resolveAliases: false
    files:
      - tokenSet: components
        splitBySourceFile:
          include: ["components/**/*.style.json"]
          excludeBundledSources: true
          outputPattern: "{fileName}.css"
    bundlesFromCollections:
      - tokenSet: components
        mode: Default
        include:
          - WBanner
          - WInput
        fileInclude:
          - components/**/*.style.json
        fileExclude:
          - "**/__draft__/*.json"
        outputPattern: "{collection}.css"
        selector: ":root"
        strict: true
    indexes:
      - output: index.css
        includeGenerated:
          - "**/*.css"
        exclude:
          - index.css

bundlesFromCollections читает enabled mode.files из config.json / projectConfig, фильтрует их через fileInclude / fileExclude и генерирует один bundle на matched collection. Поддержанные placeholders: {collection}, {mode}, {collectionKebab}, {collectionSnake}, {collectionLower}, {modeKebab}, {modeSnake}, {modeLower}.

Чтобы избежать двойной генерации, используйте splitBySourceFile.excludeBundledSources: true. Для явного контроля без генерации bundles используйте splitBySourceFile.excludeFromCollections.

Quality gates

npm run eslint
npm run typecheck
npm run test
npm run build
npm run check

Что нового в 3.2.0

Цветовое ядро синхронизировано с Plugin token engine (единое поведение «что видно в Figma = что в коде»):

  • figma.modify: amount принимает дробь 0.56, шкалу 0..100 (56), процент "56%" и alias {token} — нормализация как в плагине (parseModifierAmount).
  • space: "lch" — настоящий CIE L*a*b*/LCH (D65), отдельно от OKLCH.
  • mix: powerless hue по CSS Color 4 §12.2 — подмешивание white/black/gray больше не уводит тон (blue + white ≠ cyan). Исправлено синхронно и в плагине.
  • CSS named colors (полный набор CSS Color 4: red, rebeccapurple, …) и честный Display-P3 → XYZ → sRGB.
  • Inline color-mutation выражения: "$value": "rgba({color} {alpha})" (alpha — литерал/процент/alias, паритет с figma.modify alpha).
  • Math: научная нотация (1e-3, 2.5e+2; голое e — константа Эйлера), процентная арифметика (100% - 20px, 50% + 10%) без конфликта с оператором % (модуло).

Эмиттеры и типы:

  • Kotlin: цветовой литерал Color(0xFF2D6CDF) без u-суффикса (u-форма резолвилась в raw ULong-конструктор Compose и давала повреждённый packed-цвет).
  • rem/em масштабируются через remBase в Swift/UIKit/Kotlin/XML (1.5rem24).
  • CSS typography: %-lineHeight валиден при любом юните fontSize (600 1rem/150% "Inter").
  • Effects: смешанные эффекты (shadow + blur/backgroundBlur) в CSS/SCSS раскладываются в --x-shadow / --x-filter / --x-backdrop-filter вместо потери данных; однородные токены сохраняют базовое имя.
  • custom — preserved/opaque тип: значение хранится verbatim, экспортируется только в manifest, на типизированных платформах skip + warning.
  • Совместимость алиасов как в Figma: FLOAT-типы взаимно алиасуемы (number → borderWidth, lineHeight → sizing, …), STRING-типы взаимно алиасуемы; плюс gradient → color, shadow ↔ effects.
  • duration принимает объектную форму { "duration": "100" }; cubicBezier — объектную форму { x1, y1, x2, y2 } (в т.ч. строковые числа).
  • Shadow-слои: x/y приняты как алиасы offsetX/offsetY (Tokens Studio shape).
  • Android XML: дробные number больше не округляются — эмитятся в floats.xml как <item type="dimen" format="float"> (чтение через ResourcesCompat.getFloat).
  • outputs[].excludeHidden: true — исключить из output токены с $extensions["figma.hide"] (по умолчанию false: скрытые токены часто обслуживают alias-цепочки видимых).
  • Диагностика: *_COLOR_INVALID/*_DIMENSION_INVALID и т.п. указывают неразрешённый алиас в значении ((unresolved alias in value: {…})).

Проверено на реальных проектах (2026-07-02, полные прогоны на 7 платформ по двум боевым репозиториям токенов — 436 JSON-файлов, 63 token set'а, 613 выходных файлов; машинная валидация postcss/dart-sass/XML/JSON):

  • Математика гейтится по типу (парити с FLOAT-веткой плагина): строковые значения никогда не вычисляются — text-токен "37-37" остаётся "37-37", а не превращается в 0; числовые поля композитов (offsets/blur теней, размеры typography) математику сохраняют.
  • Инлайн-мутации цвета ("rgba({color}, {alpha})") запекаются в вычисленный литерал на CSS/SCSS даже при resolveAliases: falsergba(var(--hex), var(--amount)) невалиден на этапе вычисления значений CSS.
  • SCSS: mode-файлы делают @use на свой фактический ближайший root.scss (вложенные раскладки по проектам); подстановка $var ограничена видимой областью (свой файл + прилинкованный root) — определения чужих проектов больше не «протекают» как Undefined variable; standalone-файлы теряют угаданный @use.
  • Неразрешённые {alias}-плейсхолдеры больше не попадают в сгенерированный код строковыми литералами — новые диагностики SWIFT/UIKIT/KOTLIN/XML_ALIAS_UNRESOLVED (в CSS — через CSS_UNSUPPORTED_TOKEN).
  • Ошибки теней стали актионабельными: Invalid shadow token value (layer 1: missing "color"; unknown key "vcolor"); debug-отчёт дедуплицирует повторы (×N) и показывает точный file= проблемного токена.

Важно

  • Запрещённые к трансформации типы: template, composition, grid (рекомендуемый профиль: template/composition/grid -> skip, остальные unsupported -> error).
  • Поддержан figma.modify, включая chain и alias-based параметры модификаторов.
  • Поддержана безопасная математика (+ - * / %, round, floor, ceil, clamp, и др.) без eval.
  • Для selector по collection/mode можно управлять зависимостями:
    • includeRefs: true|false — подключать ли ref-цепочки из token config.json в resolver scope.
    • refModeMap — принудительно выбрать mode для зависимых коллекций (например Projects: Customer).
  • Аффиксы имен настраиваются в YAML:
    • outputs[].prefix / outputs[].suffix — для всего output
    • outputs[].files[].prefix / outputs[].files[].suffix — переопределение на уровне mapping
    • splitBySourceFile.prefixPattern / splitBySourceFile.suffixPattern — для split-генерации по файлам
  • В режиме --only-file утилита не делает stale-cleanup вне выбранных файлов: точечная генерация безопасна для остального output.
  • SCSS-выгрузка ориентирована на token-first подключение: root-файлы нужно подключать до component-файлов в entrypoint, чтобы межфайловые $token-ссылки резолвились детерминированно.
  • CSS selector поддерживает статичные селекторы и split placeholders вроде {component}. CSS/SCSS indexes генерируют entrypoints и участвуют в smart state/stale cleanup. bundles и bundlesFromCollections группируют source-файлы в один output через тот же emitter pipeline, а не через конкатенацию готовых строк.
  • Android XML: если color токен содержит CSS linear-gradient(...), transformer генерирует нативный drawable/*.xml, а не невалидное значение в colors.xml.
  • platform: manifest генерирует JSON-артефакт из того же resolved token graph, что и CSS/native outputs. Manifest содержит name, cssVar, path, type, value, resolvedValue, originalValue, references, sourceFile и source metadata для документации, Storybook/devtools и аудита миграций.
  • Manifest level и weight читаются из token extensions (level, sxl.level, sxl.weight), если они есть; они не выводятся из path или названия collection.
  • Manifest options можно задавать на уровне outputs[].options или конкретного outputs[].files[].options. Поддержаны cssVarPrefix, cssVarSuffix, includePrivate, includeSource, includeExtensions, groupBy, schemaVersion.

Матрица Поддержки Типов (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, effects
  • boolean, text
  • transition, duration, cubicBezier, easing (как токены значений/spec, без auto-apply к UI-элементам)
  • custom — preserved/opaque: verbatim в manifest, skip + warning на типизированных платформах

glass распознаётся и сохраняется в manifest. Настоящие Figma glass-объекты не имеют поддерживаемой формы в CSS/SCSS/native outputs и получают ошибку вместо подстановки blur.

Исключены из трансформации:

  • template, composition, grid

Manifest поддерживает те же разрешённые типы токенов и намеренно не эмитит запрещённые template, composition, grid.

Политика no-hallucination:

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