From 6c46b74665d24de576265dde7f96e0b2d7b62bce Mon Sep 17 00:00:00 2001
From: Lukas Oppermann <lukasoppermann@github.com>
Date: Mon, 25 Nov 2024 15:37:42 +0100
Subject: [PATCH] add schema validation and improvement for figma attribute

---
 scripts/buildTokens.ts                        | 13 +++++++---
 scripts/themes.config.ts                      | 10 ++++++-
 src/platforms/docJson.ts                      |  1 +
 src/platforms/figma.ts                        |  1 +
 src/platforms/javascript.ts                   |  1 +
 src/platforms/json.ts                         |  1 +
 src/platforms/styleLint.ts                    |  1 +
 src/platforms/typeDefinitions.ts              |  1 +
 src/platforms/typescript.ts                   |  1 +
 src/schemas/colorToken.ts                     | 26 +++++++++++++++++++
 .../color/components/topicTag.json5           | 15 +++++++++++
 .../functional/color/dark/app-dark.json5      | 14 ----------
 .../functional/color/light/app-light.json5    | 18 -------------
 .../functional/color/testComponent.json5      | 22 ----------------
 src/transformers/figmaAttributes.ts           |  2 +-
 15 files changed, 68 insertions(+), 59 deletions(-)
 create mode 100644 src/tokens/functional/color/components/topicTag.json5
 delete mode 100644 src/tokens/functional/color/testComponent.json5

diff --git a/scripts/buildTokens.ts b/scripts/buildTokens.ts
index e55afe383..1d5d7fbdb 100644
--- a/scripts/buildTokens.ts
+++ b/scripts/buildTokens.ts
@@ -37,9 +37,16 @@ const getStyleDictionaryConfig: StyleDictionaryConfigGenerator = (
   },
   platforms: Object.fromEntries(
     Object.entries({
-      css: css(`css/${filename}.css`, options.prefix, options.buildPath, {themed: options.themed}),
-      docJson: docJson(`docs/${filename}.json`, options.prefix, options.buildPath),
-      styleLint: styleLint(`styleLint/${filename}.json`, options.prefix, options.buildPath),
+      css: css(`css/${filename}.css`, options.prefix, options.buildPath, {
+        themed: options.themed,
+        theme: options.theme,
+      }),
+      docJson: docJson(`docs/${filename}.json`, options.prefix, options.buildPath, {
+        theme: options.theme,
+      }),
+      styleLint: styleLint(`styleLint/${filename}.json`, options.prefix, options.buildPath{
+        theme: options.theme,
+      }),
       fallbacks: fallbacks(`fallbacks/${filename}.json`, options.prefix, options.buildPath),
       ...platforms,
     }).filter((entry: [string, unknown]) => entry[1] !== undefined),
diff --git a/scripts/themes.config.ts b/scripts/themes.config.ts
index fccb31249..86587e593 100644
--- a/scripts/themes.config.ts
+++ b/scripts/themes.config.ts
@@ -8,7 +8,7 @@ export const themes: TokenBuildInput[] = [
       `src/tokens/functional/color/light/*.json5`,
       `src/tokens/functional/shadow/light.json5`,
       `src/tokens/functional/border/light.json5`,
-      `src/tokens/functional/color/testComponent.json5`,
+      `src/tokens/functional/color/components/*.json5`,
     ],
     include: [`src/tokens/base/color/light/light.json5`, `src/tokens/base/color/light/display-light.json5`],
   },
