@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.
- package/README.md +41 -0
- package/dist-plugin/index.cjs +444 -0
- package/dist-plugin/index.d.cts +12 -0
- package/dist-plugin/index.d.ts +12 -0
- package/dist-plugin/index.js +407 -0
- package/package.json +86 -0
- package/src/components/Badge.svelte +82 -0
- package/src/components/Button.svelte +333 -0
- package/src/components/Card.svelte +83 -0
- package/src/components/CollapsibleSection.svelte +82 -0
- package/src/components/DetailNav.svelte +78 -0
- package/src/components/Dialog.svelte +269 -0
- package/src/components/InlineEditActions.svelte +73 -0
- package/src/components/Notification.svelte +308 -0
- package/src/components/ProgressBar.svelte +99 -0
- package/src/components/RadioButton.svelte +87 -0
- package/src/components/SectionDivider.svelte +121 -0
- package/src/components/TabBar.svelte +92 -0
- package/src/components/Toggle.svelte +86 -0
- package/src/components/Tooltip.svelte +64 -0
- package/src/lib/ColumnsOverlay.svelte +120 -0
- package/src/lib/LiveEditorOverlay.svelte +467 -0
- package/src/lib/columnsOverlay.ts +26 -0
- package/src/lib/cssVarSync.ts +72 -0
- package/src/lib/editorConfig.ts +9 -0
- package/src/lib/editorConfigStore.ts +14 -0
- package/src/lib/index.ts +51 -0
- package/src/lib/oklch.ts +129 -0
- package/src/lib/pageSource.ts +6 -0
- package/src/lib/tokenInit.ts +29 -0
- package/src/lib/tokenService.ts +144 -0
- package/src/lib/tokenTypes.ts +45 -0
- package/src/pages/Admin.svelte +100 -0
- package/src/pages/ShowcasePage.svelte +146 -0
- package/src/showcase/BackupBrowser.svelte +617 -0
- package/src/showcase/BezierCurveEditor.svelte +648 -0
- package/src/showcase/ColorEditPanel.svelte +498 -0
- package/src/showcase/ComponentsTab.svelte +107 -0
- package/src/showcase/EditorDialog.svelte +137 -0
- package/src/showcase/PaletteEditor.svelte +2579 -0
- package/src/showcase/PaletteSelector.svelte +627 -0
- package/src/showcase/SurfacesTab.svelte +409 -0
- package/src/showcase/TextTab.svelte +205 -0
- package/src/showcase/TokenFileManager.svelte +683 -0
- package/src/showcase/TokenMap.svelte +54 -0
- package/src/showcase/VariablesTab.svelte +2657 -0
- package/src/showcase/VisualsTab.svelte +233 -0
- package/src/showcase/curveEngine.ts +190 -0
- package/src/showcase/demos/BadgeDemo.svelte +58 -0
- package/src/showcase/demos/CardDemo.svelte +52 -0
- package/src/showcase/demos/ChoiceButtonsDemo.svelte +194 -0
- package/src/showcase/demos/CollapsibleSectionDemo.svelte +56 -0
- package/src/showcase/demos/DialogDemo.svelte +42 -0
- package/src/showcase/demos/InlineEditActionsDemo.svelte +27 -0
- package/src/showcase/demos/NotificationDemo.svelte +149 -0
- package/src/showcase/demos/ProgressBarDemo.svelte +56 -0
- package/src/showcase/demos/RadioButtonDemo.svelte +58 -0
- package/src/showcase/demos/SectionDividerDemo.svelte +79 -0
- package/src/showcase/demos/StandardButtonsDemo.svelte +457 -0
- package/src/showcase/demos/TabBarDemo.svelte +60 -0
- package/src/showcase/demos/TooltipDemo.svelte +54 -0
- package/src/showcase/editor.css +93 -0
- package/src/showcase/index.ts +17 -0
- package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
- package/src/styles/fonts/Domine/OFL.txt +97 -0
- package/src/styles/fonts/Domine/README.txt +66 -0
- package/src/styles/fonts.css +18 -0
- 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 };
|