tokvista 1.6.1 → 1.7.1
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 +11 -0
- package/dist/bin/tokvista.js +184 -0
- package/package.json +1 -1
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) |
|
package/dist/bin/tokvista.js
CHANGED
|
@@ -813,6 +813,128 @@ 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) || value === "transparent" || value === "currentColor";
|
|
825
|
+
}
|
|
826
|
+
function isValidDimension(value) {
|
|
827
|
+
return /^\d+(\.\d+)?(px|rem|em|%|vh|vw)$/.test(value) || value === "0";
|
|
828
|
+
}
|
|
829
|
+
function buildTokenMap(tokens) {
|
|
830
|
+
const tokenPaths = /* @__PURE__ */ new Set();
|
|
831
|
+
function walk(node, path2 = []) {
|
|
832
|
+
if (!isRecord4(node)) return;
|
|
833
|
+
if (path2.length === 0 && Object.keys(node).some((k) => k.includes("/"))) {
|
|
834
|
+
Object.values(node).forEach((val) => walk(val, []));
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (isTokenLike2(node)) {
|
|
838
|
+
tokenPaths.add(path2.join("."));
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
Object.entries(node).forEach(([key, val]) => {
|
|
842
|
+
walk(val, [...path2, key]);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
walk(tokens);
|
|
846
|
+
return tokenPaths;
|
|
847
|
+
}
|
|
848
|
+
function validateTokens(tokens) {
|
|
849
|
+
const errors = [];
|
|
850
|
+
const warnings = [];
|
|
851
|
+
let totalTokens = 0;
|
|
852
|
+
const allAliases = [];
|
|
853
|
+
if (!isRecord4(tokens)) {
|
|
854
|
+
return {
|
|
855
|
+
valid: false,
|
|
856
|
+
errors: [{ type: "error", path: "root", message: "Token file must be a JSON object" }],
|
|
857
|
+
warnings: [],
|
|
858
|
+
totalTokens: 0
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const detection = detectTokenFormat(tokens);
|
|
862
|
+
let normalizedTokens = tokens;
|
|
863
|
+
if (detection.format !== "token-studio" && detection.format !== "unknown") {
|
|
864
|
+
normalizedTokens = normalizeTokenFormat(tokens, detection.format);
|
|
865
|
+
}
|
|
866
|
+
function walk(node, path2 = []) {
|
|
867
|
+
if (!isRecord4(node)) return;
|
|
868
|
+
if (path2.length === 0 && Object.keys(node).some((k) => k.includes("/"))) {
|
|
869
|
+
Object.values(node).forEach((val) => walk(val, []));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (isTokenLike2(node)) {
|
|
873
|
+
totalTokens++;
|
|
874
|
+
const tokenPath = path2.join(".");
|
|
875
|
+
if (!node.type) {
|
|
876
|
+
warnings.push({
|
|
877
|
+
type: "warning",
|
|
878
|
+
path: tokenPath,
|
|
879
|
+
message: "Missing type field"
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
if (node.value === null || node.value === void 0) {
|
|
883
|
+
errors.push({
|
|
884
|
+
type: "error",
|
|
885
|
+
path: tokenPath,
|
|
886
|
+
message: "Token value is null or undefined"
|
|
887
|
+
});
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const value = String(node.value);
|
|
891
|
+
if (value.match(/^\{(.+)\}$/)) {
|
|
892
|
+
allAliases.push({ tokenPath, reference: value });
|
|
893
|
+
}
|
|
894
|
+
if (node.type === "color" && !value.startsWith("{")) {
|
|
895
|
+
if (!isValidColor(value)) {
|
|
896
|
+
errors.push({
|
|
897
|
+
type: "error",
|
|
898
|
+
path: tokenPath,
|
|
899
|
+
message: `Invalid color value: "${value}"`
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (["spacing", "sizing", "borderRadius", "borderWidth"].includes(node.type || "")) {
|
|
904
|
+
if (!value.startsWith("{") && !isValidDimension(value)) {
|
|
905
|
+
errors.push({
|
|
906
|
+
type: "error",
|
|
907
|
+
path: tokenPath,
|
|
908
|
+
message: `Invalid dimension value: "${value}"`
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
Object.entries(node).forEach(([key, val]) => {
|
|
915
|
+
walk(val, [...path2, key]);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
walk(normalizedTokens);
|
|
919
|
+
const definedTokens = buildTokenMap(normalizedTokens);
|
|
920
|
+
allAliases.forEach(({ tokenPath, reference }) => {
|
|
921
|
+
const refPath = reference.slice(1, -1);
|
|
922
|
+
if (!definedTokens.has(refPath)) {
|
|
923
|
+
errors.push({
|
|
924
|
+
type: "error",
|
|
925
|
+
path: tokenPath,
|
|
926
|
+
message: `References missing token: ${reference}`
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
return {
|
|
931
|
+
valid: errors.length === 0,
|
|
932
|
+
errors,
|
|
933
|
+
warnings,
|
|
934
|
+
totalTokens
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
816
938
|
// src/bin/watcher.ts
|
|
817
939
|
import { watch } from "fs";
|
|
818
940
|
function watchFile(filePath, onChange) {
|
|
@@ -844,6 +966,7 @@ Usage:
|
|
|
844
966
|
tokvista [tokens.json] [--config tokvista.config.ts] [--port 3000] [--no-open]
|
|
845
967
|
tokvista init [--force] [--port 3000] [--no-open] [--no-preview]
|
|
846
968
|
tokvista export <tokens.json> --format <css|scss|json|tailwind> [--output <file>]
|
|
969
|
+
tokvista validate <tokens.json>
|
|
847
970
|
|
|
848
971
|
Arguments:
|
|
849
972
|
tokens.json Path to your tokens file (overrides config.tokens)
|
|
@@ -970,8 +1093,30 @@ function parseArgs(args) {
|
|
|
970
1093
|
if (args[0] === "export") {
|
|
971
1094
|
return parseExportArgs(args.slice(1));
|
|
972
1095
|
}
|
|
1096
|
+
if (args[0] === "validate") {
|
|
1097
|
+
return parseValidateArgs(args.slice(1));
|
|
1098
|
+
}
|
|
973
1099
|
return parseServeArgs(args);
|
|
974
1100
|
}
|
|
1101
|
+
function parseValidateArgs(args) {
|
|
1102
|
+
let tokenFileArg;
|
|
1103
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1104
|
+
const arg = args[index];
|
|
1105
|
+
if (arg === "-h" || arg === "--help") {
|
|
1106
|
+
printHelp();
|
|
1107
|
+
process.exit(0);
|
|
1108
|
+
}
|
|
1109
|
+
if (arg.startsWith("-")) {
|
|
1110
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1111
|
+
}
|
|
1112
|
+
if (tokenFileArg) {
|
|
1113
|
+
throw new Error(`Only one token file is supported. Unexpected value: "${arg}"`);
|
|
1114
|
+
}
|
|
1115
|
+
tokenFileArg = arg;
|
|
1116
|
+
}
|
|
1117
|
+
if (!tokenFileArg) throw new Error("Token file is required for validate");
|
|
1118
|
+
return { command: "validate", tokenFileArg };
|
|
1119
|
+
}
|
|
975
1120
|
function parseExportArgs(args) {
|
|
976
1121
|
let tokenFileArg;
|
|
977
1122
|
let format;
|
|
@@ -1645,6 +1790,41 @@ async function runExportCommand(cwd, options) {
|
|
|
1645
1790
|
console.log(output);
|
|
1646
1791
|
}
|
|
1647
1792
|
}
|
|
1793
|
+
async function runValidateCommand(cwd, options) {
|
|
1794
|
+
const resolvedTokenPath = path.resolve(cwd, options.tokenFileArg);
|
|
1795
|
+
if (!existsSync(resolvedTokenPath)) {
|
|
1796
|
+
throw new Error(`Token file not found: ${resolvedTokenPath}`);
|
|
1797
|
+
}
|
|
1798
|
+
const tokens = await readTokens(resolvedTokenPath);
|
|
1799
|
+
const result = validateTokens(tokens);
|
|
1800
|
+
console.log(`
|
|
1801
|
+
Validating ${resolvedTokenPath}...
|
|
1802
|
+
`);
|
|
1803
|
+
if (result.errors.length > 0) {
|
|
1804
|
+
console.log("\u274C Errors:");
|
|
1805
|
+
result.errors.forEach((err) => {
|
|
1806
|
+
console.log(` ${err.path}: ${err.message}`);
|
|
1807
|
+
});
|
|
1808
|
+
console.log("");
|
|
1809
|
+
}
|
|
1810
|
+
if (result.warnings.length > 0) {
|
|
1811
|
+
console.log("\u26A0\uFE0F Warnings:");
|
|
1812
|
+
result.warnings.forEach((warn) => {
|
|
1813
|
+
console.log(` ${warn.path}: ${warn.message}`);
|
|
1814
|
+
});
|
|
1815
|
+
console.log("");
|
|
1816
|
+
}
|
|
1817
|
+
console.log(`Total tokens: ${result.totalTokens}`);
|
|
1818
|
+
console.log(`Errors: ${result.errors.length}`);
|
|
1819
|
+
console.log(`Warnings: ${result.warnings.length}`);
|
|
1820
|
+
if (result.valid) {
|
|
1821
|
+
console.log("\n\u2705 All tokens are valid!\n");
|
|
1822
|
+
process.exit(0);
|
|
1823
|
+
} else {
|
|
1824
|
+
console.log("\n\u274C Validation failed\n");
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1648
1828
|
async function main() {
|
|
1649
1829
|
try {
|
|
1650
1830
|
const options = parseArgs(process.argv.slice(2));
|
|
@@ -1660,6 +1840,10 @@ async function main() {
|
|
|
1660
1840
|
await runExportCommand(cwd, options);
|
|
1661
1841
|
return;
|
|
1662
1842
|
}
|
|
1843
|
+
if (options.command === "validate") {
|
|
1844
|
+
await runValidateCommand(cwd, options);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1663
1847
|
await runServeCommand(cwd, options);
|
|
1664
1848
|
} catch (error) {
|
|
1665
1849
|
console.error(error.message);
|