terrazzo-plugin-figma-json 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1828,20 +1828,63 @@ function transformToken(token, rawValue, options, context, allTokens, rawTokens,
1828
1828
  });
1829
1829
  }
1830
1830
  /**
1831
+ * Collect the minimal set of resolver inputs that the build step will query.
1832
+ *
1833
+ * The build step requests transforms for:
1834
+ * 1. The default input (first permutation — all modifier defaults)
1835
+ * 2. One input per individual modifier context (default + one override)
1836
+ *
1837
+ * This avoids the cartesian-product explosion from listPermutations().
1838
+ * For example, 8 modifiers with ~4 contexts each produce ~40k full
1839
+ * permutations but only ~30 targeted inputs.
1840
+ */
1841
+ function collectBuildInputs(resolver) {
1842
+ const defaultInput = resolver.listPermutations()[0] ?? {};
1843
+ const inputs = [defaultInput];
1844
+ const resolverSource = resolver.source;
1845
+ if (!resolverSource?.modifiers) return inputs;
1846
+ for (const [modifierName, modifier] of Object.entries(resolverSource.modifiers)) {
1847
+ if (!modifier.contexts) continue;
1848
+ if (modifierName === "tzMode") {
1849
+ const contextNames = Object.keys(modifier.contexts);
1850
+ if (contextNames.length === 1 && contextNames[0] === ".") continue;
1851
+ }
1852
+ for (const contextName of Object.keys(modifier.contexts)) {
1853
+ if (defaultInput[modifierName] === contextName) continue;
1854
+ inputs.push({
1855
+ ...defaultInput,
1856
+ [modifierName]: contextName
1857
+ });
1858
+ }
1859
+ }
1860
+ return inputs;
1861
+ }
1862
+ /**
1831
1863
  * Transform DTCG tokens into Figma-compatible format.
1832
- * Uses the resolver to iterate all permutations and convert token values.
1864
+ *
1865
+ * Only iterates the minimal set of resolver inputs that the build step
1866
+ * will actually query (default + one per modifier context), avoiding
1867
+ * the combinatorial explosion of the full permutation set.
1833
1868
  *
1834
1869
  * @param options - Transform options containing the transform hook context and plugin options
1835
1870
  */
