truecanvas 0.2.0 → 0.4.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 +81 -181
- package/dist/catalog-worker.js +1 -1
- package/dist/chunk-M7B3W7EP.js +659 -0
- package/dist/{chunk-ZPARY6OD.js → chunk-OVYSWA2F.js} +387 -340
- 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/cli.js +989 -257
- package/dist/core/index.d.ts +56 -10
- package/dist/core/index.js +16 -25
- package/dist/editor/assets/{Menu-DXPm0EXo.css → Menu-BJ3OmqzH.css} +1 -1
- package/dist/editor/assets/hub-B5hqZCM2.js +2 -0
- package/dist/editor/assets/{main-DlADROMw.js → main-woTyWmya.js} +5 -5
- package/dist/editor/hub.html +3 -3
- package/dist/editor/index.html +3 -3
- package/dist/next/index.js +3 -2
- package/dist/share/index.d.ts +36 -0
- package/dist/share/index.js +6 -0
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.js +81 -0
- package/package.json +19 -6
- package/dist/editor/assets/hub-BnbEAnt6.js +0 -2
- /package/dist/editor/assets/{Menu-BFifVcgz.js → Menu-C0Ti-6T2.js} +0 -0
|
@@ -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,34 +968,46 @@ ${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
|
+
read(canvas) {
|
|
1015
988
|
try {
|
|
1016
|
-
|
|
989
|
+
const f = JSON.parse(fs2.readFileSync(this.file(canvas), "utf8"));
|
|
990
|
+
return { version: 1, threads: f.threads ?? [], dismissed: f.dismissed };
|
|
1017
991
|
} catch {
|
|
1018
|
-
return [];
|
|
992
|
+
return { version: 1, threads: [] };
|
|
1019
993
|
}
|
|
1020
994
|
}
|
|
1021
|
-
|
|
995
|
+
list(canvas) {
|
|
996
|
+
return this.read(canvas).threads;
|
|
997
|
+
}
|
|
998
|
+
/** Ids of client threads deleted here. */
|
|
999
|
+
dismissed(canvas) {
|
|
1000
|
+
return new Set(this.read(canvas).dismissed ?? []);
|
|
1001
|
+
}
|
|
1002
|
+
save(canvas, threads, dismissed = this.read(canvas).dismissed) {
|
|
1022
1003
|
const file = this.file(canvas);
|
|
1023
|
-
if (!threads.length) {
|
|
1024
|
-
if (
|
|
1004
|
+
if (!threads.length && !dismissed?.length) {
|
|
1005
|
+
if (fs2.existsSync(file)) fs2.rmSync(file);
|
|
1025
1006
|
return;
|
|
1026
1007
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1008
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
1009
|
+
const data = { version: 1, threads, ...dismissed?.length ? { dismissed } : {} };
|
|
1010
|
+
fs2.writeFileSync(file, `${JSON.stringify(data, null, 2)}
|
|
1029
1011
|
`);
|
|
1030
1012
|
}
|
|
1031
1013
|
update(canvas, id, fn) {
|
|
@@ -1063,12 +1045,23 @@ var Comments = class {
|
|
|
1063
1045
|
return this.update(canvas, id, (t) => {
|
|
1064
1046
|
t.resolved = resolved;
|
|
1065
1047
|
t.resolvedBy = resolved ? author : void 0;
|
|
1048
|
+
t.resolvedAt = Date.now();
|
|
1066
1049
|
});
|
|
1067
1050
|
}
|
|
1051
|
+
/** Applies synced threads (from a share link) in one write; returns whether anything changed. */
|
|
1052
|
+
merge(canvas, fn) {
|
|
1053
|
+
const threads = this.list(canvas);
|
|
1054
|
+
if (!fn(threads)) return false;
|
|
1055
|
+
this.save(canvas, threads);
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1068
1058
|
remove(canvas, id) {
|
|
1059
|
+
const { threads, dismissed = [] } = this.read(canvas);
|
|
1060
|
+
const gone = threads.find((t) => t.id === id);
|
|
1069
1061
|
this.save(
|
|
1070
1062
|
canvas,
|
|
1071
|
-
|
|
1063
|
+
threads.filter((t) => t.id !== id),
|
|
1064
|
+
gone?.share ? [.../* @__PURE__ */ new Set([...dismissed, id])] : dismissed
|
|
1072
1065
|
);
|
|
1073
1066
|
}
|
|
1074
1067
|
/** Frames keep their comments when renamed. */
|
|
@@ -1080,14 +1073,13 @@ var Comments = class {
|
|
|
1080
1073
|
}
|
|
1081
1074
|
renameCanvas(from, to) {
|
|
1082
1075
|
const a = this.file(from);
|
|
1083
|
-
if (
|
|
1076
|
+
if (fs2.existsSync(a)) fs2.renameSync(a, this.file(to));
|
|
1084
1077
|
}
|
|
1085
1078
|
};
|
|
1086
1079
|
|
|
1087
1080
|
// src/core/git.ts
|
|
1088
1081
|
import { execFile } from "child_process";
|
|
1089
|
-
import
|
|
1090
|
-
import path4 from "path";
|
|
1082
|
+
import path3 from "path";
|
|
1091
1083
|
var GitError = class extends EditError {
|
|
1092
1084
|
};
|
|
1093
1085
|
function git(cwd, args, timeout = 3e4, opts = {}) {
|
|
@@ -1127,6 +1119,7 @@ var Git = class {
|
|
|
1127
1119
|
return { repo: false, root: null, branch: null, unborn: false, upstream: null, ahead: 0, behind: 0, files: [], user: null, defaultBranch: null, remote: null };
|
|
1128
1120
|
}
|
|
1129
1121
|
const out = await git(this.cwd, ["status", "--porcelain=v2", "-z", "--branch", "--untracked-files=all", "--", "."]);
|
|
1122
|
+
const prefix = await this.prefix();
|
|
1130
1123
|
const s = { repo: true, root, branch: null, unborn: false, upstream: null, ahead: 0, behind: 0, files: [], user: null, defaultBranch: null, remote: null };
|
|
1131
1124
|
const entries = out.split("\0");
|
|
1132
1125
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -1143,10 +1136,10 @@ var Git = class {
|
|
|
1143
1136
|
const file = parts.slice(line.startsWith("2 ") ? 9 : 8).join(" ");
|
|
1144
1137
|
if (line.startsWith("2 ")) i++;
|
|
1145
1138
|
const code = xy.replace(".", "")[0] ?? "M";
|
|
1146
|
-
s.files.push({ path: this.rel(
|
|
1139
|
+
s.files.push({ path: this.rel(prefix, file), status: code === "A" ? "added" : code === "D" ? "deleted" : code === "R" ? "renamed" : "modified", staged: xy[0] !== "." });
|
|
1147
1140
|
} else if (line.startsWith("u ")) {
|
|
1148
|
-
s.files.push({ path: this.rel(
|
|
1149
|
-
} else if (line.startsWith("? ")) s.files.push({ path: this.rel(
|
|
1141
|
+
s.files.push({ path: this.rel(prefix, line.split(" ").slice(10).join(" ")), status: "conflicted", staged: false });
|
|
1142
|
+
} else if (line.startsWith("? ")) s.files.push({ path: this.rel(prefix, line.slice(2)), status: "untracked", staged: false });
|
|
1150
1143
|
}
|
|
1151
1144
|
s.user = (await git(this.cwd, ["config", "user.name"]).catch(() => "")).trim() || null;
|
|
1152
1145
|
s.remote = (await git(this.cwd, ["remote", "get-url", "origin"]).catch(() => "")).trim() || null;
|
|
@@ -1201,8 +1194,8 @@ var Git = class {
|
|
|
1201
1194
|
safeRef(branch);
|
|
1202
1195
|
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
1196
|
const os = await import("os");
|
|
1204
|
-
const
|
|
1205
|
-
const index =
|
|
1197
|
+
const fs13 = await import("fs");
|
|
1198
|
+
const index = path3.join(os.tmpdir(), `truecanvas-index-${process.pid}-${Date.now()}`);
|
|
1206
1199
|
const env = { GIT_INDEX_FILE: index };
|
|
1207
1200
|
try {
|
|
1208
1201
|
if (tip) await git(this.cwd, ["read-tree", tip], 3e4, { env });
|
|
@@ -1216,7 +1209,7 @@ var Git = class {
|
|
|
1216
1209
|
await git(this.cwd, ["update-ref", `refs/heads/${branch}`, commit]);
|
|
1217
1210
|
await git(this.cwd, ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`], 12e4);
|
|
1218
1211
|
} finally {
|
|
1219
|
-
|
|
1212
|
+
fs13.rmSync(index, { force: true });
|
|
1220
1213
|
}
|
|
1221
1214
|
}
|
|
1222
1215
|
// ---------- GitHub (gh CLI) ----------
|
|
@@ -1250,19 +1243,16 @@ var Git = class {
|
|
|
1250
1243
|
return (await git(this.cwd, ["remote", "get-url", "origin"])).trim();
|
|
1251
1244
|
}
|
|
1252
1245
|
/** git reports paths from the repo root; we show them from the project root. */
|
|
1253
|
-
rel(
|
|
1254
|
-
return
|
|
1246
|
+
rel(prefix, file) {
|
|
1247
|
+
return file.startsWith(prefix) ? file.slice(prefix.length) : posix(path3.posix.relative(prefix, file));
|
|
1255
1248
|
}
|
|
1256
1249
|
/**
|
|
1257
|
-
* The project folder
|
|
1258
|
-
*
|
|
1250
|
+
* The project folder inside the repo ("apps/web/", or "" at the root), as git
|
|
1251
|
+
* itself sees it. Asking git instead of comparing file system paths holds up
|
|
1252
|
+
* through symlinks (macOS /var → /private/var), junctions and Windows short names.
|
|
1259
1253
|
*/
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return fs4.realpathSync(this.cwd);
|
|
1263
|
-
} catch {
|
|
1264
|
-
return this.cwd;
|
|
1265
|
-
}
|
|
1254
|
+
async prefix() {
|
|
1255
|
+
return (await git(this.cwd, ["rev-parse", "--show-prefix"]).catch(() => "")).trim();
|
|
1266
1256
|
}
|
|
1267
1257
|
async branches() {
|
|
1268
1258
|
const out = await git(this.cwd, ["branch", "-a", "--format=%(refname)%09%(HEAD)"]).catch(() => "");
|
|
@@ -1290,8 +1280,7 @@ var Git = class {
|
|
|
1290
1280
|
}
|
|
1291
1281
|
/** A file's content at a ref, or null if it doesn't exist there. */
|
|
1292
1282
|
async show(ref, file) {
|
|
1293
|
-
const
|
|
1294
|
-
const inRepo = path4.relative(root, path4.join(this.realCwd(), file)).split(path4.sep).join("/");
|
|
1283
|
+
const inRepo = path3.posix.normalize(`${await this.prefix()}${posix(file)}`);
|
|
1295
1284
|
return git(this.cwd, ["show", `${safeRef(ref)}:${inRepo}`, "--"]).catch(() => null);
|
|
1296
1285
|
}
|
|
1297
1286
|
async commit(message, files) {
|
|
@@ -1320,17 +1309,17 @@ var Git = class {
|
|
|
1320
1309
|
};
|
|
1321
1310
|
|
|
1322
1311
|
// src/core/workspace.ts
|
|
1323
|
-
import
|
|
1324
|
-
import
|
|
1312
|
+
import fs7 from "fs";
|
|
1313
|
+
import path8 from "path";
|
|
1325
1314
|
import { createRequire } from "module";
|
|
1326
1315
|
|
|
1327
1316
|
// src/core/link.ts
|
|
1328
|
-
import
|
|
1329
|
-
import
|
|
1317
|
+
import fs4 from "fs";
|
|
1318
|
+
import path5 from "path";
|
|
1330
1319
|
|
|
1331
1320
|
// src/core/routes.ts
|
|
1332
|
-
import
|
|
1333
|
-
import
|
|
1321
|
+
import fs3 from "fs";
|
|
1322
|
+
import path4 from "path";
|
|
1334
1323
|
|
|
1335
1324
|
// src/core/source.ts
|
|
1336
1325
|
function defaultReturn(source, layout) {
|
|
@@ -1542,28 +1531,29 @@ function parseTargetDoc(file, source) {
|
|
|
1542
1531
|
var PAGE = /^page\.(tsx|jsx|ts|js)$/;
|
|
1543
1532
|
var LAYOUT = ["layout.tsx", "layout.jsx", "layout.js", "layout.ts"];
|
|
1544
1533
|
function listRoutes(config) {
|
|
1545
|
-
|
|
1534
|
+
if (config.framework !== "next") return [];
|
|
1535
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1546
1536
|
const out = [];
|
|
1547
1537
|
const walkDir = (dir, segments) => {
|
|
1548
1538
|
let entries;
|
|
1549
1539
|
try {
|
|
1550
|
-
entries =
|
|
1540
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1551
1541
|
} catch {
|
|
1552
1542
|
return;
|
|
1553
1543
|
}
|
|
1554
1544
|
for (const e of entries) {
|
|
1555
1545
|
if (e.isFile() && PAGE.test(e.name)) {
|
|
1556
1546
|
const visible = segments.filter((s) => !/^\(.*\)$/.test(s) && !s.startsWith("@"));
|
|
1557
|
-
out.push({ route: `/${visible.join("/")}`, file:
|
|
1547
|
+
out.push({ route: `/${visible.join("/")}`, file: posix(path4.relative(config.root, path4.join(dir, e.name))), dynamic: segments.some((s) => s.startsWith("[")) });
|
|
1558
1548
|
}
|
|
1559
|
-
if (e.isDirectory() && !e.name.startsWith("_") && e.name !== "truecanvas" && e.name !== "api" && e.name !== "node_modules") walkDir(
|
|
1549
|
+
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
1550
|
}
|
|
1561
1551
|
};
|
|
1562
1552
|
walkDir(appDir, []);
|
|
1563
1553
|
return out.sort((a, b) => a.route.localeCompare(b.route));
|
|
1564
1554
|
}
|
|
1565
1555
|
function extract(file, canvasFile, config, layout) {
|
|
1566
|
-
const source =
|
|
1556
|
+
const source = fs3.readFileSync(file, "utf8");
|
|
1567
1557
|
const info = defaultReturn(source, layout);
|
|
1568
1558
|
if (!info?.pure) return null;
|
|
1569
1559
|
const imports = [];
|
|
@@ -1579,26 +1569,26 @@ function extract(file, canvasFile, config, layout) {
|
|
|
1579
1569
|
}
|
|
1580
1570
|
function repoint(config, spec, fromFile, toFile) {
|
|
1581
1571
|
if (!spec.startsWith(".")) return spec;
|
|
1582
|
-
return importSpecifier(config.root,
|
|
1572
|
+
return importSpecifier(config.root, path4.relative(config.root, toFile), path4.relative(config.root, path4.resolve(path4.dirname(fromFile), spec)));
|
|
1583
1573
|
}
|
|
1584
1574
|
function layoutFiles(config, pageFile) {
|
|
1585
|
-
const appDir =
|
|
1575
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1586
1576
|
const out = [];
|
|
1587
|
-
for (let dir =
|
|
1588
|
-
const file = LAYOUT.map((f) =>
|
|
1589
|
-
if (file) out.push(
|
|
1577
|
+
for (let dir = path4.dirname(path4.resolve(config.root, pageFile)); dir.startsWith(appDir) && dir !== appDir; dir = path4.dirname(dir)) {
|
|
1578
|
+
const file = LAYOUT.map((f) => path4.join(dir, f)).find((f) => fs3.existsSync(f));
|
|
1579
|
+
if (file) out.push(posix(path4.relative(config.root, file)));
|
|
1590
1580
|
}
|
|
1591
1581
|
return out;
|
|
1592
1582
|
}
|
|
1593
1583
|
function routeOf(config, pageFile) {
|
|
1594
|
-
const rel =
|
|
1595
|
-
const visible = rel.split(
|
|
1584
|
+
const rel = path4.relative(path4.join(config.root, config.appDir), path4.dirname(path4.resolve(config.root, pageFile)));
|
|
1585
|
+
const visible = rel.split(path4.sep).filter((s) => s && !/^\(.*\)$/.test(s) && !s.startsWith("@"));
|
|
1596
1586
|
return `/${visible.join("/")}`;
|
|
1597
1587
|
}
|
|
1598
1588
|
function routeFrame(config, pageFile, canvasFile) {
|
|
1599
|
-
const appDir =
|
|
1600
|
-
const pageAbs =
|
|
1601
|
-
const canvasAbs =
|
|
1589
|
+
const appDir = path4.join(config.root, config.appDir);
|
|
1590
|
+
const pageAbs = path4.resolve(config.root, pageFile);
|
|
1591
|
+
const canvasAbs = path4.resolve(config.root, canvasFile);
|
|
1602
1592
|
const imports = [];
|
|
1603
1593
|
let mode = "inline";
|
|
1604
1594
|
const page = extract(pageAbs, canvasAbs, config, false);
|
|
@@ -1612,8 +1602,8 @@ function routeFrame(config, pageFile, canvasFile) {
|
|
|
1612
1602
|
jsx = "<RoutePage />";
|
|
1613
1603
|
}
|
|
1614
1604
|
let i = 0;
|
|
1615
|
-
for (let dir =
|
|
1616
|
-
const file = LAYOUT.map((f) =>
|
|
1605
|
+
for (let dir = path4.dirname(pageAbs); dir.startsWith(appDir) && dir !== appDir; dir = path4.dirname(dir)) {
|
|
1606
|
+
const file = LAYOUT.map((f) => path4.join(dir, f)).find((f) => fs3.existsSync(f));
|
|
1617
1607
|
if (!file) continue;
|
|
1618
1608
|
const layout = extract(file, canvasAbs, config, true);
|
|
1619
1609
|
const holes = layout ? (layout.jsx.match(/\{\s*children\s*\}/g) ?? []).length : 0;
|
|
@@ -1623,7 +1613,7 @@ function routeFrame(config, pageFile, canvasFile) {
|
|
|
1623
1613
|
imports.push(...layout.imports);
|
|
1624
1614
|
} else {
|
|
1625
1615
|
const local = `RouteLayout${i++ || ""}`;
|
|
1626
|
-
imports.push(`import ${local} from ${JSON.stringify(importSpecifier(config.root, canvasFile,
|
|
1616
|
+
imports.push(`import ${local} from ${JSON.stringify(importSpecifier(config.root, canvasFile, path4.relative(config.root, file)))};`);
|
|
1627
1617
|
jsx = `<${local}>${jsx}</${local}>`;
|
|
1628
1618
|
}
|
|
1629
1619
|
}
|
|
@@ -1644,16 +1634,16 @@ function wrapperChain(frame) {
|
|
|
1644
1634
|
}
|
|
1645
1635
|
function readSource(config, rel) {
|
|
1646
1636
|
try {
|
|
1647
|
-
return
|
|
1637
|
+
return fs4.readFileSync(path5.join(config.root, rel), "utf8");
|
|
1648
1638
|
} catch {
|
|
1649
1639
|
return null;
|
|
1650
1640
|
}
|
|
1651
1641
|
}
|
|
1652
1642
|
var parseMemo = /* @__PURE__ */ new Map();
|
|
1653
|
-
function memoParse(key, source,
|
|
1643
|
+
function memoParse(key, source, parse) {
|
|
1654
1644
|
let hit = parseMemo.get(key);
|
|
1655
1645
|
if (!hit || hit.source !== source) {
|
|
1656
|
-
hit = { source, doc:
|
|
1646
|
+
hit = { source, doc: parse() };
|
|
1657
1647
|
parseMemo.set(key, hit);
|
|
1658
1648
|
}
|
|
1659
1649
|
return hit.doc && { ...hit.doc, frames: hit.doc.frames.map(cloneTree) };
|
|
@@ -1755,7 +1745,7 @@ function linkedFrameBody(config, doc, canvasRel, page) {
|
|
|
1755
1745
|
const pageName = importName(config, doc, canvasRel, page, `${segments.length ? pascal(segments[segments.length - 1]) : "Home"}Page`, taken, imports);
|
|
1756
1746
|
let jsx = `<${pageName} />`;
|
|
1757
1747
|
for (const rel of layoutFiles(config, page)) {
|
|
1758
|
-
const dir =
|
|
1748
|
+
const dir = path5.basename(path5.dirname(rel));
|
|
1759
1749
|
const name = importName(config, doc, canvasRel, rel, `${pascal(dir) || "Route"}Layout`, taken, imports);
|
|
1760
1750
|
jsx = `<${name}>
|
|
1761
1751
|
${indentBlock(jsx)}
|
|
@@ -1777,7 +1767,7 @@ function dedent(code) {
|
|
|
1777
1767
|
function explorationBody(config, frame, canvasRel) {
|
|
1778
1768
|
if (frame.component) throw new EditError(`"${frame.frameName}" is a main component: edit it directly, every instance follows.`);
|
|
1779
1769
|
if (!frame.page) throw new EditError(`"${frame.frameName}" isn't linked to a page.`);
|
|
1780
|
-
const pageAbs =
|
|
1770
|
+
const pageAbs = path5.join(config.root, frame.page);
|
|
1781
1771
|
const source = readSource(config, frame.page);
|
|
1782
1772
|
if (source === null) throw new EditError(`${frame.page} doesn't exist anymore.`);
|
|
1783
1773
|
const chain = wrapperChain(frame);
|
|
@@ -1789,7 +1779,7 @@ function explorationBody(config, frame, canvasRel) {
|
|
|
1789
1779
|
for (const stmt of info.ast.program.body) {
|
|
1790
1780
|
if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type" || /\.(css|scss|sass|less)$/.test(stmt.source.value)) continue;
|
|
1791
1781
|
const src = stmt.source.value;
|
|
1792
|
-
const from = repoint(config, src, pageAbs,
|
|
1782
|
+
const from = repoint(config, src, pageAbs, path5.join(config.root, canvasRel));
|
|
1793
1783
|
const text = source.slice(stmt.start, stmt.end);
|
|
1794
1784
|
imports.push(from === src ? text : rewriteImportSource(text, src, from));
|
|
1795
1785
|
}
|
|
@@ -1805,7 +1795,7 @@ ${indentBlock(jsx)}
|
|
|
1805
1795
|
function applyPlan(config, canvasSource, doc, frame, canvasRel) {
|
|
1806
1796
|
const page = frame.from;
|
|
1807
1797
|
if (!page) throw new EditError(`"${frame.frameName}" isn't an exploration of a page. Explore a copy of a linked frame first.`);
|
|
1808
|
-
if (!
|
|
1798
|
+
if (!fs4.existsSync(path5.join(config.root, page))) throw new EditError(`${page} doesn't exist anymore.`);
|
|
1809
1799
|
const layoutSpecs = new Set(doc.imports.filter((i) => /(^|\/)layout(\.\w+)?$/.test(i.source)).flatMap((i) => [...i.names, ...i.defaultName ? [i.defaultName] : []]));
|
|
1810
1800
|
let content = frame.children;
|
|
1811
1801
|
for (; ; ) {
|
|
@@ -1831,7 +1821,7 @@ ${indentBlock(body)}
|
|
|
1831
1821
|
continue;
|
|
1832
1822
|
}
|
|
1833
1823
|
const sp = decl.specifiers.find((x) => x.local.name === name);
|
|
1834
|
-
const from = repoint(config, decl.source.value,
|
|
1824
|
+
const from = repoint(config, decl.source.value, path5.join(config.root, canvasRel), path5.join(config.root, page));
|
|
1835
1825
|
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
1826
|
imports.push(`import ${what} from ${JSON.stringify(from)};`);
|
|
1837
1827
|
}
|
|
@@ -1864,8 +1854,8 @@ function pruneImports(source, candidates) {
|
|
|
1864
1854
|
}
|
|
1865
1855
|
|
|
1866
1856
|
// src/core/motion.ts
|
|
1867
|
-
import
|
|
1868
|
-
import
|
|
1857
|
+
import fs5 from "fs";
|
|
1858
|
+
import path6 from "path";
|
|
1869
1859
|
var REVEAL = `"use client";
|
|
1870
1860
|
|
|
1871
1861
|
import { Children, cloneElement, isValidElement, useEffect, useRef, useState, type CSSProperties, type ReactElement, type ReactNode } from "react";
|
|
@@ -2090,8 +2080,8 @@ export function TextAnimate({ effect = "words", stagger = 0.06, delay = 0, durat
|
|
|
2090
2080
|
function motionFiles(config) {
|
|
2091
2081
|
const glob = config.components[0] ?? "components/**/*.tsx";
|
|
2092
2082
|
const base = glob.split("/").filter((p) => !p.includes("*")).join("/") || "components";
|
|
2093
|
-
const dir =
|
|
2094
|
-
return { reveal:
|
|
2083
|
+
const dir = `${base}/motion`;
|
|
2084
|
+
return { reveal: `${dir}/reveal.tsx`, text: `${dir}/text-animate.tsx` };
|
|
2095
2085
|
}
|
|
2096
2086
|
function ensureMotionComponents(config) {
|
|
2097
2087
|
const files = motionFiles(config);
|
|
@@ -2100,18 +2090,18 @@ function ensureMotionComponents(config) {
|
|
|
2100
2090
|
[files.reveal, REVEAL],
|
|
2101
2091
|
[files.text, TEXT_ANIMATE]
|
|
2102
2092
|
]) {
|
|
2103
|
-
const abs =
|
|
2104
|
-
if (
|
|
2105
|
-
|
|
2106
|
-
|
|
2093
|
+
const abs = path6.join(config.root, rel);
|
|
2094
|
+
if (fs5.existsSync(abs)) continue;
|
|
2095
|
+
fs5.mkdirSync(path6.dirname(abs), { recursive: true });
|
|
2096
|
+
fs5.writeFileSync(abs, content);
|
|
2107
2097
|
created = true;
|
|
2108
2098
|
}
|
|
2109
2099
|
return { ...files, created };
|
|
2110
2100
|
}
|
|
2111
2101
|
|
|
2112
2102
|
// src/core/components.ts
|
|
2113
|
-
import
|
|
2114
|
-
import
|
|
2103
|
+
import fs6 from "fs";
|
|
2104
|
+
import path7 from "path";
|
|
2115
2105
|
function componentsDir(config) {
|
|
2116
2106
|
const glob = config.components[0] ?? "components/**/*.tsx";
|
|
2117
2107
|
return glob.split("/").filter((p) => !p.includes("*")).join("/") || "components";
|
|
@@ -2175,11 +2165,11 @@ function importsFor(config, jsx, source, doc, fromRel, toRel) {
|
|
|
2175
2165
|
(s) => s.type === "ImportDeclaration" && s.importKind !== "type" && s.specifiers.some((sp2) => sp2.local.name === name)
|
|
2176
2166
|
);
|
|
2177
2167
|
if (!decl) {
|
|
2178
|
-
if (doc.declared.includes(name)) throw new EditError(`These layers use ${name}, defined in ${
|
|
2168
|
+
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
2169
|
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
2170
|
}
|
|
2181
2171
|
const sp = decl.specifiers.find((x) => x.local.name === name);
|
|
2182
|
-
const from = repoint(config, decl.source.value,
|
|
2172
|
+
const from = repoint(config, decl.source.value, path7.join(config.root, fromRel), path7.join(config.root, toRel));
|
|
2183
2173
|
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
2174
|
out.push(`import ${what} from ${JSON.stringify(from)};`);
|
|
2185
2175
|
}
|
|
@@ -2203,8 +2193,8 @@ ${indentBlock(indentBlock(jsx.trim()))}
|
|
|
2203
2193
|
`;
|
|
2204
2194
|
}
|
|
2205
2195
|
function newComponentPath(config, name) {
|
|
2206
|
-
const rel =
|
|
2207
|
-
if (
|
|
2196
|
+
const rel = `${componentsDir(config)}/${kebab(name)}.tsx`;
|
|
2197
|
+
if (fs6.existsSync(path7.join(config.root, rel))) throw new EditError(`${rel} already exists. Pick another name.`);
|
|
2208
2198
|
return rel;
|
|
2209
2199
|
}
|
|
2210
2200
|
|
|
@@ -2224,7 +2214,7 @@ var Workspace = class {
|
|
|
2224
2214
|
this.catalog = new Catalog(config.root, config.components, config.libraries);
|
|
2225
2215
|
this.comments = new Comments(config);
|
|
2226
2216
|
this.git = new Git(config.root);
|
|
2227
|
-
for (const c of this.canvases()) this.sources.set(c,
|
|
2217
|
+
for (const c of this.canvases()) this.sources.set(c, fs7.readFileSync(this.file(c), "utf8"));
|
|
2228
2218
|
}
|
|
2229
2219
|
config;
|
|
2230
2220
|
catalog;
|
|
@@ -2255,12 +2245,12 @@ var Workspace = class {
|
|
|
2255
2245
|
return canvasPath(this.config, canvas);
|
|
2256
2246
|
}
|
|
2257
2247
|
relFile(canvas) {
|
|
2258
|
-
return
|
|
2248
|
+
return posix(path8.relative(this.config.root, this.file(canvas)));
|
|
2259
2249
|
}
|
|
2260
2250
|
read(canvas) {
|
|
2261
2251
|
const file = this.file(canvas);
|
|
2262
|
-
if (!
|
|
2263
|
-
const src =
|
|
2252
|
+
if (!fs7.existsSync(file)) throw new EditError(`Canvas "${canvas}" does not exist. Canvases: ${this.canvases().join(", ") || "none"}`);
|
|
2253
|
+
const src = fs7.readFileSync(file, "utf8");
|
|
2264
2254
|
if (!this.sources.has(canvas)) this.sources.set(canvas, src);
|
|
2265
2255
|
return src;
|
|
2266
2256
|
}
|
|
@@ -2285,7 +2275,7 @@ var Workspace = class {
|
|
|
2285
2275
|
docCache = /* @__PURE__ */ new Map();
|
|
2286
2276
|
stamp(rel) {
|
|
2287
2277
|
try {
|
|
2288
|
-
const st =
|
|
2278
|
+
const st = fs7.statSync(path8.join(this.config.root, rel));
|
|
2289
2279
|
return `${st.mtimeMs}:${st.size}`;
|
|
2290
2280
|
} catch {
|
|
2291
2281
|
return "missing";
|
|
@@ -2299,8 +2289,8 @@ var Workspace = class {
|
|
|
2299
2289
|
const linked = fileOfId(id);
|
|
2300
2290
|
if (linked) assertShown(doc, linked);
|
|
2301
2291
|
const rel = linked ?? this.relFile(canvas);
|
|
2302
|
-
const abs =
|
|
2303
|
-
return { node: hit.node, rel, abs, source:
|
|
2292
|
+
const abs = path8.join(this.config.root, rel);
|
|
2293
|
+
return { node: hit.node, rel, abs, source: fs7.readFileSync(abs, "utf8") };
|
|
2304
2294
|
}
|
|
2305
2295
|
/** Page and layout files shown by linked frames, across all canvases (the Git panel counts them as design files). */
|
|
2306
2296
|
linkedFiles() {
|
|
@@ -2319,7 +2309,7 @@ var Workspace = class {
|
|
|
2319
2309
|
*/
|
|
2320
2310
|
designFiles() {
|
|
2321
2311
|
const m = motionFiles(this.config);
|
|
2322
|
-
return [...this.linkedFiles(), ...[m.reveal, m.text].filter((f) =>
|
|
2312
|
+
return [...this.linkedFiles(), ...[m.reveal, m.text].filter((f) => fs7.existsSync(path8.join(this.config.root, f)))];
|
|
2323
2313
|
}
|
|
2324
2314
|
/** Canvases with a linked frame showing this page or layout file. */
|
|
2325
2315
|
canvasesLinking(rel) {
|
|
@@ -2335,7 +2325,7 @@ var Workspace = class {
|
|
|
2335
2325
|
linkedFileChanged(rel) {
|
|
2336
2326
|
let next;
|
|
2337
2327
|
try {
|
|
2338
|
-
next =
|
|
2328
|
+
next = fs7.readFileSync(path8.join(this.config.root, rel), "utf8");
|
|
2339
2329
|
} catch {
|
|
2340
2330
|
next = "";
|
|
2341
2331
|
}
|
|
@@ -2347,9 +2337,9 @@ var Workspace = class {
|
|
|
2347
2337
|
}
|
|
2348
2338
|
createCanvas(name, actor, content = STARTER_CANVAS) {
|
|
2349
2339
|
const file = canvasPath(this.config, name);
|
|
2350
|
-
if (
|
|
2351
|
-
|
|
2352
|
-
|
|
2340
|
+
if (fs7.existsSync(file)) throw new EditError(`Canvas "${name}" already exists.`);
|
|
2341
|
+
fs7.mkdirSync(path8.dirname(file), { recursive: true });
|
|
2342
|
+
fs7.writeFileSync(file, content);
|
|
2353
2343
|
this.sources.set(name, content);
|
|
2354
2344
|
this.canvasesChanged();
|
|
2355
2345
|
this.record(name, `Created canvas ${name}`, actor, "", content, []);
|
|
@@ -2384,10 +2374,10 @@ var Workspace = class {
|
|
|
2384
2374
|
}
|
|
2385
2375
|
renameCanvas(from, to) {
|
|
2386
2376
|
const src = this.file(from);
|
|
2387
|
-
if (!
|
|
2377
|
+
if (!fs7.existsSync(src)) throw new EditError(`Canvas "${from}" does not exist.`);
|
|
2388
2378
|
const dest = canvasPath(this.config, to);
|
|
2389
|
-
if (
|
|
2390
|
-
|
|
2379
|
+
if (fs7.existsSync(dest)) throw new EditError(`A canvas named "${to}" already exists.`);
|
|
2380
|
+
fs7.renameSync(src, dest);
|
|
2391
2381
|
this.comments.renameCanvas(from, to);
|
|
2392
2382
|
for (const map of [this.sources, this.undoStacks, this.redoStacks]) {
|
|
2393
2383
|
if (map.has(from)) {
|
|
@@ -2404,7 +2394,7 @@ var Workspace = class {
|
|
|
2404
2394
|
duplicateCanvas(name) {
|
|
2405
2395
|
const source = this.read(name);
|
|
2406
2396
|
const copy = this.uniqueCanvasName(`${name}-copy`);
|
|
2407
|
-
|
|
2397
|
+
fs7.writeFileSync(canvasPath(this.config, copy), source);
|
|
2408
2398
|
this.sources.set(copy, source);
|
|
2409
2399
|
this.canvasesChanged();
|
|
2410
2400
|
return copy;
|
|
@@ -2413,9 +2403,9 @@ var Workspace = class {
|
|
|
2413
2403
|
trash = /* @__PURE__ */ new Map();
|
|
2414
2404
|
deleteCanvas(name) {
|
|
2415
2405
|
const file = this.file(name);
|
|
2416
|
-
if (!
|
|
2417
|
-
this.trash.set(name,
|
|
2418
|
-
|
|
2406
|
+
if (!fs7.existsSync(file)) throw new EditError(`Canvas "${name}" does not exist.`);
|
|
2407
|
+
this.trash.set(name, fs7.readFileSync(file, "utf8"));
|
|
2408
|
+
fs7.unlinkSync(file);
|
|
2419
2409
|
this.sources.delete(name);
|
|
2420
2410
|
this.undoStacks.delete(name);
|
|
2421
2411
|
this.redoStacks.delete(name);
|
|
@@ -2425,7 +2415,7 @@ var Workspace = class {
|
|
|
2425
2415
|
const source = this.trash.get(name);
|
|
2426
2416
|
if (source === void 0) throw new EditError(`Nothing to restore for "${name}".`);
|
|
2427
2417
|
const target = this.uniqueCanvasName(name);
|
|
2428
|
-
|
|
2418
|
+
fs7.writeFileSync(canvasPath(this.config, target), source);
|
|
2429
2419
|
this.sources.set(target, source);
|
|
2430
2420
|
this.trash.delete(name);
|
|
2431
2421
|
this.canvasesChanged();
|
|
@@ -2436,14 +2426,14 @@ var Workspace = class {
|
|
|
2436
2426
|
if (!fileName.endsWith(CANVAS_SUFFIX)) return;
|
|
2437
2427
|
const canvas = canvasNameOf(fileName);
|
|
2438
2428
|
const file = this.file(canvas);
|
|
2439
|
-
if (!
|
|
2429
|
+
if (!fs7.existsSync(file)) {
|
|
2440
2430
|
this.sources.delete(canvas);
|
|
2441
2431
|
syncRoute(this.config);
|
|
2442
2432
|
this.emit({ type: "canvases", canvases: this.canvases() });
|
|
2443
2433
|
return;
|
|
2444
2434
|
}
|
|
2445
2435
|
const prev = this.sources.get(canvas);
|
|
2446
|
-
const next =
|
|
2436
|
+
const next = fs7.readFileSync(file, "utf8");
|
|
2447
2437
|
if (prev === next) return;
|
|
2448
2438
|
this.sources.set(canvas, next);
|
|
2449
2439
|
if (prev === void 0) {
|
|
@@ -2462,8 +2452,8 @@ var Workspace = class {
|
|
|
2462
2452
|
} catch (err) {
|
|
2463
2453
|
if (!(err instanceof ConflictError)) throw err;
|
|
2464
2454
|
if (err.file) this.linkedFileChanged(err.file);
|
|
2465
|
-
else this.externalChange(
|
|
2466
|
-
if (nodeRefs(cmd).length || "frame" in cmd && cmd.frame) throw new EditError(`${
|
|
2455
|
+
else this.externalChange(path8.basename(this.file(cmd.canvas)));
|
|
2456
|
+
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
2457
|
return this.exec(cmd, actor);
|
|
2468
2458
|
}
|
|
2469
2459
|
});
|
|
@@ -2473,7 +2463,7 @@ var Workspace = class {
|
|
|
2473
2463
|
async exec(cmd, actor) {
|
|
2474
2464
|
cmd = { ...cmd };
|
|
2475
2465
|
let prepared = null;
|
|
2476
|
-
if (cmd.op === "add_component_frame" && !
|
|
2466
|
+
if (cmd.op === "add_component_frame" && !fs7.existsSync(this.file(cmd.canvas))) {
|
|
2477
2467
|
await this.catalog.load();
|
|
2478
2468
|
const spec = this.catalog.get(cmd.component);
|
|
2479
2469
|
if (!spec) throw new EditError(`Unknown component "${cmd.component}". Use list_components.`);
|
|
@@ -2501,12 +2491,12 @@ var Workspace = class {
|
|
|
2501
2491
|
prepared = { kind: "apply", frame, ...plan };
|
|
2502
2492
|
}
|
|
2503
2493
|
const rel = target ?? this.relFile(cmd.canvas);
|
|
2504
|
-
const file =
|
|
2505
|
-
const original = target ?
|
|
2494
|
+
const file = path8.join(this.config.root, rel);
|
|
2495
|
+
const original = target ? fs7.readFileSync(file, "utf8") : canvasSource;
|
|
2506
2496
|
const crlf = original.includes("\r\n");
|
|
2507
2497
|
const before = crlf ? original.replace(/\r\n/g, "\n") : original;
|
|
2508
|
-
const
|
|
2509
|
-
const doc =
|
|
2498
|
+
const parse = (src) => target ? parseTargetDoc(target, src) : parseCanvas(cmd.canvas, rel, src);
|
|
2499
|
+
const doc = parse(before);
|
|
2510
2500
|
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
2501
|
if (cmd.op === "insert_icon") {
|
|
2512
2502
|
const icon = cmd;
|
|
@@ -2560,7 +2550,7 @@ var Workspace = class {
|
|
|
2560
2550
|
imports = importsFor(this.config, jsx, before, doc, rel, fileRel);
|
|
2561
2551
|
movedNames = new Set(componentNamesIn(jsx));
|
|
2562
2552
|
}
|
|
2563
|
-
const abs =
|
|
2553
|
+
const abs = path8.join(this.config.root, fileRel);
|
|
2564
2554
|
newComponent = { rel: fileRel, content: await this.format(componentFile(name, jsx, imports), abs).catch(() => componentFile(name, jsx, imports)) };
|
|
2565
2555
|
prepared = { kind: "component", name, file: fileRel };
|
|
2566
2556
|
}
|
|
@@ -2578,8 +2568,8 @@ var Workspace = class {
|
|
|
2578
2568
|
let after = await finish(result);
|
|
2579
2569
|
let ids = [];
|
|
2580
2570
|
const reparse = (src) => {
|
|
2581
|
-
const d =
|
|
2582
|
-
if (!d) throw new EditError(`That edit would put logic into ${
|
|
2571
|
+
const d = parse(src);
|
|
2572
|
+
if (!d) throw new EditError(`That edit would put logic into ${path8.basename(rel)}. Only plain JSX can be edited from the canvas.`);
|
|
2583
2573
|
return d;
|
|
2584
2574
|
};
|
|
2585
2575
|
if (after.includes(MARK)) {
|
|
@@ -2604,8 +2594,8 @@ var Workspace = class {
|
|
|
2604
2594
|
if (cmd.op === "apply_to_page") ids = [findFrame(canvasDoc, cmd.frame).id];
|
|
2605
2595
|
const writeNewComponent = () => {
|
|
2606
2596
|
if (!newComponent) return;
|
|
2607
|
-
|
|
2608
|
-
|
|
2597
|
+
fs7.mkdirSync(path8.dirname(path8.join(this.config.root, newComponent.rel)), { recursive: true });
|
|
2598
|
+
fs7.writeFileSync(path8.join(this.config.root, newComponent.rel), newComponent.content, { flag: "wx" });
|
|
2609
2599
|
this.catalog.invalidate();
|
|
2610
2600
|
this.emit({ type: "catalog" });
|
|
2611
2601
|
};
|
|
@@ -2614,11 +2604,11 @@ var Workspace = class {
|
|
|
2614
2604
|
writeNewComponent();
|
|
2615
2605
|
return { doc: target ? this.doc(cmd.canvas) : view, ids, label };
|
|
2616
2606
|
}
|
|
2617
|
-
if (
|
|
2607
|
+
if (fs7.readFileSync(file, "utf8") !== original) throw new ConflictError(`${path8.basename(rel)} changed during the edit.`, target ?? void 0);
|
|
2618
2608
|
writeNewComponent();
|
|
2619
2609
|
if (target) this.sources.set(`file:${target}`, after);
|
|
2620
2610
|
else this.sources.set(cmd.canvas, after);
|
|
2621
|
-
|
|
2611
|
+
fs7.writeFileSync(file, after);
|
|
2622
2612
|
const newDoc = this.doc(cmd.canvas);
|
|
2623
2613
|
this.record(cmd.canvas, label, actor, original, after, ids, target ?? void 0, touchedFrames(cmd, ids, view, newDoc));
|
|
2624
2614
|
if (renamedFrame) {
|
|
@@ -2644,7 +2634,7 @@ var Workspace = class {
|
|
|
2644
2634
|
const f = findFrame(view, cmd.parent);
|
|
2645
2635
|
if (f?.link) {
|
|
2646
2636
|
if (!f.link.editable) throw viewOnly(f);
|
|
2647
|
-
const src =
|
|
2637
|
+
const src = fs7.readFileSync(path8.join(this.config.root, f.link.page), "utf8");
|
|
2648
2638
|
if (f.link.kind === "component") {
|
|
2649
2639
|
const own = parseModuleDoc(f.link.page, src)?.frames.find((x) => x.frameName === f.link.route);
|
|
2650
2640
|
if (!own) throw viewOnly(f);
|
|
@@ -2669,7 +2659,7 @@ var Workspace = class {
|
|
|
2669
2659
|
const refs = nodeRefs(cmd);
|
|
2670
2660
|
const files = new Set(refs.map(fileOfId));
|
|
2671
2661
|
if (files.size > 1) {
|
|
2672
|
-
const names = [...files].map((f) => f ?
|
|
2662
|
+
const names = [...files].map((f) => f ? path8.basename(f) : "the canvas");
|
|
2673
2663
|
throw new EditError(`These layers live in different files (${names.join(", ")}). Edit them one file at a time.`);
|
|
2674
2664
|
}
|
|
2675
2665
|
return [...files][0] ?? null;
|
|
@@ -2717,12 +2707,12 @@ var Workspace = class {
|
|
|
2717
2707
|
const doc = ed.doc;
|
|
2718
2708
|
const guard = (node, verb) => {
|
|
2719
2709
|
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(
|
|
2710
|
+
if (node.path.length <= 2) throw new EditError(`Can't ${verb} the root element of ${path8.basename(rel)}.`);
|
|
2711
|
+
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
2712
|
};
|
|
2723
2713
|
const literalOnly = (node, key) => {
|
|
2724
2714
|
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 ${
|
|
2715
|
+
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
2716
|
};
|
|
2727
2717
|
switch (cmd.op) {
|
|
2728
2718
|
case "set_props": {
|
|
@@ -3187,11 +3177,11 @@ ${rows}
|
|
|
3187
3177
|
const entry = stack[stack.length - 1];
|
|
3188
3178
|
if (!entry) throw new EditError(`Nothing to ${dir}.`);
|
|
3189
3179
|
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 ?
|
|
3180
|
+
const file = entry.file ? path8.join(this.config.root, entry.file) : this.file(canvas);
|
|
3181
|
+
const name = entry.file ? path8.basename(entry.file) : "the file";
|
|
3192
3182
|
let current;
|
|
3193
3183
|
try {
|
|
3194
|
-
current = entry.file ?
|
|
3184
|
+
current = entry.file ? fs7.readFileSync(file, "utf8") : this.read(canvas);
|
|
3195
3185
|
} catch {
|
|
3196
3186
|
throw new EditError(`Cannot ${dir} "${entry.label}": ${name} can't be read right now.`);
|
|
3197
3187
|
}
|
|
@@ -3200,7 +3190,7 @@ ${rows}
|
|
|
3200
3190
|
stack.pop();
|
|
3201
3191
|
const next = dir === "undo" ? entry.before : entry.after;
|
|
3202
3192
|
this.sources.set(entry.file ? `file:${entry.file}` : canvas, next);
|
|
3203
|
-
|
|
3193
|
+
fs7.writeFileSync(file, next);
|
|
3204
3194
|
const list = to.get(canvas) ?? [];
|
|
3205
3195
|
list.push(entry);
|
|
3206
3196
|
to.set(canvas, list);
|
|
@@ -3241,17 +3231,17 @@ ${rows}
|
|
|
3241
3231
|
if (this.agents.delete(session)) this.emit({ type: "agents", agents: [...this.agents.values()] });
|
|
3242
3232
|
}
|
|
3243
3233
|
ensureRoute() {
|
|
3244
|
-
if (!
|
|
3245
|
-
|
|
3234
|
+
if (!fs7.existsSync(path8.join(this.config.root, this.config.canvasDir))) {
|
|
3235
|
+
fs7.mkdirSync(path8.join(this.config.root, this.config.canvasDir), { recursive: true });
|
|
3246
3236
|
}
|
|
3247
3237
|
if (!this.canvases().length) {
|
|
3248
3238
|
const file = canvasPath(this.config, "home");
|
|
3249
3239
|
const content = this.homeCanvas();
|
|
3250
|
-
|
|
3240
|
+
fs7.writeFileSync(file, content);
|
|
3251
3241
|
void this.format(content, file).then((pretty) => {
|
|
3252
|
-
if (
|
|
3242
|
+
if (fs7.readFileSync(file, "utf8") !== content) return;
|
|
3253
3243
|
this.sources.set("home", pretty);
|
|
3254
|
-
|
|
3244
|
+
fs7.writeFileSync(file, pretty);
|
|
3255
3245
|
}).catch(() => {
|
|
3256
3246
|
});
|
|
3257
3247
|
}
|
|
@@ -3262,7 +3252,7 @@ ${rows}
|
|
|
3262
3252
|
try {
|
|
3263
3253
|
const home = listRoutes(this.config).find((r) => r.route === "/");
|
|
3264
3254
|
if (!home) return STARTER_CANVAS;
|
|
3265
|
-
const rel =
|
|
3255
|
+
const rel = path8.relative(this.config.root, canvasPath(this.config, "home"));
|
|
3266
3256
|
const empty = parseCanvas("home", rel, `import { Canvas, Frame } from "truecanvas";
|
|
3267
3257
|
export default function C() { return <Canvas></Canvas>; }
|
|
3268
3258
|
`);
|
|
@@ -3320,7 +3310,7 @@ function publicEntry(e) {
|
|
|
3320
3310
|
}
|
|
3321
3311
|
function assertShown(view, rel) {
|
|
3322
3312
|
const shown = view.frames.some((f) => f.link?.files.includes(rel));
|
|
3323
|
-
if (!shown ||
|
|
3313
|
+
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
3314
|
}
|
|
3325
3315
|
function rightEdge(doc) {
|
|
3326
3316
|
return doc.frames.reduce((m, f) => Math.max(m, f.x + f.width), -80);
|
|
@@ -3392,15 +3382,15 @@ function componentJsx(spec, props = {}, text) {
|
|
|
3392
3382
|
|
|
3393
3383
|
// src/core/pm.ts
|
|
3394
3384
|
import { spawn } from "child_process";
|
|
3395
|
-
import
|
|
3396
|
-
import
|
|
3385
|
+
import fs8 from "fs";
|
|
3386
|
+
import path9 from "path";
|
|
3397
3387
|
function detectPm(dir) {
|
|
3398
|
-
for (let d = dir; ; d =
|
|
3399
|
-
if (
|
|
3400
|
-
if (
|
|
3401
|
-
if (
|
|
3402
|
-
if (
|
|
3403
|
-
if (
|
|
3388
|
+
for (let d = dir; ; d = path9.dirname(d)) {
|
|
3389
|
+
if (fs8.existsSync(path9.join(d, "pnpm-lock.yaml")) || fs8.existsSync(path9.join(d, "pnpm-workspace.yaml"))) return "pnpm";
|
|
3390
|
+
if (fs8.existsSync(path9.join(d, "yarn.lock"))) return "yarn";
|
|
3391
|
+
if (fs8.existsSync(path9.join(d, "bun.lock")) || fs8.existsSync(path9.join(d, "bun.lockb"))) return "bun";
|
|
3392
|
+
if (fs8.existsSync(path9.join(d, "package-lock.json"))) return "npm";
|
|
3393
|
+
if (path9.dirname(d) === d || fs8.existsSync(path9.join(d, ".git"))) break;
|
|
3404
3394
|
}
|
|
3405
3395
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
3406
3396
|
if (agent.startsWith("pnpm")) return "pnpm";
|
|
@@ -3420,7 +3410,12 @@ function dlx(pm, pkg, args) {
|
|
|
3420
3410
|
return ["npx", ["--yes", pkg, ...args]];
|
|
3421
3411
|
}
|
|
3422
3412
|
function toolEnv(extra = {}) {
|
|
3423
|
-
return { ...process.env, PATH: `${
|
|
3413
|
+
return { ...process.env, PATH: `${path9.dirname(process.execPath)}${path9.delimiter}${process.env.PATH ?? ""}`, ...extra };
|
|
3414
|
+
}
|
|
3415
|
+
function shellSafe(cmd, args) {
|
|
3416
|
+
if (process.platform !== "win32") return { cmd, args, shell: false };
|
|
3417
|
+
const quote = (a) => /[\s"&|<>^()%!]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a;
|
|
3418
|
+
return { cmd: quote(cmd), args: args.map(quote), shell: true };
|
|
3424
3419
|
}
|
|
3425
3420
|
function run2(cmd, args, cwd, timeoutMs = 3e5) {
|
|
3426
3421
|
return new Promise((resolve) => {
|
|
@@ -3432,7 +3427,8 @@ function run2(cmd, args, cwd, timeoutMs = 3e5) {
|
|
|
3432
3427
|
clearTimeout(timer);
|
|
3433
3428
|
resolve({ ok, out: (out + extra).slice(-2e4) });
|
|
3434
3429
|
};
|
|
3435
|
-
const
|
|
3430
|
+
const safe = shellSafe(cmd, args);
|
|
3431
|
+
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
3432
|
const timer = setTimeout(() => {
|
|
3437
3433
|
p.kill();
|
|
3438
3434
|
finish(false, `
|
|
@@ -3447,24 +3443,24 @@ ${err.message}`));
|
|
|
3447
3443
|
}
|
|
3448
3444
|
|
|
3449
3445
|
// src/core/shadcn.ts
|
|
3450
|
-
import
|
|
3451
|
-
import
|
|
3446
|
+
import fs9 from "fs";
|
|
3447
|
+
import path10 from "path";
|
|
3452
3448
|
function shadcnStatus(root) {
|
|
3453
|
-
const file =
|
|
3454
|
-
if (!
|
|
3449
|
+
const file = path10.join(root, "components.json");
|
|
3450
|
+
if (!fs9.existsSync(file)) return { initialized: false, uiDir: null, installed: [] };
|
|
3455
3451
|
let ui = "@/components/ui";
|
|
3456
3452
|
try {
|
|
3457
|
-
ui = JSON.parse(
|
|
3453
|
+
ui = JSON.parse(fs9.readFileSync(file, "utf8"))?.aliases?.ui ?? ui;
|
|
3458
3454
|
} catch {
|
|
3459
3455
|
}
|
|
3460
3456
|
const dir = resolveAliasPath(root, ui);
|
|
3461
3457
|
let installed = [];
|
|
3462
3458
|
try {
|
|
3463
|
-
installed =
|
|
3459
|
+
installed = fs9.readdirSync(dir).filter((f) => /\.(tsx|jsx)$/.test(f)).map((f) => f.replace(/\.(tsx|jsx)$/, "")).sort();
|
|
3464
3460
|
} catch {
|
|
3465
3461
|
installed = [];
|
|
3466
3462
|
}
|
|
3467
|
-
return { initialized: true, uiDir:
|
|
3463
|
+
return { initialized: true, uiDir: posix(path10.relative(root, dir)), installed };
|
|
3468
3464
|
}
|
|
3469
3465
|
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
3466
|
var registryCache = null;
|
|
@@ -3596,8 +3592,8 @@ function describeComponent(c, verbose = false) {
|
|
|
3596
3592
|
}
|
|
3597
3593
|
|
|
3598
3594
|
// src/core/tokens.ts
|
|
3599
|
-
import
|
|
3600
|
-
import
|
|
3595
|
+
import fs10 from "fs";
|
|
3596
|
+
import path11 from "path";
|
|
3601
3597
|
import { globSync } from "tinyglobby";
|
|
3602
3598
|
var GROUPS = [
|
|
3603
3599
|
["--color-", "Colors", "bg-{name} text-{name} border-{name}"],
|
|
@@ -3614,9 +3610,9 @@ function readDesignTokens(root) {
|
|
|
3614
3610
|
const sources = [];
|
|
3615
3611
|
let darkVariant = null;
|
|
3616
3612
|
for (const file of files) {
|
|
3617
|
-
const css =
|
|
3613
|
+
const css = fs10.readFileSync(file, "utf8");
|
|
3618
3614
|
if (!css.includes("@theme") && !css.includes("@custom-variant")) continue;
|
|
3619
|
-
sources.push(
|
|
3615
|
+
sources.push(posix(path11.relative(root, file)));
|
|
3620
3616
|
const dv = /@custom-variant\s+dark\s*\(([^;]+)\);/.exec(css);
|
|
3621
3617
|
if (dv) darkVariant = dv[1].trim();
|
|
3622
3618
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) {
|
|
@@ -3655,7 +3651,7 @@ function readTokenData(root) {
|
|
|
3655
3651
|
for (const m of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) into.set(m[1], m[2].trim());
|
|
3656
3652
|
};
|
|
3657
3653
|
for (const file of files) {
|
|
3658
|
-
const css =
|
|
3654
|
+
const css = fs10.readFileSync(file, "utf8");
|
|
3659
3655
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) decls(block[1], theme);
|
|
3660
3656
|
for (const block of css.matchAll(/(?:^|\n)\s*:root\s*\{([\s\S]*?)\n\}/g)) decls(block[1], lightVars);
|
|
3661
3657
|
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 +3683,30 @@ function readTokenData(root) {
|
|
|
3687
3683
|
}
|
|
3688
3684
|
|
|
3689
3685
|
// src/core/compare.ts
|
|
3690
|
-
import
|
|
3691
|
-
import
|
|
3686
|
+
import fs11 from "fs";
|
|
3687
|
+
import path12 from "path";
|
|
3692
3688
|
var COMPARE_PREFIX = "__compare__";
|
|
3693
3689
|
function writeCompare(config, canvas, source, files = {}, as = canvas) {
|
|
3694
|
-
const routeDir =
|
|
3695
|
-
const dir =
|
|
3696
|
-
|
|
3697
|
-
const canvasDir =
|
|
3698
|
-
const canvasRel =
|
|
3690
|
+
const routeDir = routeDirOf(config);
|
|
3691
|
+
const dir = path12.join(routeDir, "compare");
|
|
3692
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
3693
|
+
const canvasDir = path12.join(config.root, config.canvasDir);
|
|
3694
|
+
const canvasRel = `${config.canvasDir}/${canvas}.canvas.tsx`;
|
|
3699
3695
|
const swapped = /* @__PURE__ */ new Map();
|
|
3700
3696
|
for (const [rel, content] of Object.entries(files)) {
|
|
3701
|
-
const ext =
|
|
3697
|
+
const ext = path12.extname(rel);
|
|
3702
3698
|
const name = `${as}--${rel.slice(0, -ext.length).replace(/[^\w.-]+/g, "_")}${ext}`;
|
|
3703
|
-
const original =
|
|
3704
|
-
|
|
3699
|
+
const original = path12.join(config.root, rel);
|
|
3700
|
+
fs11.writeFileSync(path12.join(dir, name), rewriteImports(content, (spec) => spec.startsWith(".") ? relativeSpec(dir, path12.resolve(path12.dirname(original), spec)) : spec));
|
|
3705
3701
|
swapped.set(importSpecifier(config.root, canvasRel, rel), `./${name.slice(0, -ext.length)}`);
|
|
3706
3702
|
}
|
|
3707
|
-
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir,
|
|
3708
|
-
|
|
3703
|
+
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir, path12.resolve(canvasDir, spec)) : spec));
|
|
3704
|
+
fs11.writeFileSync(path12.join(dir, `${as}.canvas.tsx`), out);
|
|
3709
3705
|
syncRoute(config);
|
|
3710
3706
|
return `${COMPARE_PREFIX}${as}`;
|
|
3711
3707
|
}
|
|
3712
3708
|
function relativeSpec(fromDir, target) {
|
|
3713
|
-
const next =
|
|
3709
|
+
const next = posix(path12.relative(fromDir, target));
|
|
3714
3710
|
return next.startsWith(".") ? next : `./${next}`;
|
|
3715
3711
|
}
|
|
3716
3712
|
function rewriteImports(source, map) {
|
|
@@ -3726,9 +3722,9 @@ function rewriteImports(source, map) {
|
|
|
3726
3722
|
}
|
|
3727
3723
|
function clearCompare(config, canvas) {
|
|
3728
3724
|
if (canvas) assertCanvasName(canvas);
|
|
3729
|
-
const dir =
|
|
3730
|
-
if (!
|
|
3731
|
-
for (const f of
|
|
3725
|
+
const dir = path12.join(routeDirOf(config), "compare");
|
|
3726
|
+
if (!fs11.existsSync(dir)) return;
|
|
3727
|
+
for (const f of fs11.readdirSync(dir)) if (!canvas || f === `${canvas}.canvas.tsx` || f.startsWith(`${canvas}--`)) fs11.rmSync(path12.join(dir, f));
|
|
3732
3728
|
syncRoute(config);
|
|
3733
3729
|
}
|
|
3734
3730
|
function frameChanges(before, after) {
|
|
@@ -3745,24 +3741,48 @@ function frameChanges(before, after) {
|
|
|
3745
3741
|
}
|
|
3746
3742
|
|
|
3747
3743
|
// src/core/init.ts
|
|
3748
|
-
import
|
|
3744
|
+
import fs12 from "fs";
|
|
3749
3745
|
import MagicString2 from "magic-string";
|
|
3750
|
-
import
|
|
3751
|
-
var CONFIG_FILES =
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3746
|
+
import path13 from "path";
|
|
3747
|
+
var CONFIG_FILES = {
|
|
3748
|
+
next: ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.mts"],
|
|
3749
|
+
vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]
|
|
3750
|
+
};
|
|
3751
|
+
function projectFramework(root) {
|
|
3752
|
+
try {
|
|
3753
|
+
const pkg = JSON.parse(fs12.readFileSync(path13.join(root, "package.json"), "utf8"));
|
|
3754
|
+
return detectFramework({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
3755
|
+
} catch {
|
|
3756
|
+
return "next";
|
|
3756
3757
|
}
|
|
3757
|
-
|
|
3758
|
+
}
|
|
3759
|
+
function configStatus(root, framework = projectFramework(root)) {
|
|
3760
|
+
const marker = framework === "vite" ? "truecanvas/vite" : "withTruecanvas";
|
|
3761
|
+
for (const name of CONFIG_FILES[framework]) {
|
|
3762
|
+
const file = path13.join(root, name);
|
|
3763
|
+
if (fs12.existsSync(file)) return { file, wrapped: fs12.readFileSync(file, "utf8").includes(marker), framework };
|
|
3764
|
+
}
|
|
3765
|
+
return { file: null, wrapped: false, framework };
|
|
3758
3766
|
}
|
|
3759
3767
|
function initProject(config) {
|
|
3760
3768
|
const { root } = config;
|
|
3761
3769
|
const report = { done: [], todo: [] };
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3770
|
+
const vite = config.framework === "vite";
|
|
3771
|
+
const status = configStatus(root, config.framework);
|
|
3772
|
+
if (vite) {
|
|
3773
|
+
const name = status.file ? path13.basename(status.file) : "vite.config.ts";
|
|
3774
|
+
if (status.wrapped) report.done.push(`${name} already uses the truecanvas() plugin`);
|
|
3775
|
+
else {
|
|
3776
|
+
const src = status.file ? fs12.readFileSync(status.file, "utf8") : null;
|
|
3777
|
+
const next = src && addVitePlugin(src);
|
|
3778
|
+
if (next) {
|
|
3779
|
+
fs12.writeFileSync(status.file, next);
|
|
3780
|
+
report.done.push(`Added the truecanvas() plugin to ${name}`);
|
|
3781
|
+
} else report.todo.push(`Add truecanvas() from "truecanvas/vite" to the plugins in ${name}`);
|
|
3782
|
+
}
|
|
3783
|
+
} else if (!status.file) {
|
|
3784
|
+
const file = path13.join(root, "next.config.ts");
|
|
3785
|
+
fs12.writeFileSync(file, `import type { NextConfig } from "next";
|
|
3766
3786
|
import { withTruecanvas } from "truecanvas/next";
|
|
3767
3787
|
|
|
3768
3788
|
const nextConfig: NextConfig = {};
|
|
@@ -3771,49 +3791,49 @@ export default withTruecanvas(nextConfig);
|
|
|
3771
3791
|
`);
|
|
3772
3792
|
report.done.push("Created next.config.ts with withTruecanvas()");
|
|
3773
3793
|
} else if (status.wrapped) {
|
|
3774
|
-
report.done.push(`${
|
|
3794
|
+
report.done.push(`${path13.basename(status.file)} already uses withTruecanvas()`);
|
|
3775
3795
|
} else {
|
|
3776
|
-
const src =
|
|
3796
|
+
const src = fs12.readFileSync(status.file, "utf8");
|
|
3777
3797
|
const next = wrapConfigExport(src);
|
|
3778
3798
|
if (next && next !== src && next.includes("withTruecanvas(")) {
|
|
3779
|
-
|
|
3780
|
-
report.done.push(`Wrapped ${
|
|
3799
|
+
fs12.writeFileSync(status.file, next);
|
|
3800
|
+
report.done.push(`Wrapped ${path13.basename(status.file)} with withTruecanvas()`);
|
|
3781
3801
|
} else {
|
|
3782
|
-
report.todo.push(`Wrap your ${
|
|
3802
|
+
report.todo.push(`Wrap your ${path13.basename(status.file)} export with withTruecanvas() from "truecanvas/next"`);
|
|
3783
3803
|
}
|
|
3784
3804
|
}
|
|
3785
|
-
const pkgFile =
|
|
3805
|
+
const pkgFile = path13.join(root, "package.json");
|
|
3786
3806
|
try {
|
|
3787
|
-
const raw =
|
|
3807
|
+
const raw = fs12.readFileSync(pkgFile, "utf8");
|
|
3788
3808
|
const pkg = JSON.parse(raw);
|
|
3789
3809
|
pkg.scripts ??= {};
|
|
3790
3810
|
if (!Object.values(pkg.scripts).some((v) => typeof v === "string" && v.includes("truecanvas"))) {
|
|
3791
3811
|
pkg.scripts.canvas = "truecanvas dev";
|
|
3792
3812
|
const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
|
|
3793
|
-
|
|
3813
|
+
fs12.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, indent)}
|
|
3794
3814
|
`);
|
|
3795
3815
|
report.done.push(`Added the "canvas" script (npm run canvas)`);
|
|
3796
3816
|
}
|
|
3797
3817
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3798
3818
|
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
|
|
3819
|
+
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
3820
|
} catch {
|
|
3801
3821
|
report.todo.push("Couldn't read package.json");
|
|
3802
3822
|
}
|
|
3803
|
-
const ignoreFile =
|
|
3804
|
-
const entry = `/${config.
|
|
3805
|
-
const ignore =
|
|
3823
|
+
const ignoreFile = path13.join(root, ".gitignore");
|
|
3824
|
+
const entry = `/${config.routeDir}/`;
|
|
3825
|
+
const ignore = fs12.existsSync(ignoreFile) ? fs12.readFileSync(ignoreFile, "utf8") : "";
|
|
3806
3826
|
if (!ignore.split("\n").some((l) => l.trim() === entry || l.trim() === entry.slice(1))) {
|
|
3807
|
-
|
|
3827
|
+
fs12.writeFileSync(ignoreFile, `${ignore}${ignore && !ignore.endsWith("\n") ? "\n" : ""}
|
|
3808
3828
|
# Truecanvas (generated dev-only route)
|
|
3809
3829
|
${entry}
|
|
3810
3830
|
`);
|
|
3811
3831
|
report.done.push(`Ignored ${entry} in .gitignore`);
|
|
3812
3832
|
}
|
|
3813
3833
|
for (const name of ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs", "tailwind.config.cjs"]) {
|
|
3814
|
-
const file =
|
|
3815
|
-
if (!
|
|
3816
|
-
const src =
|
|
3834
|
+
const file = path13.join(root, name);
|
|
3835
|
+
if (!fs12.existsSync(file)) continue;
|
|
3836
|
+
const src = fs12.readFileSync(file, "utf8");
|
|
3817
3837
|
const glob = `./${config.canvasDir}/**/*.{ts,tsx}`;
|
|
3818
3838
|
if (src.includes(config.canvasDir)) break;
|
|
3819
3839
|
const m = /content\s*:\s*\[([ \t]*\n?)([ \t]*)/.exec(src);
|
|
@@ -3822,12 +3842,12 @@ ${entry}
|
|
|
3822
3842
|
const at = m.index + m[0].length;
|
|
3823
3843
|
const entry2 = m[1].includes("\n") ? `${JSON.stringify(glob)},
|
|
3824
3844
|
${indent}` : `${JSON.stringify(glob)}, `;
|
|
3825
|
-
|
|
3845
|
+
fs12.writeFileSync(file, src.slice(0, at) + entry2 + src.slice(at));
|
|
3826
3846
|
report.done.push(`Added ${config.canvasDir} to Tailwind's content in ${name}`);
|
|
3827
3847
|
} else report.todo.push(`Add "${glob}" to the content list in ${name} so Tailwind styles your canvases`);
|
|
3828
3848
|
break;
|
|
3829
3849
|
}
|
|
3830
|
-
if (!
|
|
3850
|
+
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
3851
|
return report;
|
|
3832
3852
|
}
|
|
3833
3853
|
function addImport(src, line) {
|
|
@@ -3874,12 +3894,35 @@ ${s.toString()}`;
|
|
|
3874
3894
|
}
|
|
3875
3895
|
return null;
|
|
3876
3896
|
}
|
|
3897
|
+
function addVitePlugin(src) {
|
|
3898
|
+
let ast;
|
|
3899
|
+
try {
|
|
3900
|
+
ast = parseModule(src);
|
|
3901
|
+
} catch {
|
|
3902
|
+
return null;
|
|
3903
|
+
}
|
|
3904
|
+
let plugins = null;
|
|
3905
|
+
walk(ast, (node) => {
|
|
3906
|
+
if (plugins) return false;
|
|
3907
|
+
if (node.type !== "ObjectProperty" || node.value.type !== "ArrayExpression") return;
|
|
3908
|
+
const key = node.key.type === "Identifier" ? node.key.name : node.key.type === "StringLiteral" ? node.key.value : null;
|
|
3909
|
+
if (key !== "plugins") return;
|
|
3910
|
+
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 })) };
|
|
3911
|
+
return false;
|
|
3912
|
+
});
|
|
3913
|
+
if (!plugins) return null;
|
|
3914
|
+
const { start, end, elements } = plugins;
|
|
3915
|
+
const s = new MagicString2(src);
|
|
3916
|
+
if (!elements.length) s.overwrite(start, end, "[truecanvas()]");
|
|
3917
|
+
else {
|
|
3918
|
+
const last = elements[elements.length - 1];
|
|
3919
|
+
const before = src.slice(start + 1, elements[0].start);
|
|
3920
|
+
s.appendLeft(last.end, before.includes("\n") ? `,${before.replace(/^[^\n]*/, "")}truecanvas()` : ", truecanvas()");
|
|
3921
|
+
}
|
|
3922
|
+
return addImport(s.toString(), `import { truecanvas } from "truecanvas/vite";`);
|
|
3923
|
+
}
|
|
3877
3924
|
|
|
3878
3925
|
export {
|
|
3879
|
-
loadConfig,
|
|
3880
|
-
parseModule,
|
|
3881
|
-
idOf,
|
|
3882
|
-
walk,
|
|
3883
3926
|
parseCanvas,
|
|
3884
3927
|
allNodes,
|
|
3885
3928
|
findNode,
|
|
@@ -3895,6 +3938,8 @@ export {
|
|
|
3895
3938
|
DEVICES,
|
|
3896
3939
|
findDevice,
|
|
3897
3940
|
assertCanvasName,
|
|
3941
|
+
syncRoute,
|
|
3942
|
+
viteStylesheets,
|
|
3898
3943
|
syncComponentRegistry,
|
|
3899
3944
|
Comments,
|
|
3900
3945
|
Git,
|
|
@@ -3903,6 +3948,7 @@ export {
|
|
|
3903
3948
|
detectPm,
|
|
3904
3949
|
addArgs,
|
|
3905
3950
|
toolEnv,
|
|
3951
|
+
shellSafe,
|
|
3906
3952
|
run2 as run,
|
|
3907
3953
|
shadcnStatus,
|
|
3908
3954
|
shadcnRegistry,
|
|
@@ -3915,7 +3961,8 @@ export {
|
|
|
3915
3961
|
writeCompare,
|
|
3916
3962
|
clearCompare,
|
|
3917
3963
|
frameChanges,
|
|
3918
|
-
|
|
3964
|
+
configStatus,
|
|
3919
3965
|
initProject,
|
|
3920
|
-
wrapConfigExport
|
|
3966
|
+
wrapConfigExport,
|
|
3967
|
+
addVitePlugin
|
|
3921
3968
|
};
|