@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 CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../core/src/cli/bootstrap.ts
4
- import fs2 from "fs";
5
- import path2 from "path";
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 = process.argv.slice(2);
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 = process.cwd();
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 = path2.join(projectRoot, "package.json");
119
- if (!fs2.existsSync(pkgPath)) {
120
- console.log(` ${red("\u2717")} No package.json found`);
121
- console.log(` ${dim("Run this command from your project root.")}`);
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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
312
+ process2.exit(1);
170
313
  }
171
314
  console.log(` ${green("\u2713")} Tool http://localhost:${toolPort}`);
172
315
  console.log("");
173
- return { framework, targetPort, toolPort, projectRoot };
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 path9 from "path";
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 fs3 from "fs";
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 = fs3.readFileSync(config.injectScriptPath, "utf-8");
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
- const injected = body.replace(
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 fs5 from "fs/promises";
314
- import path4 from "path";
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 fs4 from "fs/promises";
318
- import path3 from "path";
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 = path3.join(projectRoot, dir);
332
- const entries = await fs4.readdir(fullDir);
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(path3.join(dir, entry));
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 fs4.readFile(path3.join(projectRoot, file), "utf-8");
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 fs4.writeFile(filePath, content, "utf-8");
643
+ await fs5.writeFile(filePath, content, "utf-8");
473
644
  }
474
645
  async function updateDesignTokenShadow(filePath, tokenPath, newCssValue) {
475
- const content = await fs4.readFile(filePath, "utf-8");
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 fs4.writeFile(filePath, JSON.stringify(tokens, null, 2) + "\n", "utf-8");
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 = path4.join(projectRoot, filePath);
668
+ const fullPath = safePath(projectRoot, filePath);
498
669
  if (selector === "scss") {
499
- let scss = await fs5.readFile(fullPath, "utf-8");
670
+ let scss = await fs6.readFile(fullPath, "utf-8");
500
671
  scss = writeShadowToScss(scss, variableName, value);
501
- await fs5.writeFile(fullPath, scss, "utf-8");
672
+ await fs6.writeFile(fullPath, scss, "utf-8");
502
673
  } else if (selector === "@theme") {
503
- let css = await fs5.readFile(fullPath, "utf-8");
674
+ let css = await fs6.readFile(fullPath, "utf-8");
504
675
  css = writeShadowToTheme(css, variableName, value);
505
- await fs5.writeFile(fullPath, css, "utf-8");
676
+ await fs6.writeFile(fullPath, css, "utf-8");
506
677
  } else {
507
- let css = await fs5.readFile(fullPath, "utf-8");
678
+ let css = await fs6.readFile(fullPath, "utf-8");
508
679
  css = writeShadowToSelector(css, selector, variableName, value);
509
- await fs5.writeFile(fullPath, css, "utf-8");
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 = path4.join(projectRoot, filePath);
691
+ const fullPath = safePath(projectRoot, filePath);
521
692
  if (selector === "scss") {
522
693
  let scss;
523
694
  try {
524
- scss = await fs5.readFile(fullPath, "utf-8");
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 fs5.writeFile(fullPath, scss, "utf-8");
700
+ await fs6.writeFile(fullPath, scss, "utf-8");
530
701
  } else if (selector === "@theme") {
531
- let css = await fs5.readFile(fullPath, "utf-8");
702
+ let css = await fs6.readFile(fullPath, "utf-8");
532
703
  css = addShadowToTheme(css, variableName, value);
533
- await fs5.writeFile(fullPath, css, "utf-8");
704
+ await fs6.writeFile(fullPath, css, "utf-8");
534
705
  } else {
535
- let css = await fs5.readFile(fullPath, "utf-8");
706
+ let css = await fs6.readFile(fullPath, "utf-8");
536
707
  css = addShadowToSelector(css, selector, variableName, value);
537
- await fs5.writeFile(fullPath, css, "utf-8");
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 = path4.join(projectRoot, filePath);
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 = path4.join(projectRoot, filePath);
730
+ const fullPath = safePath(projectRoot, filePath);
560
731
  const tokens = buildDesignTokensJson(shadows);
561
- await fs5.mkdir(path4.dirname(fullPath), { recursive: true });
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
- if (!tokenRegex.test(block)) {
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
- if (tokenRegex.test(block)) {
614
- block = block.replace(tokenRegex, `$1${newValue}$3`);
615
- } else {
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
- if (!regex.test(scss)) {
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 scss.replace(regex, `$1${newValue}$3`);
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 fs6 from "fs/promises";
680
- import path5 from "path";
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 = path5.join(projectRoot, cssFilePath);
687
- const css = await fs6.readFile(fullPath, "utf-8");
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 path8 from "path";
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 path7 from "path";
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(path7.join(projectRoot, file), "utf-8");
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(path7.join(projectRoot, file), "utf-8");
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 cssFilePath = framework.cssFiles[0] || "";
1043
- const customShadows = await scanCustomShadows(projectRoot, framework.cssFiles);
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
- return a.name.localeCompare(b.name);
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(path8.join(projectRoot, file), "utf-8");
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*\{([\s\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 = path9.dirname(fileURLToPath(import.meta.url));
1271
- var packageRoot = fs10.existsSync(path9.join(__dirname, "../package.json")) ? path9.resolve(__dirname, "..") : path9.resolve(__dirname, "../..");
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 = path9.join(packageRoot, "src/client");
1274
- const injectScriptPath = path9.join(packageRoot, "../core/src/inject/selection.ts");
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,