truecanvas 0.4.1 → 0.6.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 +13 -4
- package/dist/chunk-D565QDII.js +814 -0
- package/dist/chunk-LKFOLM3L.js +54 -0
- package/dist/chunk-NAK4DQWO.js +1005 -0
- package/dist/{chunk-M7B3W7EP.js → chunk-OPKROZI2.js} +333 -7
- package/dist/chunk-OZOV4Y6C.js +45 -0
- package/dist/{chunk-A4QIAVTG.js → chunk-QJ5M3G4E.js} +88 -852
- package/dist/{chunk-TSHD2S36.js → chunk-VWKLYIY6.js} +1 -1
- package/dist/cli.js +798 -962
- package/dist/core/index.js +14 -11
- package/dist/editor/assets/{Menu-BJ3OmqzH.css → Menu-CACFFH7S.css} +1 -1
- package/dist/editor/assets/hub-B_TP_af8.js +2 -0
- package/dist/editor/assets/main-Dj60SnCH.js +26 -0
- package/dist/editor/hub.html +3 -3
- package/dist/editor/index.html +3 -3
- package/dist/hub.d.ts +88 -0
- package/dist/hub.js +15 -0
- package/dist/share/index.d.ts +77 -2
- package/dist/share/index.js +13 -1
- package/dist/vite/index.js +3 -2
- package/package.json +5 -1
- package/dist/chunk-PBKX3SBC.js +0 -97
- package/dist/editor/assets/hub-B5hqZCM2.js +0 -2
- package/dist/editor/assets/main-woTyWmya.js +0 -26
- /package/dist/editor/assets/{Menu-C0Ti-6T2.js → Menu-CZr87VA3.js} +0 -0
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
CanvasEditor,
|
|
3
|
+
EditError,
|
|
4
|
+
attrCode,
|
|
5
|
+
buildNode,
|
|
6
|
+
componentNamesIn,
|
|
7
|
+
detectPm,
|
|
8
|
+
dlx,
|
|
9
|
+
findFrame,
|
|
10
|
+
findNode,
|
|
11
|
+
frameCode,
|
|
12
|
+
indentBlock,
|
|
13
|
+
parseCanvas,
|
|
14
|
+
parseJsx,
|
|
15
|
+
readImports,
|
|
16
|
+
reindex,
|
|
17
|
+
run,
|
|
18
|
+
textCode,
|
|
19
|
+
topLevelNames
|
|
20
|
+
} from "./chunk-D565QDII.js";
|
|
21
|
+
import {
|
|
22
|
+
detectFramework
|
|
23
|
+
} from "./chunk-OZOV4Y6C.js";
|
|
24
|
+
import {
|
|
5
25
|
jsxName,
|
|
6
26
|
parseModule,
|
|
7
27
|
rewriteImportSource,
|
|
8
28
|
walk
|
|
9
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LKFOLM3L.js";
|
|
10
30
|
import {
|
|
11
31
|
Catalog,
|
|
12
32
|
importSpecifier,
|
|
@@ -20,714 +40,6 @@ import {
|
|
|
20
40
|
searchIcons
|
|
21
41
|
} from "./chunk-C4SATRZT.js";
|
|
22
42
|
|
|
23
|
-
// src/core/parse.ts
|
|
24
|
-
function parseCanvas(name, file, source) {
|
|
25
|
-
let ast;
|
|
26
|
-
try {
|
|
27
|
-
ast = parseModule(source);
|
|
28
|
-
} catch (err) {
|
|
29
|
-
return { name, file, frames: [], imports: [], canvasNode: null, prologueEnd: 0, declared: [], error: err.message };
|
|
30
|
-
}
|
|
31
|
-
const imports = readImports(ast);
|
|
32
|
-
const directives = ast.program.directives;
|
|
33
|
-
const prologueEnd = directives.length ? directives[directives.length - 1].end : 0;
|
|
34
|
-
const declared = topLevelNames(ast);
|
|
35
|
-
let canvasEl = null;
|
|
36
|
-
walk(ast, (node) => {
|
|
37
|
-
if (canvasEl) return false;
|
|
38
|
-
if (node.type === "JSXElement" && jsxName(node.openingElement.name) === "Canvas") {
|
|
39
|
-
canvasEl = node;
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
if (!canvasEl) {
|
|
44
|
-
return { name, file, frames: [], imports, canvasNode: null, prologueEnd, declared, error: "No <Canvas> element found in the default export." };
|
|
45
|
-
}
|
|
46
|
-
const canvasNode = buildNode(canvasEl, source, []);
|
|
47
|
-
const frames = [];
|
|
48
|
-
let frameIndex = 0;
|
|
49
|
-
for (const child of canvasNode.children) {
|
|
50
|
-
if (child.kind !== "component" || child.name !== "Frame") continue;
|
|
51
|
-
const path14 = [frameIndex++];
|
|
52
|
-
reindex(child, path14);
|
|
53
|
-
frames.push({
|
|
54
|
-
...child,
|
|
55
|
-
frameName: str(child.props.name) ?? `Frame ${frameIndex}`,
|
|
56
|
-
x: num(child.props.x) ?? 0,
|
|
57
|
-
y: num(child.props.y) ?? 0,
|
|
58
|
-
width: num(child.props.width) ?? 1024,
|
|
59
|
-
height: num(child.props.height) ?? null,
|
|
60
|
-
theme: str(child.props.theme) ?? null,
|
|
61
|
-
device: str(child.props.device) ?? null,
|
|
62
|
-
page: str(child.props.page) ?? null,
|
|
63
|
-
from: str(child.props.from) ?? null,
|
|
64
|
-
component: str(child.props.component) ?? null,
|
|
65
|
-
link: null
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return { name, file, frames, imports, canvasNode, prologueEnd, declared };
|
|
69
|
-
}
|
|
70
|
-
function readImports(ast) {
|
|
71
|
-
const imports = [];
|
|
72
|
-
for (const stmt of ast.program.body) {
|
|
73
|
-
if (stmt.type !== "ImportDeclaration") continue;
|
|
74
|
-
const typeOnly = stmt.importKind === "type";
|
|
75
|
-
const named = stmt.specifiers.filter((s) => s.type === "ImportSpecifier");
|
|
76
|
-
const def = stmt.specifiers.find((s) => s.type === "ImportDefaultSpecifier");
|
|
77
|
-
const ns = stmt.specifiers.find((s) => s.type === "ImportNamespaceSpecifier");
|
|
78
|
-
imports.push({
|
|
79
|
-
source: stmt.source.value,
|
|
80
|
-
typeOnly,
|
|
81
|
-
// value bindings only: `import type` and `{ type X }` don't exist at runtime
|
|
82
|
-
names: typeOnly ? [] : [...named.filter((s) => s.importKind !== "type").map((s) => s.local.name), ...ns ? [ns.local.name] : []],
|
|
83
|
-
bindings: typeOnly ? [] : named.filter((s) => s.importKind !== "type").map((s) => ({ imported: s.imported.type === "Identifier" ? s.imported.name : s.imported.value, local: s.local.name })),
|
|
84
|
-
defaultName: def ? def.local.name : null,
|
|
85
|
-
start: stmt.start,
|
|
86
|
-
end: stmt.end,
|
|
87
|
-
lastSpecifierEnd: named.length && !typeOnly ? named[named.length - 1].end : null
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return imports;
|
|
91
|
-
}
|
|
92
|
-
function topLevelNames(ast) {
|
|
93
|
-
const out = [];
|
|
94
|
-
for (let stmt of ast.program.body) {
|
|
95
|
-
if ((stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") && stmt.declaration) stmt = stmt.declaration;
|
|
96
|
-
if ((stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration") && stmt.id) out.push(stmt.id.name);
|
|
97
|
-
if (stmt.type === "VariableDeclaration") {
|
|
98
|
-
for (const d of stmt.declarations) if (d.id.type === "Identifier") out.push(d.id.name);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
function reindex(node, path14) {
|
|
104
|
-
node.path = path14;
|
|
105
|
-
node.children.forEach((c, i) => reindex(c, [...path14, i]));
|
|
106
|
-
}
|
|
107
|
-
function str(v) {
|
|
108
|
-
return v?.kind === "string" ? v.value : void 0;
|
|
109
|
-
}
|
|
110
|
-
function num(v) {
|
|
111
|
-
return v?.kind === "number" ? v.value : void 0;
|
|
112
|
-
}
|
|
113
|
-
function buildNode(el, source, path14) {
|
|
114
|
-
const children = [];
|
|
115
|
-
for (const child of el.children) {
|
|
116
|
-
const built = buildChild(child, source, [...path14, children.length]);
|
|
117
|
-
if (built) children.push(built);
|
|
118
|
-
}
|
|
119
|
-
if (el.type === "JSXFragment") {
|
|
120
|
-
return {
|
|
121
|
-
id: idOf(el),
|
|
122
|
-
kind: "fragment",
|
|
123
|
-
name: "Fragment",
|
|
124
|
-
props: {},
|
|
125
|
-
spreads: 0,
|
|
126
|
-
children,
|
|
127
|
-
start: el.start,
|
|
128
|
-
end: el.end,
|
|
129
|
-
line: el.loc.start.line,
|
|
130
|
-
col: el.loc.start.column,
|
|
131
|
-
path: path14,
|
|
132
|
-
attrs: [],
|
|
133
|
-
nameEnd: el.openingFragment.end - 1,
|
|
134
|
-
openEnd: el.openingFragment.end,
|
|
135
|
-
selfClosing: false,
|
|
136
|
-
closeStart: el.closingFragment.start
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
const open = el.openingElement;
|
|
140
|
-
const name = jsxName(open.name);
|
|
141
|
-
const props = {};
|
|
142
|
-
const attrs = [];
|
|
143
|
-
let spreads = 0;
|
|
144
|
-
for (const attr of open.attributes) {
|
|
145
|
-
if (attr.type === "JSXSpreadAttribute") {
|
|
146
|
-
spreads++;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const attrName = attr.name.type === "JSXIdentifier" ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
150
|
-
if (attrName === "data-tc") continue;
|
|
151
|
-
const literal = classLiteral(attr.value);
|
|
152
|
-
attrs.push({ name: attrName, start: attr.start, end: attr.end, ...literal ? { literal: { start: literal.start, end: literal.end } } : {} });
|
|
153
|
-
props[attrName] = literal ? { kind: "string", value: literal.type === "StringLiteral" ? literal.value : literal.quasis.map((q) => q.value.cooked ?? "").join("") } : readValue(attr.value, source);
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
id: idOf(open),
|
|
157
|
-
kind: isComponentName(name) ? "component" : "element",
|
|
158
|
-
name,
|
|
159
|
-
props,
|
|
160
|
-
spreads,
|
|
161
|
-
children,
|
|
162
|
-
start: el.start,
|
|
163
|
-
end: el.end,
|
|
164
|
-
line: open.loc.start.line,
|
|
165
|
-
col: open.loc.start.column,
|
|
166
|
-
path: path14,
|
|
167
|
-
attrs,
|
|
168
|
-
// after type arguments: `<Select<Opt> …>`
|
|
169
|
-
nameEnd: (open.typeParameters ?? open.name).end,
|
|
170
|
-
openEnd: open.end,
|
|
171
|
-
selfClosing: open.selfClosing,
|
|
172
|
-
closeStart: el.closingElement ? el.closingElement.start : open.end
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
function buildChild(child, source, path14) {
|
|
176
|
-
if (child.type === "JSXElement" || child.type === "JSXFragment") return buildNode(child, source, path14);
|
|
177
|
-
if (child.type === "JSXText") {
|
|
178
|
-
const text = child.value.replace(/\s+/g, " ").trim();
|
|
179
|
-
if (!text) return null;
|
|
180
|
-
return leaf("text", text, child, path14, { text });
|
|
181
|
-
}
|
|
182
|
-
if (child.type === "JSXExpressionContainer") {
|
|
183
|
-
const expr = child.expression;
|
|
184
|
-
if (expr.type === "JSXEmptyExpression") return null;
|
|
185
|
-
if (expr.type === "StringLiteral") return leaf("text", expr.value, child, path14, { text: expr.value });
|
|
186
|
-
if (expr.type === "TemplateLiteral" && expr.expressions.length === 0) {
|
|
187
|
-
const text = expr.quasis.map((q) => q.value.cooked ?? "").join("");
|
|
188
|
-
return leaf("text", text, child, path14, { text });
|
|
189
|
-
}
|
|
190
|
-
return leaf("expression", source.slice(expr.start, expr.end), child, path14, {});
|
|
191
|
-
}
|
|
192
|
-
if (child.type === "JSXSpreadChild") return leaf("expression", source.slice(child.start, child.end), child, path14, {});
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
function leaf(kind, name, node, path14, extra) {
|
|
196
|
-
return {
|
|
197
|
-
id: `${kind[0]}${idOf(node)}`,
|
|
198
|
-
kind,
|
|
199
|
-
name,
|
|
200
|
-
props: {},
|
|
201
|
-
spreads: 0,
|
|
202
|
-
children: [],
|
|
203
|
-
...extra,
|
|
204
|
-
start: node.start,
|
|
205
|
-
end: node.end,
|
|
206
|
-
line: node.loc.start.line,
|
|
207
|
-
col: node.loc.start.column,
|
|
208
|
-
path: path14,
|
|
209
|
-
attrs: [],
|
|
210
|
-
nameEnd: node.end,
|
|
211
|
-
openEnd: node.end,
|
|
212
|
-
selfClosing: true,
|
|
213
|
-
closeStart: node.end
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
var CLASS_HELPERS = /* @__PURE__ */ new Set(["cn", "clsx", "cx", "twMerge", "classNames", "classnames"]);
|
|
217
|
-
function classLiteral(value) {
|
|
218
|
-
if (value?.type !== "JSXExpressionContainer") return null;
|
|
219
|
-
const e = value.expression;
|
|
220
|
-
if (e.type !== "CallExpression" || e.callee.type !== "Identifier" || !CLASS_HELPERS.has(e.callee.name)) return null;
|
|
221
|
-
const first = e.arguments[0];
|
|
222
|
-
if (first?.type === "StringLiteral") return first;
|
|
223
|
-
if (first?.type === "TemplateLiteral" && first.expressions.length === 0) return first;
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
function readValue(value, source) {
|
|
227
|
-
if (value == null) return { kind: "boolean", value: true };
|
|
228
|
-
if (value.type === "StringLiteral") return { kind: "string", value: value.value };
|
|
229
|
-
if (value.type === "JSXExpressionContainer") {
|
|
230
|
-
const e = value.expression;
|
|
231
|
-
if (e.type === "StringLiteral") return { kind: "string", value: e.value };
|
|
232
|
-
if (e.type === "NumericLiteral") return { kind: "number", value: e.value };
|
|
233
|
-
if (e.type === "BooleanLiteral") return { kind: "boolean", value: e.value };
|
|
234
|
-
if (e.type === "UnaryExpression" && e.operator === "-" && e.argument.type === "NumericLiteral") {
|
|
235
|
-
return { kind: "number", value: -e.argument.value };
|
|
236
|
-
}
|
|
237
|
-
if (e.type === "TemplateLiteral" && e.expressions.length === 0) {
|
|
238
|
-
return { kind: "string", value: e.quasis.map((q) => q.value.cooked ?? "").join("") };
|
|
239
|
-
}
|
|
240
|
-
if (e.type === "ArrayExpression") {
|
|
241
|
-
const items = [];
|
|
242
|
-
for (const el of e.elements) {
|
|
243
|
-
if (el?.type === "StringLiteral" || el?.type === "NumericLiteral" || el?.type === "BooleanLiteral") items.push(el.value);
|
|
244
|
-
else if (el?.type === "UnaryExpression" && el.operator === "-" && el.argument.type === "NumericLiteral") items.push(-el.argument.value);
|
|
245
|
-
else return { kind: "expression", code: source.slice(e.start, e.end) };
|
|
246
|
-
}
|
|
247
|
-
return { kind: "array", value: items };
|
|
248
|
-
}
|
|
249
|
-
if (e.type === "JSXEmptyExpression") return { kind: "expression", code: "" };
|
|
250
|
-
return { kind: "expression", code: source.slice(e.start, e.end) };
|
|
251
|
-
}
|
|
252
|
-
return { kind: "expression", code: source.slice(value.start, value.end) };
|
|
253
|
-
}
|
|
254
|
-
function* allNodes(doc) {
|
|
255
|
-
for (const frame of doc.frames) {
|
|
256
|
-
yield { node: frame, parent: null, frame };
|
|
257
|
-
const stack = frame.children.map((c) => ({ node: c, parent: frame })).reverse();
|
|
258
|
-
while (stack.length) {
|
|
259
|
-
const item = stack.pop();
|
|
260
|
-
yield { ...item, frame };
|
|
261
|
-
for (let i = item.node.children.length - 1; i >= 0; i--) stack.push({ node: item.node.children[i], parent: item.node });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
function findNode(doc, id) {
|
|
266
|
-
for (const entry of allNodes(doc)) if (entry.node.id === id) return entry;
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
function findFrame(doc, ref) {
|
|
270
|
-
return doc.frames.find((f) => f.id === ref || f.frameName === ref) ?? null;
|
|
271
|
-
}
|
|
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];
|
|
276
|
-
return node ?? null;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/core/edit.ts
|
|
280
|
-
import MagicString from "magic-string";
|
|
281
|
-
var EditError = class extends Error {
|
|
282
|
-
};
|
|
283
|
-
function attrCode(name, value) {
|
|
284
|
-
if (value === true) return name;
|
|
285
|
-
if (typeof value === "string") {
|
|
286
|
-
return /["\n\\{}&]/.test(value) ? `${name}={${JSON.stringify(value)}}` : `${name}="${value}"`;
|
|
287
|
-
}
|
|
288
|
-
if (typeof value === "number" && !Number.isFinite(value)) throw new EditError(`Invalid number for ${name}.`);
|
|
289
|
-
if (Array.isArray(value)) return `${name}={[${value.map((v) => JSON.stringify(v)).join(", ")}]}`;
|
|
290
|
-
return `${name}={${JSON.stringify(value)}}`;
|
|
291
|
-
}
|
|
292
|
-
var PROP_NAME = /^[A-Za-z_$][\w$-]*(:[\w$-]+)?$/;
|
|
293
|
-
function textCode(text) {
|
|
294
|
-
if (text === "") return "";
|
|
295
|
-
if (/[{}<>&\n]/.test(text) || /^\s|\s$/.test(text)) return `{${JSON.stringify(text)}}`;
|
|
296
|
-
return text;
|
|
297
|
-
}
|
|
298
|
-
var CanvasEditor = class {
|
|
299
|
-
constructor(source, doc) {
|
|
300
|
-
this.source = source;
|
|
301
|
-
this.doc = doc;
|
|
302
|
-
this.s = new MagicString(source);
|
|
303
|
-
}
|
|
304
|
-
source;
|
|
305
|
-
doc;
|
|
306
|
-
s;
|
|
307
|
-
/** Path of the node that should be selected after the edit (best effort, before formatting). */
|
|
308
|
-
selectPaths = [];
|
|
309
|
-
usedFrameNames = null;
|
|
310
|
-
placed = null;
|
|
311
|
-
node(id) {
|
|
312
|
-
const found = findNode(this.doc, id);
|
|
313
|
-
if (!found) throw new EditError(`Node ${id} not found. Re-read the canvas, ids change after every edit.`);
|
|
314
|
-
return found;
|
|
315
|
-
}
|
|
316
|
-
frame(ref) {
|
|
317
|
-
const f = findFrame(this.doc, ref);
|
|
318
|
-
if (!f) throw new EditError(`Frame "${ref}" not found. Available: ${this.doc.frames.map((x) => `"${x.frameName}"`).join(", ")}`);
|
|
319
|
-
return f;
|
|
320
|
-
}
|
|
321
|
-
/** A frame id/name or a node id: whatever an agent passes as "parent". */
|
|
322
|
-
container(ref) {
|
|
323
|
-
const f = findFrame(this.doc, ref);
|
|
324
|
-
if (f) return f;
|
|
325
|
-
const n = this.node(ref).node;
|
|
326
|
-
if (n.kind === "text" || n.kind === "expression") throw new EditError(`${ref} is a ${n.kind} node and cannot have children.`);
|
|
327
|
-
return n;
|
|
328
|
-
}
|
|
329
|
-
setProp(node, name, value, opts = {}) {
|
|
330
|
-
if (node.kind !== "component" && node.kind !== "element") throw new EditError(`Cannot set props on a ${node.kind} node.`);
|
|
331
|
-
if (!PROP_NAME.test(name) || name === "data-tc") throw new EditError(`Invalid prop name "${name}".`);
|
|
332
|
-
const existing = node.attrs.find((a) => a.name === name);
|
|
333
|
-
if (existing?.literal && opts.raw === void 0 && (typeof value === "string" || value === null)) {
|
|
334
|
-
this.s.overwrite(existing.literal.start, existing.literal.end, JSON.stringify(value ?? ""));
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const code = opts.raw !== void 0 ? `${name}={${opts.raw}}` : value === null || value === void 0 ? null : attrCode(name, value);
|
|
338
|
-
if (existing) {
|
|
339
|
-
if (code === null) {
|
|
340
|
-
let start = existing.start;
|
|
341
|
-
while (start > 0 && /\s/.test(this.source[start - 1])) start--;
|
|
342
|
-
this.s.remove(start, existing.end);
|
|
343
|
-
} else {
|
|
344
|
-
this.s.overwrite(existing.start, existing.end, code, { contentOnly: true });
|
|
345
|
-
}
|
|
346
|
-
} else if (code !== null) {
|
|
347
|
-
const at = node.attrs.length ? node.attrs[node.attrs.length - 1].end : node.nameEnd;
|
|
348
|
-
this.s.appendRight(at, ` ${code}`);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
setText(node, text) {
|
|
352
|
-
if (node.kind === "text") {
|
|
353
|
-
this.s.overwrite(this.contentStart(node), this.contentEnd(node), textCode(text) || '{""}');
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (node.kind !== "component" && node.kind !== "element") throw new EditError(`Cannot set text on a ${node.kind} node.`);
|
|
357
|
-
if (node.children.some((c) => c.kind !== "text")) {
|
|
358
|
-
throw new EditError(`Node ${node.id} has element children; edit a text child instead.`);
|
|
359
|
-
}
|
|
360
|
-
const code = textCode(text);
|
|
361
|
-
if (node.selfClosing) {
|
|
362
|
-
let end = node.openEnd - 2;
|
|
363
|
-
while (/\s/.test(this.source[end - 1])) end--;
|
|
364
|
-
this.s.overwrite(end, node.openEnd, `>${code}</${node.name}>`);
|
|
365
|
-
} else if (node.openEnd === node.closeStart) {
|
|
366
|
-
this.s.appendLeft(node.openEnd, code);
|
|
367
|
-
} else {
|
|
368
|
-
const comments = this.source.slice(node.openEnd, node.closeStart).match(/\{\s*\/\*[\s\S]*?\*\/\s*\}/g) ?? [];
|
|
369
|
-
this.s.overwrite(node.openEnd, node.closeStart, comments.join("") + code);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
// ---------- indentation helpers: edits match the file's existing style ----------
|
|
373
|
-
unitCache = null;
|
|
374
|
-
/** The file's indent unit: tabs, or the smallest non-zero space indent. */
|
|
375
|
-
unit() {
|
|
376
|
-
if (this.unitCache) return this.unitCache;
|
|
377
|
-
if (/^\t/m.test(this.source)) return this.unitCache = " ";
|
|
378
|
-
let min = Infinity;
|
|
379
|
-
for (const m of this.source.matchAll(/^( +)(?!\*)\S/gm)) min = Math.min(min, m[1].length);
|
|
380
|
-
return this.unitCache = " ".repeat(Number.isFinite(min) && min <= 8 ? min : 2);
|
|
381
|
-
}
|
|
382
|
-
lineIndent(pos) {
|
|
383
|
-
const lineStart = this.source.lastIndexOf("\n", pos - 1) + 1;
|
|
384
|
-
return /^[ \t]*/.exec(this.source.slice(lineStart))[0];
|
|
385
|
-
}
|
|
386
|
-
/** Where a node's real content starts/ends (JSX text includes surrounding whitespace). */
|
|
387
|
-
contentStart(n) {
|
|
388
|
-
if (n.kind !== "text") return n.start;
|
|
389
|
-
const raw = this.source.slice(n.start, n.end);
|
|
390
|
-
return n.start + (raw.length - raw.trimStart().length);
|
|
391
|
-
}
|
|
392
|
-
contentEnd(n) {
|
|
393
|
-
if (n.kind !== "text") return n.end;
|
|
394
|
-
return n.start + this.source.slice(n.start, n.end).trimEnd().length;
|
|
395
|
-
}
|
|
396
|
-
childIndent(parent) {
|
|
397
|
-
const kid = parent.children.find((c) => c.kind !== "text") ?? parent.children[0];
|
|
398
|
-
if (kid) {
|
|
399
|
-
const pos = this.contentStart(kid);
|
|
400
|
-
const lineStart = this.source.lastIndexOf("\n", pos - 1) + 1;
|
|
401
|
-
if (/^[ \t]*$/.test(this.source.slice(lineStart, pos))) return this.lineIndent(pos);
|
|
402
|
-
}
|
|
403
|
-
return this.lineIndent(parent.start) + this.unit();
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Re-indents lines 2..n of a snippet so it sits at `indent`. Lines that start
|
|
407
|
-
* inside a multi-line string or template literal are content, not code: they
|
|
408
|
-
* are kept exactly as written.
|
|
409
|
-
*/
|
|
410
|
-
shift(code, indent) {
|
|
411
|
-
const trimmed = code.trim();
|
|
412
|
-
const lines = trimmed.split("\n");
|
|
413
|
-
const frozen = literalLines(trimmed);
|
|
414
|
-
const free = (i) => !frozen.has(i);
|
|
415
|
-
const rest = lines.slice(1).filter((l, i) => l.trim() && free(i + 1));
|
|
416
|
-
const base = rest.length ? Math.min(...rest.map((l) => /^[ \t]*/.exec(l)[0].length)) : 0;
|
|
417
|
-
const relative = lines.slice(1).map((l, i) => free(i + 1) ? l.trim() ? l.slice(base) : "" : l);
|
|
418
|
-
const steps = relative.filter((_, i) => free(i + 1)).map((l) => /^ */.exec(l)[0].length).filter((n) => n > 0);
|
|
419
|
-
const from = steps.length ? Math.min(...steps) : 0;
|
|
420
|
-
const unit = this.unit();
|
|
421
|
-
const convert = (l) => {
|
|
422
|
-
if (!from || unit === " ".repeat(from)) return l;
|
|
423
|
-
const n = /^ */.exec(l)[0].length;
|
|
424
|
-
return unit.repeat(Math.floor(n / from)) + " ".repeat(n % from) + l.slice(n);
|
|
425
|
-
};
|
|
426
|
-
return [lines[0], ...relative.map((l, i) => !free(i + 1) ? l : l ? indent + convert(l) : "")].join("\n");
|
|
427
|
-
}
|
|
428
|
-
openToClose(node, inner) {
|
|
429
|
-
let end = node.openEnd - 2;
|
|
430
|
-
while (/\s/.test(this.source[end - 1])) end--;
|
|
431
|
-
this.s.overwrite(end, node.openEnd, `>${inner}</${node.name}>`);
|
|
432
|
-
}
|
|
433
|
-
insertAt(parent, kids, i, code) {
|
|
434
|
-
const ci = this.childIndent(parent);
|
|
435
|
-
const pi = this.lineIndent(parent.start);
|
|
436
|
-
const body = this.shift(code, ci);
|
|
437
|
-
if (parent.selfClosing) {
|
|
438
|
-
this.openToClose(parent, `
|
|
439
|
-
${ci}${body}
|
|
440
|
-
${pi}`);
|
|
441
|
-
} else if (i < kids.length) {
|
|
442
|
-
this.s.appendRight(this.contentStart(kids[i]), `${body}
|
|
443
|
-
${ci}`);
|
|
444
|
-
} else if (kids.length) {
|
|
445
|
-
this.s.appendLeft(this.contentEnd(kids[kids.length - 1]), `
|
|
446
|
-
${ci}${body}`);
|
|
447
|
-
} else if (parent.openEnd === parent.closeStart) {
|
|
448
|
-
this.s.appendLeft(parent.openEnd, `
|
|
449
|
-
${ci}${body}
|
|
450
|
-
${pi}`);
|
|
451
|
-
} else if (/^\s*$/.test(this.source.slice(parent.openEnd, parent.closeStart))) {
|
|
452
|
-
this.s.overwrite(parent.openEnd, parent.closeStart, `
|
|
453
|
-
${ci}${body}
|
|
454
|
-
${pi}`);
|
|
455
|
-
} else {
|
|
456
|
-
this.s.appendRight(parent.closeStart, `${body}`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
insertChild(parent, index, jsx) {
|
|
460
|
-
const kids = parent.children;
|
|
461
|
-
const i = index === void 0 || index < 0 || index > kids.length ? kids.length : index;
|
|
462
|
-
this.insertAt(parent, kids, i, jsx);
|
|
463
|
-
this.selectPaths.push([...parent.path, i]);
|
|
464
|
-
}
|
|
465
|
-
/** Removes a node together with its line when it sits alone on it. */
|
|
466
|
-
remove(node) {
|
|
467
|
-
const start = this.contentStart(node);
|
|
468
|
-
const end = this.contentEnd(node);
|
|
469
|
-
let a = start;
|
|
470
|
-
while (a > 0 && (this.source[a - 1] === " " || this.source[a - 1] === " ")) a--;
|
|
471
|
-
const restOfLine = /^[ \t]*(\n|$)/.test(this.source.slice(end));
|
|
472
|
-
if (a > 0 && this.source[a - 1] === "\n" && restOfLine) this.s.remove(a - 1, end);
|
|
473
|
-
else this.s.remove(node.start, node.end);
|
|
474
|
-
}
|
|
475
|
-
duplicate(node) {
|
|
476
|
-
let code = this.source.slice(node.start, node.end);
|
|
477
|
-
if ("frameName" in node) {
|
|
478
|
-
const f = node;
|
|
479
|
-
this.usedFrameNames ??= new Set(this.doc.frames.map((x) => x.frameName));
|
|
480
|
-
this.placed ??= this.doc.frames.map((o) => ({ x: o.x, y: o.y, width: o.width, height: o.height ?? 600 }));
|
|
481
|
-
let name = `${f.frameName} copy`;
|
|
482
|
-
for (let n = 2; this.usedFrameNames.has(name); n++) name = `${f.frameName} copy ${n}`;
|
|
483
|
-
this.usedFrameNames.add(name);
|
|
484
|
-
const h = f.height ?? 600;
|
|
485
|
-
const row = this.placed.filter((o) => o.y < f.y + Math.max(h, 1) && o.y + Math.max(o.height, 1) > f.y);
|
|
486
|
-
const right = row.length ? Math.max(...row.map((o) => o.x + o.width)) : f.x + f.width;
|
|
487
|
-
this.placed.push({ x: right + 80, y: f.y, width: f.width, height: h });
|
|
488
|
-
code = rewriteFrameProps(code, { name, x: right + 80 });
|
|
489
|
-
}
|
|
490
|
-
const marked = withMarker(code.trim(), node);
|
|
491
|
-
this.s.appendLeft(this.contentEnd(node), `
|
|
492
|
-
${this.lineIndent(node.start)}${marked}`);
|
|
493
|
-
if (marked === code.trim()) {
|
|
494
|
-
const path14 = [...node.path];
|
|
495
|
-
path14[path14.length - 1] += 1;
|
|
496
|
-
this.selectPaths.push(path14);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
move(node, target, index) {
|
|
500
|
-
if (isInside(target, node)) throw new EditError("Cannot move a node into itself.");
|
|
501
|
-
const code = withMarker(this.source.slice(this.contentStart(node), this.contentEnd(node)), node);
|
|
502
|
-
const kids = target.children.filter((c) => c.id !== node.id);
|
|
503
|
-
const i = index === void 0 || index < 0 || index > kids.length ? kids.length : index;
|
|
504
|
-
this.remove(node);
|
|
505
|
-
this.insertAt(target, kids, i, code);
|
|
506
|
-
}
|
|
507
|
-
wrap(nodes, open, close) {
|
|
508
|
-
const sorted = [...nodes].sort((a, b) => a.start - b.start);
|
|
509
|
-
const first = sorted[0];
|
|
510
|
-
const last = sorted[sorted.length - 1];
|
|
511
|
-
if (sorted.some((n) => "frameName" in n)) throw new EditError("Frames can't be wrapped; wrap the layers inside a frame.");
|
|
512
|
-
const parentPath = first.path.slice(0, -1).join(",");
|
|
513
|
-
if (sorted.some((n) => n.path.slice(0, -1).join(",") !== parentPath)) throw new EditError("Nodes to wrap must share the same parent.");
|
|
514
|
-
const indices = sorted.map((n) => n.path[n.path.length - 1]);
|
|
515
|
-
const parent = findByPath(this.doc, first.path.slice(0, -1));
|
|
516
|
-
const between = parent?.children.slice(indices[0], indices[indices.length - 1] + 1) ?? [];
|
|
517
|
-
if (between.some((c) => c.kind !== "text" && !sorted.includes(c))) throw new EditError("Nodes to wrap must be adjacent siblings.");
|
|
518
|
-
const ind = this.lineIndent(this.contentStart(first));
|
|
519
|
-
const inner = ind + this.unit();
|
|
520
|
-
const slice = this.source.slice(this.contentStart(first), this.contentEnd(last));
|
|
521
|
-
this.s.overwrite(this.contentStart(first), this.contentEnd(last), `${open}
|
|
522
|
-
${inner}${this.shift(slice, inner)}
|
|
523
|
-
${ind}${close}`);
|
|
524
|
-
this.selectPaths.push(first.path);
|
|
525
|
-
}
|
|
526
|
-
/** Like wrap, but keeps content that sits on one line on one line: `<p><X>Hi</X></p>`. */
|
|
527
|
-
wrapInline(nodes, open, close) {
|
|
528
|
-
const sorted = [...nodes].sort((a, b) => a.start - b.start);
|
|
529
|
-
const start = this.contentStart(sorted[0]);
|
|
530
|
-
const end = this.contentEnd(sorted[sorted.length - 1]);
|
|
531
|
-
const slice = this.source.slice(start, end);
|
|
532
|
-
if (slice.includes("\n")) return this.wrap(nodes, open, close);
|
|
533
|
-
this.s.overwrite(start, end, `${open}${slice}${close}`);
|
|
534
|
-
this.selectPaths.push(sorted[0].path);
|
|
535
|
-
}
|
|
536
|
-
/** Replaces a wrapper element with its children (re-indented to its place). */
|
|
537
|
-
unwrap(node) {
|
|
538
|
-
const kids = node.children;
|
|
539
|
-
if (!kids.length) {
|
|
540
|
-
this.remove(node);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
const inner = node.selfClosing ? "" : this.source.slice(node.openEnd, node.closeStart).trim();
|
|
544
|
-
this.s.overwrite(this.contentStart(node), this.contentEnd(node), this.shift(inner, this.lineIndent(this.contentStart(node))));
|
|
545
|
-
this.selectPaths.push(node.path);
|
|
546
|
-
}
|
|
547
|
-
replace(node, jsx) {
|
|
548
|
-
this.s.overwrite(this.contentStart(node), this.contentEnd(node), this.shift(jsx, this.lineIndent(this.contentStart(node))));
|
|
549
|
-
this.selectPaths.push(node.path);
|
|
550
|
-
}
|
|
551
|
-
added = /* @__PURE__ */ new Set();
|
|
552
|
-
ensureImport(name, from, isDefault = false) {
|
|
553
|
-
if (this.doc.imports.some((i) => i.names.includes(name) || i.defaultName === name)) return;
|
|
554
|
-
if (this.added.has(name)) return;
|
|
555
|
-
this.added.add(name);
|
|
556
|
-
if (isDefault) {
|
|
557
|
-
this.addImportLine(`import ${name} from ${JSON.stringify(from)};`);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const same = this.doc.imports.find((i) => i.source === from && !i.typeOnly && i.lastSpecifierEnd !== null);
|
|
561
|
-
if (same) {
|
|
562
|
-
this.s.appendLeft(same.lastSpecifierEnd, `, ${name}`);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
this.addImportLine(`import { ${name} } from ${JSON.stringify(from)};`);
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Imports `name` from `from` and returns the local name to write in JSX. When
|
|
569
|
-
* the name is already taken by something else (another import, a local
|
|
570
|
-
* component), it is imported under `alias` instead: `{ Search as SearchIcon }`.
|
|
571
|
-
*/
|
|
572
|
-
importAs(name, from, alias) {
|
|
573
|
-
const existing = this.doc.imports.find((i) => i.source === from && !i.typeOnly)?.bindings.find((b) => b.imported === name);
|
|
574
|
-
if (existing) return existing.local;
|
|
575
|
-
const key = `${from}\0${name}`;
|
|
576
|
-
const pending = this.importedAs.get(key);
|
|
577
|
-
if (pending) return pending;
|
|
578
|
-
const taken = (n) => this.doc.declared.includes(n) || this.added.has(n) || this.doc.imports.some((i) => i.names.includes(n) || i.defaultName === n);
|
|
579
|
-
let local = name;
|
|
580
|
-
if (taken(local)) {
|
|
581
|
-
local = alias;
|
|
582
|
-
for (let k = 2; taken(local); k++) local = `${alias}${k}`;
|
|
583
|
-
}
|
|
584
|
-
this.added.add(local);
|
|
585
|
-
this.importedAs.set(key, local);
|
|
586
|
-
const specifier = local === name ? name : `${name} as ${local}`;
|
|
587
|
-
const same = this.doc.imports.find((i) => i.source === from && !i.typeOnly && i.lastSpecifierEnd !== null);
|
|
588
|
-
if (same) this.s.appendLeft(same.lastSpecifierEnd, `, ${specifier}`);
|
|
589
|
-
else this.addImportLine(`import { ${specifier} } from ${JSON.stringify(from)};`);
|
|
590
|
-
return local;
|
|
591
|
-
}
|
|
592
|
-
importedAs = /* @__PURE__ */ new Map();
|
|
593
|
-
addImportLine(line) {
|
|
594
|
-
const last = this.doc.imports[this.doc.imports.length - 1];
|
|
595
|
-
if (last) this.s.appendLeft(last.end, `
|
|
596
|
-
${line}`);
|
|
597
|
-
else if (this.doc.prologueEnd) this.s.appendLeft(this.doc.prologueEnd, `
|
|
598
|
-
${line}`);
|
|
599
|
-
else this.prependImport(line);
|
|
600
|
-
}
|
|
601
|
-
/** Adds a whole import statement unless every name it binds is already imported. */
|
|
602
|
-
addImportStatement(statement) {
|
|
603
|
-
let names = [];
|
|
604
|
-
try {
|
|
605
|
-
const ast = parseModule(statement);
|
|
606
|
-
const decl = ast.program.body[0];
|
|
607
|
-
if (decl?.type === "ImportDeclaration") names = decl.specifiers.map((sp) => sp.local.name);
|
|
608
|
-
} catch {
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
const known = /* @__PURE__ */ new Set([...this.doc.imports.flatMap((i) => [...i.names, ...i.defaultName ? [i.defaultName] : []]), ...this.added]);
|
|
612
|
-
if (names.length && names.every((n) => known.has(n))) return;
|
|
613
|
-
for (const n of names) this.added.add(n);
|
|
614
|
-
this.addImportLine(statement);
|
|
615
|
-
}
|
|
616
|
-
importsPrepended = false;
|
|
617
|
-
/** First imports of a file: in order, with a blank line before the code. */
|
|
618
|
-
prependImport(line) {
|
|
619
|
-
if (!this.importsPrepended) {
|
|
620
|
-
this.importsPrepended = true;
|
|
621
|
-
this.s.prependRight(0, "\n");
|
|
622
|
-
}
|
|
623
|
-
this.s.appendLeft(0, `${line}
|
|
624
|
-
`);
|
|
625
|
-
}
|
|
626
|
-
insertFrame(code) {
|
|
627
|
-
const canvas = this.doc.canvasNode;
|
|
628
|
-
if (!canvas) throw new EditError("No <Canvas> element to add a frame to.");
|
|
629
|
-
this.insertAt(canvas, canvas.children, canvas.children.length, code);
|
|
630
|
-
this.selectPaths.push([this.doc.frames.length]);
|
|
631
|
-
}
|
|
632
|
-
result() {
|
|
633
|
-
return this.s.toString();
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
function isInside(target, node) {
|
|
637
|
-
return target.start >= node.start && target.end <= node.end;
|
|
638
|
-
}
|
|
639
|
-
function rewriteFrameProps(code, props) {
|
|
640
|
-
const wrapped = `(${code})`;
|
|
641
|
-
const ast = parseModule(wrapped);
|
|
642
|
-
let open = null;
|
|
643
|
-
walk(ast, (n) => {
|
|
644
|
-
if (open) return false;
|
|
645
|
-
if (n.type === "JSXOpeningElement") open = n;
|
|
646
|
-
});
|
|
647
|
-
if (!open) return code;
|
|
648
|
-
const o = open;
|
|
649
|
-
const s = new MagicString(wrapped);
|
|
650
|
-
for (const [name, value] of Object.entries(props)) {
|
|
651
|
-
const attr = o.attributes.find((a) => a.type === "JSXAttribute" && a.name.name === name);
|
|
652
|
-
if (attr) {
|
|
653
|
-
if (value === null) s.remove(attr.start - 1, attr.end);
|
|
654
|
-
else s.overwrite(attr.start, attr.end, attrCode(name, value));
|
|
655
|
-
} else if (value !== null) s.appendLeft(o.name.end, ` ${attrCode(name, value)}`);
|
|
656
|
-
}
|
|
657
|
-
return s.toString().slice(1, -1);
|
|
658
|
-
}
|
|
659
|
-
function componentNamesIn(jsx) {
|
|
660
|
-
const names = /* @__PURE__ */ new Set();
|
|
661
|
-
const ast = parseJsx(jsx);
|
|
662
|
-
walk(ast, (n) => {
|
|
663
|
-
if (n.type === "JSXOpeningElement") {
|
|
664
|
-
const name = jsxName(n.name);
|
|
665
|
-
if (/^[A-Z]/.test(name) || name.includes(".")) names.add(name.split(".")[0]);
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
return [...names];
|
|
669
|
-
}
|
|
670
|
-
function withMarker(code, node) {
|
|
671
|
-
if (node.kind !== "component" && node.kind !== "element") return code;
|
|
672
|
-
const m = /^<[\w.:-]+/.exec(code);
|
|
673
|
-
if (!m) return code;
|
|
674
|
-
let i = m[0].length;
|
|
675
|
-
if (code[i] === "<") {
|
|
676
|
-
for (let depth = 0; i < code.length; i++) {
|
|
677
|
-
if (code[i] === "<") depth++;
|
|
678
|
-
else if (code[i] === ">" && --depth === 0) {
|
|
679
|
-
i++;
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
return `${code.slice(0, i)} __tcsel${code.slice(i)}`;
|
|
685
|
-
}
|
|
686
|
-
function literalLines(code) {
|
|
687
|
-
const out = /* @__PURE__ */ new Set();
|
|
688
|
-
if (!code.includes("\n") || !/[`"']/.test(code)) return out;
|
|
689
|
-
let ast;
|
|
690
|
-
try {
|
|
691
|
-
ast = parseModule(`<>${code}</>`);
|
|
692
|
-
} catch {
|
|
693
|
-
return out;
|
|
694
|
-
}
|
|
695
|
-
const ranges = [];
|
|
696
|
-
walk(ast, (n) => {
|
|
697
|
-
if (n.type === "TemplateLiteral" || n.type === "StringLiteral") ranges.push([n.start - 2, n.end - 2]);
|
|
698
|
-
});
|
|
699
|
-
if (!ranges.length) return out;
|
|
700
|
-
let offset = 0;
|
|
701
|
-
code.split("\n").forEach((line, i) => {
|
|
702
|
-
if (i > 0 && ranges.some(([a, b]) => offset > a && offset < b)) out.add(i);
|
|
703
|
-
offset += line.length + 1;
|
|
704
|
-
});
|
|
705
|
-
return out;
|
|
706
|
-
}
|
|
707
|
-
function parseJsx(jsx) {
|
|
708
|
-
try {
|
|
709
|
-
return parseModule(`<>${jsx}</>`);
|
|
710
|
-
} catch (err) {
|
|
711
|
-
throw new EditError(`Invalid JSX: ${err.message.replace(/\(\d+:\d+\)$/, "")}`);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
function frameCode(opts) {
|
|
715
|
-
const attrs = [attrCode("name", opts.name), attrCode("x", Math.round(opts.x)), attrCode("y", Math.round(opts.y)), attrCode("width", Math.round(opts.width))];
|
|
716
|
-
if (opts.height) attrs.push(attrCode("height", Math.round(opts.height)));
|
|
717
|
-
if (opts.theme) attrs.push(attrCode("theme", opts.theme));
|
|
718
|
-
if (opts.device) attrs.push(attrCode("device", opts.device));
|
|
719
|
-
if (opts.page) attrs.push(attrCode("page", opts.page));
|
|
720
|
-
if (opts.from) attrs.push(attrCode("from", opts.from));
|
|
721
|
-
if (opts.component) attrs.push(attrCode("component", opts.component));
|
|
722
|
-
const body = opts.body ?? `<div className="flex flex-col gap-3 p-6"></div>`;
|
|
723
|
-
return `<Frame ${attrs.join(" ")}>
|
|
724
|
-
${indentBlock(body)}
|
|
725
|
-
</Frame>`;
|
|
726
|
-
}
|
|
727
|
-
function indentBlock(code, unit = " ") {
|
|
728
|
-
return code.split("\n").map((l) => l.trim() ? unit + l : l).join("\n");
|
|
729
|
-
}
|
|
730
|
-
|
|
731
43
|
// src/core/devices.ts
|
|
732
44
|
var DEVICES = [
|
|
733
45
|
{ id: "iphone-16", name: "iPhone 16", kind: "phone", width: 393, height: 852, dpr: 3, statusBar: 54, homeIndicator: 34, radius: 55, island: true },
|
|
@@ -1088,9 +400,9 @@ import path3 from "path";
|
|
|
1088
400
|
var GitError = class extends EditError {
|
|
1089
401
|
};
|
|
1090
402
|
function git(cwd, args, timeout = 3e4, opts = {}) {
|
|
1091
|
-
return
|
|
403
|
+
return run2("git", cwd, args, timeout, opts);
|
|
1092
404
|
}
|
|
1093
|
-
function
|
|
405
|
+
function run2(cmd, cwd, args, timeout, opts = {}) {
|
|
1094
406
|
return new Promise((resolve, reject) => {
|
|
1095
407
|
const child = execFile(
|
|
1096
408
|
cmd,
|
|
@@ -1199,7 +511,7 @@ var Git = class {
|
|
|
1199
511
|
safeRef(branch);
|
|
1200
512
|
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();
|
|
1201
513
|
const os = await import("os");
|
|
1202
|
-
const
|
|
514
|
+
const fs12 = await import("fs");
|
|
1203
515
|
const index = path3.join(os.tmpdir(), `truecanvas-index-${process.pid}-${Date.now()}`);
|
|
1204
516
|
const env = { GIT_INDEX_FILE: index };
|
|
1205
517
|
try {
|
|
@@ -1214,18 +526,18 @@ var Git = class {
|
|
|
1214
526
|
await git(this.cwd, ["update-ref", `refs/heads/${branch}`, commit]);
|
|
1215
527
|
await git(this.cwd, ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`], 12e4);
|
|
1216
528
|
} finally {
|
|
1217
|
-
|
|
529
|
+
fs12.rmSync(index, { force: true });
|
|
1218
530
|
}
|
|
1219
531
|
}
|
|
1220
532
|
// ---------- GitHub (gh CLI) ----------
|
|
1221
533
|
/** owner/name of the GitHub repo, if origin is on GitHub and gh is set up. */
|
|
1222
534
|
async githubRepo() {
|
|
1223
|
-
const out = await
|
|
535
|
+
const out = await run2("gh", this.cwd, ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"], 2e4).catch(() => "");
|
|
1224
536
|
return out.trim() || null;
|
|
1225
537
|
}
|
|
1226
538
|
/** The open (or latest) pull request of the current branch. */
|
|
1227
539
|
async pullRequest() {
|
|
1228
|
-
const out = await
|
|
540
|
+
const out = await run2("gh", this.cwd, ["pr", "view", "--json", "number,url,title,state,isDraft,reviewDecision,statusCheckRollup"], 2e4).catch(() => "");
|
|
1229
541
|
if (!out.trim()) return null;
|
|
1230
542
|
const pr = JSON.parse(out);
|
|
1231
543
|
const checks = { passed: 0, failed: 0, pending: 0 };
|
|
@@ -1238,13 +550,13 @@ var Git = class {
|
|
|
1238
550
|
return { number: pr.number, url: pr.url, title: pr.title, state: pr.state, draft: pr.isDraft, review: pr.reviewDecision || null, checks };
|
|
1239
551
|
}
|
|
1240
552
|
async createPullRequest(opts) {
|
|
1241
|
-
const out = await
|
|
553
|
+
const out = await run2("gh", this.cwd, ["pr", "create", "--title", opts.title, "--body-file", "-", "--base", safeRef(opts.base), ...opts.draft ? ["--draft"] : []], 12e4, { input: opts.body });
|
|
1242
554
|
return out.trim().split("\n").pop() ?? "";
|
|
1243
555
|
}
|
|
1244
556
|
/** Creates a GitHub repo for this project and pushes it (gh repo create). */
|
|
1245
557
|
async publish(name, isPrivate) {
|
|
1246
558
|
if (!/^[\w.-]+$/.test(name)) throw new GitError("Invalid repository name.");
|
|
1247
|
-
await
|
|
559
|
+
await run2("gh", this.cwd, ["repo", "create", name, isPrivate ? "--private" : "--public", "--source", ".", "--remote", "origin", "--push"], 18e4);
|
|
1248
560
|
return (await git(this.cwd, ["remote", "get-url", "origin"])).trim();
|
|
1249
561
|
}
|
|
1250
562
|
/** git reports paths from the repo root; we show them from the project root. */
|
|
@@ -3385,87 +2697,25 @@ function componentJsx(spec, props = {}, text) {
|
|
|
3385
2697
|
return childText !== void 0 ? `${open}>${textCode(childText)}</${spec.name}>` : `${open} />`;
|
|
3386
2698
|
}
|
|
3387
2699
|
|
|
3388
|
-
// src/core/
|
|
3389
|
-
import { spawn } from "child_process";
|
|
2700
|
+
// src/core/shadcn.ts
|
|
3390
2701
|
import fs8 from "fs";
|
|
3391
2702
|
import path9 from "path";
|
|
3392
|
-
function detectPm(dir) {
|
|
3393
|
-
for (let d = dir; ; d = path9.dirname(d)) {
|
|
3394
|
-
if (fs8.existsSync(path9.join(d, "pnpm-lock.yaml")) || fs8.existsSync(path9.join(d, "pnpm-workspace.yaml"))) return "pnpm";
|
|
3395
|
-
if (fs8.existsSync(path9.join(d, "yarn.lock"))) return "yarn";
|
|
3396
|
-
if (fs8.existsSync(path9.join(d, "bun.lock")) || fs8.existsSync(path9.join(d, "bun.lockb"))) return "bun";
|
|
3397
|
-
if (fs8.existsSync(path9.join(d, "package-lock.json"))) return "npm";
|
|
3398
|
-
if (path9.dirname(d) === d || fs8.existsSync(path9.join(d, ".git"))) break;
|
|
3399
|
-
}
|
|
3400
|
-
const agent = process.env.npm_config_user_agent ?? "";
|
|
3401
|
-
if (agent.startsWith("pnpm")) return "pnpm";
|
|
3402
|
-
if (agent.startsWith("yarn")) return "yarn";
|
|
3403
|
-
if (agent.startsWith("bun")) return "bun";
|
|
3404
|
-
return "npm";
|
|
3405
|
-
}
|
|
3406
|
-
function addArgs(pm, specs, dev = false) {
|
|
3407
|
-
if (pm === "npm") return ["install", ...dev ? ["-D"] : [], ...specs];
|
|
3408
|
-
if (pm === "bun") return ["add", ...dev ? ["-d"] : [], ...specs];
|
|
3409
|
-
return ["add", ...dev ? ["-D"] : [], ...specs];
|
|
3410
|
-
}
|
|
3411
|
-
function dlx(pm, pkg, args) {
|
|
3412
|
-
if (pm === "pnpm") return ["pnpm", ["dlx", pkg, ...args]];
|
|
3413
|
-
if (pm === "yarn") return ["yarn", ["dlx", pkg, ...args]];
|
|
3414
|
-
if (pm === "bun") return ["bunx", [pkg, ...args]];
|
|
3415
|
-
return ["npx", ["--yes", pkg, ...args]];
|
|
3416
|
-
}
|
|
3417
|
-
function toolEnv(extra = {}) {
|
|
3418
|
-
return { ...process.env, PATH: `${path9.dirname(process.execPath)}${path9.delimiter}${process.env.PATH ?? ""}`, ...extra };
|
|
3419
|
-
}
|
|
3420
|
-
function shellSafe(cmd, args) {
|
|
3421
|
-
if (process.platform !== "win32") return { cmd, args, shell: false };
|
|
3422
|
-
const quote = (a) => /[\s"&|<>^()%!]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a;
|
|
3423
|
-
return { cmd: quote(cmd), args: args.map(quote), shell: true };
|
|
3424
|
-
}
|
|
3425
|
-
function run2(cmd, args, cwd, timeoutMs = 3e5) {
|
|
3426
|
-
return new Promise((resolve) => {
|
|
3427
|
-
let out = "";
|
|
3428
|
-
let done = false;
|
|
3429
|
-
const finish = (ok, extra = "") => {
|
|
3430
|
-
if (done) return;
|
|
3431
|
-
done = true;
|
|
3432
|
-
clearTimeout(timer);
|
|
3433
|
-
resolve({ ok, out: (out + extra).slice(-2e4) });
|
|
3434
|
-
};
|
|
3435
|
-
const safe = shellSafe(cmd, args);
|
|
3436
|
-
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 });
|
|
3437
|
-
const timer = setTimeout(() => {
|
|
3438
|
-
p.kill();
|
|
3439
|
-
finish(false, `
|
|
3440
|
-
${cmd} timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
3441
|
-
}, timeoutMs);
|
|
3442
|
-
p.stdout.on("data", (d) => out += d);
|
|
3443
|
-
p.stderr.on("data", (d) => out += d);
|
|
3444
|
-
p.on("error", (err) => finish(false, `
|
|
3445
|
-
${err.message}`));
|
|
3446
|
-
p.on("close", (code) => finish(code === 0));
|
|
3447
|
-
});
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
// src/core/shadcn.ts
|
|
3451
|
-
import fs9 from "fs";
|
|
3452
|
-
import path10 from "path";
|
|
3453
2703
|
function shadcnStatus(root) {
|
|
3454
|
-
const file =
|
|
3455
|
-
if (!
|
|
2704
|
+
const file = path9.join(root, "components.json");
|
|
2705
|
+
if (!fs8.existsSync(file)) return { initialized: false, uiDir: null, installed: [] };
|
|
3456
2706
|
let ui = "@/components/ui";
|
|
3457
2707
|
try {
|
|
3458
|
-
ui = JSON.parse(
|
|
2708
|
+
ui = JSON.parse(fs8.readFileSync(file, "utf8"))?.aliases?.ui ?? ui;
|
|
3459
2709
|
} catch {
|
|
3460
2710
|
}
|
|
3461
2711
|
const dir = resolveAliasPath(root, ui);
|
|
3462
2712
|
let installed = [];
|
|
3463
2713
|
try {
|
|
3464
|
-
installed =
|
|
2714
|
+
installed = fs8.readdirSync(dir).filter((f) => /\.(tsx|jsx)$/.test(f)).map((f) => f.replace(/\.(tsx|jsx)$/, "")).sort();
|
|
3465
2715
|
} catch {
|
|
3466
2716
|
installed = [];
|
|
3467
2717
|
}
|
|
3468
|
-
return { initialized: true, uiDir: posix(
|
|
2718
|
+
return { initialized: true, uiDir: posix(path9.relative(root, dir)), installed };
|
|
3469
2719
|
}
|
|
3470
2720
|
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(" ");
|
|
3471
2721
|
var registryCache = null;
|
|
@@ -3486,7 +2736,7 @@ async function shadcnRegistry() {
|
|
|
3486
2736
|
var VALID = /^[a-z0-9][a-z0-9-]*$/;
|
|
3487
2737
|
async function shadcnInit(root) {
|
|
3488
2738
|
const [cmd, args] = dlx(detectPm(root), "shadcn@latest", ["init", "--defaults", "--yes", "--no-monorepo"]);
|
|
3489
|
-
return
|
|
2739
|
+
return run(cmd, args, root);
|
|
3490
2740
|
}
|
|
3491
2741
|
async function shadcnAdd(root, names) {
|
|
3492
2742
|
const bad = names.filter((n) => !VALID.test(n));
|
|
@@ -3501,7 +2751,7 @@ async function shadcnAdd(root, names) {
|
|
|
3501
2751
|
const wanted = names.filter((n) => !before.has(n));
|
|
3502
2752
|
if (wanted.length) {
|
|
3503
2753
|
const [cmd, args] = dlx(detectPm(root), "shadcn@latest", ["add", ...wanted, "--yes"]);
|
|
3504
|
-
const res = await
|
|
2754
|
+
const res = await run(cmd, args, root);
|
|
3505
2755
|
log += res.out;
|
|
3506
2756
|
if (!res.ok) return { ok: false, out: log, added: [] };
|
|
3507
2757
|
}
|
|
@@ -3597,8 +2847,8 @@ function describeComponent(c, verbose = false) {
|
|
|
3597
2847
|
}
|
|
3598
2848
|
|
|
3599
2849
|
// src/core/tokens.ts
|
|
3600
|
-
import
|
|
3601
|
-
import
|
|
2850
|
+
import fs9 from "fs";
|
|
2851
|
+
import path10 from "path";
|
|
3602
2852
|
import { globSync } from "tinyglobby";
|
|
3603
2853
|
var GROUPS = [
|
|
3604
2854
|
["--color-", "Colors", "bg-{name} text-{name} border-{name}"],
|
|
@@ -3615,9 +2865,9 @@ function readDesignTokens(root) {
|
|
|
3615
2865
|
const sources = [];
|
|
3616
2866
|
let darkVariant = null;
|
|
3617
2867
|
for (const file of files) {
|
|
3618
|
-
const css =
|
|
2868
|
+
const css = fs9.readFileSync(file, "utf8");
|
|
3619
2869
|
if (!css.includes("@theme") && !css.includes("@custom-variant")) continue;
|
|
3620
|
-
sources.push(posix(
|
|
2870
|
+
sources.push(posix(path10.relative(root, file)));
|
|
3621
2871
|
const dv = /@custom-variant\s+dark\s*\(([^;]+)\);/.exec(css);
|
|
3622
2872
|
if (dv) darkVariant = dv[1].trim();
|
|
3623
2873
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) {
|
|
@@ -3656,7 +2906,7 @@ function readTokenData(root) {
|
|
|
3656
2906
|
for (const m of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) into.set(m[1], m[2].trim());
|
|
3657
2907
|
};
|
|
3658
2908
|
for (const file of files) {
|
|
3659
|
-
const css =
|
|
2909
|
+
const css = fs9.readFileSync(file, "utf8");
|
|
3660
2910
|
for (const block of css.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g)) decls(block[1], theme);
|
|
3661
2911
|
for (const block of css.matchAll(/(?:^|\n)\s*:root\s*\{([\s\S]*?)\n\}/g)) decls(block[1], lightVars);
|
|
3662
2912
|
for (const block of css.matchAll(/(?:^|\n)\s*(?:\.dark|\[data-theme=["']?dark["']?\]|:root\.dark)\s*\{([\s\S]*?)\n\}/g)) decls(block[1], darkVars);
|
|
@@ -3688,30 +2938,30 @@ function readTokenData(root) {
|
|
|
3688
2938
|
}
|
|
3689
2939
|
|
|
3690
2940
|
// src/core/compare.ts
|
|
3691
|
-
import
|
|
3692
|
-
import
|
|
2941
|
+
import fs10 from "fs";
|
|
2942
|
+
import path11 from "path";
|
|
3693
2943
|
var COMPARE_PREFIX = "__compare__";
|
|
3694
2944
|
function writeCompare(config, canvas, source, files = {}, as = canvas) {
|
|
3695
2945
|
const routeDir = routeDirOf(config);
|
|
3696
|
-
const dir =
|
|
3697
|
-
|
|
3698
|
-
const canvasDir =
|
|
2946
|
+
const dir = path11.join(routeDir, "compare");
|
|
2947
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
2948
|
+
const canvasDir = path11.join(config.root, config.canvasDir);
|
|
3699
2949
|
const canvasRel = `${config.canvasDir}/${canvas}.canvas.tsx`;
|
|
3700
2950
|
const swapped = /* @__PURE__ */ new Map();
|
|
3701
2951
|
for (const [rel, content] of Object.entries(files)) {
|
|
3702
|
-
const ext =
|
|
2952
|
+
const ext = path11.extname(rel);
|
|
3703
2953
|
const name = `${as}--${rel.slice(0, -ext.length).replace(/[^\w.-]+/g, "_")}${ext}`;
|
|
3704
|
-
const original =
|
|
3705
|
-
|
|
2954
|
+
const original = path11.join(config.root, rel);
|
|
2955
|
+
fs10.writeFileSync(path11.join(dir, name), rewriteImports(content, (spec) => spec.startsWith(".") ? relativeSpec(dir, path11.resolve(path11.dirname(original), spec)) : spec));
|
|
3706
2956
|
swapped.set(importSpecifier(config.root, canvasRel, rel), `./${name.slice(0, -ext.length)}`);
|
|
3707
2957
|
}
|
|
3708
|
-
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir,
|
|
3709
|
-
|
|
2958
|
+
const out = rewriteImports(source, (spec) => swapped.get(spec) ?? (spec.startsWith(".") ? relativeSpec(dir, path11.resolve(canvasDir, spec)) : spec));
|
|
2959
|
+
fs10.writeFileSync(path11.join(dir, `${as}.canvas.tsx`), out);
|
|
3710
2960
|
syncRoute(config);
|
|
3711
2961
|
return `${COMPARE_PREFIX}${as}`;
|
|
3712
2962
|
}
|
|
3713
2963
|
function relativeSpec(fromDir, target) {
|
|
3714
|
-
const next = posix(
|
|
2964
|
+
const next = posix(path11.relative(fromDir, target));
|
|
3715
2965
|
return next.startsWith(".") ? next : `./${next}`;
|
|
3716
2966
|
}
|
|
3717
2967
|
function rewriteImports(source, map) {
|
|
@@ -3727,9 +2977,9 @@ function rewriteImports(source, map) {
|
|
|
3727
2977
|
}
|
|
3728
2978
|
function clearCompare(config, canvas) {
|
|
3729
2979
|
if (canvas) assertCanvasName(canvas);
|
|
3730
|
-
const dir =
|
|
3731
|
-
if (!
|
|
3732
|
-
for (const f of
|
|
2980
|
+
const dir = path11.join(routeDirOf(config), "compare");
|
|
2981
|
+
if (!fs10.existsSync(dir)) return;
|
|
2982
|
+
for (const f of fs10.readdirSync(dir)) if (!canvas || f === `${canvas}.canvas.tsx` || f.startsWith(`${canvas}--`)) fs10.rmSync(path11.join(dir, f));
|
|
3733
2983
|
syncRoute(config);
|
|
3734
2984
|
}
|
|
3735
2985
|
function frameChanges(before, after) {
|
|
@@ -3746,16 +2996,16 @@ function frameChanges(before, after) {
|
|
|
3746
2996
|
}
|
|
3747
2997
|
|
|
3748
2998
|
// src/core/init.ts
|
|
3749
|
-
import
|
|
3750
|
-
import
|
|
3751
|
-
import
|
|
2999
|
+
import fs11 from "fs";
|
|
3000
|
+
import MagicString from "magic-string";
|
|
3001
|
+
import path12 from "path";
|
|
3752
3002
|
var CONFIG_FILES = {
|
|
3753
3003
|
next: ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.mts"],
|
|
3754
3004
|
vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]
|
|
3755
3005
|
};
|
|
3756
3006
|
function projectFramework(root) {
|
|
3757
3007
|
try {
|
|
3758
|
-
const pkg = JSON.parse(
|
|
3008
|
+
const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf8"));
|
|
3759
3009
|
return detectFramework({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
3760
3010
|
} catch {
|
|
3761
3011
|
return "next";
|
|
@@ -3764,8 +3014,8 @@ function projectFramework(root) {
|
|
|
3764
3014
|
function configStatus(root, framework = projectFramework(root)) {
|
|
3765
3015
|
const marker = framework === "vite" ? "truecanvas/vite" : "withTruecanvas";
|
|
3766
3016
|
for (const name of CONFIG_FILES[framework]) {
|
|
3767
|
-
const file =
|
|
3768
|
-
if (
|
|
3017
|
+
const file = path12.join(root, name);
|
|
3018
|
+
if (fs11.existsSync(file)) return { file, wrapped: fs11.readFileSync(file, "utf8").includes(marker), framework };
|
|
3769
3019
|
}
|
|
3770
3020
|
return { file: null, wrapped: false, framework };
|
|
3771
3021
|
}
|
|
@@ -3775,19 +3025,19 @@ function initProject(config) {
|
|
|
3775
3025
|
const vite = config.framework === "vite";
|
|
3776
3026
|
const status = configStatus(root, config.framework);
|
|
3777
3027
|
if (vite) {
|
|
3778
|
-
const name = status.file ?
|
|
3028
|
+
const name = status.file ? path12.basename(status.file) : "vite.config.ts";
|
|
3779
3029
|
if (status.wrapped) report.done.push(`${name} already uses the truecanvas() plugin`);
|
|
3780
3030
|
else {
|
|
3781
|
-
const src = status.file ?
|
|
3031
|
+
const src = status.file ? fs11.readFileSync(status.file, "utf8") : null;
|
|
3782
3032
|
const next = src && addVitePlugin(src);
|
|
3783
3033
|
if (next) {
|
|
3784
|
-
|
|
3034
|
+
fs11.writeFileSync(status.file, next);
|
|
3785
3035
|
report.done.push(`Added the truecanvas() plugin to ${name}`);
|
|
3786
3036
|
} else report.todo.push(`Add truecanvas() from "truecanvas/vite" to the plugins in ${name}`);
|
|
3787
3037
|
}
|
|
3788
3038
|
} else if (!status.file) {
|
|
3789
|
-
const file =
|
|
3790
|
-
|
|
3039
|
+
const file = path12.join(root, "next.config.ts");
|
|
3040
|
+
fs11.writeFileSync(file, `import type { NextConfig } from "next";
|
|
3791
3041
|
import { withTruecanvas } from "truecanvas/next";
|
|
3792
3042
|
|
|
3793
3043
|
const nextConfig: NextConfig = {};
|
|
@@ -3796,26 +3046,26 @@ export default withTruecanvas(nextConfig);
|
|
|
3796
3046
|
`);
|
|
3797
3047
|
report.done.push("Created next.config.ts with withTruecanvas()");
|
|
3798
3048
|
} else if (status.wrapped) {
|
|
3799
|
-
report.done.push(`${
|
|
3049
|
+
report.done.push(`${path12.basename(status.file)} already uses withTruecanvas()`);
|
|
3800
3050
|
} else {
|
|
3801
|
-
const src =
|
|
3051
|
+
const src = fs11.readFileSync(status.file, "utf8");
|
|
3802
3052
|
const next = wrapConfigExport(src);
|
|
3803
3053
|
if (next && next !== src && next.includes("withTruecanvas(")) {
|
|
3804
|
-
|
|
3805
|
-
report.done.push(`Wrapped ${
|
|
3054
|
+
fs11.writeFileSync(status.file, next);
|
|
3055
|
+
report.done.push(`Wrapped ${path12.basename(status.file)} with withTruecanvas()`);
|
|
3806
3056
|
} else {
|
|
3807
|
-
report.todo.push(`Wrap your ${
|
|
3057
|
+
report.todo.push(`Wrap your ${path12.basename(status.file)} export with withTruecanvas() from "truecanvas/next"`);
|
|
3808
3058
|
}
|
|
3809
3059
|
}
|
|
3810
|
-
const pkgFile =
|
|
3060
|
+
const pkgFile = path12.join(root, "package.json");
|
|
3811
3061
|
try {
|
|
3812
|
-
const raw =
|
|
3062
|
+
const raw = fs11.readFileSync(pkgFile, "utf8");
|
|
3813
3063
|
const pkg = JSON.parse(raw);
|
|
3814
3064
|
pkg.scripts ??= {};
|
|
3815
3065
|
if (!Object.values(pkg.scripts).some((v) => typeof v === "string" && v.includes("truecanvas"))) {
|
|
3816
3066
|
pkg.scripts.canvas = "truecanvas dev";
|
|
3817
3067
|
const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
|
|
3818
|
-
|
|
3068
|
+
fs11.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, indent)}
|
|
3819
3069
|
`);
|
|
3820
3070
|
report.done.push(`Added the "canvas" script (npm run canvas)`);
|
|
3821
3071
|
}
|
|
@@ -3825,20 +3075,20 @@ export default withTruecanvas(nextConfig);
|
|
|
3825
3075
|
} catch {
|
|
3826
3076
|
report.todo.push("Couldn't read package.json");
|
|
3827
3077
|
}
|
|
3828
|
-
const ignoreFile =
|
|
3078
|
+
const ignoreFile = path12.join(root, ".gitignore");
|
|
3829
3079
|
const entry = `/${config.routeDir}/`;
|
|
3830
|
-
const ignore =
|
|
3080
|
+
const ignore = fs11.existsSync(ignoreFile) ? fs11.readFileSync(ignoreFile, "utf8") : "";
|
|
3831
3081
|
if (!ignore.split("\n").some((l) => l.trim() === entry || l.trim() === entry.slice(1))) {
|
|
3832
|
-
|
|
3082
|
+
fs11.writeFileSync(ignoreFile, `${ignore}${ignore && !ignore.endsWith("\n") ? "\n" : ""}
|
|
3833
3083
|
# Truecanvas (generated dev-only route)
|
|
3834
3084
|
${entry}
|
|
3835
3085
|
`);
|
|
3836
3086
|
report.done.push(`Ignored ${entry} in .gitignore`);
|
|
3837
3087
|
}
|
|
3838
3088
|
for (const name of ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs", "tailwind.config.cjs"]) {
|
|
3839
|
-
const file =
|
|
3840
|
-
if (!
|
|
3841
|
-
const src =
|
|
3089
|
+
const file = path12.join(root, name);
|
|
3090
|
+
if (!fs11.existsSync(file)) continue;
|
|
3091
|
+
const src = fs11.readFileSync(file, "utf8");
|
|
3842
3092
|
const glob = `./${config.canvasDir}/**/*.{ts,tsx}`;
|
|
3843
3093
|
if (src.includes(config.canvasDir)) break;
|
|
3844
3094
|
const m = /content\s*:\s*\[([ \t]*\n?)([ \t]*)/.exec(src);
|
|
@@ -3847,12 +3097,12 @@ ${entry}
|
|
|
3847
3097
|
const at = m.index + m[0].length;
|
|
3848
3098
|
const entry2 = m[1].includes("\n") ? `${JSON.stringify(glob)},
|
|
3849
3099
|
${indent}` : `${JSON.stringify(glob)}, `;
|
|
3850
|
-
|
|
3100
|
+
fs11.writeFileSync(file, src.slice(0, at) + entry2 + src.slice(at));
|
|
3851
3101
|
report.done.push(`Added ${config.canvasDir} to Tailwind's content in ${name}`);
|
|
3852
3102
|
} else report.todo.push(`Add "${glob}" to the content list in ${name} so Tailwind styles your canvases`);
|
|
3853
3103
|
break;
|
|
3854
3104
|
}
|
|
3855
|
-
if (!vite && !
|
|
3105
|
+
if (!vite && !fs11.existsSync(path12.join(root, config.appDir))) report.todo.push(`No ${config.appDir}/ folder found: Truecanvas needs the Next.js App Router`);
|
|
3856
3106
|
return report;
|
|
3857
3107
|
}
|
|
3858
3108
|
function addImport(src, line) {
|
|
@@ -3872,7 +3122,7 @@ function wrapConfigExport(src) {
|
|
|
3872
3122
|
} catch {
|
|
3873
3123
|
return null;
|
|
3874
3124
|
}
|
|
3875
|
-
const s = new
|
|
3125
|
+
const s = new MagicString(src);
|
|
3876
3126
|
for (const stmt of ast.program.body) {
|
|
3877
3127
|
if (stmt.type === "ExportDefaultDeclaration") {
|
|
3878
3128
|
const d = stmt.declaration;
|
|
@@ -3917,7 +3167,7 @@ function addVitePlugin(src) {
|
|
|
3917
3167
|
});
|
|
3918
3168
|
if (!plugins) return null;
|
|
3919
3169
|
const { start, end, elements } = plugins;
|
|
3920
|
-
const s = new
|
|
3170
|
+
const s = new MagicString(src);
|
|
3921
3171
|
if (!elements.length) s.overwrite(start, end, "[truecanvas()]");
|
|
3922
3172
|
else {
|
|
3923
3173
|
const last = elements[elements.length - 1];
|
|
@@ -3928,15 +3178,6 @@ function addVitePlugin(src) {
|
|
|
3928
3178
|
}
|
|
3929
3179
|
|
|
3930
3180
|
export {
|
|
3931
|
-
parseCanvas,
|
|
3932
|
-
allNodes,
|
|
3933
|
-
findNode,
|
|
3934
|
-
findFrame,
|
|
3935
|
-
findByPath,
|
|
3936
|
-
EditError,
|
|
3937
|
-
attrCode,
|
|
3938
|
-
textCode,
|
|
3939
|
-
CanvasEditor,
|
|
3940
3181
|
listRoutes,
|
|
3941
3182
|
layoutFiles,
|
|
3942
3183
|
componentsDir,
|
|
@@ -3950,11 +3191,6 @@ export {
|
|
|
3950
3191
|
Git,
|
|
3951
3192
|
Workspace,
|
|
3952
3193
|
componentJsx,
|
|
3953
|
-
detectPm,
|
|
3954
|
-
addArgs,
|
|
3955
|
-
toolEnv,
|
|
3956
|
-
shellSafe,
|
|
3957
|
-
run2 as run,
|
|
3958
3194
|
shadcnStatus,
|
|
3959
3195
|
shadcnRegistry,
|
|
3960
3196
|
shadcnInit,
|