Context
I'm embedding ThemeKit inside a Swift Package that is itself a library (an SDK consumed by third-party app developers), not a top-level app. The generated theme should stay an internal implementation detail of the SDK — it must not leak into, or collide with, the consuming app's own symbols.
Problem
Every generator hardcodes public, and the root type name is a fixed literal. E.g.:
EnvironmentThemeGenerator.swift:
nonisolated extension EnvironmentValues {
@Entry public var theme: Theme = .default
}
ThemeStructGenerator.swift:
nonisolated public struct Theme: Sendable, Codable, Equatable { ... }
Same pattern in CategoryStructGenerator and ShapeStyleExtensionGenerator. ThemeConfig.GenerationConfig only exposes outputPath and shouldGeneratePreview — there's no way to opt into a lower access level or rename the root type via theme.json.
Why this matters for library authors
- Bare-name collision.
Theme is a very generic public name. If the host app (or any other dependency in its graph) also declares a top-level Theme, every unqualified use of Theme anywhere the two are simultaneously visible becomes ambiguous — a compile break caused purely by importing the SDK.
EnvironmentValues.theme collision. If the host app also uses ThemeKit for its own theming, it generates its own public var theme: Theme in its module. Two extensions now grant .theme on EnvironmentValues with different concrete return types — some call sites resolve fine via inference, others (implicit-type usage, generic contexts) become ambiguous or just confusing (autocomplete shows two unrelated Theme types).
- Unwanted API-surface leakage. Even without a literal collision, a library's internal styling tokens (
.onSurface, .primaryColor, etc.) become part of its public interface just by having generated the theme — when the intent was almost always for the theme to be a private implementation detail.
Proposed fix
Add to GenerationConfig (theme.json's config section):
"accessLevel": "internal" | "public" (default "public" to preserve current behavior) — threaded through every generator (ThemeStructGenerator, EnvironmentThemeGenerator, CategoryStructGenerator, ShapeStyleExtensionGenerator, CopyWithGenerator, etc.) in place of the hardcoded public keyword.
- Optionally, a
"rootTypeName" override (default "Theme") so the generated root struct can be renamed to something collision-resistant (e.g. ConsentTheme) without hand-editing generated files.
Today the workaround is hand-editing every generated file after each run (rename Theme → something project-specific, drop public → default internal), which has to be redone on every regeneration. A config-level switch would make ThemeKit safe to embed in a redistributable package out of the box.
Happy to help sketch/PR this if useful — just wanted to raise it as an issue first since it touches several generator files.
Context
I'm embedding ThemeKit inside a Swift Package that is itself a library (an SDK consumed by third-party app developers), not a top-level app. The generated theme should stay an internal implementation detail of the SDK — it must not leak into, or collide with, the consuming app's own symbols.
Problem
Every generator hardcodes
public, and the root type name is a fixed literal. E.g.:EnvironmentThemeGenerator.swift:ThemeStructGenerator.swift:Same pattern in
CategoryStructGeneratorandShapeStyleExtensionGenerator.ThemeConfig.GenerationConfigonly exposesoutputPathandshouldGeneratePreview— there's no way to opt into a lower access level or rename the root type viatheme.json.Why this matters for library authors
Themeis a very generic public name. If the host app (or any other dependency in its graph) also declares a top-levelTheme, every unqualified use ofThemeanywhere the two are simultaneously visible becomes ambiguous — a compile break caused purely by importing the SDK.EnvironmentValues.themecollision. If the host app also uses ThemeKit for its own theming, it generates its ownpublic var theme: Themein its module. Two extensions now grant.themeonEnvironmentValueswith different concrete return types — some call sites resolve fine via inference, others (implicit-type usage, generic contexts) become ambiguous or just confusing (autocomplete shows two unrelatedThemetypes)..onSurface,.primaryColor, etc.) become part of its public interface just by having generated the theme — when the intent was almost always for the theme to be a private implementation detail.Proposed fix
Add to
GenerationConfig(theme.json'sconfigsection):"accessLevel": "internal" | "public"(default"public"to preserve current behavior) — threaded through every generator (ThemeStructGenerator,EnvironmentThemeGenerator,CategoryStructGenerator,ShapeStyleExtensionGenerator,CopyWithGenerator, etc.) in place of the hardcodedpublickeyword."rootTypeName"override (default"Theme") so the generated root struct can be renamed to something collision-resistant (e.g.ConsentTheme) without hand-editing generated files.Today the workaround is hand-editing every generated file after each run (rename
Theme→ something project-specific, droppublic→ defaultinternal), which has to be redone on every regeneration. A config-level switch would make ThemeKit safe to embed in a redistributable package out of the box.Happy to help sketch/PR this if useful — just wanted to raise it as an issue first since it touches several generator files.