truecanvas 0.2.0 → 0.3.0
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 +54 -183
- package/dist/catalog-worker.js +1 -1
- package/dist/chunk-PBKX3SBC.js +97 -0
- package/dist/chunk-TSHD2S36.js +28 -0
- package/dist/{chunk-T2TCBSTG.js → chunk-WU7NBTJH.js} +25 -18
- package/dist/{chunk-ZPARY6OD.js → chunk-XWQ5MS24.js} +362 -335
- package/dist/cli.js +105 -59
- package/dist/core/index.d.ts +42 -9
- package/dist/core/index.js +16 -25
- package/dist/editor/assets/hub-UhU8yp5F.js +2 -0
- package/dist/editor/assets/{main-DlADROMw.js → main-UtSaCfnK.js} +2 -2
- package/dist/editor/hub.html +1 -1
- package/dist/editor/index.html +1 -1
- package/dist/next/index.js +3 -2
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.js +81 -0
- package/package.json +15 -6
- package/dist/editor/assets/hub-BnbEAnt6.js +0 -2
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectFramework,
|
|
3
|
+
idOf,
|
|
4
|
+
isComponentName,
|
|
5
|
+
jsxName,
|
|
6
|
+
parseModule,
|
|
7
|
+
rewriteImportSource,
|
|
8
|
+
walk
|
|
9
|
+
} from "./chunk-PBKX3SBC.js";
|
|
1
10
|
import {
|
|
2
11
|
Catalog,
|
|
3
12
|
importSpecifier,
|
|
13
|
+
posix,
|
|
4
14
|
resolveAliasPath
|
|
5
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-WU7NBTJH.js";
|
|
6
16
|
import {
|
|
7
17
|
ICON_LIBRARIES,
|
|
8
18
|
iconLibrary,
|
|
@@ -10,86 +20,6 @@ import {
|
|
|
10
20
|
searchIcons
|
|
11
21
|
} from "./chunk-C4SATRZT.js";
|
|
12
22
|
|
|
13
|
-
// src/core/config.ts
|
|
14
|
-
import fs from "fs";
|
|
15
|
-
import path from "path";
|
|
16
|
-
function loadConfig(root, overrides = {}) {
|
|
17
|
-
let file = {};
|
|
18
|
-
const configPath = path.join(root, "truecanvas.config.json");
|
|
19
|
-
if (fs.existsSync(configPath)) file = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
20
|
-
const appDir = file.appDir ?? (fs.existsSync(path.join(root, "src/app")) ? "src/app" : "app");
|
|
21
|
-
const srcPrefix = appDir.startsWith("src/") ? "src/" : "";
|
|
22
|
-
let deps = {};
|
|
23
|
-
try {
|
|
24
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
25
|
-
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
26
|
-
} catch {
|
|
27
|
-
deps = {};
|
|
28
|
-
}
|
|
29
|
-
const knownLibraries = ["@paper-design/shaders-react"].filter((l) => l in deps);
|
|
30
|
-
return {
|
|
31
|
-
root,
|
|
32
|
-
appUrl: "http://localhost:3000",
|
|
33
|
-
port: 4800,
|
|
34
|
-
canvasDir: `${srcPrefix}canvas`,
|
|
35
|
-
appDir,
|
|
36
|
-
components: [`${srcPrefix}components/**/*.tsx`],
|
|
37
|
-
darkMode: { strategy: "class", value: "dark" },
|
|
38
|
-
...file,
|
|
39
|
-
libraries: [.../* @__PURE__ */ new Set([...knownLibraries, ...file.libraries ?? []])],
|
|
40
|
-
...stripUndefined(overrides)
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function stripUndefined(o) {
|
|
44
|
-
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/core/ast.ts
|
|
48
|
-
import { parse } from "@babel/parser";
|
|
49
|
-
function parseModule(source) {
|
|
50
|
-
return parse(source, {
|
|
51
|
-
sourceType: "module",
|
|
52
|
-
plugins: ["typescript", "jsx"],
|
|
53
|
-
errorRecovery: false
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function idOf(node) {
|
|
57
|
-
const loc = node.loc.start;
|
|
58
|
-
return `${loc.line}:${loc.column}`;
|
|
59
|
-
}
|
|
60
|
-
function jsxName(name) {
|
|
61
|
-
if (name.type === "JSXIdentifier") return name.name;
|
|
62
|
-
if (name.type === "JSXNamespacedName") return `${name.namespace.name}:${name.name.name}`;
|
|
63
|
-
return `${jsxName(name.object)}.${name.property.name}`;
|
|
64
|
-
}
|
|
65
|
-
function walk(node, visit, parent = null) {
|
|
66
|
-
if (!node || typeof node !== "object") return;
|
|
67
|
-
if (Array.isArray(node)) {
|
|
68
|
-
for (const child of node) walk(child, visit, parent);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const n = node;
|
|
72
|
-
if (typeof n.type !== "string") return;
|
|
73
|
-
if (visit(n, parent) === false) return;
|
|
74
|
-
for (const key of Object.keys(n)) {
|
|
75
|
-
if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments" || key === "extra") continue;
|
|
76
|
-
const value = n[key];
|
|
77
|
-
if (value && typeof value === "object") walk(value, visit, n);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function isComponentName(name) {
|
|
81
|
-
return /^[A-Z]/.test(name) || name.includes(".");
|
|
82
|
-
}
|
|
83
|
-
function rewriteImportSource(statement, from, to) {
|
|
84
|
-
if (from === to) return statement;
|
|
85
|
-
for (const q of ['"', "'"]) {
|
|
86
|
-
const needle = `${q}${from}${q}`;
|
|
87
|
-
const i = statement.indexOf(needle);
|
|
88
|
-
if (i >= 0) return `${statement.slice(0, i)}${q}${to}${q}${statement.slice(i + needle.length)}`;
|
|
89
|
-
}
|
|
90
|
-
return statement;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
23
|
// src/core/parse.ts
|
|
94
24
|
function parseCanvas(name, file, source) {
|
|
95
25
|
let ast;
|
|
@@ -118,8 +48,8 @@ function parseCanvas(name, file, source) {
|
|
|
118
48
|
let frameIndex = 0;
|
|
119
49
|
for (const child of canvasNode.children) {
|
|
120
50
|
if (child.kind !== "component" || child.name !== "Frame") continue;
|
|
121
|
-
const
|
|
122
|
-
reindex(child,
|
|
51
|
+
const path14 = [frameIndex++];
|
|
52
|
+
reindex(child, path14);
|
|
123
53
|
frames.push({
|
|
124
54
|
...child,
|
|
125
55
|
frameName: str(child.props.name) ?? `Frame ${frameIndex}`,
|
|
@@ -170,9 +100,9 @@ function topLevelNames(ast) {
|
|
|
170
100
|
}
|
|
171
101
|
return out;
|
|
172
102
|
}
|
|
173
|
-
function reindex(node,
|
|
174
|
-
node.path =
|
|
175
|
-
node.children.forEach((c, i) => reindex(c, [...
|
|
103
|
+
function reindex(node, path14) {
|
|
104
|
+
node.path = path14;
|
|
105
|
+
node.children.forEach((c, i) => reindex(c, [...path14, i]));
|
|
176
106
|
}
|
|
177
107
|
function str(v) {
|
|
178
108
|
return v?.kind === "string" ? v.value : void 0;
|
|
@@ -180,10 +110,10 @@ function str(v) {
|
|
|
180
110
|
function num(v) {
|
|
181
111
|
return v?.kind === "number" ? v.value : void 0;
|
|
182
112
|
}
|
|
183
|
-
function buildNode(el, source,
|
|
113
|
+
function buildNode(el, source, path14) {
|
|
184
114
|
const children = [];
|
|
185
115
|
for (const child of el.children) {
|
|
186
|
-
const built = buildChild(child, source, [...
|
|
116
|
+
const built = buildChild(child, source, [...path14, children.length]);
|
|
187
117
|
if (built) children.push(built);
|
|
188
118
|
}
|
|
189
119
|
if (el.type === "JSXFragment") {
|
|
@@ -198,7 +128,7 @@ function buildNode(el, source, path15) {
|
|
|
198
128
|
end: el.end,
|
|
199
129
|
line: el.loc.start.line,
|
|
200
130
|
col: el.loc.start.column,
|
|
201
|
-
path:
|
|
131
|
+
path: path14,
|
|
202
132
|
attrs: [],
|
|
203
133
|
nameEnd: el.openingFragment.end - 1,
|
|
204
134
|
openEnd: el.openingFragment.end,
|
|
@@ -233,7 +163,7 @@ function buildNode(el, source, path15) {
|
|
|
233
163
|
end: el.end,
|
|
234
164
|
line: open.loc.start.line,
|
|
235
165
|
col: open.loc.start.column,
|
|
236
|
-
path:
|
|
166
|
+
path: path14,
|
|
237
167
|
attrs,
|
|
238
168
|
// after type arguments: `<Select<Opt> …>`
|
|
239
169
|
nameEnd: (open.typeParameters ?? open.name).end,
|
|
@@ -242,27 +172,27 @@ function buildNode(el, source, path15) {
|
|
|
242
172
|
closeStart: el.closingElement ? el.closingElement.start : open.end
|
|
243
173
|
};
|
|
244
174
|
}
|
|
245
|
-
function buildChild(child, source,
|
|
246
|
-
if (child.type === "JSXElement" || child.type === "JSXFragment") return buildNode(child, source,
|
|
175
|
+
function buildChild(child, source, path14) {
|
|
176
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment") return buildNode(child, source, path14);
|
|
247
177
|
if (child.type === "JSXText") {
|
|
248
178
|
const text = child.value.replace(/\s+/g, " ").trim();
|
|
249
179
|
if (!text) return null;
|
|
250
|
-
return leaf("text", text, child,
|
|
180
|
+
return leaf("text", text, child, path14, { text });
|
|
251
181
|
}
|
|
252
182
|
if (child.type === "JSXExpressionContainer") {
|
|
253
183
|
const expr = child.expression;
|
|
254
184
|
if (expr.type === "JSXEmptyExpression") return null;
|
|
255
|
-
if (expr.type === "StringLiteral") return leaf("text", expr.value, child,
|
|
185
|
+
if (expr.type === "StringLiteral") return leaf("text", expr.value, child, path14, { text: expr.value });
|
|
256
186
|
if (expr.type === "TemplateLiteral" && expr.expressions.length === 0) {
|
|
257
187
|
const text = expr.quasis.map((q) => q.value.cooked ?? "").join("");
|
|
258
|
-
return leaf("text", text, child,
|
|
188
|
+
return leaf("text", text, child, path14, { text });
|
|
259
189
|
}
|
|
260
|
-
return leaf("expression", source.slice(expr.start, expr.end), child,
|
|
190
|
+
return leaf("expression", source.slice(expr.start, expr.end), child, path14, {});
|
|
261
191
|
}
|
|
262
|
-
if (child.type === "JSXSpreadChild") return leaf("expression", source.slice(child.start, child.end), child,
|
|
192
|
+
if (child.type === "JSXSpreadChild") return leaf("expression", source.slice(child.start, child.end), child, path14, {});
|
|
263
193
|
return null;
|
|
264
194
|
}
|
|
265
|
-
function leaf(kind, name, node,
|
|
195
|
+
function leaf(kind, name, node, path14, extra) {
|
|
266
196
|
return {
|
|
267
197
|
id: `${kind[0]}${idOf(node)}`,
|
|
268
198
|
kind,
|
|
@@ -275,7 +205,7 @@ function leaf(kind, name, node, path15, extra) {
|
|
|
275
205
|
end: node.end,
|
|
276
206
|
line: node.loc.start.line,
|
|
277
207
|
col: node.loc.start.column,
|
|
278
|
-
path:
|
|
208
|
+
path: path14,
|
|
279
209
|
attrs: [],
|
|
280
210
|
nameEnd: node.end,
|
|
281
211
|
openEnd: node.end,
|
|
@@ -339,10 +269,10 @@ function findNode(doc, id) {
|
|
|
339
269
|
function findFrame(doc, ref) {
|
|
340
270
|
return doc.frames.find((f) => f.id === ref || f.frameName === ref) ?? null;
|
|
341
271
|
}
|
|
342
|
-
function findByPath(doc,
|
|
343
|
-
if (!
|
|
344
|
-
let node = doc.frames[
|
|
345
|
-
for (const i of
|
|
272
|
+
function findByPath(doc, path14) {
|
|
273
|
+
if (!path14.length) return null;
|
|
274
|
+
let node = doc.frames[path14[0]];
|
|
275
|
+
for (const i of path14.slice(1)) node = node?.children[i];
|
|
346
276
|
return node ?? null;
|
|
347
277
|
}
|
|
348
278
|
|
|
@@ -561,9 +491,9 @@ ${pi}`);
|
|
|
561
491
|
this.s.appendLeft(this.contentEnd(node), `
|
|
562
492
|
${this.lineIndent(node.start)}${marked}`);
|
|
563
493
|
if (marked === code.trim()) {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
this.selectPaths.push(
|
|
494
|
+
const path14 = [...node.path];
|
|
495
|
+
path14[path14.length - 1] += 1;
|
|
496
|
+
this.selectPaths.push(path14);
|
|
567
497
|
}
|
|
568
498
|
}
|
|
569
499
|
move(node, target, index) {
|
|
@@ -811,23 +741,17 @@ function findDevice(id) {
|
|
|
811
741
|
return id ? DEVICES.find((d) => d.id === id) : void 0;
|
|
812
742
|
}
|
|
813
743
|
|
|
814
|
-
// src/core/comments.ts
|
|
815
|
-
import fs3 from "fs";
|
|
816
|
-
import path3 from "path";
|
|
817
|
-
import { randomUUID } from "crypto";
|
|
818
|
-
|
|
819
744
|
// src/core/scaffold.ts
|
|
820
|
-
import
|
|
821
|
-
import
|
|
745
|
+
import fs from "fs";
|
|
746
|
+
import path from "path";
|
|
822
747
|
var CANVAS_SUFFIX = ".canvas.tsx";
|
|
823
|
-
var ROUTE = "truecanvas";
|
|
824
748
|
function canvasFiles(config) {
|
|
825
|
-
const dir =
|
|
826
|
-
if (!
|
|
827
|
-
return
|
|
749
|
+
const dir = path.join(config.root, config.canvasDir);
|
|
750
|
+
if (!fs.existsSync(dir)) return [];
|
|
751
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(CANVAS_SUFFIX)).sort((a, b) => canvasNameOf(a).localeCompare(canvasNameOf(b)));
|
|
828
752
|
}
|
|
829
753
|
function canvasNameOf(file) {
|
|
830
|
-
return
|
|
754
|
+
return path.basename(file).slice(0, -CANVAS_SUFFIX.length);
|
|
831
755
|
}
|
|
832
756
|
function assertCanvasName(name) {
|
|
833
757
|
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) throw new Error(`Invalid canvas name "${name}". Use letters, digits, - and _.`);
|
|
@@ -835,23 +759,25 @@ function assertCanvasName(name) {
|
|
|
835
759
|
}
|
|
836
760
|
function canvasPath(config, name) {
|
|
837
761
|
assertCanvasName(name);
|
|
838
|
-
return
|
|
762
|
+
return path.join(config.root, config.canvasDir, `${name}${CANVAS_SUFFIX}`);
|
|
839
763
|
}
|
|
840
764
|
function writeIfChanged(file, content) {
|
|
841
|
-
if (
|
|
842
|
-
|
|
843
|
-
|
|
765
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === content) return false;
|
|
766
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
767
|
+
fs.writeFileSync(file, content);
|
|
844
768
|
return true;
|
|
845
769
|
}
|
|
770
|
+
function routeDirOf(config) {
|
|
771
|
+
return path.join(config.root, config.routeDir);
|
|
772
|
+
}
|
|
846
773
|
function syncRoute(config) {
|
|
847
|
-
const routeDir =
|
|
848
|
-
const
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
let rel = path2.relative(routeDir, canvasDirAbs);
|
|
774
|
+
const routeDir = routeDirOf(config);
|
|
775
|
+
const registryFile = path.join(routeDir, "canvases.tsx");
|
|
776
|
+
const canvasDirAbs = path.join(config.root, config.canvasDir);
|
|
777
|
+
let rel = posix(path.relative(routeDir, canvasDirAbs));
|
|
852
778
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
853
|
-
const compareDir =
|
|
854
|
-
const compares =
|
|
779
|
+
const compareDir = path.join(routeDir, "compare");
|
|
780
|
+
const compares = fs.existsSync(compareDir) ? fs.readdirSync(compareDir).filter((f) => f.endsWith(CANVAS_SUFFIX)) : [];
|
|
855
781
|
const entries = [
|
|
856
782
|
...canvasFiles(config).map((f) => ` ${JSON.stringify(canvasNameOf(f))}: lazy(() => import(${JSON.stringify(`${rel}/${f.replace(/\.tsx$/, "")}`)})),`),
|
|
857
783
|
// older versions rendered for Compare
|
|
@@ -873,6 +799,10 @@ export default function Canvases(props: Omit<HostProps, "canvases" | "components
|
|
|
873
799
|
}
|
|
874
800
|
`;
|
|
875
801
|
const dm = config.darkMode;
|
|
802
|
+
const componentsFile = path.join(routeDir, "components.tsx");
|
|
803
|
+
if (!fs.existsSync(componentsFile)) writeIfChanged(componentsFile, componentRegistry(config, []));
|
|
804
|
+
const a = writeIfChanged(registryFile, registry);
|
|
805
|
+
if (config.framework === "vite") return writeIfChanged(path.join(routeDir, "entry.tsx"), viteEntry(config)) || a;
|
|
876
806
|
const page = `/* eslint-disable */
|
|
877
807
|
// Generated by Truecanvas. Dev-only route that renders canvas frames.
|
|
878
808
|
import { notFound } from "next/navigation";
|
|
@@ -900,11 +830,51 @@ export default async function TruecanvasFrame({
|
|
|
900
830
|
);
|
|
901
831
|
}
|
|
902
832
|
`;
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
return
|
|
833
|
+
return writeIfChanged(path.join(routeDir, "[canvas]", "page.tsx"), page) || a;
|
|
834
|
+
}
|
|
835
|
+
function viteEntry(config) {
|
|
836
|
+
const css = (config.css ?? viteStylesheets(config.root)).map((f) => f.startsWith("/") ? f : f.startsWith("./") ? f.slice(1) : fs.existsSync(path.join(config.root, f)) ? `/${f}` : f);
|
|
837
|
+
return `/* eslint-disable */
|
|
838
|
+
// Generated by Truecanvas. Dev-only entry that renders canvas frames at /truecanvas/<canvas>.
|
|
839
|
+
${css.map((f) => `import ${JSON.stringify(f)};`).join("\n")}
|
|
840
|
+
import { createRoot } from "react-dom/client";
|
|
841
|
+
import Canvases from "./canvases";
|
|
842
|
+
|
|
843
|
+
const query = new URLSearchParams(location.search);
|
|
844
|
+
const canvas = decodeURIComponent(location.pathname.split("/")[2] ?? "");
|
|
845
|
+
|
|
846
|
+
createRoot(document.getElementById("tc-root")!).render(
|
|
847
|
+
<Canvases
|
|
848
|
+
canvas={canvas}
|
|
849
|
+
frame={query.get("frame")}
|
|
850
|
+
theme={(query.get("theme") as "light" | "dark" | "system" | null) ?? "system"}
|
|
851
|
+
darkMode={${JSON.stringify(config.darkMode)}}
|
|
852
|
+
/>,
|
|
853
|
+
);
|
|
854
|
+
`;
|
|
855
|
+
}
|
|
856
|
+
function viteStylesheets(root) {
|
|
857
|
+
let html = "";
|
|
858
|
+
try {
|
|
859
|
+
html = fs.readFileSync(path.join(root, "index.html"), "utf8");
|
|
860
|
+
} catch {
|
|
861
|
+
return [];
|
|
862
|
+
}
|
|
863
|
+
const src = /<script[^>]*type=["']module["'][^>]*src=["']([^"']+)["']/i.exec(html)?.[1] ?? /<script[^>]*src=["']([^"']+)["'][^>]*type=["']module["']/i.exec(html)?.[1];
|
|
864
|
+
if (!src || /^https?:/.test(src)) return [];
|
|
865
|
+
const entry = path.join(root, src.replace(/^\//, ""));
|
|
866
|
+
let code = "";
|
|
867
|
+
try {
|
|
868
|
+
code = fs.readFileSync(entry, "utf8");
|
|
869
|
+
} catch {
|
|
870
|
+
return [];
|
|
871
|
+
}
|
|
872
|
+
const out = [];
|
|
873
|
+
for (const m of code.matchAll(/^\s*import\s+["']([^"']+\.(?:css|scss|sass|less|styl|pcss))["']/gm)) {
|
|
874
|
+
const spec = m[1];
|
|
875
|
+
out.push(spec.startsWith(".") ? `/${posix(path.relative(root, path.resolve(path.dirname(entry), spec)))}` : spec);
|
|
876
|
+
}
|
|
877
|
+
return out;
|
|
908
878
|
}
|
|
909
879
|
var STARTER_CANVAS = `"use client";
|
|
910
880
|
import { Canvas, Frame } from "truecanvas";
|
|
@@ -977,11 +947,11 @@ export default function StarterCanvas() {
|
|
|
977
947
|
}
|
|
978
948
|
`;
|
|
979
949
|
function componentRegistry(config, specs) {
|
|
980
|
-
const routeDir =
|
|
950
|
+
const routeDir = routeDirOf(config);
|
|
981
951
|
const lines = specs.map((c) => {
|
|
982
952
|
let from = c.library;
|
|
983
953
|
if (!from) {
|
|
984
|
-
from =
|
|
954
|
+
from = posix(path.relative(routeDir, path.join(config.root, c.file))).replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
985
955
|
if (!from.startsWith(".")) from = `./${from}`;
|
|
986
956
|
}
|
|
987
957
|
return ` ${JSON.stringify(c.name)}: lazy(() => import(${JSON.stringify(from)}).then((m) => ({ default: m[${JSON.stringify(c.name)}] }))),`;
|
|
@@ -998,22 +968,25 @@ ${lines.join("\n")}
|
|
|
998
968
|
`;
|
|
999
969
|
}
|
|
1000
970
|
function syncComponentRegistry(config, specs) {
|
|
1001
|
-
const file =
|
|
971
|
+
const file = path.join(routeDirOf(config), "components.tsx");
|
|
1002
972
|
return writeIfChanged(file, componentRegistry(config, specs));
|
|
1003
973
|
}
|
|
1004
974
|
|
|
1005
975
|
// src/core/comments.ts
|
|
976
|
+
import fs2 from "fs";
|
|
977
|
+
import path2 from "path";
|
|
978
|
+
import { randomUUID } from "crypto";
|
|
1006
979
|
var Comments = class {
|
|
1007
980
|
constructor(config) {
|
|
1008
981
|
this.config = config;
|
|
1009
982
|
}
|
|
1010
983
|
config;
|
|
1011
984
|
file(canvas) {
|
|
1012
|
-
return
|
|
985
|
+
return path2.join(this.config.root, this.config.canvasDir, `${assertCanvasName(canvas)}.comments.json`);
|
|
1013
986
|
}
|
|
1014
987
|
list(canvas) {
|
|
1015
988
|
try {
|
|
1016
|
-
return JSON.parse(
|
|
989
|
+
return JSON.parse(fs2.readFileSync(this.file(canvas), "utf8")).threads ?? [];
|
|
1017
990
|
} catch {
|
|
1018
991
|
return [];
|
|
1019
992
|
}
|
|
@@ -1021,11 +994,11 @@ var Comments = class {
|
|
|
1021
994
|
save(canvas, threads) {
|
|
1022
995
|
const file = this.file(canvas);
|
|
1023
996
|
if (!threads.length) {
|
|
1024
|
-
if (
|
|
997
|
+
if (fs2.existsSync(file)) fs2.rmSync(file);
|
|
1025
998
|
return;
|
|
1026
999
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1000
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
1001
|
+
fs2.writeFileSync(file, `${JSON.stringify({ version: 1, threads }, null, 2)}
|
|
1029
1002
|
`);
|
|
1030
1003
|
}
|
|
1031
1004
|
update(canvas, id, fn) {
|
|
@@ -1080,14 +1053,13 @@ var Comments = class {
|
|
|
1080
1053
|
}
|
|
1081
1054
|
renameCanvas(from, to) {
|
|
1082
1055
|
const a = this.file(from);
|
|
1083
|
-
if (
|
|
1056
|
+
if (fs2.existsSync(a)) fs2.renameSync(a, this.file(to));
|
|
1084
1057
|
}
|
|
1085
1058
|
};
|
|
1086
1059
|
|
|
1087
1060
|
// src/core/git.ts
|
|
1088
1061
|
import { execFile } from "child_process";
|
|
1089
|
-
import
|
|
1090
|
-
import path4 from "path";
|
|
1062
|
+
import path3 from "path";
|
|
1091
1063
|
var GitError = class extends EditError {
|
|
1092
1064
|
};
|
|
1093
1065
|
function git(cwd, args, timeout = 3e4, opts = {}) {
|
|
@@ -1127,6 +1099,7 @@ var Git = class {
|
|
|
1127
1099
|
return { repo: false, root: null, branch: null, unborn: false, upstream: null, ahead: 0, behind: 0, files: [], user: null, defaultBranch: null, remote: null };
|
|
1128
1100
|
}
|
|
1129
1101
|
const out = await git(this.cwd, ["status", "--porcelain=v2", "-z", "--branch", "--untracked-files=all", "--", "."]);
|
|
1102
|
+
const prefix = await this.prefix();
|
|
1130
1103
|
const s = { repo: true, root, branch: null, unborn: false, upstream: null, ahead: 0, behind: 0, files: [], user: null, defaultBranch: null, remote: null };
|
|
1131
1104
|
const entries = out.split("\0");
|
|
1132
1105
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -1143,10 +1116,10 @@ var Git = class {
|
|
|
1143
1116
|
const file = parts.slice(line.startsWith("2 ") ? 9 : 8).join(" ");
|
|
1144
1117
|
if (line.startsWith("2 ")) i++;
|
|
1145
1118
|
const code = xy.replace(".", "")[0] ?? "M";
|
|
1146
|
-
s.files.push({ path: this.rel(
|
|
1119
|
+
s.files.push({ path: this.rel(prefix, file), status: code === "A" ? "added" : code === "D" ? "deleted" : code === "R" ? "renamed" : "modified", staged: xy[0] !== "." });
|
|
1147
1120
|
} else if (line.startsWith("u ")) {
|
|
1148
|
-
s.files.push({ path: this.rel(
|
|
1149
|
-
} else if (line.startsWith("? ")) s.files.push({ path: this.rel(
|
|
1121
|
+
s.files.push({ path: this.rel(prefix, line.split(" ").slice(10).join(" ")), status: "conflicted", staged: false });
|
|
1122
|
+
} else if (line.startsWith("? ")) s.files.push({ path: this.rel(prefix, line.slice(2)), status: "untracked", staged: false });
|
|
1150
1123
|
}
|
|
1151
1124
|
s.user = (await git(this.cwd, ["config", "user.name"]).catch(() => "")).trim() || null;
|
|
1152
1125
|
s.remote = (await git(this.cwd, ["remote", "get-url", "origin"]).catch(() => "")).trim() || null;
|
|
@@ -1201,8 +1174,8 @@ var Git = class {
|
|
|
1201
1174
|
safeRef(branch);
|
|
1202
1175
|
const tip = (await git(this.cwd, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]).catch(() => "")).trim() || (await git(this.cwd, ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${branch}`]).catch(() => "")).trim();
|
|
1203
1176
|
const os = await import("os");
|
|
1204
|
-
const
|
|
1205
|
-
const index =
|
|
1177
|
+
const fs13 = await import("fs");
|
|
1178
|
+
const index = path3.join(os.tmpdir(), `truecanvas-index-${process.pid}-${Date.now()}`);
|
|
1206
1179
|
const env = { GIT_INDEX_FILE: index };
|
|
1207
1180
|
try {
|
|
1208
1181
|
if (tip) await git(this.cwd, ["read-tree", tip], 3e4, { env });
|
|
@@ -1216,7 +1189,7 @@ var Git = class {
|
|
|
1216
1189
|
await git(this.cwd, ["update-ref", `refs/heads/${branch}`, commit]);
|
|
1217
1190
|
await git(this.cwd, ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`], 12e4);
|
|
1218
1191
|
} finally {
|
|
1219
|
-
|
|
1192
|
+
fs13.rmSync(index, { force: true });
|
|
1220
1193
|
}
|
|
1221
1194
|
}
|
|
1222
1195
|
// ---------- GitHub (gh CLI) ----------
|
|
@@ -1250,19 +1223,16 @@ var Git = class {
|
|
|
1250
1223
|
return (await git(this.cwd, ["remote", "get-url", "origin"])).trim();
|
|
1251
1224
|
}
|
|
1252
1225
|
/** git reports paths from the repo root; we show them from the project root. */
|
|
1253
|
-
rel(
|
|
1254
|
-
return
|
|
1226
|
+
rel(prefix, file) {
|
|
1227
|
+
return file.startsWith(prefix) ? file.slice(prefix.length) : posix(path3.posix.relative(prefix, file));
|
|
1255
1228
|
}
|
|
1256
1229
|
/**
|
|
1257
|
-
* The project folder
|
|
1258
|
-
*
|
|
1230
|
+
* The project folder inside the repo ("apps/web/", or "" at the root), as git
|
|
1231
|
+
* itself sees it. Asking git instead of comparing file system paths holds up
|
|
1232
|
+
* through symlinks (macOS /var → /private/var), junctions and Windows short names.
|
|
1259
1233
|
*/
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return fs4.realpathSync(this.cwd);
|
|
1263
|
-
} catch {
|
|
1264
|
-
return this.cwd;
|
|
1265
|
-
}
|
|
1234
|
+
async prefix() {
|
|
1235
|
+
return (await git(this.cwd, ["rev-parse", "--show-prefix"]).catch(() => "")).trim();
|
|
1266
1236
|
}
|
|
1267
1237
|
async branches() {
|
|
1268
1238
|
const out = await git(this.cwd, ["branch", "-a", "--format=%(refname)%09%(HEAD)"]).catch(() => "");
|
|
@@ -1290,8 +1260,7 @@ var Git = class {
|
|
|
1290
1260
|
}
|
|
1291
1261
|
/** A file's content at a ref, or null if it doesn't exist there. */
|
|
1292
1262
|
async show(ref, file) {
|
|
1293
|
-
const
|
|
1294
|
-
const inRepo = path4.relative(root, path4.join(this.realCwd(), file)).split(path4.sep).join("/");
|
|
1263
|
+
const inRepo = path3.posix.normalize(`${await this.prefix()}${posix(file)}`);
|
|
1295
1264
|
return git(this.cwd, ["show", `${safeRef(ref)}:${inRepo}`, "--"]).catch(() => null);
|
|
1296
1265
|
}
|
|
1297
1266
|
async commit(message, files) {
|
|
@@ -1320,17 +1289,17 @@ var Git = class {
|
|
|
1320
1289
|
};
|
|
1321
1290
|
|
|
1322
1291
|
// src/core/workspace.ts
|
|
1323
|
-
import
|
|
1324
|
-
import
|
|
1292
|
+
import fs7 from "fs";
|
|
1293
|
+
import path8 from "path";
|
|
1325
1294
|
import { createRequire } from "module";
|
|
1326
1295
|
|
|
1327
1296
|
// src/core/link.ts
|
|
1328
|
-
import
|
|
1329
|
-
import
|
|
1297
|
+
import fs4 from "fs";
|
|
1298
|
+
import path5 from "path";
|
|
1330
1299
|
|
|
1331
1300
|
// src/core/routes.ts
|
|
1332
|
-
import
|
|
1333
|
-
import
|
|
1301
|
+
import fs3 from "fs";
|
|
1302
|
+
import path4 from "path";
|
|
1334
1303
|
|
|
1335
1304
|
// src/core/source.ts
|
|
1336
1305
|
function defaultReturn(source, layout) {
|
|
@@ -1542,28 +1511,29 @@ function parseTargetDoc(file, source) {
|
|
|
1542
1511
|
var PAGE = /^page\.(tsx|jsx|ts|js)$/;
|
|
1543
1512
|
var LAYOUT = ["layout.tsx", "layout.jsx", "layout.js", "layout.ts"];
|
|
1544
1513
|
function listRoutes(config) {
|
|
1545
|
-
|
|
1514
|
+
if (config.framework !== "next") return [];
|
|
1515
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1546
1516
|
const out = [];
|
|
1547
1517
|
const walkDir = (dir, segments) => {
|
|
1548
1518
|
let entries;
|
|
1549
1519
|
try {
|
|
1550
|
-
entries =
|
|
1520
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1551
1521
|
} catch {
|
|
1552
1522
|
return;
|
|
1553
1523
|
}
|
|
1554
1524
|
for (const e of entries) {
|
|
1555
1525
|
if (e.isFile() && PAGE.test(e.name)) {
|
|
1556
1526
|
const visible = segments.filter((s) => !/^\(.*\)$/.test(s) && !s.startsWith("@"));
|
|
1557
|
-
out.push({ route: `/${visible.join("/")}`, file:
|
|
1527
|
+
out.push({ route: `/${visible.join("/")}`, file: posix(path4.relative(config.root, path4.join(dir, e.name))), dynamic: segments.some((s) => s.startsWith("[")) });
|
|
1558
1528
|
}
|
|
1559
|
-
if (e.isDirectory() && !e.name.startsWith("_") && e.name !== "truecanvas" && e.name !== "api" && e.name !== "node_modules") walkDir(
|
|
1529
|
+
if (e.isDirectory() && !e.name.startsWith("_") && e.name !== "truecanvas" && e.name !== "api" && e.name !== "node_modules") walkDir(path4.join(dir, e.name), [...segments, e.name]);
|
|
1560
1530
|
}
|
|
1561
1531
|
};
|
|
1562
1532
|
walkDir(appDir, []);
|
|
1563
1533
|
return out.sort((a, b) => a.route.localeCompare(b.route));
|
|
1564
1534
|
}
|
|
1565
1535
|
function extract(file, canvasFile, config, layout) {
|
|
1566
|
-
const source =
|
|
1536
|
+
const source = fs3.readFileSync(file, "utf8");
|
|
1567
1537
|
const info = defaultReturn(source, layout);
|
|
1568
1538
|
if (!info?.pure) return null;
|
|
1569
1539
|
const imports = [];
|
|
@@ -1579,26 +1549,26 @@ function extract(file, canvasFile, config, layout) {
|
|
|
1579
1549
|
}
|
|
1580
1550
|
function repoint(config, spec, fromFile, toFile) {
|
|
1581
1551
|
if (!spec.startsWith(".")) return spec;
|
|
1582
|
-
return importSpecifier(config.root,
|
|
1552
|
+
return importSpecifier(config.root, path4.relative(config.root, toFile), path4.relative(config.root, path4.resolve(path4.dirname(fromFile), spec)));
|
|
1583
1553
|
}
|
|
1584
1554
|
function layoutFiles(config, pageFile) {
|
|
1585
|
-
const appDir =
|
|
1555
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1586
1556
|
const out = [];
|
|
1587
|
-
for (let dir =
|
|
1588
|
-
const file = LAYOUT.map((f) =>
|
|
1589
|
-
if (file) out.push(
|
|
1557
|
+
for (let dir = path4.dirname(path4.resolve(config.root, pageFile)); dir.startsWith(appDir) && dir !== appDir; dir = path4.dirname(dir)) {
|
|
1558
|
+
const file = LAYOUT.map((f) => path4.join(dir, f)).find((f) => fs3.existsSync(f));
|
|
1559
|
+
if (file) out.push(posix(path4.relative(config.root, file)));
|
|
1590
1560
|
}
|
|
1591
1561
|
return out;
|
|
1592
1562
|
}
|
|
1593
1563
|
function routeOf(config, pageFile) {
|
|
1594
|
-
const rel =
|
|
1595
|
-
const visible = rel.split(
|
|
1564
|
+
const rel = path4.relative(path4.join(config.root, config.appDir), path4.dirname(path4.resolve(config.root, pageFile)));
|
|
1565
|
+
const visible = rel.split(path4.sep).filter((s) => s && !/^\(.*\)$/.test(s) && !s.startsWith("@"));
|
|
1596
1566
|
return `/${visible.join("/")}`;
|
|
1597
1567
|
}
|
|
1598
1568
|
function routeFrame(config, pageFile, canvasFile) {
|
|
1599
|
-
const appDir =
|
|
1600
|
-
const pageAbs =
|
|
1601
|
-
const canvasAbs =
|
|
1569
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1570
|
+
const pageAbs = path4.resolve(config.root, pageFile);
|
|
1571
|
+
const canvasAbs = path4.resolve(config.root, canvasFile);
|
|
1602
1572
|
const imports = [];
|
|
1603
1573
|
let mode = "inline";
|
|
1604
1574
|
const page = extract(pageAbs, canvasAbs, config, false);
|
|
@@ -1612,8 +1582,8 @@ function routeFrame(config, pageFile, canvasFile) {
|
|
|
1612
1582
|
jsx = "<RoutePage />";
|
|
1613
1583
|
}
|
|
1614
1584
|
let i = 0;
|
|
1615
|
-
for (let dir =
|
|
1616
|
-
const file = LAYOUT.map((f) =>
|
|
1585
|
+
for (let dir = path4.dirname(pageAbs); dir.startsWith(appDir) && dir !== appDir; dir = path4.dirname(dir)) {
|
|
1586
|
+
const file = LAYOUT.map((f) => path4.join(dir, f)).find((f) => fs3.existsSync(f));
|
|
1617
1587
|
if (!file) continue;
|
|
1618
1588
|
const layout = extract(file, canvasAbs, config, true);
|
|
1619
1589
|
const holes = layout ? (layout.jsx.match(/\{\s*children\s*\}/g) ?? []).length : 0;
|
|
@@ -1623,7 +1593,7 @@ function routeFrame(config, pageFile, canvasFile) {
|
|
|
1623
1593
|
imports.push(...layout.imports);
|
|
1624
1594
|
} else {
|
|
1625
1595
|
const local = `RouteLayout${i++ || ""}`;
|
|
1626
|
-
imports.push(`import ${local} from ${JSON.stringify(importSpecifier(config.root, canvasFile,
|
|
1596
|
+
imports.push(`import ${local} from ${JSON.stringify(importSpecifier(config.root, canvasFile, path4.relative(config.root, file)))};`);
|
|
1627
1597
|
jsx = `<${local}>${jsx}</${local}>`;
|
|
1628
1598
|
}
|
|
1629
1599
|
}
|
|
@@ -1644,16 +1614,16 @@ function wrapperChain(frame) {
|
|
|
1644
1614
|
}
|
|
1645
1615
|
function readSource(config, rel) {
|
|
1646
1616
|
try {
|
|
1647
|
-
return
|
|
1617
|
+
return fs4.readFileSync(path5.join(config.root, rel), "utf8");
|
|
1648
1618
|
} catch {
|
|
1649
1619
|
return null;
|
|
1650
1620
|
}
|
|
1651
1621
|
}
|
|
1652
1622
|
var parseMemo = /* @__PURE__ */ new Map();
|
|
1653
|
-
function memoParse(key, source,
|
|
1623
|
+
function memoParse(key, source, parse) {
|
|
1654
1624
|
let hit = parseMemo.get(key);
|
|
1655
1625
|
if (!hit || hit.source !== source) {
|
|
1656
|
-
hit = { source, doc:
|
|
1626
|
+
hit = { source, doc: parse() };
|
|
1657
1627
|
parseMemo.set(key, hit);
|
|
1658
1628
|
}
|
|
1659
1629
|
return hit.doc && { ...hit.doc, frames: hit.doc.frames.map(cloneTree) };
|
|
@@ -1755,7 +1725,7 @@ function linkedFrameBody(config, doc, canvasRel, page) {
|
|
|
1755
1725
|
const pageName = importName(config, doc, canvasRel, page, `${segments.length ? pascal(segments[segments.length - 1]) : "Home"}Page`, taken, imports);
|
|
1756
1726
|
let jsx = `<${pageName} />`;
|
|
1757
1727
|
for (const rel of layoutFiles(config, page)) {
|
|
1758
|
-
const dir =
|
|
1728
|
+
const dir = path5.basename(path5.dirname(rel));
|
|
1759
1729
|
const name = importName(config, doc, canvasRel, rel, `${pascal(dir) || "Route"}Layout`, taken, imports);
|
|
1760
1730
|
jsx = `<${name}>
|
|
1761
1731
|
${indentBlock(jsx)}
|
|
@@ -1777,7 +1747,7 @@ function dedent(code) {
|
|
|
1777
1747
|
function explorationBody(config, frame, canvasRel) {
|
|
1778
1748
|
if (frame.component) throw new EditError(`"${frame.frameName}" is a main component: edit it directly, every instance follows.`);
|
|
1779
1749
|
if (!frame.page) throw new EditError(`"${frame.frameName}" isn't linked to a page.`);
|
|
1780
|
-
const pageAbs =
|
|
1750
|
+
const pageAbs = path5.join(config.root, frame.page);
|
|
1781
1751
|
const source = readSource(config, frame.page);
|
|
1782
1752
|
if (source === null) throw new EditError(`${frame.page} doesn't exist anymore.`);
|
|
1783
1753
|
const chain = wrapperChain(frame);
|
|
@@ -1789,7 +1759,7 @@ function explorationBody(config, frame, canvasRel) {
|
|
|
1789
1759
|
for (const stmt of info.ast.program.body) {
|
|
1790
1760
|
if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type" || /\.(css|scss|sass|less)$/.test(stmt.source.value)) continue;
|
|
1791
1761
|
const src = stmt.source.value;
|
|
1792
|
-
const from = repoint(config, src, pageAbs,
|
|
1762
|
+
const from = repoint(config, src, pageAbs, path5.join(config.root, canvasRel));
|
|
1793
1763
|
const text = source.slice(stmt.start, stmt.end);
|
|
1794
1764
|
imports.push(from === src ? text : rewriteImportSource(text, src, from));
|
|
1795
1765
|
}
|
|
@@ -1805,7 +1775,7 @@ ${indentBlock(jsx)}
|
|
|
1805
1775
|
function applyPlan(config, canvasSource, doc, frame, canvasRel) {
|
|
1806
1776
|
const page = frame.from;
|
|
1807
1777
|
if (!page) throw new EditError(`"${frame.frameName}" isn't an exploration of a page. Explore a copy of a linked frame first.`);
|
|
1808
|
-
if (!
|
|
1778
|
+
if (!fs4.existsSync(path5.join(config.root, page))) throw new EditError(`${page} doesn't exist anymore.`);
|
|
1809
1779
|
const layoutSpecs = new Set(doc.imports.filter((i) => /(^|\/)layout(\.\w+)?$/.test(i.source)).flatMap((i) => [...i.names, ...i.defaultName ? [i.defaultName] : []]));
|
|
1810
1780
|
let content = frame.children;
|
|
1811
1781
|
for (; ; ) {
|
|
@@ -1831,7 +1801,7 @@ ${indentBlock(body)}
|
|
|
1831
1801
|
continue;
|
|
1832
1802
|
}
|
|
1833
1803
|
const sp = decl.specifiers.find((x) => x.local.name === name);
|
|
1834
|
-
const from = repoint(config, decl.source.value,
|
|
1804
|
+
const from = repoint(config, decl.source.value, path5.join(config.root, canvasRel), path5.join(config.root, page));
|
|
1835
1805
|
const what = sp.type === "ImportDefaultSpecifier" ? name : sp.type === "ImportNamespaceSpecifier" ? `* as ${name}` : `{ ${sp.imported.type === "Identifier" && sp.imported.name !== name ? `${sp.imported.name} as ${name}` : name} }`;
|
|
1836
1806
|
imports.push(`import ${what} from ${JSON.stringify(from)};`);
|
|
1837
1807
|
}
|
|
@@ -1864,8 +1834,8 @@ function pruneImports(source, candidates) {
|
|
|
1864
1834
|
}
|
|
1865
1835
|
|
|
1866
1836
|
// src/core/motion.ts
|
|
1867
|
-
import
|
|
1868
|
-
import
|
|
1837
|
+
import fs5 from "fs";
|
|
1838
|
+
import path6 from "path";
|
|
1869
1839
|
var REVEAL = `"use client";
|
|
1870
1840
|
|
|
1871
1841
|
import { Children, cloneElement, isValidElement, useEffect, useRef, useState, type CSSProperties, type ReactElement, type ReactNode } from "react";
|
|
@@ -2090,8 +2060,8 @@ export function TextAnimate({ effect = "words", stagger = 0.06, delay = 0, durat
|
|
|
2090
2060
|
function motionFiles(config) {
|
|
2091
2061
|
const glob = config.components[0] ?? "components/**/*.tsx";
|
|
2092
2062
|
const base = glob.split("/").filter((p) => !p.includes("*")).join("/") || "components";
|
|
2093
|
-
const dir =
|
|
2094
|
-
return { reveal:
|
|
2063
|
+
const dir = `${base}/motion`;
|
|
2064
|
+
return { reveal: `${dir}/reveal.tsx`, text: `${dir}/text-animate.tsx` };
|
|
2095
2065
|
}
|
|
2096
2066
|
function ensureMotionComponents(config) {
|
|
2097
2067
|
const files = motionFiles(config);
|
|
@@ -2100,18 +2070,18 @@ function ensureMotionComponents(config) {
|
|
|
2100
2070
|
[files.reveal, REVEAL],
|
|
2101
2071
|
[files.text, TEXT_ANIMATE]
|
|
2102
2072
|
]) {
|
|
2103
|
-
const abs =
|
|
2104
|
-
if (
|
|
2105
|
-
|
|
2106
|
-
|
|
2073
|
+
const abs = path6.join(config.root, rel);
|
|
2074
|
+
if (fs5.existsSync(abs)) continue;
|
|
2075
|
+
fs5.mkdirSync(path6.dirname(abs), { recursive: true });
|
|
2076
|
+
fs5.writeFileSync(abs, content);
|
|
2107
2077
|
created = true;
|
|
2108
2078
|
}
|
|
2109
2079
|
return { ...files, created };
|
|
2110
2080
|
}
|
|
2111
2081
|
|
|
2112
2082
|
// src/core/components.ts
|
|
2113
|
-
import
|
|
2114
|
-
import
|
|
2083
|
+
import fs6 from "fs";
|
|
2084
|
+
import path7 from "path";
|
|
2115
2085
|
function componentsDir(config) {
|
|
2116
2086
|
const glob = config.components[0] ?? "components/**/*.tsx";
|
|
2117
2087
|
return glob.split("/").filter((p) => !p.includes("*")).join("/") || "components";
|
|
@@ -2175,11 +2145,11 @@ function importsFor(config, jsx, source, doc, fromRel, toRel) {
|
|
|
2175
2145
|
(s) => s.type === "ImportDeclaration" && s.importKind !== "type" && s.specifiers.some((sp2) => sp2.local.name === name)
|
|
2176
2146
|
);
|
|
2177
2147
|
if (!decl) {
|
|
2178
|
-
if (doc.declared.includes(name)) throw new EditError(`These layers use ${name}, defined in ${
|
|
2148
|
+
if (doc.declared.includes(name)) throw new EditError(`These layers use ${name}, defined in ${path7.basename(fromRel)}. Move it to its own file first, or ask your agent to extract the component.`);
|
|
2179
2149
|
throw new EditError(`These layers use \`${name}\` from the surrounding code, so they can't become a component automatically. Ask your agent to extract it with props.`);
|
|
2180
2150
|
}
|
|
2181
2151
|
const sp = decl.specifiers.find((x) => x.local.name === name);
|
|
2182
|
-
const from = repoint(config, decl.source.value,
|
|
2152
|
+
const from = repoint(config, decl.source.value, path7.join(config.root, fromRel), path7.join(config.root, toRel));
|
|
2183
2153
|
const what = sp.type === "ImportDefaultSpecifier" ? name : sp.type === "ImportNamespaceSpecifier" ? `* as ${name}` : `{ ${sp.imported.type === "Identifier" && sp.imported.name !== name ? `${sp.imported.name} as ${name}` : name} }`;
|
|
2184
2154
|
out.push(`import ${what} from ${JSON.stringify(from)};`);
|
|
2185
2155
|
}
|
|
@@ -2203,8 +2173,8 @@ ${indentBlock(indentBlock(jsx.trim()))}
|
|
|
2203
2173
|
`;
|
|
2204
2174
|
}
|
|
2205
2175
|
function newComponentPath(config, name) {
|
|
2206
|
-
const rel =
|
|
2207
|
-
if (
|
|
2176
|
+
const rel = `${componentsDir(config)}/${kebab(name)}.tsx`;
|
|
2177
|
+
if (fs6.existsSync(path7.join(config.root, rel))) throw new EditError(`${rel} already exists. Pick another name.`);
|
|
2208
2178
|
return rel;
|
|
2209
2179
|
}
|
|
2210
2180
|
|
|
@@ -2224,7 +2194,7 @@ var Workspace = class {
|
|
|
2224
2194
|
this.catalog = new Catalog(config.root, config.components, config.libraries);
|
|
2225
2195
|
this.comments = new Comments(config);
|
|
2226
2196
|
this.git = new Git(config.root);
|
|
2227
|
-
for (const c of this.canvases()) this.sources.set(c,
|
|
2197
|
+
for (const c of this.canvases()) this.sources.set(c, fs7.readFileSync(this.file(c), "utf8"));
|
|
2228
2198
|
}
|
|
2229
2199
|
config;
|
|
2230
2200
|
catalog;
|
|
@@ -2255,12 +2225,12 @@ var Workspace = class {
|
|
|
2255
2225
|
return canvasPath(this.config, canvas);
|
|
2256
2226
|
}
|
|
2257
2227
|
relFile(canvas) {
|
|
2258
|
-
return
|
|
2228
|
+
return posix(path8.relative(this.config.root, this.file(canvas)));
|
|
2259
2229
|
}
|
|
2260
2230
|
read(canvas) {
|
|
2261
2231
|
const file = this.file(canvas);
|
|
2262
|
-
if (!
|
|
2263
|
-
const src =
|
|
2232
|
+
if (!fs7.existsSync(file)) throw new EditError(`Canvas "${canvas}" does not exist. Canvases: ${this.canvases().join(", ") || "none"}`);
|
|
2233
|
+
const src = fs7.readFileSync(file, "utf8");
|
|
2264
2234
|
if (!this.sources.has(canvas)) this.sources.set(canvas, src);
|
|
2265
2235
|
return src;
|
|
2266
2236
|
}
|
|
@@ -2285,7 +2255,7 @@ var Workspace = class {
|
|
|
2285
2255
|
docCache = /* @__PURE__ */ new Map();
|
|
2286
2256
|
stamp(rel) {
|
|
2287
2257
|
try {
|
|
2288
|
-
const st =
|
|
2258
|
+
const st = fs7.statSync(path8.join(this.config.root, rel));
|
|
2289
2259
|
return `${st.mtimeMs}:${st.size}`;
|
|
2290
2260
|
} catch {
|
|
2291
2261
|
return "missing";
|
|
@@ -2299,8 +2269,8 @@ var Workspace = class {
|
|
|
2299
2269
|
const linked = fileOfId(id);
|
|
2300
2270
|
if (linked) assertShown(doc, linked);
|
|
2301
2271
|
const rel = linked ?? this.relFile(canvas);
|
|
2302
|
-
const abs =
|
|
2303
|
-
return { node: hit.node, rel, abs, source:
|
|
2272
|
+
const abs = path8.join(this.config.root, rel);
|
|
2273
|
+
return { node: hit.node, rel, abs, source: fs7.readFileSync(abs, "utf8") };
|
|
2304
2274
|
}
|
|
2305
2275
|
/** Page and layout files shown by linked frames, across all canvases (the Git panel counts them as design files). */
|
|
2306
2276
|
linkedFiles() {
|
|
@@ -2319,7 +2289,7 @@ var Workspace = class {
|
|
|
2319
2289
|
*/
|
|
2320
2290
|
designFiles() {
|
|
2321
2291
|
const m = motionFiles(this.config);
|
|
2322
|
-
return [...this.linkedFiles(), ...[m.reveal, m.text].filter((f) =>
|
|
2292
|
+
return [...this.linkedFiles(), ...[m.reveal, m.text].filter((f) => fs7.existsSync(path8.join(this.config.root, f)))];
|
|
2323
2293
|
}
|
|
2324
2294
|
/** Canvases with a linked frame showing this page or layout file. */
|
|
2325
2295
|
canvasesLinking(rel) {
|
|
@@ -2335,7 +2305,7 @@ var Workspace = class {
|
|
|
2335
2305
|
linkedFileChanged(rel) {
|
|
2336
2306
|
let next;
|
|
2337
2307
|
try {
|
|
2338
|
-
next =
|
|
2308
|
+
next = fs7.readFileSync(path8.join(this.config.root, rel), "utf8");
|
|
2339
2309
|
} catch {
|
|
2340
2310
|
next = "";
|
|
2341
2311
|
}
|
|
@@ -2347,9 +2317,9 @@ var Workspace = class {
|
|
|
2347
2317
|
}
|
|
2348
2318
|
createCanvas(name, actor, content = STARTER_CANVAS) {
|
|
2349
2319
|
const file = canvasPath(this.config, name);
|
|
2350
|
-
if (
|
|
2351
|
-
|
|
2352
|
-
|
|
2320
|
+
if (fs7.existsSync(file)) throw new EditError(`Canvas "${name}" already exists.`);
|
|
2321
|
+
fs7.mkdirSync(path8.dirname(file), { recursive: true });
|
|
2322
|
+
fs7.writeFileSync(file, content);
|
|
2353
2323
|
this.sources.set(name, content);
|
|
2354
2324
|
this.canvasesChanged();
|
|
2355
2325
|
this.record(name, `Created canvas ${name}`, actor, "", content, []);
|
|
@@ -2384,10 +2354,10 @@ var Workspace = class {
|
|
|
2384
2354
|
}
|
|
2385
2355
|
renameCanvas(from, to) {
|
|
2386
2356
|
const src = this.file(from);
|
|
2387
|
-
if (!
|
|
2357
|
+
if (!fs7.existsSync(src)) throw new EditError(`Canvas "${from}" does not exist.`);
|
|
2388
2358
|
const dest = canvasPath(this.config, to);
|
|
2389
|
-
if (
|
|
2390
|
-
|
|
2359
|
+
if (fs7.existsSync(dest)) throw new EditError(`A canvas named "${to}" already exists.`);
|
|
2360
|
+
fs7.renameSync(src, dest);
|
|
2391
2361
|
this.comments.renameCanvas(from, to);
|
|
2392
2362
|
for (const map of [this.sources, this.undoStacks, this.redoStacks]) {
|
|
2393
2363
|
if (map.has(from)) {
|
|
@@ -2404,7 +2374,7 @@ var Workspace = class {
|
|
|
2404
2374
|
duplicateCanvas(name) {
|
|
2405
2375
|
const source = this.read(name);
|
|
2406
2376
|
const copy = this.uniqueCanvasName(`${name}-copy`);
|
|
2407
|
-
|
|
2377
|
+
fs7.writeFileSync(canvasPath(this.config, copy), source);
|
|
2408
2378
|
this.sources.set(copy, source);
|
|
2409
2379
|
this.canvasesChanged();
|
|
2410
2380
|
return copy;
|
|
@@ -2413,9 +2383,9 @@ var Workspace = class {
|
|
|
2413
2383
|
trash = /* @__PURE__ */ new Map();
|
|
2414
2384
|
deleteCanvas(name) {
|
|
2415
2385
|
const file = this.file(name);
|
|
2416
|
-
if (!
|
|
2417
|
-
this.trash.set(name,
|
|
2418
|
-
|
|
2386
|
+
if (!fs7.existsSync(file)) throw new EditError(`Canvas "${name}" does not exist.`);
|
|
2387
|
+
this.trash.set(name, fs7.readFileSync(file, "utf8"));
|
|
2388
|
+
fs7.unlinkSync(file);
|
|
2419
2389
|
this.sources.delete(name);
|
|
2420
2390
|
this.undoStacks.delete(name);
|
|
2421
2391
|
this.redoStacks.delete(name);
|
|
@@ -2425,7 +2395,7 @@ var Workspace = class {
|
|
|
2425
2395
|
const source = this.trash.get(name);
|
|
2426
2396
|
if (source === void 0) throw new EditError(`Nothing to restore for "${name}".`);
|
|
2427
2397
|
const target = this.uniqueCanvasName(name);
|
|
2428
|
-
|
|
2398
|
+
fs7.writeFileSync(canvasPath(this.config, target), source);
|
|
2429
2399
|
this.sources.set(target, source);
|
|
2430
2400
|
this.trash.delete(name);
|
|
2431
2401
|
this.canvasesChanged();
|
|
@@ -2436,14 +2406,14 @@ var Workspace = class {
|
|
|
2436
2406
|
if (!fileName.endsWith(CANVAS_SUFFIX)) return;
|
|
2437
2407
|
const canvas = canvasNameOf(fileName);
|
|
2438
2408
|
const file = this.file(canvas);
|
|
2439
|
-
if (!
|
|
2409
|
+
if (!fs7.existsSync(file)) {
|
|
2440
2410
|
this.sources.delete(canvas);
|
|
2441
2411
|
syncRoute(this.config);
|
|
2442
2412
|
this.emit({ type: "canvases", canvases: this.canvases() });
|
|
2443
2413
|
return;
|
|
2444
2414
|
}
|
|
2445
2415
|
const prev = this.sources.get(canvas);
|
|
2446
|
-
const next =
|
|
2416
|
+
const next = fs7.readFileSync(file, "utf8");
|
|
2447
2417
|
if (prev === next) return;
|
|
2448
2418
|
this.sources.set(canvas, next);
|
|
2449
2419
|
if (prev === void 0) {
|
|
@@ -2462,8 +2432,8 @@ var Workspace = class {
|
|
|
2462
2432
|
} catch (err) {
|
|
2463
2433
|
if (!(err instanceof ConflictError)) throw err;
|
|
2464
2434
|
if (err.file) this.linkedFileChanged(err.file);
|
|
2465
|
-
else this.externalChange(
|
|
2466
|
-
if (nodeRefs(cmd).length || "frame" in cmd && cmd.frame) throw new EditError(`${
|
|
2435
|
+
else this.externalChange(path8.basename(this.file(cmd.canvas)));
|
|
2436
|
+
if (nodeRefs(cmd).length || "frame" in cmd && cmd.frame) throw new EditError(`${path8.basename(err.file ?? this.relFile(cmd.canvas))} changed while editing. Re-read the canvas and try again.`);
|
|
2467
2437
|
return this.exec(cmd, actor);
|
|
2468
2438
|
}
|
|
2469
2439
|
});
|
|
@@ -2473,7 +2443,7 @@ var Workspace = class {
|
|
|
2473
2443
|
async exec(cmd, actor) {
|
|
2474
2444
|
cmd = { ...cmd };
|
|
2475
2445
|
let prepared = null;
|
|
2476
|
-
if (cmd.op === "add_component_frame" && !
|
|
2446
|
+
if (cmd.op === "add_component_frame" && !fs7.existsSync(this.file(cmd.canvas))) {
|
|
2477
2447
|
await this.catalog.load();
|
|
2478
2448
|
const spec = this.catalog.get(cmd.component);
|
|
2479
2449
|
if (!spec) throw new EditError(`Unknown component "${cmd.component}". Use list_components.`);
|
|
@@ -2501,12 +2471,12 @@ var Workspace = class {
|
|
|
2501
2471
|
prepared = { kind: "apply", frame, ...plan };
|
|
2502
2472
|
}
|
|
2503
2473
|
const rel = target ?? this.relFile(cmd.canvas);
|
|
2504
|
-
const file =
|
|
2505
|
-
const original = target ?
|
|
2474
|
+
const file = path8.join(this.config.root, rel);
|
|
2475
|
+
const original = target ? fs7.readFileSync(file, "utf8") : canvasSource;
|
|
2506
2476
|
const crlf = original.includes("\r\n");
|
|
2507
2477
|
const before = crlf ? original.replace(/\r\n/g, "\n") : original;
|
|
2508
|
-
const
|
|
2509
|
-
const doc =
|
|
2478
|
+
const parse = (src) => target ? parseTargetDoc(target, src) : parseCanvas(cmd.canvas, rel, src);
|
|
2479
|
+
const doc = parse(before);
|
|
2510
2480
|
if (!doc) throw new EditError(`${rel} has data or logic in its component now, so it can't be edited from the canvas. Explore a copy instead.`);
|
|
2511
2481
|
if (cmd.op === "insert_icon") {
|
|
2512
2482
|
const icon = cmd;
|
|
@@ -2560,7 +2530,7 @@ var Workspace = class {
|
|
|
2560
2530
|
imports = importsFor(this.config, jsx, before, doc, rel, fileRel);
|
|
2561
2531
|
movedNames = new Set(componentNamesIn(jsx));
|
|
2562
2532
|
}
|
|
2563
|
-
const abs =
|
|
2533
|
+
const abs = path8.join(this.config.root, fileRel);
|
|
2564
2534
|
newComponent = { rel: fileRel, content: await this.format(componentFile(name, jsx, imports), abs).catch(() => componentFile(name, jsx, imports)) };
|
|
2565
2535
|
prepared = { kind: "component", name, file: fileRel };
|
|
2566
2536
|
}
|
|
@@ -2578,8 +2548,8 @@ var Workspace = class {
|
|
|
2578
2548
|
let after = await finish(result);
|
|
2579
2549
|
let ids = [];
|
|
2580
2550
|
const reparse = (src) => {
|
|
2581
|
-
const d =
|
|
2582
|
-
if (!d) throw new EditError(`That edit would put logic into ${
|
|
2551
|
+
const d = parse(src);
|
|
2552
|
+
if (!d) throw new EditError(`That edit would put logic into ${path8.basename(rel)}. Only plain JSX can be edited from the canvas.`);
|
|
2583
2553
|
return d;
|
|
2584
2554
|
};
|
|
2585
2555
|
if (after.includes(MARK)) {
|
|
@@ -2604,8 +2574,8 @@ var Workspace = class {
|
|
|
2604
2574
|
if (cmd.op === "apply_to_page") ids = [findFrame(canvasDoc, cmd.frame).id];
|
|
2605
2575
|
const writeNewComponent = () => {
|
|
2606
2576
|
if (!newComponent) return;
|
|
2607
|
-
|
|
2608
|
-
|
|
2577
|
+
fs7.mkdirSync(path8.dirname(path8.join(this.config.root, newComponent.rel)), { recursive: true });
|
|
2578
|
+
fs7.writeFileSync(path8.join(this.config.root, newComponent.rel), newComponent.content, { flag: "wx" });
|
|
2609
2579
|
this.catalog.invalidate();
|
|
2610
2580
|
this.emit({ type: "catalog" });
|
|
2611
2581
|
};
|
|
@@ -2614,11 +2584,11 @@ var Workspace = class {
|
|
|
2614
2584
|
writeNewComponent();
|
|
2615
2585
|
return { doc: target ? this.doc(cmd.canvas) : view, ids, label };
|
|
2616
2586
|
}
|
|
2617
|
-
if (
|
|
2587
|
+
if (fs7.readFileSync(file, "utf8") !== original) throw new ConflictError(`${path8.basename(rel)} changed during the edit.`, target ?? void 0);
|
|
2618
2588
|
writeNewComponent();
|
|
2619
2589
|
if (target) this.sources.set(`file:${target}`, after);
|
|
2620
2590
|
else this.sources.set(cmd.canvas, after);
|
|
2621
|
-
|
|
2591
|
+
fs7.writeFileSync(file, after);
|
|
2622
2592
|
const newDoc = this.doc(cmd.canvas);
|
|
2623
2593
|
this.record(cmd.canvas, label, actor, original, after, ids, target ?? void 0, touchedFrames(cmd, ids, view, newDoc));
|
|
2624
2594
|
if (renamedFrame) {
|
|
@@ -2644,7 +2614,7 @@ var Workspace = class {
|
|
|
2644
2614
|
const f = findFrame(view, cmd.parent);
|
|
2645
2615
|
if (f?.link) {
|
|
2646
2616
|
if (!f.link.editable) throw viewOnly(f);
|
|
2647
|
-
const src =
|
|
2617
|
+
const src = fs7.readFileSync(path8.join(this.config.root, f.link.page), "utf8");
|
|
2648
2618
|
if (f.link.kind === "component") {
|
|
2649
2619
|
const own = parseModuleDoc(f.link.page, src)?.frames.find((x) => x.frameName === f.link.route);
|
|
2650
2620
|
if (!own) throw viewOnly(f);
|
|
@@ -2669,7 +2639,7 @@ var Workspace = class {
|
|
|
2669
2639
|
const refs = nodeRefs(cmd);
|
|
2670
2640
|
const files = new Set(refs.map(fileOfId));
|
|
2671
2641
|
if (files.size > 1) {
|
|
2672
|
-
const names = [...files].map((f) => f ?
|
|
2642
|
+
const names = [...files].map((f) => f ? path8.basename(f) : "the canvas");
|
|
2673
2643
|
throw new EditError(`These layers live in different files (${names.join(", ")}). Edit them one file at a time.`);
|
|
2674
2644
|
}
|
|
2675
2645
|
return [...files][0] ?? null;
|
|
@@ -2717,12 +2687,12 @@ var Workspace = class {
|
|
|
2717
2687
|
const doc = ed.doc;
|
|
2718
2688
|
const guard = (node, verb) => {
|
|
2719
2689
|
if (doc.canvasNode) return;
|
|
2720
|
-
if (node.path.length <= 2) throw new EditError(`Can't ${verb} the root element of ${
|
|
2721
|
-
if (node.kind === "expression" && node.name.trim() === "children" && /^layout\.[jt]sx?$/.test(
|
|
2690
|
+
if (node.path.length <= 2) throw new EditError(`Can't ${verb} the root element of ${path8.basename(rel)}.`);
|
|
2691
|
+
if (node.kind === "expression" && node.name.trim() === "children" && /^layout\.[jt]sx?$/.test(path8.basename(rel))) throw new EditError(`Can't ${verb} the {children} slot of ${path8.basename(rel)}: that's where the page renders.`);
|
|
2722
2692
|
};
|
|
2723
2693
|
const literalOnly = (node, key) => {
|
|
2724
2694
|
const v = node.props[key];
|
|
2725
|
-
if (!doc.canvasNode && v?.kind === "expression") throw new EditError(`${key} on ${node.name} is computed in code ({${short(v.code, 50)}}). Edit it in ${
|
|
2695
|
+
if (!doc.canvasNode && v?.kind === "expression") throw new EditError(`${key} on ${node.name} is computed in code ({${short(v.code, 50)}}). Edit it in ${path8.basename(rel)}, or ask your agent.`);
|
|
2726
2696
|
};
|
|
2727
2697
|
switch (cmd.op) {
|
|
2728
2698
|
case "set_props": {
|
|
@@ -3187,11 +3157,11 @@ ${rows}
|
|
|
3187
3157
|
const entry = stack[stack.length - 1];
|
|
3188
3158
|
if (!entry) throw new EditError(`Nothing to ${dir}.`);
|
|
3189
3159
|
if (dir === "undo" && entry.before === "") throw new EditError("Cannot undo creating the canvas; delete the file instead.");
|
|
3190
|
-
const file = entry.file ?
|
|
3191
|
-
const name = entry.file ?
|
|
3160
|
+
const file = entry.file ? path8.join(this.config.root, entry.file) : this.file(canvas);
|
|
3161
|
+
const name = entry.file ? path8.basename(entry.file) : "the file";
|
|
3192
3162
|
let current;
|
|
3193
3163
|
try {
|
|
3194
|
-
current = entry.file ?
|
|
3164
|
+
current = entry.file ? fs7.readFileSync(file, "utf8") : this.read(canvas);
|
|
3195
3165
|
} catch {
|
|
3196
3166
|
throw new EditError(`Cannot ${dir} "${entry.label}": ${name} can't be read right now.`);
|
|
3197
3167
|
}
|
|
@@ -3200,7 +3170,7 @@ ${rows}
|
|
|
3200
3170
|
stack.pop();
|
|
3201
3171
|
const next = dir === "undo" ? entry.before : entry.after;
|
|
3202
3172
|
this.sources.set(entry.file ? `file:${entry.file}` : canvas, next);
|
|
3203
|
-
|
|
3173
|
+
fs7.writeFileSync(file, next);
|
|
3204
3174
|
const list = to.get(canvas) ?? [];
|
|
3205
3175
|
list.push(entry);
|
|
3206
3176
|
to.set(canvas, list);
|
|
@@ -3241,17 +3211,17 @@ ${rows}
|
|
|
3241
3211
|
if (this.agents.delete(session)) this.emit({ type: "agents", agents: [...this.agents.values()] });
|
|
3242
3212
|
}
|
|
3243
3213
|
ensureRoute() {
|
|
3244
|
-
if (!
|
|
3245
|
-
|
|
3214
|
+
if (!fs7.existsSync(path8.join(this.config.root, this.config.canvasDir))) {
|
|
3215
|
+
fs7.mkdirSync(path8.join(this.config.root, this.config.canvasDir), { recursive: true });
|
|
3246
3216
|
}
|
|
3247
3217
|
if (!this.canvases().length) {
|
|
3248
3218
|
const file = canvasPath(this.config, "home");
|
|
3249
3219
|
const content = this.homeCanvas();
|
|
3250
|
-
|
|
3220
|
+
fs7.writeFileSync(file, content);
|
|
3251
3221
|
void this.format(content, file).then((pretty) => {
|
|
3252
|
-
if (
|
|
3222
|
+
if (fs7.readFileSync(file, "utf8") !== content) return;
|
|
3253
3223
|
this.sources.set("home", pretty);
|
|
3254
|
-
|
|
3224
|
+
fs7.writeFileSync(file, pretty);
|
|
3255
3225
|
}).catch(() => {
|
|
3256
3226
|
});
|
|
3257
3227
|
}
|
|
@@ -3262,7 +3232,7 @@ ${rows}
|
|
|
3262
3232
|
try {
|
|
3263
3233
|
const home = listRoutes(this.config).find((r) => r.route === "/");
|
|
3264
3234
|
if (!home) return STARTER_CANVAS;
|
|
3265
|
-
const rel =
|
|
3235
|
+
const rel = path8.relative(this.config.root, canvasPath(this.config, "home"));
|
|
3266
3236
|
const empty = parseCanvas("home", rel, `import { Canvas, Frame } from "truecanvas";
|
|
3267
3237
|
export default function C() { return <Canvas></Canvas>; }
|
|
3268
3238
|
`);
|
|
@@ -3320,7 +3290,7 @@ function publicEntry(e) {
|
|
|
3320
3290
|
}
|
|
3321
3291
|
function assertShown(view, rel) {
|
|
3322
3292
|
const shown = view.frames.some((f) => f.link?.files.includes(rel));
|
|
3323
|
-
if (!shown ||
|
|
3293
|
+
if (!shown || path8.isAbsolute(rel) || rel.split(/[\\/]/).includes("..")) throw new EditError(`${rel} isn't shown on this canvas. Re-read the canvas for current ids.`);
|
|
3324
3294
|
}
|
|
3325
3295
|
function rightEdge(doc) {
|
|
3326
3296
|
return doc.frames.reduce((m, f) => Math.max(m, f.x + f.width), -80);
|
|
@@ -3392,15 +3362,15 @@ function componentJsx(spec, props = {}, text) {
|
|
|
3392
3362
|
|
|
3393
3363
|
// src/core/pm.ts
|
|
3394
3364
|
import { spawn } from "child_process";
|
|
3395
|
-
import
|
|
3396
|
-
import
|
|
3365
|
+
import fs8 from "fs";
|
|
3366
|
+
import path9 from "path";
|
|
3397
3367
|
function detectPm(dir) {
|
|
3398
|
-
for (let d = dir; ; d =
|
|
3399
|
-
if (
|
|
3400
|
-
if (
|
|
3401
|
-
if (
|
|
3402
|
-
if (
|
|
3403
|
-
if (
|
|
3368
|
+
for (let d = dir; ; d = path9.dirname(d)) {
|
|
3369
|
+
if (fs8.existsSync(path9.join(d, "pnpm-lock.yaml")) || fs8.existsSync(path9.join(d, "pnpm-workspace.yaml"))) return "pnpm";
|
|
3370
|
+
if (fs8.existsSync(path9.join(d, "yarn.lock"))) return "yarn";
|
|
3371
|
+
if (fs8.existsSync(path9.join(d, "bun.lock")) || fs8.existsSync(path9.join(d, "bun.lockb"))) return "bun";
|
|
3372
|
+
if (fs8.existsSync(path9.join(d, "package-lock.json"))) return "npm";
|
|
3373
|
+
if (path9.dirname(d) === d || fs8.existsSync(path9.join(d, ".git"))) break;
|
|
3404
3374
|
}
|
|
3405
3375
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
3406
3376
|
if (agent.startsWith("pnpm")) return "pnpm";
|
|
@@ -3420,7 +3390,12 @@ function dlx(pm, pkg, args) {
|
|
|
3420
3390
|
return ["npx", ["--yes", pkg, ...args]];
|
|
3421
3391
|
}
|
|
3422
3392
|
function toolEnv(extra = {}) {
|
|
3423
|
-
return { ...process.env, PATH: `${
|
|
3393
|
+
return { ...process.env, PATH: `${path9.dirname(process.execPath)}${path9.delimiter}${process.env.PATH ?? ""}`, ...extra };
|
|
3394
|
+
}
|
|
3395
|
+
function shellSafe(cmd, args) {
|
|
3396
|
+
if (process.platform !== "win32") return { cmd, args, shell: false };
|
|
3397
|
+
const quote = (a) => /[\s"&|<>^()%!]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a;
|
|
3398
|
+
return { cmd: quote(cmd), args: args.map(quote), shell: true };
|
|
3424
3399
|
}
|
|
3425
3400
|
function run2(cmd, args, cwd, timeoutMs = 3e5) {
|
|
3426
3401
|
return new Promise((resolve) => {
|
|
@@ -3432,7 +3407,8 @@ function run2(cmd, args, cwd, timeoutMs = 3e5) {
|
|
|
3432
3407
|
clearTimeout(timer);
|
|
3433
3408
|
resolve({ ok, out: (out + extra).slice(-2e4) });
|
|
3434
3409
|
};
|
|
3435
|
-
const
|
|
3410
|
+
const safe = shellSafe(cmd, args);
|
|
3411
|
+
const p = spawn(safe.cmd, safe.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: toolEnv({ CI: "1", FORCE_COLOR: "0", NO_COLOR: "1" }), shell: safe.shell });
|
|
3436
3412
|
const timer = setTimeout(() => {
|
|
3437
3413
|
p.kill();
|
|
3438
3414
|
finish(false, `
|
|
@@ -3447,24 +3423,24 @@ ${err.message}`));
|
|
|
3447
3423
|
}
|
|
3448
3424
|
|
|
3449
3425
|
// src/core/shadcn.ts
|
|
3450
|
-
import
|
|
3451
|
-
import
|
|
3426
|
+
import fs9 from "fs";
|
|
3427
|
+
import path10 from "path";
|
|
3452
3428
|
function shadcnStatus(root) {
|
|
3453
|
-
const file =
|
|
3454
|
-
if (!
|
|
3429
|
+
const file = path10.join(root, "components.json");
|
|
3430
|
+
if (!fs9.existsSync(file)) return { initialized: false, uiDir: null, installed: [] };
|
|
3455
3431
|
let ui = "@/components/ui";
|
|
3456
3432
|
try {
|
|
3457
|
-
ui = JSON.parse(
|
|
3433
|
+
ui = JSON.parse(fs9.readFileSync(file, "utf8"))?.aliases?.ui ?? ui;
|
|
3458
3434
|
} catch {
|
|
3459
3435
|
}
|
|
3460
3436
|
const dir = resolveAliasPath(root, ui);
|
|
3461
3437
|
let installed = [];
|
|
3462
3438
|
try {
|
|
3463
|
-
installed =
|
|
3439
|
+
installed = fs9.readdirSync(dir).filter((f) => /\.(tsx|jsx)$/.test(f)).map((f) => f.replace(/\.(tsx|jsx)$/, "")).sort();
|
|
3464
3440
|
} catch {
|
|
3465
3441
|
installed = [];
|
|
3466
3442
|
}
|
|
3467
|
-
return { initialized: true, uiDir:
|
|
3443
|
+
return { initialized: true, uiDir: posix(path10.relative(root, dir)), installed };
|
|
3468
3444
|
}
|
|
3469
3445
|
var FALLBACK = "accordion alert alert-dialog aspect-ratio avatar badge breadcrumb button button-group calendar card carousel chart checkbox collapsible combobox command context-menu dialog drawer dropdown-menu empty field form hover-card input input-group input-otp item kbd label menubar navigation-menu pagination popover progress radio-group resizable scroll-area select separator sheet sidebar skeleton slider sonner spinner switch table tabs textarea toggle toggle-group tooltip".split(" ");
|
|
3470
3446
|
var registryCache = null;
|
|
@@ -3596,8 +3572,8 @@ function describeComponent(c, verbose = false) {
|
|
|
3596
3572
|
}
|
|
3597
3573
|
|
|
3598
3574
|
// src/core/tokens.ts
|
|
3599
|
-
import
|
|
3600
|
-
import
|
|
3575
|
+
import fs10 from "fs";
|
|
3576
|
+
import path11 from "path";
|
|
3601
3577
|
import { globSync } from "tinyglobby";
|
|
3602
3578
|
var GROUPS = [
|
|
3603
3579
|
["--color-", "Colors", "bg-{name} text-{name} border-{name}"],
|
|
@@ -3614,9 +3590,9 @@ function readDesignTokens(root) {
|
|
|
3614
3590
|
const sources = [];
|
|
3615
3591
|
let darkVariant = null;
|
|
3616
3592
|
for (const file of files) {
|
|
3617
|
-
const css =
|
|
3593
|
+
const css = fs10.readFileSync(file, "utf8");
|
|
3618
3594
|
if (!css.includes("@theme") && !css.includes("@custom-variant")) continue;
|
|
3619
|
-
sources.push(
|
|
3595
|
+
sources.push(posix(path11.relative(root, file)));
|
|
3620
3596
|
const dv = /@custom-variant\s+dark\s*\(([^;]+)\);/.exec(css);
|
|
3621
3597
|
if (dv) darkVariant = dv[1].trim();
|
|
3622
3598
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) {
|
|
@@ -3655,7 +3631,7 @@ function readTokenData(root) {
|
|
|
3655
3631
|
for (const m of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) into.set(m[1], m[2].trim());
|
|
3656
3632
|
};
|
|
3657
3633
|
for (const file of files) {
|
|
3658
|
-
const css =
|
|
3634
|
+
const css = fs10.readFileSync(file, "utf8");
|
|
3659
3635
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) decls(block[1], theme);
|
|
3660
3636
|
for (const block of css.matchAll(/(?:^|\n)\s*:root\s*\{([\s\S]*?)\n\}/g)) decls(block[1], lightVars);
|
|
3661
3637
|
for (const block of css.matchAll(/(?:^|\n)\s*(?:\.dark|\[data-theme=["']?dark["']?\]|:root\.dark)\s*\{([\s\S]*?)\n\}/g)) decls(block[1], darkVars);
|
|
@@ -3687,30 +3663,30 @@ function readTokenData(root) {
|
|
|
3687
3663
|
}
|
|
3688
3664
|
|
|
3689
3665
|
// src/core/compare.ts
|
|
3690
|
-
import
|
|
3691
|
-
import
|
|
3666
|
+
import fs11 from "fs";
|
|
3667
|
+
import path12 from "path";
|
|
3692
3668
|
var COMPARE_PREFIX = "__compare__";
|
|
3693
3669
|
function writeCompare(config, canvas, source, files = {}, as = canvas) {
|
|
3694
|
-
const routeDir =
|
|
3695
|
-
const dir =
|
|
3696
|
-
|
|
3697
|
-
const canvasDir =
|
|
3698
|
-
const canvasRel =
|
|
3670
|
+
const routeDir = routeDirOf(config);
|
|
3671
|
+
const dir = path12.join(routeDir, "compare");
|
|
3672
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
3673
|
+
const canvasDir = path12.join(config.root, config.canvasDir);
|
|
3674
|
+
const canvasRel = `${config.canvasDir}/${canvas}.canvas.tsx`;
|
|
3699
3675
|
const swapped = /* @__PURE__ */ new Map();
|
|
3700
3676
|
for (const [rel, content] of Object.entries(files)) {
|
|
3701
|
-
const ext =
|
|
3677
|
+
const ext = path12.extname(rel);
|
|
3702
3678
|
const name = `${as}--${rel.slice(0, -ext.length).replace(/[^\w.-]+/g, "_")}${ext}`;
|
|
3703
|
-
const original =
|
|
3704
|
-
|
|
3679
|
+
const original = path12.join(config.root, rel);
|
|
3680
|
+
fs11.writeFileSync(path12.join(dir, name), rewriteImports(content, (spec) => spec.startsWith(".") ? relativeSpec(dir, path12.resolve(path12.dirname(original), spec)) : spec));
|
|
3705
3681
|
swapped.set(importSpecifier(config.root, canvasRel, rel), `./${name.slice(0, -ext.length)}`);
|
|
3706
3682
|
}
|
|
3707
|
-
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir,
|
|
3708
|
-
|
|
3683
|
+
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir, path12.resolve(canvasDir, spec)) : spec));
|
|
3684
|
+
fs11.writeFileSync(path12.join(dir, `${as}.canvas.tsx`), out);
|
|
3709
3685
|
syncRoute(config);
|
|
3710
3686
|
return `${COMPARE_PREFIX}${as}`;
|
|
3711
3687
|
}
|
|
3712
3688
|
function relativeSpec(fromDir, target) {
|
|
3713
|
-
const next =
|
|
3689
|
+
const next = posix(path12.relative(fromDir, target));
|
|
3714
3690
|
return next.startsWith(".") ? next : `./${next}`;
|
|
3715
3691
|
}
|
|
3716
3692
|
function rewriteImports(source, map) {
|
|
@@ -3726,9 +3702,9 @@ function rewriteImports(source, map) {
|
|
|
3726
3702
|
}
|
|
3727
3703
|
function clearCompare(config, canvas) {
|
|
3728
3704
|
if (canvas) assertCanvasName(canvas);
|
|
3729
|
-
const dir =
|
|
3730
|
-
if (!
|
|
3731
|
-
for (const f of
|
|
3705
|
+
const dir = path12.join(routeDirOf(config), "compare");
|
|
3706
|
+
if (!fs11.existsSync(dir)) return;
|
|
3707
|
+
for (const f of fs11.readdirSync(dir)) if (!canvas || f === `${canvas}.canvas.tsx` || f.startsWith(`${canvas}--`)) fs11.rmSync(path12.join(dir, f));
|
|
3732
3708
|
syncRoute(config);
|
|
3733
3709
|
}
|
|
3734
3710
|
function frameChanges(before, after) {
|
|
@@ -3745,24 +3721,48 @@ function frameChanges(before, after) {
|
|
|
3745
3721
|
}
|
|
3746
3722
|
|
|
3747
3723
|
// src/core/init.ts
|
|
3748
|
-
import
|
|
3724
|
+
import fs12 from "fs";
|
|
3749
3725
|
import MagicString2 from "magic-string";
|
|
3750
|
-
import
|
|
3751
|
-
var CONFIG_FILES =
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3726
|
+
import path13 from "path";
|
|
3727
|
+
var CONFIG_FILES = {
|
|
3728
|
+
next: ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.mts"],
|
|
3729
|
+
vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]
|
|
3730
|
+
};
|
|
3731
|
+
function projectFramework(root) {
|
|
3732
|
+
try {
|
|
3733
|
+
const pkg = JSON.parse(fs12.readFileSync(path13.join(root, "package.json"), "utf8"));
|
|
3734
|
+
return detectFramework({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
3735
|
+
} catch {
|
|
3736
|
+
return "next";
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
function configStatus(root, framework = projectFramework(root)) {
|
|
3740
|
+
const marker = framework === "vite" ? "truecanvas/vite" : "withTruecanvas";
|
|
3741
|
+
for (const name of CONFIG_FILES[framework]) {
|
|
3742
|
+
const file = path13.join(root, name);
|
|
3743
|
+
if (fs12.existsSync(file)) return { file, wrapped: fs12.readFileSync(file, "utf8").includes(marker), framework };
|
|
3756
3744
|
}
|
|
3757
|
-
return { file: null, wrapped: false };
|
|
3745
|
+
return { file: null, wrapped: false, framework };
|
|
3758
3746
|
}
|
|
3759
3747
|
function initProject(config) {
|
|
3760
3748
|
const { root } = config;
|
|
3761
3749
|
const report = { done: [], todo: [] };
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3750
|
+
const vite = config.framework === "vite";
|
|
3751
|
+
const status = configStatus(root, config.framework);
|
|
3752
|
+
if (vite) {
|
|
3753
|
+
const name = status.file ? path13.basename(status.file) : "vite.config.ts";
|
|
3754
|
+
if (status.wrapped) report.done.push(`${name} already uses the truecanvas() plugin`);
|
|
3755
|
+
else {
|
|
3756
|
+
const src = status.file ? fs12.readFileSync(status.file, "utf8") : null;
|
|
3757
|
+
const next = src && addVitePlugin(src);
|
|
3758
|
+
if (next) {
|
|
3759
|
+
fs12.writeFileSync(status.file, next);
|
|
3760
|
+
report.done.push(`Added the truecanvas() plugin to ${name}`);
|
|
3761
|
+
} else report.todo.push(`Add truecanvas() from "truecanvas/vite" to the plugins in ${name}`);
|
|
3762
|
+
}
|
|
3763
|
+
} else if (!status.file) {
|
|
3764
|
+
const file = path13.join(root, "next.config.ts");
|
|
3765
|
+
fs12.writeFileSync(file, `import type { NextConfig } from "next";
|
|
3766
3766
|
import { withTruecanvas } from "truecanvas/next";
|
|
3767
3767
|
|
|
3768
3768
|
const nextConfig: NextConfig = {};
|
|
@@ -3771,49 +3771,49 @@ export default withTruecanvas(nextConfig);
|
|
|
3771
3771
|
`);
|
|
3772
3772
|
report.done.push("Created next.config.ts with withTruecanvas()");
|
|
3773
3773
|
} else if (status.wrapped) {
|
|
3774
|
-
report.done.push(`${
|
|
3774
|
+
report.done.push(`${path13.basename(status.file)} already uses withTruecanvas()`);
|
|
3775
3775
|
} else {
|
|
3776
|
-
const src =
|
|
3776
|
+
const src = fs12.readFileSync(status.file, "utf8");
|
|
3777
3777
|
const next = wrapConfigExport(src);
|
|
3778
3778
|
if (next && next !== src && next.includes("withTruecanvas(")) {
|
|
3779
|
-
|
|
3780
|
-
report.done.push(`Wrapped ${
|
|
3779
|
+
fs12.writeFileSync(status.file, next);
|
|
3780
|
+
report.done.push(`Wrapped ${path13.basename(status.file)} with withTruecanvas()`);
|
|
3781
3781
|
} else {
|
|
3782
|
-
report.todo.push(`Wrap your ${
|
|
3782
|
+
report.todo.push(`Wrap your ${path13.basename(status.file)} export with withTruecanvas() from "truecanvas/next"`);
|
|
3783
3783
|
}
|
|
3784
3784
|
}
|
|
3785
|
-
const pkgFile =
|
|
3785
|
+
const pkgFile = path13.join(root, "package.json");
|
|
3786
3786
|
try {
|
|
3787
|
-
const raw =
|
|
3787
|
+
const raw = fs12.readFileSync(pkgFile, "utf8");
|
|
3788
3788
|
const pkg = JSON.parse(raw);
|
|
3789
3789
|
pkg.scripts ??= {};
|
|
3790
3790
|
if (!Object.values(pkg.scripts).some((v) => typeof v === "string" && v.includes("truecanvas"))) {
|
|
3791
3791
|
pkg.scripts.canvas = "truecanvas dev";
|
|
3792
3792
|
const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
|
|
3793
|
-
|
|
3793
|
+
fs12.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, indent)}
|
|
3794
3794
|
`);
|
|
3795
3795
|
report.done.push(`Added the "canvas" script (npm run canvas)`);
|
|
3796
3796
|
}
|
|
3797
3797
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3798
3798
|
if (!("tailwindcss" in deps)) report.todo.push("Tailwind CSS isn't installed: layout and style controls write Tailwind classes");
|
|
3799
|
-
if (!("next" in deps)) report.todo.push("Next.js
|
|
3799
|
+
if (!("next" in deps) && !("vite" in deps)) report.todo.push("Neither Next.js nor Vite is in package.json: Truecanvas needs a Next.js (App Router) or Vite + React app");
|
|
3800
3800
|
} catch {
|
|
3801
3801
|
report.todo.push("Couldn't read package.json");
|
|
3802
3802
|
}
|
|
3803
|
-
const ignoreFile =
|
|
3804
|
-
const entry = `/${config.
|
|
3805
|
-
const ignore =
|
|
3803
|
+
const ignoreFile = path13.join(root, ".gitignore");
|
|
3804
|
+
const entry = `/${config.routeDir}/`;
|
|
3805
|
+
const ignore = fs12.existsSync(ignoreFile) ? fs12.readFileSync(ignoreFile, "utf8") : "";
|
|
3806
3806
|
if (!ignore.split("\n").some((l) => l.trim() === entry || l.trim() === entry.slice(1))) {
|
|
3807
|
-
|
|
3807
|
+
fs12.writeFileSync(ignoreFile, `${ignore}${ignore && !ignore.endsWith("\n") ? "\n" : ""}
|
|
3808
3808
|
# Truecanvas (generated dev-only route)
|
|
3809
3809
|
${entry}
|
|
3810
3810
|
`);
|
|
3811
3811
|
report.done.push(`Ignored ${entry} in .gitignore`);
|
|
3812
3812
|
}
|
|
3813
3813
|
for (const name of ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs", "tailwind.config.cjs"]) {
|
|
3814
|
-
const file =
|
|
3815
|
-
if (!
|
|
3816
|
-
const src =
|
|
3814
|
+
const file = path13.join(root, name);
|
|
3815
|
+
if (!fs12.existsSync(file)) continue;
|
|
3816
|
+
const src = fs12.readFileSync(file, "utf8");
|
|
3817
3817
|
const glob = `./${config.canvasDir}/**/*.{ts,tsx}`;
|
|
3818
3818
|
if (src.includes(config.canvasDir)) break;
|
|
3819
3819
|
const m = /content\s*:\s*\[([ \t]*\n?)([ \t]*)/.exec(src);
|
|
@@ -3822,12 +3822,12 @@ ${entry}
|
|
|
3822
3822
|
const at = m.index + m[0].length;
|
|
3823
3823
|
const entry2 = m[1].includes("\n") ? `${JSON.stringify(glob)},
|
|
3824
3824
|
${indent}` : `${JSON.stringify(glob)}, `;
|
|
3825
|
-
|
|
3825
|
+
fs12.writeFileSync(file, src.slice(0, at) + entry2 + src.slice(at));
|
|
3826
3826
|
report.done.push(`Added ${config.canvasDir} to Tailwind's content in ${name}`);
|
|
3827
3827
|
} else report.todo.push(`Add "${glob}" to the content list in ${name} so Tailwind styles your canvases`);
|
|
3828
3828
|
break;
|
|
3829
3829
|
}
|
|
3830
|
-
if (!
|
|
3830
|
+
if (!vite && !fs12.existsSync(path13.join(root, config.appDir))) report.todo.push(`No ${config.appDir}/ folder found: Truecanvas needs the Next.js App Router`);
|
|
3831
3831
|
return report;
|
|
3832
3832
|
}
|
|
3833
3833
|
function addImport(src, line) {
|
|
@@ -3874,12 +3874,35 @@ ${s.toString()}`;
|
|
|
3874
3874
|
}
|
|
3875
3875
|
return null;
|
|
3876
3876
|
}
|
|
3877
|
+
function addVitePlugin(src) {
|
|
3878
|
+
let ast;
|
|
3879
|
+
try {
|
|
3880
|
+
ast = parseModule(src);
|
|
3881
|
+
} catch {
|
|
3882
|
+
return null;
|
|
3883
|
+
}
|
|
3884
|
+
let plugins = null;
|
|
3885
|
+
walk(ast, (node) => {
|
|
3886
|
+
if (plugins) return false;
|
|
3887
|
+
if (node.type !== "ObjectProperty" || node.value.type !== "ArrayExpression") return;
|
|
3888
|
+
const key = node.key.type === "Identifier" ? node.key.name : node.key.type === "StringLiteral" ? node.key.value : null;
|
|
3889
|
+
if (key !== "plugins") return;
|
|
3890
|
+
plugins = { start: node.value.start, end: node.value.end, elements: node.value.elements.filter((e) => e !== null).map((e) => ({ start: e.start, end: e.end })) };
|
|
3891
|
+
return false;
|
|
3892
|
+
});
|
|
3893
|
+
if (!plugins) return null;
|
|
3894
|
+
const { start, end, elements } = plugins;
|
|
3895
|
+
const s = new MagicString2(src);
|
|
3896
|
+
if (!elements.length) s.overwrite(start, end, "[truecanvas()]");
|
|
3897
|
+
else {
|
|
3898
|
+
const last = elements[elements.length - 1];
|
|
3899
|
+
const before = src.slice(start + 1, elements[0].start);
|
|
3900
|
+
s.appendLeft(last.end, before.includes("\n") ? `,${before.replace(/^[^\n]*/, "")}truecanvas()` : ", truecanvas()");
|
|
3901
|
+
}
|
|
3902
|
+
return addImport(s.toString(), `import { truecanvas } from "truecanvas/vite";`);
|
|
3903
|
+
}
|
|
3877
3904
|
|
|
3878
3905
|
export {
|
|
3879
|
-
loadConfig,
|
|
3880
|
-
parseModule,
|
|
3881
|
-
idOf,
|
|
3882
|
-
walk,
|
|
3883
3906
|
parseCanvas,
|
|
3884
3907
|
allNodes,
|
|
3885
3908
|
findNode,
|
|
@@ -3895,6 +3918,8 @@ export {
|
|
|
3895
3918
|
DEVICES,
|
|
3896
3919
|
findDevice,
|
|
3897
3920
|
assertCanvasName,
|
|
3921
|
+
syncRoute,
|
|
3922
|
+
viteStylesheets,
|
|
3898
3923
|
syncComponentRegistry,
|
|
3899
3924
|
Comments,
|
|
3900
3925
|
Git,
|
|
@@ -3903,6 +3928,7 @@ export {
|
|
|
3903
3928
|
detectPm,
|
|
3904
3929
|
addArgs,
|
|
3905
3930
|
toolEnv,
|
|
3931
|
+
shellSafe,
|
|
3906
3932
|
run2 as run,
|
|
3907
3933
|
shadcnStatus,
|
|
3908
3934
|
shadcnRegistry,
|
|
@@ -3915,7 +3941,8 @@ export {
|
|
|
3915
3941
|
writeCompare,
|
|
3916
3942
|
clearCompare,
|
|
3917
3943
|
frameChanges,
|
|
3918
|
-
|
|
3944
|
+
configStatus,
|
|
3919
3945
|
initProject,
|
|
3920
|
-
wrapConfigExport
|
|
3946
|
+
wrapConfigExport,
|
|
3947
|
+
addVitePlugin
|
|
3921
3948
|
};
|