tokvista 1.5.0 → 1.5.2
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 +2 -0
- package/dist/bin/tokvista.js +122 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Zero configuration. Multiple formats. One command.
|
|
|
24
24
|
- 🔍 **Instant search** - `Cmd+K` / `Ctrl+K` to find any token
|
|
25
25
|
- 🎯 **Zero config** - Works out of the box with any token format
|
|
26
26
|
- ⚡ **Two modes** - CLI for quick preview or React component for apps
|
|
27
|
+
- 🔥 **Live reload** - Auto-refresh on file changes
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -120,6 +121,7 @@ Then run `npx tokvista` to use your config.
|
|
|
120
121
|
| `--config`, `-c` | Config file path |
|
|
121
122
|
| `--port`, `-p` | Server port (default: `3000`) |
|
|
122
123
|
| `--no-open` | Don't open browser |
|
|
124
|
+
| `--no-watch` | Disable live reload |
|
|
123
125
|
| `--no-preview` | Skip preview after init |
|
|
124
126
|
| `--force`, `-f` | Overwrite existing config |
|
|
125
127
|
| `--help`, `-h` | Show help |
|
package/dist/bin/tokvista.js
CHANGED
|
@@ -2,12 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
// src/bin/tokvista.ts
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
|
-
import { existsSync } from "fs";
|
|
5
|
+
import { existsSync, readdirSync } from "fs";
|
|
6
6
|
import { readFile, writeFile } from "fs/promises";
|
|
7
7
|
import { createServer } from "http";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { createInterface } from "readline";
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11
|
+
|
|
12
|
+
// src/bin/hotreload.ts
|
|
13
|
+
import { createHash } from "crypto";
|
|
14
|
+
var HotReloadServer = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.clients = /* @__PURE__ */ new Set();
|
|
17
|
+
}
|
|
18
|
+
handleUpgrade(request, socket, head) {
|
|
19
|
+
if (request.url !== "/__tokvista_ws") {
|
|
20
|
+
socket.destroy();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const key = request.headers["sec-websocket-key"];
|
|
24
|
+
if (!key) {
|
|
25
|
+
socket.destroy();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const accept = createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
|
|
29
|
+
socket.write(
|
|
30
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
31
|
+
Upgrade: websocket\r
|
|
32
|
+
Connection: Upgrade\r
|
|
33
|
+
Sec-WebSocket-Accept: ${accept}\r
|
|
34
|
+
\r
|
|
35
|
+
`
|
|
36
|
+
);
|
|
37
|
+
const client = {
|
|
38
|
+
socket,
|
|
39
|
+
send: (data) => {
|
|
40
|
+
const buffer = Buffer.from(data);
|
|
41
|
+
const frame = Buffer.allocUnsafe(2 + buffer.length);
|
|
42
|
+
frame[0] = 129;
|
|
43
|
+
frame[1] = buffer.length;
|
|
44
|
+
buffer.copy(frame, 2);
|
|
45
|
+
socket.write(frame);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
this.clients.add(client);
|
|
49
|
+
socket.on("close", () => this.clients.delete(client));
|
|
50
|
+
}
|
|
51
|
+
reload() {
|
|
52
|
+
for (const client of this.clients) {
|
|
53
|
+
client.send("reload");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
close() {
|
|
57
|
+
for (const client of this.clients) {
|
|
58
|
+
client.socket.destroy();
|
|
59
|
+
}
|
|
60
|
+
this.clients.clear();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/bin/watcher.ts
|
|
65
|
+
import { watch } from "fs";
|
|
66
|
+
function watchFile(filePath, onChange) {
|
|
67
|
+
let debounce = null;
|
|
68
|
+
return watch(filePath, () => {
|
|
69
|
+
if (debounce) clearTimeout(debounce);
|
|
70
|
+
debounce = setTimeout(onChange, 100);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/bin/tokvista.ts
|
|
11
75
|
var DEFAULT_PORT = 3e3;
|
|
12
76
|
var MAX_PORT_ATTEMPTS = 25;
|
|
13
77
|
var SHUTDOWN_TIMEOUT_MS = 1500;
|
|
@@ -52,6 +116,7 @@ function parseServeArgs(args) {
|
|
|
52
116
|
let configPathArg;
|
|
53
117
|
let port = DEFAULT_PORT;
|
|
54
118
|
let openBrowser2 = true;
|
|
119
|
+
let watch2 = true;
|
|
55
120
|
for (let index = 0; index < args.length; index += 1) {
|
|
56
121
|
const arg = args[index];
|
|
57
122
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -62,6 +127,10 @@ function parseServeArgs(args) {
|
|
|
62
127
|
openBrowser2 = false;
|
|
63
128
|
continue;
|
|
64
129
|
}
|
|
130
|
+
if (arg === "--no-watch") {
|
|
131
|
+
watch2 = false;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
65
134
|
if (arg === "--port" || arg === "-p") {
|
|
66
135
|
const next = args[index + 1];
|
|
67
136
|
if (!next) {
|
|
@@ -96,7 +165,7 @@ function parseServeArgs(args) {
|
|
|
96
165
|
}
|
|
97
166
|
tokenFileArg = arg;
|
|
98
167
|
}
|
|
99
|
-
return { command: "serve", tokenFileArg, configPathArg, port, openBrowser: openBrowser2 };
|
|
168
|
+
return { command: "serve", tokenFileArg, configPathArg, port, openBrowser: openBrowser2, watch: watch2 };
|
|
100
169
|
}
|
|
101
170
|
function parseInitArgs(args) {
|
|
102
171
|
let force = false;
|
|
@@ -176,6 +245,17 @@ function askRawQuestion(rl, prompt) {
|
|
|
176
245
|
rl.question(prompt, (answer) => resolve(answer.trim()));
|
|
177
246
|
});
|
|
178
247
|
}
|
|
248
|
+
function createCompleter(cwd) {
|
|
249
|
+
return (line) => {
|
|
250
|
+
try {
|
|
251
|
+
const files = readdirSync(cwd);
|
|
252
|
+
const hits = files.filter((f) => f.startsWith(line));
|
|
253
|
+
return [hits.length ? hits : files, line];
|
|
254
|
+
} catch {
|
|
255
|
+
return [[], line];
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
179
259
|
async function askWithDefault(rl, label, defaultValue) {
|
|
180
260
|
const suffix = defaultValue != null && defaultValue !== "" ? ` [${defaultValue}]` : "";
|
|
181
261
|
const answer = await askRawQuestion(rl, `${label}${suffix}: `);
|
|
@@ -257,7 +337,12 @@ async function buildInitConfigFromPrompt(cwd, askPreviewQuestion) {
|
|
|
257
337
|
}
|
|
258
338
|
console.log("TokVista init");
|
|
259
339
|
console.log("Press Enter to accept defaults.\n");
|
|
260
|
-
const rl = createInterface({
|
|
340
|
+
const rl = createInterface({
|
|
341
|
+
input: process.stdin,
|
|
342
|
+
output: process.stdout,
|
|
343
|
+
completer: createCompleter(cwd),
|
|
344
|
+
tabSize: 2
|
|
345
|
+
});
|
|
261
346
|
try {
|
|
262
347
|
const title = await askWithDefault(rl, "Title", defaults.title);
|
|
263
348
|
const subtitle = await askWithDefault(rl, "Subtitle", defaults.subtitle);
|
|
@@ -351,7 +436,8 @@ async function runInitCommand(cwd, options) {
|
|
|
351
436
|
tokenFileArg: void 0,
|
|
352
437
|
configPathArg: configPath,
|
|
353
438
|
port: options.port,
|
|
354
|
-
openBrowser: options.openBrowser
|
|
439
|
+
openBrowser: options.openBrowser,
|
|
440
|
+
watch: true
|
|
355
441
|
};
|
|
356
442
|
}
|
|
357
443
|
function serializeForInlineScript(value) {
|
|
@@ -528,12 +614,13 @@ async function buildRuntimeConfig(config, configPath, cwd) {
|
|
|
528
614
|
...typeof config.showSearch === "boolean" ? { showSearch: config.showSearch } : {}
|
|
529
615
|
};
|
|
530
616
|
}
|
|
531
|
-
function buildHtml(tokens, runtimeConfig, css, appBundle) {
|
|
617
|
+
function buildHtml(tokens, runtimeConfig, css, appBundle, enableHotReload) {
|
|
532
618
|
const serializedTokens = serializeForInlineScript(tokens);
|
|
533
619
|
const serializedConfig = serializeForInlineScript(runtimeConfig);
|
|
534
620
|
const safeCss = escapeInlineTag(css, "style");
|
|
535
621
|
const safeAppBundle = escapeInlineTag(appBundle, "script");
|
|
536
622
|
const pageTitle = runtimeConfig.title || "TokVista";
|
|
623
|
+
const hotReloadScript = enableHotReload ? `<script>(function(){const ws=new WebSocket('ws://'+location.host+'/__tokvista_ws');ws.onmessage=()=>location.reload();ws.onerror=()=>setTimeout(()=>location.reload(),1000);})();</script>` : "";
|
|
537
624
|
return `<!doctype html>
|
|
538
625
|
<html lang="en">
|
|
539
626
|
<head>
|
|
@@ -547,6 +634,7 @@ function buildHtml(tokens, runtimeConfig, css, appBundle) {
|
|
|
547
634
|
<script>window.__TOKVISTA_TOKENS__ = ${serializedTokens};</script>
|
|
548
635
|
<script>window.__TOKVISTA_CONFIG__ = ${serializedConfig};</script>
|
|
549
636
|
<script type="module">${safeAppBundle}</script>
|
|
637
|
+
${hotReloadScript}
|
|
550
638
|
</body>
|
|
551
639
|
</html>`;
|
|
552
640
|
}
|
|
@@ -555,7 +643,7 @@ function resolveDistAsset(relativePath) {
|
|
|
555
643
|
const binDir = path.dirname(currentFilePath);
|
|
556
644
|
return path.resolve(binDir, "..", relativePath);
|
|
557
645
|
}
|
|
558
|
-
function handleRequest(request, response,
|
|
646
|
+
function handleRequest(request, response, getHtml) {
|
|
559
647
|
const requestUrl = request.url ?? "/";
|
|
560
648
|
const pathname = requestUrl.split("?")[0];
|
|
561
649
|
if (pathname === "/" || pathname === "") {
|
|
@@ -563,7 +651,7 @@ function handleRequest(request, response, html) {
|
|
|
563
651
|
"content-type": "text/html; charset=utf-8",
|
|
564
652
|
"cache-control": "no-store"
|
|
565
653
|
});
|
|
566
|
-
response.end(
|
|
654
|
+
response.end(getHtml());
|
|
567
655
|
return;
|
|
568
656
|
}
|
|
569
657
|
if (pathname === "/favicon.ico") {
|
|
@@ -577,11 +665,11 @@ function handleRequest(request, response, html) {
|
|
|
577
665
|
function isPortInUse(error) {
|
|
578
666
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
579
667
|
}
|
|
580
|
-
async function startServer(
|
|
668
|
+
async function startServer(getHtml, preferredPort) {
|
|
581
669
|
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt += 1) {
|
|
582
670
|
const port = preferredPort + attempt;
|
|
583
671
|
const server = createServer(
|
|
584
|
-
(request, response) => handleRequest(request, response,
|
|
672
|
+
(request, response) => handleRequest(request, response, getHtml)
|
|
585
673
|
);
|
|
586
674
|
try {
|
|
587
675
|
await new Promise((resolve, reject) => {
|
|
@@ -637,27 +725,48 @@ async function runServeCommand(cwd, options) {
|
|
|
637
725
|
throw new Error(`Token file not found: ${resolvedTokenPath}`);
|
|
638
726
|
}
|
|
639
727
|
const runtimeConfig = await buildRuntimeConfig(config, configPath, cwd);
|
|
640
|
-
const [
|
|
641
|
-
readTokens(resolvedTokenPath),
|
|
728
|
+
const [css, appBundle] = await Promise.all([
|
|
642
729
|
readFile(resolveDistAsset("styles.css"), "utf8"),
|
|
643
730
|
readFile(resolveDistAsset("cli/browser.js"), "utf8")
|
|
644
731
|
]);
|
|
645
|
-
|
|
646
|
-
const
|
|
732
|
+
let cachedTokens = await readTokens(resolvedTokenPath);
|
|
733
|
+
const getHtml = () => buildHtml(cachedTokens, runtimeConfig, css, appBundle, options.watch);
|
|
734
|
+
const { server, port } = await startServer(getHtml, options.port);
|
|
647
735
|
const openSockets = /* @__PURE__ */ new Set();
|
|
648
736
|
let isShuttingDown = false;
|
|
737
|
+
const hotReload = options.watch ? new HotReloadServer() : null;
|
|
649
738
|
server.on("connection", (socket) => {
|
|
650
739
|
openSockets.add(socket);
|
|
651
740
|
socket.on("close", () => {
|
|
652
741
|
openSockets.delete(socket);
|
|
653
742
|
});
|
|
654
743
|
});
|
|
744
|
+
if (hotReload) {
|
|
745
|
+
server.on("upgrade", (req, socket, head) => hotReload.handleUpgrade(req, socket, head));
|
|
746
|
+
}
|
|
655
747
|
const url = `http://localhost:${port}`;
|
|
656
748
|
console.log(`TokVista running at ${url}`);
|
|
657
749
|
console.log(`Using tokens: ${resolvedTokenPath}`);
|
|
658
750
|
if (configPath) {
|
|
659
751
|
console.log(`Using config: ${configPath}`);
|
|
660
752
|
}
|
|
753
|
+
if (options.watch) {
|
|
754
|
+
console.log("Watching for changes...");
|
|
755
|
+
const watcher = watchFile(resolvedTokenPath, async () => {
|
|
756
|
+
try {
|
|
757
|
+
cachedTokens = await readTokens(resolvedTokenPath);
|
|
758
|
+
hotReload == null ? void 0 : hotReload.reload();
|
|
759
|
+
console.log("Tokens reloaded");
|
|
760
|
+
} catch (error) {
|
|
761
|
+
console.error("Failed to reload tokens:", error.message);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
const cleanup = () => {
|
|
765
|
+
watcher.close();
|
|
766
|
+
hotReload == null ? void 0 : hotReload.close();
|
|
767
|
+
};
|
|
768
|
+
process.on("exit", cleanup);
|
|
769
|
+
}
|
|
661
770
|
if (options.openBrowser) {
|
|
662
771
|
try {
|
|
663
772
|
await openBrowser(url);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokvista",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Interactive visual documentation for design tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"url": "https://www.linkedin.com/in/nibin-kurian"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
|
-
"node": ">=
|
|
83
|
+
"node": ">=20.0.0"
|
|
84
84
|
},
|
|
85
85
|
"license": "MIT",
|
|
86
86
|
"peerDependencies": {
|