@sxl-studio/export-icons
v1.0.0
Published
Export icons and assets from Figma with diff sync, fonts, and SF Symbols
Maintainers
Readme
SXL Export Icons
@sxl-studio/export-icons — installable CLI for exporting icon assets from Figma with incremental diff, flexible multi-config, font generation, SF Symbols pipeline, and optional Bridge/MCP source mode.
Install
npm install --save-dev @sxl-studio/export-iconsEnvironment and secrets
Create .env in your repository root and provide your Figma credentials there:
FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx
SXL_ICONS_FILE_KEY=xxxxxxxxxxxxRequired Figma token scopes:
file_content:readfile_metadata:read
No write scopes are required for this CLI.
Add .env to .gitignore and never commit tokens:
.env
.env.localCLI commands
# If package is installed in repo (recommended)
pnpm exec sxl-export-icons init
pnpm exec sxl-export-icons init --wizard
# One-shot run without install
pnpm dlx @sxl-studio/export-icons init
npx @sxl-studio/export-icons init
# Validate one or many configs
pnpm exec sxl-export-icons validate-config --config sxl-export-icons.config.yaml
pnpm exec sxl-export-icons validate-config --config-glob "packages/*/sxl-export-icons*.yaml"
# Sync (default command)
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --adopt-existing
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --target font-subset-local
pnpm exec sxl-export-icons --config sxl-export-icons.config.yaml --dry-run
pnpm exec sxl-export-icons --config sxl-export-icons.config.yaml --full
pnpm exec sxl-export-icons --config-glob "packages/*/sxl-export-icons*.yaml" --report .reports/icons-sync.json
# Enable fallback if source mode=mcp failed
pnpm exec sxl-export-icons sync --config ./icons.yaml --allow-fallback-rest
# Clean generated files from state/config scopes
pnpm exec sxl-export-icons clean --config ./icons.yaml
pnpm exec sxl-export-icons clean --config ./icons.yaml --dry-run
# Print state coverage report
pnpm exec sxl-export-icons report --config ./icons.yamlDo not use
pnpx sxl-export-icons ...in registries with private proxy mapping.pnpx/dlxcan try to resolve unscoped packagesxl-export-iconsand return 404.
Running from Cursor terminal
cd /Users/andysobol/Git/platform/packages/ds/tokens/tokens
# First run: adopt already downloaded files as baseline state
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --adopt-existing
# Regular sync
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml
# Local subset conversion only
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --target font-subset-localNotes:
- CLI auto-loads
.env.local/.envfrom current and parent folders. --adopt-existingis bootstrap mode for empty scope state.- During REST source loading, CLI shows live progress stages/pages
(
REST 1/3,REST 2/3,REST 3/3) so long runs are observable.
Config v3
Canonical filename: sxl-export-icons.config.yaml.
Legacy config version: 2 (icons.config.yaml) is supported via auto-migration.
When v2 config is loaded, relative paths are resolved from the current workspace root to keep legacy behavior.
version: 3
env:
figmaTokenVar: FIGMA_TOKEN
bridge:
baseUrl: http://127.0.0.1:37830
authTokenVar: BRIDGE_AUTH_TOKEN
state:
file: .sxl/cache/icons-state.json
safety:
allowOutsideWorkspace: false
sources:
- id: ds-icons
mode: rest # rest | mcp
fileKeyVar: SXL_ICONS_FILE_KEY
pageName: Icons
downloadSpeed: 20
descriptionExportMarker:
key: sxl-studio-export-icon
includeWhenMissing: true
selectors:
includeComponentSetNames: []
includeComponentNames: []
variantMatch:
style: outline
targets:
- id: web-svg
mode: sync # sync | convert-local
sourceIds: [ds-icons]
download:
enabled: true
pruneUntracked: false
output:
path: assets/icons/svg
format: svg # svg | png | jpg | webp | gif | pdf
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null # null | cover | proportion | fill
naming:
case: kebab # kebab | snake | camel | pascal
separator: "-"
pattern: "{prefix}{variant.style}{sectionName}{baseName}{suffix}"
prefix: null
suffix: null
includeSectionName: false
collisionStrategy: add-nodeid # error | add-nodeid | add-index
font:
enabled: true
path: assets/icons/font
formats: [woff2, woff, ttf, eot, svg]
fontName: icons
engine: webfonts-generator # webfonts-generator | advanced
includeNames: [] # optional subset for font/SF generation
excludeNames: [] # optional exclusion list
sfSymbols:
enabled: true
path: assets/icons/ios
overrides:
- name: flags-webp
match:
sectionName: Flags
patch:
output:
format: webp
quality: 85Source mode: rest and mcp
mode: rest— direct Figma REST API.mode: mcp— read via SXL Bridge/Remote Connect (/api/commandwith page/tree traversal).- If
mode: mcpfails and--allow-fallback-restis passed, CLI auto-falls back to REST for sources with resolvedfileKey.
bridge block in config is used only for mode: mcp.
For mode: rest it is ignored.
Target mode: sync and convert-local
mode: sync(default) — reads Figma sources, downloads/updates assets, then runs aux pipelines.mode: convert-local— does not use Figma sources; reads existing local SVG files fromoutput.pathand runs font/SF pipelines.
Rules for convert-local:
sourceIdsmust be empty;download.enabledmust befalse;output.formatmust besvg;- at least one of
font.enabledorsfSymbols.enabledmust betrue.
To run only a local-conversion target from mixed config:
pnpm exec sxl-export-icons sync --config ./sxl-export-icons.config.yaml --target font-subset-localDescription marker filter
You can control export in Figma description of COMPONENT / COMPONENT_SET:
sxl-studio-export-icon: trueor
sxl-studio-export-icon: falseConfig:
sources:
- id: ds-icons
descriptionExportMarker:
key: sxl-studio-export-icon
includeWhenMissing: trueRules:
- marker
true-> include - marker
false-> exclude - marker missing -> use
includeWhenMissing
What is exported
- Only
COMPONENTnodes. - For
COMPONENT_SET, exports eachCOMPONENTvariant child as a separate item. - Variant matching works with arbitrary property names (for example
styleMode,platform,state, ...), not onlystyle. - If source sets
sectionName/sectionId, items outside that section are skipped.
One or multiple formats
target.output.formatis a single format per target.- To export the same source in multiple formats, add multiple targets with shared
sourceIds.
targets:
- id: icons-svg
sourceIds: [ds-icons]
output:
path: assets/icons/svg
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
- id: icons-webp
sourceIds: [ds-icons]
output:
path: assets/icons/webp
format: webp
scale: 1
quality: 90
qualitySize: null
width: null
height: null
modeWH: nullIncremental diff behavior
Scope identity: sourceId::targetId.
NEW— node appears first time in scope.UPDATED—updatedAt/fingerprint changes, file missing, format changed, or scope config hash changed.DELETED— node removed from source scope.RENAMED— canonical name/path changes while format stays same.UNCHANGED— no content and no config-impact changes.
State file stores scope metadata, config hash, and exported item hashes.
For REST sources, node fingerprints are computed from render-relevant node data (geometry=paths), so pure vector-shape edits are detected even when metadata timestamps stay unchanged.
Bootstrap behavior (first run without scope state):
- every matched icon is treated as
NEWand re-exported; - this guarantees replacement/update even when files already exist on disk.
- with
--adopt-existing, existing files are adopted asUNCHANGEDbaseline instead of forced re-download.
Optional strict cleanup:
- set
download.pruneUntracked: trueto remove files with the same target format inoutput.paththat are not present in current Figma scope. - enable it only for dedicated output directories, because cleanup is path+format based.
Output policies
- Paths are resolved relative to config file location.
- Export outside workspace is blocked by default.
- To allow external absolute/relative destinations set:
safety:
allowOutsideWorkspace: truePost-processing
svg-> SVGO optimizationpng/jpg-> Sharp optimization + optional resizewebp-> export PNG from Figma, then convert to WebPgif-> export PNG from Figma, then convert to GIFpdf-> direct Figma export
Convert-only targets (no download)
If icons already exist locally and you only need font/SF conversion:
targets:
- id: icons-font-only
mode: convert-local
sourceIds: []
download:
enabled: false
output:
path: assets/icons/svg
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
naming:
case: kebab
separator: "-"
pattern: "{variant.style}{baseName}"
includeSectionName: false
collisionStrategy: add-nodeid
font:
enabled: true
path: assets/icons/font
formats: [woff2, woff, ttf, eot]
fontName: ds-icons
includeNames: [filled-casino-menu, outline-casino-menu]Notes:
download.enabled: falseskips file mutation (no download/rename/delete);font.includeNameslimits conversion to a selected subset;- names in
font.includeNames/excludeNamescan be provided with or without.svg; - source SVG files must already exist in
output.path.
Font pipeline
- Adapter-based interface with
webfonts-generatordefault engine. - Supports
woff2,woff,ttf,svg,eot. - Preserves stable codepoints (
codepoints.json) between runs. - Generates preview (
preview.html) + stylesheet.
SF Symbols pipeline
- Generates
Assets.xcassets/*.symbolset. - Uses
symbol-rendering-intentin generatedContents.json. - Rejects complex SVG features (gradients, filters, masks, embedded images).
Multi-config usage
You can run many configs in one call:
pnpm exec sxl-export-icons sync \
--config packages/ds-a/sxl-export-icons.config.yaml \
--config packages/ds-b/sxl-export-icons.config.yaml \
--report .reports/icons-mono.jsonOr by glob:
pnpm exec sxl-export-icons sync --config-glob "packages/*/sxl-export-icons.config.yaml"For multiple pages/sections from the same Figma file, define separate sources with the same fileKey/fileKeyVar and different pageName/sectionName.
Naming token notes (variant.*)
variantis a reserved token namespace innaming.pattern.variant.<propName>uses any Figma variant property name (for examplestyle,type,size,state).- Multi-prop naming is supported by chaining tokens:
"{prefix}{pageName}{variant.style}{variant.type}{baseName}"
- If a property is missing for a node, that segment is skipped.
In mixed sets this can cause collisions, so use
collisionStrategyand/or add disambiguators like{sectionName}or{nodeId}.
Duplicate preflight (before download)
When duplicate output names are detected, CLI prints the full duplicate list (name + node id + variant props) and asks for action:
refresh— re-read source and re-check duplicates (after you fix names in Figma)skip— keep one node and skip duplicate entriesall— download all duplicates using rename strategy (add-nodeid/add-index)abort— stop sync
Interactive TTY mode uses keyboard selection (↑/↓ + Enter) instead of manual text input.
In non-interactive mode (CI), prompts are skipped and configured collisionStrategy is applied automatically.
Troubleshooting
Missing Figma token-> set env var fromenv.figmaTokenVar.Path ... is outside workspace-> enablesafety.allowOutsideWorkspace.Bridge session is not connected-> startUtils/bridge, open plugin, enable Remote Connect.Naming collision detected-> changenaming.patternor usecollisionStrategy: add-nodeid/add-index.- Too many 429 responses -> lower
downloadSpeedfor source.
Contract references used in implementation
- Figma REST:
GET /v1/files/:key,GET /v1/images/:key, components/component_sets metadata endpoints. - Sharp conversion/encoding docs for WebP/GIF/resize.
webfonts-generatorformats and options.
This utility keeps those contracts explicit in code and docs to reduce drift and regression risk.
