From d85b7c3f9870af1ff2d934d593ec054362c85dc9 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Sat, 28 Feb 2026 21:24:51 +0500 Subject: [PATCH 1/4] feat: migrate to pkl-swift 0.8.0 + Pkl 0.31 - Update pkl-swift dependency from 0.7.2 to 0.8.0 - Update Pkl CLI from 0.30.2 to 0.31.0 - Add registerPklTypes() with 41 types for O(N) type scanning bypass - Migrate codegen from pkl-gen-swift to pkl run @pkl.swift/gen.pkl - Replace !isEmpty with isNotEmpty in PKL schemas (Pkl 0.31 builtin) - Regenerate Swift types from updated schemas - Update CLAUDE.md documentation --- .claude/rules/pkl-codegen.md | 10 +- CLAUDE.md | 6 +- Package.resolved | 6 +- Package.swift | 2 +- .../ExFigCLI/Resources/Schemas/Android.pkl | 8 +- Sources/ExFigCLI/Resources/Schemas/Common.pkl | 8 +- .../ExFigCLI/Resources/Schemas/Flutter.pkl | 6 +- Sources/ExFigCLI/Resources/Schemas/PklProject | 6 + .../Resources/Schemas/PklProject.deps.json | 12 + Sources/ExFigCLI/Resources/Schemas/Web.pkl | 6 +- .../Resources/Schemas/generator-settings.pkl | 1 + Sources/ExFigCLI/Resources/Schemas/iOS.pkl | 6 +- Sources/ExFigConfig/CLAUDE.md | 13 +- .../ExFigConfig/Generated/Android.pkl.swift | 232 +++++++++--------- Sources/ExFigConfig/PKL/PKLEvaluator.swift | 61 +++++ mise.lock | 8 +- mise.toml | 15 +- 17 files changed, 245 insertions(+), 161 deletions(-) create mode 100644 Sources/ExFigCLI/Resources/Schemas/PklProject.deps.json create mode 100644 Sources/ExFigCLI/Resources/Schemas/generator-settings.pkl diff --git a/.claude/rules/pkl-codegen.md b/.claude/rules/pkl-codegen.md index d304516a..85d1c5c4 100644 --- a/.claude/rules/pkl-codegen.md +++ b/.claude/rules/pkl-codegen.md @@ -1,15 +1,17 @@ # PKL Codegen (pkl-swift) -Config types are generated by `pkl-gen-swift` from PKL schemas. DO NOT edit `Sources/ExFigConfig/Generated/*.pkl.swift` manually. +Config types are generated from PKL schemas via `pkl run @pkl.swift/gen.pkl`. DO NOT edit `Sources/ExFigConfig/Generated/*.pkl.swift` manually. ## Regeneration ```bash -./bin/mise run codegen:pkl # requires pkl-gen-swift in PATH -# Or build from package dependency directly: -swift build --product pkl-gen-swift && .build/debug/pkl-gen-swift --output-path Sources/ExFigConfig/Generated/ Sources/ExFigCLI/Resources/Schemas/*.pkl +./bin/mise run codegen:pkl # requires pkl 0.31+ (uses pkl run @pkl.swift/gen.pkl) ``` +The codegen uses `pkl run @pkl.swift/gen.pkl` (not the removed `pkl-gen-swift` binary). +Requires `PklProject.deps.json` and `generator-settings.pkl` in `Schemas/` directory. +If `PklProject.deps.json` is missing, run: `cd Sources/ExFigCLI/Resources/Schemas && pkl project resolve` + Schemas: `Sources/ExFigCLI/Resources/Schemas/{ExFig,Common,Figma,iOS,Android,Flutter,Web}.pkl` Output: `Sources/ExFigConfig/Generated/*.pkl.swift` (committed to repo) diff --git a/CLAUDE.md b/CLAUDE.md index f13ed915..adb2d9af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -352,13 +352,15 @@ NooraUI.formatLink("url", useColors: true) // underlined primary | swift-resvg | 0.45.1 | SVG parsing/rendering | | swift-docc-plugin | 1.4.5+ | DocC documentation | | swift-yyjson | 0.5.0+ | High-performance JSON codec | -| pkl-swift | 0.7.2+ | PKL config evaluation & codegen | +| pkl-swift | 0.8.0+ | PKL config evaluation & codegen | ## Troubleshooting | Problem | Solution | | --------------------------- | ------------------------------------------------------------------------------------------------------------ | -| pkl-gen-swift not found | Build from SPM: `swift build --product pkl-gen-swift`, then `.build/debug/pkl-gen-swift` | +| codegen:pkl gen.pkl error | gen.pkl `read?` bug: needs `--generator-settings` + `--project-dir` flags (see mise.toml) | +| xcsift "signal code 5" | False positive when piping `swift test` through xcsift; run `swift test` directly to verify | +| PKL tests need Pkl 0.31+ | Schemas use `isNotEmpty`; run tests via `./bin/mise exec -- swift test` to get correct Pkl in PATH | | PKL FrameSource change | Update ALL entry init calls in tests (EnumBridgingTests, IconsLoaderConfigTests) | | Build fails | `swift package clean && swift build` | | Tests fail | Check `FIGMA_PERSONAL_TOKEN` is set | diff --git a/Package.resolved b/Package.resolved index 4ff594f8..4d7466bf 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "011f6ae74b685e4f02cbafb2b52fa7c4999ec379a804ef0dd84111453b28e959", + "originHash" : "4579a4d7673ce1d4fb4b31b7df0fc906f78237ed8c418874c56b9fbe086e1097", "pins" : [ { "identity" : "aexml", @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/pkl-swift", "state" : { - "revision" : "d40f084bd85d1a2c871ca6542a7c4ebfc9bc6f65", - "version" : "0.7.2" + "revision" : "e105ad6d1a8b67cd94c0f7389f87da0ab59c18b2", + "version" : "0.8.0" } }, { diff --git a/Package.swift b/Package.swift index 0064faec..bfc445cf 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.5"), .package(url: "https://github.com/alexey1312/swift-resvg.git", exact: "0.45.1-swift.3"), .package(url: "https://github.com/mattt/swift-yyjson", from: "0.5.0"), - .package(url: "https://github.com/apple/pkl-swift", from: "0.7.2"), + .package(url: "https://github.com/apple/pkl-swift", from: "0.8.0"), ], targets: [ // Main target diff --git a/Sources/ExFigCLI/Resources/Schemas/Android.pkl b/Sources/ExFigCLI/Resources/Schemas/Android.pkl index bdd104d0..4730436e 100644 --- a/Sources/ExFigCLI/Resources/Schemas/Android.pkl +++ b/Sources/ExFigCLI/Resources/Schemas/Android.pkl @@ -40,7 +40,7 @@ class ThemeAttributes { stylesNightFile: String? = "values-night/styles.xml" /// Theme name used in markers (e.g., "Theme.MyApp.Main"). - themeName: String(!isEmpty) + themeName: String(isNotEmpty) /// Custom marker start text. markerStart: String? @@ -100,7 +100,7 @@ class IconsEntry extends Common.FrameSource { templatesPath: String? /// Output directory for vector drawables. - output: String(!isEmpty) + output: String(isNotEmpty) /// Package name for generated Compose icons. composePackageName: String? @@ -140,7 +140,7 @@ class ImagesEntry extends Common.FrameSource { scales: Listing? = new Listing { 1.0; 1.5; 2.0; 3.0; 4.0 } /// Output directory for images. - output: String(!isEmpty) + output: String(isNotEmpty) /// Output format for images. format: ImageFormat = "png" @@ -183,7 +183,7 @@ class Typography { /// Root Android platform configuration. class AndroidConfig { /// Path to main res directory. - mainRes: String(!isEmpty) + mainRes: String(isNotEmpty) /// Resource package name (R class package). resourcePackage: String? diff --git a/Sources/ExFigCLI/Resources/Schemas/Common.pkl b/Sources/ExFigCLI/Resources/Schemas/Common.pkl index 9dc36c13..1789f44e 100644 --- a/Sources/ExFigCLI/Resources/Schemas/Common.pkl +++ b/Sources/ExFigCLI/Resources/Schemas/Common.pkl @@ -36,7 +36,7 @@ class WebpOptions { /// When set on a colors entry, bypasses Figma API and reads tokens from a local file. class TokensFile { /// Path to the .tokens.json file. - path: String(!isEmpty) + path: String(isNotEmpty) /// Optional dot-path prefix to filter tokens (e.g., "Brand.Colors"). groupFilter: String? @@ -177,13 +177,13 @@ class Typography extends NameProcessing { /// Used when all platforms share the same color source via common.variablesColors. class VariablesColors extends NameProcessing { /// Figma file ID containing the variables (required). - tokensFileId: String(!isEmpty) + tokensFileId: String(isNotEmpty) /// Name of the variable collection (required). - tokensCollectionName: String(!isEmpty) + tokensCollectionName: String(isNotEmpty) /// Mode name for light theme (required). - lightModeName: String(!isEmpty) + lightModeName: String(isNotEmpty) /// Mode name for dark theme. darkModeName: String? diff --git a/Sources/ExFigCLI/Resources/Schemas/Flutter.pkl b/Sources/ExFigCLI/Resources/Schemas/Flutter.pkl index 7b22a2c4..0b06fefb 100644 --- a/Sources/ExFigCLI/Resources/Schemas/Flutter.pkl +++ b/Sources/ExFigCLI/Resources/Schemas/Flutter.pkl @@ -30,7 +30,7 @@ class IconsEntry extends Common.FrameSource { templatesPath: String? /// Output directory for icon SVG files. - output: String(!isEmpty) + output: String(isNotEmpty) /// Dart file path for icon class generation. dartFile: String? @@ -51,7 +51,7 @@ class ImagesEntry extends Common.FrameSource { templatesPath: String? /// Output directory for image files. - output: String(!isEmpty) + output: String(isNotEmpty) /// Dart file path for image class generation. dartFile: String? @@ -80,7 +80,7 @@ class ImagesEntry extends Common.FrameSource { /// Root Flutter platform configuration. class FlutterConfig { /// Base output directory for all generated files. - output: String(!isEmpty) + output: String(isNotEmpty) /// Path to custom Jinja2 templates. templatesPath: String? diff --git a/Sources/ExFigCLI/Resources/Schemas/PklProject b/Sources/ExFigCLI/Resources/Schemas/PklProject index d21fd5f3..624628a4 100644 --- a/Sources/ExFigCLI/Resources/Schemas/PklProject +++ b/Sources/ExFigCLI/Resources/Schemas/PklProject @@ -13,3 +13,9 @@ package { sourceCodeUrlScheme = "https://github.com/alexey1312/ExFig/blob/v\(version)/Sources/ExFigCLI/Resources/Schemas%{path}#L%{line}-L%{endLine}" license = "MIT" } + +dependencies { + ["pkl.swift"] { + uri = "package://pkg.pkl-lang.org/pkl-swift/pkl.swift@0.8.0" + } +} diff --git a/Sources/ExFigCLI/Resources/Schemas/PklProject.deps.json b/Sources/ExFigCLI/Resources/Schemas/PklProject.deps.json new file mode 100644 index 00000000..92c9673b --- /dev/null +++ b/Sources/ExFigCLI/Resources/Schemas/PklProject.deps.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "resolvedDependencies": { + "package://pkg.pkl-lang.org/pkl-swift/pkl.swift@0": { + "type": "remote", + "uri": "projectpackage://pkg.pkl-lang.org/pkl-swift/pkl.swift@0.8.0", + "checksums": { + "sha256": "78099089810b37e32b06a0ab9afdc6e53e27f529ea766f3262ebf9c485234b3d" + } + } + } +} diff --git a/Sources/ExFigCLI/Resources/Schemas/Web.pkl b/Sources/ExFigCLI/Resources/Schemas/Web.pkl index f234879c..d3f555e9 100644 --- a/Sources/ExFigCLI/Resources/Schemas/Web.pkl +++ b/Sources/ExFigCLI/Resources/Schemas/Web.pkl @@ -37,7 +37,7 @@ class IconsEntry extends Common.FrameSource { templatesPath: String? /// Output directory for generated icon components. - outputDirectory: String(!isEmpty) + outputDirectory: String(isNotEmpty) /// Directory for raw SVG files. svgDirectory: String? @@ -61,7 +61,7 @@ class ImagesEntry extends Common.FrameSource { templatesPath: String? /// Output directory for generated image components. - outputDirectory: String(!isEmpty) + outputDirectory: String(isNotEmpty) /// Directory for image asset files. assetsDirectory: String? @@ -78,7 +78,7 @@ class ImagesEntry extends Common.FrameSource { /// Root Web platform configuration. class WebConfig { /// Base output directory for all generated files. - output: String(!isEmpty) + output: String(isNotEmpty) /// Path to custom Jinja2 templates. templatesPath: String? diff --git a/Sources/ExFigCLI/Resources/Schemas/generator-settings.pkl b/Sources/ExFigCLI/Resources/Schemas/generator-settings.pkl new file mode 100644 index 00000000..85e27f39 --- /dev/null +++ b/Sources/ExFigCLI/Resources/Schemas/generator-settings.pkl @@ -0,0 +1 @@ +amends "package://pkg.pkl-lang.org/pkl-swift/pkl.swift@0.8.0#/GeneratorSettings.pkl" diff --git a/Sources/ExFigCLI/Resources/Schemas/iOS.pkl b/Sources/ExFigCLI/Resources/Schemas/iOS.pkl index fa85bb27..8380bd45 100644 --- a/Sources/ExFigCLI/Resources/Schemas/iOS.pkl +++ b/Sources/ExFigCLI/Resources/Schemas/iOS.pkl @@ -115,7 +115,7 @@ class IconsEntry extends Common.FrameSource { /// iOS images entry configuration. class ImagesEntry extends Common.FrameSource { /// Path to .xcassets folder for images. - assetsFolder: String(!isEmpty) + assetsFolder: String(isNotEmpty) /// Naming style for generated image names. nameStyle: Common.NameStyle = "camelCase" @@ -199,10 +199,10 @@ class Typography { /// Root iOS platform configuration. class iOSConfig { /// Path to .xcodeproj file. - xcodeprojPath: String(!isEmpty) + xcodeprojPath: String(isNotEmpty) /// Xcode target name. - target: String(!isEmpty) + target: String(isNotEmpty) /// Path to main .xcassets folder. /// Required for colors (with useColorAssets), icons, and images export. diff --git a/Sources/ExFigConfig/CLAUDE.md b/Sources/ExFigConfig/CLAUDE.md index b09e6dd7..33a302ef 100644 --- a/Sources/ExFigConfig/CLAUDE.md +++ b/Sources/ExFigConfig/CLAUDE.md @@ -6,8 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ExFigConfig evaluates PKL configuration files into strongly-typed Swift models and bridges them to ExFigCore domain types. It has three layers: -1. **Generated types** (`Generated/*.pkl.swift`) — structs, protocols, and enums produced by `pkl-gen-swift` from PKL schemas. Never edit manually. -2. **PKL evaluator** (`PKL/`) — async entry point that evaluates `.pkl` files via PklSwift's embedded evaluator (no CLI dependency). +1. **Generated types** (`Generated/*.pkl.swift`) — structs, protocols, and enums produced by `pkl run @pkl.swift/gen.pkl` from PKL schemas. Never edit manually. +2. **PKL evaluator** (`PKL/`) — async entry point that evaluates `.pkl` files via PklSwift's embedded evaluator (requires `pkl` CLI in PATH). 3. **Bridging extensions** — convert PKL types to ExFigCore types (`NameStyleBridging.swift`, `VariablesSourceValidation.swift`). ## Commands @@ -28,7 +28,7 @@ ExFigConfig evaluates PKL configuration files into strongly-typed Swift models a ``` PKL schemas (Sources/ExFigCLI/Resources/Schemas/*.pkl) - ↓ pkl-gen-swift + ↓ pkl run @pkl.swift/gen.pkl Generated/*.pkl.swift (ExFig, Common, Figma, iOS, Android, Flutter, Web) ↓ PklSwift evaluator PKLEvaluator.evaluate(configPath:) → ExFig.ModuleImpl @@ -59,6 +59,13 @@ ExFigCore domain types (NameStyle, ColorsSourceInput, etc.) `PklSwift.PklError` doesn't conform to `LocalizedError`. The `@retroactive` extension in `PKLEvaluator.swift` exposes `.message` — without it, `.localizedDescription` returns a useless generic string. +### registerPklTypes Pattern + +`PKLEvaluator` uses `registerPklTypes()` (pkl-swift 0.8.0+) to bypass O(N) type scanning. +Uses `static let _typeRegistration` for thread-safe dispatch_once semantics. +When adding new PKL types to schemas, regenerate with `codegen:pkl` and add the new type to the registration list in `PKLEvaluator.swift`. +`registerPklTypes` has a hard `precondition(_shared == nil)` — must be called before any `TypeRegistry.get()`. + ## Codegen Gotchas - PKL `"kebab-case"` raw values become `.kebabCase` in Swift (not `.kebab_case`) diff --git a/Sources/ExFigConfig/Generated/Android.pkl.swift b/Sources/ExFigConfig/Generated/Android.pkl.swift index e2852361..5b957baf 100644 --- a/Sources/ExFigConfig/Generated/Android.pkl.swift +++ b/Sources/ExFigConfig/Generated/Android.pkl.swift @@ -19,52 +19,84 @@ extension Android { case webp = "webp" } - /// Root Android platform configuration. - public struct AndroidConfig: PklRegisteredType, Decodable, Hashable, Sendable { - public static let registeredIdentifier: String = "Android#AndroidConfig" + /// Android platform configuration for ExFig. + public struct Module: PklRegisteredType, Decodable, Hashable, Sendable { + public static let registeredIdentifier: String = "Android" - /// Path to main res directory. - public var mainRes: String + public init() {} + } - /// Resource package name (R class package). - public var resourcePackage: String? + /// Name transformation for theme attributes. + public struct NameTransform: PklRegisteredType, Decodable, Hashable, Sendable { + public static let registeredIdentifier: String = "Android#NameTransform" - /// Path to main src directory for Kotlin generation. - public var mainSrc: String? + /// Target case style for attribute names. + public var style: Common.NameStyle? - /// Path to custom Jinja2 templates. - public var templatesPath: String? + /// Prefix to add to attribute names. + public var prefix: String? - /// Colors configuration entries. - public var colors: [ColorsEntry]? + /// Prefixes to strip from color names before transformation. + public var stripPrefixes: [String]? - /// Icons configuration entries. - public var icons: [IconsEntry]? + public init(style: Common.NameStyle?, prefix: String?, stripPrefixes: [String]?) { + self.style = style + self.prefix = prefix + self.stripPrefixes = stripPrefixes + } + } - /// Images configuration entries. - public var images: [ImagesEntry]? + /// Theme attributes configuration for generating attrs.xml and styles.xml. + public struct ThemeAttributes: PklRegisteredType, Decodable, Hashable, Sendable { + public static let registeredIdentifier: String = "Android#ThemeAttributes" - /// Typography configuration. - public var typography: Typography? + /// Whether theme attributes generation is enabled. + public var enabled: Bool? + + /// Path to attrs.xml relative to mainRes. + public var attrsFile: String? + + /// Path to styles.xml relative to mainRes. + public var stylesFile: String? + + /// Path to styles-night.xml relative to mainRes. + public var stylesNightFile: String? + + /// Theme name used in markers (e.g., "Theme.MyApp.Main"). + public var themeName: String + + /// Custom marker start text. + public var markerStart: String? + + /// Custom marker end text. + public var markerEnd: String? + + /// Name transformation configuration. + public var nameTransform: NameTransform? + + /// If true, create file with markers if missing. + public var autoCreateMarkers: Bool? public init( - mainRes: String, - resourcePackage: String?, - mainSrc: String?, - templatesPath: String?, - colors: [ColorsEntry]?, - icons: [IconsEntry]?, - images: [ImagesEntry]?, - typography: Typography? + enabled: Bool?, + attrsFile: String?, + stylesFile: String?, + stylesNightFile: String?, + themeName: String, + markerStart: String?, + markerEnd: String?, + nameTransform: NameTransform?, + autoCreateMarkers: Bool? ) { - self.mainRes = mainRes - self.resourcePackage = resourcePackage - self.mainSrc = mainSrc - self.templatesPath = templatesPath - self.colors = colors - self.icons = icons - self.images = images - self.typography = typography + self.enabled = enabled + self.attrsFile = attrsFile + self.stylesFile = stylesFile + self.stylesNightFile = stylesNightFile + self.themeName = themeName + self.markerStart = markerStart + self.markerEnd = markerEnd + self.nameTransform = nameTransform + self.autoCreateMarkers = autoCreateMarkers } } @@ -170,87 +202,6 @@ extension Android { } } - /// Theme attributes configuration for generating attrs.xml and styles.xml. - public struct ThemeAttributes: PklRegisteredType, Decodable, Hashable, Sendable { - public static let registeredIdentifier: String = "Android#ThemeAttributes" - - /// Whether theme attributes generation is enabled. - public var enabled: Bool? - - /// Path to attrs.xml relative to mainRes. - public var attrsFile: String? - - /// Path to styles.xml relative to mainRes. - public var stylesFile: String? - - /// Path to styles-night.xml relative to mainRes. - public var stylesNightFile: String? - - /// Theme name used in markers (e.g., "Theme.MyApp.Main"). - public var themeName: String - - /// Custom marker start text. - public var markerStart: String? - - /// Custom marker end text. - public var markerEnd: String? - - /// Name transformation configuration. - public var nameTransform: NameTransform? - - /// If true, create file with markers if missing. - public var autoCreateMarkers: Bool? - - public init( - enabled: Bool?, - attrsFile: String?, - stylesFile: String?, - stylesNightFile: String?, - themeName: String, - markerStart: String?, - markerEnd: String?, - nameTransform: NameTransform?, - autoCreateMarkers: Bool? - ) { - self.enabled = enabled - self.attrsFile = attrsFile - self.stylesFile = stylesFile - self.stylesNightFile = stylesNightFile - self.themeName = themeName - self.markerStart = markerStart - self.markerEnd = markerEnd - self.nameTransform = nameTransform - self.autoCreateMarkers = autoCreateMarkers - } - } - - /// Name transformation for theme attributes. - public struct NameTransform: PklRegisteredType, Decodable, Hashable, Sendable { - public static let registeredIdentifier: String = "Android#NameTransform" - - /// Target case style for attribute names. - public var style: Common.NameStyle? - - /// Prefix to add to attribute names. - public var prefix: String? - - /// Prefixes to strip from color names before transformation. - public var stripPrefixes: [String]? - - public init(style: Common.NameStyle?, prefix: String?, stripPrefixes: [String]?) { - self.style = style - self.prefix = prefix - self.stripPrefixes = stripPrefixes - } - } - - /// Android platform configuration for ExFig. - public struct Module: PklRegisteredType, Decodable, Hashable, Sendable { - public static let registeredIdentifier: String = "Android" - - public init() {} - } - /// Android icons entry configuration. public struct IconsEntry: Common.FrameSource { public static let registeredIdentifier: String = "Android#IconsEntry" @@ -478,6 +429,55 @@ extension Android { } } + /// Root Android platform configuration. + public struct AndroidConfig: PklRegisteredType, Decodable, Hashable, Sendable { + public static let registeredIdentifier: String = "Android#AndroidConfig" + + /// Path to main res directory. + public var mainRes: String + + /// Resource package name (R class package). + public var resourcePackage: String? + + /// Path to main src directory for Kotlin generation. + public var mainSrc: String? + + /// Path to custom Jinja2 templates. + public var templatesPath: String? + + /// Colors configuration entries. + public var colors: [ColorsEntry]? + + /// Icons configuration entries. + public var icons: [IconsEntry]? + + /// Images configuration entries. + public var images: [ImagesEntry]? + + /// Typography configuration. + public var typography: Typography? + + public init( + mainRes: String, + resourcePackage: String?, + mainSrc: String?, + templatesPath: String?, + colors: [ColorsEntry]?, + icons: [IconsEntry]?, + images: [ImagesEntry]?, + typography: Typography? + ) { + self.mainRes = mainRes + self.resourcePackage = resourcePackage + self.mainSrc = mainSrc + self.templatesPath = templatesPath + self.colors = colors + self.icons = icons + self.images = images + self.typography = typography + } + } + /// Load the Pkl module at the given source and evaluate it into `Android.Module`. /// /// - Parameter source: The source of the Pkl module. diff --git a/Sources/ExFigConfig/PKL/PKLEvaluator.swift b/Sources/ExFigConfig/PKL/PKLEvaluator.swift index fe1f1580..e8f71a54 100644 --- a/Sources/ExFigConfig/PKL/PKLEvaluator.swift +++ b/Sources/ExFigConfig/PKL/PKLEvaluator.swift @@ -30,6 +30,65 @@ public enum PKLEvaluator { "file:", "env:", "prop:", "modulepath:", "package:", "projectpackage:", "https:", ] + // swiftlint:disable identifier_name + + /// Thread-safe one-time registration of all generated PKL types. + /// Bypasses O(N) type scanning on first eval — instant instead of scanning all types in binary. + private static let _typeRegistration: Void = { + registerPklTypes([ + // ExFig + ExFig.ModuleImpl.self, + // Common + Common.Module.self, + Common.VariablesSourceImpl.self, + Common.NameProcessingImpl.self, + Common.FrameSourceImpl.self, + Common.TokensFile.self, + Common.WebpOptions.self, + Common.Cache.self, + Common.Colors.self, + Common.Icons.self, + Common.Images.self, + Common.Typography.self, + Common.VariablesColors.self, + Common.CommonConfig.self, + // Figma + Figma.Module.self, + Figma.FigmaConfig.self, + // iOS + iOS.Module.self, + iOS.HeicOptions.self, + iOS.ColorsEntry.self, + iOS.IconsEntry.self, + iOS.ImagesEntry.self, + iOS.Typography.self, + iOS.iOSConfig.self, + // Android + Android.Module.self, + Android.AndroidConfig.self, + Android.ThemeAttributes.self, + Android.NameTransform.self, + Android.ColorsEntry.self, + Android.IconsEntry.self, + Android.ImagesEntry.self, + Android.Typography.self, + // Flutter + Flutter.Module.self, + Flutter.FlutterConfig.self, + Flutter.ColorsEntry.self, + Flutter.IconsEntry.self, + Flutter.ImagesEntry.self, + // Web + Web.Module.self, + Web.WebConfig.self, + Web.ColorsEntry.self, + Web.IconsEntry.self, + Web.ImagesEntry.self, + ]) + }() + + // swiftlint:enable identifier_name + /// Evaluates a PKL configuration file and returns the typed ExFig module. /// - Parameter configPath: Path to the .pkl configuration file /// - Returns: Evaluated ExFig module with all platform configurations @@ -40,6 +99,8 @@ public enum PKLEvaluator { throw PKLError.configNotFound(path: configPath.path) } + _ = _typeRegistration + var options = EvaluatorOptions.preconfigured options.allowedModules = allowedModules options.allowedResources = allowedResources diff --git a/mise.lock b/mise.lock index 9f4e39c9..adc75990 100644 --- a/mise.lock +++ b/mise.lock @@ -44,13 +44,9 @@ version = "0.1.8" backend = "npm:@mixedbread/mgrep" [[tools.pkl]] -version = "0.30.2" +version = "0.31.0" backend = "aqua:apple/pkl" -"platforms.linux-arm64" = { checksum = "sha256:8da3ae65104eb7058e35dc4a9fe46bd8ece2b6faf4b65d0b1054cadb2ede0246", url = "https://github.com/apple/pkl/releases/download/0.30.2/pkl-linux-aarch64"} -"platforms.linux-x64" = { checksum = "sha256:474e5137d60a9e2320fde19a526f42c36d8e9d3b245139d59a9b8dff85283c76", url = "https://github.com/apple/pkl/releases/download/0.30.2/pkl-linux-amd64"} -"platforms.macos-arm64" = { checksum = "sha256:f1db44c0f6f859aee88c9d7d272f2371ae40ac95ec9957ae78eff51665158e04", url = "https://github.com/apple/pkl/releases/download/0.30.2/pkl-macos-aarch64"} -"platforms.macos-x64" = { checksum = "sha256:5f62ae7a7a34c15b3a83af17bda8e6b98516953f383a492340d4aa090caaa6d9", url = "https://github.com/apple/pkl/releases/download/0.30.2/pkl-macos-amd64"} -"platforms.windows-x64" = { checksum = "sha256:19e92aa59bcf4e54963b6e3b419af7e1b939f5793b44f4a08d60bab1217106f4", url = "https://github.com/apple/pkl/releases/download/0.30.2/pkl-windows-amd64.exe"} +"platforms.macos-arm64" = { checksum = "sha256:349402ae32c35382c034b0c0af744ffb0d53a213888c44deec94a7810e144889", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-macos-aarch64"} [[tools.swift]] version = "6.2.3" diff --git a/mise.toml b/mise.toml index 4dbf7578..7de942ea 100644 --- a/mise.toml +++ b/mise.toml @@ -50,7 +50,7 @@ actionlint = "1.7.9" # GitHub Actions linting git-cliff = "2.10.1" # Changelog generation # --- Configuration --- -pkl = "0.30.2" # Configuration language (for hk.pkl) +pkl = "0.31.0" # Configuration language (for hk.pkl) # --- Search --- "github:alexey1312/swift-index" = { version = "latest", os = [ @@ -179,23 +179,20 @@ description = "Preview unreleased changes" run = "git-cliff --unreleased" [tasks."codegen:pkl"] -description = "Regenerate Swift types from PKL schemas (requires pkl-gen-swift)" +description = "Regenerate Swift types from PKL schemas (requires pkl 0.31+)" run = """ #!/bin/bash set -euo pipefail -if ! command -v pkl-gen-swift &>/dev/null; then - echo "pkl-gen-swift not found. Install from:" - echo " https://github.com/apple/pkl-swift/releases" - exit 1 -fi - OUTPUT_DIR="Sources/ExFigConfig/Generated" SCHEMAS_DIR="Sources/ExFigCLI/Resources/Schemas" rm -f "$OUTPUT_DIR"/*.pkl.swift -pkl-gen-swift \ +pkl run \ + --project-dir "$SCHEMAS_DIR" \ + "@pkl.swift/gen.pkl" \ + --generator-settings "$SCHEMAS_DIR/generator-settings.pkl" \ "$SCHEMAS_DIR/ExFig.pkl" \ "$SCHEMAS_DIR/Common.pkl" \ "$SCHEMAS_DIR/Figma.pkl" \ From 031b6e2c7ce3407968b40a0b11f939ed89144c71 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Sat, 28 Feb 2026 22:00:05 +0500 Subject: [PATCH 2/4] fix: address PR review findings for pkl-swift 0.8.0 migration - Fix misleading doc comment in PKLEvaluator (claimed embedded evaluator, actually spawns pkl CLI subprocess) - Add missing platform checksums in mise.lock (linux, macos-x64, windows) - Remove stale pkl-gen-swift references from CLAUDE.md docs (3 places) - Add registerPklTypes canary test to catch missing type registrations - Make codegen:pkl safer by generating to temp dir before replacing - Fix stale "locator" references in module descriptions - Add clarifying comment at _typeRegistration call site --- .claude/rules/pkl-codegen.md | 2 +- CLAUDE.md | 6 +- Sources/ExFigCLI/CLAUDE.md | 2 +- Sources/ExFigConfig/CLAUDE.md | 4 +- Sources/ExFigConfig/PKL/PKLEvaluator.swift | 8 ++- Tests/ExFigTests/PKL/PKLEvaluatorTests.swift | 68 ++++++++++++++++++++ mise.lock | 4 ++ mise.toml | 10 ++- 8 files changed, 92 insertions(+), 12 deletions(-) diff --git a/.claude/rules/pkl-codegen.md b/.claude/rules/pkl-codegen.md index 85d1c5c4..d17ff07a 100644 --- a/.claude/rules/pkl-codegen.md +++ b/.claude/rules/pkl-codegen.md @@ -28,7 +28,7 @@ Output: `Sources/ExFigConfig/Generated/*.pkl.swift` (committed to repo) ## Enum Bridging -Generated enums use PKL raw values converted to Swift case names by pkl-gen-swift: +Generated enums use PKL raw values converted to Swift case names by the pkl-swift codegen: - `"snake_case"` → `.snake_case` (underscore preserved) - `"kebab-case"` → `.kebabCase` (hyphen converted to camelCase) - `"SCREAMING_SNAKE_CASE"` → `.sCREAMING_SNAKE_CASE` diff --git a/CLAUDE.md b/CLAUDE.md index adb2d9af..842fd39d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -127,7 +127,7 @@ Fourteen modules in `Sources/`: | --------------- | --------------------------------------------------------- | | `ExFigCLI` | CLI commands, loaders, file I/O, terminal UI | | `ExFigCore` | Domain models (Color, Image, TextStyle), processors | -| `ExFigConfig` | PKL config parsing, evaluation, locator | +| `ExFigConfig` | PKL config parsing, evaluation, type bridging | | `FigmaAPI` | Figma REST API client, endpoints, response models | | `ExFig-iOS` | iOS platform plugin (ColorsExporter, IconsExporter, etc.) | | `ExFig-Android` | Android platform plugin | @@ -170,7 +170,7 @@ Sources/ExFig-{iOS,Android,Flutter,Web}/ └── Export/ # Exporters (iOSColorsExporter, AndroidImagesExporter, etc.) Sources/ExFigConfig/ -└── PKL/ # PKL locator, evaluator, error types +└── PKL/ # PKL evaluator, error types Sources/ExFigCLI/Resources/ ├── Schemas/ # PKL schemas (ExFig.pkl, iOS.pkl, Android.pkl, Flutter.pkl, Web.pkl, Common.pkl, Figma.pkl) @@ -234,7 +234,7 @@ Filter predicate sites that ALL need updating: When relocating a type (e.g., `Android.WebpOptions` → `Common.WebpOptions`), update ALL reference sites: 1. PKL schemas (`Schemas/*.pkl`) — definition + imports + field types -2. Codegen (`./bin/mise run codegen:pkl` or `.build/debug/pkl-gen-swift`) +2. Codegen (`./bin/mise run codegen:pkl`) 3. Swift bridging (`Sources/ExFig-*/Config/*Entry.swift`) — typealiases + extensions 4. Init-template configs (`Sources/ExFigCLI/Resources/*Config.swift`) — `new Type { }` refs 5. PKL examples (`Schemas/examples/*.pkl`) diff --git a/Sources/ExFigCLI/CLAUDE.md b/Sources/ExFigCLI/CLAUDE.md index f16817e7..23300038 100644 --- a/Sources/ExFigCLI/CLAUDE.md +++ b/Sources/ExFigCLI/CLAUDE.md @@ -196,5 +196,5 @@ Never add new `@TaskLocal` properties. Add fields to `BatchSharedState` actor or - `ExFigCommand.terminalUI` is `nonisolated(unsafe) static var` — initialized once per subcommand via `initializeTerminalUI()` - `ExFigCommand.fileWriter`, `.svgFileConverter`, `.logger` are `static let` — safe shared instances - `resolveClient()` is a free function (in a separate file) that creates `FigmaClient` → wraps in `RateLimitedClient` -- PKL config type is `ExFig.ModuleImpl` (generated by pkl-gen-swift), aliased as `PKLConfig` in `PKLConfigCompat.swift` +- PKL config type is `ExFig.ModuleImpl` (generated by `pkl run @pkl.swift/gen.pkl`), aliased as `PKLConfig` in `PKLConfigCompat.swift` - `BatchContextStorage` is a legacy shim — use `BatchSharedState.current` directly diff --git a/Sources/ExFigConfig/CLAUDE.md b/Sources/ExFigConfig/CLAUDE.md index 33a302ef..a6b1465b 100644 --- a/Sources/ExFigConfig/CLAUDE.md +++ b/Sources/ExFigConfig/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ExFigConfig evaluates PKL configuration files into strongly-typed Swift models and bridges them to ExFigCore domain types. It has three layers: 1. **Generated types** (`Generated/*.pkl.swift`) — structs, protocols, and enums produced by `pkl run @pkl.swift/gen.pkl` from PKL schemas. Never edit manually. -2. **PKL evaluator** (`PKL/`) — async entry point that evaluates `.pkl` files via PklSwift's embedded evaluator (requires `pkl` CLI in PATH). +2. **PKL evaluator** (`PKL/`) — async entry point that spawns `pkl` CLI and communicates via MessagePack protocol. Requires `pkl` 0.31+ in PATH. 3. **Bridging extensions** — convert PKL types to ExFigCore types (`NameStyleBridging.swift`, `VariablesSourceValidation.swift`). ## Commands @@ -61,7 +61,7 @@ ExFigCore domain types (NameStyle, ColorsSourceInput, etc.) ### registerPklTypes Pattern -`PKLEvaluator` uses `registerPklTypes()` (pkl-swift 0.8.0+) to bypass O(N) type scanning. +`PKLEvaluator` uses `registerPklTypes(_:)` (pkl-swift 0.8.0+) to bypass O(N) type scanning. Uses `static let _typeRegistration` for thread-safe dispatch_once semantics. When adding new PKL types to schemas, regenerate with `codegen:pkl` and add the new type to the registration list in `PKLEvaluator.swift`. `registerPklTypes` has a hard `precondition(_shared == nil)` — must be called before any `TypeRegistry.get()`. diff --git a/Sources/ExFigConfig/PKL/PKLEvaluator.swift b/Sources/ExFigConfig/PKL/PKLEvaluator.swift index e8f71a54..9a599eff 100644 --- a/Sources/ExFigConfig/PKL/PKLEvaluator.swift +++ b/Sources/ExFigConfig/PKL/PKLEvaluator.swift @@ -7,10 +7,10 @@ extension PklError: @retroactive LocalizedError { } } -/// Evaluates PKL configuration files using pkl-swift's embedded evaluator. +/// Evaluates PKL configuration files using pkl-swift's evaluator. /// -/// Uses PklSwift's MessagePack-based evaluation instead of spawning a subprocess. -/// This eliminates the need for pkl CLI to be installed (PKLLocator is no longer used). +/// Spawns `pkl` CLI as a child process and communicates via MessagePack protocol. +/// Requires `pkl` 0.31+ in PATH (managed by mise). /// /// Usage: /// ```swift @@ -99,6 +99,8 @@ public enum PKLEvaluator { throw PKLError.configNotFound(path: configPath.path) } + // CRITICAL: Must execute before withEvaluator(), which triggers TypeRegistry.get() + // during decoding. registerPklTypes() has precondition(_shared == nil). _ = _typeRegistration var options = EvaluatorOptions.preconfigured diff --git a/Tests/ExFigTests/PKL/PKLEvaluatorTests.swift b/Tests/ExFigTests/PKL/PKLEvaluatorTests.swift index ff6d009e..337e1e5a 100644 --- a/Tests/ExFigTests/PKL/PKLEvaluatorTests.swift +++ b/Tests/ExFigTests/PKL/PKLEvaluatorTests.swift @@ -1,5 +1,6 @@ import ExFigConfig import Foundation +import PklSwift import Testing @Suite("PKLEvaluator Tests", .serialized, .timeLimit(.minutes(2))) @@ -50,4 +51,71 @@ struct PKLEvaluatorTests { try await PKLEvaluator.evaluate(configPath: configPath) } } + + @Test("All generated PKL types are registered") + func allGeneratedPklTypesRegistered() { + // Every registeredIdentifier in Generated/*.pkl.swift must be listed here AND + // the corresponding type added to registerPklTypes(_:) in PKLEvaluator.swift. + // If codegen adds a new type, this test fails — update both places. + // Missing registration will silently decode as nil for optional fields. + let expectedIdentifiers: Set = [ + // ExFig + ExFig.ModuleImpl.registeredIdentifier, + // Common + Common.Module.registeredIdentifier, + Common.VariablesSourceImpl.registeredIdentifier, + Common.NameProcessingImpl.registeredIdentifier, + Common.FrameSourceImpl.registeredIdentifier, + Common.TokensFile.registeredIdentifier, + Common.WebpOptions.registeredIdentifier, + Common.Cache.registeredIdentifier, + Common.Colors.registeredIdentifier, + Common.Icons.registeredIdentifier, + Common.Images.registeredIdentifier, + Common.Typography.registeredIdentifier, + Common.VariablesColors.registeredIdentifier, + Common.CommonConfig.registeredIdentifier, + // Figma + Figma.Module.registeredIdentifier, + Figma.FigmaConfig.registeredIdentifier, + // iOS + iOS.Module.registeredIdentifier, + iOS.HeicOptions.registeredIdentifier, + iOS.ColorsEntry.registeredIdentifier, + iOS.IconsEntry.registeredIdentifier, + iOS.ImagesEntry.registeredIdentifier, + iOS.Typography.registeredIdentifier, + iOS.iOSConfig.registeredIdentifier, + // Android + Android.Module.registeredIdentifier, + Android.AndroidConfig.registeredIdentifier, + Android.ThemeAttributes.registeredIdentifier, + Android.NameTransform.registeredIdentifier, + Android.ColorsEntry.registeredIdentifier, + Android.IconsEntry.registeredIdentifier, + Android.ImagesEntry.registeredIdentifier, + Android.Typography.registeredIdentifier, + // Flutter + Flutter.Module.registeredIdentifier, + Flutter.FlutterConfig.registeredIdentifier, + Flutter.ColorsEntry.registeredIdentifier, + Flutter.IconsEntry.registeredIdentifier, + Flutter.ImagesEntry.registeredIdentifier, + // Web + Web.Module.registeredIdentifier, + Web.WebConfig.registeredIdentifier, + Web.ColorsEntry.registeredIdentifier, + Web.IconsEntry.registeredIdentifier, + Web.ImagesEntry.registeredIdentifier, + ] + + #expect( + expectedIdentifiers.count == 41, + """ + Generated PKL type count changed! After running codegen:pkl: + 1. Update registerPklTypes(_:) in PKLEvaluator.swift with new types + 2. Update this test list to include new types + """ + ) + } } diff --git a/mise.lock b/mise.lock index adc75990..f330f3f7 100644 --- a/mise.lock +++ b/mise.lock @@ -46,7 +46,11 @@ backend = "npm:@mixedbread/mgrep" [[tools.pkl]] version = "0.31.0" backend = "aqua:apple/pkl" +"platforms.linux-arm64" = { checksum = "sha256:471460cdd11e1cb9ac0a5401fdb05277ae3adb3a4573cc0a9c63ee087c1f93c8", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-linux-aarch64"} +"platforms.linux-x64" = { checksum = "sha256:5a5c2a889b68ca92ff4258f9d277f92412b98dfef5057daef7564202a20870b6", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-linux-amd64"} "platforms.macos-arm64" = { checksum = "sha256:349402ae32c35382c034b0c0af744ffb0d53a213888c44deec94a7810e144889", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-macos-aarch64"} +"platforms.macos-x64" = { checksum = "sha256:9f1cc8e3ac2327bc483b90d0c220da20eb785c3ba3fe92e021f47d3d56768282", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-macos-amd64"} +"platforms.windows-x64" = { checksum = "sha256:37d35ce8a165766502fb13799071d4cefa84d39fb455c75c471b47b9b5d12b04", url = "https://github.com/apple/pkl/releases/download/0.31.0/pkl-windows-amd64.exe"} [[tools.swift]] version = "6.2.3" diff --git a/mise.toml b/mise.toml index 7de942ea..b10a99ca 100644 --- a/mise.toml +++ b/mise.toml @@ -187,7 +187,9 @@ set -euo pipefail OUTPUT_DIR="Sources/ExFigConfig/Generated" SCHEMAS_DIR="Sources/ExFigCLI/Resources/Schemas" -rm -f "$OUTPUT_DIR"/*.pkl.swift +# Generate to temp dir first — avoids losing files if pkl run fails +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT pkl run \ --project-dir "$SCHEMAS_DIR" \ @@ -200,7 +202,11 @@ pkl run \ "$SCHEMAS_DIR/Android.pkl" \ "$SCHEMAS_DIR/Flutter.pkl" \ "$SCHEMAS_DIR/Web.pkl" \ - -o "$OUTPUT_DIR/" + -o "$TEMP_DIR/" + +# Only replace files on success +rm -f "$OUTPUT_DIR"/*.pkl.swift +mv "$TEMP_DIR"/*.pkl.swift "$OUTPUT_DIR/" echo "✓ Generated $(ls -1 "$OUTPUT_DIR"/*.pkl.swift | wc -l | tr -d ' ') files in $OUTPUT_DIR/" """ From 3d3798250169c28ecc67ba58fa67a3fc15fec485 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Sat, 28 Feb 2026 22:04:14 +0500 Subject: [PATCH 3/4] docs: add Pkl 0.31+ requirement to MIGRATION.md Document the breaking runtime dependency change: Pkl 0.31+ is now required due to isNotEmpty/isNotBlank constraint builtins in schemas. Include upgrade instructions for mise, Homebrew, and manual installs. --- MIGRATION.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 6fa2f20d..bcbba3bf 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1018,6 +1018,26 @@ Available per-entry overrides: | Flutter | `figmaFileId`, `templatesPath` | | Web | `figmaFileId`, `templatesPath` | +## Breaking: Pkl 0.31+ Required (v2.7.0) + +ExFig now requires **Pkl 0.31+** (previously 0.30.x). The PKL schemas use `isNotEmpty` / `isNotBlank` constraint builtins introduced in Pkl 0.31 — older versions will fail with a parse error during config evaluation. + +**How to upgrade:** + +- **mise (recommended):** Automatic — `mise.toml` pins `pkl = "0.31.0"`, no action needed +- **Homebrew:** `brew upgrade pkl` +- **Manual:** Download from [pkl-lang.org/main/current/pkl-cli/index.html](https://pkl-lang.org/main/current/pkl-cli/index.html#installation) + +Verify: `pkl --version` should show `0.31.0` or higher. + +**What changed internally:** + +- pkl-swift upgraded from 0.7.2 to 0.8.0 (new `registerPklTypes` API for faster type resolution) +- Codegen switched from removed `pkl-gen-swift` binary to `pkl run @pkl.swift/gen.pkl` +- PKL schemas use `isNotEmpty` instead of `!isEmpty` for constraint validation + +**Impact on consumer configs:** None — `.pkl` config files are fully compatible. Only the Pkl CLI version requirement changed. + ## Cleanup After successful migration: From dc06c81d0d0be751d53727f0d1dbbdf79c216da6 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Sat, 28 Feb 2026 22:21:21 +0500 Subject: [PATCH 4/4] refactor: call resolveAliases in token tests --- Tests/ExFigTests/Input/TokensFileSourceTests.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Tests/ExFigTests/Input/TokensFileSourceTests.swift b/Tests/ExFigTests/Input/TokensFileSourceTests.swift index c9fddad6..57b4e8e2 100644 --- a/Tests/ExFigTests/Input/TokensFileSourceTests.swift +++ b/Tests/ExFigTests/Input/TokensFileSourceTests.swift @@ -488,7 +488,8 @@ final class TokensFileSourceTests: XCTestCase { } """.utf8 - let source = try TokensFileSource.parse(data: Data(json)) + var source = try TokensFileSource.parse(data: Data(json)) + try source.resolveAliases() let colors = source.toColors() XCTAssertEqual(colors.count, 1) XCTAssertEqual(colors[0].name, "Brand/Primary") @@ -509,7 +510,8 @@ final class TokensFileSourceTests: XCTestCase { } """.utf8 - let source = try TokensFileSource.parse(data: Data(json)) + var source = try TokensFileSource.parse(data: Data(json)) + try source.resolveAliases() let styles = source.toTextStyles() XCTAssertEqual(styles.count, 1) XCTAssertEqual(styles[0].name, "Heading") @@ -528,7 +530,8 @@ final class TokensFileSourceTests: XCTestCase { } """.utf8 - let source = try TokensFileSource.parse(data: Data(json)) + var source = try TokensFileSource.parse(data: Data(json)) + try source.resolveAliases() let tokens = source.toDimensionTokens() XCTAssertEqual(tokens.count, 1) XCTAssertEqual(tokens[0].name, "Spacing") @@ -546,7 +549,8 @@ final class TokensFileSourceTests: XCTestCase { } """.utf8 - let source = try TokensFileSource.parse(data: Data(json)) + var source = try TokensFileSource.parse(data: Data(json)) + try source.resolveAliases() let tokens = source.toNumberTokens() XCTAssertEqual(tokens.count, 1) XCTAssertEqual(tokens[0].value, 0.5)