tokvista 1.6.0 → 1.7.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.
package/README.md CHANGED
@@ -109,6 +109,16 @@ npx tokvista export tokens.json --format tailwind --output tailwind.config.js
109
109
  npx tokvista export tokens.json --format css
110
110
  ```
111
111
 
112
+ ### Validate Tokens
113
+
114
+ ```bash
115
+ # Check for errors
116
+ npx tokvista validate tokens.json
117
+
118
+ # Use in CI/CD (exits with code 1 on errors)
119
+ npm run validate-tokens
120
+ ```
121
+
112
122
  ### Interactive Setup
113
123
 
114
124
  ```bash
@@ -138,6 +148,7 @@ Then run `npx tokvista` to use your config.
138
148
  | `tokvista [file]` | Token file path (default: `./tokens.json`) |
139
149
  | `tokvista init` | Interactive config setup |
140
150
  | `tokvista export <file> --format <type>` | Export tokens (css, scss, json, tailwind) |
151
+ | `tokvista validate <file>` | Validate token structure and values |
141
152
  | `--config`, `-c` | Config file path |
142
153
  | `--port`, `-p` | Server port (default: `3000`) |
143
154
  | `--format` | Export format (export only) |
@@ -813,6 +813,102 @@ Sec-WebSocket-Accept: ${accept}\r
813
813
  }
814
814
  };
815
815
 
816
+ // src/bin/validator.ts
817
+ function isRecord4(value) {
818
+ return typeof value === "object" && value !== null && !Array.isArray(value);
819
+ }
820
+ function isTokenLike2(obj) {
821
+ return isRecord4(obj) && "value" in obj;
822
+ }
823
+ function isValidColor(value) {
824
+ return /^#([0-9A-F]{3}|[0-9A-F]{6}|[0-9A-F]{8})$/i.test(value) || /^rgb\(/.test(value) || /^rgba\(/.test(value) || /^hsl\(/.test(value) || /^hsla\(/.test(value);
825
+ }
826
+ function isValidDimension(value) {
827
+ return /^\d+(\.\d+)?(px|rem|em|%|vh|vw)$/.test(value);
828
+ }
829
+ function validateTokens(tokens) {
830
+ const errors = [];
831
+ const warnings = [];
832
+ let totalTokens = 0;
833
+ const allAliases = /* @__PURE__ */ new Set();
834
+ const definedTokens = /* @__PURE__ */ new Set();
835
+ if (!isRecord4(tokens)) {
836
+ return {
837
+ valid: false,
838
+ errors: [{ type: "error", path: "root", message: "Token file must be a JSON object" }],
839
+ warnings: [],
840
+ totalTokens: 0
841
+ };
842
+ }
843
+ function walk(node, path2 = []) {
844
+ if (!isRecord4(node)) return;
845
+ if (isTokenLike2(node)) {
846
+ totalTokens++;
847
+ const tokenPath = path2.join(".");
848
+ definedTokens.add(tokenPath);
849
+ if (!node.type) {
850
+ warnings.push({
851
+ type: "warning",
852
+ path: tokenPath,
853
+ message: "Missing type field"
854
+ });
855
+ }
856
+ if (node.value === null || node.value === void 0) {
857
+ errors.push({
858
+ type: "error",
859
+ path: tokenPath,
860
+ message: "Token value is null or undefined"
861
+ });
862
+ return;
863
+ }
864
+ const value = String(node.value);
865
+ if (value.match(/^\{(.+)\}$/)) {
866
+ allAliases.add(tokenPath + "\u2192" + value);
867
+ }
868
+ if (node.type === "color" && !value.startsWith("{")) {
869
+ if (!isValidColor(value)) {
870
+ errors.push({
871
+ type: "error",
872
+ path: tokenPath,
873
+ message: `Invalid color value: "${value}"`
874
+ });
875
+ }
876
+ }
877
+ if (["spacing", "sizing", "borderRadius", "borderWidth"].includes(node.type || "")) {
878
+ if (!value.startsWith("{") && !isValidDimension(value)) {
879
+ errors.push({
880
+ type: "error",
881
+ path: tokenPath,
882
+ message: `Invalid dimension value: "${value}"`
883
+ });
884
+ }
885
+ }
886
+ return;
887
+ }
888
+ Object.entries(node).forEach(([key, val]) => {
889
+ walk(val, [...path2, key]);
890
+ });
891
+ }
892
+ walk(tokens);
893
+ allAliases.forEach((alias) => {
894
+ const [tokenPath, reference] = alias.split("\u2192");
895
+ const refPath = reference.slice(1, -1).replace(/\./g, ".");
896
+ if (!definedTokens.has(refPath)) {
897
+ errors.push({
898
+ type: "error",
899
+ path: tokenPath,
900
+ message: `References missing token: ${reference}`
901
+ });
902
+ }
903
+ });
904
+ return {
905
+ valid: errors.length === 0,
906
+ errors,
907
+ warnings,
908
+ totalTokens
909
+ };
910
+ }
911
+
816
912
  // src/bin/watcher.ts
