design-constraint-validator 1.0.0

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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +659 -0
  3. package/adapters/README.md +46 -0
  4. package/adapters/css.d.ts +44 -0
  5. package/adapters/css.d.ts.map +1 -0
  6. package/adapters/css.js +97 -0
  7. package/adapters/css.ts +116 -0
  8. package/adapters/js.d.ts +3 -0
  9. package/adapters/js.d.ts.map +1 -0
  10. package/adapters/js.js +15 -0
  11. package/adapters/js.ts +14 -0
  12. package/adapters/json.d.ts +18 -0
  13. package/adapters/json.d.ts.map +1 -0
  14. package/adapters/json.js +35 -0
  15. package/adapters/json.ts +45 -0
  16. package/cli/build-css.d.ts +2 -0
  17. package/cli/build-css.d.ts.map +1 -0
  18. package/cli/build-css.js +23 -0
  19. package/cli/build-css.ts +32 -0
  20. package/cli/commands/build.d.ts +5 -0
  21. package/cli/commands/build.d.ts.map +1 -0
  22. package/cli/commands/build.js +89 -0
  23. package/cli/commands/build.ts +65 -0
  24. package/cli/commands/graph.d.ts +3 -0
  25. package/cli/commands/graph.d.ts.map +1 -0
  26. package/cli/commands/graph.js +219 -0
  27. package/cli/commands/graph.ts +137 -0
  28. package/cli/commands/index.d.ts +8 -0
  29. package/cli/commands/index.d.ts.map +1 -0
  30. package/cli/commands/index.js +7 -0
  31. package/cli/commands/index.ts +7 -0
  32. package/cli/commands/patch-apply.d.ts +3 -0
  33. package/cli/commands/patch-apply.d.ts.map +1 -0
  34. package/cli/commands/patch-apply.js +75 -0
  35. package/cli/commands/patch-apply.ts +80 -0
  36. package/cli/commands/patch.d.ts +3 -0
  37. package/cli/commands/patch.d.ts.map +1 -0
  38. package/cli/commands/patch.js +21 -0
  39. package/cli/commands/patch.ts +22 -0
  40. package/cli/commands/set.d.ts +3 -0
  41. package/cli/commands/set.d.ts.map +1 -0
  42. package/cli/commands/set.js +286 -0
  43. package/cli/commands/set.ts +225 -0
  44. package/cli/commands/utils.d.ts +4 -0
  45. package/cli/commands/utils.d.ts.map +1 -0
  46. package/cli/commands/utils.js +51 -0
  47. package/cli/commands/utils.ts +50 -0
  48. package/cli/commands/validate.d.ts +3 -0
  49. package/cli/commands/validate.d.ts.map +1 -0
  50. package/cli/commands/validate.js +131 -0
  51. package/cli/commands/validate.ts +115 -0
  52. package/cli/commands/why.d.ts +3 -0
  53. package/cli/commands/why.d.ts.map +1 -0
  54. package/cli/commands/why.js +64 -0
  55. package/cli/commands/why.ts +46 -0
  56. package/cli/config-schema.d.ts +238 -0
  57. package/cli/config-schema.d.ts.map +1 -0
  58. package/cli/config-schema.js +21 -0
  59. package/cli/config-schema.ts +27 -0
  60. package/cli/config.d.ts +4 -0
  61. package/cli/config.d.ts.map +1 -0
  62. package/cli/config.js +37 -0
  63. package/cli/config.ts +35 -0
  64. package/cli/dcv.d.ts +3 -0
  65. package/cli/dcv.d.ts.map +1 -0
  66. package/cli/dcv.js +86 -0
  67. package/cli/dcv.ts +107 -0
  68. package/cli/engine-helpers.d.ts +8 -0
  69. package/cli/engine-helpers.d.ts.map +1 -0
  70. package/cli/engine-helpers.js +70 -0
  71. package/cli/engine-helpers.ts +61 -0
  72. package/cli/graph-poset.d.ts +9 -0
  73. package/cli/graph-poset.d.ts.map +1 -0
  74. package/cli/graph-poset.js +58 -0
  75. package/cli/graph-poset.ts +74 -0
  76. package/cli/index.d.ts +3 -0
  77. package/cli/index.d.ts.map +1 -0
  78. package/cli/index.js +2 -0
  79. package/cli/index.ts +2 -0
  80. package/cli/result.d.ts +17 -0
  81. package/cli/result.d.ts.map +1 -0
  82. package/cli/result.js +29 -0
  83. package/cli/result.ts +27 -0
  84. package/cli/run.d.ts +3 -0
  85. package/cli/run.d.ts.map +1 -0
  86. package/cli/run.js +47 -0
  87. package/cli/run.ts +54 -0
  88. package/cli/smoke-test.d.ts +2 -0
  89. package/cli/smoke-test.d.ts.map +1 -0
  90. package/cli/smoke-test.js +33 -0
  91. package/cli/smoke-test.ts +40 -0
  92. package/cli/types.d.ts +86 -0
  93. package/cli/types.d.ts.map +1 -0
  94. package/cli/types.js +1 -0
  95. package/cli/types.ts +78 -0
  96. package/core/breakpoints.d.ts +12 -0
  97. package/core/breakpoints.d.ts.map +1 -0
  98. package/core/breakpoints.js +48 -0
  99. package/core/breakpoints.ts +50 -0
  100. package/core/cli-format.d.ts +8 -0
  101. package/core/cli-format.d.ts.map +1 -0
  102. package/core/cli-format.js +29 -0
  103. package/core/cli-format.ts +31 -0
  104. package/core/color.d.ts +14 -0
  105. package/core/color.d.ts.map +1 -0
  106. package/core/color.js +136 -0
  107. package/core/color.ts +148 -0
  108. package/core/constraints/cross-axis.d.ts +33 -0
  109. package/core/constraints/cross-axis.d.ts.map +1 -0
  110. package/core/constraints/cross-axis.js +93 -0
  111. package/core/constraints/cross-axis.ts +114 -0
  112. package/core/constraints/monotonic-lightness.d.ts +5 -0
  113. package/core/constraints/monotonic-lightness.d.ts.map +1 -0
  114. package/core/constraints/monotonic-lightness.js +37 -0
  115. package/core/constraints/monotonic-lightness.ts +38 -0
  116. package/core/constraints/monotonic.d.ts +7 -0
  117. package/core/constraints/monotonic.d.ts.map +1 -0
  118. package/core/constraints/monotonic.js +65 -0
  119. package/core/constraints/monotonic.ts +74 -0
  120. package/core/constraints/threshold.d.ts +10 -0
  121. package/core/constraints/threshold.d.ts.map +1 -0
  122. package/core/constraints/threshold.js +36 -0
  123. package/core/constraints/threshold.ts +43 -0
  124. package/core/constraints/wcag.d.ts +11 -0
  125. package/core/constraints/wcag.d.ts.map +1 -0
  126. package/core/constraints/wcag.js +53 -0
  127. package/core/constraints/wcag.ts +70 -0
  128. package/core/cross-axis-config.d.ts +5 -0
  129. package/core/cross-axis-config.d.ts.map +1 -0
  130. package/core/cross-axis-config.js +144 -0
  131. package/core/cross-axis-config.ts +152 -0
  132. package/core/engine.d.ts +32 -0
  133. package/core/engine.d.ts.map +1 -0
  134. package/core/engine.js +46 -0
  135. package/core/engine.ts +65 -0
  136. package/core/flatten.d.ts +20 -0
  137. package/core/flatten.d.ts.map +1 -0
  138. package/core/flatten.js +80 -0
  139. package/core/flatten.ts +116 -0
  140. package/core/image-export.d.ts +10 -0
  141. package/core/image-export.d.ts.map +1 -0
  142. package/core/image-export.js +43 -0
  143. package/core/image-export.ts +48 -0
  144. package/core/index.d.ts +31 -0
  145. package/core/index.d.ts.map +1 -0
  146. package/core/index.js +54 -0
  147. package/core/index.ts +72 -0
  148. package/core/patch.d.ts +28 -0
  149. package/core/patch.d.ts.map +1 -0
  150. package/core/patch.js +110 -0
  151. package/core/patch.ts +134 -0
  152. package/core/poset.d.ts +41 -0
  153. package/core/poset.d.ts.map +1 -0
  154. package/core/poset.js +275 -0
  155. package/core/poset.ts +311 -0
  156. package/core/why.d.ts +17 -0
  157. package/core/why.d.ts.map +1 -0
  158. package/core/why.js +45 -0
  159. package/core/why.ts +63 -0
  160. package/dist/test-overrides-removal.json +4 -0
  161. package/dist/tmp.patch.json +35 -0
  162. package/package.json +90 -0
  163. package/themes/color.lg.order.json +15 -0
  164. package/themes/color.md.order.json +15 -0
  165. package/themes/color.order.json +15 -0
  166. package/themes/color.sm.order.json +15 -0
  167. package/themes/cross-axis.rules.json +36 -0
  168. package/themes/cross-axis.sm.rules.json +12 -0
  169. package/themes/layout.lg.order.json +18 -0
  170. package/themes/layout.md.order.json +18 -0
  171. package/themes/layout.order.json +18 -0
  172. package/themes/layout.sm.order.json +18 -0
  173. package/themes/spacing.order.json +14 -0
  174. package/themes/typography.lg.order.json +15 -0
  175. package/themes/typography.md.order.json +15 -0
  176. package/themes/typography.order.json +15 -0
  177. package/themes/typography.sm.order.json +15 -0
  178. package/tokens/overrides/base.json +22 -0
  179. package/tokens/overrides/lg.json +20 -0
  180. package/tokens/overrides/md.json +16 -0
  181. package/tokens/overrides/sm.json +16 -0
  182. package/tokens/overrides/viol.color.json +6 -0
  183. package/tokens/overrides/viol.typography.json +6 -0
  184. package/tokens/tokens.demo-violations.json +116 -0
  185. package/tokens/tokens.example.json +128 -0
  186. package/tokens/tokens.json +67 -0
  187. package/tokens/tokens.multi-violations.json +21 -0
  188. package/tokens/tokens.schema.d.ts +2298 -0
  189. package/tokens/tokens.schema.d.ts.map +1 -0
  190. package/tokens/tokens.schema.js +148 -0
  191. package/tokens/tokens.schema.ts +196 -0
  192. package/tokens/tokens.test.json +38 -0
  193. package/tokens/tokens.touch-violation.json +8 -0
  194. package/tokens/typography.classes.css +11 -0
  195. package/tokens/typography.css +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.schema.d.ts","sourceRoot":"","sources":["tokens.schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA6KxB,6CAA6C;AAE7C,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMvB,CAAC;AAGH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,0CAA0C"}
