Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ and Flutter projects.
.build/debug/exfig icons -i exfig.pkl
.build/debug/exfig batch exfig.pkl # All resources from unified config (positional arg!)
.build/debug/exfig fetch -f FILE_ID -r "Frame" -o ./output
.build/debug/exfig download tokens -o tokens.json # Unified W3C design tokens

# PKL Validation (validate config templates against schemas)
pkl eval --format json <file.pkl> # Package URI requires published package
Expand Down Expand Up @@ -120,7 +121,7 @@ pkl eval --format json <file.pkl> # Package URI requires published package

## Architecture

Twelve modules in `Sources/`:
Fourteen modules in `Sources/`:

| Module | Purpose |
| --------------- | --------------------------------------------------------- |
Expand All @@ -137,8 +138,10 @@ Twelve modules in `Sources/`:
| `FlutterExport` | Flutter export (Dart code, SVG/PNG assets) |
| `WebExport` | Web/React export (CSS variables, JSX icons) |
| `SVGKit` | SVG parsing, ImageVector/VectorDrawable generation |
| `JinjaSupport` | Shared Jinja2 template rendering across Export modules |

**Data flow:** CLI -> PKL config parsing -> FigmaAPI fetch -> ExFigCore processing -> Platform plugin -> Export module -> File write
**Alt data flow (tokens):** CLI -> local .tokens.json file -> TokensFileSource -> ExFigCore models -> W3C JSON export

**Batch mode:** Single `@TaskLocal` via `BatchSharedState` actor — see `ExFigCLI/CLAUDE.md`.

Expand Down Expand Up @@ -290,12 +293,13 @@ See `ExFigCore/CLAUDE.md` (Modification Checklist) and platform module CLAUDE.md

## Code Conventions

| Area | Use | Instead of |
| --------------- | --------------------------------- | ------------------------------------ |
| JSON parsing | `JSONCodec` (swift-yyjson) | `JSONDecoder`/`JSONEncoder` |
| Terminal UI | Noora (`NooraUI`, `TerminalText`) | Rainbow color methods |
| Terminal output | `TerminalUI` facade | Direct `print()` calls |
| README.md | Keep compact (~300 lines) | Detailed docs (use CONFIG.md / DocC) |
| Area | Use | Instead of |
| --------------- | --------------------------------- | ------------------------------------- |
| JSON parsing | `JSONCodec` (swift-yyjson) | `JSONDecoder`/`JSONEncoder` |
| JSON DOM access | `JSONCodec.parseValue(from:)` | `JSONSerialization` / `import YYJSON` |
| Terminal UI | Noora (`NooraUI`, `TerminalText`) | Rainbow color methods |
| Terminal output | `TerminalUI` facade | Direct `print()` calls |
| README.md | Keep compact (~300 lines) | Detailed docs (use CONFIG.md / DocC) |

**JSONCodec usage:**

Expand All @@ -307,6 +311,13 @@ let data = try JSONCodec.decode(MyType.self, from: jsonData)

// Encode
let jsonData = try JSONCodec.encode(myValue)

