@designtools/codesurface 0.1.7 → 0.1.9

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,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import fs18 from "fs";
5
- import path16 from "path";
4
+ import fs19 from "fs";
5
+ import path17 from "path";
6
6
  import process2 from "process";
7
7
  import readline from "readline";
8
8
  import open from "open";
@@ -216,8 +216,8 @@ async function findBootstrapScssFiles(projectRoot) {
216
216
 
217
217
  // src/server/index.ts
218
218
  import express from "express";
219
- import path15 from "path";
220
- import fs17 from "fs";
219
+ import path16 from "path";
220
+ import fs18 from "fs";
221
221
  import { fileURLToPath } from "url";
222
222
  import { exec } from "child_process";
223
223
 
@@ -255,7 +255,7 @@ import { namedTypes as n, builders as b } from "ast-types";
255
255
  var _parser = null;
256
256
  async function getParser() {
257
257
  if (!_parser) {
258
- _parser = await import("recast/parsers/babel-ts");
258
+ _parser = await import("recast/parsers/babel-ts.js");
259
259
  }
260
260
  return _parser;
261
261
  }
@@ -400,13 +400,13 @@ import { namedTypes as n2 } from "ast-types";
400
400
  function findElementAtSource(ast, line, col) {
401
401
  let found = null;
402
402
  visit(ast, {
403
- visitJSXOpeningElement(path17) {
404
- const loc = path17.node.loc;
403
+ visitJSXOpeningElement(path18) {
404
+ const loc = path18.node.loc;
405
405
  if (loc && loc.start.line === line && loc.start.column === col) {
406
- found = path17;
406
+ found = path18;
407
407
  return false;
408
408
  }
409
- this.traverse(path17);
409
+ this.traverse(path18);
410
410
  }
411
411
  });
412
412
  return found;
@@ -414,16 +414,16 @@ function findElementAtSource(ast, line, col) {
414
414
  function findComponentAtSource(ast, componentName, line, col) {
415
415
  let found = null;
416
416
  visit(ast, {
417
- visitJSXOpeningElement(path17) {
418
- const name = path17.node.name;
417
+ visitJSXOpeningElement(path18) {
418
+ const name = path18.node.name;
419
419
  if (n2.JSXIdentifier.check(name) && name.name === componentName) {
420
- const loc = path17.node.loc;
420
+ const loc = path18.node.loc;
421
421
  if (loc && loc.start.line === line && loc.start.column === col) {
422
- found = path17;
422
+ found = path18;
423
423
  return false;
424
424
  }
425
425
  }
426
- this.traverse(path17);
426
+ this.traverse(path18);
427
427
  }
428
428
  });
429
429
  return found;
@@ -1207,12 +1207,12 @@ function createWriteElementRouter(config) {
1207
1207
 
1208
1208
  // src/server/api/write-tokens.ts
1209
1209
  import { Router as Router3 } from "express";
1210
- import fs13 from "fs/promises";
1210
+ import fs14 from "fs/promises";
1211
1211
 
1212
1212
  // src/server/lib/scanner.ts
1213
1213
  import { Router as Router2 } from "express";
1214
- import fs12 from "fs/promises";
1215
- import path12 from "path";
1214
+ import fs13 from "fs/promises";
1215
+ import path13 from "path";
1216
1216
 
1217
1217
  // src/server/lib/scan-tokens.ts
1218
1218
  import fs4 from "fs/promises";
@@ -1381,8 +1381,8 @@ function parseComponentAST(source, filePath, parser) {
1381
1381
  let currentComponentName = null;
1382
1382
  recast2.visit(ast, {
1383
1383
  // Find cva() calls: const fooVariants = cva("base classes", { variants: {...}, defaultVariants: {...} })
1384
- visitVariableDeclaration(path17) {
1385
- for (const decl of path17.node.declarations) {
1384
+ visitVariableDeclaration(path18) {
1385
+ for (const decl of path18.node.declarations) {
1386
1386
  if (n3.VariableDeclarator.check(decl) && n3.Identifier.check(decl.id) && n3.CallExpression.check(decl.init) && isIdentifierNamed(decl.init.callee, "cva")) {
1387
1387
  const varName = decl.id.name;
1388
1388
  const args = decl.init.arguments;
@@ -1392,45 +1392,45 @@ function parseComponentAST(source, filePath, parser) {
1392
1392
  cvaMap.set(varName, { baseClasses, variants });
1393
1393
  }
1394
1394
  }
1395
- this.traverse(path17);
1395
+ this.traverse(path18);
1396
1396
  },
1397
1397
  // Find forwardRef and function components to track data-slot and currentComponentName
1398
- visitCallExpression(path17) {
1399
- const node = path17.node;
1398
+ visitCallExpression(path18) {
1399
+ const node = path18.node;
1400
1400
  if (isForwardRefCall(node)) {
1401
- const parent = path17.parent?.node;
1401
+ const parent = path18.parent?.node;
1402
1402
  if (n3.VariableDeclarator.check(parent) && n3.Identifier.check(parent.id)) {
1403
1403
  currentComponentName = parent.id.name;
1404
1404
  }
1405
1405
  }
1406
- this.traverse(path17);
1406
+ this.traverse(path18);
1407
1407
  },
1408
1408
  // Find data-slot JSX attributes
1409
- visitJSXAttribute(path17) {
1410
- const attr = path17.node;
1409
+ visitJSXAttribute(path18) {
1410
+ const attr = path18.node;
1411
1411
  if (n3.JSXIdentifier.check(attr.name) && attr.name.name === "data-slot" && n3.StringLiteral.check(attr.value)) {
1412
1412
  const slotValue = attr.value.value;
1413
- const compName = findEnclosingComponentName(path17) || currentComponentName;
1413
+ const compName = findEnclosingComponentName(path18) || currentComponentName;
1414
1414
  if (compName) {
1415
1415
  slotToComponent.set(slotValue, compName);
1416
1416
  }
1417
1417
  }
1418
- this.traverse(path17);
1418
+ this.traverse(path18);
1419
1419
  },
1420
1420
  // Detect {...props} spread in JSX — indicates component accepts children
1421
- visitJSXSpreadAttribute(path17) {
1422
- const expr = path17.node.argument;
1421
+ visitJSXSpreadAttribute(path18) {
1422
+ const expr = path18.node.argument;
1423
1423
  if (n3.Identifier.check(expr) && expr.name === "props") {
1424
- const compName = findEnclosingComponentName(path17) || currentComponentName;
1424
+ const compName = findEnclosingComponentName(path18) || currentComponentName;
1425
1425
  if (compName) {
1426
1426
  acceptsChildrenSet.add(compName);
1427
1427
  }
1428
1428
  }
1429
- this.traverse(path17);
1429
+ this.traverse(path18);
1430
1430
  },
1431
1431
  // Collect named exports: export { Button, Card, ... }
1432
- visitExportNamedDeclaration(path17) {
1433
- const node = path17.node;
1432
+ visitExportNamedDeclaration(path18) {
1433
+ const node = path18.node;
1434
1434
  if (node.specifiers) {
1435
1435
  for (const spec of node.specifiers) {
1436
1436
  if (n3.ExportSpecifier.check(spec) && n3.Identifier.check(spec.exported)) {
@@ -1449,37 +1449,37 @@ function parseComponentAST(source, filePath, parser) {
1449
1449
  exportedNames.add(node.declaration.id.name);
1450
1450
  }
1451
1451
  }
1452
- this.traverse(path17);
1452
+ this.traverse(path18);
1453
1453
  },
1454
1454
  // export default function Foo
1455
- visitExportDefaultDeclaration(path17) {
1456
- const decl = path17.node.declaration;
1455
+ visitExportDefaultDeclaration(path18) {
1456
+ const decl = path18.node.declaration;
1457
1457
  if (n3.FunctionDeclaration.check(decl) && decl.id) {
1458
1458
  exportedNames.add(decl.id.name);
1459
1459
  }
1460
- this.traverse(path17);
1460
+ this.traverse(path18);
1461
1461
  }
1462
1462
  });
1463
1463
  recast2.visit(ast, {
1464
- visitFunctionDeclaration(path17) {
1465
- const name = path17.node.id ? String(path17.node.id.name) : null;
1464
+ visitFunctionDeclaration(path18) {
1465
+ const name = path18.node.id ? String(path18.node.id.name) : null;
1466
1466
  if (name) {
1467
1467
  currentComponentName = name;
1468
- this.traverse(path17);
1468
+ this.traverse(path18);
1469
1469
  currentComponentName = null;
1470
1470
  } else {
1471
- this.traverse(path17);
1471
+ this.traverse(path18);
1472
1472
  }
1473
1473
  },
1474
- visitJSXAttribute(path17) {
1475
- const attr = path17.node;
1474
+ visitJSXAttribute(path18) {
1475
+ const attr = path18.node;
1476
1476
  if (n3.JSXIdentifier.check(attr.name) && attr.name.name === "data-slot" && n3.StringLiteral.check(attr.value)) {
1477
1477
  const slotValue = attr.value.value;
1478
1478
  if (!slotToComponent.has(slotValue) && currentComponentName) {
1479
1479
  slotToComponent.set(slotValue, currentComponentName);
1480
1480
  }
1481
1481
  }
1482
- this.traverse(path17);
1482
+ this.traverse(path18);
1483
1483
  }
1484
1484
  });
1485
1485
  const tokenRefs = extractTokenReferences(source);
@@ -2333,24 +2333,105 @@ function isGradientValue(name, value) {
2333
2333
  return false;
2334
2334
  }
2335
2335
 
2336
- // src/server/lib/scan-usages.ts
2336
+ // src/server/lib/scan-spacing.ts
2337
2337
  import fs11 from "fs/promises";
2338
2338
  import path11 from "path";
2339
+ var TAILWIND_SPACING_BASE = {
2340
+ name: "spacing",
2341
+ value: "0.25rem",
2342
+ source: "framework-preset",
2343
+ isOverridden: false,
2344
+ cssVariable: "--spacing",
2345
+ isBase: true
2346
+ };
2347
+ async function scanSpacing(projectRoot, framework, styling) {
2348
+ const spacing = [];
2349
+ const allCssFiles = framework.cssFiles.length > 0 ? framework.cssFiles : styling.cssFiles;
2350
+ const cssFilePath = allCssFiles[0] || "";
2351
+ const customSpacing = await scanCustomSpacing(projectRoot, allCssFiles);
2352
+ const overriddenNames = new Set(customSpacing.map((s) => s.name));
2353
+ if (styling.type === "tailwind-v4") {
2354
+ if (!overriddenNames.has("spacing")) {
2355
+ spacing.push(TAILWIND_SPACING_BASE);
2356
+ }
2357
+ } else if (styling.type === "tailwind-v3") {
2358
+ if (!overriddenNames.has("spacing")) {
2359
+ spacing.push({
2360
+ ...TAILWIND_SPACING_BASE,
2361
+ source: "framework-preset"
2362
+ });
2363
+ }
2364
+ }
2365
+ for (const custom of customSpacing) {
2366
+ spacing.push({ ...custom, isOverridden: true });
2367
+ }
2368
+ return { spacing, cssFilePath, stylingType: styling.type };
2369
+ }
2370
+ async function scanCustomSpacing(projectRoot, cssFiles) {
2371
+ const spacing = [];
2372
+ for (const file of cssFiles) {
2373
+ try {
2374
+ const css = await fs11.readFile(path11.join(projectRoot, file), "utf-8");
2375
+ const rootTokens = parseBlock(css, ":root");
2376
+ for (const [name, value] of rootTokens) {
2377
+ const s = classifySpacingToken(name, value);
2378
+ if (s) spacing.push(s);
2379
+ }
2380
+ const themeMatch = css.match(/@theme\s*(?:inline\s*)?\{([\s\S]*?)\}/);
2381
+ if (themeMatch) {
2382
+ const themeBlock = themeMatch[1];
2383
+ const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g;
2384
+ let match;
2385
+ while ((match = propRegex.exec(themeBlock)) !== null) {
2386
+ const name = match[1];
2387
+ const value = match[2].trim();
2388
+ if (!spacing.find((s) => s.name === name.replace(/^--/, ""))) {
2389
+ const s = classifySpacingToken(name, value);
2390
+ if (s) spacing.push(s);
2391
+ }
2392
+ }
2393
+ }
2394
+ } catch {
2395
+ }
2396
+ }
2397
+ return spacing;
2398
+ }
2399
+ function classifySpacingToken(name, value) {
2400
+ const cleanName = name.replace(/^--/, "");
2401
+ if (value.includes("oklch") || value.includes("hsl") || value.includes("rgb") || value.startsWith("#")) {
2402
+ return null;
2403
+ }
2404
+ if (cleanName === "spacing" || cleanName.startsWith("spacing-")) {
2405
+ return {
2406
+ name: cleanName,
2407
+ value,
2408
+ source: "custom",
2409
+ isOverridden: false,
2410
+ cssVariable: name.startsWith("--") ? name : `--${name}`,
2411
+ isBase: cleanName === "spacing"
2412
+ };
2413
+ }
2414
+ return null;
2415
+ }
2416
+
2417
+ // src/server/lib/scan-usages.ts
2418
+ import fs12 from "fs/promises";
2419
+ import path12 from "path";
2339
2420
  var PAGE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
2340
2421
  var LAYOUT_FILES = ["layout", "page"];
2341
2422
  async function scanUsages(projectRoot, framework) {
2342
2423
  if (!framework.appDirExists) {
2343
2424
  return { usages: {} };
2344
2425
  }
2345
- const absAppDir = path11.join(projectRoot, framework.appDir);
2426
+ const absAppDir = path12.join(projectRoot, framework.appDir);
2346
2427
  const usages = {};
2347
2428
  const routeFiles = [];
2348
2429
  await walkAppDir(absAppDir, absAppDir, "", routeFiles);
2349
2430
  for (const { file, route } of routeFiles) {
2350
- const absFile = path11.join(absAppDir, file);
2431
+ const absFile = path12.join(absAppDir, file);
2351
2432
  let source;
2352
2433
  try {
2353
- source = await fs11.readFile(absFile, "utf-8");
2434
+ source = await fs12.readFile(absFile, "utf-8");
2354
2435
  } catch {
2355
2436
  continue;
2356
2437
  }
@@ -2366,7 +2447,7 @@ async function scanUsages(projectRoot, framework) {
2366
2447
  const rf = routeFiles.find((r) => r.route === route);
2367
2448
  return {
2368
2449
  route,
2369
- file: rf ? path11.join(framework.appDir, rf.file) : ""
2450
+ file: rf ? path12.join(framework.appDir, rf.file) : ""
2370
2451
  };
2371
2452
  });
2372
2453
  }
@@ -2375,7 +2456,7 @@ async function scanUsages(projectRoot, framework) {
2375
2456
  async function walkAppDir(baseDir, currentDir, routePrefix, results) {
2376
2457
  let entries;
2377
2458
  try {
2378
- entries = await fs11.readdir(currentDir, { withFileTypes: true });
2459
+ entries = await fs12.readdir(currentDir, { withFileTypes: true });
2379
2460
  } catch {
2380
2461
  return;
2381
2462
  }
@@ -2383,7 +2464,7 @@ async function walkAppDir(baseDir, currentDir, routePrefix, results) {
2383
2464
  for (const ext of PAGE_EXTENSIONS) {
2384
2465
  const candidate = `${base}${ext}`;
2385
2466
  if (entries.some((e) => e.isFile() && e.name === candidate)) {
2386
- const relFile = path11.relative(baseDir, path11.join(currentDir, candidate));
2467
+ const relFile = path12.relative(baseDir, path12.join(currentDir, candidate));
2387
2468
  const route = routePrefix || "/";
2388
2469
  results.push({ file: relFile, route });
2389
2470
  }
@@ -2394,7 +2475,7 @@ async function walkAppDir(baseDir, currentDir, routePrefix, results) {
2394
2475
  const dirName = entry.name;
2395
2476
  if (dirName.startsWith("_") || dirName.startsWith(".")) continue;
2396
2477
  if (dirName === "api") continue;
2397
- const subDir = path11.join(currentDir, dirName);
2478
+ const subDir = path12.join(currentDir, dirName);
2398
2479
  if (dirName.startsWith("(") && dirName.endsWith(")")) {
2399
2480
  await walkAppDir(baseDir, subDir, routePrefix, results);
2400
2481
  } else if (dirName.startsWith("@")) {
@@ -2457,15 +2538,16 @@ var cachedScan = null;
2457
2538
  async function runScan(projectRoot) {
2458
2539
  const framework = await detectFramework(projectRoot);
2459
2540
  const styling = await detectStylingSystem(projectRoot, framework);
2460
- const [tokens, components, shadows, borders, gradients, usages] = await Promise.all([
2541
+ const [tokens, components, shadows, borders, gradients, spacing, usages] = await Promise.all([
2461
2542
  scanTokens(projectRoot, framework),
2462
2543
  scanComponents(projectRoot),
2463
2544
  scanShadows(projectRoot, framework, styling),
2464
2545
  scanBorders(projectRoot, framework, styling),
2465
2546
  scanGradients(projectRoot, framework, styling),
2547
+ scanSpacing(projectRoot, framework, styling),
2466
2548
  scanUsages(projectRoot, framework)
2467
2549
  ]);
2468
- cachedScan = { framework, tokens, components, shadows, borders, gradients, styling, usages };
2550
+ cachedScan = { framework, tokens, components, shadows, borders, gradients, spacing, styling, usages };
2469
2551
  return cachedScan;
2470
2552
  }
2471
2553
  function patchCachedScan(key, value) {
@@ -2491,6 +2573,12 @@ async function rescanBorders(projectRoot) {
2491
2573
  patchCachedScan("borders", borders);
2492
2574
  return borders;
2493
2575
  }
2576
+ async function rescanSpacing(projectRoot) {
2577
+ const scan = cachedScan || await runScan(projectRoot);
2578
+ const spacing = await scanSpacing(projectRoot, scan.framework, scan.styling);
2579
+ patchCachedScan("spacing", spacing);
2580
+ return spacing;
2581
+ }
2494
2582
  async function rescanGradients(projectRoot) {
2495
2583
  const scan = cachedScan || await runScan(projectRoot);
2496
2584
  const gradients = await scanGradients(projectRoot, scan.framework, scan.styling);
@@ -2544,6 +2632,14 @@ function createScanRouter(projectRoot) {
2544
2632
  res.status(500).json({ error: err.message });
2545
2633
  }
2546
2634
  });
2635
+ router.get("/spacing", async (_req, res) => {
2636
+ try {
2637
+ const result = cachedScan || await runScan(projectRoot);
2638
+ res.json(result.spacing);
2639
+ } catch (err) {
2640
+ res.status(500).json({ error: err.message });
2641
+ }
2642
+ });
2547
2643
  router.get("/shadows", async (_req, res) => {
2548
2644
  try {
2549
2645
  const result = cachedScan || await runScan(projectRoot);
@@ -2592,6 +2688,14 @@ function createScanRouter(projectRoot) {
2592
2688
  res.status(500).json({ error: err.message });
2593
2689
  }
2594
2690
  });
2691
+ router.post("/rescan/spacing", async (_req, res) => {
2692
+ try {
2693
+ const spacing = await rescanSpacing(projectRoot);
2694
+ res.json(spacing);
2695
+ } catch (err) {
2696
+ res.status(500).json({ error: err.message });
2697
+ }
2698
+ });
2595
2699
  router.post("/rescan/gradients", async (_req, res) => {
2596
2700
  try {
2597
2701
  const gradients = await rescanGradients(projectRoot);
@@ -2616,26 +2720,26 @@ function createScanRouter(projectRoot) {
2616
2720
  var PAGE_EXTENSIONS2 = [".tsx", ".jsx", ".ts", ".js"];
2617
2721
  async function resolveRouteToFile(projectRoot, appDir, routePath) {
2618
2722
  const segments = routePath === "/" ? [] : routePath.replace(/^\//, "").replace(/\/$/, "").split("/");
2619
- const absAppDir = path12.join(projectRoot, appDir);
2723
+ const absAppDir = path13.join(projectRoot, appDir);
2620
2724
  const result = await matchSegments(absAppDir, segments, 0);
2621
2725
  if (result) {
2622
- return path12.relative(projectRoot, result);
2726
+ return path13.relative(projectRoot, result);
2623
2727
  }
2624
2728
  return null;
2625
2729
  }
2626
2730
  async function findPageFile(dir) {
2627
2731
  for (const ext of PAGE_EXTENSIONS2) {
2628
- const candidate = path12.join(dir, `page${ext}`);
2732
+ const candidate = path13.join(dir, `page${ext}`);
2629
2733
  try {
2630
- await fs12.access(candidate);
2734
+ await fs13.access(candidate);
2631
2735
  return candidate;
2632
2736
  } catch {
2633
2737
  }
2634
2738
  }
2635
2739
  for (const ext of PAGE_EXTENSIONS2) {
2636
- const candidate = path12.join(dir, `index${ext}`);
2740
+ const candidate = path13.join(dir, `index${ext}`);
2637
2741
  try {
2638
- await fs12.access(candidate);
2742
+ await fs13.access(candidate);
2639
2743
  return candidate;
2640
2744
  } catch {
2641
2745
  }
@@ -2644,7 +2748,7 @@ async function findPageFile(dir) {
2644
2748
  }
2645
2749
  async function listDirs(dir) {
2646
2750
  try {
2647
- const entries = await fs12.readdir(dir, { withFileTypes: true });
2751
+ const entries = await fs13.readdir(dir, { withFileTypes: true });
2648
2752
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
2649
2753
  } catch {
2650
2754
  return [];
@@ -2657,7 +2761,7 @@ async function matchSegments(currentDir, segments, index) {
2657
2761
  const dirs2 = await listDirs(currentDir);
2658
2762
  for (const d of dirs2) {
2659
2763
  if (d.startsWith("(") && d.endsWith(")")) {
2660
- const page2 = await findPageFile(path12.join(currentDir, d));
2764
+ const page2 = await findPageFile(path13.join(currentDir, d));
2661
2765
  if (page2) return page2;
2662
2766
  }
2663
2767
  }
@@ -2666,30 +2770,30 @@ async function matchSegments(currentDir, segments, index) {
2666
2770
  const segment = segments[index];
2667
2771
  const dirs = await listDirs(currentDir);
2668
2772
  if (dirs.includes(segment)) {
2669
- const result = await matchSegments(path12.join(currentDir, segment), segments, index + 1);
2773
+ const result = await matchSegments(path13.join(currentDir, segment), segments, index + 1);
2670
2774
  if (result) return result;
2671
2775
  }
2672
2776
  for (const d of dirs) {
2673
2777
  if (d.startsWith("(") && d.endsWith(")")) {
2674
- const result = await matchSegments(path12.join(currentDir, d), segments, index);
2778
+ const result = await matchSegments(path13.join(currentDir, d), segments, index);
2675
2779
  if (result) return result;
2676
2780
  }
2677
2781
  }
2678
2782
  for (const d of dirs) {
2679
2783
  if (d.startsWith("[") && d.endsWith("]") && !d.startsWith("[...") && !d.startsWith("[[")) {
2680
- const result = await matchSegments(path12.join(currentDir, d), segments, index + 1);
2784
+ const result = await matchSegments(path13.join(currentDir, d), segments, index + 1);
2681
2785
  if (result) return result;
2682
2786
  }
2683
2787
  }
2684
2788
  for (const d of dirs) {
2685
2789
  if (d.startsWith("[...") && d.endsWith("]")) {
2686
- const page = await findPageFile(path12.join(currentDir, d));
2790
+ const page = await findPageFile(path13.join(currentDir, d));
2687
2791
  if (page) return page;
2688
2792
  }
2689
2793
  }
2690
2794
  for (const d of dirs) {
2691
2795
  if (d.startsWith("[[...") && d.endsWith("]]")) {
2692
- const page = await findPageFile(path12.join(currentDir, d));
2796
+ const page = await findPageFile(path13.join(currentDir, d));
2693
2797
  if (page) return page;
2694
2798
  }
2695
2799
  }
@@ -2703,9 +2807,9 @@ function createTokensRouter(projectRoot) {
2703
2807
  try {
2704
2808
  const { filePath, token, value, selector } = req.body;
2705
2809
  const fullPath = safePath(projectRoot, filePath);
2706
- let css = await fs13.readFile(fullPath, "utf-8");
2810
+ let css = await fs14.readFile(fullPath, "utf-8");
2707
2811
  css = replaceTokenInBlock(css, selector, token, value);
2708
- await fs13.writeFile(fullPath, css, "utf-8");
2812
+ await fs14.writeFile(fullPath, css, "utf-8");
2709
2813
  const tokens = await rescanTokens(projectRoot);
2710
2814
  res.json({ ok: true, filePath, token, value, tokens });
2711
2815
  } catch (err) {
@@ -2749,16 +2853,16 @@ ${indent}${token}: ${newValue};
2749
2853
 
2750
2854
  // src/server/api/write-component.ts
2751
2855
  import { Router as Router4 } from "express";
2752
- import fs14 from "fs/promises";
2856
+ import fs15 from "fs/promises";
2753
2857
  function createComponentRouter(projectRoot) {
2754
2858
  const router = Router4();
2755
2859
  router.post("/", async (req, res) => {
2756
2860
  try {
2757
2861
  const { filePath, oldClass, newClass, variantContext } = req.body;
2758
2862
  const fullPath = safePath(projectRoot, filePath);
2759
- let source = await fs14.readFile(fullPath, "utf-8");
2863
+ let source = await fs15.readFile(fullPath, "utf-8");
2760
2864
  source = replaceClassInComponent(source, oldClass, newClass, variantContext);
2761
- await fs14.writeFile(fullPath, source, "utf-8");
2865
+ await fs15.writeFile(fullPath, source, "utf-8");
2762
2866
  res.json({ ok: true, filePath, oldClass, newClass });
2763
2867
  } catch (err) {
2764
2868
  console.error("Component write error:", err);
@@ -2852,10 +2956,10 @@ function replaceInCvaBase(source, oldClass, newClass) {
2852
2956
 
2853
2957
  // src/server/api/write-shadows.ts
2854
2958
  import { Router as Router5 } from "express";
2855
- import fs15 from "fs/promises";
2856
- import path13 from "path";
2959
+ import fs16 from "fs/promises";
2960
+ import path14 from "path";
2857
2961
  function safePath2(projectRoot, filePath) {
2858
- const resolved = path13.resolve(projectRoot, filePath);
2962
+ const resolved = path14.resolve(projectRoot, filePath);
2859
2963
  if (!resolved.startsWith(projectRoot)) {
2860
2964
  throw new Error(`Path "${filePath}" escapes project root`);
2861
2965
  }
@@ -2868,17 +2972,17 @@ function createShadowsRouter(projectRoot) {
2868
2972
  const { filePath, variableName, value, selector } = req.body;
2869
2973
  const fullPath = safePath2(projectRoot, filePath);
2870
2974
  if (selector === "scss") {
2871
- let scss = await fs15.readFile(fullPath, "utf-8");
2975
+ let scss = await fs16.readFile(fullPath, "utf-8");
2872
2976
  scss = writeShadowToScss(scss, variableName, value);
2873
- await fs15.writeFile(fullPath, scss, "utf-8");
2977
+ await fs16.writeFile(fullPath, scss, "utf-8");
2874
2978
  } else if (selector === "@theme") {
2875
- let css = await fs15.readFile(fullPath, "utf-8");
2979
+ let css = await fs16.readFile(fullPath, "utf-8");
2876
2980
  css = writeShadowToTheme(css, variableName, value);
2877
- await fs15.writeFile(fullPath, css, "utf-8");
2981
+ await fs16.writeFile(fullPath, css, "utf-8");
2878
2982
  } else {
2879
- let css = await fs15.readFile(fullPath, "utf-8");
2983
+ let css = await fs16.readFile(fullPath, "utf-8");
2880
2984
  css = writeShadowToSelector(css, selector, variableName, value);
2881
- await fs15.writeFile(fullPath, css, "utf-8");
2985
+ await fs16.writeFile(fullPath, css, "utf-8");
2882
2986
  }
2883
2987
  const shadows = await rescanShadows(projectRoot);
2884
2988
  res.json({ ok: true, filePath, variableName, value, shadows });
@@ -2894,20 +2998,20 @@ function createShadowsRouter(projectRoot) {
2894
2998
  if (selector === "scss") {
2895
2999
  let scss;
2896
3000
  try {
2897
- scss = await fs15.readFile(fullPath, "utf-8");
3001
+ scss = await fs16.readFile(fullPath, "utf-8");
2898
3002
  } catch {
2899
3003
  scss = "";
2900
3004
  }
2901
3005
  scss = addShadowToScss(scss, variableName, value);
2902
- await fs15.writeFile(fullPath, scss, "utf-8");
3006
+ await fs16.writeFile(fullPath, scss, "utf-8");
2903
3007
  } else if (selector === "@theme") {
2904
- let css = await fs15.readFile(fullPath, "utf-8");
3008
+ let css = await fs16.readFile(fullPath, "utf-8");
2905
3009
  css = addShadowToTheme(css, variableName, value);
2906
- await fs15.writeFile(fullPath, css, "utf-8");
3010
+ await fs16.writeFile(fullPath, css, "utf-8");
2907
3011
  } else {
2908
- let css = await fs15.readFile(fullPath, "utf-8");
3012
+ let css = await fs16.readFile(fullPath, "utf-8");
2909
3013
  css = addShadowToSelector(css, selector, variableName, value);
2910
- await fs15.writeFile(fullPath, css, "utf-8");
3014
+ await fs16.writeFile(fullPath, css, "utf-8");
2911
3015
  }
2912
3016
  const shadows = await rescanShadows(projectRoot);
2913
3017
  res.json({ ok: true, filePath, variableName, value, shadows });
@@ -2933,7 +3037,7 @@ function createShadowsRouter(projectRoot) {
2933
3037
  const { filePath, shadows } = req.body;
2934
3038
  const fullPath = safePath2(projectRoot, filePath);
2935
3039
  const tokens = buildDesignTokensJson(shadows);
2936
- await fs15.mkdir(path13.dirname(fullPath), { recursive: true });
3040
+ await fs16.mkdir(path14.dirname(fullPath), { recursive: true });
2937
3041
  await writeDesignTokensFile(fullPath, tokens);
2938
3042
  res.json({ ok: true, filePath, tokenCount: shadows.length });
2939
3043
  } catch (err) {
@@ -3051,10 +3155,10 @@ function addShadowToScss(scss, variableName, value) {
3051
3155
 
3052
3156
  // src/server/api/write-gradients.ts
3053
3157
  import { Router as Router6 } from "express";
3054
- import fs16 from "fs/promises";
3055
- import path14 from "path";
3158
+ import fs17 from "fs/promises";
3159
+ import path15 from "path";
3056
3160
  function safePath3(projectRoot, filePath) {
3057
- const resolved = path14.resolve(projectRoot, filePath);
3161
+ const resolved = path15.resolve(projectRoot, filePath);
3058
3162
  if (!resolved.startsWith(projectRoot)) {
3059
3163
  throw new Error(`Path "${filePath}" escapes project root`);
3060
3164
  }
@@ -3066,13 +3170,13 @@ function createGradientsRouter(projectRoot) {
3066
3170
  try {
3067
3171
  const { filePath, variableName, value, selector } = req.body;
3068
3172
  const fullPath = safePath3(projectRoot, filePath);
3069
- let css = await fs16.readFile(fullPath, "utf-8");
3173
+ let css = await fs17.readFile(fullPath, "utf-8");
3070
3174
  if (selector === "@theme") {
3071
3175
  css = writeToTheme(css, variableName, value);
3072
3176
  } else {
3073
3177
  css = writeToSelector(css, selector, variableName, value);
3074
3178
  }
3075
- await fs16.writeFile(fullPath, css, "utf-8");
3179
+ await fs17.writeFile(fullPath, css, "utf-8");
3076
3180
  const [gradients, borders] = await Promise.all([
3077
3181
  rescanGradients(projectRoot),
3078
3182
  rescanBorders(projectRoot)
@@ -3087,13 +3191,13 @@ function createGradientsRouter(projectRoot) {
3087
3191
  try {
3088
3192
  const { filePath, variableName, value, selector } = req.body;
3089
3193
  const fullPath = safePath3(projectRoot, filePath);
3090
- let css = await fs16.readFile(fullPath, "utf-8");
3194
+ let css = await fs17.readFile(fullPath, "utf-8");
3091
3195
  if (selector === "@theme") {
3092
3196
  css = addToTheme(css, variableName, value);
3093
3197
  } else {
3094
3198
  css = addToSelector(css, selector, variableName, value);
3095
3199
  }
3096
- await fs16.writeFile(fullPath, css, "utf-8");
3200
+ await fs17.writeFile(fullPath, css, "utf-8");
3097
3201
  const [gradients, borders] = await Promise.all([
3098
3202
  rescanGradients(projectRoot),
3099
3203
  rescanBorders(projectRoot)
@@ -3108,9 +3212,9 @@ function createGradientsRouter(projectRoot) {
3108
3212
  try {
3109
3213
  const { filePath, variableName, selector } = req.body;
3110
3214
  const fullPath = safePath3(projectRoot, filePath);
3111
- let css = await fs16.readFile(fullPath, "utf-8");
3215
+ let css = await fs17.readFile(fullPath, "utf-8");
3112
3216
  css = deleteFromBlock(css, selector, variableName);
3113
- await fs16.writeFile(fullPath, css, "utf-8");
3217
+ await fs17.writeFile(fullPath, css, "utf-8");
3114
3218
  const [gradients, borders] = await Promise.all([
3115
3219
  rescanGradients(projectRoot),
3116
3220
  rescanBorders(projectRoot)
@@ -3248,7 +3352,7 @@ async function createServer(config) {
3248
3352
  res.status(400).json({ error: "Missing file" });
3249
3353
  return;
3250
3354
  }
3251
- const absPath = path15.isAbsolute(file) ? file : path15.join(config.projectRoot, file);
3355
+ const absPath = path16.isAbsolute(file) ? file : path16.join(config.projectRoot, file);
3252
3356
  const lineCol = line ? `:${line}${col ? `:${col}` : ""}` : "";
3253
3357
  const platform = process.platform;
3254
3358
  const tryOpen = () => {
@@ -3280,15 +3384,15 @@ async function createServer(config) {
3280
3384
  tryOpen();
3281
3385
  });
3282
3386
  app.use("/scan", createScanRouter(config.projectRoot));
3283
- const __dirname = path15.dirname(fileURLToPath(import.meta.url));
3284
- const clientDistPath = path15.join(__dirname, "client");
3285
- const isDev = !fs17.existsSync(path15.join(clientDistPath, "index.html"));
3387
+ const __dirname = path16.dirname(fileURLToPath(import.meta.url));
3388
+ const clientDistPath = path16.join(__dirname, "client");
3389
+ const isDev = !fs18.existsSync(path16.join(clientDistPath, "index.html"));
3286
3390
  let viteDevServer = null;
3287
3391
  if (isDev) {
3288
3392
  const { createServer: createViteServer } = await import("vite");
3289
- const viteRoot = path15.resolve(__dirname, "../client");
3393
+ const viteRoot = path16.resolve(__dirname, "../client");
3290
3394
  viteDevServer = await createViteServer({
3291
- configFile: path15.resolve(__dirname, "../../vite.config.ts"),
3395
+ configFile: path16.resolve(__dirname, "../../vite.config.ts"),
3292
3396
  server: {
3293
3397
  middlewareMode: true,
3294
3398
  hmr: { port: 24679 }
@@ -3299,8 +3403,8 @@ async function createServer(config) {
3299
3403
  app.use(async (req, res, next) => {
3300
3404
  try {
3301
3405
  const url = req.originalUrl || "/";
3302
- const htmlPath = path15.join(viteRoot, "index.html");
3303
- let html = fs17.readFileSync(htmlPath, "utf-8");
3406
+ const htmlPath = path16.join(viteRoot, "index.html");
3407
+ let html = fs18.readFileSync(htmlPath, "utf-8");
3304
3408
  html = await viteDevServer.transformIndexHtml(url, html);
3305
3409
  res.status(200).set({ "Content-Type": "text/html" }).end(html);
3306
3410
  } catch (err) {
@@ -3311,7 +3415,7 @@ async function createServer(config) {
3311
3415
  } else {
3312
3416
  app.use(express.static(clientDistPath));
3313
3417
  app.use((_req, res) => {
3314
- res.sendFile(path15.join(clientDistPath, "index.html"));
3418
+ res.sendFile(path16.join(clientDistPath, "index.html"));
3315
3419
  });
3316
3420
  }
3317
3421
  return { app, viteDevServer };
@@ -3383,8 +3487,8 @@ async function main() {
3383
3487
  console.log(` ${bold("@designtools/codesurface")}`);
3384
3488
  console.log(` ${dim(projectRoot)}`);
3385
3489
  console.log("");
3386
- const pkgPath = path16.join(projectRoot, "package.json");
3387
- if (!fs18.existsSync(pkgPath)) {
3490
+ const pkgPath = path17.join(projectRoot, "package.json");
3491
+ if (!fs19.existsSync(pkgPath)) {
3388
3492
  console.log(` ${red("\u2717")} No package.json found in ${projectRoot}`);
3389
3493
  console.log(` ${dim("Run this command from the root of the app you want to edit.")}`);
3390
3494
  console.log(` ${dim("All file reads and writes are scoped to this directory.")}`);