@@ -0,0 +1,148 @@
1
+ // tokens.schema.ts
2
+ import { z } from "zod";
3
+ /** ---------- Helpers ---------- */
4
+ // Simple alias format: {namespace.path.to.token}
5
+ const AliasRef = z.string().regex(/^\{[a-zA-Z0-9_.-]+\}$/, {
6
+ message: "Expected an alias like {color.role.accent.default} or a concrete value.",
7
+ });
8
+ // Very lightweight color/string formats (OKLCH, hex, rgb[a], hsl[a], color()).
9
+ // You can tighten these later if you want stricter parsing.
10
+ const ColorLiteral = z
11
+ .string()
12
+ .regex(/^(oklch\(.+\)|#([0-9a-fA-F]{3,8})\b|rgba?\(.+\)|hsla?\(.+\)|color\(.+\))$/, "Expected a color (oklch(...), #hex, rgb/rgba(...), hsl/hsla(...), or color(...))");
13
+ const DurationLiteral = z
14
+ .string()
15
+ .regex(/^\d+(\.\d+)?(ms|s)$/i, "Expected a CSS time like '150ms' or '0.2s'");
16
+ const DimensionLiteral = z
17
+ .string()
18
+ .regex(/^-?\d+(\.\d+)?(px|rem|em|ch|vh|vw|%)$/i, "Expected a CSS length like '1rem', '12px', '50%'");
19
+ const ShadowLiteral = z
20
+ .string()
21
+ .regex(/^(none|[-0-9a-zA-Z ().,/%]+)$/, "Expected a box-shadow string (e.g., '0 1px 2px rgba(0,0,0,.1)')");
22
+ const EasingLiteral = z
23
+ .string()
24
+ .regex(/^(linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier\(.+\))$/i, "Expected an easing like 'ease' or 'cubic-bezier(...)'");
25
+ const FontFamilyLiteral = z.string(); // keep loose; real-world lists vary
26
+ const FontSizeLiteral = z.string(); // allow 'clamp(...)', '1rem', etc.
27
+ const LineHeightLiteral = z.union([z.number(), z.string()]); // 1.6 or '1.6'
28
+ /** ---------- Token leaf kinds ---------- */
29
+ const TokenTypeEnum = z.enum([
30
+ // color
31
+ "color",
32
+ // dimensions / sizes
33
+ "dimension",
34
+ "borderRadius",
35
+ "fontSize",
36
+ "shadow",
37
+ "duration",
38
+ "easing",
39
+ // typography
40
+ "fontFamily",
41
+ "lineHeight",
42
+ ]);
43
+ // By $type, what values are allowed?
44
+ const ValueByType = {
45
+ color: z.union([ColorLiteral, AliasRef]),
46
+ dimension: z.union([DimensionLiteral, AliasRef]),
47
+ borderRadius: z.union([DimensionLiteral, AliasRef]),
48
+ fontSize: z.union([FontSizeLiteral, AliasRef]),
49
+ shadow: z.union([ShadowLiteral, AliasRef]),
50
+ duration: z.union([DurationLiteral, AliasRef]),
51
+ easing: z.union([EasingLiteral, AliasRef]),
52
+ fontFamily: z.union([FontFamilyLiteral, AliasRef]),
53
+ lineHeight: z.union([LineHeightLiteral, AliasRef]),
54
+ };
55
+ // Generic "leaf" token: { $type: 'color' | ..., $value: ... }
56
+ const TokenLeaf = z
57
+ .object({
58
+ $type: TokenTypeEnum,
59
+ $value: z.any(), // refined below based on $type
60
+ })
61
+ .superRefine((val, ctx) => {
62
+ const validator = ValueByType[val.$type];
63
+ const res = validator.safeParse(val.$value);
64
+ if (!res.success) {
65
+ res.error.issues.forEach((issue) => ctx.addIssue({
66
+ code: z.ZodIssueCode.custom,
67
+ message: `Invalid $value for $type="${val.$type}": ${issue.message}`,
68
+ }));
69
+ }
70
+ });
71
+ /** ---------- Namespaces / structure ---------- */
72
+ // We'll allow arbitrary numeric keys like "0", "600", "700", etc.
73
+ const StringKeyRecord = (schema) => z.record(z.string(), schema);
74
+ // ---- color.palette (brand/gray/white/...) ----
75
+ const ColorScale = StringKeyRecord(TokenLeaf); // e.g., {"600": {...}, "700": {...}}
76
+ const ColorFamily = z.union([
77
+ ColorScale, // For scales like gray: { "0": {...}, "900": {...} }
78
+ TokenLeaf, // For direct tokens like white: { "$type": "color", "$value": "..." }
79
+ ]);
80
+ const ColorPalette = z.object({
81
+ // open-ended families (brand, gray, white, success, etc.)
82
+ // Example file uses brand, gray, white.
83
+ ...{},
84
+ }).catchall(ColorFamily);
85
+ // ---- color.role (text/bg/accent/on/focus) ----
86
+ const ColorRole = z.object({
87
+ // text: default, muted...
88
+ text: z
89
+ .object({
90
+ default: TokenLeaf,
91
+ muted: TokenLeaf,
92
+ })
93
+ .partial()
94
+ .passthrough(),
95
+ // bg: surface...
96
+ bg: z.object({ surface: TokenLeaf }).partial().passthrough(),
97
+ // accent: default, hover...
98
+ accent: z.object({ default: TokenLeaf, hover: TokenLeaf }).partial().passthrough(),
99
+ // on: accent...
100
+ on: z.object({ accent: TokenLeaf }).partial().passthrough(),
101
+ // focus: ring...
102
+ focus: z.object({ ring: TokenLeaf }).partial().passthrough(),
103
+ })
104
+ .partial()
105
+ .passthrough();
106
+ // ---- color root ----
107
+ const ColorRoot = z.object({
108
+ palette: ColorPalette,
109
+ role: ColorRole,
110
+ });
111
+ // ---- size namespace (spacing, radius, border) ----
112
+ const SizeSpacing = StringKeyRecord(TokenLeaf); // {"2": {...}, "4": {...}}
113
+ const SizeRadius = StringKeyRecord(TokenLeaf); // {"md": {...}}
114
+ const SizeBorder = StringKeyRecord(TokenLeaf); // {"1": {...}}
115
+ const SizeRoot = z.object({
116
+ spacing: SizeSpacing,
117
+ radius: SizeRadius,
118
+ border: SizeBorder,
119
+ });
120
+ // ---- typography namespace ----
121
+ const TypographyRoot = z.object({
122
+ font: z.object({ body: TokenLeaf }).partial().passthrough(),
123
+ size: z.object({ body: TokenLeaf }).partial().passthrough(),
124
+ lineHeight: z.object({ body: TokenLeaf }).partial().passthrough(),
125
+ });
126
+ // ---- motion namespace ----
127
+ const MotionRoot = z.object({
128
+ duration: z.object({ fast: TokenLeaf }).partial().passthrough(),
129
+ easing: z.object({ standard: TokenLeaf }).partial().passthrough(),
130
+ });
131
+ // ---- elevation namespace ----
132
+ const ElevationRoot = StringKeyRecord(TokenLeaf); // {"1": {...}, "2": {...}}
133
+ /** ---------- Top-level schema ---------- */
134
+ export const TokensSchema = z.object({
135
+ color: ColorRoot,
136
+ size: SizeRoot,
137
+ typography: TypographyRoot,
138
+ motion: MotionRoot,
139
+ elevation: ElevationRoot,
140
+ });
141
+ /** ---------- Example usage ---------- */
142
+ // import tokens from "./tokens.example.json";
143
+ // const result = TokensSchema.safeParse(tokens);
144
+ // if (!result.success) {
145
+ // console.error(JSON.stringify(result.error.format(), null, 2));
146
+ // process.exit(1);
147
+ // }
148
+ // console.log("✅ tokens.json is valid!");
@@ -0,0 +1,196 @@
1
+ // tokens.schema.ts
2
+ import { z } from "zod";
3
+
4
+ /** ---------- Helpers ---------- */
5
+
6
+ // Simple alias format: {namespace.path.to.token}
7
+ const AliasRef = z.string().regex(/^\{[a-zA-Z0-9_.-]+\}$/, {
8
+ message:
9
+ "Expected an alias like {color.role.accent.default} or a concrete value.",
10
+ });
11
+
12
+ // Very lightweight color/string formats (OKLCH, hex, rgb[a], hsl[a], color()).
13
+ // You can tighten these later if you want stricter parsing.
14
+ const ColorLiteral = z
15
+ .string()
16
+ .regex(
17
+ /^(oklch\(.+\)|#([0-9a-fA-F]{3,8})\b|rgba?\(.+\)|hsla?\(.+\)|color\(.+\))$/,
18
+ "Expected a color (oklch(...), #hex, rgb/rgba(...), hsl/hsla(...), or color(...))"
19
+ );
20
+
21
+ const DurationLiteral = z
22
+ .string()
23
+ .regex(/^\d+(\.\d+)?(ms|s)$/i, "Expected a CSS time like '150ms' or '0.2s'");
24
+
25
+ const DimensionLiteral = z
26
+ .string()
27
+ .regex(
28
+ /^-?\d+(\.\d+)?(px|rem|em|ch|vh|vw|%)$/i,
29
+ "Expected a CSS length like '1rem', '12px', '50%'"
30
+ );
31
+
32
+ const ShadowLiteral = z
33
+ .string()
34
+ .regex(
35
+ /^(none|[-0-9a-zA-Z ().,/%]+)$/,
36
+ "Expected a box-shadow string (e.g., '0 1px 2px rgba(0,0,0,.1)')"
37
+ );
38
+
39
+ const EasingLiteral = z
40
+ .string()
41
+ .regex(
42
+ /^(linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier\(.+\))$/i,
43
+ "Expected an easing like 'ease' or 'cubic-bezier(...)'"
44
+ );
45
+
46
+ const FontFamilyLiteral = z.string(); // keep loose; real-world lists vary
47
+ const FontSizeLiteral = z.string(); // allow 'clamp(...)', '1rem', etc.
48
+ const LineHeightLiteral = z.union([z.number(), z.string()]); // 1.6 or '1.6'
49
+
50
+ /** ---------- Token leaf kinds ---------- */
51
+
52
+ const TokenTypeEnum = z.enum([
53
+ // color
54
+ "color",
55
+ // dimensions / sizes
56
+ "dimension",
57
+ "borderRadius",
58
+ "fontSize",
59
+ "shadow",
60
+ "duration",
61
+ "easing",
62
+ // typography
63
+ "fontFamily",
64
+ "lineHeight",
65
+ ]);
66
+
67
+ // By $type, what values are allowed?
68
+ const ValueByType: Record<
69
+ z.infer<typeof TokenTypeEnum>,
70
+ z.ZodTypeAny
71
+ > = {
72
+ color: z.union([ColorLiteral, AliasRef]),
73
+ dimension: z.union([DimensionLiteral, AliasRef]),
74
+ borderRadius: z.union([DimensionLiteral, AliasRef]),
75
+ fontSize: z.union([FontSizeLiteral, AliasRef]),
76
+ shadow: z.union([ShadowLiteral, AliasRef]),
77
+ duration: z.union([DurationLiteral, AliasRef]),
78
+ easing: z.union([EasingLiteral, AliasRef]),
79
+ fontFamily: z.union([FontFamilyLiteral, AliasRef]),
80
+ lineHeight: z.union([LineHeightLiteral, AliasRef]),
81
+ };
82
+
83
+ // Generic "leaf" token: { $type: 'color' | ..., $value: ... }
84
+ const TokenLeaf = z
85
+ .object({
86
+ $type: TokenTypeEnum,
87
+ $value: z.any(), // refined below based on $type
88
+ })
89
+ .superRefine((val, ctx) => {
90
+ const validator = ValueByType[val.$type];
91
+ const res = validator.safeParse(val.$value);
92
+ if (!res.success) {
93
+ res.error.issues.forEach((issue) =>
94
+ ctx.addIssue({
95
+ code: z.ZodIssueCode.custom,
96
+ message: `Invalid $value for $type="${val.$type}": ${issue.message}`,
97
+ })
98
+ );
99
+ }
100
+ });
101
+
102
+ /** ---------- Namespaces / structure ---------- */
103
+
104
+ // We'll allow arbitrary numeric keys like "0", "600", "700", etc.
105
+ const StringKeyRecord = <T extends z.ZodTypeAny>(schema: T) =>
106
+ z.record(z.string(), schema);
107
+
108
+ // ---- color.palette (brand/gray/white/...) ----
109
+ const ColorScale = StringKeyRecord(TokenLeaf); // e.g., {"600": {...}, "700": {...}}
110
+ const ColorFamily = z.union([
111
+ ColorScale, // For scales like gray: { "0": {...}, "900": {...} }
112
+ TokenLeaf, // For direct tokens like white: { "$type": "color", "$value": "..." }
113
+ ]);
114
+ const ColorPalette = z.object({
115
+ // open-ended families (brand, gray, white, success, etc.)
116
+ // Example file uses brand, gray, white.
117
+ ...({} as Record<string, z.ZodTypeAny>),
118
+ }).catchall(ColorFamily);
119
+
120
+ // ---- color.role (text/bg/accent/on/focus) ----
121
+ const ColorRole = z.object({
122
+ // text: default, muted...
123
+ text: z
124
+ .object({
125
+ default: TokenLeaf,
126
+ muted: TokenLeaf,
127
+ })
128
+ .partial()
129
+ .passthrough(),
130
+ // bg: surface...
131
+ bg: z.object({ surface: TokenLeaf }).partial().passthrough(),
132
+ // accent: default, hover...
133
+ accent: z.object({ default: TokenLeaf, hover: TokenLeaf }).partial().passthrough(),
134
+ // on: accent...
135
+ on: z.object({ accent: TokenLeaf }).partial().passthrough(),
136
+ // focus: ring...
137
+ focus: z.object({ ring: TokenLeaf }).partial().passthrough(),
138
+ })
139
+ .partial()
140
+ .passthrough();
141
+
142
+ // ---- color root ----
143
+ const ColorRoot = z.object({
144
+ palette: ColorPalette,
145
+ role: ColorRole,
146
+ });
147
+
148
+ // ---- size namespace (spacing, radius, border) ----
149
+ const SizeSpacing = StringKeyRecord(TokenLeaf); // {"2": {...}, "4": {...}}
150
+ const SizeRadius = StringKeyRecord(TokenLeaf); // {"md": {...}}
151
+ const SizeBorder = StringKeyRecord(TokenLeaf); // {"1": {...}}
152
+
153
+ const SizeRoot = z.object({
154
+ spacing: SizeSpacing,
155
+ radius: SizeRadius,
156
+ border: SizeBorder,
157
+ });
158
+
159
+ // ---- typography namespace ----
160
+ const TypographyRoot = z.object({
161
+ font: z.object({ body: TokenLeaf }).partial().passthrough(),
162
+ size: z.object({ body: TokenLeaf }).partial().passthrough(),
163
+ lineHeight: z.object({ body: TokenLeaf }).partial().passthrough(),
164
+ });
165
+
166
+ // ---- motion namespace ----
167
+ const MotionRoot = z.object({
168
+ duration: z.object({ fast: TokenLeaf }).partial().passthrough(),
169
+ easing: z.object({ standard: TokenLeaf }).partial().passthrough(),
170
+ });
171
+
172
+ // ---- elevation namespace ----
173
+ const ElevationRoot = StringKeyRecord(TokenLeaf); // {"1": {...}, "2": {...}}
174
+
175
+ /** ---------- Top-level schema ---------- */
176
+
177
+ export const TokensSchema = z.object({
178
+ color: ColorRoot,
179
+ size: SizeRoot,
180
+ typography: TypographyRoot,
181
+ motion: MotionRoot,
182
+ elevation: ElevationRoot,
183
+ });
184
+
185
+ // Export the TypeScript type you can use in your app/CI.
186
+ export type Tokens = z.infer<typeof TokensSchema>;
187
+
188
+ /** ---------- Example usage ---------- */
189
+
190
+ // import tokens from "./tokens.example.json";
191
+ // const result = TokensSchema.safeParse(tokens);
192
+ // if (!result.success) {
193
+ // console.error(JSON.stringify(result.error.format(), null, 2));
194
+ // process.exit(1);
195
+ // }
196
+ // console.log("✅ tokens.json is valid!");
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schemaVersion": "1.0.0",
3
+
4
+ "color": {
5
+ "palette": {
6
+ "gray": {
7
+ "0": { "$type": "color", "$value": "oklch(0.99 0.001 95)" },
8
+ "300": { "$type": "color", "$value": "oklch(0.75 0.01 95)" },
9
+ "400": { "$type": "color", "$value": "oklch(0.65 0.015 95)" },
10
+ "500": { "$type": "color", "$value": "oklch(0.55 0.02 95)" },
11
+ "600": { "$type": "color", "$value": "oklch(0.45 0.02 95)" },
12
+ "900": { "$type": "color", "$value": "oklch(0.20 0.02 95)" }
13
+ },
14
+ "brand": {
15
+ "400": { "$type": "color", "$value": "oklch(0.70 0.15 260)" },
16
+ "500": { "$type": "color", "$value": "oklch(0.66 0.16 260)" },
17
+ "600": { "$type": "color", "$value": "oklch(0.62 0.18 260)" },
18
+ "700": { "$type": "color", "$value": "oklch(0.56 0.19 260)" }
19
+ }
20
+ }
21
+ },
22
+
23
+ "typography": {
24
+ "size": {
25
+ "h1": { "$type": "fontSize", "$value": "20px" },
26
+ "h2": { "$type": "fontSize", "$value": "24px" },
27
+ "h3": { "$type": "fontSize", "$value": "18px" },
28
+ "h4": { "$type": "fontSize", "$value": "16px" },
29
+ "h5": { "$type": "fontSize", "$value": "14px" },
30
+ "h6": { "$type": "fontSize", "$value": "12px" },
31
+ "xl": { "$type": "fontSize", "$value": "20px" },
32
+ "lg": { "$type": "fontSize", "$value": "18px" },
33
+ "base": { "$type": "fontSize", "$value": "16px" },
34
+ "sm": { "$type": "fontSize", "$value": "14px" },
35
+ "xs": { "$type": "fontSize", "$value": "12px" }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schemaVersion": "1.0.0",
3
+ "control": {
4
+ "size": {
5
+ "min": { "$type": "dimension", "$value": "30px" }
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ .t-root { font-family: var(--t-font-family); }
2
+
3
+ .t-h1 { font-size: var(--t-h1-size); line-height: var(--t-h1-leading); font-weight: var(--t-h1-weight); letter-spacing: var(--t-h1-track); margin: var(--t-block-gap) 0; }
4
+
5
+ .t-h2 { font-size: var(--t-h2-size); line-height: var(--t-h2-leading); font-weight: var(--t-h2-weight); letter-spacing: var(--t-h2-track); margin: calc(var(--t-block-gap) * .9) 0; }
6
+
7
+ .t-h3 { font-size: var(--t-h3-size); line-height: var(--t-h3-leading); font-weight: var(--t-h3-weight); letter-spacing: var(--t-h3-track); margin: calc(var(--t-block-gap) * .8) 0; }
8
+
9
+ .t-body { font-size: var(--t-body-size); line-height: var(--t-body-leading); font-weight: var(--t-body-weight); letter-spacing: var(--t-body-track); }
10
+
11
+ .t-button { font: inherit; font-weight: 600; }
@@ -0,0 +1,20 @@
1
+ :root {
2
+ --t-font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
3
+ --t-h1-size: clamp(2rem, 1.4rem + 2vw, 3rem);
4
+ --t-h2-size: clamp(1.5rem, 1.1rem + 1.2vw, 2.25rem);
5
+ --t-h3-size: clamp(1.25rem, 1rem + .6vw, 1.5rem);
6
+ --t-body-size: 1rem;
7
+ --t-h1-leading: 1.2;
8
+ --t-h2-leading: 1.25;
9
+ --t-h3-leading: 1.3;
10
+ --t-body-leading: 1.6;
11
+ --t-h1-weight: 700;
12
+ --t-h2-weight: 700;
13
+ --t-h3-weight: 600;
14
+ --t-body-weight: 400;
15
+ --t-h1-track: -0.01em;
16
+ --t-h2-track: -0.005em;
17
+ --t-h3-track: 0;
18
+ --t-body-track: 0;
19
+ --t-block-gap: 1rem;
20
+ }