817
913
  import { watch } from "fs";
818
914
  function watchFile(filePath, onChange) {
@@ -844,6 +940,7 @@ Usage:
844
940
  tokvista [tokens.json] [--config tokvista.config.ts] [--port 3000] [--no-open]
845
941
  tokvista init [--force] [--port 3000] [--no-open] [--no-preview]
846
942
  tokvista export <tokens.json> --format <css|scss|json|tailwind> [--output <file>]
943
+ tokvista validate <tokens.json>
847
944
 
848
945
  Arguments:
849
946
  tokens.json Path to your tokens file (overrides config.tokens)
@@ -855,6 +952,7 @@ Options:
855
952
  --format Export format: css, scss, json, tailwind (export only)
856
953
  --output, -o Output file path (export only)
857
954
  --no-open Do not automatically open the browser
955
+ --no-watch Disable live reload (serve only)
858
956
  --no-preview Skip starting live preview after init
859
957
  -h, --help Show this help message
860
958
  `);
@@ -969,8 +1067,30 @@ function parseArgs(args) {
969
1067
  if (args[0] === "export") {
970
1068
  return parseExportArgs(args.slice(1));
971
1069
  }
1070
+ if (args[0] === "validate") {
1071
+ return parseValidateArgs(args.slice(1));
1072
+ }
972
1073
  return parseServeArgs(args);
973
1074
  }
1075
+ function parseValidateArgs(args) {
1076
+ let tokenFileArg;
1077
+ for (let index = 0; index < args.length; index += 1) {
1078
+ const arg = args[index];
1079
+ if (arg === "-h" || arg === "--help") {
1080
+ printHelp();
1081
+ process.exit(0);
1082
+ }
1083
+ if (arg.startsWith("-")) {
1084
+ throw new Error(`Unknown option: ${arg}`);
1085
+ }
1086
+ if (tokenFileArg) {
1087
+ throw new Error(`Only one token file is supported. Unexpected value: "${arg}"`);
1088
+ }
1089
+ tokenFileArg = arg;
1090
+ }
1091
+ if (!tokenFileArg) throw new Error("Token file is required for validate");
1092
+ return { command: "validate", tokenFileArg };
1093
+ }
974
1094
  function parseExportArgs(args) {
975
1095
  let tokenFileArg;
976
1096
  let format;
@@ -1644,6 +1764,41 @@ async function runExportCommand(cwd, options) {
1644
1764
  console.log(output);
1645
1765
  }
1646
1766
  }
1767
+ async function runValidateCommand(cwd, options) {
1768
+ const resolvedTokenPath = path.resolve(cwd, options.tokenFileArg);
1769
+ if (!existsSync(resolvedTokenPath)) {
1770
+ throw new Error(`Token file not found: ${resolvedTokenPath}`);
1771
+ }
1772
+ const tokens = await readTokens(resolvedTokenPath);
1773
+ const result = validateTokens(tokens);
1774
+ console.log(`
1775
+ Validating ${resolvedTokenPath}...
1776
+ `);
1777
+ if (result.errors.length > 0) {
1778
+ console.log("\u274C Errors:");
1779
+ result.errors.forEach((err) => {
1780
+ console.log(` ${err.path}: ${err.message}`);
1781
+ });
1782
+ console.log("");
1783
+ }
1784
+ if (result.warnings.length > 0) {
1785
+ console.log("\u26A0\uFE0F Warnings:");
1786
+ result.warnings.forEach((warn) => {
1787
+ console.log(` ${warn.path}: ${warn.message}`);
1788
+ });
1789
+ console.log("");
1790
+ }
1791
+ console.log(`Total tokens: ${result.totalTokens}`);
1792
+ console.log(`Errors: ${result.errors.length}`);
1793
+ console.log(`Warnings: ${result.warnings.length}`);
1794
+ if (result.valid) {
1795
+ console.log("\n\u2705 All tokens are valid!\n");
1796
+ process.exit(0);
1797
+ } else {
1798
+ console.log("\n\u274C Validation failed\n");
1799
+ process.exit(1);
1800
+ }
1801
+ }
1647
1802
  async function main() {
1648
1803
  try {
1649
1804
  const options = parseArgs(process.argv.slice(2));
@@ -1659,6 +1814,10 @@ async function main() {
1659
1814
  await runExportCommand(cwd, options);
1660
1815
  return;
1661
1816
  }
1817
+ if (options.command === "validate") {
1818
+ await runValidateCommand(cwd, options);
1819
+ return;
1820
+ }
1662
1821
  await runServeCommand(cwd, options);
1663
1822
  } catch (error) {
1664
1823
  console.error(error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokvista",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Interactive visual documentation for design tokens.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",