// DOM access (for dynamic JSON without Codable types)
let json = try JSONCodec.parseValue(from: data) // returns JSONValue
let name = json["key"]?.string // String?
let count = json["count"]?.number // Double?
if let obj = json.object { for (k, v) in obj { } } // iterate keys
if let arr = json["items"]?.array { arr.compactMap(\.string) } // array
```

**Noora usage:** See `.claude/rules/terminal-ui.md` for full patterns.
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![CI](https://github.com/alexey1312/ExFig/actions/workflows/ci.yml/badge.svg)](https://github.com/alexey1312/ExFig/actions/workflows/ci.yml)
[![Release](https://github.com/alexey1312/ExFig/actions/workflows/release.yml/badge.svg)](https://github.com/alexey1312/ExFig/actions/workflows/release.yml)
[![Docs](https://github.com/alexey1312/ExFig/actions/workflows/deploy-docc.yml/badge.svg)](https://alexey1312.github.io/ExFig/documentation/exfig)
![Coverage](https://img.shields.io/badge/coverage-49.36%25-yellow)
![Coverage](https://img.shields.io/badge/coverage-50.65%25-yellow)
[![License](https://img.shields.io/github/license/alexey1312/ExFig.svg)](LICENSE)

Command-line utility to export colors, typography, icons, and images from Figma to Xcode, Android Studio, Flutter, and
Expand All @@ -32,6 +32,7 @@ Flutter, and React/TypeScript.
- 📝 Typography with Dynamic Type support (iOS)
- 🔄 RTL (Right-to-Left) layout support
- 🎯 Figma Variables support
- 📁 Local `.tokens.json` file import (no Figma API needed)

### Platform Support

Expand All @@ -46,7 +47,7 @@ Flutter, and React/TypeScript.
### Export Formats

- 🖼️ PNG, SVG, PDF, JPEG, WebP, HEIC (with quality control)
- 📊 W3C Design Tokens (JSON export)
- 📊 W3C Design Tokens (DTCG v2025 format, unified JSON export)
- ⚡ Quick fetch mode (no config file needed)

### Performance & Reliability
Expand Down Expand Up @@ -216,16 +217,26 @@ exfig fetch -f FILE_ID -r "Images" -o ./images --format webp --webp-quality 90
Supports all formats (PNG, SVG, PDF, JPEG, WebP), filtering (`--filter`), name conversion (`--name-style`), and dark
mode variants (`--dark-mode-suffix`). Run `exfig fetch --help` for all options.

### JSON Export (Design Tokens)
### Design Tokens

Export Figma data as [W3C Design Tokens](https://design-tokens.github.io/community-group/format/):
Export Figma data as [W3C Design Tokens](https://design-tokens.github.io/community-group/format/) (DTCG v2025 format):

```bash
# Export from Figma API
exfig download colors -o tokens/colors.json
exfig download icons -o tokens/icons.json --asset-format svg
exfig download tokens -o tokens/design-tokens.json # Unified (colors + typography + dimensions + numbers)
exfig download all -o ./tokens/

# Work with local .tokens.json files (no Figma token needed)
exfig tokens info ./tokens.json # Inspect token file
exfig tokens convert ./tokens.json -o out.json # Re-export (filter/transform)
exfig tokens convert ./tokens.json --group "Brand" --type color -o brand-colors.json
```

Use `--w3c-version v1` for the legacy hex-string format. Colors entries also support `tokensFile` to import from a local
`.tokens.json` file (e.g., from Tokens Studio) without a Figma token — see [CONFIG.md](CONFIG.md).

### Version Tracking

Skip unchanged exports using Figma file version tracking:
Expand Down
6 changes: 5 additions & 1 deletion Sources/ExFigCLI/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ exfig typography → ExportTypography
exfig init → GenerateConfigFile
exfig schemas → ExtractSchemas
exfig fetch → FetchImages
exfig download {colors|icons|images|typography|all} → Download (nested)
exfig download {colors|icons|images|typography|tokens|all} → Download (nested)
exfig batch → Batch
```

Expand Down Expand Up @@ -161,6 +161,10 @@ Converter factories (`WebpConverterFactory`, `HeicConverterFactory`) handle plat
| `Pipeline/SharedDownloadQueue.swift` | Cross-config download pipelining actor |
| `Output/FileWriter.swift` | Sequential and parallel file writing with directory creation |
| `Shared/ComponentPreFetcher.swift` | Pre-fetch components for multi-entry exports |
| `Input/TokensFileSource.swift` | W3C DTCG .tokens.json parser (local file → ExFigCore models) |
| `Output/W3CTokensExporter.swift` | W3C design token JSON exporter (v1/v2025 formats) |
| `Loaders/NumberVariablesLoader.swift` | Figma number variables → dimension/number tokens |
| `Subcommands/DownloadTokens.swift` | Unified `download tokens` subcommand |

## Modification Patterns

