@motion-proto/live-tokens 0.1.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.
Files changed (68) hide show
  1. package/README.md +41 -0
  2. package/dist-plugin/index.cjs +444 -0
  3. package/dist-plugin/index.d.cts +12 -0
  4. package/dist-plugin/index.d.ts +12 -0
  5. package/dist-plugin/index.js +407 -0
  6. package/package.json +86 -0
  7. package/src/components/Badge.svelte +82 -0
  8. package/src/components/Button.svelte +333 -0
  9. package/src/components/Card.svelte +83 -0
  10. package/src/components/CollapsibleSection.svelte +82 -0
  11. package/src/components/DetailNav.svelte +78 -0
  12. package/src/components/Dialog.svelte +269 -0
  13. package/src/components/InlineEditActions.svelte +73 -0
  14. package/src/components/Notification.svelte +308 -0
  15. package/src/components/ProgressBar.svelte +99 -0
  16. package/src/components/RadioButton.svelte +87 -0
  17. package/src/components/SectionDivider.svelte +121 -0
  18. package/src/components/TabBar.svelte +92 -0
  19. package/src/components/Toggle.svelte +86 -0
  20. package/src/components/Tooltip.svelte +64 -0
  21. package/src/lib/ColumnsOverlay.svelte +120 -0
  22. package/src/lib/LiveEditorOverlay.svelte +467 -0
  23. package/src/lib/columnsOverlay.ts +26 -0
  24. package/src/lib/cssVarSync.ts +72 -0
  25. package/src/lib/editorConfig.ts +9 -0
  26. package/src/lib/editorConfigStore.ts +14 -0
  27. package/src/lib/index.ts +51 -0
  28. package/src/lib/oklch.ts +129 -0
  29. package/src/lib/pageSource.ts +6 -0
  30. package/src/lib/tokenInit.ts +29 -0
  31. package/src/lib/tokenService.ts +144 -0
  32. package/src/lib/tokenTypes.ts +45 -0
  33. package/src/pages/Admin.svelte +100 -0
  34. package/src/pages/ShowcasePage.svelte +146 -0
  35. package/src/showcase/BackupBrowser.svelte +617 -0
  36. package/src/showcase/BezierCurveEditor.svelte +648 -0
  37. package/src/showcase/ColorEditPanel.svelte +498 -0
  38. package/src/showcase/ComponentsTab.svelte +107 -0
  39. package/src/showcase/EditorDialog.svelte +137 -0
  40. package/src/showcase/PaletteEditor.svelte +2579 -0
  41. package/src/showcase/PaletteSelector.svelte +627 -0
  42. package/src/showcase/SurfacesTab.svelte +409 -0
  43. package/src/showcase/TextTab.svelte +205 -0
  44. package/src/showcase/TokenFileManager.svelte +683 -0
  45. package/src/showcase/TokenMap.svelte +54 -0
  46. package/src/showcase/VariablesTab.svelte +2657 -0
  47. package/src/showcase/VisualsTab.svelte +233 -0
  48. package/src/showcase/curveEngine.ts +190 -0
  49. package/src/showcase/demos/BadgeDemo.svelte +58 -0
  50. package/src/showcase/demos/CardDemo.svelte +52 -0
  51. package/src/showcase/demos/ChoiceButtonsDemo.svelte +194 -0
  52. package/src/showcase/demos/CollapsibleSectionDemo.svelte +56 -0
  53. package/src/showcase/demos/DialogDemo.svelte +42 -0
  54. package/src/showcase/demos/InlineEditActionsDemo.svelte +27 -0
  55. package/src/showcase/demos/NotificationDemo.svelte +149 -0
  56. package/src/showcase/demos/ProgressBarDemo.svelte +56 -0
  57. package/src/showcase/demos/RadioButtonDemo.svelte +58 -0
  58. package/src/showcase/demos/SectionDividerDemo.svelte +79 -0
  59. package/src/showcase/demos/StandardButtonsDemo.svelte +457 -0
  60. package/src/showcase/demos/TabBarDemo.svelte +60 -0
  61. package/src/showcase/demos/TooltipDemo.svelte +54 -0
  62. package/src/showcase/editor.css +93 -0
  63. package/src/showcase/index.ts +17 -0
  64. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  65. package/src/styles/fonts/Domine/OFL.txt +97 -0
  66. package/src/styles/fonts/Domine/README.txt +66 -0
  67. package/src/styles/fonts.css +18 -0
  68. package/src/styles/form-controls.css +190 -0
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Live Tokens
2
+
3
+ A starter Svelte + Vite site with a full design-token system and an in-browser editor.
4
+
5
+ ## What you get
6
+
7
+ - **Design-token admin panel** at `/admin` (dev-only). Edit colors, typography, spacing, radii, shadows, and motion, and see the whole site update live.
8
+ - **Live editor overlay** pinned to the top-right of every page in dev. Opens the token editor in a side panel or floating window without leaving the page you're styling.
9
+ - **Page Source** button — jumps straight to the current page's Svelte file in VS Code (`vscode://` link).
10
+ - **Component library** at `/components`: Button, Card, Dialog, Badge, Tabs, Tooltip, Toggle, and more.
11
+ - **Token persistence**: each saved palette is a JSON file in `tokens/`. The active palette syncs into `src/styles/variables.css` on save, so production builds ship pure CSS.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ npm install
17
+ npm run dev
18
+ ```
19
+
20
+ Open http://localhost:5173. The editor overlay sits in the top-right corner. Click **Editor** to expand it, or hit **Fullscreen** to jump to `/admin`.
21
+
22
+ ## Layout
23
+
24
+ - `src/pages/Landing.svelte` — the welcome page (replace this with your site)
25
+ - `src/pages/Admin.svelte` — design-system editor
26
+ - `src/pages/ShowcasePage.svelte` — component showcase
27
+ - `src/components/` — the reusable components
28
+ - `src/showcase/` — the editor UI (tabs, palette editors, curve editor, backup browser)
29
+ - `src/lib/` — the overlay, router glue, token persistence, and color helpers
30
+ - `src/styles/variables.css` — the generated CSS variables (this is the source of truth at runtime)
31
+ - `tokens/` — persisted palette files; `_active.json` selects the palette loaded on dev, `_production.json` selects the palette synced to CSS
32
+
33
+ ## How the editor ships changes to prod
34
+
35
+ 1. Edit in `/admin` → the overlay writes to the active JSON file in `tokens/`.
36
+ 2. When you promote a palette to "production", its variables are written into `src/styles/variables.css` and backed up under `src/styles/_backups/`.
37
+ 3. `npm run build` bundles that CSS file as-is. No token-editor code or JSON lookups in the prod bundle.
38
+
39
+ ## License
40
+
41
+ MIT. Originally extracted from RuneGoblin.
@@ -0,0 +1,444 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/vite-plugin/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ tokenFileApi: () => tokenFileApi
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/vite-plugin/tokenFileApi.ts
38
+ var import_fs = __toESM(require("fs"), 1);
39
+ var import_path = __toESM(require("path"), 1);
40
+ function tokenFileApi(opts) {
41
+ const TOKENS_DIR = import_path.default.resolve(opts.tokensDir);
42
+ const CSS_PATH = import_path.default.resolve(opts.variablesCssPath);
43
+ const TOKENS_BACKUP_DIR = opts.tokensBackupDir ? import_path.default.resolve(opts.tokensBackupDir) : import_path.default.join(TOKENS_DIR, "_backups");
44
+ const CSS_BACKUP_DIR = opts.cssBackupDir ? import_path.default.resolve(opts.cssBackupDir) : import_path.default.join(import_path.default.dirname(CSS_PATH), "_backups");
45
+ const API_BASE = opts.apiBase ?? "/api";
46
+ const ACTIVE_FILE = import_path.default.join(TOKENS_DIR, "_active.json");
47
+ const PRODUCTION_FILE = import_path.default.join(TOKENS_DIR, "_production.json");
48
+ function ensureTokensDir() {
49
+ if (!import_fs.default.existsSync(TOKENS_DIR)) {
50
+ import_fs.default.mkdirSync(TOKENS_DIR, { recursive: true });
51
+ }
52
+ if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, "default.json"))) {
53
+ const defaultTokens = {
54
+ name: "Default",
55
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
56
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
+ editorConfigs: {},
58
+ cssVariables: {}
59
+ };
60
+ import_fs.default.writeFileSync(import_path.default.join(TOKENS_DIR, "default.json"), JSON.stringify(defaultTokens, null, 2));
61
+ }
62
+ if (!import_fs.default.existsSync(ACTIVE_FILE)) {
63
+ import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
64
+ }
65
+ if (!import_fs.default.existsSync(PRODUCTION_FILE)) {
66
+ const activeFile = getActiveFileName();
67
+ import_fs.default.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: activeFile }));
68
+ }
69
+ }
70
+ function backupTokenFile(filePath, fileName) {
71
+ if (!import_fs.default.existsSync(filePath)) return;
72
+ if (!import_fs.default.existsSync(TOKENS_BACKUP_DIR)) {
73
+ import_fs.default.mkdirSync(TOKENS_BACKUP_DIR, { recursive: true });
74
+ }
75
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
76
+ const backupPath = import_path.default.join(TOKENS_BACKUP_DIR, `${fileName}_${timestamp}.json`);
77
+ import_fs.default.copyFileSync(filePath, backupPath);
78
+ const allBackups = import_fs.default.readdirSync(TOKENS_BACKUP_DIR).filter((f) => f.startsWith(`${fileName}_`) && f.endsWith(".json")).sort().reverse();
79
+ for (const old of allBackups.slice(10)) {
80
+ import_fs.default.unlinkSync(import_path.default.join(TOKENS_BACKUP_DIR, old));
81
+ }
82
+ }
83
+ function backupCssFile() {
84
+ if (!import_fs.default.existsSync(CSS_PATH)) return;
85
+ if (!import_fs.default.existsSync(CSS_BACKUP_DIR)) {
86
+ import_fs.default.mkdirSync(CSS_BACKUP_DIR, { recursive: true });
87
+ }
88
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
89
+ const backupPath = import_path.default.join(CSS_BACKUP_DIR, `variables_${timestamp}.css`);
90
+ import_fs.default.copyFileSync(CSS_PATH, backupPath);
91
+ const allBackups = import_fs.default.readdirSync(CSS_BACKUP_DIR).filter((f) => f.startsWith("variables_") && f.endsWith(".css")).sort().reverse();
92
+ for (const old of allBackups.slice(10)) {
93
+ import_fs.default.unlinkSync(import_path.default.join(CSS_BACKUP_DIR, old));
94
+ }
95
+ }
96
+ function getActiveFileName() {
97
+ try {
98
+ const data = JSON.parse(import_fs.default.readFileSync(ACTIVE_FILE, "utf-8"));
99
+ return data.activeFile || "default";
100
+ } catch {
101
+ return "default";
102
+ }
103
+ }
104
+ function getProductionFileName() {
105
+ try {
106
+ const data = JSON.parse(import_fs.default.readFileSync(PRODUCTION_FILE, "utf-8"));
107
+ return data.productionFile || "default";
108
+ } catch {
109
+ return "default";
110
+ }
111
+ }
112
+ function readBody(req) {
113
+ return new Promise((resolve, reject) => {
114
+ let body = "";
115
+ req.on("data", (chunk) => body += chunk);
116
+ req.on("end", () => resolve(body));
117
+ req.on("error", reject);
118
+ });
119
+ }
120
+ function jsonResponse(res, status, data) {
121
+ res.statusCode = status;
122
+ res.setHeader("Content-Type", "application/json");
123
+ res.end(JSON.stringify(data));
124
+ }
125
+ function sanitize(name) {
126
+ return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
127
+ }
128
+ function syncTokensToCss(fileName) {
129
+ const tokenPath = import_path.default.join(TOKENS_DIR, `${fileName}.json`);
130
+ if (!import_fs.default.existsSync(tokenPath)) return;
131
+ const tokenData = JSON.parse(import_fs.default.readFileSync(tokenPath, "utf-8"));
132
+ const cssVars = tokenData.cssVariables || {};
133
+ if (Object.keys(cssVars).length === 0) return;
134
+ const cssContent = import_fs.default.readFileSync(CSS_PATH, "utf-8");
135
+ const remaining = new Set(Object.keys(cssVars));
136
+ const updatedContent = cssContent.replace(
137
+ /^(\s*)(--[\w-]+):\s*(.+);/gm,
138
+ (_match, indent, varName, oldValue) => {
139
+ if (cssVars[varName] !== void 0) {
140
+ remaining.delete(varName);
141
+ return `${indent}${varName}: ${cssVars[varName]};`;
142
+ }
143
+ return `${indent}${varName}: ${oldValue.trim()};`;
144
+ }
145
+ );
146
+ let finalContent = updatedContent;
147
+ if (remaining.size > 0) {
148
+ const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
149
+ finalContent = finalContent.replace(
150
+ /\n\}(\s*)$/,
151
+ `
152
+
153
+ /* Token additions */
154
+ ${newVars}
155
+ }$1`
156
+ );
157
+ }
158
+ backupCssFile();
159
+ import_fs.default.writeFileSync(CSS_PATH, finalContent);
160
+ console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into variables.css`);
161
+ }
162
+ const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
163
+ const TOKENS_ROUTE = `${API_BASE}/tokens`;
164
+ const TOKENS_ACTIVE_ROUTE = `${API_BASE}/tokens/active`;
165
+ const TOKENS_PRODUCTION_ROUTE = `${API_BASE}/tokens/production`;
166
+ const BACKUPS_ROUTE = `${API_BASE}/backups`;
167
+ const CURRENT_CSS_ROUTE = `${API_BASE}/current-css`;
168
+ const TOKEN_BY_NAME_REGEX = new RegExp(`^${escapedBase}/tokens/([a-z0-9\\-_]+)$`);
169
+ const BACKUP_GET_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)$`);
170
+ const BACKUP_RESTORE_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)/restore$`);
171
+ return {
172
+ name: "token-file-api",
173
+ configureServer(server) {
174
+ ensureTokensDir();
175
+ server.middlewares.use(async (req, res, next) => {
176
+ const url = req.url || "";
177
+ if (url === TOKENS_ROUTE && req.method === "GET") {
178
+ try {
179
+ const activeFile = getActiveFileName();
180
+ const files = import_fs.default.readdirSync(TOKENS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
181
+ const filePath = import_path.default.join(TOKENS_DIR, f);
182
+ const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
183
+ const fileName = f.replace(".json", "");
184
+ return {
185
+ name: data.name || fileName,
186
+ fileName,
187
+ updatedAt: data.updatedAt || "",
188
+ isActive: fileName === activeFile
189
+ };
190
+ });
191
+ jsonResponse(res, 200, { files });
192
+ } catch (err) {
193
+ jsonResponse(res, 500, { error: err.message });
194
+ }
195
+ return;
196
+ }
197
+ if (url === TOKENS_ACTIVE_ROUTE && req.method === "GET") {
198
+ try {
199
+ const activeFile = getActiveFileName();
200
+ const filePath = import_path.default.join(TOKENS_DIR, `${activeFile}.json`);
201
+ if (!import_fs.default.existsSync(filePath)) {
202
+ jsonResponse(res, 404, { error: "Active token file not found" });
203
+ return;
204
+ }
205
+ const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
206
+ data._fileName = activeFile;
207
+ jsonResponse(res, 200, data);
208
+ } catch (err) {
209
+ jsonResponse(res, 500, { error: err.message });
210
+ }
211
+ return;
212
+ }
213
+ if (url === TOKENS_ACTIVE_ROUTE && req.method === "PUT") {
214
+ try {
215
+ const body = JSON.parse(await readBody(req));
216
+ const fileName = sanitize(body.name || "default");
217
+ if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`))) {
218
+ jsonResponse(res, 404, { error: "Token file not found" });
219
+ return;
220
+ }
221
+ import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: fileName }));
222
+ jsonResponse(res, 200, { ok: true, activeFile: fileName });
223
+ } catch (err) {
224
+ jsonResponse(res, 500, { error: err.message });
225
+ }
226
+ return;
227
+ }
228
+ if (url === TOKENS_PRODUCTION_ROUTE && req.method === "GET") {
229
+ try {
230
+ const prodFile = getProductionFileName();
231
+ const filePath = import_path.default.join(TOKENS_DIR, `${prodFile}.json`);
232
+ if (!import_fs.default.existsSync(filePath)) {
233
+ jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
234
+ return;
235
+ }
236
+ const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
237
+ jsonResponse(res, 200, {
238
+ fileName: prodFile,
239
+ name: data.name || prodFile,
240
+ updatedAt: data.updatedAt || "",
241
+ cssVariables: data.cssVariables || {}
242
+ });
243
+ } catch (err) {
244
+ jsonResponse(res, 500, { error: err.message });
245
+ }
246
+ return;
247
+ }
248
+ if (url === TOKENS_PRODUCTION_ROUTE && req.method === "PUT") {
249
+ try {
250
+ const body = JSON.parse(await readBody(req));
251
+ const fileName = sanitize(body.name || "default");
252
+ if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`))) {
253
+ jsonResponse(res, 404, { error: "Token file not found" });
254
+ return;
255
+ }
256
+ import_fs.default.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: fileName }));
257
+ syncTokensToCss(fileName);
258
+ const data = JSON.parse(import_fs.default.readFileSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`), "utf-8"));
259
+ jsonResponse(res, 200, {
260
+ ok: true,
261
+ fileName,
262
+ name: data.name || fileName,
263
+ updatedAt: data.updatedAt || ""
264
+ });
265
+ } catch (err) {
266
+ jsonResponse(res, 500, { error: err.message });
267
+ }
268
+ return;
269
+ }
270
+ if (url === BACKUPS_ROUTE && req.method === "GET") {
271
+ try {
272
+ let fileTimestampToISO2 = function(ts) {
273
+ return ts.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/, "T$1:$2:$3.$4Z");
274
+ };
275
+ var fileTimestampToISO = fileTimestampToISO2;
276
+ const backups = [];
277
+ if (import_fs.default.existsSync(TOKENS_BACKUP_DIR)) {
278
+ for (const f of import_fs.default.readdirSync(TOKENS_BACKUP_DIR)) {
279
+ if (!f.endsWith(".json")) continue;
280
+ const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.json$/);
281
+ if (!match2) continue;
282
+ const stat = import_fs.default.statSync(import_path.default.join(TOKENS_BACKUP_DIR, f));
283
+ backups.push({
284
+ type: "tokens",
285
+ file: f,
286
+ name: match2[1],
287
+ timestamp: fileTimestampToISO2(match2[2]),
288
+ size: stat.size
289
+ });
290
+ }
291
+ }
292
+ if (import_fs.default.existsSync(CSS_BACKUP_DIR)) {
293
+ for (const f of import_fs.default.readdirSync(CSS_BACKUP_DIR)) {
294
+ if (!f.endsWith(".css")) continue;
295
+ const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.css$/);
296
+ if (!match2) continue;
297
+ const stat = import_fs.default.statSync(import_path.default.join(CSS_BACKUP_DIR, f));
298
+ backups.push({
299
+ type: "css",
300
+ file: f,
301
+ name: match2[1],
302
+ timestamp: fileTimestampToISO2(match2[2]),
303
+ size: stat.size
304
+ });
305
+ }
306
+ }
307
+ backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
308
+ jsonResponse(res, 200, { backups });
309
+ } catch (err) {
310
+ jsonResponse(res, 500, { error: err.message });
311
+ }
312
+ return;
313
+ }
314
+ {
315
+ const restoreMatch = url.match(BACKUP_RESTORE_REGEX);
316
+ if (restoreMatch && req.method === "POST") {
317
+ const [, type, file] = restoreMatch;
318
+ try {
319
+ if (type === "css") {
320
+ const backupPath = import_path.default.join(CSS_BACKUP_DIR, decodeURIComponent(file));
321
+ if (!backupPath.startsWith(CSS_BACKUP_DIR) || !import_fs.default.existsSync(backupPath)) {
322
+ jsonResponse(res, 404, { error: "Backup not found" });
323
+ return;
324
+ }
325
+ backupCssFile();
326
+ import_fs.default.copyFileSync(backupPath, CSS_PATH);
327
+ jsonResponse(res, 200, { ok: true, restored: file });
328
+ } else {
329
+ const backupPath = import_path.default.join(TOKENS_BACKUP_DIR, decodeURIComponent(file));
330
+ if (!backupPath.startsWith(TOKENS_BACKUP_DIR) || !import_fs.default.existsSync(backupPath)) {
331
+ jsonResponse(res, 404, { error: "Backup not found" });
332
+ return;
333
+ }
334
+ const nameMatch = decodeURIComponent(file).match(/^(.+)_\d{4}-/);
335
+ if (!nameMatch) {
336
+ jsonResponse(res, 400, { error: "Cannot determine token file name from backup" });
337
+ return;
338
+ }
339
+ const tokenFilePath = import_path.default.join(TOKENS_DIR, `${nameMatch[1]}.json`);
340
+ backupTokenFile(tokenFilePath, nameMatch[1]);
341
+ import_fs.default.copyFileSync(backupPath, tokenFilePath);
342
+ jsonResponse(res, 200, { ok: true, restored: file });
343
+ }
344
+ } catch (err) {
345
+ jsonResponse(res, 500, { error: err.message });
346
+ }
347
+ return;
348
+ }
349
+ }
350
+ {
351
+ const backupMatch = url.match(BACKUP_GET_REGEX);
352
+ if (backupMatch && req.method === "GET") {
353
+ const [, type, file] = backupMatch;
354
+ const dir = type === "css" ? CSS_BACKUP_DIR : TOKENS_BACKUP_DIR;
355
+ const filePath = import_path.default.join(dir, decodeURIComponent(file));
356
+ if (!filePath.startsWith(dir) || !import_fs.default.existsSync(filePath)) {
357
+ jsonResponse(res, 404, { error: "Backup not found" });
358
+ return;
359
+ }
360
+ const content = import_fs.default.readFileSync(filePath, "utf-8");
361
+ jsonResponse(res, 200, { content, type, file });
362
+ return;
363
+ }
364
+ }
365
+ if (url === CURRENT_CSS_ROUTE && req.method === "GET") {
366
+ try {
367
+ const content = import_fs.default.readFileSync(CSS_PATH, "utf-8");
368
+ jsonResponse(res, 200, { content });
369
+ } catch (err) {
370
+ jsonResponse(res, 500, { error: err.message });
371
+ }
372
+ return;
373
+ }
374
+ const match = url.match(TOKEN_BY_NAME_REGEX);
375
+ if (match) {
376
+ const fileName = match[1];
377
+ const filePath = import_path.default.join(TOKENS_DIR, `${fileName}.json`);
378
+ if (req.method === "GET") {
379
+ try {
380
+ if (!import_fs.default.existsSync(filePath)) {
381
+ jsonResponse(res, 404, { error: "Not found" });
382
+ return;
383
+ }
384
+ const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
385
+ data._fileName = fileName;
386
+ jsonResponse(res, 200, data);
387
+ } catch (err) {
388
+ jsonResponse(res, 500, { error: err.message });
389
+ }
390
+ return;
391
+ }
392
+ if (req.method === "PUT") {
393
+ try {
394
+ const body = JSON.parse(await readBody(req));
395
+ body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
396
+ if (import_fs.default.existsSync(filePath)) {
397
+ try {
398
+ const existing = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
399
+ if (existing.createdAt) body.createdAt = existing.createdAt;
400
+ } catch {
401
+ }
402
+ }
403
+ if (!body.createdAt) body.createdAt = body.updatedAt;
404
+ backupTokenFile(filePath, fileName);
405
+ import_fs.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
406
+ if (fileName === getProductionFileName()) {
407
+ syncTokensToCss(fileName);
408
+ }
409
+ jsonResponse(res, 200, { ok: true, fileName });
410
+ } catch (err) {
411
+ jsonResponse(res, 500, { error: err.message });
412
+ }
413
+ return;
414
+ }
415
+ if (req.method === "DELETE") {
416
+ if (fileName === "default") {
417
+ jsonResponse(res, 403, { error: "Cannot delete the default token file" });
418
+ return;
419
+ }
420
+ try {
421
+ if (import_fs.default.existsSync(filePath)) {
422
+ import_fs.default.unlinkSync(filePath);
423
+ if (getActiveFileName() === fileName) {
424
+ import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
425
+ }
426
+ }
427
+ jsonResponse(res, 200, { ok: true });
428
+ } catch (err) {
429
+ jsonResponse(res, 500, { error: err.message });
430
+ }
431
+ return;
432
+ }
433
+ jsonResponse(res, 405, { error: "Method not allowed" });
434
+ return;
435
+ }
436
+ next();
437
+ });
438
+ }
439
+ };
440
+ }
441
+ // Annotate the CommonJS export names for ESM import in node:
442
+ 0 && (module.exports = {
443
+ tokenFileApi
444
+ });
@@ -0,0 +1,12 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface TokenFileApiOptions {
4
+ tokensDir: string;
5
+ variablesCssPath: string;
6
+ tokensBackupDir?: string;
7
+ cssBackupDir?: string;
8
+ apiBase?: string;
9
+ }
10
+ declare function tokenFileApi(opts: TokenFileApiOptions): Plugin;
11
+
12
+ export { type TokenFileApiOptions, tokenFileApi };
@@ -0,0 +1,12 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface TokenFileApiOptions {
4
+ tokensDir: string;
5
+ variablesCssPath: string;
6
+ tokensBackupDir?: string;
7
+ cssBackupDir?: string;
8
+ apiBase?: string;
9
+ }
10
+ declare function tokenFileApi(opts: TokenFileApiOptions): Plugin;
11
+
12
+ export { type TokenFileApiOptions, tokenFileApi };