@designtools/codesurface 0.1.3 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -15
- package/dist/cli.js +217 -109
- package/dist/client/assets/index--eM7ukq5.js +284 -0
- package/dist/client/assets/index-1q-WwsbN.css +1 -0
- package/dist/client/assets/index-A_L26x1h.js +164 -0
- package/dist/client/assets/index-B6jN1q8m.js +221 -0
- package/dist/client/assets/index-B7jQZQIh.js +164 -0
- package/dist/client/assets/index-BCq28ejx.js +274 -0
- package/dist/client/assets/index-BSe1-ayg.js +221 -0
- package/dist/client/assets/index-BoqOSsiL.js +164 -0
- package/dist/client/assets/index-BrTweOBw.css +1 -0
- package/dist/client/assets/index-BvlvZBFo.js +164 -0
- package/dist/client/assets/index-C0W09LqU.js +274 -0
- package/dist/client/assets/index-C8zdCN9Y.css +1 -0
- package/dist/client/assets/index-CNoLX-EE.css +1 -0
- package/dist/client/assets/index-CT_MpeRK.css +1 -0
- package/dist/client/assets/index-Caw3C8nD.js +164 -0
- package/dist/client/assets/index-CiJuTDXP.css +1 -0
- package/dist/client/assets/index-D1sfJ6Vs.js +274 -0
- package/dist/client/assets/index-D4qADRtt.js +221 -0
- package/dist/client/assets/index-DLNdLnNZ.css +1 -0
- package/dist/client/assets/index-DOOYG-7t.js +221 -0
- package/dist/client/assets/index-DOo00IKO.js +164 -0
- package/dist/client/assets/index-DT_WVyln.js +274 -0
- package/dist/client/assets/index-Dd5ywdn8.css +1 -0
- package/dist/client/assets/index-DevUtWzq.js +274 -0
- package/dist/client/assets/index-DnmW8VmS.js +274 -0
- package/dist/client/assets/index-Ndu6k3-t.css +1 -0
- package/dist/client/assets/index-WBV2EURP.js +284 -0
- package/dist/client/assets/index-lOuYsLDH.css +1 -0
- package/dist/client/assets/index-nMHvJQPa.css +1 -0
- package/dist/client/assets/index-s0F6GbJf.js +164 -0
- package/dist/client/assets/index-xf9QDxu1.css +1 -0
- package/dist/client/index.html +2 -2
- package/package.json +4 -1
- package/dist/client/assets/index-B5g8qgFW.css +0 -1
- package/dist/client/assets/index-BB9cuIlF.js +0 -111
- package/dist/client/assets/index-BH-ji2K7.js +0 -150
- package/dist/client/assets/index-BH4MtIeE.js +0 -150
- package/dist/client/assets/index-BIpQhd8p.js +0 -150
- package/dist/client/assets/index-BRJOTqhn.js +0 -130
- package/dist/client/assets/index-Bb8QmYLf.css +0 -1
- package/dist/client/assets/index-Bbi0p5WW.js +0 -150
- package/dist/client/assets/index-BdWkGSfZ.js +0 -130
- package/dist/client/assets/index-BnRqJ-Bb.js +0 -150
- package/dist/client/assets/index-BneJISnX.js +0 -150
- package/dist/client/assets/index-C9_QxP1g.css +0 -1
- package/dist/client/assets/index-CBjuhHfq.js +0 -150
- package/dist/client/assets/index-CEFmdeB7.css +0 -1
- package/dist/client/assets/index-CQpCDasO.js +0 -131
- package/dist/client/assets/index-CRUCeIYi.css +0 -1
- package/dist/client/assets/index-CUSGVUIj.js +0 -150
- package/dist/client/assets/index-CZJQ3vJi.js +0 -150
- package/dist/client/assets/index-CdshIoQY.css +0 -1
- package/dist/client/assets/index-Cne4pc0S.css +0 -1
- package/dist/client/assets/index-Cps9lJoI.css +0 -1
- package/dist/client/assets/index-Cs7mKzsY.css +0 -1
- package/dist/client/assets/index-D-bUCwC3.css +0 -1
- package/dist/client/assets/index-D11nArXR.css +0 -1
- package/dist/client/assets/index-D2DhQd-K.js +0 -51
- package/dist/client/assets/index-D3ju6tgw.css +0 -1
- package/dist/client/assets/index-DG2JRz0f.js +0 -150
- package/dist/client/assets/index-DO-UYUx0.js +0 -150
- package/dist/client/assets/index-DOb58Ii1.js +0 -111
- package/dist/client/assets/index-DVd7DS_W.css +0 -1
- package/dist/client/assets/index-DZNHUm4M.js +0 -130
- package/dist/client/assets/index-Di7zgczR.css +0 -1
- package/dist/client/assets/index-DlUCyHdA.css +0 -1
- package/dist/client/assets/index-FTflg87d.js +0 -130
- package/dist/client/assets/index-U5_kmGxX.js +0 -150
- package/dist/client/assets/index-_4EXPfyt.css +0 -1
- package/dist/client/assets/index-gow3pju2.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
|
220
|
-
import
|
|
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
|
|
|
@@ -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(
|
|
404
|
-
const loc =
|
|
403
|
+
visitJSXOpeningElement(path18) {
|
|
404
|
+
const loc = path18.node.loc;
|
|
405
405
|
if (loc && loc.start.line === line && loc.start.column === col) {
|
|
406
|
-
found =
|
|
406
|
+
found = path18;
|
|
407
407
|
return false;
|
|
408
408
|
}
|
|
409
|
-
this.traverse(
|
|
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(
|
|
418
|
-
const name =
|
|
417
|
+
visitJSXOpeningElement(path18) {
|
|
418
|
+
const name = path18.node.name;
|
|
419
419
|
if (n2.JSXIdentifier.check(name) && name.name === componentName) {
|
|
420
|
-
const loc =
|
|
420
|
+
const loc = path18.node.loc;
|
|
421
421
|
if (loc && loc.start.line === line && loc.start.column === col) {
|
|
422
|
-
found =
|
|
422
|
+
found = path18;
|
|
423
423
|
return false;
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
-
this.traverse(
|
|
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
|
|
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
|
|
1215
|
-
import
|
|
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(
|
|
1385
|
-
for (const decl of
|
|
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(
|
|
1395
|
+
this.traverse(path18);
|
|
1396
1396
|
},
|
|
1397
1397
|
// Find forwardRef and function components to track data-slot and currentComponentName
|
|
1398
|
-
visitCallExpression(
|
|
1399
|
-
const node =
|
|
1398
|
+
visitCallExpression(path18) {
|
|
1399
|
+
const node = path18.node;
|
|
1400
1400
|
if (isForwardRefCall(node)) {
|
|
1401
|
-
const parent =
|
|
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(
|
|
1406
|
+
this.traverse(path18);
|
|
1407
1407
|
},
|
|
1408
1408
|
// Find data-slot JSX attributes
|
|
1409
|
-
visitJSXAttribute(
|
|
1410
|
-
const attr =
|
|
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(
|
|
1413
|
+
const compName = findEnclosingComponentName(path18) || currentComponentName;
|
|
1414
1414
|
if (compName) {
|
|
1415
1415
|
slotToComponent.set(slotValue, compName);
|
|
1416
1416
|
}
|
|
1417
1417
|
}
|
|
1418
|
-
this.traverse(
|
|
1418
|
+
this.traverse(path18);
|
|
1419
1419
|
},
|
|
1420
1420
|
// Detect {...props} spread in JSX — indicates component accepts children
|
|
1421
|
-
visitJSXSpreadAttribute(
|
|
1422
|
-
const expr =
|
|
1421
|
+
visitJSXSpreadAttribute(path18) {
|
|
1422
|
+
const expr = path18.node.argument;
|
|
1423
1423
|
if (n3.Identifier.check(expr) && expr.name === "props") {
|
|
1424
|
-
const compName = findEnclosingComponentName(
|
|
1424
|
+
const compName = findEnclosingComponentName(path18) || currentComponentName;
|
|
1425
1425
|
if (compName) {
|
|
1426
1426
|
acceptsChildrenSet.add(compName);
|
|
1427
1427
|
}
|
|
1428
1428
|
}
|
|
1429
|
-
this.traverse(
|
|
1429
|
+
this.traverse(path18);
|
|
1430
1430
|
},
|
|
1431
1431
|
// Collect named exports: export { Button, Card, ... }
|
|
1432
|
-
visitExportNamedDeclaration(
|
|
1433
|
-
const 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(
|
|
1452
|
+
this.traverse(path18);
|
|
1453
1453
|
},
|
|
1454
1454
|
// export default function Foo
|
|
1455
|
-
visitExportDefaultDeclaration(
|
|
1456
|
-
const decl =
|
|
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(
|
|
1460
|
+
this.traverse(path18);
|
|
1461
1461
|
}
|
|
1462
1462
|
});
|
|
1463
1463
|
recast2.visit(ast, {
|
|
1464
|
-
visitFunctionDeclaration(
|
|
1465
|
-
const name =
|
|
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(
|
|
1468
|
+
this.traverse(path18);
|
|
1469
1469
|
currentComponentName = null;
|
|
1470
1470
|
} else {
|
|
1471
|
-
this.traverse(
|
|
1471
|
+
this.traverse(path18);
|
|
1472
1472
|
}
|
|
1473
1473
|
},
|
|
1474
|
-
visitJSXAttribute(
|
|
1475
|
-
const attr =
|
|
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(
|
|
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-
|
|
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 =
|
|
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 =
|
|
2431
|
+
const absFile = path12.join(absAppDir, file);
|
|
2351
2432
|
let source;
|
|
2352
2433
|
try {
|
|
2353
|
-
source = await
|
|
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 ?
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2723
|
+
const absAppDir = path13.join(projectRoot, appDir);
|
|
2620
2724
|
const result = await matchSegments(absAppDir, segments, 0);
|
|
2621
2725
|
if (result) {
|
|
2622
|
-
return
|
|
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 =
|
|
2732
|
+
const candidate = path13.join(dir, `page${ext}`);
|
|
2629
2733
|
try {
|
|
2630
|
-
await
|
|
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 =
|
|
2740
|
+
const candidate = path13.join(dir, `index${ext}`);
|
|
2637
2741
|
try {
|
|
2638
|
-
await
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2810
|
+
let css = await fs14.readFile(fullPath, "utf-8");
|
|
2707
2811
|
css = replaceTokenInBlock(css, selector, token, value);
|
|
2708
|
-
await
|
|
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) {
|
|
@@ -2737,7 +2841,11 @@ function replaceTokenInBlock(css, selector, token, newValue) {
|
|
|
2737
2841
|
"g"
|
|
2738
2842
|
);
|
|
2739
2843
|
if (!tokenRegex.test(block)) {
|
|
2740
|
-
|
|
2844
|
+
const indent = block.match(/\n(\s+)--/)?.[1] ?? " ";
|
|
2845
|
+
block = block.trimEnd() + `
|
|
2846
|
+
${indent}${token}: ${newValue};
|
|
2847
|
+
`;
|
|
2848
|
+
return css.slice(0, openBrace + 1) + block + css.slice(blockEnd - 1);
|
|
2741
2849
|
}
|
|
2742
2850
|
block = block.replace(tokenRegex, `$1${newValue}$3`);
|
|
2743
2851
|
return css.slice(0, openBrace + 1) + block + css.slice(blockEnd - 1);
|
|
@@ -2745,16 +2853,16 @@ function replaceTokenInBlock(css, selector, token, newValue) {
|
|
|
2745
2853
|
|
|
2746
2854
|
// src/server/api/write-component.ts
|
|
2747
2855
|
import { Router as Router4 } from "express";
|
|
2748
|
-
import
|
|
2856
|
+
import fs15 from "fs/promises";
|
|
2749
2857
|
function createComponentRouter(projectRoot) {
|
|
2750
2858
|
const router = Router4();
|
|
2751
2859
|
router.post("/", async (req, res) => {
|
|
2752
2860
|
try {
|
|
2753
2861
|
const { filePath, oldClass, newClass, variantContext } = req.body;
|
|
2754
2862
|
const fullPath = safePath(projectRoot, filePath);
|
|
2755
|
-
let source = await
|
|
2863
|
+
let source = await fs15.readFile(fullPath, "utf-8");
|
|
2756
2864
|
source = replaceClassInComponent(source, oldClass, newClass, variantContext);
|
|
2757
|
-
await
|
|
2865
|
+
await fs15.writeFile(fullPath, source, "utf-8");
|
|
2758
2866
|
res.json({ ok: true, filePath, oldClass, newClass });
|
|
2759
2867
|
} catch (err) {
|
|
2760
2868
|
console.error("Component write error:", err);
|
|
@@ -2848,10 +2956,10 @@ function replaceInCvaBase(source, oldClass, newClass) {
|
|
|
2848
2956
|
|
|
2849
2957
|
// src/server/api/write-shadows.ts
|
|
2850
2958
|
import { Router as Router5 } from "express";
|
|
2851
|
-
import
|
|
2852
|
-
import
|
|
2959
|
+
import fs16 from "fs/promises";
|
|
2960
|
+
import path14 from "path";
|
|
2853
2961
|
function safePath2(projectRoot, filePath) {
|
|
2854
|
-
const resolved =
|
|
2962
|
+
const resolved = path14.resolve(projectRoot, filePath);
|
|
2855
2963
|
if (!resolved.startsWith(projectRoot)) {
|
|
2856
2964
|
throw new Error(`Path "${filePath}" escapes project root`);
|
|
2857
2965
|
}
|
|
@@ -2864,17 +2972,17 @@ function createShadowsRouter(projectRoot) {
|
|
|
2864
2972
|
const { filePath, variableName, value, selector } = req.body;
|
|
2865
2973
|
const fullPath = safePath2(projectRoot, filePath);
|
|
2866
2974
|
if (selector === "scss") {
|
|
2867
|
-
let scss = await
|
|
2975
|
+
let scss = await fs16.readFile(fullPath, "utf-8");
|
|
2868
2976
|
scss = writeShadowToScss(scss, variableName, value);
|
|
2869
|
-
await
|
|
2977
|
+
await fs16.writeFile(fullPath, scss, "utf-8");
|
|
2870
2978
|
} else if (selector === "@theme") {
|
|
2871
|
-
let css = await
|
|
2979
|
+
let css = await fs16.readFile(fullPath, "utf-8");
|
|
2872
2980
|
css = writeShadowToTheme(css, variableName, value);
|
|
2873
|
-
await
|
|
2981
|
+
await fs16.writeFile(fullPath, css, "utf-8");
|
|
2874
2982
|
} else {
|
|
2875
|
-
let css = await
|
|
2983
|
+
let css = await fs16.readFile(fullPath, "utf-8");
|
|
2876
2984
|
css = writeShadowToSelector(css, selector, variableName, value);
|
|
2877
|
-
await
|
|
2985
|
+
await fs16.writeFile(fullPath, css, "utf-8");
|
|
2878
2986
|
}
|
|
2879
2987
|
const shadows = await rescanShadows(projectRoot);
|
|
2880
2988
|
res.json({ ok: true, filePath, variableName, value, shadows });
|
|
@@ -2890,20 +2998,20 @@ function createShadowsRouter(projectRoot) {
|
|
|
2890
2998
|
if (selector === "scss") {
|
|
2891
2999
|
let scss;
|
|
2892
3000
|
try {
|
|
2893
|
-
scss = await
|
|
3001
|
+
scss = await fs16.readFile(fullPath, "utf-8");
|
|
2894
3002
|
} catch {
|
|
2895
3003
|
scss = "";
|
|
2896
3004
|
}
|
|
2897
3005
|
scss = addShadowToScss(scss, variableName, value);
|
|
2898
|
-
await
|
|
3006
|
+
await fs16.writeFile(fullPath, scss, "utf-8");
|
|
2899
3007
|
} else if (selector === "@theme") {
|
|
2900
|
-
let css = await
|
|
3008
|
+
let css = await fs16.readFile(fullPath, "utf-8");
|
|
2901
3009
|
css = addShadowToTheme(css, variableName, value);
|
|
2902
|
-
await
|
|
3010
|
+
await fs16.writeFile(fullPath, css, "utf-8");
|
|
2903
3011
|
} else {
|
|
2904
|
-
let css = await
|
|
3012
|
+
let css = await fs16.readFile(fullPath, "utf-8");
|
|
2905
3013
|
css = addShadowToSelector(css, selector, variableName, value);
|
|
2906
|
-
await
|
|
3014
|
+
await fs16.writeFile(fullPath, css, "utf-8");
|
|
2907
3015
|
}
|
|
2908
3016
|
const shadows = await rescanShadows(projectRoot);
|
|
2909
3017
|
res.json({ ok: true, filePath, variableName, value, shadows });
|
|
@@ -2929,7 +3037,7 @@ function createShadowsRouter(projectRoot) {
|
|
|
2929
3037
|
const { filePath, shadows } = req.body;
|
|
2930
3038
|
const fullPath = safePath2(projectRoot, filePath);
|
|
2931
3039
|
const tokens = buildDesignTokensJson(shadows);
|
|
2932
|
-
await
|
|
3040
|
+
await fs16.mkdir(path14.dirname(fullPath), { recursive: true });
|
|
2933
3041
|
await writeDesignTokensFile(fullPath, tokens);
|
|
2934
3042
|
res.json({ ok: true, filePath, tokenCount: shadows.length });
|
|
2935
3043
|
} catch (err) {
|
|
@@ -3047,10 +3155,10 @@ function addShadowToScss(scss, variableName, value) {
|
|
|
3047
3155
|
|
|
3048
3156
|
// src/server/api/write-gradients.ts
|
|
3049
3157
|
import { Router as Router6 } from "express";
|
|
3050
|
-
import
|
|
3051
|
-
import
|
|
3158
|
+
import fs17 from "fs/promises";
|
|
3159
|
+
import path15 from "path";
|
|
3052
3160
|
function safePath3(projectRoot, filePath) {
|
|
3053
|
-
const resolved =
|
|
3161
|
+
const resolved = path15.resolve(projectRoot, filePath);
|
|
3054
3162
|
if (!resolved.startsWith(projectRoot)) {
|
|
3055
3163
|
throw new Error(`Path "${filePath}" escapes project root`);
|
|
3056
3164
|
}
|
|
@@ -3062,13 +3170,13 @@ function createGradientsRouter(projectRoot) {
|
|
|
3062
3170
|
try {
|
|
3063
3171
|
const { filePath, variableName, value, selector } = req.body;
|
|
3064
3172
|
const fullPath = safePath3(projectRoot, filePath);
|
|
3065
|
-
let css = await
|
|
3173
|
+
let css = await fs17.readFile(fullPath, "utf-8");
|
|
3066
3174
|
if (selector === "@theme") {
|
|
3067
3175
|
css = writeToTheme(css, variableName, value);
|
|
3068
3176
|
} else {
|
|
3069
3177
|
css = writeToSelector(css, selector, variableName, value);
|
|
3070
3178
|
}
|
|
3071
|
-
await
|
|
3179
|
+
await fs17.writeFile(fullPath, css, "utf-8");
|
|
3072
3180
|
const [gradients, borders] = await Promise.all([
|
|
3073
3181
|
rescanGradients(projectRoot),
|
|
3074
3182
|
rescanBorders(projectRoot)
|
|
@@ -3083,13 +3191,13 @@ function createGradientsRouter(projectRoot) {
|
|
|
3083
3191
|
try {
|
|
3084
3192
|
const { filePath, variableName, value, selector } = req.body;
|
|
3085
3193
|
const fullPath = safePath3(projectRoot, filePath);
|
|
3086
|
-
let css = await
|
|
3194
|
+
let css = await fs17.readFile(fullPath, "utf-8");
|
|
3087
3195
|
if (selector === "@theme") {
|
|
3088
3196
|
css = addToTheme(css, variableName, value);
|
|
3089
3197
|
} else {
|
|
3090
3198
|
css = addToSelector(css, selector, variableName, value);
|
|
3091
3199
|
}
|
|
3092
|
-
await
|
|
3200
|
+
await fs17.writeFile(fullPath, css, "utf-8");
|
|
3093
3201
|
const [gradients, borders] = await Promise.all([
|
|
3094
3202
|
rescanGradients(projectRoot),
|
|
3095
3203
|
rescanBorders(projectRoot)
|
|
@@ -3104,9 +3212,9 @@ function createGradientsRouter(projectRoot) {
|
|
|
3104
3212
|
try {
|
|
3105
3213
|
const { filePath, variableName, selector } = req.body;
|
|
3106
3214
|
const fullPath = safePath3(projectRoot, filePath);
|
|
3107
|
-
let css = await
|
|
3215
|
+
let css = await fs17.readFile(fullPath, "utf-8");
|
|
3108
3216
|
css = deleteFromBlock(css, selector, variableName);
|
|
3109
|
-
await
|
|
3217
|
+
await fs17.writeFile(fullPath, css, "utf-8");
|
|
3110
3218
|
const [gradients, borders] = await Promise.all([
|
|
3111
3219
|
rescanGradients(projectRoot),
|
|
3112
3220
|
rescanBorders(projectRoot)
|
|
@@ -3244,7 +3352,7 @@ async function createServer(config) {
|
|
|
3244
3352
|
res.status(400).json({ error: "Missing file" });
|
|
3245
3353
|
return;
|
|
3246
3354
|
}
|
|
3247
|
-
const absPath =
|
|
3355
|
+
const absPath = path16.isAbsolute(file) ? file : path16.join(config.projectRoot, file);
|
|
3248
3356
|
const lineCol = line ? `:${line}${col ? `:${col}` : ""}` : "";
|
|
3249
3357
|
const platform = process.platform;
|
|
3250
3358
|
const tryOpen = () => {
|
|
@@ -3276,15 +3384,15 @@ async function createServer(config) {
|
|
|
3276
3384
|
tryOpen();
|
|
3277
3385
|
});
|
|
3278
3386
|
app.use("/scan", createScanRouter(config.projectRoot));
|
|
3279
|
-
const __dirname =
|
|
3280
|
-
const clientDistPath =
|
|
3281
|
-
const isDev = !
|
|
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"));
|
|
3282
3390
|
let viteDevServer = null;
|
|
3283
3391
|
if (isDev) {
|
|
3284
3392
|
const { createServer: createViteServer } = await import("vite");
|
|
3285
|
-
const viteRoot =
|
|
3393
|
+
const viteRoot = path16.resolve(__dirname, "../client");
|
|
3286
3394
|
viteDevServer = await createViteServer({
|
|
3287
|
-
configFile:
|
|
3395
|
+
configFile: path16.resolve(__dirname, "../../vite.config.ts"),
|
|
3288
3396
|
server: {
|
|
3289
3397
|
middlewareMode: true,
|
|
3290
3398
|
hmr: { port: 24679 }
|
|
@@ -3295,8 +3403,8 @@ async function createServer(config) {
|
|
|
3295
3403
|
app.use(async (req, res, next) => {
|
|
3296
3404
|
try {
|
|
3297
3405
|
const url = req.originalUrl || "/";
|
|
3298
|
-
const htmlPath =
|
|
3299
|
-
let html =
|
|
3406
|
+
const htmlPath = path16.join(viteRoot, "index.html");
|
|
3407
|
+
let html = fs18.readFileSync(htmlPath, "utf-8");
|
|
3300
3408
|
html = await viteDevServer.transformIndexHtml(url, html);
|
|
3301
3409
|
res.status(200).set({ "Content-Type": "text/html" }).end(html);
|
|
3302
3410
|
} catch (err) {
|
|
@@ -3307,7 +3415,7 @@ async function createServer(config) {
|
|
|
3307
3415
|
} else {
|
|
3308
3416
|
app.use(express.static(clientDistPath));
|
|
3309
3417
|
app.use((_req, res) => {
|
|
3310
|
-
res.sendFile(
|
|
3418
|
+
res.sendFile(path16.join(clientDistPath, "index.html"));
|
|
3311
3419
|
});
|
|
3312
3420
|
}
|
|
3313
3421
|
return { app, viteDevServer };
|
|
@@ -3379,8 +3487,8 @@ async function main() {
|
|
|
3379
3487
|
console.log(` ${bold("@designtools/codesurface")}`);
|
|
3380
3488
|
console.log(` ${dim(projectRoot)}`);
|
|
3381
3489
|
console.log("");
|
|
3382
|
-
const pkgPath =
|
|
3383
|
-
if (!
|
|
3490
|
+
const pkgPath = path17.join(projectRoot, "package.json");
|
|
3491
|
+
if (!fs19.existsSync(pkgPath)) {
|
|
3384
3492
|
console.log(` ${red("\u2717")} No package.json found in ${projectRoot}`);
|
|
3385
3493
|
console.log(` ${dim("Run this command from the root of the app you want to edit.")}`);
|
|
3386
3494
|
console.log(` ${dim("All file reads and writes are scoped to this directory.")}`);
|