Expand Down
32 changes: 32 additions & 0 deletions Sources/ExFigCLI/Context/ColorsExportContextImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,38 @@ struct ColorsExportContextImpl: ColorsExportContext {
// MARK: - ColorsExportContext

func loadColors(from source: ColorsSourceInput) async throws -> ColorsLoadOutput {
if let tokensFilePath = source.tokensFilePath {
// Warn if mode-related fields are configured but will be ignored
if source.darkModeName != nil || source.lightHCModeName != nil || source.darkHCModeName != nil {
ui.warning(
"Local tokens file provides single-mode colors only"
+ " — darkModeName/lightHCModeName/darkHCModeName will be ignored"
)
}
return try loadColorsFromTokensFile(path: tokensFilePath, groupFilter: source.tokensFileGroupFilter)
}
return try await loadColorsFromFigma(source: source)
}

private func loadColorsFromTokensFile(path: String, groupFilter: String?) throws -> ColorsLoadOutput {
var source = try TokensFileSource.parse(fileAt: path)
try source.resolveAliases()

for warning in source.warnings {
ui.warning(warning)
}

var colors = source.toColors()

if let groupFilter {
let prefix = groupFilter.replacingOccurrences(of: ".", with: "/") + "/"
colors = colors.filter { $0.name.hasPrefix(prefix) }
}

return ColorsLoadOutput(light: colors)
}

private func loadColorsFromFigma(source: ColorsSourceInput) async throws -> ColorsLoadOutput {
let variableParams = Common.VariablesColors(
tokensFileId: source.tokensFileId,
tokensCollectionName: source.tokensCollectionName,
Expand Down
27 changes: 27 additions & 0 deletions Sources/ExFigCLI/ExFig.docc/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ common = new Common.CommonConfig {
}
```

### Tokens File Source

Use a local W3C DTCG `.tokens.json` file instead of the Figma Variables API:

```pkl
import ".exfig/schemas/Common.pkl"
import ".exfig/schemas/iOS.pkl"

ios = new iOS.iOSConfig {
colors = new iOS.ColorsEntry {
// Load colors from a local .tokens.json file
tokensFile = new Common.TokensFile {
// Path to the .tokens.json file
path = "./design-tokens/colors.tokens.json"

// Optional: filter to specific token group
groupFilter = "Brand.Colors"
}

assetsFolder = "Colors"
nameStyle = "camelCase"
}
}
```

> When `tokensFile` is set, ExFig reads color tokens from the local file and does not require `FIGMA_PERSONAL_TOKEN` or Figma Variables configuration (`tokensFileId`, `tokensCollectionName`, `lightModeName`).

### Icons

```pkl
Expand Down
1 change: 1 addition & 0 deletions Sources/ExFigCLI/ExFig.docc/ExFig.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ExFig automates the export of design tokens from Figma to native platform resour
- **Icons**: Export vector icons as PDF/SVG (iOS), VectorDrawable (Android), or SVG (Flutter)
- **Images**: Export raster images with multi-scale support for all platforms
- **Typography**: Export text styles as Swift extensions, XML styles, or Dart constants
- **Design Tokens**: Export unified W3C DTCG design tokens (colors, typography, dimensions, numbers) as JSON

## Topics

Expand Down
17 changes: 11 additions & 6 deletions Sources/ExFigCLI/ExFig.docc/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ exfig download colors -o debug/colors.json --format raw
# Export icons with SVG URLs
exfig download icons -o tokens/icons.json --asset-format svg

# Export unified design tokens (colors + typography + dimensions + numbers)
exfig download tokens -o tokens/design-tokens.json

# Export all token types
exfig download all -o ./tokens/
```
Expand All @@ -211,21 +214,23 @@ exfig download all -o ./tokens/

| Subcommand | Description |
| ------------ | ------------------------------- |
| `colors` | Export colors as JSON |
| `icons` | Export icon metadata with URLs |
| `images` | Export image metadata with URLs |
| `typography` | Export text styles as JSON |
| `all` | Export all types to a directory |
| `colors` | Export colors as JSON |
| `icons` | Export icon metadata with URLs |
| `images` | Export image metadata with URLs |
| `typography` | Export text styles as JSON |
| `tokens` | Export unified design tokens (colors, typography, dimensions, numbers) |
| `all` | Export all types to a directory |

### Download Options

| Option | Short | Description | Default |
| ---------------- | ----- | -------------------------------- | ------- |
| `--output` | `-o` | Output file path (required) | - |
| `--output` | `-o` | Output file path | varies |
| `--format` | `-f` | Output format: w3c, raw | w3c |
| `--compact` | - | Output minified JSON | false |
| `--asset-format` | - | Image format: svg, png, pdf, jpg | svg |
| `--scale` | - | Scale for raster formats | 3 |
| `--w3c-version` | - | W3C format version: v1, v2025 | v2025 |

## Quick Fetch

Expand Down
1 change: 1 addition & 0 deletions Sources/ExFigCLI/ExFigCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ struct ExFigCommand: AsyncParsableCommand {
ExtractSchemas.self,
FetchImages.self,
Download.self,
Tokens.self,
Batch.self,
],
defaultSubcommand: ExportColors.self
Expand Down
Loading
Loading