1836
1871
  function transformFigmaJson({ transform, options }) {
1837
1872
  const { setTransform, context, resolver, tokens: rawTokens } = transform;
1838
1873
  const shouldExclude = createExcludeMatcher(options.exclude);
1839
- const permutations = resolver.listPermutations();
1840
- for (const input of permutations) {
1874
+ const inputs = collectBuildInputs(resolver);
1875
+ for (const input of inputs) {
1841
1876
  if (Object.keys(input).length === 0) continue;
1842
1877
  const contextTokens = resolver.apply(input);
1843
1878
  for (const token of Object.values(contextTokens)) {
1844
1879
  if (shouldExclude(token.id)) continue;
1880
+ if (!(token.id in rawTokens)) {
1881
+ context.logger.warn({
1882
+ group: "plugin",
1883
+ label: PLUGIN_NAME,
1884
+ message: `Token "${token.id}" exists only in a non-default modifier context and is not present in the base token set. Skipping — Terrazzo does not support setTransform() for context-only tokens.`
1885
+ });
1886
+ continue;
1887
+ }
1845
1888
  transformToken(token, token.$value, options, context, contextTokens, rawTokens, setTransform, input);
1846
1889
  }
1847
1890
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["figmaUnsupportedType"],"sources":["../src/constants.ts","../src/utils.ts","../src/build/helpers.ts","../src/build/aliases.ts","../src/build/output.ts","../src/build/source-maps.ts","../src/build/index.ts","../src/lint/figma-unsupported-type.ts","../src/converters/color.ts","../src/converters/dimension.ts","../src/converters/border.ts","../src/converters/duration.ts","../src/converters/font-family.ts","../src/converters/font-weight.ts","../src/converters/gradient.ts","../src/converters/number.ts","../src/converters/shadow.ts","../src/converters/line-height.ts","../src/converters/typography.ts","../src/converters/index.ts","../src/transform.ts","../src/index.ts"],"sourcesContent":["export const PLUGIN_NAME = 'terrazzo-plugin-figma-json';\n\nexport const FORMAT_ID = 'figma-json';\n\n/**\n * Token types supported by Figma.\n */\nexport const SUPPORTED_TYPES = [\n 'color',\n 'dimension',\n 'duration',\n 'fontFamily',\n 'fontWeight',\n 'number',\n 'typography',\n 'shadow',\n 'border',\n 'gradient',\n] as const;\n\nexport type SupportedType = (typeof SUPPORTED_TYPES)[number];\n\n/**\n * Token types that are not supported by Figma and will be dropped with a warning.\n */\nexport const UNSUPPORTED_TYPES = ['transition', 'strokeStyle', 'cubicBezier'] as const;\n\nexport type UnsupportedType = (typeof UNSUPPORTED_TYPES)[number];\n\n/**\n * Color spaces that Figma natively supports.\n */\nexport const FIGMA_COLOR_SPACES = ['srgb', 'hsl'] as const;\n\nexport type FigmaColorSpace = (typeof FIGMA_COLOR_SPACES)[number];\n","import wcmatch from 'wildcard-match';\nimport type {\n DTCGBorderValue,\n DTCGColorValue,\n DTCGDimensionValue,\n DTCGDurationValue,\n DTCGGradientStop,\n DTCGShadowValue,\n DTCGTypographyValue,\n TokenExtensions,\n} from './types.js';\n\n/**\n * Compute the localID for a token in Figma's slash-notation format.\n * This is used as the `localID` in `setTransform()` and represents\n * how the token is referenced within the Figma JSON format.\n *\n * @param tokenId - Dot-notation token ID (e.g., \"color.primary.base\")\n * @returns Slash-notation Figma variable name (e.g., \"color/primary/base\")\n *\n * @example\n * toFigmaLocalID(\"color.primary.base\") // \"color/primary/base\"\n * toFigmaLocalID(\"spacing.200\") // \"spacing/200\"\n */\nexport function toFigmaLocalID(tokenId: string): string {\n return tokenId.replace(/\\./g, '/');\n}\n\n/**\n * Create an exclude matcher function from glob patterns.\n *\n * @param patterns - Array of glob patterns to match against token IDs\n * @returns A function that returns true if the token ID should be excluded\n */\nexport function createExcludeMatcher(patterns: string[] | undefined): (tokenId: string) => boolean {\n return patterns?.length ? wcmatch(patterns) : () => false;\n}\n\n/**\n * Filter extensions to only include Figma-specific ones (com.figma.*).\n * Removes non-Figma extensions to keep output clean.\n *\n * @param extensions - Token extensions object that may include various namespaces\n * @returns Object with only com.figma.* keys, or undefined if none exist\n *\n * @example\n * filterFigmaExtensions({ \"com.figma.type\": \"boolean\", \"custom.ext\": \"value\" })\n * // { \"com.figma.type\": \"boolean\" }\n */\nexport function filterFigmaExtensions(extensions: TokenExtensions | undefined): TokenExtensions | undefined {\n if (!extensions) {\n return undefined;\n }\n\n const figmaExtensions: TokenExtensions = {};\n let hasFigmaExtensions = false;\n\n for (const [key, value] of Object.entries(extensions)) {\n if (key.startsWith('com.figma')) {\n figmaExtensions[key] = value;\n hasFigmaExtensions = true;\n }\n }\n\n return hasFigmaExtensions ? figmaExtensions : undefined;\n}\n\n/**\n * Safely parse a transform value that may be a JSON string or already an object.\n *\n * @param value - The transform value (JSON string or object)\n * @returns The parsed object, or null if parsing fails\n */\nexport function parseTransformValue(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'string') {\n return value as Record<string, unknown>;\n }\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Type guard to validate DTCGColorValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGColorValue with colorSpace, components, and optional alpha\n */\nexport function isDTCGColorValue(value: unknown): value is DTCGColorValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (typeof v.colorSpace !== 'string') {\n return false;\n }\n if (!Array.isArray(v.components) || v.components.length !== 3) {\n return false;\n }\n for (const c of v.components) {\n if (c !== 'none' && typeof c !== 'number') {\n return false;\n }\n }\n if (v.alpha !== undefined && v.alpha !== 'none' && typeof v.alpha !== 'number') {\n return false;\n }\n return true;\n}\n\n/**\n * Type guard to validate DTCGDimensionValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGDimensionValue with numeric value and string unit\n */\nexport function isDTCGDimensionValue(value: unknown): value is DTCGDimensionValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n return typeof v.value === 'number' && typeof v.unit === 'string';\n}\n\n/**\n * Type guard to validate DTCGDurationValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGDurationValue with numeric value and string unit\n */\nexport function isDTCGDurationValue(value: unknown): value is DTCGDurationValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n return typeof v.value === 'number' && typeof v.unit === 'string';\n}\n\n/**\n * Type guard to validate DTCGTypographyValue structure.\n * Only checks that it's an object - individual properties are validated during conversion.\n *\n * @param value - The value to check\n * @returns True if value is a non-null, non-array object\n */\nexport function isDTCGTypographyValue(value: unknown): value is DTCGTypographyValue {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Type guard to validate DTCGShadowValue structure.\n * Accepts a single shadow object or an array of shadow objects.\n *\n * @param value - The value to check\n * @returns True if value is a non-null object or a non-empty array of non-null objects\n */\nexport function isDTCGShadowValue(value: unknown): value is DTCGShadowValue | DTCGShadowValue[] {\n if (Array.isArray(value)) {\n return value.length > 0 && value.every((item) => item !== null && typeof item === 'object');\n }\n return value !== null && typeof value === 'object';\n}\n\n/**\n * Type guard to validate DTCGBorderValue structure.\n * Only checks that it's an object - individual properties are validated during conversion.\n *\n * @param value - The value to check\n * @returns True if value is a non-null, non-array object\n */\nexport function isDTCGBorderValue(value: unknown): value is DTCGBorderValue {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Type guard to validate DTCGGradientValue structure.\n * Checks that it's an array of gradient stops.\n *\n * @param value - The value to check\n * @returns True if value is a non-empty array of non-null objects\n */\nexport function isDTCGGradientValue(value: unknown): value is DTCGGradientStop[] {\n return Array.isArray(value) && value.length > 0 && value.every((item) => item !== null && typeof item === 'object');\n}\n","import type { Resolver } from '@terrazzo/parser';\nimport type { TokenExtensions } from '../types.js';\nimport { filterFigmaExtensions } from '../utils.js';\nimport type { FigmaOutputExtensions, ParsedTokenValue } from './types.js';\n\n/**\n * Set a nested property on an object using dot-notation path.\n * Creates intermediate objects as needed.\n * Note: this intentionally mutates `obj` for efficient output tree construction.\n *\n * @param obj - The object to modify\n * @param path - Dot-notation path (e.g., \"color.primary.base\")\n * @param value - The value to set at the path\n *\n * @example\n * const obj = {};\n * setNestedProperty(obj, \"color.primary\", { $value: \"#ff0000\" });\n * // obj = { color: { primary: { $value: \"#ff0000\" } } }\n */\nexport function setNestedProperty(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n if (parts.length === 0) {\n return;\n }\n\n let current = obj;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n if (!(part in current)) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const lastPart = parts[parts.length - 1]!;\n current[lastPart] = value;\n}\n\n/**\n * Convert $root in a token ID to root for Figma compatibility.\n * DTCG uses $root for default values, but Figma doesn't support $ in names.\n *\n * @param path - The token path that may contain \".$root\" segments\n * @returns The path with \".$root\" replaced by \".root\"\n */\nexport function normalizeRootInPath(path: string): string {\n return path.replace(/\\.\\$root\\b/g, '.root');\n}\n\n/**\n * Check if this resolver is the auto-generated default (no user-defined resolver file).\n * The default resolver has only an \"allTokens\" set and a \"tzMode\" modifier with \".\" context.\n *\n * @param resolverSource - The resolver source configuration to inspect\n * @returns True if this is the auto-generated default resolver\n */\nexport function isDefaultResolver(resolverSource: NonNullable<Resolver['source']>): boolean {\n const sets = resolverSource.sets ?? {};\n const modifiers = resolverSource.modifiers ?? {};\n\n const setNames = Object.keys(sets);\n if (setNames.length !== 1 || setNames[0] !== 'allTokens') {\n return false;\n }\n\n const modifierNames = Object.keys(modifiers);\n if (modifierNames.length === 0) {\n return true;\n }\n if (modifierNames.length === 1 && modifierNames[0] === 'tzMode') {\n const tzMode = modifiers.tzMode;\n if (tzMode?.contexts) {\n const contextNames = Object.keys(tzMode.contexts);\n return contextNames.length === 1 && contextNames[0] === '.';\n }\n }\n\n return false;\n}\n\n/**\n * Build a default input from the resolver's first permutation.\n *\n * @param resolver - The terrazzo resolver instance\n * @returns The first permutation input object, or an empty object if none exist\n */\nexport function getDefaultInput(resolver: Resolver): Record<string, string> {\n const permutations = resolver.listPermutations();\n return permutations[0] ?? {};\n}\n\n/**\n * Return a new ParsedTokenValue with $description and filtered $extensions added.\n *\n * @param parsedValue - The parsed token value to augment\n * @param token - The source token containing $description and $extensions metadata\n * @returns A new ParsedTokenValue with description and Figma-specific extensions merged in\n */\nexport function withTokenMetadata(\n parsedValue: ParsedTokenValue,\n token: { $description?: string; $extensions?: Record<string, unknown> },\n): ParsedTokenValue {\n let result = parsedValue;\n\n if (token.$description) {\n result = { ...result, $description: token.$description };\n }\n\n const figmaExtensions = filterFigmaExtensions(token.$extensions as TokenExtensions | undefined);\n if (figmaExtensions) {\n // Merge with any existing extensions (e.g., aliasData added later)\n const existing = result.$extensions ?? {};\n result = {\n ...result,\n $extensions: {\n ...(figmaExtensions as unknown as FigmaOutputExtensions),\n ...existing,\n },\n };\n }\n\n return result;\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport type { SupportedType } from '../constants.js';\nimport type { PartialAliasOf, TokenWithPartialAlias } from '../types.js';\nimport { toFigmaLocalID } from '../utils.js';\nimport { normalizeRootInPath } from './helpers.js';\nimport type { AliasReferenceOptions, ParsedTokenValue, SourceInfo } from './types.js';\n\n/**\n * Extract partialAliasOf from a token if present.\n * This property is added by terrazzo parser for composite tokens but not in public types.\n *\n * @param token - The token object to inspect\n * @returns The partialAliasOf record mapping sub-properties to alias targets, or undefined\n */\nexport function getPartialAliasOf(token: unknown): PartialAliasOf | undefined {\n if (token && typeof token === 'object' && 'partialAliasOf' in token) {\n const value = (token as TokenWithPartialAlias).partialAliasOf;\n if (value && typeof value === 'object') {\n return value;\n }\n }\n return undefined;\n}\n\n/**\n * Get the correct alias reference for a composite sub-property.\n * When a composite property references another composite token of the same type,\n * the alias needs to point to the corresponding sub-token.\n *\n * @param aliasOf - The raw alias target token ID, or undefined if not an alias\n * @param propertyName - The sub-property name (e.g., \"color\", \"width\")\n * @param allTokens - Map of all tokens for type checking\n * @param parentType - The parent composite token's type (e.g., \"border\", \"shadow\")\n * @returns The resolved alias target (with sub-property suffix if needed), or undefined\n */\nexport function getSubTokenAlias(\n aliasOf: string | undefined,\n propertyName: string,\n allTokens: Record<string, { $type?: string }> | undefined,\n parentType: SupportedType,\n): string | undefined {\n if (!aliasOf) {\n return undefined;\n }\n const referencedToken = allTokens?.[aliasOf];\n if (referencedToken?.$type === parentType) {\n return `${aliasOf}.${propertyName}`;\n }\n return aliasOf;\n}\n\n/**\n * Compute the direct alias target for a simple (non-composite) token.\n *\n * @param token - The normalized token to inspect\n * @returns The direct alias target token ID, or undefined if the token is not an alias\n */\nexport function getDirectAliasTarget(token: TokenNormalized): string | undefined {\n const aliasOf = token.aliasOf;\n if (!aliasOf) {\n return undefined;\n }\n\n // Use originalValue.$value to get the direct reference (not fully resolved chain)\n // e.g., \"{dimension.size.height.baseline}\" instead of \"dimension.100\"\n const originalValueStr = token.originalValue?.$value;\n if (typeof originalValueStr === 'string' && originalValueStr.startsWith('{') && originalValueStr.endsWith('}')) {\n return originalValueStr.slice(1, -1);\n }\n\n return typeof aliasOf === 'string' ? aliasOf : undefined;\n}\n\n/**\n * Collect all non-undefined string references from a partialAliasOf object,\n * recursively flattening any nested arrays or objects.\n *\n * @param value - The value to extract references from (string, array, or object)\n * @param refs - Mutable array to collect found string references into\n */\nfunction collectRefs(value: unknown, refs: string[]): void {\n if (typeof value === 'string') {\n refs.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n collectRefs(item, refs);\n }\n } else if (value && typeof value === 'object') {\n for (const v of Object.values(value)) {\n collectRefs(v, refs);\n }\n }\n}\n\n/**\n * For tokens without a direct alias, check if all sub-property references\n * in partialAliasOf point to the same token. If so, the token can be treated\n * as a unified alias to that single target.\n *\n * This handles cases like JSON pointer references where individual sub-properties\n * (e.g., colorSpace, components[0], components[1], etc.) all reference the same\n * source token.\n *\n * Works for any token type with partialAliasOf — colors, borders, shadows, etc.\n *\n * @param token - The normalized token to inspect\n * @returns The unified alias target token ID, or undefined if refs are mixed or absent\n */\nexport function getUnifiedPartialAlias(token: TokenNormalized): string | undefined {\n // Skip tokens that already have a direct alias\n if (token.aliasOf) {\n return undefined;\n }\n\n const partialAliasOf = getPartialAliasOf(token);\n if (!partialAliasOf) {\n return undefined;\n }\n\n const refs: string[] = [];\n collectRefs(partialAliasOf, refs);\n\n if (refs.length > 0) {\n const uniqueRefs = [...new Set(refs)];\n if (uniqueRefs.length === 1) {\n return uniqueRefs[0];\n }\n }\n\n return undefined;\n}\n\n/**\n * Compute the alias target for a token, considering direct aliases and\n * unified partial aliases (where all sub-property refs point to the same token).\n *\n * @param token - The normalized token to inspect\n * @returns The alias target token ID, or undefined if the token is not an alias\n */\nexport function computeAliasTarget(token: TokenNormalized): string | undefined {\n return getDirectAliasTarget(token) ?? getUnifiedPartialAlias(token);\n}\n\n/**\n * Compute alias targets for each sub-property of a composite token.\n *\n * @param token - The normalized composite token\n * @param subTokenSuffixes - Array of sub-property suffixes to resolve (e.g., [\"color\", \"width\"])\n * @param allTokens - Map of all tokens for type checking during alias resolution\n * @returns Map from sub-property suffix to alias target token ID (or undefined if not aliased)\n */\nexport function computeSubTokenAliases(\n token: TokenNormalized,\n subTokenSuffixes: string[],\n allTokens: Record<string, TokenNormalized> | undefined,\n): Map<string, string | undefined> {\n const result = new Map<string, string | undefined>();\n const partialAliasOf = getPartialAliasOf(token);\n const parentType = token.$type as SupportedType;\n\n for (const suffix of subTokenSuffixes) {\n const raw = partialAliasOf?.[suffix];\n result.set(suffix, getSubTokenAlias(raw, suffix, allTokens, parentType));\n }\n\n return result;\n}\n\n/**\n * Resolve an alias reference and return a new ParsedTokenValue with the\n * appropriate $value or $extensions set.\n *\n * - Same-file references: Sets $value to curly brace syntax (e.g., \"{color.primary}\")\n * - Cross-file references: Keeps resolved $value and adds com.figma.aliasData extension\n *\n * Returns the original value unchanged if no alias handling applies.\n *\n * @param parsedValue - The parsed token value to potentially augment with alias info\n * @param options - Alias resolution options including target, source context, and reference maps\n * @returns A new ParsedTokenValue with alias reference syntax or extension, or the original value unchanged\n */\nexport function withAliasReference(\n parsedValue: ParsedTokenValue,\n { aliasOf, sourceName, tokenSources, tokenOutputPaths, preserveReferences }: AliasReferenceOptions,\n): ParsedTokenValue {\n if (!preserveReferences || !aliasOf) {\n return parsedValue;\n }\n\n // Normalize aliasOf to remove $root for lookups (terrazzo uses normalized IDs)\n const normalizedAliasOf = aliasOf.replace(/\\.\\$root\\b/g, '');\n // Get target's output path, or normalize $root -> root in the original aliasOf\n const targetOutputPath = tokenOutputPaths.get(normalizedAliasOf) ?? normalizeRootInPath(aliasOf);\n\n // Find the target token's sources, handling split sub-tokens by looking up parent\n let targetSources: SourceInfo[] | undefined = tokenSources.get(normalizedAliasOf);\n if (!targetSources) {\n // Try parent tokens for split sub-tokens (e.g., \"typography.heading.fontFamily\")\n const parts = normalizedAliasOf.split('.');\n while (parts.length > 1 && !targetSources) {\n parts.pop();\n targetSources = tokenSources.get(parts.join('.'));\n }\n }\n\n if (!targetSources?.length) {\n return parsedValue;\n }\n\n // Check if target exists in current source (same-file reference)\n const inCurrentSource = targetSources.some((s) => s.source === sourceName);\n if (inCurrentSource) {\n return { ...parsedValue, $value: `{${targetOutputPath}}` };\n }\n\n // Check for SET sources only (not modifier contexts)\n const setSource = targetSources.find((s) => !s.isModifier);\n if (setSource) {\n const existingExtensions = parsedValue.$extensions ?? {};\n return {\n ...parsedValue,\n $extensions: {\n ...existingExtensions,\n 'com.figma.aliasData': {\n targetVariableSetName: setSource.source,\n targetVariableName: toFigmaLocalID(targetOutputPath),\n },\n },\n };\n }\n\n return parsedValue;\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport type { FigmaJsonPluginOptions } from '../types.js';\nimport { parseTransformValue } from '../utils.js';\nimport { computeAliasTarget, computeSubTokenAliases, withAliasReference } from './aliases.js';\nimport { setNestedProperty, withTokenMetadata } from './helpers.js';\nimport type { ParsedTokenValue, SourceInfo } from './types.js';\n\n/**\n * Process a single transform and place it in the output object.\n * Handles both simple tokens (string value) and composite tokens (Record<string, string> value).\n *\n * Alias resolution is computed here from the token metadata (transform.token)\n * rather than from embedded internal metadata in the value.\n *\n * Note: this intentionally mutates `output` via setNestedProperty for efficient\n * output tree construction.\n *\n * @param transform - The transform result containing the token and its transformed value\n * @param output - The mutable output object to place the token into (mutated in place)\n * @param sourceName - The current source/context name for alias resolution\n * @param tokenName - Optional custom function to control the output token name\n * @param tokenOutputPaths - Map of token IDs to their output paths\n * @param tokenSources - Map of token IDs to their source info for cross-file alias resolution\n * @param preserveReferences - Whether to preserve alias references in output\n * @param shouldExclude - Function to check if a token ID should be excluded\n * @param allTokens - Map of all tokens for composite alias resolution\n */\nexport function processTransform(\n transform: {\n token: TokenNormalized;\n value: string | Record<string, string>;\n },\n output: Record<string, unknown>,\n sourceName: string,\n tokenName: FigmaJsonPluginOptions['tokenName'],\n tokenOutputPaths: Map<string, string>,\n tokenSources: Map<string, SourceInfo[]>,\n preserveReferences: boolean,\n shouldExclude: (id: string) => boolean,\n allTokens: Record<string, TokenNormalized> | undefined,\n): void {\n const token = transform.token;\n const tokenId = token.id;\n\n if (shouldExclude(tokenId)) {\n return;\n }\n\n const outputName = tokenName?.(token) ?? tokenOutputPaths.get(tokenId) ?? tokenId;\n\n if (typeof transform.value === 'string') {\n // Simple token: parse value, compute alias from token metadata, add metadata\n const rawParsed = parseTransformValue(transform.value) as ParsedTokenValue | null;\n if (!rawParsed) {\n return;\n }\n\n // Compute alias from the token itself\n const aliasOf = computeAliasTarget(token);\n\n let parsedValue = withTokenMetadata(rawParsed, token);\n\n if (aliasOf) {\n parsedValue = withAliasReference(parsedValue, {\n aliasOf,\n sourceName,\n tokenSources,\n tokenOutputPaths,\n preserveReferences,\n });\n }\n\n setNestedProperty(output, outputName, parsedValue);\n } else {\n // Composite token (Record<string, string>): expand sub-tokens into output\n const parentOutputPath = tokenOutputPaths.get(tokenId);\n\n // Compute alias targets for all sub-properties from the token metadata\n const subTokenSuffixes = Object.keys(transform.value);\n const subTokenAliases = computeSubTokenAliases(token, subTokenSuffixes, allTokens);\n\n for (const [suffix, subValueStr] of Object.entries(transform.value)) {\n const rawSubParsed = parseTransformValue(subValueStr) as ParsedTokenValue | null;\n if (!rawSubParsed) {\n continue;\n }\n\n // Get alias for this sub-property from computed aliases\n const aliasOf = subTokenAliases.get(suffix);\n\n let subParsed = withTokenMetadata(rawSubParsed, token);\n\n if (aliasOf) {\n subParsed = withAliasReference(subParsed, {\n aliasOf,\n sourceName,\n tokenSources,\n tokenOutputPaths,\n preserveReferences,\n });\n }\n\n // Build sub-token output path: use parent's output path (preserves $root) + suffix\n let subOutputName: string;\n if (tokenName) {\n subOutputName = `${tokenName(token)}.${suffix}`;\n } else if (parentOutputPath && parentOutputPath !== tokenId) {\n subOutputName = `${parentOutputPath}.${suffix}`;\n } else {\n subOutputName = `${outputName}.${suffix}`;\n }\n\n setNestedProperty(output, subOutputName, subParsed);\n }\n }\n}\n","import type { Resolver } from '@terrazzo/parser';\nimport type { SourceInfo, TokenIdInfo, TokenSourceMaps } from './types.js';\n\n/**\n * Extract token IDs from a resolver group (token definitions).\n * Recursively walks the group structure to find all token IDs.\n *\n * @param group - The resolver group object containing nested token definitions\n * @param prefix - Dot-notation prefix for building full token paths (used in recursion)\n * @returns Array of token ID info objects with normalized IDs and output paths\n */\nexport function extractTokenIds(group: Record<string, unknown>, prefix = ''): TokenIdInfo[] {\n const ids: TokenIdInfo[] = [];\n\n for (const [key, value] of Object.entries(group)) {\n if (key.startsWith('$') && key !== '$root') {\n continue;\n }\n\n const outputKey = key === '$root' ? 'root' : key;\n const outputPath = prefix ? `${prefix}.${outputKey}` : outputKey;\n const normalizedPath = key === '$root' ? prefix : outputPath;\n\n if (value && typeof value === 'object' && '$value' in value) {\n if (normalizedPath) {\n ids.push({ id: normalizedPath, outputPath });\n }\n } else if (value && typeof value === 'object') {\n ids.push(...extractTokenIds(value as Record<string, unknown>, outputPath));\n }\n }\n\n return ids;\n}\n\n/**\n * Build maps tracking which tokens belong to which sources.\n * Processes both sets and modifier contexts from the resolver source.\n *\n * @param resolverSource - The resolver source configuration containing sets and modifiers\n * @returns Token source maps with tokenSources, tokenOutputPaths, and allContexts\n */\nexport function buildTokenSourceMaps(resolverSource: NonNullable<Resolver['source']>): TokenSourceMaps {\n const tokenSources = new Map<string, SourceInfo[]>();\n const tokenOutputPaths = new Map<string, string>();\n const allContexts = new Set<string>();\n\n function addTokenSource(tokenId: string, outputPath: string, info: SourceInfo) {\n const existing = tokenSources.get(tokenId);\n if (existing) {\n existing.push(info);\n } else {\n tokenSources.set(tokenId, [info]);\n }\n if (!tokenOutputPaths.has(tokenId)) {\n tokenOutputPaths.set(tokenId, outputPath);\n }\n }\n\n // Process sets\n if (resolverSource.sets) {\n for (const [setName, set] of Object.entries(resolverSource.sets)) {\n if (set.sources) {\n for (const source of set.sources) {\n const tokenInfos = extractTokenIds(source as Record<string, unknown>);\n for (const { id, outputPath } of tokenInfos) {\n addTokenSource(id, outputPath, {\n source: setName,\n isModifier: false,\n });\n }\n }\n }\n }\n }\n\n // Process modifiers (skip auto-generated tzMode with \".\" context)\n if (resolverSource.modifiers) {\n for (const [modifierName, modifier] of Object.entries(resolverSource.modifiers)) {\n if (modifier.contexts) {\n // Skip the auto-generated tzMode modifier\n if (modifierName === 'tzMode') {\n const contextNames = Object.keys(modifier.contexts);\n if (contextNames.length === 1 && contextNames[0] === '.') {\n continue;\n }\n }\n\n for (const [contextName, contextSources] of Object.entries(modifier.contexts)) {\n const contextKey = `${modifierName}-${contextName}`;\n allContexts.add(contextKey);\n\n if (Array.isArray(contextSources)) {\n for (const source of contextSources) {\n const tokenInfos = extractTokenIds(source as Record<string, unknown>);\n for (const { id, outputPath } of tokenInfos) {\n addTokenSource(id, outputPath, {\n source: contextKey,\n isModifier: true,\n modifierName,\n contextName,\n });\n }\n }\n }\n }\n }\n }\n }\n\n return { tokenSources, tokenOutputPaths, allContexts };\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport { FORMAT_ID } from '../constants.js';\nimport { createExcludeMatcher } from '../utils.js';\nimport { getDefaultInput, isDefaultResolver } from './helpers.js';\nimport { processTransform } from './output.js';\nimport { buildTokenSourceMaps } from './source-maps.js';\nimport type { BuildOptions, SourceInfo } from './types.js';\n\nexport type { BuildOptions } from './types.js';\n\n/**\n * Build the Figma-compatible JSON output from transformed tokens.\n * Uses the resolver to determine output file structure.\n *\n * @param options - Build options including getTransforms, exclude patterns, tokenName, preserveReferences, and resolver\n * @returns Map of output name to JSON string (e.g., \"primitive\" → \"{...}\", \"default\" for single-file output)\n */\nexport default function buildFigmaJson({\n getTransforms,\n exclude,\n tokenName,\n preserveReferences = true,\n resolver,\n}: BuildOptions): Map<string, string> {\n const shouldExclude = createExcludeMatcher(exclude);\n const resolverSource = resolver.source;\n\n if (!resolverSource) {\n return new Map();\n }\n\n // Build maps tracking token sources and output paths\n const { tokenSources, tokenOutputPaths, allContexts } = buildTokenSourceMaps(resolverSource);\n\n // Build a combined token map for alias resolution in composite tokens\n const defaultInput = getDefaultInput(resolver);\n const defaultTokens: Record<string, TokenNormalized> | undefined = (() => {\n try {\n return resolver.apply(defaultInput);\n } catch {\n return undefined;\n }\n })();\n\n // Group outputs by source\n const outputBySource = new Map<string, Record<string, unknown>>();\n\n // Initialize empty outputs for all contexts\n for (const contextKey of allContexts) {\n outputBySource.set(contextKey, {});\n }\n\n // Get transforms using default input (for set tokens)\n const defaultTransforms = getTransforms({\n format: FORMAT_ID,\n input: defaultInput,\n });\n\n // Process set tokens using default transforms\n for (const transform of defaultTransforms) {\n if (!transform.token) {\n continue;\n }\n\n const tokenId = transform.token.id;\n const sources = tokenSources.get(tokenId) ?? [];\n const setSource = sources.find((s) => !s.isModifier);\n if (!setSource) {\n continue;\n }\n\n const sourceName = setSource.source;\n let sourceOutput = outputBySource.get(sourceName);\n if (!sourceOutput) {\n sourceOutput = {};\n outputBySource.set(sourceName, sourceOutput);\n }\n\n processTransform(\n transform,\n sourceOutput,\n sourceName,\n tokenName,\n tokenOutputPaths,\n tokenSources,\n preserveReferences,\n shouldExclude,\n defaultTokens,\n );\n }\n\n // Process modifier context tokens\n const modifierTokensByContext = new Map<string, Set<string>>();\n\n for (const [tokenId, sources] of tokenSources) {\n for (const sourceInfo of sources) {\n if (!sourceInfo.isModifier) {\n continue;\n }\n\n const contextKey = sourceInfo.source;\n const existing = modifierTokensByContext.get(contextKey);\n if (existing) {\n existing.add(tokenId);\n } else {\n modifierTokensByContext.set(contextKey, new Set([tokenId]));\n }\n }\n }\n\n for (const [contextKey, tokenIds] of modifierTokensByContext) {\n let contextInfo: SourceInfo | undefined;\n for (const tokenId of tokenIds) {\n const sources = tokenSources.get(tokenId);\n contextInfo = sources?.find((s) => s.source === contextKey);\n if (contextInfo) {\n break;\n }\n }\n\n if (!contextInfo?.modifierName || !contextInfo?.contextName) {\n continue;\n }\n\n const input: Record<string, string> = { ...defaultInput };\n input[contextInfo.modifierName] = contextInfo.contextName;\n\n const contextTransforms = getTransforms({ format: FORMAT_ID, input });\n\n // Build context-specific token map for alias resolution\n const contextTokens: Record<string, TokenNormalized> | undefined = (() => {\n try {\n return resolver.apply(input);\n } catch {\n return undefined;\n }\n })();\n\n let contextOutput = outputBySource.get(contextKey);\n if (!contextOutput) {\n contextOutput = {};\n outputBySource.set(contextKey, contextOutput);\n }\n\n for (const tokenId of tokenIds) {\n if (shouldExclude(tokenId)) {\n continue;\n }\n\n const transform = contextTransforms.find((t) => t.token?.id === tokenId);\n if (!transform?.token) {\n continue;\n }\n\n processTransform(\n transform,\n contextOutput,\n contextKey,\n tokenName,\n tokenOutputPaths,\n tokenSources,\n preserveReferences,\n shouldExclude,\n contextTokens,\n );\n }\n }\n\n // For the default resolver (allTokens only), rename to \"default\" for index.ts mapping\n if (isDefaultResolver(resolverSource)) {\n const allTokensOutput = outputBySource.get('allTokens');\n if (allTokensOutput) {\n outputBySource.delete('allTokens');\n outputBySource.set('default', allTokensOutput);\n }\n }\n\n // Return split output by source\n const result = new Map<string, string>();\n for (const [sourceName, output] of outputBySource) {\n result.set(sourceName, JSON.stringify(output, null, 2));\n }\n return result;\n}\n","import type { LintRule } from '@terrazzo/parser';\nimport { SUPPORTED_TYPES, UNSUPPORTED_TYPES } from '../constants.js';\n\n/**\n * Lint rule that warns when tokens have $type values that are not supported by Figma.\n *\n * Unsupported types (transition, strokeStyle, cubicBezier) will be dropped\n * during transformation. Unknown types (not in either supported or known-unsupported\n * lists) will also be flagged.\n *\n * Users opt in via config:\n * ```ts\n * lint: { rules: { 'figma/unsupported-type': 'warn' } }\n * ```\n */\nconst figmaUnsupportedType: LintRule<'unsupported' | 'unknown'> = {\n meta: {\n messages: {\n unsupported:\n 'Token \"{{id}}\" has $type \"{{type}}\" which is not supported by Figma and will be skipped. Consider excluding it or using a supported type.',\n unknown:\n 'Token \"{{id}}\" has $type \"{{type}}\" which is not recognized. It will be skipped during Figma JSON generation.',\n },\n },\n defaultOptions: {},\n create(context) {\n for (const [id, token] of Object.entries(context.tokens)) {\n const type = token.$type;\n if (!type) {\n continue;\n }\n\n if (SUPPORTED_TYPES.includes(type as (typeof SUPPORTED_TYPES)[number])) {\n continue;\n }\n\n const isKnownUnsupported = UNSUPPORTED_TYPES.includes(type as (typeof UNSUPPORTED_TYPES)[number]);\n\n context.report({\n messageId: isKnownUnsupported ? 'unsupported' : 'unknown',\n node: token.source?.node,\n data: { id, type },\n });\n }\n },\n};\n\nexport default figmaUnsupportedType;\n","import Color from 'colorjs.io';\nimport { FIGMA_COLOR_SPACES, PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGColorValue } from '../types.js';\nimport { isDTCGColorValue } from '../utils.js';\n\n/**\n * Number of decimal places to round color components to.\n * 6 decimals provides sufficient precision while avoiding floating-point issues.\n */\nconst COLOR_PRECISION = 6;\n\n/**\n * Round a number to COLOR_PRECISION decimal places and clamp to [0, 1] range.\n * Prevents floating-point precision issues (e.g., 1.0000000000000007 -> 1).\n *\n * @param value - Color component value (typically 0-1 for sRGB)\n * @returns Rounded and clamped value in [0, 1] range\n */\nfunction roundAndClamp(value: number): number {\n const rounded = Math.round(value * 10 ** COLOR_PRECISION) / 10 ** COLOR_PRECISION;\n return Math.max(0, Math.min(1, rounded));\n}\n\n/**\n * Normalize color components: round to precision and clamp to valid range.\n * Applies roundAndClamp to each component in the RGB/HSL triplet.\n *\n * @param components - Array of 3 color component values\n * @returns Normalized triplet with values rounded and clamped\n */\nfunction normalizeComponents(components: [number, number, number]): [number, number, number] {\n return components.map(roundAndClamp) as [number, number, number];\n}\n\n/**\n * Map DTCG color space names to colorjs.io color space IDs.\n */\nconst DTCG_TO_COLORJS_SPACE: Record<string, string> = {\n srgb: 'srgb',\n 'srgb-linear': 'srgb-linear',\n hsl: 'hsl',\n hwb: 'hwb',\n lab: 'lab',\n lch: 'lch',\n oklab: 'oklab',\n oklch: 'oklch',\n 'display-p3': 'p3',\n 'a98-rgb': 'a98rgb',\n 'prophoto-rgb': 'prophoto',\n rec2020: 'rec2020',\n 'xyz-d65': 'xyz-d65',\n 'xyz-d50': 'xyz-d50',\n};\n\n/**\n * Convert a DTCG color value to Figma-compatible format.\n * Figma only supports sRGB and HSL color spaces.\n *\n * @example\n * // sRGB colors pass through unchanged\n * convertColor({\n * colorSpace: \"srgb\",\n * components: [0.5, 0.5, 0.5],\n * alpha: 1\n * }, context);\n * // => { value: { colorSpace: \"srgb\", components: [0.5, 0.5, 0.5], alpha: 1 } }\n *\n * @example\n * // OKLCH colors are converted to sRGB\n * convertColor({\n * colorSpace: \"oklch\",\n * components: [0.7, 0.15, 150]\n * }, context);\n * // => { value: { colorSpace: \"srgb\", components: [...], alpha: 1 } }\n *\n * @param value - The DTCG color value to convert (should match DTCGColorValue structure)\n * @param context - Converter context with logger and plugin options\n * @returns Converted color value in sRGB or HSL, or skip indicator for invalid values\n */\nexport function convertColor(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGColorValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid color value: expected object with colorSpace and components`,\n });\n return { value: undefined, skip: true };\n }\n const color = value;\n\n // If already in a Figma-compatible color space, pass through with alpha normalization\n if (FIGMA_COLOR_SPACES.includes(color.colorSpace as (typeof FIGMA_COLOR_SPACES)[number])) {\n // Handle 'none' values in components\n const components = color.components.map((c) => (c === 'none' ? 0 : c)) as [number, number, number];\n\n // Only normalize sRGB components (which are in 0-1 range), not HSL (which uses different ranges)\n const normalizedComponents = color.colorSpace === 'srgb' ? normalizeComponents(components) : components;\n\n return {\n value: {\n ...color,\n components: normalizedComponents,\n alpha: color.alpha === 'none' ? 1 : (color.alpha ?? 1),\n },\n };\n }\n\n // Handle 'none' values - treat as 0 for conversion purposes\n const components = color.components.map((c) => (c === 'none' ? 0 : c)) as [number, number, number];\n\n // Get the colorjs.io color space ID\n const colorjsSpace = DTCG_TO_COLORJS_SPACE[color.colorSpace];\n if (!colorjsSpace) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unknown color space: ${color.colorSpace}`,\n });\n return { value: undefined, skip: true };\n }\n\n try {\n // Create color in the source color space\n const sourceColor = new Color(colorjsSpace, components);\n\n // Convert to sRGB\n const srgbColor = sourceColor.to('srgb');\n\n // Check if gamut clipping is needed\n if (!srgbColor.inGamut()) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color was clipped to sRGB gamut (original color space: ${color.colorSpace})`,\n });\n srgbColor.toGamut({ method: 'css' });\n }\n\n // Get the sRGB coordinates and normalize them\n const srgbChannels = normalizeComponents(srgbColor.coords as [number, number, number]);\n\n // Log info about color space conversion (expected behavior for non-sRGB colors)\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color converted from ${color.colorSpace} to sRGB`,\n });\n\n const result: DTCGColorValue = {\n colorSpace: 'srgb',\n components: srgbChannels,\n alpha: color.alpha === 'none' ? 1 : (color.alpha ?? 1),\n };\n\n return { value: result };\n } catch (err) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color conversion failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n return { value: undefined, skip: true };\n }\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { isDTCGDimensionValue } from '../utils.js';\n\n/**\n * Convert a DTCG dimension value to Figma-compatible format.\n * Figma only supports px units.\n *\n * @example\n * // px values pass through unchanged\n * convertDimension({ value: 16, unit: \"px\" }, context);\n * // => { value: { value: 16, unit: \"px\" } }\n *\n * @example\n * // rem values are converted to px (default base: 16px)\n * convertDimension({ value: 1.5, unit: \"rem\" }, context);\n * // => { value: { value: 24, unit: \"px\" } }\n *\n * @param value - The DTCG dimension value to convert (should match DTCGDimensionValue structure)\n * @param context - Converter context with logger and plugin options (includes remBasePx)\n * @returns Converted dimension in px units, or skip indicator for unsupported units\n */\nexport function convertDimension(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGDimensionValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid dimension value: expected object with value (number) and unit (string)`,\n });\n return { value: undefined, skip: true };\n }\n const dimension = value;\n\n // Validate numeric value\n if (!Number.isFinite(dimension.value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid dimension value: ${dimension.value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // px passthrough\n if (dimension.unit === 'px') {\n return { value: dimension };\n }\n\n // rem to px conversion\n if (dimension.unit === 'rem') {\n const remBasePx = context.options.remBasePx ?? 16;\n const pxValue = dimension.value * remBasePx;\n\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" converted from ${dimension.value}rem to ${pxValue}px (base: ${remBasePx}px)`,\n });\n\n return {\n value: {\n value: pxValue,\n unit: 'px',\n },\n };\n }\n\n // Unknown unit - warn and skip\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unsupported dimension unit: \"${dimension.unit}\". Figma only supports px units. Convert the value to px or use the 'transform' option to handle this token.`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGBorderValue } from '../utils.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\n\n/**\n * Convert a DTCG border value to Figma-compatible format.\n * Border tokens are partially split into individual sub-tokens.\n * Only color and width are supported; style is dropped.\n *\n * @param value - The DTCG border value (object with color, width, and optional style)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with color and width sub-tokens, or skip indicator for invalid values\n */\nexport function convertBorder(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGBorderValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid border value: expected object, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const border = value;\n const subTokens: SubToken[] = [];\n\n // Convert color\n if (border.color !== undefined) {\n const result = convertColor(border.color, {\n ...context,\n tokenId: `${context.tokenId}.color`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: 'color', $type: 'color', value: result.value });\n }\n }\n\n // Convert width\n if (border.width !== undefined) {\n const result = convertDimension(border.width, {\n ...context,\n tokenId: `${context.tokenId}.width`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: 'width', $type: 'dimension', value: result.value });\n }\n }\n\n // Drop style (can't be represented as a Figma variable)\n if (border.style !== undefined) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" border \"style\" property dropped (variables cannot be applied to border style in Figma)`,\n });\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" border value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { isDTCGDurationValue } from '../utils.js';\n\n/**\n * Convert a DTCG duration value to Figma-compatible format.\n * Figma only supports seconds (s) unit.\n *\n * @example\n * // s values pass through unchanged\n * convertDuration({ value: 0.5, unit: \"s\" }, context);\n * // => { value: { value: 0.5, unit: \"s\" } }\n *\n * @example\n * // ms values are converted to s\n * convertDuration({ value: 500, unit: \"ms\" }, context);\n * // => { value: { value: 0.5, unit: \"s\" } }\n *\n * @param value - The DTCG duration value to convert (should match DTCGDurationValue structure)\n * @param context - Converter context with logger and plugin options\n * @returns Converted duration in seconds, or skip indicator for unsupported units\n */\nexport function convertDuration(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGDurationValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid duration value: expected object with value (number) and unit (string)`,\n });\n return { value: undefined, skip: true };\n }\n const duration = value;\n\n // Validate numeric value\n if (!Number.isFinite(duration.value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid duration value: ${duration.value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // s passthrough\n if (duration.unit === 's') {\n return { value: duration };\n }\n\n // ms to s conversion (lossless)\n if (duration.unit === 'ms') {\n const sValue = duration.value / 1000;\n\n // This is a lossless conversion, so just info level (not warning)\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" converted from ${duration.value}ms to ${sValue}s`,\n });\n\n return {\n value: {\n value: sValue,\n unit: 's',\n },\n };\n }\n\n // Unknown unit - warn and skip\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unsupported duration unit: \"${duration.unit}\". Figma only supports seconds (s). Convert the value to seconds or use the 'transform' option to handle this token.`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Convert a DTCG fontFamily value to Figma-compatible format.\n * Figma requires a single string, not an array.\n *\n * @example\n * // String values pass through unchanged\n * convertFontFamily(\"Inter\", context);\n * // => { value: \"Inter\" }\n *\n * @example\n * // Arrays are truncated to the first element\n * convertFontFamily([\"Inter\", \"Helvetica\", \"sans-serif\"], context);\n * // => { value: \"Inter\" } (with warning about dropped fallbacks)\n *\n * @param value - The DTCG fontFamily value (string or string array)\n * @param context - Converter context with logger and plugin options\n * @returns Single font family string, or skip indicator for invalid values\n */\nexport function convertFontFamily(value: unknown, context: ConverterContext): ConverterResult {\n // String passthrough\n if (typeof value === 'string') {\n return { value };\n }\n\n // Array - take first element\n if (Array.isArray(value)) {\n if (value.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has empty fontFamily array`,\n });\n return { value: undefined, skip: true };\n }\n\n const firstFont = value[0];\n\n if (value.length > 1) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" fontFamily array truncated to first element \"${firstFont}\" (dropped: ${value.slice(1).join(', ')})`,\n });\n }\n\n return { value: firstFont };\n }\n\n // Invalid value\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontFamily value: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Valid string aliases for font weights as per W3C DTCG spec.\n */\nconst FONT_WEIGHT_ALIASES: Record<string, number> = {\n thin: 100,\n hairline: 100,\n 'extra-light': 200,\n 'ultra-light': 200,\n light: 300,\n normal: 400,\n regular: 400,\n book: 400,\n medium: 500,\n 'semi-bold': 600,\n 'demi-bold': 600,\n bold: 700,\n 'extra-bold': 800,\n 'ultra-bold': 800,\n black: 900,\n heavy: 900,\n 'extra-black': 950,\n 'ultra-black': 950,\n};\n\n/**\n * Convert a DTCG fontWeight value to Figma-compatible format.\n * Output type matches input type (string stays string, number stays number).\n *\n * @example\n * // Number values pass through with validation (1-1000)\n * convertFontWeight(400, context);\n * // => { value: 400 }\n *\n * @example\n * // String aliases pass through if valid\n * convertFontWeight(\"bold\", context);\n * // => { value: \"bold\" }\n *\n * @param value - The DTCG fontWeight value (number 1-1000 or string alias like \"bold\")\n * @param context - Converter context with logger and plugin options\n * @returns Font weight value with outputType set to 'number' or 'string', or skip indicator for invalid values\n */\nexport function convertFontWeight(value: unknown, context: ConverterContext): ConverterResult {\n // Number passthrough - validate range, output as 'number' type\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || value < 1 || value > 1000) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontWeight value: ${value} (must be 1-1000)`,\n });\n return { value: undefined, skip: true };\n }\n return { value, outputType: 'number' };\n }\n\n // String - validate against known aliases, output as 'string' type\n if (typeof value === 'string') {\n const normalized = value.toLowerCase();\n if (!(normalized in FONT_WEIGHT_ALIASES)) {\n const validAliases = Object.keys(FONT_WEIGHT_ALIASES).slice(0, 5).join(', ');\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unknown fontWeight alias: \"${value}\". Valid aliases include: ${validAliases}, etc. Use a valid alias or a numeric weight (1-1000).`,\n });\n return { value: undefined, skip: true };\n }\n // Pass through the original string value - Figma accepts string font weights\n return { value, outputType: 'string' };\n }\n\n // Invalid type\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontWeight type: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGGradientValue } from '../utils.js';\nimport { convertColor } from './color.js';\n\n/**\n * Convert a DTCG gradient value to Figma-compatible format.\n * Gradient tokens are partially split: only stop colors are extracted.\n * Stop positions are dropped since they can't be represented as Figma variables.\n *\n * @param value - The DTCG gradient value (array of gradient stops with color and position)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with color sub-tokens for each stop, or skip indicator for invalid values\n */\nexport function convertGradient(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGGradientValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid gradient value: expected array of gradient stops, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const subTokens: SubToken[] = [];\n let hasPosition = false;\n\n for (let i = 0; i < value.length; i++) {\n const stop = value[i]!;\n\n if (stop.color !== undefined) {\n const aliasKey = `${i}.color`;\n const result = convertColor(stop.color, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'color', value: result.value });\n }\n }\n\n if (stop.position !== undefined) {\n hasPosition = true;\n }\n }\n\n // Log once if any positions were dropped\n if (hasPosition) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" gradient \"position\" values dropped (variables cannot be applied to gradient stop positions in Figma)`,\n });\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" gradient value has no valid color stops`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Convert a DTCG number value to Figma-compatible format.\n * Can output as Boolean if token has com.figma.type extension set to \"boolean\".\n *\n * @example\n * // Regular number token\n * convertNumber(1.5, context) // => { value: 1.5 }\n *\n * @example\n * // Boolean extension: 0 becomes false, non-zero becomes true\n * // Token with $extensions: { \"com.figma\": { \"type\": \"boolean\" } }\n * convertNumber(0, contextWithBooleanExt) // => { value: false }\n * convertNumber(1, contextWithBooleanExt) // => { value: true }\n *\n * @param value - The DTCG number value to convert\n * @param context - Converter context with logger, plugin options, and token extensions\n * @returns Number value (or boolean if com.figma.type is \"boolean\"), or skip indicator for invalid values\n */\nexport function convertNumber(value: unknown, context: ConverterContext): ConverterResult {\n if (typeof value !== 'number') {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid number value: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n if (!Number.isFinite(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has non-finite number value: ${value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // Check for boolean type override via com.figma.type extension\n // Per Figma docs: $extensions: { \"com.figma.type\": \"boolean\" }\n const figmaType = context.extensions?.['com.figma.type'];\n if (figmaType === 'boolean') {\n // Convert number to boolean: 0 = false, non-zero = true\n return { value: value !== 0 };\n }\n\n // Number passthrough\n return { value };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGShadowValue, SubToken } from '../types.js';\nimport { isDTCGShadowValue } from '../utils.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\n\n/**\n * Convert a single shadow object's properties into sub-tokens.\n *\n * @param shadow - The shadow value object\n * @param prefix - Prefix for sub-token IDs (empty for single, \"0.\" for arrays)\n * @param context - Converter context\n * @returns Array of sub-tokens\n */\nfunction convertShadowLayer(shadow: DTCGShadowValue, prefix: string, context: ConverterContext): SubToken[] {\n const subTokens: SubToken[] = [];\n\n // Convert color\n if (shadow.color !== undefined) {\n const aliasKey = `${prefix}color`;\n const result = convertColor(shadow.color, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'color', value: result.value });\n }\n }\n\n // Convert offsetX\n if (shadow.offsetX !== undefined) {\n const aliasKey = `${prefix}offsetX`;\n const result = convertDimension(shadow.offsetX, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert offsetY\n if (shadow.offsetY !== undefined) {\n const aliasKey = `${prefix}offsetY`;\n const result = convertDimension(shadow.offsetY, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert blur\n if (shadow.blur !== undefined) {\n const aliasKey = `${prefix}blur`;\n const result = convertDimension(shadow.blur, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert spread\n if (shadow.spread !== undefined) {\n const aliasKey = `${prefix}spread`;\n const result = convertDimension(shadow.spread, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Drop inset (can't be applied in Figma)\n if (shadow.inset !== undefined) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" shadow \"inset\" property dropped (variables cannot be applied to inset shadows in Figma)`,\n });\n }\n\n return subTokens;\n}\n\n/**\n * Convert a DTCG shadow value to Figma-compatible format.\n * Shadow tokens are split into individual sub-tokens since Figma\n * doesn't support the composite shadow type.\n *\n * Single shadows produce: color, offsetX, offsetY, blur, spread\n * Multiple shadow layers produce indexed sub-tokens: 0.color, 0.offsetX, ..., 1.color, etc.\n *\n * @param value - The DTCG shadow value (single object or array of shadow layers)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with sub-tokens for each shadow property, or skip indicator for invalid values\n */\nexport function convertShadow(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGShadowValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid shadow value: expected object or array, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const subTokens: SubToken[] = [];\n\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single-element array: treat as a single shadow (no index prefix)\n const layerTokens = convertShadowLayer(value[0] as DTCGShadowValue, '', context);\n subTokens.push(...layerTokens);\n } else {\n // Multiple shadow layers: use indexed prefixes\n for (let i = 0; i < value.length; i++) {\n const layer = value[i] as DTCGShadowValue;\n const layerTokens = convertShadowLayer(layer, `${i}.`, context);\n subTokens.push(...layerTokens);\n }\n }\n } else {\n // Single shadow object\n const layerTokens = convertShadowLayer(value, '', context);\n subTokens.push(...layerTokens);\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" shadow value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGDimensionValue } from '../types.js';\n\n/**\n * Context for lineHeight conversion, extending the base converter context\n * with the fontSize value needed for calculating absolute lineHeight.\n */\nexport interface LineHeightConverterContext extends ConverterContext {\n /**\n * The resolved fontSize dimension value from the typography token.\n * Required to calculate absolute lineHeight from the multiplier.\n * Should already be converted to px units.\n */\n fontSize?: DTCGDimensionValue;\n}\n\n/**\n * Convert a W3C DTCG lineHeight value to Figma-compatible format.\n *\n * ## W3C DTCG vs Figma Incompatibility\n *\n * The W3C DTCG specification defines lineHeight as a **number** type -\n * a unitless multiplier relative to fontSize (e.g., `1.5` means 1.5× the\n * font size). This matches CSS behavior where `line-height: 1.5` is unitless.\n *\n * However, Figma Variables require lineHeight to be a **dimension** type\n * with explicit px units. There is no way to represent a unitless multiplier\n * in Figma's variable system.\n *\n * ## Conversion Strategy\n *\n * This converter calculates the absolute lineHeight by multiplying the\n * unitless multiplier with the fontSize:\n *\n * `absoluteLineHeight = lineHeight × fontSize`\n *\n * For example: `lineHeight: 1.5` with `fontSize: 16px` → `24px`\n *\n * ## Trade-off: Loss of Token Reference\n *\n * When converting a multiplier to an absolute dimension, any reference to\n * a primitive number token is lost. This is unavoidable because:\n *\n * 1. Figma does not support unitless multipliers for lineHeight\n * 2. We must compute a concrete px value at build time\n * 3. The computed value cannot maintain an alias to the original number token\n *\n * This approach is the most token-setup-agnostic solution, as it works\n * regardless of how the source tokens are structured.\n *\n * @example\n * // Input: W3C DTCG typography with number lineHeight\n * // lineHeight: 1.5, fontSize: { value: 16, unit: \"px\" }\n * convertLineHeight(1.5, { ...context, fontSize: { value: 16, unit: \"px\" } });\n * // Output: { value: { value: 24, unit: \"px\" } }\n *\n * @param value - The DTCG lineHeight value (unitless number multiplier)\n * @param context - Extended converter context including the resolved fontSize for calculation\n * @returns Dimension value in px (multiplier × fontSize), or skip indicator if fontSize is missing or value is invalid\n */\nexport function convertLineHeight(value: unknown, context: LineHeightConverterContext): ConverterResult {\n // W3C DTCG specifies lineHeight as a number (unitless multiplier)\n if (typeof value !== 'number') {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid lineHeight value: expected number (per W3C DTCG spec), got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // Validate the multiplier value\n if (!Number.isFinite(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has non-finite lineHeight value: ${value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // fontSize is required to calculate the absolute lineHeight\n if (!context.fontSize) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has lineHeight multiplier (${value}) but no fontSize is defined. Cannot calculate absolute lineHeight for Figma. Provide a fontSize in the typography token.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Calculate absolute lineHeight: multiplier × fontSize\n const rawLineHeight = value * context.fontSize.value;\n\n // Round by default (roundLineHeight defaults to true)\n const shouldRound = context.options.roundLineHeight !== false;\n const absoluteLineHeight = shouldRound ? Math.round(rawLineHeight) : rawLineHeight;\n\n const roundingNote = shouldRound && rawLineHeight !== absoluteLineHeight ? ` (rounded from ${rawLineHeight})` : '';\n\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" lineHeight: ${value} × ${context.fontSize.value}px = ${absoluteLineHeight}px${roundingNote} (converted from W3C multiplier to Figma dimension)`,\n });\n\n return {\n value: {\n value: absoluteLineHeight,\n unit: 'px',\n },\n };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGTypographyValue } from '../utils.js';\nimport { convertDimension } from './dimension.js';\nimport { convertFontFamily } from './font-family.js';\nimport { convertFontWeight } from './font-weight.js';\nimport { convertLineHeight } from './line-height.js';\n\n/**\n * Convert a DTCG typography value to Figma-compatible format.\n * Typography tokens are split into individual sub-tokens since Figma\n * doesn't support the composite typography type.\n *\n * @example\n * // Input typography token\n * convertTypography({\n * fontFamily: \"Inter\",\n * fontSize: { value: 16, unit: \"px\" },\n * fontWeight: 400,\n * lineHeight: 1.5,\n * letterSpacing: { value: 0, unit: \"px\" }\n * }, context);\n * // => { value: undefined, split: true, subTokens: [...] }\n *\n * @param value - The DTCG typography value (object with fontFamily, fontSize, fontWeight, lineHeight, letterSpacing)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with sub-tokens for each typography property, or skip indicator for invalid values\n */\nexport function convertTypography(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGTypographyValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid typography value: expected object, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n const typography = value;\n\n const subTokens: SubToken[] = [];\n\n // Convert fontFamily\n if (typography.fontFamily !== undefined) {\n const result = convertFontFamily(typography.fontFamily, {\n ...context,\n tokenId: `${context.tokenId}.fontFamily`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'fontFamily',\n $type: 'fontFamily',\n value: result.value,\n });\n }\n }\n\n // Convert fontSize (dimension)\n // We also store the resolved fontSize for lineHeight calculation\n let resolvedFontSize: { value: number; unit: string } | undefined;\n if (typography.fontSize !== undefined) {\n const result = convertDimension(typography.fontSize, {\n ...context,\n tokenId: `${context.tokenId}.fontSize`,\n });\n if (!result.skip) {\n resolvedFontSize = result.value as { value: number; unit: string };\n subTokens.push({\n idSuffix: 'fontSize',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // Convert fontWeight\n if (typography.fontWeight !== undefined) {\n const result = convertFontWeight(typography.fontWeight, {\n ...context,\n tokenId: `${context.tokenId}.fontWeight`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'fontWeight',\n $type: result.outputType ?? 'fontWeight',\n value: result.value,\n });\n }\n }\n\n // Convert lineHeight (W3C number → Figma dimension)\n // Per W3C DTCG spec, lineHeight is a unitless number (multiplier).\n // Figma requires a dimension, so we compute: lineHeight × fontSize.\n // Note: This loses the reference to any primitive number token - see line-height.ts for details.\n if (typography.lineHeight !== undefined) {\n const result = convertLineHeight(typography.lineHeight, {\n ...context,\n tokenId: `${context.tokenId}.lineHeight`,\n fontSize: resolvedFontSize,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'lineHeight',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // Convert letterSpacing (dimension)\n if (typography.letterSpacing !== undefined) {\n const result = convertDimension(typography.letterSpacing, {\n ...context,\n tokenId: `${context.tokenId}.letterSpacing`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'letterSpacing',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // If no sub-tokens were created, skip the token\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" typography value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport { PLUGIN_NAME, SUPPORTED_TYPES, type SupportedType, UNSUPPORTED_TYPES } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { convertBorder } from './border.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\nimport { convertDuration } from './duration.js';\nimport { convertFontFamily } from './font-family.js';\nimport { convertFontWeight } from './font-weight.js';\nimport { convertGradient } from './gradient.js';\nimport { convertNumber } from './number.js';\nimport { convertShadow } from './shadow.js';\nimport { convertTypography } from './typography.js';\n\n/**\n * Converter function signature.\n */\nexport type Converter = (value: unknown, context: ConverterContext) => ConverterResult;\n\n/**\n * Registry of converters by token type.\n */\nconst converters: Record<SupportedType, Converter> = {\n color: convertColor,\n dimension: convertDimension,\n duration: convertDuration,\n fontFamily: convertFontFamily,\n fontWeight: convertFontWeight,\n number: convertNumber,\n typography: convertTypography,\n shadow: convertShadow,\n border: convertBorder,\n gradient: convertGradient,\n};\n\n/**\n * Check if a token type is supported by Figma.\n *\n * @param type - The DTCG token type string\n * @returns True if the type is one of the Figma-supported token types\n */\nexport function isSupportedType(type: string): type is SupportedType {\n return SUPPORTED_TYPES.includes(type as SupportedType);\n}\n\n/**\n * Check if a value is an alias reference (curly brace syntax).\n *\n * @param value - The value to check\n * @returns True if value is a string wrapped in curly braces (e.g., \"{color.primary}\")\n */\nexport function isAlias(value: unknown): value is string {\n return typeof value === 'string' && value.startsWith('{') && value.endsWith('}');\n}\n\n/**\n * Extract the token ID from an alias reference.\n * @param alias - The alias string, e.g., \"{color.primary}\"\n * @returns The token ID, e.g., \"color.primary\"\n */\nfunction extractAliasTarget(alias: string): string {\n return alias.slice(1, -1);\n}\n\n/**\n * Validate an alias reference and return any warnings.\n *\n * @param alias - The alias string to validate (e.g., \"{color.primary}\")\n * @param context - Converter context with allTokens map for validation\n * @returns Object with valid flag and optional warning message\n */\nfunction validateAlias(alias: string, context: ConverterContext): { valid: boolean; warning?: string } {\n const targetId = extractAliasTarget(alias);\n\n // Check if target exists\n if (!context.allTokens) {\n // Can't validate without token map\n return { valid: true };\n }\n\n const targetToken = context.allTokens[targetId];\n if (!targetToken) {\n return {\n valid: false,\n warning: `Token \"${context.tokenId}\" references non-existent token \"${targetId}\". Check the token path for typos or ensure the referenced token is defined.`,\n };\n }\n\n // Check if target is a Figma-compatible type\n if (!isSupportedType(targetToken.$type)) {\n const isKnownUnsupported = UNSUPPORTED_TYPES.includes(targetToken.$type as (typeof UNSUPPORTED_TYPES)[number]);\n return {\n valid: false,\n warning: isKnownUnsupported\n ? `Token \"${context.tokenId}\" aliases unsupported type \"${targetToken.$type}\" (from \"${targetId}\"). This alias will be preserved but may not work in Figma. Consider referencing a supported token type instead.`\n : `Token \"${context.tokenId}\" aliases unknown type \"${targetToken.$type}\" (from \"${targetId}\"). This alias will be preserved but may not work in Figma. Verify the target token has a supported type.`,\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert a token value to Figma-compatible format.\n *\n * @param token - The normalized token\n * @param value - The token value to convert\n * @param context - Converter context with logger and options\n * @returns Converted value or skip indicator\n */\nexport function convertToken(token: TokenNormalized, value: unknown, context: ConverterContext): ConverterResult {\n const { $type } = token;\n\n // Handle missing $type\n if (!$type) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" is missing $type. Ensure all tokens have a valid $type defined either directly or inherited from a parent group.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Handle undefined or null values\n if (value === undefined || value === null) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has no value (${value}). Ensure $value is defined for this token.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Handle alias references - validate and pass through\n if (isAlias(value)) {\n const validation = validateAlias(value, context);\n\n if (validation.warning) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: validation.warning,\n });\n }\n\n // Always pass through the alias - Figma uses the same syntax\n // Even invalid aliases are preserved to avoid breaking references\n return { value };\n }\n\n // Silently skip unsupported types — the figma/unsupported-type lint rule\n // handles reporting these to the user with configurable severity.\n if (!isSupportedType($type)) {\n return { value: undefined, skip: true };\n }\n\n // Get the converter for this type\n const converter = converters[$type];\n return converter(value, context);\n}\n","import type { TokenNormalized, TransformHookOptions } from '@terrazzo/parser';\nimport { FORMAT_ID } from './constants.js';\nimport { convertToken } from './converters/index.js';\nimport type { FigmaJsonPluginOptions } from './types.js';\nimport { createExcludeMatcher, toFigmaLocalID } from './utils.js';\n\nexport interface TransformOptions {\n transform: TransformHookOptions;\n options: FigmaJsonPluginOptions;\n}\n\n/**\n * Transform a single token and register it via setTransform.\n * Handles custom transforms and composite tokens (via Record<string, string>).\n *\n * Alias resolution is deferred entirely to the build step, which has access to\n * the full TokenNormalized (including aliasOf, originalValue, partialAliasOf)\n * via the transform.token property on getTransforms() results.\n *\n * @param token - The normalized token from terrazzo parser\n * @param rawValue - The resolved token value\n * @param options - Plugin configuration options\n * @param context - Plugin hook context with logger\n * @param allTokens - Map of all tokens for validation\n * @param rawTokens - Original token map (from transform.tokens) — needed because\n * resolver.apply() strips $extensions from tokens\n * @param setTransform - Terrazzo callback to register transformed value\n * @param input - Resolver input for this permutation\n * @returns void - Registers the transformed token via setTransform callback\n */\nfunction transformToken(\n token: TokenNormalized,\n rawValue: unknown,\n options: FigmaJsonPluginOptions,\n context: TransformHookOptions['context'],\n allTokens: Record<string, TokenNormalized>,\n rawTokens: Record<string, TokenNormalized>,\n setTransform: TransformHookOptions['setTransform'],\n input: Record<string, string>,\n): void {\n const localID = toFigmaLocalID(token.id);\n\n // Allow custom transform to override\n const customValue = options.transform?.(token);\n if (customValue !== undefined) {\n setTransform(token.id, { format: FORMAT_ID, localID, value: JSON.stringify(customValue), input });\n return;\n }\n\n // Look up $extensions from the raw token map — resolver.apply() strips extensions\n const rawToken = rawTokens[token.id];\n const extensions = rawToken?.$extensions ?? token.$extensions;\n\n // Convert the token value (always resolve to final value)\n const result = convertToken(token, rawValue, {\n logger: context.logger,\n options,\n tokenId: token.id,\n extensions,\n allTokens,\n originalValue: token.originalValue?.$value,\n });\n\n // Skip if converter indicates to skip\n if (result.skip) {\n return;\n }\n\n // Handle composite tokens (typography, shadow, border, gradient)\n // Pack sub-tokens into a Record<string, string> — Terrazzo's built-in mechanism\n // for tokens that store multiple values.\n if (result.split && result.subTokens) {\n const record: Record<string, string> = {};\n for (const subToken of result.subTokens) {\n record[subToken.idSuffix] = JSON.stringify({\n $type: subToken.$type,\n $value: subToken.value,\n });\n }\n setTransform(token.id, { format: FORMAT_ID, localID, value: record, input });\n return;\n }\n\n // Build the transformed token structure with resolved value\n const transformedValue = {\n $type: result.outputType ?? token.$type,\n $value: result.value,\n };\n\n setTransform(token.id, { format: FORMAT_ID, localID, value: JSON.stringify(transformedValue), input });\n}\n\n/**\n * Transform DTCG tokens into Figma-compatible format.\n * Uses the resolver to iterate all permutations and convert token values.\n *\n * @param options - Transform options containing the transform hook context and plugin options\n */\nexport default function transformFigmaJson({ transform, options }: TransformOptions): void {\n const { setTransform, context, resolver, tokens: rawTokens } = transform;\n\n const shouldExclude = createExcludeMatcher(options.exclude);\n\n const permutations = resolver.listPermutations();\n\n // Process each permutation (context combination)\n for (const input of permutations) {\n // Skip empty permutations — when there are no tokens, the resolver may\n // have an empty permutation that fails on apply()\n if (Object.keys(input).length === 0) {\n continue;\n }\n const contextTokens = resolver.apply(input);\n\n for (const token of Object.values(contextTokens)) {\n if (shouldExclude(token.id)) {\n continue;\n }\n\n transformToken(token, token.$value, options, context, contextTokens, rawTokens, setTransform, input);\n }\n }\n}\n","import type { Plugin } from '@terrazzo/parser';\nimport buildFigmaJson from './build/index.js';\nimport { FORMAT_ID, PLUGIN_NAME } from './constants.js';\nimport { figmaUnsupportedType } from './lint/index.js';\nimport transformFigmaJson from './transform.js';\nimport type { FigmaJsonPluginOptions } from './types.js';\n\nexport type { BuildOptions } from './build/types.js';\nexport * from './constants.js';\nexport * from './transform.js';\nexport * from './types.js';\nexport * from './utils.js';\n\n/**\n * Terrazzo plugin to convert DTCG design tokens to Figma-compatible JSON format.\n *\n * @example\n * // Basic usage\n * import { defineConfig } from \"@terrazzo/cli\";\n * import figmaJson from \"terrazzo-plugin-figma-json\";\n *\n * export default defineConfig({\n * plugins: [\n * figmaJson({ filename: \"tokens.figma.json\" }),\n * ],\n * });\n *\n * @example\n * // With all options\n * figmaJson({\n * filename: \"design-tokens.figma.json\",\n * exclude: [\"internal.*\", \"deprecated.*\"],\n * remBasePx: 16,\n\n * preserveReferences: true,\n * tokenName: (token) => token.id.replace(\"color.\", \"brand.\"),\n * transform: (token) => {\n * if (token.id === \"special.token\") return { custom: true };\n * return undefined; // Use default transformation\n * },\n * });\n *\n * @param options - Plugin configuration options\n * @returns A Terrazzo plugin instance\n */\nexport default function figmaJsonPlugin(options?: FigmaJsonPluginOptions): Plugin {\n const { skipBuild } = options ?? {};\n const filename = options?.filename ?? 'tokens.figma.json';\n return {\n name: PLUGIN_NAME,\n enforce: options?.enforce,\n\n config(_config) {\n // Validate options at config time for early failure\n if (options?.remBasePx !== undefined && options.remBasePx <= 0) {\n throw new Error(`[${PLUGIN_NAME}] remBasePx must be a positive number, got ${options.remBasePx}`);\n }\n if (options?.filename?.includes('..')) {\n throw new Error(`[${PLUGIN_NAME}] filename must not contain '..', got \"${options.filename}\"`);\n }\n },\n\n lint() {\n return {\n 'figma/unsupported-type': figmaUnsupportedType,\n };\n },\n\n async transform(transformOptions) {\n // Skip if another figma-json plugin has already run\n const existingTransforms = transformOptions.getTransforms({\n format: FORMAT_ID,\n id: '*',\n });\n if (existingTransforms.length) {\n return;\n }\n\n transformFigmaJson({\n transform: transformOptions,\n options: options ?? {},\n });\n },\n\n async build({ getTransforms, outputFile, resolver }) {\n if (skipBuild === true) {\n return;\n }\n\n const result = buildFigmaJson({\n getTransforms,\n exclude: options?.exclude,\n tokenName: options?.tokenName,\n preserveReferences: options?.preserveReferences,\n resolver,\n });\n\n // Output multiple files based on resolver structure\n for (const [sourceName, contents] of result) {\n // sourceName is like \"primitive\" or \"breakpoint-small\" or \"default\" (when default resolver)\n const outputName = sourceName === 'default' ? filename : `${sourceName}.${filename}`;\n outputFile(outputName, contents);\n }\n },\n\n async buildEnd({ outputFiles, context }) {\n for (const file of outputFiles) {\n if (file.plugin !== PLUGIN_NAME) {\n continue;\n }\n\n // Validate JSON is parseable\n try {\n const parsed = JSON.parse(file.contents as string);\n\n // Validate it's a non-null object\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Output file \"${file.filename}\" produced invalid structure (expected object, got ${Array.isArray(parsed) ? 'array' : typeof parsed})`,\n });\n }\n } catch (err) {\n context.logger.error({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Output file \"${file.filename}\" contains invalid JSON: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["figmaUnsupportedType"],"sources":["../src/constants.ts","../src/utils.ts","../src/build/helpers.ts","../src/build/aliases.ts","../src/build/output.ts","../src/build/source-maps.ts","../src/build/index.ts","../src/lint/figma-unsupported-type.ts","../src/converters/color.ts","../src/converters/dimension.ts","../src/converters/border.ts","../src/converters/duration.ts","../src/converters/font-family.ts","../src/converters/font-weight.ts","../src/converters/gradient.ts","../src/converters/number.ts","../src/converters/shadow.ts","../src/converters/line-height.ts","../src/converters/typography.ts","../src/converters/index.ts","../src/transform.ts","../src/index.ts"],"sourcesContent":["export const PLUGIN_NAME = 'terrazzo-plugin-figma-json';\n\nexport const FORMAT_ID = 'figma-json';\n\n/**\n * Token types supported by Figma.\n */\nexport const SUPPORTED_TYPES = [\n 'color',\n 'dimension',\n 'duration',\n 'fontFamily',\n 'fontWeight',\n 'number',\n 'typography',\n 'shadow',\n 'border',\n 'gradient',\n] as const;\n\nexport type SupportedType = (typeof SUPPORTED_TYPES)[number];\n\n/**\n * Token types that are not supported by Figma and will be dropped with a warning.\n */\nexport const UNSUPPORTED_TYPES = ['transition', 'strokeStyle', 'cubicBezier'] as const;\n\nexport type UnsupportedType = (typeof UNSUPPORTED_TYPES)[number];\n\n/**\n * Color spaces that Figma natively supports.\n */\nexport const FIGMA_COLOR_SPACES = ['srgb', 'hsl'] as const;\n\nexport type FigmaColorSpace = (typeof FIGMA_COLOR_SPACES)[number];\n","import wcmatch from 'wildcard-match';\nimport type {\n DTCGBorderValue,\n DTCGColorValue,\n DTCGDimensionValue,\n DTCGDurationValue,\n DTCGGradientStop,\n DTCGShadowValue,\n DTCGTypographyValue,\n TokenExtensions,\n} from './types.js';\n\n/**\n * Compute the localID for a token in Figma's slash-notation format.\n * This is used as the `localID` in `setTransform()` and represents\n * how the token is referenced within the Figma JSON format.\n *\n * @param tokenId - Dot-notation token ID (e.g., \"color.primary.base\")\n * @returns Slash-notation Figma variable name (e.g., \"color/primary/base\")\n *\n * @example\n * toFigmaLocalID(\"color.primary.base\") // \"color/primary/base\"\n * toFigmaLocalID(\"spacing.200\") // \"spacing/200\"\n */\nexport function toFigmaLocalID(tokenId: string): string {\n return tokenId.replace(/\\./g, '/');\n}\n\n/**\n * Create an exclude matcher function from glob patterns.\n *\n * @param patterns - Array of glob patterns to match against token IDs\n * @returns A function that returns true if the token ID should be excluded\n */\nexport function createExcludeMatcher(patterns: string[] | undefined): (tokenId: string) => boolean {\n return patterns?.length ? wcmatch(patterns) : () => false;\n}\n\n/**\n * Filter extensions to only include Figma-specific ones (com.figma.*).\n * Removes non-Figma extensions to keep output clean.\n *\n * @param extensions - Token extensions object that may include various namespaces\n * @returns Object with only com.figma.* keys, or undefined if none exist\n *\n * @example\n * filterFigmaExtensions({ \"com.figma.type\": \"boolean\", \"custom.ext\": \"value\" })\n * // { \"com.figma.type\": \"boolean\" }\n */\nexport function filterFigmaExtensions(extensions: TokenExtensions | undefined): TokenExtensions | undefined {\n if (!extensions) {\n return undefined;\n }\n\n const figmaExtensions: TokenExtensions = {};\n let hasFigmaExtensions = false;\n\n for (const [key, value] of Object.entries(extensions)) {\n if (key.startsWith('com.figma')) {\n figmaExtensions[key] = value;\n hasFigmaExtensions = true;\n }\n }\n\n return hasFigmaExtensions ? figmaExtensions : undefined;\n}\n\n/**\n * Safely parse a transform value that may be a JSON string or already an object.\n *\n * @param value - The transform value (JSON string or object)\n * @returns The parsed object, or null if parsing fails\n */\nexport function parseTransformValue(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'string') {\n return value as Record<string, unknown>;\n }\n try {\n return JSON.parse(value) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Type guard to validate DTCGColorValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGColorValue with colorSpace, components, and optional alpha\n */\nexport function isDTCGColorValue(value: unknown): value is DTCGColorValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (typeof v.colorSpace !== 'string') {\n return false;\n }\n if (!Array.isArray(v.components) || v.components.length !== 3) {\n return false;\n }\n for (const c of v.components) {\n if (c !== 'none' && typeof c !== 'number') {\n return false;\n }\n }\n if (v.alpha !== undefined && v.alpha !== 'none' && typeof v.alpha !== 'number') {\n return false;\n }\n return true;\n}\n\n/**\n * Type guard to validate DTCGDimensionValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGDimensionValue with numeric value and string unit\n */\nexport function isDTCGDimensionValue(value: unknown): value is DTCGDimensionValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n return typeof v.value === 'number' && typeof v.unit === 'string';\n}\n\n/**\n * Type guard to validate DTCGDurationValue structure.\n *\n * @param value - The value to check\n * @returns True if value is a valid DTCGDurationValue with numeric value and string unit\n */\nexport function isDTCGDurationValue(value: unknown): value is DTCGDurationValue {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const v = value as Record<string, unknown>;\n return typeof v.value === 'number' && typeof v.unit === 'string';\n}\n\n/**\n * Type guard to validate DTCGTypographyValue structure.\n * Only checks that it's an object - individual properties are validated during conversion.\n *\n * @param value - The value to check\n * @returns True if value is a non-null, non-array object\n */\nexport function isDTCGTypographyValue(value: unknown): value is DTCGTypographyValue {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Type guard to validate DTCGShadowValue structure.\n * Accepts a single shadow object or an array of shadow objects.\n *\n * @param value - The value to check\n * @returns True if value is a non-null object or a non-empty array of non-null objects\n */\nexport function isDTCGShadowValue(value: unknown): value is DTCGShadowValue | DTCGShadowValue[] {\n if (Array.isArray(value)) {\n return value.length > 0 && value.every((item) => item !== null && typeof item === 'object');\n }\n return value !== null && typeof value === 'object';\n}\n\n/**\n * Type guard to validate DTCGBorderValue structure.\n * Only checks that it's an object - individual properties are validated during conversion.\n *\n * @param value - The value to check\n * @returns True if value is a non-null, non-array object\n */\nexport function isDTCGBorderValue(value: unknown): value is DTCGBorderValue {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Type guard to validate DTCGGradientValue structure.\n * Checks that it's an array of gradient stops.\n *\n * @param value - The value to check\n * @returns True if value is a non-empty array of non-null objects\n */\nexport function isDTCGGradientValue(value: unknown): value is DTCGGradientStop[] {\n return Array.isArray(value) && value.length > 0 && value.every((item) => item !== null && typeof item === 'object');\n}\n","import type { Resolver } from '@terrazzo/parser';\nimport type { TokenExtensions } from '../types.js';\nimport { filterFigmaExtensions } from '../utils.js';\nimport type { FigmaOutputExtensions, ParsedTokenValue } from './types.js';\n\n/**\n * Set a nested property on an object using dot-notation path.\n * Creates intermediate objects as needed.\n * Note: this intentionally mutates `obj` for efficient output tree construction.\n *\n * @param obj - The object to modify\n * @param path - Dot-notation path (e.g., \"color.primary.base\")\n * @param value - The value to set at the path\n *\n * @example\n * const obj = {};\n * setNestedProperty(obj, \"color.primary\", { $value: \"#ff0000\" });\n * // obj = { color: { primary: { $value: \"#ff0000\" } } }\n */\nexport function setNestedProperty(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n if (parts.length === 0) {\n return;\n }\n\n let current = obj;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n if (!(part in current)) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const lastPart = parts[parts.length - 1]!;\n current[lastPart] = value;\n}\n\n/**\n * Convert $root in a token ID to root for Figma compatibility.\n * DTCG uses $root for default values, but Figma doesn't support $ in names.\n *\n * @param path - The token path that may contain \".$root\" segments\n * @returns The path with \".$root\" replaced by \".root\"\n */\nexport function normalizeRootInPath(path: string): string {\n return path.replace(/\\.\\$root\\b/g, '.root');\n}\n\n/**\n * Check if this resolver is the auto-generated default (no user-defined resolver file).\n * The default resolver has only an \"allTokens\" set and a \"tzMode\" modifier with \".\" context.\n *\n * @param resolverSource - The resolver source configuration to inspect\n * @returns True if this is the auto-generated default resolver\n */\nexport function isDefaultResolver(resolverSource: NonNullable<Resolver['source']>): boolean {\n const sets = resolverSource.sets ?? {};\n const modifiers = resolverSource.modifiers ?? {};\n\n const setNames = Object.keys(sets);\n if (setNames.length !== 1 || setNames[0] !== 'allTokens') {\n return false;\n }\n\n const modifierNames = Object.keys(modifiers);\n if (modifierNames.length === 0) {\n return true;\n }\n if (modifierNames.length === 1 && modifierNames[0] === 'tzMode') {\n const tzMode = modifiers.tzMode;\n if (tzMode?.contexts) {\n const contextNames = Object.keys(tzMode.contexts);\n return contextNames.length === 1 && contextNames[0] === '.';\n }\n }\n\n return false;\n}\n\n/**\n * Build a default input from the resolver's first permutation.\n *\n * @param resolver - The terrazzo resolver instance\n * @returns The first permutation input object, or an empty object if none exist\n */\nexport function getDefaultInput(resolver: Resolver): Record<string, string> {\n const permutations = resolver.listPermutations();\n return permutations[0] ?? {};\n}\n\n/**\n * Return a new ParsedTokenValue with $description and filtered $extensions added.\n *\n * @param parsedValue - The parsed token value to augment\n * @param token - The source token containing $description and $extensions metadata\n * @returns A new ParsedTokenValue with description and Figma-specific extensions merged in\n */\nexport function withTokenMetadata(\n parsedValue: ParsedTokenValue,\n token: { $description?: string; $extensions?: Record<string, unknown> },\n): ParsedTokenValue {\n let result = parsedValue;\n\n if (token.$description) {\n result = { ...result, $description: token.$description };\n }\n\n const figmaExtensions = filterFigmaExtensions(token.$extensions as TokenExtensions | undefined);\n if (figmaExtensions) {\n // Merge with any existing extensions (e.g., aliasData added later)\n const existing = result.$extensions ?? {};\n result = {\n ...result,\n $extensions: {\n ...(figmaExtensions as unknown as FigmaOutputExtensions),\n ...existing,\n },\n };\n }\n\n return result;\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport type { SupportedType } from '../constants.js';\nimport type { PartialAliasOf, TokenWithPartialAlias } from '../types.js';\nimport { toFigmaLocalID } from '../utils.js';\nimport { normalizeRootInPath } from './helpers.js';\nimport type { AliasReferenceOptions, ParsedTokenValue, SourceInfo } from './types.js';\n\n/**\n * Extract partialAliasOf from a token if present.\n * This property is added by terrazzo parser for composite tokens but not in public types.\n *\n * @param token - The token object to inspect\n * @returns The partialAliasOf record mapping sub-properties to alias targets, or undefined\n */\nexport function getPartialAliasOf(token: unknown): PartialAliasOf | undefined {\n if (token && typeof token === 'object' && 'partialAliasOf' in token) {\n const value = (token as TokenWithPartialAlias).partialAliasOf;\n if (value && typeof value === 'object') {\n return value;\n }\n }\n return undefined;\n}\n\n/**\n * Get the correct alias reference for a composite sub-property.\n * When a composite property references another composite token of the same type,\n * the alias needs to point to the corresponding sub-token.\n *\n * @param aliasOf - The raw alias target token ID, or undefined if not an alias\n * @param propertyName - The sub-property name (e.g., \"color\", \"width\")\n * @param allTokens - Map of all tokens for type checking\n * @param parentType - The parent composite token's type (e.g., \"border\", \"shadow\")\n * @returns The resolved alias target (with sub-property suffix if needed), or undefined\n */\nexport function getSubTokenAlias(\n aliasOf: string | undefined,\n propertyName: string,\n allTokens: Record<string, { $type?: string }> | undefined,\n parentType: SupportedType,\n): string | undefined {\n if (!aliasOf) {\n return undefined;\n }\n const referencedToken = allTokens?.[aliasOf];\n if (referencedToken?.$type === parentType) {\n return `${aliasOf}.${propertyName}`;\n }\n return aliasOf;\n}\n\n/**\n * Compute the direct alias target for a simple (non-composite) token.\n *\n * @param token - The normalized token to inspect\n * @returns The direct alias target token ID, or undefined if the token is not an alias\n */\nexport function getDirectAliasTarget(token: TokenNormalized): string | undefined {\n const aliasOf = token.aliasOf;\n if (!aliasOf) {\n return undefined;\n }\n\n // Use originalValue.$value to get the direct reference (not fully resolved chain)\n // e.g., \"{dimension.size.height.baseline}\" instead of \"dimension.100\"\n const originalValueStr = token.originalValue?.$value;\n if (typeof originalValueStr === 'string' && originalValueStr.startsWith('{') && originalValueStr.endsWith('}')) {\n return originalValueStr.slice(1, -1);\n }\n\n return typeof aliasOf === 'string' ? aliasOf : undefined;\n}\n\n/**\n * Collect all non-undefined string references from a partialAliasOf object,\n * recursively flattening any nested arrays or objects.\n *\n * @param value - The value to extract references from (string, array, or object)\n * @param refs - Mutable array to collect found string references into\n */\nfunction collectRefs(value: unknown, refs: string[]): void {\n if (typeof value === 'string') {\n refs.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n collectRefs(item, refs);\n }\n } else if (value && typeof value === 'object') {\n for (const v of Object.values(value)) {\n collectRefs(v, refs);\n }\n }\n}\n\n/**\n * For tokens without a direct alias, check if all sub-property references\n * in partialAliasOf point to the same token. If so, the token can be treated\n * as a unified alias to that single target.\n *\n * This handles cases like JSON pointer references where individual sub-properties\n * (e.g., colorSpace, components[0], components[1], etc.) all reference the same\n * source token.\n *\n * Works for any token type with partialAliasOf — colors, borders, shadows, etc.\n *\n * @param token - The normalized token to inspect\n * @returns The unified alias target token ID, or undefined if refs are mixed or absent\n */\nexport function getUnifiedPartialAlias(token: TokenNormalized): string | undefined {\n // Skip tokens that already have a direct alias\n if (token.aliasOf) {\n return undefined;\n }\n\n const partialAliasOf = getPartialAliasOf(token);\n if (!partialAliasOf) {\n return undefined;\n }\n\n const refs: string[] = [];\n collectRefs(partialAliasOf, refs);\n\n if (refs.length > 0) {\n const uniqueRefs = [...new Set(refs)];\n if (uniqueRefs.length === 1) {\n return uniqueRefs[0];\n }\n }\n\n return undefined;\n}\n\n/**\n * Compute the alias target for a token, considering direct aliases and\n * unified partial aliases (where all sub-property refs point to the same token).\n *\n * @param token - The normalized token to inspect\n * @returns The alias target token ID, or undefined if the token is not an alias\n */\nexport function computeAliasTarget(token: TokenNormalized): string | undefined {\n return getDirectAliasTarget(token) ?? getUnifiedPartialAlias(token);\n}\n\n/**\n * Compute alias targets for each sub-property of a composite token.\n *\n * @param token - The normalized composite token\n * @param subTokenSuffixes - Array of sub-property suffixes to resolve (e.g., [\"color\", \"width\"])\n * @param allTokens - Map of all tokens for type checking during alias resolution\n * @returns Map from sub-property suffix to alias target token ID (or undefined if not aliased)\n */\nexport function computeSubTokenAliases(\n token: TokenNormalized,\n subTokenSuffixes: string[],\n allTokens: Record<string, TokenNormalized> | undefined,\n): Map<string, string | undefined> {\n const result = new Map<string, string | undefined>();\n const partialAliasOf = getPartialAliasOf(token);\n const parentType = token.$type as SupportedType;\n\n for (const suffix of subTokenSuffixes) {\n const raw = partialAliasOf?.[suffix];\n result.set(suffix, getSubTokenAlias(raw, suffix, allTokens, parentType));\n }\n\n return result;\n}\n\n/**\n * Resolve an alias reference and return a new ParsedTokenValue with the\n * appropriate $value or $extensions set.\n *\n * - Same-file references: Sets $value to curly brace syntax (e.g., \"{color.primary}\")\n * - Cross-file references: Keeps resolved $value and adds com.figma.aliasData extension\n *\n * Returns the original value unchanged if no alias handling applies.\n *\n * @param parsedValue - The parsed token value to potentially augment with alias info\n * @param options - Alias resolution options including target, source context, and reference maps\n * @returns A new ParsedTokenValue with alias reference syntax or extension, or the original value unchanged\n */\nexport function withAliasReference(\n parsedValue: ParsedTokenValue,\n { aliasOf, sourceName, tokenSources, tokenOutputPaths, preserveReferences }: AliasReferenceOptions,\n): ParsedTokenValue {\n if (!preserveReferences || !aliasOf) {\n return parsedValue;\n }\n\n // Normalize aliasOf to remove $root for lookups (terrazzo uses normalized IDs)\n const normalizedAliasOf = aliasOf.replace(/\\.\\$root\\b/g, '');\n // Get target's output path, or normalize $root -> root in the original aliasOf\n const targetOutputPath = tokenOutputPaths.get(normalizedAliasOf) ?? normalizeRootInPath(aliasOf);\n\n // Find the target token's sources, handling split sub-tokens by looking up parent\n let targetSources: SourceInfo[] | undefined = tokenSources.get(normalizedAliasOf);\n if (!targetSources) {\n // Try parent tokens for split sub-tokens (e.g., \"typography.heading.fontFamily\")\n const parts = normalizedAliasOf.split('.');\n while (parts.length > 1 && !targetSources) {\n parts.pop();\n targetSources = tokenSources.get(parts.join('.'));\n }\n }\n\n if (!targetSources?.length) {\n return parsedValue;\n }\n\n // Check if target exists in current source (same-file reference)\n const inCurrentSource = targetSources.some((s) => s.source === sourceName);\n if (inCurrentSource) {\n return { ...parsedValue, $value: `{${targetOutputPath}}` };\n }\n\n // Check for SET sources only (not modifier contexts)\n const setSource = targetSources.find((s) => !s.isModifier);\n if (setSource) {\n const existingExtensions = parsedValue.$extensions ?? {};\n return {\n ...parsedValue,\n $extensions: {\n ...existingExtensions,\n 'com.figma.aliasData': {\n targetVariableSetName: setSource.source,\n targetVariableName: toFigmaLocalID(targetOutputPath),\n },\n },\n };\n }\n\n return parsedValue;\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport type { FigmaJsonPluginOptions } from '../types.js';\nimport { parseTransformValue } from '../utils.js';\nimport { computeAliasTarget, computeSubTokenAliases, withAliasReference } from './aliases.js';\nimport { setNestedProperty, withTokenMetadata } from './helpers.js';\nimport type { ParsedTokenValue, SourceInfo } from './types.js';\n\n/**\n * Process a single transform and place it in the output object.\n * Handles both simple tokens (string value) and composite tokens (Record<string, string> value).\n *\n * Alias resolution is computed here from the token metadata (transform.token)\n * rather than from embedded internal metadata in the value.\n *\n * Note: this intentionally mutates `output` via setNestedProperty for efficient\n * output tree construction.\n *\n * @param transform - The transform result containing the token and its transformed value\n * @param output - The mutable output object to place the token into (mutated in place)\n * @param sourceName - The current source/context name for alias resolution\n * @param tokenName - Optional custom function to control the output token name\n * @param tokenOutputPaths - Map of token IDs to their output paths\n * @param tokenSources - Map of token IDs to their source info for cross-file alias resolution\n * @param preserveReferences - Whether to preserve alias references in output\n * @param shouldExclude - Function to check if a token ID should be excluded\n * @param allTokens - Map of all tokens for composite alias resolution\n */\nexport function processTransform(\n transform: {\n token: TokenNormalized;\n value: string | Record<string, string>;\n },\n output: Record<string, unknown>,\n sourceName: string,\n tokenName: FigmaJsonPluginOptions['tokenName'],\n tokenOutputPaths: Map<string, string>,\n tokenSources: Map<string, SourceInfo[]>,\n preserveReferences: boolean,\n shouldExclude: (id: string) => boolean,\n allTokens: Record<string, TokenNormalized> | undefined,\n): void {\n const token = transform.token;\n const tokenId = token.id;\n\n if (shouldExclude(tokenId)) {\n return;\n }\n\n const outputName = tokenName?.(token) ?? tokenOutputPaths.get(tokenId) ?? tokenId;\n\n if (typeof transform.value === 'string') {\n // Simple token: parse value, compute alias from token metadata, add metadata\n const rawParsed = parseTransformValue(transform.value) as ParsedTokenValue | null;\n if (!rawParsed) {\n return;\n }\n\n // Compute alias from the token itself\n const aliasOf = computeAliasTarget(token);\n\n let parsedValue = withTokenMetadata(rawParsed, token);\n\n if (aliasOf) {\n parsedValue = withAliasReference(parsedValue, {\n aliasOf,\n sourceName,\n tokenSources,\n tokenOutputPaths,\n preserveReferences,\n });\n }\n\n setNestedProperty(output, outputName, parsedValue);\n } else {\n // Composite token (Record<string, string>): expand sub-tokens into output\n const parentOutputPath = tokenOutputPaths.get(tokenId);\n\n // Compute alias targets for all sub-properties from the token metadata\n const subTokenSuffixes = Object.keys(transform.value);\n const subTokenAliases = computeSubTokenAliases(token, subTokenSuffixes, allTokens);\n\n for (const [suffix, subValueStr] of Object.entries(transform.value)) {\n const rawSubParsed = parseTransformValue(subValueStr) as ParsedTokenValue | null;\n if (!rawSubParsed) {\n continue;\n }\n\n // Get alias for this sub-property from computed aliases\n const aliasOf = subTokenAliases.get(suffix);\n\n let subParsed = withTokenMetadata(rawSubParsed, token);\n\n if (aliasOf) {\n subParsed = withAliasReference(subParsed, {\n aliasOf,\n sourceName,\n tokenSources,\n tokenOutputPaths,\n preserveReferences,\n });\n }\n\n // Build sub-token output path: use parent's output path (preserves $root) + suffix\n let subOutputName: string;\n if (tokenName) {\n subOutputName = `${tokenName(token)}.${suffix}`;\n } else if (parentOutputPath && parentOutputPath !== tokenId) {\n subOutputName = `${parentOutputPath}.${suffix}`;\n } else {\n subOutputName = `${outputName}.${suffix}`;\n }\n\n setNestedProperty(output, subOutputName, subParsed);\n }\n }\n}\n","import type { Resolver } from '@terrazzo/parser';\nimport type { SourceInfo, TokenIdInfo, TokenSourceMaps } from './types.js';\n\n/**\n * Extract token IDs from a resolver group (token definitions).\n * Recursively walks the group structure to find all token IDs.\n *\n * @param group - The resolver group object containing nested token definitions\n * @param prefix - Dot-notation prefix for building full token paths (used in recursion)\n * @returns Array of token ID info objects with normalized IDs and output paths\n */\nexport function extractTokenIds(group: Record<string, unknown>, prefix = ''): TokenIdInfo[] {\n const ids: TokenIdInfo[] = [];\n\n for (const [key, value] of Object.entries(group)) {\n if (key.startsWith('$') && key !== '$root') {\n continue;\n }\n\n const outputKey = key === '$root' ? 'root' : key;\n const outputPath = prefix ? `${prefix}.${outputKey}` : outputKey;\n const normalizedPath = key === '$root' ? prefix : outputPath;\n\n if (value && typeof value === 'object' && '$value' in value) {\n if (normalizedPath) {\n ids.push({ id: normalizedPath, outputPath });\n }\n } else if (value && typeof value === 'object') {\n ids.push(...extractTokenIds(value as Record<string, unknown>, outputPath));\n }\n }\n\n return ids;\n}\n\n/**\n * Build maps tracking which tokens belong to which sources.\n * Processes both sets and modifier contexts from the resolver source.\n *\n * @param resolverSource - The resolver source configuration containing sets and modifiers\n * @returns Token source maps with tokenSources, tokenOutputPaths, and allContexts\n */\nexport function buildTokenSourceMaps(resolverSource: NonNullable<Resolver['source']>): TokenSourceMaps {\n const tokenSources = new Map<string, SourceInfo[]>();\n const tokenOutputPaths = new Map<string, string>();\n const allContexts = new Set<string>();\n\n function addTokenSource(tokenId: string, outputPath: string, info: SourceInfo) {\n const existing = tokenSources.get(tokenId);\n if (existing) {\n existing.push(info);\n } else {\n tokenSources.set(tokenId, [info]);\n }\n if (!tokenOutputPaths.has(tokenId)) {\n tokenOutputPaths.set(tokenId, outputPath);\n }\n }\n\n // Process sets\n if (resolverSource.sets) {\n for (const [setName, set] of Object.entries(resolverSource.sets)) {\n if (set.sources) {\n for (const source of set.sources) {\n const tokenInfos = extractTokenIds(source as Record<string, unknown>);\n for (const { id, outputPath } of tokenInfos) {\n addTokenSource(id, outputPath, {\n source: setName,\n isModifier: false,\n });\n }\n }\n }\n }\n }\n\n // Process modifiers (skip auto-generated tzMode with \".\" context)\n if (resolverSource.modifiers) {\n for (const [modifierName, modifier] of Object.entries(resolverSource.modifiers)) {\n if (modifier.contexts) {\n // Skip the auto-generated tzMode modifier\n if (modifierName === 'tzMode') {\n const contextNames = Object.keys(modifier.contexts);\n if (contextNames.length === 1 && contextNames[0] === '.') {\n continue;\n }\n }\n\n for (const [contextName, contextSources] of Object.entries(modifier.contexts)) {\n const contextKey = `${modifierName}-${contextName}`;\n allContexts.add(contextKey);\n\n if (Array.isArray(contextSources)) {\n for (const source of contextSources) {\n const tokenInfos = extractTokenIds(source as Record<string, unknown>);\n for (const { id, outputPath } of tokenInfos) {\n addTokenSource(id, outputPath, {\n source: contextKey,\n isModifier: true,\n modifierName,\n contextName,\n });\n }\n }\n }\n }\n }\n }\n }\n\n return { tokenSources, tokenOutputPaths, allContexts };\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport { FORMAT_ID } from '../constants.js';\nimport { createExcludeMatcher } from '../utils.js';\nimport { getDefaultInput, isDefaultResolver } from './helpers.js';\nimport { processTransform } from './output.js';\nimport { buildTokenSourceMaps } from './source-maps.js';\nimport type { BuildOptions, SourceInfo } from './types.js';\n\nexport type { BuildOptions } from './types.js';\n\n/**\n * Build the Figma-compatible JSON output from transformed tokens.\n * Uses the resolver to determine output file structure.\n *\n * @param options - Build options including getTransforms, exclude patterns, tokenName, preserveReferences, and resolver\n * @returns Map of output name to JSON string (e.g., \"primitive\" → \"{...}\", \"default\" for single-file output)\n */\nexport default function buildFigmaJson({\n getTransforms,\n exclude,\n tokenName,\n preserveReferences = true,\n resolver,\n}: BuildOptions): Map<string, string> {\n const shouldExclude = createExcludeMatcher(exclude);\n const resolverSource = resolver.source;\n\n if (!resolverSource) {\n return new Map();\n }\n\n // Build maps tracking token sources and output paths\n const { tokenSources, tokenOutputPaths, allContexts } = buildTokenSourceMaps(resolverSource);\n\n // Build a combined token map for alias resolution in composite tokens\n const defaultInput = getDefaultInput(resolver);\n const defaultTokens: Record<string, TokenNormalized> | undefined = (() => {\n try {\n return resolver.apply(defaultInput);\n } catch {\n return undefined;\n }\n })();\n\n // Group outputs by source\n const outputBySource = new Map<string, Record<string, unknown>>();\n\n // Initialize empty outputs for all contexts\n for (const contextKey of allContexts) {\n outputBySource.set(contextKey, {});\n }\n\n // Get transforms using default input (for set tokens)\n const defaultTransforms = getTransforms({\n format: FORMAT_ID,\n input: defaultInput,\n });\n\n // Process set tokens using default transforms\n for (const transform of defaultTransforms) {\n if (!transform.token) {\n continue;\n }\n\n const tokenId = transform.token.id;\n const sources = tokenSources.get(tokenId) ?? [];\n const setSource = sources.find((s) => !s.isModifier);\n if (!setSource) {\n continue;\n }\n\n const sourceName = setSource.source;\n let sourceOutput = outputBySource.get(sourceName);\n if (!sourceOutput) {\n sourceOutput = {};\n outputBySource.set(sourceName, sourceOutput);\n }\n\n processTransform(\n transform,\n sourceOutput,\n sourceName,\n tokenName,\n tokenOutputPaths,\n tokenSources,\n preserveReferences,\n shouldExclude,\n defaultTokens,\n );\n }\n\n // Process modifier context tokens\n const modifierTokensByContext = new Map<string, Set<string>>();\n\n for (const [tokenId, sources] of tokenSources) {\n for (const sourceInfo of sources) {\n if (!sourceInfo.isModifier) {\n continue;\n }\n\n const contextKey = sourceInfo.source;\n const existing = modifierTokensByContext.get(contextKey);\n if (existing) {\n existing.add(tokenId);\n } else {\n modifierTokensByContext.set(contextKey, new Set([tokenId]));\n }\n }\n }\n\n for (const [contextKey, tokenIds] of modifierTokensByContext) {\n let contextInfo: SourceInfo | undefined;\n for (const tokenId of tokenIds) {\n const sources = tokenSources.get(tokenId);\n contextInfo = sources?.find((s) => s.source === contextKey);\n if (contextInfo) {\n break;\n }\n }\n\n if (!contextInfo?.modifierName || !contextInfo?.contextName) {\n continue;\n }\n\n const input: Record<string, string> = { ...defaultInput };\n input[contextInfo.modifierName] = contextInfo.contextName;\n\n const contextTransforms = getTransforms({ format: FORMAT_ID, input });\n\n // Build context-specific token map for alias resolution\n const contextTokens: Record<string, TokenNormalized> | undefined = (() => {\n try {\n return resolver.apply(input);\n } catch {\n return undefined;\n }\n })();\n\n let contextOutput = outputBySource.get(contextKey);\n if (!contextOutput) {\n contextOutput = {};\n outputBySource.set(contextKey, contextOutput);\n }\n\n for (const tokenId of tokenIds) {\n if (shouldExclude(tokenId)) {\n continue;\n }\n\n const transform = contextTransforms.find((t) => t.token?.id === tokenId);\n if (!transform?.token) {\n continue;\n }\n\n processTransform(\n transform,\n contextOutput,\n contextKey,\n tokenName,\n tokenOutputPaths,\n tokenSources,\n preserveReferences,\n shouldExclude,\n contextTokens,\n );\n }\n }\n\n // For the default resolver (allTokens only), rename to \"default\" for index.ts mapping\n if (isDefaultResolver(resolverSource)) {\n const allTokensOutput = outputBySource.get('allTokens');\n if (allTokensOutput) {\n outputBySource.delete('allTokens');\n outputBySource.set('default', allTokensOutput);\n }\n }\n\n // Return split output by source\n const result = new Map<string, string>();\n for (const [sourceName, output] of outputBySource) {\n result.set(sourceName, JSON.stringify(output, null, 2));\n }\n return result;\n}\n","import type { LintRule } from '@terrazzo/parser';\nimport { SUPPORTED_TYPES, UNSUPPORTED_TYPES } from '../constants.js';\n\n/**\n * Lint rule that warns when tokens have $type values that are not supported by Figma.\n *\n * Unsupported types (transition, strokeStyle, cubicBezier) will be dropped\n * during transformation. Unknown types (not in either supported or known-unsupported\n * lists) will also be flagged.\n *\n * Users opt in via config:\n * ```ts\n * lint: { rules: { 'figma/unsupported-type': 'warn' } }\n * ```\n */\nconst figmaUnsupportedType: LintRule<'unsupported' | 'unknown'> = {\n meta: {\n messages: {\n unsupported:\n 'Token \"{{id}}\" has $type \"{{type}}\" which is not supported by Figma and will be skipped. Consider excluding it or using a supported type.',\n unknown:\n 'Token \"{{id}}\" has $type \"{{type}}\" which is not recognized. It will be skipped during Figma JSON generation.',\n },\n },\n defaultOptions: {},\n create(context) {\n for (const [id, token] of Object.entries(context.tokens)) {\n const type = token.$type;\n if (!type) {\n continue;\n }\n\n if (SUPPORTED_TYPES.includes(type as (typeof SUPPORTED_TYPES)[number])) {\n continue;\n }\n\n const isKnownUnsupported = UNSUPPORTED_TYPES.includes(type as (typeof UNSUPPORTED_TYPES)[number]);\n\n context.report({\n messageId: isKnownUnsupported ? 'unsupported' : 'unknown',\n node: token.source?.node,\n data: { id, type },\n });\n }\n },\n};\n\nexport default figmaUnsupportedType;\n","import Color from 'colorjs.io';\nimport { FIGMA_COLOR_SPACES, PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGColorValue } from '../types.js';\nimport { isDTCGColorValue } from '../utils.js';\n\n/**\n * Number of decimal places to round color components to.\n * 6 decimals provides sufficient precision while avoiding floating-point issues.\n */\nconst COLOR_PRECISION = 6;\n\n/**\n * Round a number to COLOR_PRECISION decimal places and clamp to [0, 1] range.\n * Prevents floating-point precision issues (e.g., 1.0000000000000007 -> 1).\n *\n * @param value - Color component value (typically 0-1 for sRGB)\n * @returns Rounded and clamped value in [0, 1] range\n */\nfunction roundAndClamp(value: number): number {\n const rounded = Math.round(value * 10 ** COLOR_PRECISION) / 10 ** COLOR_PRECISION;\n return Math.max(0, Math.min(1, rounded));\n}\n\n/**\n * Normalize color components: round to precision and clamp to valid range.\n * Applies roundAndClamp to each component in the RGB/HSL triplet.\n *\n * @param components - Array of 3 color component values\n * @returns Normalized triplet with values rounded and clamped\n */\nfunction normalizeComponents(components: [number, number, number]): [number, number, number] {\n return components.map(roundAndClamp) as [number, number, number];\n}\n\n/**\n * Map DTCG color space names to colorjs.io color space IDs.\n */\nconst DTCG_TO_COLORJS_SPACE: Record<string, string> = {\n srgb: 'srgb',\n 'srgb-linear': 'srgb-linear',\n hsl: 'hsl',\n hwb: 'hwb',\n lab: 'lab',\n lch: 'lch',\n oklab: 'oklab',\n oklch: 'oklch',\n 'display-p3': 'p3',\n 'a98-rgb': 'a98rgb',\n 'prophoto-rgb': 'prophoto',\n rec2020: 'rec2020',\n 'xyz-d65': 'xyz-d65',\n 'xyz-d50': 'xyz-d50',\n};\n\n/**\n * Convert a DTCG color value to Figma-compatible format.\n * Figma only supports sRGB and HSL color spaces.\n *\n * @example\n * // sRGB colors pass through unchanged\n * convertColor({\n * colorSpace: \"srgb\",\n * components: [0.5, 0.5, 0.5],\n * alpha: 1\n * }, context);\n * // => { value: { colorSpace: \"srgb\", components: [0.5, 0.5, 0.5], alpha: 1 } }\n *\n * @example\n * // OKLCH colors are converted to sRGB\n * convertColor({\n * colorSpace: \"oklch\",\n * components: [0.7, 0.15, 150]\n * }, context);\n * // => { value: { colorSpace: \"srgb\", components: [...], alpha: 1 } }\n *\n * @param value - The DTCG color value to convert (should match DTCGColorValue structure)\n * @param context - Converter context with logger and plugin options\n * @returns Converted color value in sRGB or HSL, or skip indicator for invalid values\n */\nexport function convertColor(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGColorValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid color value: expected object with colorSpace and components`,\n });\n return { value: undefined, skip: true };\n }\n const color = value;\n\n // If already in a Figma-compatible color space, pass through with alpha normalization\n if (FIGMA_COLOR_SPACES.includes(color.colorSpace as (typeof FIGMA_COLOR_SPACES)[number])) {\n // Handle 'none' values in components\n const components = color.components.map((c) => (c === 'none' ? 0 : c)) as [number, number, number];\n\n // Only normalize sRGB components (which are in 0-1 range), not HSL (which uses different ranges)\n const normalizedComponents = color.colorSpace === 'srgb' ? normalizeComponents(components) : components;\n\n return {\n value: {\n ...color,\n components: normalizedComponents,\n alpha: color.alpha === 'none' ? 1 : (color.alpha ?? 1),\n },\n };\n }\n\n // Handle 'none' values - treat as 0 for conversion purposes\n const components = color.components.map((c) => (c === 'none' ? 0 : c)) as [number, number, number];\n\n // Get the colorjs.io color space ID\n const colorjsSpace = DTCG_TO_COLORJS_SPACE[color.colorSpace];\n if (!colorjsSpace) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unknown color space: ${color.colorSpace}`,\n });\n return { value: undefined, skip: true };\n }\n\n try {\n // Create color in the source color space\n const sourceColor = new Color(colorjsSpace, components);\n\n // Convert to sRGB\n const srgbColor = sourceColor.to('srgb');\n\n // Check if gamut clipping is needed\n if (!srgbColor.inGamut()) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color was clipped to sRGB gamut (original color space: ${color.colorSpace})`,\n });\n srgbColor.toGamut({ method: 'css' });\n }\n\n // Get the sRGB coordinates and normalize them\n const srgbChannels = normalizeComponents(srgbColor.coords as [number, number, number]);\n\n // Log info about color space conversion (expected behavior for non-sRGB colors)\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color converted from ${color.colorSpace} to sRGB`,\n });\n\n const result: DTCGColorValue = {\n colorSpace: 'srgb',\n components: srgbChannels,\n alpha: color.alpha === 'none' ? 1 : (color.alpha ?? 1),\n };\n\n return { value: result };\n } catch (err) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" color conversion failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n return { value: undefined, skip: true };\n }\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { isDTCGDimensionValue } from '../utils.js';\n\n/**\n * Convert a DTCG dimension value to Figma-compatible format.\n * Figma only supports px units.\n *\n * @example\n * // px values pass through unchanged\n * convertDimension({ value: 16, unit: \"px\" }, context);\n * // => { value: { value: 16, unit: \"px\" } }\n *\n * @example\n * // rem values are converted to px (default base: 16px)\n * convertDimension({ value: 1.5, unit: \"rem\" }, context);\n * // => { value: { value: 24, unit: \"px\" } }\n *\n * @param value - The DTCG dimension value to convert (should match DTCGDimensionValue structure)\n * @param context - Converter context with logger and plugin options (includes remBasePx)\n * @returns Converted dimension in px units, or skip indicator for unsupported units\n */\nexport function convertDimension(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGDimensionValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid dimension value: expected object with value (number) and unit (string)`,\n });\n return { value: undefined, skip: true };\n }\n const dimension = value;\n\n // Validate numeric value\n if (!Number.isFinite(dimension.value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid dimension value: ${dimension.value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // px passthrough\n if (dimension.unit === 'px') {\n return { value: dimension };\n }\n\n // rem to px conversion\n if (dimension.unit === 'rem') {\n const remBasePx = context.options.remBasePx ?? 16;\n const pxValue = dimension.value * remBasePx;\n\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" converted from ${dimension.value}rem to ${pxValue}px (base: ${remBasePx}px)`,\n });\n\n return {\n value: {\n value: pxValue,\n unit: 'px',\n },\n };\n }\n\n // Unknown unit - warn and skip\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unsupported dimension unit: \"${dimension.unit}\". Figma only supports px units. Convert the value to px or use the 'transform' option to handle this token.`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGBorderValue } from '../utils.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\n\n/**\n * Convert a DTCG border value to Figma-compatible format.\n * Border tokens are partially split into individual sub-tokens.\n * Only color and width are supported; style is dropped.\n *\n * @param value - The DTCG border value (object with color, width, and optional style)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with color and width sub-tokens, or skip indicator for invalid values\n */\nexport function convertBorder(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGBorderValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid border value: expected object, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const border = value;\n const subTokens: SubToken[] = [];\n\n // Convert color\n if (border.color !== undefined) {\n const result = convertColor(border.color, {\n ...context,\n tokenId: `${context.tokenId}.color`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: 'color', $type: 'color', value: result.value });\n }\n }\n\n // Convert width\n if (border.width !== undefined) {\n const result = convertDimension(border.width, {\n ...context,\n tokenId: `${context.tokenId}.width`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: 'width', $type: 'dimension', value: result.value });\n }\n }\n\n // Drop style (can't be represented as a Figma variable)\n if (border.style !== undefined) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" border \"style\" property dropped (variables cannot be applied to border style in Figma)`,\n });\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" border value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { isDTCGDurationValue } from '../utils.js';\n\n/**\n * Convert a DTCG duration value to Figma-compatible format.\n * Figma only supports seconds (s) unit.\n *\n * @example\n * // s values pass through unchanged\n * convertDuration({ value: 0.5, unit: \"s\" }, context);\n * // => { value: { value: 0.5, unit: \"s\" } }\n *\n * @example\n * // ms values are converted to s\n * convertDuration({ value: 500, unit: \"ms\" }, context);\n * // => { value: { value: 0.5, unit: \"s\" } }\n *\n * @param value - The DTCG duration value to convert (should match DTCGDurationValue structure)\n * @param context - Converter context with logger and plugin options\n * @returns Converted duration in seconds, or skip indicator for unsupported units\n */\nexport function convertDuration(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGDurationValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid duration value: expected object with value (number) and unit (string)`,\n });\n return { value: undefined, skip: true };\n }\n const duration = value;\n\n // Validate numeric value\n if (!Number.isFinite(duration.value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid duration value: ${duration.value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // s passthrough\n if (duration.unit === 's') {\n return { value: duration };\n }\n\n // ms to s conversion (lossless)\n if (duration.unit === 'ms') {\n const sValue = duration.value / 1000;\n\n // This is a lossless conversion, so just info level (not warning)\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" converted from ${duration.value}ms to ${sValue}s`,\n });\n\n return {\n value: {\n value: sValue,\n unit: 's',\n },\n };\n }\n\n // Unknown unit - warn and skip\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unsupported duration unit: \"${duration.unit}\". Figma only supports seconds (s). Convert the value to seconds or use the 'transform' option to handle this token.`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Convert a DTCG fontFamily value to Figma-compatible format.\n * Figma requires a single string, not an array.\n *\n * @example\n * // String values pass through unchanged\n * convertFontFamily(\"Inter\", context);\n * // => { value: \"Inter\" }\n *\n * @example\n * // Arrays are truncated to the first element\n * convertFontFamily([\"Inter\", \"Helvetica\", \"sans-serif\"], context);\n * // => { value: \"Inter\" } (with warning about dropped fallbacks)\n *\n * @param value - The DTCG fontFamily value (string or string array)\n * @param context - Converter context with logger and plugin options\n * @returns Single font family string, or skip indicator for invalid values\n */\nexport function convertFontFamily(value: unknown, context: ConverterContext): ConverterResult {\n // String passthrough\n if (typeof value === 'string') {\n return { value };\n }\n\n // Array - take first element\n if (Array.isArray(value)) {\n if (value.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has empty fontFamily array`,\n });\n return { value: undefined, skip: true };\n }\n\n const firstFont = value[0];\n\n if (value.length > 1) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" fontFamily array truncated to first element \"${firstFont}\" (dropped: ${value.slice(1).join(', ')})`,\n });\n }\n\n return { value: firstFont };\n }\n\n // Invalid value\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontFamily value: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Valid string aliases for font weights as per W3C DTCG spec.\n */\nconst FONT_WEIGHT_ALIASES: Record<string, number> = {\n thin: 100,\n hairline: 100,\n 'extra-light': 200,\n 'ultra-light': 200,\n light: 300,\n normal: 400,\n regular: 400,\n book: 400,\n medium: 500,\n 'semi-bold': 600,\n 'demi-bold': 600,\n bold: 700,\n 'extra-bold': 800,\n 'ultra-bold': 800,\n black: 900,\n heavy: 900,\n 'extra-black': 950,\n 'ultra-black': 950,\n};\n\n/**\n * Convert a DTCG fontWeight value to Figma-compatible format.\n * Output type matches input type (string stays string, number stays number).\n *\n * @example\n * // Number values pass through with validation (1-1000)\n * convertFontWeight(400, context);\n * // => { value: 400 }\n *\n * @example\n * // String aliases pass through if valid\n * convertFontWeight(\"bold\", context);\n * // => { value: \"bold\" }\n *\n * @param value - The DTCG fontWeight value (number 1-1000 or string alias like \"bold\")\n * @param context - Converter context with logger and plugin options\n * @returns Font weight value with outputType set to 'number' or 'string', or skip indicator for invalid values\n */\nexport function convertFontWeight(value: unknown, context: ConverterContext): ConverterResult {\n // Number passthrough - validate range, output as 'number' type\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || value < 1 || value > 1000) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontWeight value: ${value} (must be 1-1000)`,\n });\n return { value: undefined, skip: true };\n }\n return { value, outputType: 'number' };\n }\n\n // String - validate against known aliases, output as 'string' type\n if (typeof value === 'string') {\n const normalized = value.toLowerCase();\n if (!(normalized in FONT_WEIGHT_ALIASES)) {\n const validAliases = Object.keys(FONT_WEIGHT_ALIASES).slice(0, 5).join(', ');\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has unknown fontWeight alias: \"${value}\". Valid aliases include: ${validAliases}, etc. Use a valid alias or a numeric weight (1-1000).`,\n });\n return { value: undefined, skip: true };\n }\n // Pass through the original string value - Figma accepts string font weights\n return { value, outputType: 'string' };\n }\n\n // Invalid type\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid fontWeight type: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGGradientValue } from '../utils.js';\nimport { convertColor } from './color.js';\n\n/**\n * Convert a DTCG gradient value to Figma-compatible format.\n * Gradient tokens are partially split: only stop colors are extracted.\n * Stop positions are dropped since they can't be represented as Figma variables.\n *\n * @param value - The DTCG gradient value (array of gradient stops with color and position)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with color sub-tokens for each stop, or skip indicator for invalid values\n */\nexport function convertGradient(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGGradientValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid gradient value: expected array of gradient stops, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const subTokens: SubToken[] = [];\n let hasPosition = false;\n\n for (let i = 0; i < value.length; i++) {\n const stop = value[i]!;\n\n if (stop.color !== undefined) {\n const aliasKey = `${i}.color`;\n const result = convertColor(stop.color, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'color', value: result.value });\n }\n }\n\n if (stop.position !== undefined) {\n hasPosition = true;\n }\n }\n\n // Log once if any positions were dropped\n if (hasPosition) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" gradient \"position\" values dropped (variables cannot be applied to gradient stop positions in Figma)`,\n });\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" gradient value has no valid color stops`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\n\n/**\n * Convert a DTCG number value to Figma-compatible format.\n * Can output as Boolean if token has com.figma.type extension set to \"boolean\".\n *\n * @example\n * // Regular number token\n * convertNumber(1.5, context) // => { value: 1.5 }\n *\n * @example\n * // Boolean extension: 0 becomes false, non-zero becomes true\n * // Token with $extensions: { \"com.figma\": { \"type\": \"boolean\" } }\n * convertNumber(0, contextWithBooleanExt) // => { value: false }\n * convertNumber(1, contextWithBooleanExt) // => { value: true }\n *\n * @param value - The DTCG number value to convert\n * @param context - Converter context with logger, plugin options, and token extensions\n * @returns Number value (or boolean if com.figma.type is \"boolean\"), or skip indicator for invalid values\n */\nexport function convertNumber(value: unknown, context: ConverterContext): ConverterResult {\n if (typeof value !== 'number') {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid number value: ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n if (!Number.isFinite(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has non-finite number value: ${value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // Check for boolean type override via com.figma.type extension\n // Per Figma docs: $extensions: { \"com.figma.type\": \"boolean\" }\n const figmaType = context.extensions?.['com.figma.type'];\n if (figmaType === 'boolean') {\n // Convert number to boolean: 0 = false, non-zero = true\n return { value: value !== 0 };\n }\n\n // Number passthrough\n return { value };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGShadowValue, SubToken } from '../types.js';\nimport { isDTCGShadowValue } from '../utils.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\n\n/**\n * Convert a single shadow object's properties into sub-tokens.\n *\n * @param shadow - The shadow value object\n * @param prefix - Prefix for sub-token IDs (empty for single, \"0.\" for arrays)\n * @param context - Converter context\n * @returns Array of sub-tokens\n */\nfunction convertShadowLayer(shadow: DTCGShadowValue, prefix: string, context: ConverterContext): SubToken[] {\n const subTokens: SubToken[] = [];\n\n // Convert color\n if (shadow.color !== undefined) {\n const aliasKey = `${prefix}color`;\n const result = convertColor(shadow.color, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'color', value: result.value });\n }\n }\n\n // Convert offsetX\n if (shadow.offsetX !== undefined) {\n const aliasKey = `${prefix}offsetX`;\n const result = convertDimension(shadow.offsetX, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert offsetY\n if (shadow.offsetY !== undefined) {\n const aliasKey = `${prefix}offsetY`;\n const result = convertDimension(shadow.offsetY, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert blur\n if (shadow.blur !== undefined) {\n const aliasKey = `${prefix}blur`;\n const result = convertDimension(shadow.blur, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Convert spread\n if (shadow.spread !== undefined) {\n const aliasKey = `${prefix}spread`;\n const result = convertDimension(shadow.spread, {\n ...context,\n tokenId: `${context.tokenId}.${aliasKey}`,\n });\n if (!result.skip) {\n subTokens.push({ idSuffix: aliasKey, $type: 'dimension', value: result.value });\n }\n }\n\n // Drop inset (can't be applied in Figma)\n if (shadow.inset !== undefined) {\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" shadow \"inset\" property dropped (variables cannot be applied to inset shadows in Figma)`,\n });\n }\n\n return subTokens;\n}\n\n/**\n * Convert a DTCG shadow value to Figma-compatible format.\n * Shadow tokens are split into individual sub-tokens since Figma\n * doesn't support the composite shadow type.\n *\n * Single shadows produce: color, offsetX, offsetY, blur, spread\n * Multiple shadow layers produce indexed sub-tokens: 0.color, 0.offsetX, ..., 1.color, etc.\n *\n * @param value - The DTCG shadow value (single object or array of shadow layers)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with sub-tokens for each shadow property, or skip indicator for invalid values\n */\nexport function convertShadow(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGShadowValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid shadow value: expected object or array, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n const subTokens: SubToken[] = [];\n\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single-element array: treat as a single shadow (no index prefix)\n const layerTokens = convertShadowLayer(value[0] as DTCGShadowValue, '', context);\n subTokens.push(...layerTokens);\n } else {\n // Multiple shadow layers: use indexed prefixes\n for (let i = 0; i < value.length; i++) {\n const layer = value[i] as DTCGShadowValue;\n const layerTokens = convertShadowLayer(layer, `${i}.`, context);\n subTokens.push(...layerTokens);\n }\n }\n } else {\n // Single shadow object\n const layerTokens = convertShadowLayer(value, '', context);\n subTokens.push(...layerTokens);\n }\n\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" shadow value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, DTCGDimensionValue } from '../types.js';\n\n/**\n * Context for lineHeight conversion, extending the base converter context\n * with the fontSize value needed for calculating absolute lineHeight.\n */\nexport interface LineHeightConverterContext extends ConverterContext {\n /**\n * The resolved fontSize dimension value from the typography token.\n * Required to calculate absolute lineHeight from the multiplier.\n * Should already be converted to px units.\n */\n fontSize?: DTCGDimensionValue;\n}\n\n/**\n * Convert a W3C DTCG lineHeight value to Figma-compatible format.\n *\n * ## W3C DTCG vs Figma Incompatibility\n *\n * The W3C DTCG specification defines lineHeight as a **number** type -\n * a unitless multiplier relative to fontSize (e.g., `1.5` means 1.5× the\n * font size). This matches CSS behavior where `line-height: 1.5` is unitless.\n *\n * However, Figma Variables require lineHeight to be a **dimension** type\n * with explicit px units. There is no way to represent a unitless multiplier\n * in Figma's variable system.\n *\n * ## Conversion Strategy\n *\n * This converter calculates the absolute lineHeight by multiplying the\n * unitless multiplier with the fontSize:\n *\n * `absoluteLineHeight = lineHeight × fontSize`\n *\n * For example: `lineHeight: 1.5` with `fontSize: 16px` → `24px`\n *\n * ## Trade-off: Loss of Token Reference\n *\n * When converting a multiplier to an absolute dimension, any reference to\n * a primitive number token is lost. This is unavoidable because:\n *\n * 1. Figma does not support unitless multipliers for lineHeight\n * 2. We must compute a concrete px value at build time\n * 3. The computed value cannot maintain an alias to the original number token\n *\n * This approach is the most token-setup-agnostic solution, as it works\n * regardless of how the source tokens are structured.\n *\n * @example\n * // Input: W3C DTCG typography with number lineHeight\n * // lineHeight: 1.5, fontSize: { value: 16, unit: \"px\" }\n * convertLineHeight(1.5, { ...context, fontSize: { value: 16, unit: \"px\" } });\n * // Output: { value: { value: 24, unit: \"px\" } }\n *\n * @param value - The DTCG lineHeight value (unitless number multiplier)\n * @param context - Extended converter context including the resolved fontSize for calculation\n * @returns Dimension value in px (multiplier × fontSize), or skip indicator if fontSize is missing or value is invalid\n */\nexport function convertLineHeight(value: unknown, context: LineHeightConverterContext): ConverterResult {\n // W3C DTCG specifies lineHeight as a number (unitless multiplier)\n if (typeof value !== 'number') {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid lineHeight value: expected number (per W3C DTCG spec), got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // Validate the multiplier value\n if (!Number.isFinite(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has non-finite lineHeight value: ${value}`,\n });\n return { value: undefined, skip: true };\n }\n\n // fontSize is required to calculate the absolute lineHeight\n if (!context.fontSize) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has lineHeight multiplier (${value}) but no fontSize is defined. Cannot calculate absolute lineHeight for Figma. Provide a fontSize in the typography token.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Calculate absolute lineHeight: multiplier × fontSize\n const rawLineHeight = value * context.fontSize.value;\n\n // Round by default (roundLineHeight defaults to true)\n const shouldRound = context.options.roundLineHeight !== false;\n const absoluteLineHeight = shouldRound ? Math.round(rawLineHeight) : rawLineHeight;\n\n const roundingNote = shouldRound && rawLineHeight !== absoluteLineHeight ? ` (rounded from ${rawLineHeight})` : '';\n\n context.logger.info({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" lineHeight: ${value} × ${context.fontSize.value}px = ${absoluteLineHeight}px${roundingNote} (converted from W3C multiplier to Figma dimension)`,\n });\n\n return {\n value: {\n value: absoluteLineHeight,\n unit: 'px',\n },\n };\n}\n","import { PLUGIN_NAME } from '../constants.js';\nimport type { ConverterContext, ConverterResult, SubToken } from '../types.js';\nimport { isDTCGTypographyValue } from '../utils.js';\nimport { convertDimension } from './dimension.js';\nimport { convertFontFamily } from './font-family.js';\nimport { convertFontWeight } from './font-weight.js';\nimport { convertLineHeight } from './line-height.js';\n\n/**\n * Convert a DTCG typography value to Figma-compatible format.\n * Typography tokens are split into individual sub-tokens since Figma\n * doesn't support the composite typography type.\n *\n * @example\n * // Input typography token\n * convertTypography({\n * fontFamily: \"Inter\",\n * fontSize: { value: 16, unit: \"px\" },\n * fontWeight: 400,\n * lineHeight: 1.5,\n * letterSpacing: { value: 0, unit: \"px\" }\n * }, context);\n * // => { value: undefined, split: true, subTokens: [...] }\n *\n * @param value - The DTCG typography value (object with fontFamily, fontSize, fontWeight, lineHeight, letterSpacing)\n * @param context - Converter context with logger and plugin options\n * @returns Split result with sub-tokens for each typography property, or skip indicator for invalid values\n */\nexport function convertTypography(value: unknown, context: ConverterContext): ConverterResult {\n if (!isDTCGTypographyValue(value)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has invalid typography value: expected object, got ${typeof value}`,\n });\n return { value: undefined, skip: true };\n }\n const typography = value;\n\n const subTokens: SubToken[] = [];\n\n // Convert fontFamily\n if (typography.fontFamily !== undefined) {\n const result = convertFontFamily(typography.fontFamily, {\n ...context,\n tokenId: `${context.tokenId}.fontFamily`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'fontFamily',\n $type: 'fontFamily',\n value: result.value,\n });\n }\n }\n\n // Convert fontSize (dimension)\n // We also store the resolved fontSize for lineHeight calculation\n let resolvedFontSize: { value: number; unit: string } | undefined;\n if (typography.fontSize !== undefined) {\n const result = convertDimension(typography.fontSize, {\n ...context,\n tokenId: `${context.tokenId}.fontSize`,\n });\n if (!result.skip) {\n resolvedFontSize = result.value as { value: number; unit: string };\n subTokens.push({\n idSuffix: 'fontSize',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // Convert fontWeight\n if (typography.fontWeight !== undefined) {\n const result = convertFontWeight(typography.fontWeight, {\n ...context,\n tokenId: `${context.tokenId}.fontWeight`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'fontWeight',\n $type: result.outputType ?? 'fontWeight',\n value: result.value,\n });\n }\n }\n\n // Convert lineHeight (W3C number → Figma dimension)\n // Per W3C DTCG spec, lineHeight is a unitless number (multiplier).\n // Figma requires a dimension, so we compute: lineHeight × fontSize.\n // Note: This loses the reference to any primitive number token - see line-height.ts for details.\n if (typography.lineHeight !== undefined) {\n const result = convertLineHeight(typography.lineHeight, {\n ...context,\n tokenId: `${context.tokenId}.lineHeight`,\n fontSize: resolvedFontSize,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'lineHeight',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // Convert letterSpacing (dimension)\n if (typography.letterSpacing !== undefined) {\n const result = convertDimension(typography.letterSpacing, {\n ...context,\n tokenId: `${context.tokenId}.letterSpacing`,\n });\n if (!result.skip) {\n subTokens.push({\n idSuffix: 'letterSpacing',\n $type: 'dimension',\n value: result.value,\n });\n }\n }\n\n // If no sub-tokens were created, skip the token\n if (subTokens.length === 0) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" typography value has no valid sub-properties`,\n });\n return { value: undefined, skip: true };\n }\n\n return { value: undefined, split: true, subTokens };\n}\n","import type { TokenNormalized } from '@terrazzo/parser';\nimport { PLUGIN_NAME, SUPPORTED_TYPES, type SupportedType, UNSUPPORTED_TYPES } from '../constants.js';\nimport type { ConverterContext, ConverterResult } from '../types.js';\nimport { convertBorder } from './border.js';\nimport { convertColor } from './color.js';\nimport { convertDimension } from './dimension.js';\nimport { convertDuration } from './duration.js';\nimport { convertFontFamily } from './font-family.js';\nimport { convertFontWeight } from './font-weight.js';\nimport { convertGradient } from './gradient.js';\nimport { convertNumber } from './number.js';\nimport { convertShadow } from './shadow.js';\nimport { convertTypography } from './typography.js';\n\n/**\n * Converter function signature.\n */\nexport type Converter = (value: unknown, context: ConverterContext) => ConverterResult;\n\n/**\n * Registry of converters by token type.\n */\nconst converters: Record<SupportedType, Converter> = {\n color: convertColor,\n dimension: convertDimension,\n duration: convertDuration,\n fontFamily: convertFontFamily,\n fontWeight: convertFontWeight,\n number: convertNumber,\n typography: convertTypography,\n shadow: convertShadow,\n border: convertBorder,\n gradient: convertGradient,\n};\n\n/**\n * Check if a token type is supported by Figma.\n *\n * @param type - The DTCG token type string\n * @returns True if the type is one of the Figma-supported token types\n */\nexport function isSupportedType(type: string): type is SupportedType {\n return SUPPORTED_TYPES.includes(type as SupportedType);\n}\n\n/**\n * Check if a value is an alias reference (curly brace syntax).\n *\n * @param value - The value to check\n * @returns True if value is a string wrapped in curly braces (e.g., \"{color.primary}\")\n */\nexport function isAlias(value: unknown): value is string {\n return typeof value === 'string' && value.startsWith('{') && value.endsWith('}');\n}\n\n/**\n * Extract the token ID from an alias reference.\n * @param alias - The alias string, e.g., \"{color.primary}\"\n * @returns The token ID, e.g., \"color.primary\"\n */\nfunction extractAliasTarget(alias: string): string {\n return alias.slice(1, -1);\n}\n\n/**\n * Validate an alias reference and return any warnings.\n *\n * @param alias - The alias string to validate (e.g., \"{color.primary}\")\n * @param context - Converter context with allTokens map for validation\n * @returns Object with valid flag and optional warning message\n */\nfunction validateAlias(alias: string, context: ConverterContext): { valid: boolean; warning?: string } {\n const targetId = extractAliasTarget(alias);\n\n // Check if target exists\n if (!context.allTokens) {\n // Can't validate without token map\n return { valid: true };\n }\n\n const targetToken = context.allTokens[targetId];\n if (!targetToken) {\n return {\n valid: false,\n warning: `Token \"${context.tokenId}\" references non-existent token \"${targetId}\". Check the token path for typos or ensure the referenced token is defined.`,\n };\n }\n\n // Check if target is a Figma-compatible type\n if (!isSupportedType(targetToken.$type)) {\n const isKnownUnsupported = UNSUPPORTED_TYPES.includes(targetToken.$type as (typeof UNSUPPORTED_TYPES)[number]);\n return {\n valid: false,\n warning: isKnownUnsupported\n ? `Token \"${context.tokenId}\" aliases unsupported type \"${targetToken.$type}\" (from \"${targetId}\"). This alias will be preserved but may not work in Figma. Consider referencing a supported token type instead.`\n : `Token \"${context.tokenId}\" aliases unknown type \"${targetToken.$type}\" (from \"${targetId}\"). This alias will be preserved but may not work in Figma. Verify the target token has a supported type.`,\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert a token value to Figma-compatible format.\n *\n * @param token - The normalized token\n * @param value - The token value to convert\n * @param context - Converter context with logger and options\n * @returns Converted value or skip indicator\n */\nexport function convertToken(token: TokenNormalized, value: unknown, context: ConverterContext): ConverterResult {\n const { $type } = token;\n\n // Handle missing $type\n if (!$type) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" is missing $type. Ensure all tokens have a valid $type defined either directly or inherited from a parent group.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Handle undefined or null values\n if (value === undefined || value === null) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${context.tokenId}\" has no value (${value}). Ensure $value is defined for this token.`,\n });\n return { value: undefined, skip: true };\n }\n\n // Handle alias references - validate and pass through\n if (isAlias(value)) {\n const validation = validateAlias(value, context);\n\n if (validation.warning) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: validation.warning,\n });\n }\n\n // Always pass through the alias - Figma uses the same syntax\n // Even invalid aliases are preserved to avoid breaking references\n return { value };\n }\n\n // Silently skip unsupported types — the figma/unsupported-type lint rule\n // handles reporting these to the user with configurable severity.\n if (!isSupportedType($type)) {\n return { value: undefined, skip: true };\n }\n\n // Get the converter for this type\n const converter = converters[$type];\n return converter(value, context);\n}\n","import type { TokenNormalized, TransformHookOptions } from '@terrazzo/parser';\nimport { FORMAT_ID, PLUGIN_NAME } from './constants.js';\nimport { convertToken } from './converters/index.js';\nimport type { FigmaJsonPluginOptions } from './types.js';\nimport { createExcludeMatcher, toFigmaLocalID } from './utils.js';\n\nexport interface TransformOptions {\n transform: TransformHookOptions;\n options: FigmaJsonPluginOptions;\n}\n\n/**\n * Transform a single token and register it via setTransform.\n * Handles custom transforms and composite tokens (via Record<string, string>).\n *\n * Alias resolution is deferred entirely to the build step, which has access to\n * the full TokenNormalized (including aliasOf, originalValue, partialAliasOf)\n * via the transform.token property on getTransforms() results.\n *\n * @param token - The normalized token from terrazzo parser\n * @param rawValue - The resolved token value\n * @param options - Plugin configuration options\n * @param context - Plugin hook context with logger\n * @param allTokens - Map of all tokens for validation\n * @param rawTokens - Original token map (from transform.tokens) — needed because\n * resolver.apply() strips $extensions from tokens\n * @param setTransform - Terrazzo callback to register transformed value\n * @param input - Resolver input for this permutation\n * @returns void - Registers the transformed token via setTransform callback\n */\nfunction transformToken(\n token: TokenNormalized,\n rawValue: unknown,\n options: FigmaJsonPluginOptions,\n context: TransformHookOptions['context'],\n allTokens: Record<string, TokenNormalized>,\n rawTokens: Record<string, TokenNormalized>,\n setTransform: TransformHookOptions['setTransform'],\n input: Record<string, string>,\n): void {\n const localID = toFigmaLocalID(token.id);\n\n // Allow custom transform to override\n const customValue = options.transform?.(token);\n if (customValue !== undefined) {\n setTransform(token.id, { format: FORMAT_ID, localID, value: JSON.stringify(customValue), input });\n return;\n }\n\n // Look up $extensions from the raw token map — resolver.apply() strips extensions\n const rawToken = rawTokens[token.id];\n const extensions = rawToken?.$extensions ?? token.$extensions;\n\n // Convert the token value (always resolve to final value)\n const result = convertToken(token, rawValue, {\n logger: context.logger,\n options,\n tokenId: token.id,\n extensions,\n allTokens,\n originalValue: token.originalValue?.$value,\n });\n\n // Skip if converter indicates to skip\n if (result.skip) {\n return;\n }\n\n // Handle composite tokens (typography, shadow, border, gradient)\n // Pack sub-tokens into a Record<string, string> — Terrazzo's built-in mechanism\n // for tokens that store multiple values.\n if (result.split && result.subTokens) {\n const record: Record<string, string> = {};\n for (const subToken of result.subTokens) {\n record[subToken.idSuffix] = JSON.stringify({\n $type: subToken.$type,\n $value: subToken.value,\n });\n }\n setTransform(token.id, { format: FORMAT_ID, localID, value: record, input });\n return;\n }\n\n // Build the transformed token structure with resolved value\n const transformedValue = {\n $type: result.outputType ?? token.$type,\n $value: result.value,\n };\n\n setTransform(token.id, { format: FORMAT_ID, localID, value: JSON.stringify(transformedValue), input });\n}\n\n/**\n * Collect the minimal set of resolver inputs that the build step will query.\n *\n * The build step requests transforms for:\n * 1. The default input (first permutation — all modifier defaults)\n * 2. One input per individual modifier context (default + one override)\n *\n * This avoids the cartesian-product explosion from listPermutations().\n * For example, 8 modifiers with ~4 contexts each produce ~40k full\n * permutations but only ~30 targeted inputs.\n */\nfunction collectBuildInputs(resolver: TransformHookOptions['resolver']): Record<string, string>[] {\n const permutations = resolver.listPermutations();\n const defaultInput: Record<string, string> = permutations[0] ?? {};\n const inputs: Record<string, string>[] = [defaultInput];\n\n const resolverSource = resolver.source;\n if (!resolverSource?.modifiers) {\n return inputs;\n }\n\n for (const [modifierName, modifier] of Object.entries(resolverSource.modifiers)) {\n if (!modifier.contexts) {\n continue;\n }\n // Skip the auto-generated tzMode modifier with only \".\" context\n if (modifierName === 'tzMode') {\n const contextNames = Object.keys(modifier.contexts);\n if (contextNames.length === 1 && contextNames[0] === '.') {\n continue;\n }\n }\n for (const contextName of Object.keys(modifier.contexts)) {\n // Skip if this is already the default value for this modifier\n if (defaultInput[modifierName] === contextName) {\n continue;\n }\n inputs.push({ ...defaultInput, [modifierName]: contextName });\n }\n }\n\n return inputs;\n}\n\n/**\n * Transform DTCG tokens into Figma-compatible format.\n *\n * Only iterates the minimal set of resolver inputs that the build step\n * will actually query (default + one per modifier context), avoiding\n * the combinatorial explosion of the full permutation set.\n *\n * @param options - Transform options containing the transform hook context and plugin options\n */\nexport default function transformFigmaJson({ transform, options }: TransformOptions): void {\n const { setTransform, context, resolver, tokens: rawTokens } = transform;\n\n const shouldExclude = createExcludeMatcher(options.exclude);\n\n const inputs = collectBuildInputs(resolver);\n\n for (const input of inputs) {\n if (Object.keys(input).length === 0) {\n continue;\n }\n\n const contextTokens = resolver.apply(input);\n\n for (const token of Object.values(contextTokens)) {\n if (shouldExclude(token.id)) {\n continue;\n }\n\n // Skip tokens that only exist in this modifier context but not in the\n // base token set. Terrazzo's setTransform() validates against the base\n // set and throws a fatal error for unknown IDs.\n if (!(token.id in rawTokens)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Token \"${token.id}\" exists only in a non-default modifier context and is not present in the base token set. Skipping — Terrazzo does not support setTransform() for context-only tokens.`,\n });\n continue;\n }\n\n transformToken(token, token.$value, options, context, contextTokens, rawTokens, setTransform, input);\n }\n }\n}\n","import type { Plugin } from '@terrazzo/parser';\nimport buildFigmaJson from './build/index.js';\nimport { FORMAT_ID, PLUGIN_NAME } from './constants.js';\nimport { figmaUnsupportedType } from './lint/index.js';\nimport transformFigmaJson from './transform.js';\nimport type { FigmaJsonPluginOptions } from './types.js';\n\nexport type { BuildOptions } from './build/types.js';\nexport * from './constants.js';\nexport * from './transform.js';\nexport * from './types.js';\nexport * from './utils.js';\n\n/**\n * Terrazzo plugin to convert DTCG design tokens to Figma-compatible JSON format.\n *\n * @example\n * // Basic usage\n * import { defineConfig } from \"@terrazzo/cli\";\n * import figmaJson from \"terrazzo-plugin-figma-json\";\n *\n * export default defineConfig({\n * plugins: [\n * figmaJson({ filename: \"tokens.figma.json\" }),\n * ],\n * });\n *\n * @example\n * // With all options\n * figmaJson({\n * filename: \"design-tokens.figma.json\",\n * exclude: [\"internal.*\", \"deprecated.*\"],\n * remBasePx: 16,\n\n * preserveReferences: true,\n * tokenName: (token) => token.id.replace(\"color.\", \"brand.\"),\n * transform: (token) => {\n * if (token.id === \"special.token\") return { custom: true };\n * return undefined; // Use default transformation\n * },\n * });\n *\n * @param options - Plugin configuration options\n * @returns A Terrazzo plugin instance\n */\nexport default function figmaJsonPlugin(options?: FigmaJsonPluginOptions): Plugin {\n const { skipBuild } = options ?? {};\n const filename = options?.filename ?? 'tokens.figma.json';\n return {\n name: PLUGIN_NAME,\n enforce: options?.enforce,\n\n config(_config) {\n // Validate options at config time for early failure\n if (options?.remBasePx !== undefined && options.remBasePx <= 0) {\n throw new Error(`[${PLUGIN_NAME}] remBasePx must be a positive number, got ${options.remBasePx}`);\n }\n if (options?.filename?.includes('..')) {\n throw new Error(`[${PLUGIN_NAME}] filename must not contain '..', got \"${options.filename}\"`);\n }\n },\n\n lint() {\n return {\n 'figma/unsupported-type': figmaUnsupportedType,\n };\n },\n\n async transform(transformOptions) {\n // Skip if another figma-json plugin has already run\n const existingTransforms = transformOptions.getTransforms({\n format: FORMAT_ID,\n id: '*',\n });\n if (existingTransforms.length) {\n return;\n }\n\n transformFigmaJson({\n transform: transformOptions,\n options: options ?? {},\n });\n },\n\n async build({ getTransforms, outputFile, resolver }) {\n if (skipBuild === true) {\n return;\n }\n\n const result = buildFigmaJson({\n getTransforms,\n exclude: options?.exclude,\n tokenName: options?.tokenName,\n preserveReferences: options?.preserveReferences,\n resolver,\n });\n\n // Output multiple files based on resolver structure\n for (const [sourceName, contents] of result) {\n // sourceName is like \"primitive\" or \"breakpoint-small\" or \"default\" (when default resolver)\n const outputName = sourceName === 'default' ? filename : `${sourceName}.${filename}`;\n outputFile(outputName, contents);\n }\n },\n\n async buildEnd({ outputFiles, context }) {\n for (const file of outputFiles) {\n if (file.plugin !== PLUGIN_NAME) {\n continue;\n }\n\n // Validate JSON is parseable\n try {\n const parsed = JSON.parse(file.contents as string);\n\n // Validate it's a non-null object\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n context.logger.warn({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Output file \"${file.filename}\" produced invalid structure (expected object, got ${Array.isArray(parsed) ? 'array' : typeof parsed})`,\n });\n }\n } catch (err) {\n context.logger.error({\n group: 'plugin',\n label: PLUGIN_NAME,\n message: `Output file \"${file.filename}\" contains invalid JSON: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -6,7 +6,10 @@ export interface TransformOptions {
6
6
  }
7
7
  /**
8
8
  * Transform DTCG tokens into Figma-compatible format.
9
- * Uses the resolver to iterate all permutations and convert token values.
9
+ *
10
+ * Only iterates the minimal set of resolver inputs that the build step
11
+ * will actually query (default + one per modifier context), avoiding
12
+ * the combinatorial explosion of the full permutation set.
10
13
  *
11
14
  * @param options - Transform options containing the transform hook context and plugin options
12
15
  */
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,oBAAoB,CAAC;IAChC,OAAO,EAAE,sBAAsB,CAAC;CACjC;AAmFD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,gBAAgB,GAAG,IAAI,CAwBzF"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,oBAAoB,CAAC;IAChC,OAAO,EAAE,sBAAsB,CAAC;CACjC;AA+HD;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,gBAAgB,GAAG,IAAI,CAkCzF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terrazzo-plugin-figma-json",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Convert DTCG design tokens JSON into Figma-compatible design tokens JSON format.",
5
5
  "license": "MIT",
6
6
  "type": "module",