tokvista 1.13.0 → 1.14.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 +10 -1
- package/dist/bin/tokvista.js +170 -0
- package/dist/cli/browser.js +17 -16
- package/dist/index.cjs +10 -9
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -9
- package/dist/styles.css +545 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Zero configuration. Multiple formats. One command.
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
21
|
- 🎨 **Beautiful visuals** - Colors, spacing, typography, and components
|
|
22
|
+
- 📊 **Pre-ship checklist** - Analytics tab catches broken aliases, hardcoded values, and architecture issues before you publish
|
|
22
23
|
- 🔄 **Multi-format support** - Token Studio, W3C, Style Dictionary, Supernova, Figma API
|
|
23
24
|
- 📋 **Smart copy** - CSS Variables, SCSS, or Tailwind with one click
|
|
24
25
|
- 🔍 **Instant search** - `Cmd+K` / `Ctrl+K` to find any token
|
|
@@ -111,6 +112,14 @@ npx tokvista init --no-preview
|
|
|
111
112
|
### Scan & Analyze
|
|
112
113
|
|
|
113
114
|
```bash
|
|
115
|
+
# Check token health (human-readable)
|
|
116
|
+
npx tokvista analytics tokens.json
|
|
117
|
+
|
|
118
|
+
# JSON output for CI/CD pipelines
|
|
119
|
+
npx tokvista analytics tokens.json --format json
|
|
120
|
+
# Exit code 1 if broken aliases found
|
|
121
|
+
# Perfect for GitHub Actions, GitLab CI, etc.
|
|
122
|
+
|
|
114
123
|
# Scan for token usage and issues
|
|
115
124
|
npx tokvista scan tokens.json
|
|
116
125
|
|
|
@@ -213,7 +222,7 @@ npx tokvista build tokens.json --output-dir ./dist --skip-validation
|
|
|
213
222
|
| `tokvista diff <old> <new>` | Compare two token files |
|
|
214
223
|
| `tokvista export <file>` | Export tokens to various formats |
|
|
215
224
|
| `tokvista convert <file>` | Convert between token formats |
|
|
216
|
-
| `tokvista
|
|
225
|
+
| `tokvista analytics <file>` | Check token health (CI/CD ready) |
|
|
217
226
|
|
|
218
227
|
| Option | Description |
|
|
219
228
|
|--------|-------------|
|
package/dist/bin/tokvista.js
CHANGED
|
@@ -1262,6 +1262,82 @@ async function scanTokenUsage(tokensPath, scanDir, tokens) {
|
|
|
1262
1262
|
};
|
|
1263
1263
|
}
|
|
1264
1264
|
|
|
1265
|
+
// src/utils/analytics.ts
|
|
1266
|
+
function isTokenLeaf(value) {
|
|
1267
|
+
return typeof value === "object" && value !== null && "type" in value && "value" in value;
|
|
1268
|
+
}
|
|
1269
|
+
function isAlias(value) {
|
|
1270
|
+
return typeof value === "string" && /^\{[^{}]+\}$/.test(value.trim());
|
|
1271
|
+
}
|
|
1272
|
+
function collectStats(obj, tokenMap, layer, path2 = [], stats) {
|
|
1273
|
+
if (!obj || typeof obj !== "object") return stats;
|
|
1274
|
+
if (isTokenLeaf(obj)) {
|
|
1275
|
+
stats.total++;
|
|
1276
|
+
stats[layer]++;
|
|
1277
|
+
const type = obj.type || "unknown";
|
|
1278
|
+
if (!stats.byType[type]) {
|
|
1279
|
+
stats.byType[type] = { total: 0, foundation: 0, semantic: 0, components: 0 };
|
|
1280
|
+
}
|
|
1281
|
+
stats.byType[type].total++;
|
|
1282
|
+
stats.byType[type][layer]++;
|
|
1283
|
+
if (type === "unknown") {
|
|
1284
|
+
const rawType = String(obj.type || "missing");
|
|
1285
|
+
stats.otherTypes[rawType] = (stats.otherTypes[rawType] || 0) + 1;
|
|
1286
|
+
}
|
|
1287
|
+
if (isAlias(obj.value)) {
|
|
1288
|
+
stats.aliases++;
|
|
1289
|
+
const resolved = resolveTokenValue(obj.value, tokenMap);
|
|
1290
|
+
if (resolved === obj.value) {
|
|
1291
|
+
stats.brokenAliases.push({ path: path2.join("."), reference: obj.value });
|
|
1292
|
+
}
|
|
1293
|
+
} else {
|
|
1294
|
+
stats.hardcoded++;
|
|
1295
|
+
if (layer === "semantic") {
|
|
1296
|
+
stats.hardcodedInSemantic++;
|
|
1297
|
+
stats.hardcodedSemanticPaths.push(path2.join("."));
|
|
1298
|
+
}
|
|
1299
|
+
if (layer === "components") {
|
|
1300
|
+
stats.hardcodedInComponents++;
|
|
1301
|
+
stats.hardcodedComponentPaths.push(path2.join("."));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return stats;
|
|
1305
|
+
}
|
|
1306
|
+
if (Array.isArray(obj)) {
|
|
1307
|
+
obj.forEach((item, i) => collectStats(item, tokenMap, layer, [...path2, String(i)], stats));
|
|
1308
|
+
} else {
|
|
1309
|
+
Object.entries(obj).forEach(
|
|
1310
|
+
([key, value]) => collectStats(value, tokenMap, layer, [...path2, key], stats)
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
return stats;
|
|
1314
|
+
}
|
|
1315
|
+
function analyzeTokens(tokens) {
|
|
1316
|
+
const tokenMap = createTokenMap(tokens);
|
|
1317
|
+
const foundation = extractFoundationSet(tokens);
|
|
1318
|
+
const semantic = extractSemanticSet(tokens);
|
|
1319
|
+
const components = extractComponentSet(tokens);
|
|
1320
|
+
const result = {
|
|
1321
|
+
total: 0,
|
|
1322
|
+
foundation: 0,
|
|
1323
|
+
semantic: 0,
|
|
1324
|
+
components: 0,
|
|
1325
|
+
byType: {},
|
|
1326
|
+
aliases: 0,
|
|
1327
|
+
hardcoded: 0,
|
|
1328
|
+
brokenAliases: [],
|
|
1329
|
+
hardcodedInSemantic: 0,
|
|
1330
|
+
hardcodedInComponents: 0,
|
|
1331
|
+
hardcodedSemanticPaths: [],
|
|
1332
|
+
hardcodedComponentPaths: [],
|
|
1333
|
+
otherTypes: {}
|
|
1334
|
+
};
|
|
1335
|
+
collectStats(foundation, tokenMap, "foundation", [], result);
|
|
1336
|
+
collectStats(semantic, tokenMap, "semantic", [], result);
|
|
1337
|
+
collectStats(components, tokenMap, "components", [], result);
|
|
1338
|
+
return result;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1265
1341
|
// src/bin/watcher.ts
|
|
1266
1342
|
import { watch } from "fs";
|
|
1267
1343
|
function watchFile(filePath, onChange) {
|
|
@@ -1443,6 +1519,9 @@ function parseArgs(args) {
|
|
|
1443
1519
|
if (args[0] === "scan") {
|
|
1444
1520
|
return parseScanArgs(args.slice(1));
|
|
1445
1521
|
}
|
|
1522
|
+
if (args[0] === "analytics") {
|
|
1523
|
+
return parseAnalyticsArgs(args.slice(1));
|
|
1524
|
+
}
|
|
1446
1525
|
return parseServeArgs(args);
|
|
1447
1526
|
}
|
|
1448
1527
|
function parseValidateArgs(args) {
|
|
@@ -2584,6 +2663,10 @@ async function main() {
|
|
|
2584
2663
|
await runScanCommand(cwd, options);
|
|
2585
2664
|
return;
|
|
2586
2665
|
}
|
|
2666
|
+
if (options.command === "analytics") {
|
|
2667
|
+
await runAnalyticsCommand(cwd, options);
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2587
2670
|
await runServeCommand(cwd, options);
|
|
2588
2671
|
} catch (error) {
|
|
2589
2672
|
console.error(error.message);
|
|
@@ -2591,3 +2674,90 @@ async function main() {
|
|
|
2591
2674
|
}
|
|
2592
2675
|
}
|
|
2593
2676
|
void main();
|
|
2677
|
+
function parseAnalyticsArgs(args) {
|
|
2678
|
+
let tokenFileArg;
|
|
2679
|
+
let format;
|
|
2680
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2681
|
+
const arg = args[index];
|
|
2682
|
+
if (arg === "-h" || arg === "--help") {
|
|
2683
|
+
printHelp();
|
|
2684
|
+
process.exit(0);
|
|
2685
|
+
}
|
|
2686
|
+
if (arg === "--format") {
|
|
2687
|
+
const next = args[index + 1];
|
|
2688
|
+
if (!next) throw new Error("Missing value for --format");
|
|
2689
|
+
if (!["json", "text"].includes(next)) {
|
|
2690
|
+
throw new Error("Format must be: json or text");
|
|
2691
|
+
}
|
|
2692
|
+
format = next;
|
|
2693
|
+
index += 1;
|
|
2694
|
+
continue;
|
|
2695
|
+
}
|
|
2696
|
+
if (arg.startsWith("--format=")) {
|
|
2697
|
+
const val = arg.slice("--format=".length);
|
|
2698
|
+
if (!["json", "text"].includes(val)) {
|
|
2699
|
+
throw new Error("Format must be: json or text");
|
|
2700
|
+
}
|
|
2701
|
+
format = val;
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
if (arg.startsWith("-")) {
|
|
2705
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
2706
|
+
}
|
|
2707
|
+
if (tokenFileArg) {
|
|
2708
|
+
throw new Error(`Only one token file is supported. Unexpected value: "${arg}"`);
|
|
2709
|
+
}
|
|
2710
|
+
tokenFileArg = arg;
|
|
2711
|
+
}
|
|
2712
|
+
if (!tokenFileArg) throw new Error("Token file is required for analytics");
|
|
2713
|
+
return { command: "analytics", tokenFileArg, format };
|
|
2714
|
+
}
|
|
2715
|
+
async function runAnalyticsCommand(cwd, options) {
|
|
2716
|
+
const resolvedTokenPath = path.resolve(cwd, options.tokenFileArg);
|
|
2717
|
+
if (!existsSync(resolvedTokenPath)) {
|
|
2718
|
+
throw new Error(`Token file not found: ${resolvedTokenPath}`);
|
|
2719
|
+
}
|
|
2720
|
+
const tokens = await readTokens(resolvedTokenPath);
|
|
2721
|
+
const result = analyzeTokens(tokens);
|
|
2722
|
+
if (options.format === "json") {
|
|
2723
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2724
|
+
process.exit(result.brokenAliases.length > 0 ? 1 : 0);
|
|
2725
|
+
}
|
|
2726
|
+
console.log(`
|
|
2727
|
+
\u{1F4CA} Token Analytics
|
|
2728
|
+
`);
|
|
2729
|
+
console.log(`Total: ${result.total}`);
|
|
2730
|
+
console.log(`Foundation: ${result.foundation}`);
|
|
2731
|
+
console.log(`Semantic: ${result.semantic}`);
|
|
2732
|
+
console.log(`Components: ${result.components}
|
|
2733
|
+
`);
|
|
2734
|
+
console.log(`Aliases: ${result.aliases}`);
|
|
2735
|
+
console.log(`Hardcoded: ${result.hardcoded}
|
|
2736
|
+
`);
|
|
2737
|
+
if (result.brokenAliases.length > 0) {
|
|
2738
|
+
console.log(`\u274C Broken Aliases (${result.brokenAliases.length}):`);
|
|
2739
|
+
result.brokenAliases.slice(0, 10).forEach(({ path: path2, reference }) => {
|
|
2740
|
+
console.log(` ${path2} \u2192 ${reference}`);
|
|
2741
|
+
});
|
|
2742
|
+
if (result.brokenAliases.length > 10) {
|
|
2743
|
+
console.log(` ... and ${result.brokenAliases.length - 10} more`);
|
|
2744
|
+
}
|
|
2745
|
+
console.log("");
|
|
2746
|
+
}
|
|
2747
|
+
if (result.hardcodedInSemantic > 0) {
|
|
2748
|
+
console.log(`\u26A0\uFE0F ${result.hardcodedInSemantic} hardcoded values in Semantic layer`);
|
|
2749
|
+
}
|
|
2750
|
+
if (result.hardcodedInComponents > 0) {
|
|
2751
|
+
console.log(`\u26A0\uFE0F ${result.hardcodedInComponents} hardcoded values in Components layer`);
|
|
2752
|
+
}
|
|
2753
|
+
if (result.hardcodedInSemantic > 0 || result.hardcodedInComponents > 0) {
|
|
2754
|
+
console.log("");
|
|
2755
|
+
}
|
|
2756
|
+
if (result.brokenAliases.length > 0) {
|
|
2757
|
+
console.log("\u274C Quality check failed\n");
|
|
2758
|
+
process.exit(1);
|
|
2759
|
+
} else {
|
|
2760
|
+
console.log("\u2705 All checks passed\n");
|
|
2761
|
+
process.exit(0);
|
|
2762
|
+
}
|
|
2763
|
+
}
|