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.
@@ -1,12 +1,32 @@
1
1
  import {
2
- detectFramework,
3
- idOf,
4
- isComponentName,
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-PBKX3SBC.js";
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 run("git", cwd, args, timeout, opts);
403
+ return run2("git", cwd, args, timeout, opts);
1092
404
  }
1093
- function run(cmd, cwd, args, timeout, opts = {}) {
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 fs13 = await import("fs");
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
- fs13.rmSync(index, { force: true });
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 run("gh", this.cwd, ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"], 2e4).catch(() => "");
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 run("gh", this.cwd, ["pr", "view", "--json", "number,url,title,state,isDraft,reviewDecision,statusCheckRollup"], 2e4).catch(() => "");
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 run("gh", this.cwd, ["pr", "create", "--title", opts.title, "--body-file", "-", "--base", safeRef(opts.base), ...opts.draft ? ["--draft"] : []], 12e4, { input: opts.body });
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 run("gh", this.cwd, ["repo", "create", name, isPrivate ? "--private" : "--public", "--source", ".", "--remote", "origin", "--push"], 18e4);
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/pm.ts
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 = path10.join(root, "components.json");
3455
- if (!fs9.existsSync(file)) return { initialized: false, uiDir: null, installed: [] };
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(fs9.readFileSync(file, "utf8"))?.aliases?.ui ?? ui;
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 = fs9.readdirSync(dir).filter((f) => /\.(tsx|jsx)$/.test(f)).map((f) => f.replace(/\.(tsx|jsx)$/, "")).sort();
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(path10.relative(root, dir)), installed };
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 run2(cmd, args, root);
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 run2(cmd, args, root);
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 fs10 from "fs";
3601
- import path11 from "path";
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 = fs10.readFileSync(file, "utf8");
2868
+ const css = fs9.readFileSync(file, "utf8");
3619
2869
  if (!css.includes("@theme") && !css.includes("@custom-variant")) continue;
3620
- sources.push(posix(path11.relative(root, file)));
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 = fs10.readFileSync(file, "utf8");
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 fs11 from "fs";
3692
- import path12 from "path";
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 = path12.join(routeDir, "compare");
3697
- fs11.mkdirSync(dir, { recursive: true });
3698
- const canvasDir = path12.join(config.root, config.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 = path12.extname(rel);
2952
+ const ext = path11.extname(rel);
3703
2953
  const name = `${as}--${rel.slice(0, -ext.length).replace(/[^\w.-]+/g, "_")}${ext}`;
3704
- const original = path12.join(config.root, rel);
3705
- fs11.writeFileSync(path12.join(dir, name), rewriteImports(content, (spec) => spec.startsWith(".") ? relativeSpec(dir, path12.resolve(path12.dirname(original), spec)) : spec));
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, path12.resolve(canvasDir, spec)) : spec));
3709
- fs11.writeFileSync(path12.join(dir, `${as}.canvas.tsx`), out);
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(path12.relative(fromDir, target));
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 = path12.join(routeDirOf(config), "compare");
3731
- if (!fs11.existsSync(dir)) return;
3732
- for (const f of fs11.readdirSync(dir)) if (!canvas || f === `${canvas}.canvas.tsx` || f.startsWith(`${canvas}--`)) fs11.rmSync(path12.join(dir, f));
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 fs12 from "fs";
3750
- import MagicString2 from "magic-string";
3751
- import path13 from "path";
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(fs12.readFileSync(path13.join(root, "package.json"), "utf8"));
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 = path13.join(root, name);
3768
- if (fs12.existsSync(file)) return { file, wrapped: fs12.readFileSync(file, "utf8").includes(marker), framework };
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 ? path13.basename(status.file) : "vite.config.ts";
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 ? fs12.readFileSync(status.file, "utf8") : null;
3031
+ const src = status.file ? fs11.readFileSync(status.file, "utf8") : null;
3782
3032
  const next = src && addVitePlugin(src);
3783
3033
  if (next) {
3784
- fs12.writeFileSync(status.file, next);
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 = path13.join(root, "next.config.ts");
3790
- fs12.writeFileSync(file, `import type { NextConfig } from "next";
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(`${path13.basename(status.file)} already uses withTruecanvas()`);
3049
+ report.done.push(`${path12.basename(status.file)} already uses withTruecanvas()`);
3800
3050
  } else {
3801
- const src = fs12.readFileSync(status.file, "utf8");
3051
+ const src = fs11.readFileSync(status.file, "utf8");
3802
3052
  const next = wrapConfigExport(src);
3803
3053
  if (next && next !== src && next.includes("withTruecanvas(")) {
3804
- fs12.writeFileSync(status.file, next);
3805
- report.done.push(`Wrapped ${path13.basename(status.file)} with withTruecanvas()`);
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 ${path13.basename(status.file)} export with withTruecanvas() from "truecanvas/next"`);
3057
+ report.todo.push(`Wrap your ${path12.basename(status.file)} export with withTruecanvas() from "truecanvas/next"`);
3808
3058
  }
3809
3059
  }
3810
- const pkgFile = path13.join(root, "package.json");
3060
+ const pkgFile = path12.join(root, "package.json");
3811
3061
  try {
3812
- const raw = fs12.readFileSync(pkgFile, "utf8");
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
- fs12.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, indent)}
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 = path13.join(root, ".gitignore");
3078
+ const ignoreFile = path12.join(root, ".gitignore");
3829
3079
  const entry = `/${config.routeDir}/`;
3830
- const ignore = fs12.existsSync(ignoreFile) ? fs12.readFileSync(ignoreFile, "utf8") : "";
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
- fs12.writeFileSync(ignoreFile, `${ignore}${ignore && !ignore.endsWith("\n") ? "\n" : ""}
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 = path13.join(root, name);
3840
- if (!fs12.existsSync(file)) continue;
3841
- const src = fs12.readFileSync(file, "utf8");
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
- fs12.writeFileSync(file, src.slice(0, at) + entry2 + src.slice(at));
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 && !fs12.existsSync(path13.join(root, config.appDir))) report.todo.push(`No ${config.appDir}/ folder found: Truecanvas needs the Next.js App Router`);
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 MagicString2(src);
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 MagicString2(src);
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,