@designtools/shadows 0.1.5 → 0.1.7
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/dist/cli.js +292 -193
- package/package.json +1 -1
- package/src/client/app.tsx +19 -3
- package/src/client/components/shadow-controls.tsx +9 -3
- package/src/client/components/shadow-editor-panel.tsx +31 -5
- package/src/client/components/shadow-list.tsx +95 -67
- package/src/client/components/shadow-overview.tsx +83 -78
- package/src/client/components/shadow-preview-settings.tsx +210 -0
- package/src/client/components/shadow-preview.tsx +5 -1
- package/src/client/dist/assets/index-CA2rtXvo.js +49 -0
- package/src/client/dist/index.html +1 -1
- package/src/client/dist/assets/index-DuqoHhIQ.js +0 -49
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../core/src/cli/bootstrap.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs3 from "fs";
|
|
5
|
+
import path3 from "path";
|
|
6
|
+
import process2 from "process";
|
|
6
7
|
|
|
7
8
|
// ../core/src/scanner/detect-framework.ts
|
|
8
9
|
import fs from "fs/promises";
|
|
@@ -91,14 +92,139 @@ async function findCssFiles(projectRoot) {
|
|
|
91
92
|
return found;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// ../core/src/scanner/detect-styling.ts
|
|
96
|
+
import fs2 from "fs/promises";
|
|
97
|
+
import path2 from "path";
|
|
98
|
+
async function detectStylingSystem(projectRoot, framework) {
|
|
99
|
+
const pkgPath = path2.join(projectRoot, "package.json");
|
|
100
|
+
let pkg = {};
|
|
101
|
+
try {
|
|
102
|
+
pkg = JSON.parse(await fs2.readFile(pkgPath, "utf-8"));
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
106
|
+
if (deps.tailwindcss) {
|
|
107
|
+
const version = deps.tailwindcss;
|
|
108
|
+
const isV4 = version.startsWith("^4") || version.startsWith("~4") || version.startsWith("4");
|
|
109
|
+
if (isV4) {
|
|
110
|
+
const hasDarkMode3 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
111
|
+
return {
|
|
112
|
+
type: "tailwind-v4",
|
|
113
|
+
cssFiles: framework.cssFiles,
|
|
114
|
+
scssFiles: [],
|
|
115
|
+
hasDarkMode: hasDarkMode3
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const configCandidates = [
|
|
119
|
+
"tailwind.config.ts",
|
|
120
|
+
"tailwind.config.js",
|
|
121
|
+
"tailwind.config.mjs",
|
|
122
|
+
"tailwind.config.cjs"
|
|
123
|
+
];
|
|
124
|
+
let configPath;
|
|
125
|
+
for (const candidate of configCandidates) {
|
|
126
|
+
try {
|
|
127
|
+
await fs2.access(path2.join(projectRoot, candidate));
|
|
128
|
+
configPath = candidate;
|
|
129
|
+
break;
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
134
|
+
return {
|
|
135
|
+
type: "tailwind-v3",
|
|
136
|
+
configPath,
|
|
137
|
+
cssFiles: framework.cssFiles,
|
|
138
|
+
scssFiles: [],
|
|
139
|
+
hasDarkMode: hasDarkMode2
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (deps.bootstrap) {
|
|
143
|
+
const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
144
|
+
const scssFiles = await findBootstrapScssFiles(projectRoot);
|
|
145
|
+
return {
|
|
146
|
+
type: "bootstrap",
|
|
147
|
+
cssFiles: framework.cssFiles,
|
|
148
|
+
scssFiles,
|
|
149
|
+
hasDarkMode: hasDarkMode2
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const hasDarkMode = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
153
|
+
const hasCustomProps = await checkCustomProperties(projectRoot, framework.cssFiles);
|
|
154
|
+
if (hasCustomProps) {
|
|
155
|
+
return {
|
|
156
|
+
type: "css-variables",
|
|
157
|
+
cssFiles: framework.cssFiles,
|
|
158
|
+
scssFiles: [],
|
|
159
|
+
hasDarkMode
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
type: framework.cssFiles.length > 0 ? "plain-css" : "unknown",
|
|
164
|
+
cssFiles: framework.cssFiles,
|
|
165
|
+
scssFiles: [],
|
|
166
|
+
hasDarkMode
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function checkDarkMode(projectRoot, cssFiles) {
|
|
170
|
+
for (const file of cssFiles) {
|
|
171
|
+
try {
|
|
172
|
+
const css = await fs2.readFile(path2.join(projectRoot, file), "utf-8");
|
|
173
|
+
if (css.includes(".dark") || css.includes('[data-theme="dark"]') || css.includes("prefers-color-scheme: dark")) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
async function checkCustomProperties(projectRoot, cssFiles) {
|
|
182
|
+
for (const file of cssFiles) {
|
|
183
|
+
try {
|
|
184
|
+
const css = await fs2.readFile(path2.join(projectRoot, file), "utf-8");
|
|
185
|
+
if (/--[\w-]+\s*:/.test(css)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
async function findBootstrapScssFiles(projectRoot) {
|
|
194
|
+
const candidates = [
|
|
195
|
+
"src/scss/_variables.scss",
|
|
196
|
+
"src/scss/_custom.scss",
|
|
197
|
+
"src/scss/custom.scss",
|
|
198
|
+
"src/styles/_variables.scss",
|
|
199
|
+
"src/styles/variables.scss",
|
|
200
|
+
"assets/scss/_variables.scss",
|
|
201
|
+
"scss/_variables.scss",
|
|
202
|
+
"styles/_variables.scss"
|
|
203
|
+
];
|
|
204
|
+
const found = [];
|
|
205
|
+
for (const candidate of candidates) {
|
|
206
|
+
try {
|
|
207
|
+
await fs2.access(path2.join(projectRoot, candidate));
|
|
208
|
+
found.push(candidate);
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return found;
|
|
213
|
+
}
|
|
214
|
+
|
|
94
215
|
// ../core/src/cli/bootstrap.ts
|
|
216
|
+
var originalEmitWarning = process2.emitWarning;
|
|
217
|
+
process2.emitWarning = ((warning, ...args) => {
|
|
218
|
+
if (typeof warning === "string" && warning.includes("util._extend")) return;
|
|
219
|
+
return originalEmitWarning.call(process2, warning, ...args);
|
|
220
|
+
});
|
|
95
221
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
96
222
|
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
97
223
|
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
98
224
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
99
225
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
100
226
|
async function bootstrap(config) {
|
|
101
|
-
const args =
|
|
227
|
+
const args = process2.argv.slice(2);
|
|
102
228
|
let targetPort = config.defaultTargetPort;
|
|
103
229
|
let toolPort = config.defaultToolPort;
|
|
104
230
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -111,16 +237,18 @@ async function bootstrap(config) {
|
|
|
111
237
|
i++;
|
|
112
238
|
}
|
|
113
239
|
}
|
|
114
|
-
const projectRoot =
|
|
240
|
+
const projectRoot = process2.cwd();
|
|
115
241
|
console.log("");
|
|
116
242
|
console.log(` ${bold(config.name)}`);
|
|
243
|
+
console.log(` ${dim(projectRoot)}`);
|
|
117
244
|
console.log("");
|
|
118
|
-
const pkgPath =
|
|
119
|
-
if (!
|
|
120
|
-
console.log(` ${red("\u2717")} No package.json found`);
|
|
121
|
-
console.log(` ${dim("Run this command from
|
|
245
|
+
const pkgPath = path3.join(projectRoot, "package.json");
|
|
246
|
+
if (!fs3.existsSync(pkgPath)) {
|
|
247
|
+
console.log(` ${red("\u2717")} No package.json found in ${projectRoot}`);
|
|
248
|
+
console.log(` ${dim("Run this command from the root of the app you want to edit.")}`);
|
|
249
|
+
console.log(` ${dim("All file reads and writes are scoped to this directory.")}`);
|
|
122
250
|
console.log("");
|
|
123
|
-
|
|
251
|
+
process2.exit(1);
|
|
124
252
|
}
|
|
125
253
|
const framework = await detectFramework(projectRoot);
|
|
126
254
|
const frameworkLabel = framework.name === "nextjs" ? "Next.js" : framework.name === "remix" ? "Remix" : framework.name === "vite" ? "Vite" : "Unknown";
|
|
@@ -142,6 +270,21 @@ async function bootstrap(config) {
|
|
|
142
270
|
} else {
|
|
143
271
|
console.log(` ${yellow("\u26A0")} CSS files ${dim("no CSS files found")}`);
|
|
144
272
|
}
|
|
273
|
+
const styling = await detectStylingSystem(projectRoot, framework);
|
|
274
|
+
const stylingLabels = {
|
|
275
|
+
"tailwind-v4": "Tailwind CSS v4",
|
|
276
|
+
"tailwind-v3": "Tailwind CSS v3",
|
|
277
|
+
"bootstrap": "Bootstrap",
|
|
278
|
+
"css-variables": "CSS Custom Properties",
|
|
279
|
+
"plain-css": "Plain CSS",
|
|
280
|
+
"unknown": "Unknown"
|
|
281
|
+
};
|
|
282
|
+
const stylingLabel = stylingLabels[styling.type];
|
|
283
|
+
if (styling.type !== "unknown") {
|
|
284
|
+
console.log(` ${green("\u2713")} Styling ${stylingLabel}`);
|
|
285
|
+
} else {
|
|
286
|
+
console.log(` ${yellow("\u26A0")} Styling ${dim("no styling system detected")}`);
|
|
287
|
+
}
|
|
145
288
|
if (config.extraChecks) {
|
|
146
289
|
const lines = await config.extraChecks(framework, projectRoot);
|
|
147
290
|
for (const line of lines) {
|
|
@@ -152,7 +295,7 @@ async function bootstrap(config) {
|
|
|
152
295
|
}
|
|
153
296
|
if (line.status === "error") {
|
|
154
297
|
console.log("");
|
|
155
|
-
|
|
298
|
+
process2.exit(1);
|
|
156
299
|
}
|
|
157
300
|
}
|
|
158
301
|
}
|
|
@@ -166,24 +309,27 @@ async function bootstrap(config) {
|
|
|
166
309
|
console.log(` ${dim("Start your dev server first, then run this command.")}`);
|
|
167
310
|
console.log(` ${dim(`Use --port to specify a different port.`)}`);
|
|
168
311
|
console.log("");
|
|
169
|
-
|
|
312
|
+
process2.exit(1);
|
|
170
313
|
}
|
|
171
314
|
console.log(` ${green("\u2713")} Tool http://localhost:${toolPort}`);
|
|
172
315
|
console.log("");
|
|
173
|
-
|
|
316
|
+
console.log(` ${dim("All file writes are scoped to:")} ${bold(projectRoot)}`);
|
|
317
|
+
console.log("");
|
|
318
|
+
return { framework, styling, targetPort, toolPort, projectRoot };
|
|
174
319
|
}
|
|
175
320
|
|
|
176
321
|
// src/server/index.ts
|
|
177
|
-
import
|
|
322
|
+
import path10 from "path";
|
|
178
323
|
import fs10 from "fs";
|
|
179
324
|
import { fileURLToPath } from "url";
|
|
325
|
+
import { createRequire } from "module";
|
|
180
326
|
|
|
181
327
|
// ../core/src/server/create-server.ts
|
|
182
328
|
import express from "express";
|
|
183
329
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
184
330
|
import httpProxy from "http-proxy";
|
|
185
331
|
import { WebSocketServer } from "ws";
|
|
186
|
-
import
|
|
332
|
+
import fs4 from "fs";
|
|
187
333
|
import zlib from "zlib";
|
|
188
334
|
import open from "open";
|
|
189
335
|
import { createServer as createViteServer } from "vite";
|
|
@@ -200,7 +346,7 @@ async function createToolServer(config) {
|
|
|
200
346
|
app.get(injectScriptUrl, async (_req, res) => {
|
|
201
347
|
try {
|
|
202
348
|
if (!compiledInjectCache) {
|
|
203
|
-
const raw =
|
|
349
|
+
const raw = fs4.readFileSync(config.injectScriptPath, "utf-8");
|
|
204
350
|
if (config.injectScriptPath.endsWith(".ts")) {
|
|
205
351
|
const result = await transform(raw, { loader: "ts" });
|
|
206
352
|
compiledInjectCache = result.code;
|
|
@@ -241,7 +387,11 @@ async function createToolServer(config) {
|
|
|
241
387
|
});
|
|
242
388
|
stream.on("end", () => {
|
|
243
389
|
const body = Buffer.concat(chunks).toString("utf-8");
|
|
244
|
-
|
|
390
|
+
let injected = body.replace(
|
|
391
|
+
"<head>",
|
|
392
|
+
`<head><base href="/proxy/">`
|
|
393
|
+
);
|
|
394
|
+
injected = injected.replace(
|
|
245
395
|
"</body>",
|
|
246
396
|
`<script src="${injectScriptUrl}"></script></body>`
|
|
247
397
|
);
|
|
@@ -308,14 +458,35 @@ async function createToolServer(config) {
|
|
|
308
458
|
return { app, wss, projectRoot };
|
|
309
459
|
}
|
|
310
460
|
|
|
461
|
+
// ../core/src/server/safe-path.ts
|
|
462
|
+
import path4 from "path";
|
|
463
|
+
function safePath(projectRoot, filePath) {
|
|
464
|
+
if (!filePath || typeof filePath !== "string") {
|
|
465
|
+
throw new Error("File path is required");
|
|
466
|
+
}
|
|
467
|
+
if (path4.isAbsolute(filePath)) {
|
|
468
|
+
throw new Error(
|
|
469
|
+
`Absolute paths are not allowed: "${filePath}". Paths must be relative to the project root.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const resolvedRoot = path4.resolve(projectRoot);
|
|
473
|
+
const resolvedPath = path4.resolve(resolvedRoot, filePath);
|
|
474
|
+
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(resolvedRoot + path4.sep)) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`Path "${filePath}" resolves outside the project directory. Refusing to write.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return resolvedPath;
|
|
480
|
+
}
|
|
481
|
+
|
|
311
482
|
// src/server/api/write-shadows.ts
|
|
312
483
|
import { Router } from "express";
|
|
313
|
-
import
|
|
314
|
-
import
|
|
484
|
+
import fs6 from "fs/promises";
|
|
485
|
+
import path6 from "path";
|
|
315
486
|
|
|
316
487
|
// src/server/scanner/presets/w3c-design-tokens.ts
|
|
317
|
-
import
|
|
318
|
-
import
|
|
488
|
+
import fs5 from "fs/promises";
|
|
489
|
+
import path5 from "path";
|
|
319
490
|
async function findDesignTokenFiles(projectRoot) {
|
|
320
491
|
const candidates = [
|
|
321
492
|
"tokens",
|
|
@@ -328,11 +499,11 @@ async function findDesignTokenFiles(projectRoot) {
|
|
|
328
499
|
const found = [];
|
|
329
500
|
for (const dir of candidates) {
|
|
330
501
|
try {
|
|
331
|
-
const fullDir =
|
|
332
|
-
const entries = await
|
|
502
|
+
const fullDir = path5.join(projectRoot, dir);
|
|
503
|
+
const entries = await fs5.readdir(fullDir);
|
|
333
504
|
for (const entry of entries) {
|
|
334
505
|
if (entry.endsWith(".tokens.json") || entry.endsWith(".tokens")) {
|
|
335
|
-
found.push(
|
|
506
|
+
found.push(path5.join(dir, entry));
|
|
336
507
|
}
|
|
337
508
|
}
|
|
338
509
|
} catch {
|
|
@@ -345,7 +516,7 @@ async function scanDesignTokenShadows(projectRoot, tokenFiles) {
|
|
|
345
516
|
const tokens = [];
|
|
346
517
|
for (const file of files) {
|
|
347
518
|
try {
|
|
348
|
-
const content = await
|
|
519
|
+
const content = await fs5.readFile(path5.join(projectRoot, file), "utf-8");
|
|
349
520
|
const parsed = JSON.parse(content);
|
|
350
521
|
extractShadowTokens(parsed, [], file, tokens);
|
|
351
522
|
} catch {
|
|
@@ -469,10 +640,10 @@ function buildDesignTokensJson(shadows) {
|
|
|
469
640
|
}
|
|
470
641
|
async function writeDesignTokensFile(filePath, tokens) {
|
|
471
642
|
const content = JSON.stringify(tokens, null, 2) + "\n";
|
|
472
|
-
await
|
|
643
|
+
await fs5.writeFile(filePath, content, "utf-8");
|
|
473
644
|
}
|
|
474
645
|
async function updateDesignTokenShadow(filePath, tokenPath, newCssValue) {
|
|
475
|
-
const content = await
|
|
646
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
476
647
|
const tokens = JSON.parse(content);
|
|
477
648
|
const pathParts = tokenPath.split(".");
|
|
478
649
|
let current = tokens;
|
|
@@ -485,7 +656,7 @@ async function updateDesignTokenShadow(filePath, tokenPath, newCssValue) {
|
|
|
485
656
|
throw new Error(`Token "${tokenPath}" not found`);
|
|
486
657
|
}
|
|
487
658
|
current[lastKey].$value = cssToW3cShadow(newCssValue);
|
|
488
|
-
await
|
|
659
|
+
await fs5.writeFile(filePath, JSON.stringify(tokens, null, 2) + "\n", "utf-8");
|
|
489
660
|
}
|
|
490
661
|
|
|
491
662
|
// src/server/api/write-shadows.ts
|
|
@@ -494,19 +665,19 @@ function createShadowsRouter(projectRoot) {
|
|
|
494
665
|
router.post("/", async (req, res) => {
|
|
495
666
|
try {
|
|
496
667
|
const { filePath, variableName, value, selector } = req.body;
|
|
497
|
-
const fullPath =
|
|
668
|
+
const fullPath = safePath(projectRoot, filePath);
|
|
498
669
|
if (selector === "scss") {
|
|
499
|
-
let scss = await
|
|
670
|
+
let scss = await fs6.readFile(fullPath, "utf-8");
|
|
500
671
|
scss = writeShadowToScss(scss, variableName, value);
|
|
501
|
-
await
|
|
672
|
+
await fs6.writeFile(fullPath, scss, "utf-8");
|
|
502
673
|
} else if (selector === "@theme") {
|
|
503
|
-
let css = await
|
|
674
|
+
let css = await fs6.readFile(fullPath, "utf-8");
|
|
504
675
|
css = writeShadowToTheme(css, variableName, value);
|
|
505
|
-
await
|
|
676
|
+
await fs6.writeFile(fullPath, css, "utf-8");
|
|
506
677
|
} else {
|
|
507
|
-
let css = await
|
|
678
|
+
let css = await fs6.readFile(fullPath, "utf-8");
|
|
508
679
|
css = writeShadowToSelector(css, selector, variableName, value);
|
|
509
|
-
await
|
|
680
|
+
await fs6.writeFile(fullPath, css, "utf-8");
|
|
510
681
|
}
|
|
511
682
|
res.json({ ok: true, filePath, variableName, value });
|
|
512
683
|
} catch (err) {
|
|
@@ -517,24 +688,24 @@ function createShadowsRouter(projectRoot) {
|
|
|
517
688
|
router.post("/create", async (req, res) => {
|
|
518
689
|
try {
|
|
519
690
|
const { filePath, variableName, value, selector } = req.body;
|
|
520
|
-
const fullPath =
|
|
691
|
+
const fullPath = safePath(projectRoot, filePath);
|
|
521
692
|
if (selector === "scss") {
|
|
522
693
|
let scss;
|
|
523
694
|
try {
|
|
524
|
-
scss = await
|
|
695
|
+
scss = await fs6.readFile(fullPath, "utf-8");
|
|
525
696
|
} catch {
|
|
526
697
|
scss = "";
|
|
527
698
|
}
|
|
528
699
|
scss = addShadowToScss(scss, variableName, value);
|
|
529
|
-
await
|
|
700
|
+
await fs6.writeFile(fullPath, scss, "utf-8");
|
|
530
701
|
} else if (selector === "@theme") {
|
|
531
|
-
let css = await
|
|
702
|
+
let css = await fs6.readFile(fullPath, "utf-8");
|
|
532
703
|
css = addShadowToTheme(css, variableName, value);
|
|
533
|
-
await
|
|
704
|
+
await fs6.writeFile(fullPath, css, "utf-8");
|
|
534
705
|
} else {
|
|
535
|
-
let css = await
|
|
706
|
+
let css = await fs6.readFile(fullPath, "utf-8");
|
|
536
707
|
css = addShadowToSelector(css, selector, variableName, value);
|
|
537
|
-
await
|
|
708
|
+
await fs6.writeFile(fullPath, css, "utf-8");
|
|
538
709
|
}
|
|
539
710
|
res.json({ ok: true, filePath, variableName, value });
|
|
540
711
|
} catch (err) {
|
|
@@ -545,7 +716,7 @@ function createShadowsRouter(projectRoot) {
|
|
|
545
716
|
router.post("/design-token", async (req, res) => {
|
|
546
717
|
try {
|
|
547
718
|
const { filePath, tokenPath, value } = req.body;
|
|
548
|
-
const fullPath =
|
|
719
|
+
const fullPath = safePath(projectRoot, filePath);
|
|
549
720
|
await updateDesignTokenShadow(fullPath, tokenPath, value);
|
|
550
721
|
res.json({ ok: true, filePath, tokenPath, value });
|
|
551
722
|
} catch (err) {
|
|
@@ -556,9 +727,9 @@ function createShadowsRouter(projectRoot) {
|
|
|
556
727
|
router.post("/export-tokens", async (req, res) => {
|
|
557
728
|
try {
|
|
558
729
|
const { filePath, shadows } = req.body;
|
|
559
|
-
const fullPath =
|
|
730
|
+
const fullPath = safePath(projectRoot, filePath);
|
|
560
731
|
const tokens = buildDesignTokensJson(shadows);
|
|
561
|
-
await
|
|
732
|
+
await fs6.mkdir(path6.dirname(fullPath), { recursive: true });
|
|
562
733
|
await writeDesignTokensFile(fullPath, tokens);
|
|
563
734
|
res.json({ ok: true, filePath, tokenCount: shadows.length });
|
|
564
735
|
} catch (err) {
|
|
@@ -586,14 +757,15 @@ function writeShadowToSelector(css, selector, variableName, newValue) {
|
|
|
586
757
|
let block = css.slice(openBrace + 1, blockEnd - 1);
|
|
587
758
|
const varEscaped = variableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
588
759
|
const tokenRegex = new RegExp(`(${varEscaped}\\s*:\\s*)([^;]+)(;)`, "g");
|
|
589
|
-
|
|
760
|
+
const original = block;
|
|
761
|
+
block = block.replace(tokenRegex, `$1${newValue}$3`);
|
|
762
|
+
if (block === original) {
|
|
590
763
|
throw new Error(`Variable "${variableName}" not found in "${selector}" block`);
|
|
591
764
|
}
|
|
592
|
-
block = block.replace(tokenRegex, `$1${newValue}$3`);
|
|
593
765
|
return css.slice(0, openBrace + 1) + block + css.slice(blockEnd - 1);
|
|
594
766
|
}
|
|
595
767
|
function writeShadowToTheme(css, variableName, newValue) {
|
|
596
|
-
const themeMatch = css.match(/@theme\s
|
|
768
|
+
const themeMatch = css.match(/@theme\s*(?:inline\s*)?\{/);
|
|
597
769
|
if (!themeMatch) {
|
|
598
770
|
throw new Error("No @theme block found in CSS file");
|
|
599
771
|
}
|
|
@@ -610,9 +782,9 @@ function writeShadowToTheme(css, variableName, newValue) {
|
|
|
610
782
|
let block = css.slice(openBrace + 1, blockEnd - 1);
|
|
611
783
|
const varEscaped = variableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
612
784
|
const tokenRegex = new RegExp(`(${varEscaped}\\s*:\\s*)([^;]+)(;)`, "g");
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
785
|
+
const original = block;
|
|
786
|
+
block = block.replace(tokenRegex, `$1${newValue}$3`);
|
|
787
|
+
if (block === original) {
|
|
616
788
|
block = block.trimEnd() + `
|
|
617
789
|
${variableName}: ${newValue};
|
|
618
790
|
`;
|
|
@@ -645,7 +817,7 @@ ${selector} {
|
|
|
645
817
|
return css.slice(0, openBrace + 1) + newBlock + css.slice(blockEnd - 1);
|
|
646
818
|
}
|
|
647
819
|
function addShadowToTheme(css, variableName, value) {
|
|
648
|
-
const themeMatch = css.match(/@theme\s
|
|
820
|
+
const themeMatch = css.match(/@theme\s*(?:inline\s*)?\{/);
|
|
649
821
|
if (!themeMatch) {
|
|
650
822
|
return css + `
|
|
651
823
|
@theme {
|
|
@@ -661,10 +833,11 @@ function writeShadowToScss(scss, variableName, newValue) {
|
|
|
661
833
|
`(${varEscaped}\\s*:\\s*)(.+?)(\\s*(?:!default)?\\s*;)`,
|
|
662
834
|
"g"
|
|
663
835
|
);
|
|
664
|
-
|
|
836
|
+
const result = scss.replace(regex, `$1${newValue}$3`);
|
|
837
|
+
if (result === scss) {
|
|
665
838
|
throw new Error(`Sass variable "${variableName}" not found in SCSS file`);
|
|
666
839
|
}
|
|
667
|
-
return
|
|
840
|
+
return result;
|
|
668
841
|
}
|
|
669
842
|
function addShadowToScss(scss, variableName, value) {
|
|
670
843
|
const line = `${variableName}: ${value};
|
|
@@ -676,15 +849,15 @@ function addShadowToScss(scss, variableName, value) {
|
|
|
676
849
|
import { Router as Router2 } from "express";
|
|
677
850
|
|
|
678
851
|
// ../core/src/scanner/scan-tokens.ts
|
|
679
|
-
import
|
|
680
|
-
import
|
|
852
|
+
import fs7 from "fs/promises";
|
|
853
|
+
import path7 from "path";
|
|
681
854
|
async function scanTokens(projectRoot, framework) {
|
|
682
855
|
if (framework.cssFiles.length === 0) {
|
|
683
856
|
return { tokens: [], cssFilePath: "", groups: {} };
|
|
684
857
|
}
|
|
685
858
|
const cssFilePath = framework.cssFiles[0];
|
|
686
|
-
const fullPath =
|
|
687
|
-
const css = await
|
|
859
|
+
const fullPath = path7.join(projectRoot, cssFilePath);
|
|
860
|
+
const css = await fs7.readFile(fullPath, "utf-8");
|
|
688
861
|
const rootTokens = parseBlock(css, ":root");
|
|
689
862
|
const darkTokens = parseBlock(css, ".dark");
|
|
690
863
|
const tokenMap = /* @__PURE__ */ new Map();
|
|
@@ -791,129 +964,9 @@ function detectColorFormat(value) {
|
|
|
791
964
|
return null;
|
|
792
965
|
}
|
|
793
966
|
|
|
794
|
-
// ../core/src/scanner/detect-styling.ts
|
|
795
|
-
import fs7 from "fs/promises";
|
|
796
|
-
import path6 from "path";
|
|
797
|
-
async function detectStylingSystem(projectRoot, framework) {
|
|
798
|
-
const pkgPath = path6.join(projectRoot, "package.json");
|
|
799
|
-
let pkg = {};
|
|
800
|
-
try {
|
|
801
|
-
pkg = JSON.parse(await fs7.readFile(pkgPath, "utf-8"));
|
|
802
|
-
} catch {
|
|
803
|
-
}
|
|
804
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
805
|
-
if (deps.tailwindcss) {
|
|
806
|
-
const version = deps.tailwindcss;
|
|
807
|
-
const isV4 = version.startsWith("^4") || version.startsWith("~4") || version.startsWith("4");
|
|
808
|
-
if (isV4) {
|
|
809
|
-
const hasDarkMode3 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
810
|
-
return {
|
|
811
|
-
type: "tailwind-v4",
|
|
812
|
-
cssFiles: framework.cssFiles,
|
|
813
|
-
scssFiles: [],
|
|
814
|
-
hasDarkMode: hasDarkMode3
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
const configCandidates = [
|
|
818
|
-
"tailwind.config.ts",
|
|
819
|
-
"tailwind.config.js",
|
|
820
|
-
"tailwind.config.mjs",
|
|
821
|
-
"tailwind.config.cjs"
|
|
822
|
-
];
|
|
823
|
-
let configPath;
|
|
824
|
-
for (const candidate of configCandidates) {
|
|
825
|
-
try {
|
|
826
|
-
await fs7.access(path6.join(projectRoot, candidate));
|
|
827
|
-
configPath = candidate;
|
|
828
|
-
break;
|
|
829
|
-
} catch {
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
833
|
-
return {
|
|
834
|
-
type: "tailwind-v3",
|
|
835
|
-
configPath,
|
|
836
|
-
cssFiles: framework.cssFiles,
|
|
837
|
-
scssFiles: [],
|
|
838
|
-
hasDarkMode: hasDarkMode2
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
if (deps.bootstrap) {
|
|
842
|
-
const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
843
|
-
const scssFiles = await findBootstrapScssFiles(projectRoot);
|
|
844
|
-
return {
|
|
845
|
-
type: "bootstrap",
|
|
846
|
-
cssFiles: framework.cssFiles,
|
|
847
|
-
scssFiles,
|
|
848
|
-
hasDarkMode: hasDarkMode2
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
const hasDarkMode = await checkDarkMode(projectRoot, framework.cssFiles);
|
|
852
|
-
const hasCustomProps = await checkCustomProperties(projectRoot, framework.cssFiles);
|
|
853
|
-
if (hasCustomProps) {
|
|
854
|
-
return {
|
|
855
|
-
type: "css-variables",
|
|
856
|
-
cssFiles: framework.cssFiles,
|
|
857
|
-
scssFiles: [],
|
|
858
|
-
hasDarkMode
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
return {
|
|
862
|
-
type: framework.cssFiles.length > 0 ? "plain-css" : "unknown",
|
|
863
|
-
cssFiles: framework.cssFiles,
|
|
864
|
-
scssFiles: [],
|
|
865
|
-
hasDarkMode
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
async function checkDarkMode(projectRoot, cssFiles) {
|
|
869
|
-
for (const file of cssFiles) {
|
|
870
|
-
try {
|
|
871
|
-
const css = await fs7.readFile(path6.join(projectRoot, file), "utf-8");
|
|
872
|
-
if (css.includes(".dark") || css.includes('[data-theme="dark"]') || css.includes("prefers-color-scheme: dark")) {
|
|
873
|
-
return true;
|
|
874
|
-
}
|
|
875
|
-
} catch {
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return false;
|
|
879
|
-
}
|
|
880
|
-
async function checkCustomProperties(projectRoot, cssFiles) {
|
|
881
|
-
for (const file of cssFiles) {
|
|
882
|
-
try {
|
|
883
|
-
const css = await fs7.readFile(path6.join(projectRoot, file), "utf-8");
|
|
884
|
-
if (/--[\w-]+\s*:/.test(css)) {
|
|
885
|
-
return true;
|
|
886
|
-
}
|
|
887
|
-
} catch {
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
return false;
|
|
891
|
-
}
|
|
892
|
-
async function findBootstrapScssFiles(projectRoot) {
|
|
893
|
-
const candidates = [
|
|
894
|
-
"src/scss/_variables.scss",
|
|
895
|
-
"src/scss/_custom.scss",
|
|
896
|
-
"src/scss/custom.scss",
|
|
897
|
-
"src/styles/_variables.scss",
|
|
898
|
-
"src/styles/variables.scss",
|
|
899
|
-
"assets/scss/_variables.scss",
|
|
900
|
-
"scss/_variables.scss",
|
|
901
|
-
"styles/_variables.scss"
|
|
902
|
-
];
|
|
903
|
-
const found = [];
|
|
904
|
-
for (const candidate of candidates) {
|
|
905
|
-
try {
|
|
906
|
-
await fs7.access(path6.join(projectRoot, candidate));
|
|
907
|
-
found.push(candidate);
|
|
908
|
-
} catch {
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
return found;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
967
|
// src/server/scanner/scan-shadows.ts
|
|
915
968
|
import fs9 from "fs/promises";
|
|
916
|
-
import
|
|
969
|
+
import path9 from "path";
|
|
917
970
|
|
|
918
971
|
// src/server/scanner/presets/tailwind.ts
|
|
919
972
|
var TAILWIND_SHADOW_PRESETS = [
|
|
@@ -961,7 +1014,7 @@ var TAILWIND_SHADOW_PRESETS = [
|
|
|
961
1014
|
|
|
962
1015
|
// src/server/scanner/presets/bootstrap.ts
|
|
963
1016
|
import fs8 from "fs/promises";
|
|
964
|
-
import
|
|
1017
|
+
import path8 from "path";
|
|
965
1018
|
var BOOTSTRAP_SHADOW_PRESETS = [
|
|
966
1019
|
{
|
|
967
1020
|
name: "box-shadow-sm",
|
|
@@ -984,7 +1037,7 @@ async function scanBootstrapScssOverrides(projectRoot, scssFiles) {
|
|
|
984
1037
|
const overrides = [];
|
|
985
1038
|
for (const file of scssFiles) {
|
|
986
1039
|
try {
|
|
987
|
-
const content = await fs8.readFile(
|
|
1040
|
+
const content = await fs8.readFile(path8.join(projectRoot, file), "utf-8");
|
|
988
1041
|
const lines = content.split("\n");
|
|
989
1042
|
for (const line of lines) {
|
|
990
1043
|
const match = line.match(
|
|
@@ -1012,7 +1065,7 @@ async function scanBootstrapCssOverrides(projectRoot, cssFiles) {
|
|
|
1012
1065
|
const overrides = [];
|
|
1013
1066
|
for (const file of cssFiles) {
|
|
1014
1067
|
try {
|
|
1015
|
-
const content = await fs8.readFile(
|
|
1068
|
+
const content = await fs8.readFile(path8.join(projectRoot, file), "utf-8");
|
|
1016
1069
|
const propRegex = /(--bs-box-shadow(?:-sm|-lg|-inset)?)\s*:\s*([^;]+);/g;
|
|
1017
1070
|
let match;
|
|
1018
1071
|
while ((match = propRegex.exec(content)) !== null) {
|
|
@@ -1039,8 +1092,9 @@ function resolveBootstrapSassColors(value) {
|
|
|
1039
1092
|
// src/server/scanner/scan-shadows.ts
|
|
1040
1093
|
async function scanShadows(projectRoot, framework, styling) {
|
|
1041
1094
|
const shadows = [];
|
|
1042
|
-
const
|
|
1043
|
-
const
|
|
1095
|
+
const allCssFiles = framework.cssFiles.length > 0 ? framework.cssFiles : styling.cssFiles;
|
|
1096
|
+
const cssFilePath = allCssFiles[0] || "";
|
|
1097
|
+
const customShadows = await scanCustomShadows(projectRoot, allCssFiles);
|
|
1044
1098
|
const overriddenNames = new Set(customShadows.map((s) => s.name));
|
|
1045
1099
|
if (styling.type === "tailwind-v4" || styling.type === "tailwind-v3") {
|
|
1046
1100
|
addPresets(shadows, TAILWIND_SHADOW_PRESETS, overriddenNames);
|
|
@@ -1070,12 +1124,41 @@ async function scanShadows(projectRoot, framework, styling) {
|
|
|
1070
1124
|
isOverridden: true
|
|
1071
1125
|
});
|
|
1072
1126
|
}
|
|
1127
|
+
const sizeOrder = {
|
|
1128
|
+
"2xs": 0,
|
|
1129
|
+
"xs": 1,
|
|
1130
|
+
"sm": 2,
|
|
1131
|
+
"": 3,
|
|
1132
|
+
"md": 4,
|
|
1133
|
+
"lg": 5,
|
|
1134
|
+
"xl": 6,
|
|
1135
|
+
"2xl": 7
|
|
1136
|
+
};
|
|
1137
|
+
const naturalCollator = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
|
|
1073
1138
|
shadows.sort((a, b) => {
|
|
1074
1139
|
const order = { custom: 0, "design-token": 1, "framework-preset": 2 };
|
|
1075
1140
|
const aOrder = order[a.source] ?? 3;
|
|
1076
1141
|
const bOrder = order[b.source] ?? 3;
|
|
1077
1142
|
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
1078
|
-
|
|
1143
|
+
const extractSize = (name) => {
|
|
1144
|
+
const match = name.match(/^[\w-]+-(\d*x[sl]|sm|md|lg)$/);
|
|
1145
|
+
if (match) return match[1];
|
|
1146
|
+
if (/^[a-z]+(-[a-z]+)*$/.test(name) && !name.includes("-inner") && !name.includes("-none")) {
|
|
1147
|
+
const parts = name.split("-");
|
|
1148
|
+
const last = parts[parts.length - 1];
|
|
1149
|
+
if (last in sizeOrder) return last;
|
|
1150
|
+
if (parts.length === 1 || !Object.keys(sizeOrder).includes(last)) return "";
|
|
1151
|
+
}
|
|
1152
|
+
return null;
|
|
1153
|
+
};
|
|
1154
|
+
const aSize = extractSize(a.name);
|
|
1155
|
+
const bSize = extractSize(b.name);
|
|
1156
|
+
if (aSize !== null && bSize !== null) {
|
|
1157
|
+
const aIdx = sizeOrder[aSize] ?? 99;
|
|
1158
|
+
const bIdx = sizeOrder[bSize] ?? 99;
|
|
1159
|
+
if (aIdx !== bIdx) return aIdx - bIdx;
|
|
1160
|
+
}
|
|
1161
|
+
return naturalCollator.compare(a.name, b.name);
|
|
1079
1162
|
});
|
|
1080
1163
|
return { shadows, cssFilePath, stylingType: styling.type, designTokenFiles };
|
|
1081
1164
|
}
|
|
@@ -1087,7 +1170,8 @@ function addPresets(shadows, presets, overriddenNames) {
|
|
|
1087
1170
|
value: preset.value,
|
|
1088
1171
|
source: "framework-preset",
|
|
1089
1172
|
isOverridden: false,
|
|
1090
|
-
layers: parseShadowValue(preset.value)
|
|
1173
|
+
layers: parseShadowValue(preset.value),
|
|
1174
|
+
cssVariable: `--${preset.name}`
|
|
1091
1175
|
});
|
|
1092
1176
|
}
|
|
1093
1177
|
}
|
|
@@ -1129,7 +1213,7 @@ async function scanCustomShadows(projectRoot, cssFiles) {
|
|
|
1129
1213
|
const shadows = [];
|
|
1130
1214
|
for (const file of cssFiles) {
|
|
1131
1215
|
try {
|
|
1132
|
-
const css = await fs9.readFile(
|
|
1216
|
+
const css = await fs9.readFile(path9.join(projectRoot, file), "utf-8");
|
|
1133
1217
|
const rootTokens = parseBlock(css, ":root");
|
|
1134
1218
|
for (const [name, value] of rootTokens) {
|
|
1135
1219
|
if (name.includes("shadow") || isShadowValue(value)) {
|
|
@@ -1143,7 +1227,7 @@ async function scanCustomShadows(projectRoot, cssFiles) {
|
|
|
1143
1227
|
});
|
|
1144
1228
|
}
|
|
1145
1229
|
}
|
|
1146
|
-
const themeMatch = css.match(/@theme\s
|
|
1230
|
+
const themeMatch = css.match(/@theme\s*(?:inline\s*)?\{([\s\S]*?)\}/);
|
|
1147
1231
|
if (themeMatch) {
|
|
1148
1232
|
const themeBlock = themeMatch[1];
|
|
1149
1233
|
const propRegex = /(--shadow[\w-]*)\s*:\s*([^;]+);/g;
|
|
@@ -1267,13 +1351,28 @@ function createShadowsScanRouter(projectRoot) {
|
|
|
1267
1351
|
}
|
|
1268
1352
|
|
|
1269
1353
|
// src/server/index.ts
|
|
1270
|
-
var __dirname =
|
|
1271
|
-
var
|
|
1354
|
+
var __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
1355
|
+
var require2 = createRequire(import.meta.url);
|
|
1356
|
+
var packageRoot = fs10.existsSync(path10.join(__dirname, "../package.json")) ? path10.resolve(__dirname, "..") : path10.resolve(__dirname, "../..");
|
|
1357
|
+
function resolveInjectScript() {
|
|
1358
|
+
const compiledInject = path10.join(packageRoot, "dist/inject/selection.js");
|
|
1359
|
+
if (fs10.existsSync(compiledInject)) return compiledInject;
|
|
1360
|
+
try {
|
|
1361
|
+
const corePkg = require2.resolve("@designtools/core/package.json");
|
|
1362
|
+
const coreRoot = path10.dirname(corePkg);
|
|
1363
|
+
const coreInject = path10.join(coreRoot, "src/inject/selection.ts");
|
|
1364
|
+
if (fs10.existsSync(coreInject)) return coreInject;
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
const monorepoInject = path10.join(packageRoot, "../core/src/inject/selection.ts");
|
|
1368
|
+
if (fs10.existsSync(monorepoInject)) return monorepoInject;
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
"Could not find inject script (selection.ts). Ensure @designtools/core is installed."
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1272
1373
|
async function startShadowsServer(preflight) {
|
|
1273
|
-
const clientRoot =
|
|
1274
|
-
const
|
|
1275
|
-
const compiledInject = path9.join(packageRoot, "dist/inject/selection.js");
|
|
1276
|
-
const actualInjectPath = fs10.existsSync(compiledInject) ? compiledInject : injectScriptPath;
|
|
1374
|
+
const clientRoot = path10.join(packageRoot, "src/client");
|
|
1375
|
+
const actualInjectPath = resolveInjectScript();
|
|
1277
1376
|
const { app, wss, projectRoot } = await createToolServer({
|
|
1278
1377
|
targetPort: preflight.targetPort,
|
|
1279
1378
|
toolPort: preflight.toolPort,
|