tokvista 1.7.0 → 1.8.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 +13 -0
- package/dist/bin/tokvista.js +170 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,18 @@ npx tokvista validate tokens.json
|
|
|
119
119
|
npm run validate-tokens
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### Compare Tokens
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Compare two token files
|
|
126
|
+
npx tokvista diff tokens-v1.json tokens-v2.json
|
|
127
|
+
|
|
128
|
+
# Perfect for:
|
|
129
|
+
# - Version control reviews
|
|
130
|
+
# - Release changelogs
|
|
131
|
+
# - Migration tracking
|
|
132
|
+
```
|
|
133
|
+
|
|
122
134
|
### Interactive Setup
|
|
123
135
|
|
|
124
136
|
```bash
|
|
@@ -149,6 +161,7 @@ Then run `npx tokvista` to use your config.
|
|
|
149
161
|
| `tokvista init` | Interactive config setup |
|
|
150
162
|
| `tokvista export <file> --format <type>` | Export tokens (css, scss, json, tailwind) |
|
|
151
163
|
| `tokvista validate <file>` | Validate token structure and values |
|
|
164
|
+
| `tokvista diff <old> <new>` | Compare two token files |
|
|
152
165
|
| `--config`, `-c` | Config file path |
|
|
153
166
|
| `--port`, `-p` | Server port (default: `3000`) |
|
|
154
167
|
| `--format` | Export format (export only) |
|
package/dist/bin/tokvista.js
CHANGED
|
@@ -821,17 +821,35 @@ function isTokenLike2(obj) {
|
|
|
821
821
|
return isRecord4(obj) && "value" in obj;
|
|
822
822
|
}
|
|
823
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);
|
|
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
825
|
}
|
|
826
826
|
function isValidDimension(value) {
|
|
827
|
-
return /^\d+(\.\d+)?(px|rem|em|%|vh|vw)$/.test(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;
|
|
828
847
|
}
|
|
829
848
|
function validateTokens(tokens) {
|
|
830
849
|
const errors = [];
|
|
831
850
|
const warnings = [];
|
|
832
851
|
let totalTokens = 0;
|
|
833
|
-
const allAliases =
|
|
834
|
-
const definedTokens = /* @__PURE__ */ new Set();
|
|
852
|
+
const allAliases = [];
|
|
835
853
|
if (!isRecord4(tokens)) {
|
|
836
854
|
return {
|
|
837
855
|
valid: false,
|
|
@@ -840,12 +858,20 @@ function validateTokens(tokens) {
|
|
|
840
858
|
totalTokens: 0
|
|
841
859
|
};
|
|
842
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
|
+
}
|
|
843
866
|
function walk(node, path2 = []) {
|
|
844
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
|
+
}
|
|
845
872
|
if (isTokenLike2(node)) {
|
|
846
873
|
totalTokens++;
|
|
847
874
|
const tokenPath = path2.join(".");
|
|
848
|
-
definedTokens.add(tokenPath);
|
|
849
875
|
if (!node.type) {
|
|
850
876
|
warnings.push({
|
|
851
877
|
type: "warning",
|
|
@@ -863,7 +889,7 @@ function validateTokens(tokens) {
|
|
|
863
889
|
}
|
|
864
890
|
const value = String(node.value);
|
|
865
891
|
if (value.match(/^\{(.+)\}$/)) {
|
|
866
|
-
allAliases.
|
|
892
|
+
allAliases.push({ tokenPath, reference: value });
|
|
867
893
|
}
|
|
868
894
|
if (node.type === "color" && !value.startsWith("{")) {
|
|
869
895
|
if (!isValidColor(value)) {
|
|
@@ -889,10 +915,10 @@ function validateTokens(tokens) {
|
|
|
889
915
|
walk(val, [...path2, key]);
|
|
890
916
|
});
|
|
891
917
|
}
|
|
892
|
-
walk(
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const refPath = reference.slice(1, -1)
|
|
918
|
+
walk(normalizedTokens);
|
|
919
|
+
const definedTokens = buildTokenMap(normalizedTokens);
|
|
920
|
+
allAliases.forEach(({ tokenPath, reference }) => {
|
|
921
|
+
const refPath = reference.slice(1, -1);
|
|
896
922
|
if (!definedTokens.has(refPath)) {
|
|
897
923
|
errors.push({
|
|
898
924
|
type: "error",
|
|
@@ -909,6 +935,66 @@ function validateTokens(tokens) {
|
|
|
909
935
|
};
|
|
910
936
|
}
|
|
911
937
|
|
|
938
|
+
// src/bin/differ.ts
|
|
939
|
+
function isRecord5(value) {
|
|
940
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
941
|
+
}
|
|
942
|
+
function isTokenLike3(obj) {
|
|
943
|
+
return isRecord5(obj) && "value" in obj;
|
|
944
|
+
}
|
|
945
|
+
function flattenTokens(tokens) {
|
|
946
|
+
const flat = /* @__PURE__ */ new Map();
|
|
947
|
+
function walk(node, path2 = []) {
|
|
948
|
+
if (!isRecord5(node)) return;
|
|
949
|
+
if (path2.length === 0 && Object.keys(node).some((k) => k.includes("/"))) {
|
|
950
|
+
Object.values(node).forEach((val) => walk(val, []));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (isTokenLike3(node)) {
|
|
954
|
+
flat.set(path2.join("."), String(node.value));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
Object.entries(node).forEach(([key, val]) => {
|
|
958
|
+
walk(val, [...path2, key]);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
walk(tokens);
|
|
962
|
+
return flat;
|
|
963
|
+
}
|
|
964
|
+
function diffTokens(oldTokens, newTokens) {
|
|
965
|
+
const oldDetection = detectTokenFormat(oldTokens);
|
|
966
|
+
const newDetection = detectTokenFormat(newTokens);
|
|
967
|
+
let normalizedOld = oldTokens;
|
|
968
|
+
let normalizedNew = newTokens;
|
|
969
|
+
if (oldDetection.format !== "token-studio" && oldDetection.format !== "unknown") {
|
|
970
|
+
normalizedOld = normalizeTokenFormat(oldTokens, oldDetection.format);
|
|
971
|
+
}
|
|
972
|
+
if (newDetection.format !== "token-studio" && newDetection.format !== "unknown") {
|
|
973
|
+
normalizedNew = normalizeTokenFormat(newTokens, newDetection.format);
|
|
974
|
+
}
|
|
975
|
+
const oldFlat = flattenTokens(normalizedOld);
|
|
976
|
+
const newFlat = flattenTokens(normalizedNew);
|
|
977
|
+
const added = [];
|
|
978
|
+
const removed = [];
|
|
979
|
+
const modified = [];
|
|
980
|
+
let unchanged = 0;
|
|
981
|
+
newFlat.forEach((newValue, path2) => {
|
|
982
|
+
if (!oldFlat.has(path2)) {
|
|
983
|
+
added.push(path2);
|
|
984
|
+
} else if (oldFlat.get(path2) !== newValue) {
|
|
985
|
+
modified.push({ path: path2, oldValue: oldFlat.get(path2), newValue });
|
|
986
|
+
} else {
|
|
987
|
+
unchanged++;
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
oldFlat.forEach((_, path2) => {
|
|
991
|
+
if (!newFlat.has(path2)) {
|
|
992
|
+
removed.push(path2);
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
return { added, removed, modified, unchanged };
|
|
996
|
+
}
|
|
997
|
+
|
|
912
998
|
// src/bin/watcher.ts
|
|
913
999
|
import { watch } from "fs";
|
|
914
1000
|
function watchFile(filePath, onChange) {
|
|
@@ -941,6 +1027,7 @@ Usage:
|
|
|
941
1027
|
tokvista init [--force] [--port 3000] [--no-open] [--no-preview]
|
|
942
1028
|
tokvista export <tokens.json> --format <css|scss|json|tailwind> [--output <file>]
|
|
943
1029
|
tokvista validate <tokens.json>
|
|
1030
|
+
tokvista diff <old.json> <new.json>
|
|
944
1031
|
|
|
945
1032
|
Arguments:
|
|
946
1033
|
tokens.json Path to your tokens file (overrides config.tokens)
|
|
@@ -1070,6 +1157,9 @@ function parseArgs(args) {
|
|
|
1070
1157
|
if (args[0] === "validate") {
|
|
1071
1158
|
return parseValidateArgs(args.slice(1));
|
|
1072
1159
|
}
|
|
1160
|
+
if (args[0] === "diff") {
|
|
1161
|
+
return parseDiffArgs(args.slice(1));
|
|
1162
|
+
}
|
|
1073
1163
|
return parseServeArgs(args);
|
|
1074
1164
|
}
|
|
1075
1165
|
function parseValidateArgs(args) {
|
|
@@ -1091,6 +1181,30 @@ function parseValidateArgs(args) {
|
|
|
1091
1181
|
if (!tokenFileArg) throw new Error("Token file is required for validate");
|
|
1092
1182
|
return { command: "validate", tokenFileArg };
|
|
1093
1183
|
}
|
|
1184
|
+
function parseDiffArgs(args) {
|
|
1185
|
+
let oldFileArg;
|
|
1186
|
+
let newFileArg;
|
|
1187
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1188
|
+
const arg = args[index];
|
|
1189
|
+
if (arg === "-h" || arg === "--help") {
|
|
1190
|
+
printHelp();
|
|
1191
|
+
process.exit(0);
|
|
1192
|
+
}
|
|
1193
|
+
if (arg.startsWith("-")) {
|
|
1194
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1195
|
+
}
|
|
1196
|
+
if (!oldFileArg) {
|
|
1197
|
+
oldFileArg = arg;
|
|
1198
|
+
} else if (!newFileArg) {
|
|
1199
|
+
newFileArg = arg;
|
|
1200
|
+
} else {
|
|
1201
|
+
throw new Error(`Too many arguments. Expected: tokvista diff <old.json> <new.json>`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (!oldFileArg) throw new Error("Old token file is required");
|
|
1205
|
+
if (!newFileArg) throw new Error("New token file is required");
|
|
1206
|
+
return { command: "diff", oldFileArg, newFileArg };
|
|
1207
|
+
}
|
|
1094
1208
|
function parseExportArgs(args) {
|
|
1095
1209
|
let tokenFileArg;
|
|
1096
1210
|
let format;
|
|
@@ -1799,6 +1913,48 @@ Validating ${resolvedTokenPath}...
|
|
|
1799
1913
|
process.exit(1);
|
|
1800
1914
|
}
|
|
1801
1915
|
}
|
|
1916
|
+
async function runDiffCommand(cwd, options) {
|
|
1917
|
+
const oldPath = path.resolve(cwd, options.oldFileArg);
|
|
1918
|
+
const newPath = path.resolve(cwd, options.newFileArg);
|
|
1919
|
+
if (!existsSync(oldPath)) {
|
|
1920
|
+
throw new Error(`Old token file not found: ${oldPath}`);
|
|
1921
|
+
}
|
|
1922
|
+
if (!existsSync(newPath)) {
|
|
1923
|
+
throw new Error(`New token file not found: ${newPath}`);
|
|
1924
|
+
}
|
|
1925
|
+
const [oldTokens, newTokens] = await Promise.all([
|
|
1926
|
+
readTokens(oldPath),
|
|
1927
|
+
readTokens(newPath)
|
|
1928
|
+
]);
|
|
1929
|
+
const diff = diffTokens(oldTokens, newTokens);
|
|
1930
|
+
console.log(`
|
|
1931
|
+
Comparing tokens:
|
|
1932
|
+
Old: ${oldPath}
|
|
1933
|
+
New: ${newPath}
|
|
1934
|
+
`);
|
|
1935
|
+
if (diff.added.length > 0) {
|
|
1936
|
+
console.log(`\u2705 Added (${diff.added.length}):`);
|
|
1937
|
+
diff.added.forEach((path2) => console.log(` + ${path2}`));
|
|
1938
|
+
console.log("");
|
|
1939
|
+
}
|
|
1940
|
+
if (diff.removed.length > 0) {
|
|
1941
|
+
console.log(`\u274C Removed (${diff.removed.length}):`);
|
|
1942
|
+
diff.removed.forEach((path2) => console.log(` - ${path2}`));
|
|
1943
|
+
console.log("");
|
|
1944
|
+
}
|
|
1945
|
+
if (diff.modified.length > 0) {
|
|
1946
|
+
console.log(`\u{1F504} Modified (${diff.modified.length}):`);
|
|
1947
|
+
diff.modified.forEach(({ path: path2, oldValue, newValue }) => {
|
|
1948
|
+
console.log(` ~ ${path2}`);
|
|
1949
|
+
console.log(` - ${oldValue}`);
|
|
1950
|
+
console.log(` + ${newValue}`);
|
|
1951
|
+
});
|
|
1952
|
+
console.log("");
|
|
1953
|
+
}
|
|
1954
|
+
console.log(`Unchanged: ${diff.unchanged}`);
|
|
1955
|
+
console.log(`Total changes: ${diff.added.length + diff.removed.length + diff.modified.length}
|
|
1956
|
+
`);
|
|
1957
|
+
}
|
|
1802
1958
|
async function main() {
|
|
1803
1959
|
try {
|
|
1804
1960
|
const options = parseArgs(process.argv.slice(2));
|
|
@@ -1818,6 +1974,10 @@ async function main() {
|
|
|
1818
1974
|
await runValidateCommand(cwd, options);
|
|
1819
1975
|
return;
|
|
1820
1976
|
}
|
|
1977
|
+
if (options.command === "diff") {
|
|
1978
|
+
await runDiffCommand(cwd, options);
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1821
1981
|
await runServeCommand(cwd, options);
|
|
1822
1982
|
} catch (error) {
|
|
1823
1983
|
console.error(error.message);
|