@@ -19,12 +19,14 @@ export const themes: TokenBuildInput[] = [
       `src/tokens/functional/color/light/*.json5`,
       `src/tokens/functional/shadow/light.json5`,
       `src/tokens/functional/border/light.json5`,
+      `src/tokens/functional/color/components/*.json5`,
       `src/tokens/functional/color/light/overrides/light.tritanopia.json5`,
     ],
     include: [`src/tokens/base/color/light/light.json5`, `src/tokens/base/color/light/display-light.json5`],
   },
   {
     filename: 'light-colorblind',
+    theme: 'light-protanopia-deuteranopia',
     source: [
       `src/tokens/functional/color/light/*.json5`,
       `src/tokens/functional/shadow/light.json5`,
@@ -35,6 +37,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'light-high-contrast',
+    theme: 'light-high-contrast',
     source: [
       `src/tokens/functional/color/light/*.json5`,
       `src/tokens/functional/color/light/overrides/light.high-contrast.json5`,
@@ -49,6 +52,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'dark',
+    theme: 'dark',
     source: [
       `src/tokens/functional/color/dark/*.json5`,
       `src/tokens/functional/shadow/dark.json5`,
@@ -59,6 +63,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'dark-dimmed',
+    theme: 'dark-dimmed',
     source: [
       `src/tokens/functional/color/dark/*.json5`,
       `src/tokens/functional/color/dark/overrides/dark.dimmed.json5`,
@@ -73,6 +78,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'dark-tritanopia',
+    theme: 'dark-tritanopia',
     source: [
       `src/tokens/functional/color/dark/*.json5`,
       `src/tokens/functional/shadow/dark.json5`,
@@ -83,6 +89,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'dark-colorblind',
+    theme: 'dark-protanopia-deuteranopia',
     source: [
       `src/tokens/functional/color/dark/*.json5`,
       `src/tokens/functional/shadow/dark.json5`,
@@ -93,6 +100,7 @@ export const themes: TokenBuildInput[] = [
   },
   {
     filename: 'dark-high-contrast',
+    theme: 'dark-high-contrast',
     source: [
       `src/tokens/functional/color/dark/*.json5`,
       `src/tokens/functional/color/dark/overrides/dark.high-contrast.json5`,
diff --git a/src/platforms/docJson.ts b/src/platforms/docJson.ts
index bc763114b..3a054c40d 100644
--- a/src/platforms/docJson.ts
+++ b/src/platforms/docJson.ts
@@ -5,6 +5,7 @@ import type {PlatformConfig} from 'style-dictionary/types'
 export const docJson: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'name/pathToKebabCase',
     'color/hex',
diff --git a/src/platforms/figma.ts b/src/platforms/figma.ts
index 2d4d9fd7f..eb390d9bd 100644
--- a/src/platforms/figma.ts
+++ b/src/platforms/figma.ts
@@ -23,6 +23,7 @@ const validFigmaToken = async (token: TransformedToken, options: Config) => {
 export const figma: PlatformInitializer = (outputFile, prefix, buildPath, options): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'color/rgbaFloat',
     'fontFamily/figma',
diff --git a/src/platforms/javascript.ts b/src/platforms/javascript.ts
index 5df3f08e0..192c0b6c5 100644
--- a/src/platforms/javascript.ts
+++ b/src/platforms/javascript.ts
@@ -5,6 +5,7 @@ import {isSource} from '../filters/index.js'
 export const javascript: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'color/hex',
     'color/hexMix',
diff --git a/src/platforms/json.ts b/src/platforms/json.ts
index cbb9ef360..a337ec3c5 100644
--- a/src/platforms/json.ts
+++ b/src/platforms/json.ts
@@ -5,6 +5,7 @@ import type {PlatformConfig} from 'style-dictionary/types'
 export const json: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'color/hex',
     'color/hexMix',
diff --git a/src/platforms/styleLint.ts b/src/platforms/styleLint.ts
index d85ef692c..c8d013c90 100644
--- a/src/platforms/styleLint.ts
+++ b/src/platforms/styleLint.ts
@@ -5,6 +5,7 @@ import type {PlatformConfig} from 'style-dictionary/types'
 export const styleLint: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'name/pathToKebabCase',
     'color/hex',
diff --git a/src/platforms/typeDefinitions.ts b/src/platforms/typeDefinitions.ts
index 77c91860f..b8b5f68d3 100644
--- a/src/platforms/typeDefinitions.ts
+++ b/src/platforms/typeDefinitions.ts
@@ -6,6 +6,7 @@ import type {PlatformConfig} from 'style-dictionary/types'
 export const typeDefinitions: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'color/hex',
     'shadow/css',
diff --git a/src/platforms/typescript.ts b/src/platforms/typescript.ts
index a49d46670..430579311 100644
--- a/src/platforms/typescript.ts
+++ b/src/platforms/typescript.ts
@@ -5,6 +5,7 @@ import {isSource} from '../filters/index.js'
 export const typescript: PlatformInitializer = (outputFile, prefix, buildPath): PlatformConfig => ({
   prefix,
   buildPath,
+  preprocessors: ['getThemeValue'],
   transforms: [
     'color/hex',
     'color/hexMix',
diff --git a/src/schemas/colorToken.ts b/src/schemas/colorToken.ts
index 94de93728..6e30ca853 100644
--- a/src/schemas/colorToken.ts
+++ b/src/schemas/colorToken.ts
@@ -47,6 +47,32 @@ export const colorToken = baseToken
             scopes: scopes(['all', 'bgColor', 'fgColor', 'borderColor', 'effectColor']).optional(),
           })
           .optional(),
+        'org.primer.theme': z
+          .object(
+            {
+              light: z.union([colorHexValue, referenceValue]).optional(),
+              'light-tritanopia': z.union([colorHexValue, referenceValue]).optional(),
+              'light-deutranopia-protanopia': z.union([colorHexValue, referenceValue]).optional(),
+              'light-high-contrast': z.union([colorHexValue, referenceValue]).optional(),
+              dark: z.union([colorHexValue, referenceValue]).optional(),
+              'dark-tritanopia': z.union([colorHexValue, referenceValue]).optional(),
+              'dark-deutranopia-protanopia': z.union([colorHexValue, referenceValue]).optional(),
+              'dark-high-contrast': z.union([colorHexValue, referenceValue]).optional(),
+              'dark-dimmed': z.union([colorHexValue, referenceValue]).optional(),
+            },
+            {
+              errorMap: e => {
+                if (e.code === 'unrecognized_keys') {
+                  return {
+                    message: `Unrecognized key: "${e.keys.join(', ')}", must be one of: light, light-tritanopia, light-deutranopia-protanopia, light-high-contrast, dark, dark-tritanopia, dark-deutranopia-protanopia, dark-high-contrast, dark-dimmed`,
+                  }
+                }
+                return {message: `Error: ${e.code}`}
+              },
+            },
+          )
+          .strict()
+          .optional(),
       })
       .optional(),
   })
diff --git a/src/tokens/functional/color/components/topicTag.json5 b/src/tokens/functional/color/components/topicTag.json5
new file mode 100644
index 000000000..343c9c11c
--- /dev/null
+++ b/src/tokens/functional/color/components/topicTag.json5
@@ -0,0 +1,15 @@
+{
+  topicTag: {
+    borderColor: {
+      $value: '{base.color.transparent}',
+      $type: 'color',
+      $extensions: {
+        'org.primer.figma': {
+          collection: 'mode',
+          group: 'component',
+          scopes: ['borderColor'],
+        },
+      },
+    },
+  },
+}
diff --git a/src/tokens/functional/color/dark/app-dark.json5 b/src/tokens/functional/color/dark/app-dark.json5
index 0c373d700..2e408c639 100644
--- a/src/tokens/functional/color/dark/app-dark.json5
+++ b/src/tokens/functional/color/dark/app-dark.json5
@@ -1,18 +1,4 @@
 {
-  topicTag: {
-    borderColor: {
-      $value: '{base.color.transparent}',
-      $type: 'color',
-      $extensions: {
-        'org.primer.figma': {
-          collection: 'mode',
-          mode: 'dark',
-          group: 'component',
-          scopes: ['borderColor'],
-        },
-      },
-    },
-  },
   highlight: {
     neutral: {
       bgColor: {
diff --git a/src/tokens/functional/color/light/app-light.json5 b/src/tokens/functional/color/light/app-light.json5
index 199bdca25..661a37e17 100644
--- a/src/tokens/functional/color/light/app-light.json5
+++ b/src/tokens/functional/color/light/app-light.json5
@@ -1,22 +1,4 @@
 {
-  topicTag: {
-    borderColor: {
-      $value: '{base.color.transparent}',
-      $type: 'color',
-      $extensions: {
-        'org.primer.figma': {
-          collection: 'mode',
-          mode: 'light',
-          group: 'component',
-          scopes: ['borderColor'],
-        },
-        'org.primer.theme': {
-          light: '{base.color.red.5}',
-          'light-tritanopia': '{base.color.pink.5}',
-        },
-      },
-    },
-  },
   highlight: {
     neutral: {
       bgColor: {
diff --git a/src/tokens/functional/color/testComponent.json5 b/src/tokens/functional/color/testComponent.json5
deleted file mode 100644
index 107af318a..000000000
--- a/src/tokens/functional/color/testComponent.json5
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  testComponent: {
-    borderColor: {
-      $value: '{base.color.neutral.10}',
-      $type: 'color',
-      $extensions: {
-        'org.primer': {
-          values: [
-            {
-              $value: '{base.color.red.9}',
-              mode: 'dark',
-            },
-            {
-              $value: '{base.color.green.8}',
-              mode: 'light-high-contrast',
-            },
-          ],
-        },
-      },
-    },
-  },
-}
diff --git a/src/transformers/figmaAttributes.ts b/src/transformers/figmaAttributes.ts
index d9c9ee644..8de8a4847 100644
--- a/src/transformers/figmaAttributes.ts
+++ b/src/transformers/figmaAttributes.ts
@@ -70,7 +70,7 @@ export const figmaAttributes: Transform = {
   transform: (token: TransformedToken, platform: PlatformConfig = {}) => {
     const {mode, collection, scopes, group, codeSyntax} = token.$extensions?.['org.primer.figma'] || {}
     return {
-      mode: platform.options?.mode || mode || 'default',
+      mode: platform.options?.theme || mode || 'default',
       collection,
       group: group || collection,
       scopes: getScopes(scopes),