@designtools/shadows 0.1.4 → 0.1.6

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
322
  import path10 from "path";
178
- import fs11 from "fs";
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,214 +964,8 @@ function detectColorFormat(value) {
791
964
  return null;
792
965
  }
793
966
 
794
- // ../core/src/scanner/scan-routes.ts
795
- import fs7 from "fs/promises";
796
- import path6 from "path";
797
- async function scanRoutes(projectRoot, framework) {
798
- if (framework.name === "nextjs") {
799
- return scanNextJsRoutes(projectRoot, framework.appDir);
800
- }
801
- return scanGenericRoutes(projectRoot, framework.appDir);
802
- }
803
- async function scanNextJsRoutes(projectRoot, appDir) {
804
- const routes = [];
805
- const fullAppDir = path6.join(projectRoot, appDir);
806
- try {
807
- await scanNextJsDir(fullAppDir, appDir, "", routes);
808
- } catch {
809
- }
810
- return { routes };
811
- }
812
- async function scanNextJsDir(fullDir, appDir, urlPrefix, routes) {
813
- let entries;
814
- try {
815
- entries = await fs7.readdir(fullDir, { withFileTypes: true });
816
- } catch {
817
- return;
818
- }
819
- const hasPage = entries.some(
820
- (e) => e.isFile() && (e.name === "page.tsx" || e.name === "page.jsx")
821
- );
822
- if (hasPage) {
823
- const pageFile = entries.find(
824
- (e) => e.name === "page.tsx" || e.name === "page.jsx"
825
- );
826
- routes.push({
827
- urlPath: urlPrefix || "/",
828
- filePath: path6.join(
829
- appDir,
830
- urlPrefix.replace(/^\//, ""),
831
- pageFile.name
832
- )
833
- });
834
- }
835
- for (const entry of entries) {
836
- if (!entry.isDirectory()) continue;
837
- if (entry.name.startsWith("_") || entry.name === "node_modules") continue;
838
- let segment = entry.name;
839
- if (segment.startsWith("(") && segment.endsWith(")")) {
840
- await scanNextJsDir(
841
- path6.join(fullDir, segment),
842
- appDir,
843
- urlPrefix,
844
- routes
845
- );
846
- continue;
847
- }
848
- if (segment.startsWith("[") && segment.endsWith("]")) {
849
- segment = `:${segment.slice(1, -1)}`;
850
- }
851
- if (segment === "api") continue;
852
- await scanNextJsDir(
853
- path6.join(fullDir, entry.name),
854
- appDir,
855
- `${urlPrefix}/${segment}`,
856
- routes
857
- );
858
- }
859
- }
860
- async function scanGenericRoutes(projectRoot, srcDir) {
861
- const routes = [];
862
- const fullDir = path6.join(projectRoot, srcDir);
863
- try {
864
- const files = await fs7.readdir(fullDir, { withFileTypes: true });
865
- for (const file of files) {
866
- if (file.isFile() && (file.name.endsWith(".tsx") || file.name.endsWith(".jsx"))) {
867
- const name = file.name.replace(/\.(tsx|jsx)$/, "");
868
- const urlPath = name === "index" ? "/" : `/${name}`;
869
- routes.push({
870
- urlPath,
871
- filePath: path6.join(srcDir, file.name)
872
- });
873
- }
874
- }
875
- } catch {
876
- }
877
- return { routes };
878
- }
879
-
880
- // ../core/src/scanner/detect-styling.ts
881
- import fs8 from "fs/promises";
882
- import path7 from "path";
883
- async function detectStylingSystem(projectRoot, framework) {
884
- const pkgPath = path7.join(projectRoot, "package.json");
885
- let pkg = {};
886
- try {
887
- pkg = JSON.parse(await fs8.readFile(pkgPath, "utf-8"));
888
- } catch {
889
- }
890
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
891
- if (deps.tailwindcss) {
892
- const version = deps.tailwindcss;
893
- const isV4 = version.startsWith("^4") || version.startsWith("~4") || version.startsWith("4");
894
- if (isV4) {
895
- const hasDarkMode3 = await checkDarkMode(projectRoot, framework.cssFiles);
896
- return {
897
- type: "tailwind-v4",
898
- cssFiles: framework.cssFiles,
899
- scssFiles: [],
900
- hasDarkMode: hasDarkMode3
901
- };
902
- }
903
- const configCandidates = [
904
- "tailwind.config.ts",
905
- "tailwind.config.js",
906
- "tailwind.config.mjs",
907
- "tailwind.config.cjs"
908
- ];
909
- let configPath;
910
- for (const candidate of configCandidates) {
911
- try {
912
- await fs8.access(path7.join(projectRoot, candidate));
913
- configPath = candidate;
914
- break;
915
- } catch {
916
- }
917
- }
918
- const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
919
- return {
920
- type: "tailwind-v3",
921
- configPath,
922
- cssFiles: framework.cssFiles,
923
- scssFiles: [],
924
- hasDarkMode: hasDarkMode2
925
- };
926
- }
927
- if (deps.bootstrap) {
928
- const hasDarkMode2 = await checkDarkMode(projectRoot, framework.cssFiles);
929
- const scssFiles = await findBootstrapScssFiles(projectRoot);
930
- return {
931
- type: "bootstrap",
932
- cssFiles: framework.cssFiles,
933
- scssFiles,
934
- hasDarkMode: hasDarkMode2
935
- };
936
- }
937
- const hasDarkMode = await checkDarkMode(projectRoot, framework.cssFiles);
938
- const hasCustomProps = await checkCustomProperties(projectRoot, framework.cssFiles);
939
- if (hasCustomProps) {
940
- return {
941
- type: "css-variables",
942
- cssFiles: framework.cssFiles,
943
- scssFiles: [],
944
- hasDarkMode
945
- };
946
- }
947
- return {
948
- type: framework.cssFiles.length > 0 ? "plain-css" : "unknown",
949
- cssFiles: framework.cssFiles,
950
- scssFiles: [],
951
- hasDarkMode
952
- };
953
- }
954
- async function checkDarkMode(projectRoot, cssFiles) {
955
- for (const file of cssFiles) {
956
- try {
957
- const css = await fs8.readFile(path7.join(projectRoot, file), "utf-8");
958
- if (css.includes(".dark") || css.includes('[data-theme="dark"]') || css.includes("prefers-color-scheme: dark")) {
959
- return true;
960
- }
961
- } catch {
962
- }
963
- }
964
- return false;
965
- }
966
- async function checkCustomProperties(projectRoot, cssFiles) {
967
- for (const file of cssFiles) {
968
- try {
969
- const css = await fs8.readFile(path7.join(projectRoot, file), "utf-8");
970
- if (/--[\w-]+\s*:/.test(css)) {
971
- return true;
972
- }
973
- } catch {
974
- }
975
- }
976
- return false;
977
- }
978
- async function findBootstrapScssFiles(projectRoot) {
979
- const candidates = [
980
- "src/scss/_variables.scss",
981
- "src/scss/_custom.scss",
982
- "src/scss/custom.scss",
983
- "src/styles/_variables.scss",
984
- "src/styles/variables.scss",
985
- "assets/scss/_variables.scss",
986
- "scss/_variables.scss",
987
- "styles/_variables.scss"
988
- ];
989
- const found = [];
990
- for (const candidate of candidates) {
991
- try {
992
- await fs8.access(path7.join(projectRoot, candidate));
993
- found.push(candidate);
994
- } catch {
995
- }
996
- }
997
- return found;
998
- }
999
-
1000
967
  // src/server/scanner/scan-shadows.ts
1001
- import fs10 from "fs/promises";
968
+ import fs9 from "fs/promises";
1002
969
  import path9 from "path";
1003
970
 
1004
971
  // src/server/scanner/presets/tailwind.ts
@@ -1046,7 +1013,7 @@ var TAILWIND_SHADOW_PRESETS = [
1046
1013
  ];
1047
1014
 
1048
1015
  // src/server/scanner/presets/bootstrap.ts
1049
- import fs9 from "fs/promises";
1016
+ import fs8 from "fs/promises";
1050
1017
  import path8 from "path";
1051
1018
  var BOOTSTRAP_SHADOW_PRESETS = [
1052
1019
  {
@@ -1070,7 +1037,7 @@ async function scanBootstrapScssOverrides(projectRoot, scssFiles) {
1070
1037
  const overrides = [];
1071
1038
  for (const file of scssFiles) {
1072
1039
  try {
1073
- const content = await fs9.readFile(path8.join(projectRoot, file), "utf-8");
1040
+ const content = await fs8.readFile(path8.join(projectRoot, file), "utf-8");
1074
1041
  const lines = content.split("\n");
1075
1042
  for (const line of lines) {
1076
1043
  const match = line.match(
@@ -1098,7 +1065,7 @@ async function scanBootstrapCssOverrides(projectRoot, cssFiles) {
1098
1065
  const overrides = [];
1099
1066
  for (const file of cssFiles) {
1100
1067
  try {
1101
- const content = await fs9.readFile(path8.join(projectRoot, file), "utf-8");
1068
+ const content = await fs8.readFile(path8.join(projectRoot, file), "utf-8");
1102
1069
  const propRegex = /(--bs-box-shadow(?:-sm|-lg|-inset)?)\s*:\s*([^;]+);/g;
1103
1070
  let match;
1104
1071
  while ((match = propRegex.exec(content)) !== null) {
@@ -1125,8 +1092,9 @@ function resolveBootstrapSassColors(value) {
1125
1092
  // src/server/scanner/scan-shadows.ts
1126
1093
  async function scanShadows(projectRoot, framework, styling) {
1127
1094
  const shadows = [];
1128
- const cssFilePath = framework.cssFiles[0] || "";
1129
- 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);
1130
1098
  const overriddenNames = new Set(customShadows.map((s) => s.name));
1131
1099
  if (styling.type === "tailwind-v4" || styling.type === "tailwind-v3") {
1132
1100
  addPresets(shadows, TAILWIND_SHADOW_PRESETS, overriddenNames);
@@ -1156,12 +1124,41 @@ async function scanShadows(projectRoot, framework, styling) {
1156
1124
  isOverridden: true
1157
1125
  });
1158
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" });
1159
1138
  shadows.sort((a, b) => {
1160
1139
  const order = { custom: 0, "design-token": 1, "framework-preset": 2 };
1161
1140
  const aOrder = order[a.source] ?? 3;
1162
1141
  const bOrder = order[b.source] ?? 3;
1163
1142
  if (aOrder !== bOrder) return aOrder - bOrder;
1164
- 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);
1165
1162
  });
1166
1163
  return { shadows, cssFilePath, stylingType: styling.type, designTokenFiles };
1167
1164
  }
@@ -1173,7 +1170,8 @@ function addPresets(shadows, presets, overriddenNames) {
1173
1170
  value: preset.value,
1174
1171
  source: "framework-preset",
1175
1172
  isOverridden: false,
1176
- layers: parseShadowValue(preset.value)
1173
+ layers: parseShadowValue(preset.value),
1174
+ cssVariable: `--${preset.name}`
1177
1175
  });
1178
1176
  }
1179
1177
  }
@@ -1215,7 +1213,7 @@ async function scanCustomShadows(projectRoot, cssFiles) {
1215
1213
  const shadows = [];
1216
1214
  for (const file of cssFiles) {
1217
1215
  try {
1218
- const css = await fs10.readFile(path9.join(projectRoot, file), "utf-8");
1216
+ const css = await fs9.readFile(path9.join(projectRoot, file), "utf-8");
1219
1217
  const rootTokens = parseBlock(css, ":root");
1220
1218
  for (const [name, value] of rootTokens) {
1221
1219
  if (name.includes("shadow") || isShadowValue(value)) {
@@ -1229,7 +1227,7 @@ async function scanCustomShadows(projectRoot, cssFiles) {
1229
1227
  });
1230
1228
  }
1231
1229
  }
1232
- const themeMatch = css.match(/@theme\s*\{([\s\S]*?)\}/);
1230
+ const themeMatch = css.match(/@theme\s*(?:inline\s*)?\{([\s\S]*?)\}/);
1233
1231
  if (themeMatch) {
1234
1232
  const themeBlock = themeMatch[1];
1235
1233
  const propRegex = /(--shadow[\w-]*)\s*:\s*([^;]+);/g;
@@ -1311,12 +1309,11 @@ var cachedScan = null;
1311
1309
  async function runScan(projectRoot) {
1312
1310
  const framework = await detectFramework(projectRoot);
1313
1311
  const styling = await detectStylingSystem(projectRoot, framework);
1314
- const [tokens, shadows, routes] = await Promise.all([
1312
+ const [tokens, shadows] = await Promise.all([
1315
1313
  scanTokens(projectRoot, framework),
1316
- scanShadows(projectRoot, framework, styling),
1317
- scanRoutes(projectRoot, framework)
1314
+ scanShadows(projectRoot, framework, styling)
1318
1315
  ]);
1319
- cachedScan = { framework, styling, tokens, shadows, routes };
1316
+ cachedScan = { framework, styling, tokens, shadows };
1320
1317
  return cachedScan;
1321
1318
  }
1322
1319
  function createShadowsScanRouter(projectRoot) {
@@ -1355,12 +1352,27 @@ function createShadowsScanRouter(projectRoot) {
1355
1352
 
1356
1353
  // src/server/index.ts
1357
1354
  var __dirname = path10.dirname(fileURLToPath(import.meta.url));
1358
- var packageRoot = fs11.existsSync(path10.join(__dirname, "../package.json")) ? path10.resolve(__dirname, "..") : path10.resolve(__dirname, "../..");
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
+ }
1359
1373
  async function startShadowsServer(preflight) {
1360
1374
  const clientRoot = path10.join(packageRoot, "src/client");
1361
- const injectScriptPath = path10.join(packageRoot, "../core/src/inject/selection.ts");
1362
- const compiledInject = path10.join(packageRoot, "dist/inject/selection.js");
1363
- const actualInjectPath = fs11.existsSync(compiledInject) ? compiledInject : injectScriptPath;
1375
+ const actualInjectPath = resolveInjectScript();
1364
1376
  const { app, wss, projectRoot } = await createToolServer({
1365
1377
  targetPort: preflight.targetPort,
1366
1378
  toolPort: preflight.toolPort,