foss-design 0.6.1 → 0.7.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/dist/cli.js +821 -60
- package/dist/runtime/index.js +7 -2
- package/dist/viewer/assets/{index-D6sHcSw6.js → index-CBCJT9KP.js} +1 -1
- package/dist/viewer/assets/index-CgFWotIU.css +2 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +1 -1
- package/dist/viewer-lib/styles.css +4 -0
- package/package.json +1 -1
- package/dist/viewer/assets/index-BL2Cxj98.css +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import
|
|
4
|
+
import path28 from "path";
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
6
|
|
|
7
7
|
// src/core/paths.ts
|
|
@@ -346,8 +346,8 @@ function routeError(pattern) {
|
|
|
346
346
|
}
|
|
347
347
|
return null;
|
|
348
348
|
}
|
|
349
|
-
function split(
|
|
350
|
-
return
|
|
349
|
+
function split(path29) {
|
|
350
|
+
return path29.split("/").filter(Boolean);
|
|
351
351
|
}
|
|
352
352
|
function compileRoute(pattern) {
|
|
353
353
|
return {
|
|
@@ -366,8 +366,8 @@ function decode2(part) {
|
|
|
366
366
|
return part;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
function scoreRoute(route2,
|
|
370
|
-
const parts = split(
|
|
369
|
+
function scoreRoute(route2, path29) {
|
|
370
|
+
const parts = split(path29.split(/[?#]/)[0] ?? "").map(decode2);
|
|
371
371
|
const walk2 = (s, p) => {
|
|
372
372
|
if (s === route2.segments.length) return p === parts.length ? 0 : null;
|
|
373
373
|
const segment = route2.segments[s];
|
|
@@ -385,11 +385,11 @@ function scoreRoute(route2, path27) {
|
|
|
385
385
|
};
|
|
386
386
|
return walk2(0, 0);
|
|
387
387
|
}
|
|
388
|
-
function matchRoute(entries,
|
|
388
|
+
function matchRoute(entries, path29) {
|
|
389
389
|
let best = null;
|
|
390
390
|
for (const entry of entries) {
|
|
391
391
|
for (const route2 of entry.routes) {
|
|
392
|
-
const score = scoreRoute(route2,
|
|
392
|
+
const score = scoreRoute(route2, path29);
|
|
393
393
|
if (score !== null && (!best || score > best.score)) best = { score, value: entry.value };
|
|
394
394
|
}
|
|
395
395
|
}
|
|
@@ -512,9 +512,9 @@ var CanvasSchema = z.strictObject({
|
|
|
512
512
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
513
513
|
});
|
|
514
514
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
515
|
-
function formatPath(base,
|
|
515
|
+
function formatPath(base, path29) {
|
|
516
516
|
let out = base;
|
|
517
|
-
for (const part of
|
|
517
|
+
for (const part of path29) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
518
518
|
return out;
|
|
519
519
|
}
|
|
520
520
|
function describeZodError(error, base = "") {
|
|
@@ -1874,12 +1874,12 @@ function rewriteRefs(html, originalDir, stagedDir) {
|
|
|
1874
1874
|
return tag;
|
|
1875
1875
|
return tag.replace(
|
|
1876
1876
|
/(\s(?:src|href|poster)\s*=\s*)(["'])([^"']*)\2/gi,
|
|
1877
|
-
(whole,
|
|
1877
|
+
(whole, attr2, quote, value) => {
|
|
1878
1878
|
if (!value || /^(?:[a-z][a-z0-9+.-]*:|\/|#|\{)/i.test(value)) return whole;
|
|
1879
1879
|
const [pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value).slice(1);
|
|
1880
1880
|
const target = path11.resolve(originalDir, decodeURIComponent(pathname));
|
|
1881
1881
|
if (!fs11.existsSync(target)) return whole;
|
|
1882
|
-
return `${
|
|
1882
|
+
return `${attr2}${quote}${relUrl(stagedDir, target)}${suffix}${quote}`;
|
|
1883
1883
|
}
|
|
1884
1884
|
);
|
|
1885
1885
|
});
|
|
@@ -2438,7 +2438,7 @@ var CloudClient = class {
|
|
|
2438
2438
|
}
|
|
2439
2439
|
host;
|
|
2440
2440
|
token;
|
|
2441
|
-
async send(method,
|
|
2441
|
+
async send(method, path29, init) {
|
|
2442
2442
|
const headers = {
|
|
2443
2443
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2444
2444
|
Accept: "application/json"
|
|
@@ -2446,7 +2446,7 @@ var CloudClient = class {
|
|
|
2446
2446
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2447
2447
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2448
2448
|
try {
|
|
2449
|
-
return await fetch(`${this.host}/api${
|
|
2449
|
+
return await fetch(`${this.host}/api${path29}`, {
|
|
2450
2450
|
method,
|
|
2451
2451
|
headers,
|
|
2452
2452
|
body: init.body,
|
|
@@ -2467,8 +2467,8 @@ var CloudClient = class {
|
|
|
2467
2467
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2468
2468
|
}
|
|
2469
2469
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2470
|
-
async request(method,
|
|
2471
|
-
const response = await this.send(method,
|
|
2470
|
+
async request(method, path29, body) {
|
|
2471
|
+
const response = await this.send(method, path29, {
|
|
2472
2472
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2473
2473
|
type: body === void 0 ? void 0 : "application/json",
|
|
2474
2474
|
timeout: JSON_TIMEOUT
|
|
@@ -2480,7 +2480,7 @@ var CloudClient = class {
|
|
|
2480
2480
|
try {
|
|
2481
2481
|
return JSON.parse(text2);
|
|
2482
2482
|
} catch {
|
|
2483
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2483
|
+
throw new CloudError(`${this.host} answered ${method} ${path29} with something other than JSON`, 0, "bad_response");
|
|
2484
2484
|
}
|
|
2485
2485
|
}
|
|
2486
2486
|
/** POST /device/code. */
|
|
@@ -3676,13 +3676,19 @@ async function openFrame(page, url, options = {}) {
|
|
|
3676
3676
|
record2(text2);
|
|
3677
3677
|
});
|
|
3678
3678
|
let ready = false;
|
|
3679
|
+
let reloads = 0;
|
|
3680
|
+
const onNavigate = (frame) => {
|
|
3681
|
+
if (frame === page.mainFrame()) reloads++;
|
|
3682
|
+
};
|
|
3679
3683
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3680
3684
|
errors = [];
|
|
3685
|
+
reloads = 0;
|
|
3681
3686
|
ready = options.waitForReady === false;
|
|
3682
3687
|
const turnedAway = new Promise((resolve) => {
|
|
3683
3688
|
outdated = () => resolve("outdated");
|
|
3684
3689
|
});
|
|
3685
3690
|
await page.goto(url, { waitUntil: "load", timeout: options.timeoutMs ?? 3e4 });
|
|
3691
|
+
page.on("framenavigated", onNavigate);
|
|
3686
3692
|
if (!ready) {
|
|
3687
3693
|
const signal = page.waitForFunction(() => window.__DESIGN_READY__ === true, null, {
|
|
3688
3694
|
timeout: options.timeoutMs ?? 2e4
|
|
@@ -3691,10 +3697,12 @@ async function openFrame(page, url, options = {}) {
|
|
|
3691
3697
|
});
|
|
3692
3698
|
ready = await Promise.race([signal, turnedAway]).catch(() => null) === "ready";
|
|
3693
3699
|
}
|
|
3694
|
-
if (
|
|
3700
|
+
if (ready || options.waitForReady === false) await page.waitForTimeout(options.settleMs ?? 700);
|
|
3701
|
+
page.off("framenavigated", onNavigate);
|
|
3702
|
+
if (!reloads && !errors.some((error) => OUTDATED_DEP.test(error))) break;
|
|
3695
3703
|
}
|
|
3704
|
+
if (reloads) errors.push(`the page reloaded itself ${reloads === 1 ? "once" : `${reloads} times`} while it loaded`);
|
|
3696
3705
|
const held = ready ? 0 : await page.evaluate(() => window.__DESIGN_HOLDS__ ?? 0).catch(() => 0);
|
|
3697
|
-
await page.waitForTimeout(options.settleMs ?? 700);
|
|
3698
3706
|
const runtimeErrors = await page.evaluate(() => window.__DESIGN_ERRORS__ ?? []).catch(() => []);
|
|
3699
3707
|
for (const error of runtimeErrors) if (!errors.includes(error)) errors.push(error);
|
|
3700
3708
|
const overlay = await page.evaluate(() => {
|
|
@@ -4020,8 +4028,17 @@ async function startDevServer(options) {
|
|
|
4020
4028
|
allow: [searchForWorkspaceRoot(paths.root), paths.root, paths.design, PKG.root, ...links.allow]
|
|
4021
4029
|
},
|
|
4022
4030
|
watch: {
|
|
4023
|
-
|
|
4024
|
-
|
|
4031
|
+
// `.cache` is ours (server.log, snapshots, builds): a change there must not reload frames. The
|
|
4032
|
+
// generated entries are modules the frames load, so those stay watched.
|
|
4033
|
+
ignored: (file) => {
|
|
4034
|
+
const rel = toPosix(path21.relative(paths.cache, file));
|
|
4035
|
+
const inCache = rel !== "" && !rel.startsWith("..") && !path21.isAbsolute(rel);
|
|
4036
|
+
return inCache && rel !== "entries" && !rel.startsWith("entries/");
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
4039
|
+
// Vite forwards frames' console warnings to the server when an agent runs it; the runtime
|
|
4040
|
+
// already reports errors, and the log they would fill sits in `.cache`.
|
|
4041
|
+
forwardConsole: false
|
|
4025
4042
|
},
|
|
4026
4043
|
optimizeDeps: {
|
|
4027
4044
|
entries: entries.files().map((file) => toPosix(path21.relative(paths.design, file)))
|
|
@@ -4331,7 +4348,7 @@ async function renderFrames(jobs) {
|
|
|
4331
4348
|
try {
|
|
4332
4349
|
report = await openFrame(page, job.url, { waitForReady: job.waitForReady, settleMs: 300 });
|
|
4333
4350
|
links = await page.evaluate(
|
|
4334
|
-
() => [...document.querySelectorAll("a[href], area[href]")].map((link) => ({
|
|
4351
|
+
() => [...document.querySelectorAll("a[href]:not([download]), area[href]:not([download])")].map((link) => ({
|
|
4335
4352
|
href: link.getAttribute("href") ?? "",
|
|
4336
4353
|
text: (link.textContent || link.getAttribute("aria-label") || "").replace(/\s+/g, " ").trim().slice(0, 40)
|
|
4337
4354
|
}))
|
|
@@ -4587,10 +4604,747 @@ async function runLink(paths, wanted, options) {
|
|
|
4587
4604
|
else if (remote) print(dim("Everything is in sync."));
|
|
4588
4605
|
}
|
|
4589
4606
|
|
|
4590
|
-
// src/cli/commands/
|
|
4607
|
+
// src/cli/commands/import.ts
|
|
4608
|
+
import fs26 from "fs";
|
|
4609
|
+
import path24 from "path";
|
|
4610
|
+
|
|
4611
|
+
// src/import/claude-design.ts
|
|
4591
4612
|
import fs25 from "fs";
|
|
4592
4613
|
import path23 from "path";
|
|
4593
4614
|
|
|
4615
|
+
// src/import/markup.ts
|
|
4616
|
+
var VOID = /* @__PURE__ */ new Set([
|
|
4617
|
+
"area",
|
|
4618
|
+
"base",
|
|
4619
|
+
"br",
|
|
4620
|
+
"col",
|
|
4621
|
+
"embed",
|
|
4622
|
+
"hr",
|
|
4623
|
+
"img",
|
|
4624
|
+
"input",
|
|
4625
|
+
"link",
|
|
4626
|
+
"meta",
|
|
4627
|
+
"source",
|
|
4628
|
+
"track",
|
|
4629
|
+
"wbr"
|
|
4630
|
+
]);
|
|
4631
|
+
var RAW = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
|
|
4632
|
+
var ATTR = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/y;
|
|
4633
|
+
function parseMarkup(source) {
|
|
4634
|
+
const root = { type: "element", tag: "#root", attrs: [], children: [] };
|
|
4635
|
+
const stack = [root];
|
|
4636
|
+
const top = () => stack[stack.length - 1];
|
|
4637
|
+
let i = 0;
|
|
4638
|
+
while (i < source.length) {
|
|
4639
|
+
const lt = source.indexOf("<", i);
|
|
4640
|
+
if (lt === -1) {
|
|
4641
|
+
top().children.push({ type: "text", text: source.slice(i) });
|
|
4642
|
+
break;
|
|
4643
|
+
}
|
|
4644
|
+
if (lt > i) top().children.push({ type: "text", text: source.slice(i, lt) });
|
|
4645
|
+
if (source.startsWith("<!--", lt)) {
|
|
4646
|
+
const end = source.indexOf("-->", lt + 4);
|
|
4647
|
+
i = end === -1 ? source.length : end + 3;
|
|
4648
|
+
continue;
|
|
4649
|
+
}
|
|
4650
|
+
if (source[lt + 1] === "!" || source[lt + 1] === "?") {
|
|
4651
|
+
const end = source.indexOf(">", lt);
|
|
4652
|
+
i = end === -1 ? source.length : end + 1;
|
|
4653
|
+
continue;
|
|
4654
|
+
}
|
|
4655
|
+
if (source[lt + 1] === "/") {
|
|
4656
|
+
const end = source.indexOf(">", lt);
|
|
4657
|
+
const tag = source.slice(lt + 2, end === -1 ? source.length : end).trim();
|
|
4658
|
+
i = end === -1 ? source.length : end + 1;
|
|
4659
|
+
for (let depth = stack.length - 1; depth > 0; depth--) {
|
|
4660
|
+
if (stack[depth].tag.toLowerCase() === tag.toLowerCase()) {
|
|
4661
|
+
stack.length = depth;
|
|
4662
|
+
break;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
continue;
|
|
4666
|
+
}
|
|
4667
|
+
const name = /^[A-Za-z][\w:-]*/.exec(source.slice(lt + 1));
|
|
4668
|
+
if (!name) {
|
|
4669
|
+
top().children.push({ type: "text", text: "<" });
|
|
4670
|
+
i = lt + 1;
|
|
4671
|
+
continue;
|
|
4672
|
+
}
|
|
4673
|
+
const element = { type: "element", tag: name[0], attrs: [], children: [] };
|
|
4674
|
+
let at = lt + 1 + name[0].length;
|
|
4675
|
+
let selfClosing = false;
|
|
4676
|
+
for (; ; ) {
|
|
4677
|
+
while (at < source.length && /\s/.test(source[at])) at++;
|
|
4678
|
+
if (at >= source.length) break;
|
|
4679
|
+
if (source[at] === ">") {
|
|
4680
|
+
at++;
|
|
4681
|
+
break;
|
|
4682
|
+
}
|
|
4683
|
+
if (source.startsWith("/>", at)) {
|
|
4684
|
+
selfClosing = true;
|
|
4685
|
+
at += 2;
|
|
4686
|
+
break;
|
|
4687
|
+
}
|
|
4688
|
+
ATTR.lastIndex = at;
|
|
4689
|
+
const match = ATTR.exec(source);
|
|
4690
|
+
if (!match) {
|
|
4691
|
+
at++;
|
|
4692
|
+
continue;
|
|
4693
|
+
}
|
|
4694
|
+
element.attrs.push([match[1], match[2] ?? match[3] ?? match[4] ?? null]);
|
|
4695
|
+
at = ATTR.lastIndex;
|
|
4696
|
+
}
|
|
4697
|
+
top().children.push(element);
|
|
4698
|
+
const lower = element.tag.toLowerCase();
|
|
4699
|
+
if (RAW.has(lower) && !selfClosing) {
|
|
4700
|
+
const close = source.toLowerCase().indexOf(`</${lower}`, at);
|
|
4701
|
+
const end = close === -1 ? source.length : close;
|
|
4702
|
+
element.children.push({ type: "text", text: source.slice(at, end) });
|
|
4703
|
+
const gt = close === -1 ? -1 : source.indexOf(">", close);
|
|
4704
|
+
i = gt === -1 ? source.length : gt + 1;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
if (!selfClosing && !VOID.has(lower)) stack.push(element);
|
|
4708
|
+
i = at;
|
|
4709
|
+
}
|
|
4710
|
+
return root;
|
|
4711
|
+
}
|
|
4712
|
+
function findElement(node, test) {
|
|
4713
|
+
for (const child of node.children) {
|
|
4714
|
+
if (child.type !== "element") continue;
|
|
4715
|
+
if (test(child)) return child;
|
|
4716
|
+
const found = findElement(child, test);
|
|
4717
|
+
if (found) return found;
|
|
4718
|
+
}
|
|
4719
|
+
return null;
|
|
4720
|
+
}
|
|
4721
|
+
var attr = (element, name) => element.attrs.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
4722
|
+
var NAMED = {
|
|
4723
|
+
amp: "&",
|
|
4724
|
+
lt: "<",
|
|
4725
|
+
gt: ">",
|
|
4726
|
+
quot: '"',
|
|
4727
|
+
apos: "'",
|
|
4728
|
+
nbsp: "\xA0",
|
|
4729
|
+
thinsp: "\u2009",
|
|
4730
|
+
ensp: "\u2002",
|
|
4731
|
+
emsp: "\u2003",
|
|
4732
|
+
mdash: "\u2014",
|
|
4733
|
+
ndash: "\u2013",
|
|
4734
|
+
minus: "\u2212",
|
|
4735
|
+
hellip: "\u2026",
|
|
4736
|
+
middot: "\xB7",
|
|
4737
|
+
bull: "\u2022",
|
|
4738
|
+
laquo: "\xAB",
|
|
4739
|
+
raquo: "\xBB",
|
|
4740
|
+
lsquo: "\u2018",
|
|
4741
|
+
rsquo: "\u2019",
|
|
4742
|
+
ldquo: "\u201C",
|
|
4743
|
+
rdquo: "\u201D",
|
|
4744
|
+
times: "\xD7",
|
|
4745
|
+
divide: "\xF7",
|
|
4746
|
+
deg: "\xB0",
|
|
4747
|
+
copy: "\xA9",
|
|
4748
|
+
reg: "\xAE",
|
|
4749
|
+
trade: "\u2122",
|
|
4750
|
+
euro: "\u20AC",
|
|
4751
|
+
pound: "\xA3",
|
|
4752
|
+
yen: "\xA5",
|
|
4753
|
+
cent: "\xA2",
|
|
4754
|
+
sect: "\xA7",
|
|
4755
|
+
para: "\xB6",
|
|
4756
|
+
plusmn: "\xB1",
|
|
4757
|
+
le: "\u2264",
|
|
4758
|
+
ge: "\u2265",
|
|
4759
|
+
ne: "\u2260",
|
|
4760
|
+
larr: "\u2190",
|
|
4761
|
+
rarr: "\u2192",
|
|
4762
|
+
uarr: "\u2191",
|
|
4763
|
+
darr: "\u2193",
|
|
4764
|
+
harr: "\u2194",
|
|
4765
|
+
check: "\u2713",
|
|
4766
|
+
zwj: "\u200D",
|
|
4767
|
+
zwnj: "\u200C",
|
|
4768
|
+
shy: "\xAD"
|
|
4769
|
+
};
|
|
4770
|
+
function decodeEntities(text2) {
|
|
4771
|
+
return text2.replace(/&(#x[0-9a-f]+|#\d+|[a-z][a-z0-9]*);/gi, (whole, body) => {
|
|
4772
|
+
if (body[0] === "#") {
|
|
4773
|
+
const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number(body.slice(1));
|
|
4774
|
+
return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
4775
|
+
}
|
|
4776
|
+
return NAMED[body.toLowerCase()] ?? whole;
|
|
4777
|
+
});
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// src/import/claude-design.ts
|
|
4781
|
+
var EXT = ".dc.html";
|
|
4782
|
+
var kebab = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
4783
|
+
var identifier = (name) => {
|
|
4784
|
+
const clean = name.replace(/[^\w$]/g, "_");
|
|
4785
|
+
return /^[A-Za-z_$]/.test(clean) ? clean : `_${clean}`;
|
|
4786
|
+
};
|
|
4787
|
+
var KEYWORDS = new Set(
|
|
4788
|
+
"break case catch class const continue debugger default delete do else export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static await async of undefined NaN Infinity arguments eval Math JSON Number String Boolean Object Array Date Intl RegExp Error Map Set Symbol Promise BigInt console window document globalThis location navigator parseInt parseFloat isNaN isFinite encodeURIComponent decodeURIComponent encodeURI decodeURI setTimeout clearTimeout setInterval clearInterval".split(" ")
|
|
4789
|
+
);
|
|
4790
|
+
function freeNames(expression, into) {
|
|
4791
|
+
const code = expression.replace(/'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/g, '""').replace(
|
|
4792
|
+
/`(?:\\.|\$\{([^}]*)\}|[^`\\])*`/g,
|
|
4793
|
+
(whole) => [...whole.matchAll(/\$\{([^}]*)\}/g)].map((match) => ` ${match[1]} `).join("")
|
|
4794
|
+
);
|
|
4795
|
+
for (const match of code.matchAll(/(?<![.\w$])[A-Za-z_$][\w$]*/g)) {
|
|
4796
|
+
const name = match[0];
|
|
4797
|
+
const after = code.slice(match.index + name.length);
|
|
4798
|
+
if (/^\s*:(?!:)/.test(after) && /[{,]\s*$/.test(code.slice(0, match.index))) continue;
|
|
4799
|
+
if (!KEYWORDS.has(name)) into.add(name);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
var WHOLE = /^\s*\{\{([\s\S]+?)\}\}\s*$/;
|
|
4803
|
+
var PARTS = /\{\{([\s\S]+?)\}\}/g;
|
|
4804
|
+
var RENAME = {
|
|
4805
|
+
class: "className",
|
|
4806
|
+
for: "htmlFor",
|
|
4807
|
+
tabindex: "tabIndex",
|
|
4808
|
+
readonly: "readOnly",
|
|
4809
|
+
maxlength: "maxLength",
|
|
4810
|
+
minlength: "minLength",
|
|
4811
|
+
colspan: "colSpan",
|
|
4812
|
+
rowspan: "rowSpan",
|
|
4813
|
+
autocomplete: "autoComplete",
|
|
4814
|
+
autocapitalize: "autoCapitalize",
|
|
4815
|
+
autocorrect: "autoCorrect",
|
|
4816
|
+
autosave: "autoSave",
|
|
4817
|
+
enterkeyhint: "enterKeyHint",
|
|
4818
|
+
accesskey: "accessKey",
|
|
4819
|
+
charset: "charSet",
|
|
4820
|
+
formaction: "formAction",
|
|
4821
|
+
formmethod: "formMethod",
|
|
4822
|
+
formnovalidate: "formNoValidate",
|
|
4823
|
+
formtarget: "formTarget",
|
|
4824
|
+
hreflang: "hrefLang",
|
|
4825
|
+
itemprop: "itemProp",
|
|
4826
|
+
itemscope: "itemScope",
|
|
4827
|
+
itemtype: "itemType",
|
|
4828
|
+
marginheight: "marginHeight",
|
|
4829
|
+
marginwidth: "marginWidth",
|
|
4830
|
+
mediagroup: "mediaGroup",
|
|
4831
|
+
nomodule: "noModule",
|
|
4832
|
+
radiogroup: "radioGroup",
|
|
4833
|
+
srcdoc: "srcDoc",
|
|
4834
|
+
srclang: "srcLang",
|
|
4835
|
+
popovertarget: "popoverTarget",
|
|
4836
|
+
popovertargetaction: "popoverTargetAction",
|
|
4837
|
+
autofocus: "autoFocus",
|
|
4838
|
+
autoplay: "autoPlay",
|
|
4839
|
+
crossorigin: "crossOrigin",
|
|
4840
|
+
srcset: "srcSet",
|
|
4841
|
+
enctype: "encType",
|
|
4842
|
+
novalidate: "noValidate",
|
|
4843
|
+
contenteditable: "contentEditable",
|
|
4844
|
+
spellcheck: "spellCheck",
|
|
4845
|
+
datetime: "dateTime",
|
|
4846
|
+
allowfullscreen: "allowFullScreen",
|
|
4847
|
+
frameborder: "frameBorder",
|
|
4848
|
+
cellpadding: "cellPadding",
|
|
4849
|
+
cellspacing: "cellSpacing",
|
|
4850
|
+
inputmode: "inputMode",
|
|
4851
|
+
usemap: "useMap",
|
|
4852
|
+
referrerpolicy: "referrerPolicy",
|
|
4853
|
+
playsinline: "playsInline",
|
|
4854
|
+
viewbox: "viewBox",
|
|
4855
|
+
"accept-charset": "acceptCharset",
|
|
4856
|
+
"http-equiv": "httpEquiv",
|
|
4857
|
+
"xlink:href": "xlinkHref",
|
|
4858
|
+
"xmlns:xlink": "xmlnsXlink",
|
|
4859
|
+
"xml:space": "xmlSpace",
|
|
4860
|
+
"xml:lang": "xmlLang"
|
|
4861
|
+
};
|
|
4862
|
+
var BOOLEAN = /* @__PURE__ */ new Set([
|
|
4863
|
+
"checked",
|
|
4864
|
+
"disabled",
|
|
4865
|
+
"selected",
|
|
4866
|
+
"readonly",
|
|
4867
|
+
"hidden",
|
|
4868
|
+
"open",
|
|
4869
|
+
"required",
|
|
4870
|
+
"multiple",
|
|
4871
|
+
"autofocus",
|
|
4872
|
+
"novalidate",
|
|
4873
|
+
"playsinline",
|
|
4874
|
+
"muted",
|
|
4875
|
+
"loop",
|
|
4876
|
+
"autoplay",
|
|
4877
|
+
"controls",
|
|
4878
|
+
"default",
|
|
4879
|
+
"reversed",
|
|
4880
|
+
"allowfullscreen",
|
|
4881
|
+
"async",
|
|
4882
|
+
"defer",
|
|
4883
|
+
"inert"
|
|
4884
|
+
]);
|
|
4885
|
+
var NO_TEXT = /* @__PURE__ */ new Set(["table", "thead", "tbody", "tfoot", "tr", "colgroup", "select", "optgroup", "datalist"]);
|
|
4886
|
+
function propName(name) {
|
|
4887
|
+
const lower = name.toLowerCase();
|
|
4888
|
+
if (RENAME[lower]) return RENAME[lower];
|
|
4889
|
+
if (/^on[A-Z]/.test(name)) return name;
|
|
4890
|
+
if (lower.startsWith("aria-") || lower.startsWith("data-")) return lower;
|
|
4891
|
+
if (!/^[A-Za-z][\w:-]*$/.test(name)) return null;
|
|
4892
|
+
return name.includes("-") ? name.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) : name;
|
|
4893
|
+
}
|
|
4894
|
+
var str2 = (value) => JSON.stringify(value);
|
|
4895
|
+
function interpolated(raw, reads) {
|
|
4896
|
+
if (!raw.includes("{{")) return str2(decodeEntities(raw));
|
|
4897
|
+
let out = "`";
|
|
4898
|
+
let last = 0;
|
|
4899
|
+
for (const match of raw.matchAll(PARTS)) {
|
|
4900
|
+
out += decodeEntities(raw.slice(last, match.index)).replace(/[`\\]|\$\{/g, (c) => `\\${c}`);
|
|
4901
|
+
freeNames(match[1], reads);
|
|
4902
|
+
out += "${dc.text(" + match[1].trim() + ")}";
|
|
4903
|
+
last = match.index + match[0].length;
|
|
4904
|
+
}
|
|
4905
|
+
return `${out + decodeEntities(raw.slice(last)).replace(/[`\\]|\$\{/g, (c) => `\\${c}`)}\``;
|
|
4906
|
+
}
|
|
4907
|
+
function linkTarget(value, ctx) {
|
|
4908
|
+
const match = /^(?:\.\/)?([^/?#]+)\.dc\.html(#.*)?$/.exec(value);
|
|
4909
|
+
if (!match) return null;
|
|
4910
|
+
const route2 = ctx.routes.get(match[1]);
|
|
4911
|
+
if (route2) return route2;
|
|
4912
|
+
ctx.warn(`links to ${match[1]}${EXT}, which is no board; the link goes nowhere (#)`);
|
|
4913
|
+
return "#";
|
|
4914
|
+
}
|
|
4915
|
+
function attrsJsx(element, ctx, extra = []) {
|
|
4916
|
+
const tag = element.tag.toLowerCase();
|
|
4917
|
+
const handled = new Set(element.attrs.map(([name]) => name));
|
|
4918
|
+
const hasHandler = [...handled].some((name) => /^on(Change|Input)$/.test(name));
|
|
4919
|
+
const out = [...extra];
|
|
4920
|
+
for (const [name, raw] of element.attrs) {
|
|
4921
|
+
const lower = name.toLowerCase();
|
|
4922
|
+
if (lower.startsWith("hint-") || lower === "xmlns") continue;
|
|
4923
|
+
let prop = propName(name);
|
|
4924
|
+
if (!prop) {
|
|
4925
|
+
ctx.warn(`dropped the attribute "${name}"`);
|
|
4926
|
+
continue;
|
|
4927
|
+
}
|
|
4928
|
+
if (!hasHandler && (tag === "input" || tag === "textarea") && (prop === "value" || prop === "checked"))
|
|
4929
|
+
prop = prop === "value" ? "defaultValue" : "defaultChecked";
|
|
4930
|
+
if (tag === "option" && prop === "selected") continue;
|
|
4931
|
+
if (raw === null) {
|
|
4932
|
+
out.push(BOOLEAN.has(lower) ? prop : `${prop}=""`);
|
|
4933
|
+
continue;
|
|
4934
|
+
}
|
|
4935
|
+
const whole = WHOLE.exec(raw);
|
|
4936
|
+
if (prop === "style") {
|
|
4937
|
+
const body = interpolated(raw, ctx.reads);
|
|
4938
|
+
out.push(`style={dc.css(${body})}`);
|
|
4939
|
+
continue;
|
|
4940
|
+
}
|
|
4941
|
+
if (whole) {
|
|
4942
|
+
const expression = whole[1].trim();
|
|
4943
|
+
freeNames(expression, ctx.reads);
|
|
4944
|
+
if (/^on[A-Z]/.test(prop)) out.push(`${prop}={${expression}}`);
|
|
4945
|
+
else if (BOOLEAN.has(lower)) out.push(`${prop}={dc.flag(${expression})}`);
|
|
4946
|
+
else out.push(`${prop}={dc.value(${expression})}`);
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
if (/^on[a-z]/.test(name)) {
|
|
4950
|
+
ctx.warn(`dropped the inline handler ${name}="\u2026"; handlers come from renderVals()`);
|
|
4951
|
+
continue;
|
|
4952
|
+
}
|
|
4953
|
+
if (BOOLEAN.has(lower)) {
|
|
4954
|
+
out.push(prop);
|
|
4955
|
+
continue;
|
|
4956
|
+
}
|
|
4957
|
+
if (lower === "href" && !raw.includes("{{")) {
|
|
4958
|
+
const target = linkTarget(raw, ctx);
|
|
4959
|
+
if (target !== null) {
|
|
4960
|
+
out.push(`href=${str2(target)}`);
|
|
4961
|
+
continue;
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
if (raw.includes("/_blob/"))
|
|
4965
|
+
ctx.warn("uses an uploaded Claude Design asset (/_blob/\u2026) that the export does not contain");
|
|
4966
|
+
out.push(`${prop}={${interpolated(raw, ctx.reads)}}`);
|
|
4967
|
+
}
|
|
4968
|
+
return out.length ? ` ${out.join(" ")}` : "";
|
|
4969
|
+
}
|
|
4970
|
+
function selectedOption(select) {
|
|
4971
|
+
const option = findElement(select, (el) => el.tag.toLowerCase() === "option" && attr(el, "selected") !== void 0);
|
|
4972
|
+
if (!option) return null;
|
|
4973
|
+
const value = attr(option, "value");
|
|
4974
|
+
if (value !== void 0 && value !== null) return value;
|
|
4975
|
+
return option.children.map((child) => child.type === "text" ? child.text : "").join("").trim();
|
|
4976
|
+
}
|
|
4977
|
+
function childrenJsx(nodes, ctx, parent) {
|
|
4978
|
+
const out = [];
|
|
4979
|
+
for (const node of nodes) {
|
|
4980
|
+
if (node.type === "text") {
|
|
4981
|
+
if (!node.text.trim()) {
|
|
4982
|
+
if (node.text && !NO_TEXT.has(parent)) out.push('{" "}');
|
|
4983
|
+
continue;
|
|
4984
|
+
}
|
|
4985
|
+
if (NO_TEXT.has(parent)) continue;
|
|
4986
|
+
let last = 0;
|
|
4987
|
+
for (const match of node.text.matchAll(PARTS)) {
|
|
4988
|
+
const before = node.text.slice(last, match.index);
|
|
4989
|
+
if (before) out.push(`{${str2(decodeEntities(before))}}`);
|
|
4990
|
+
freeNames(match[1], ctx.reads);
|
|
4991
|
+
out.push(`{dc.text(${match[1].trim()})}`);
|
|
4992
|
+
last = match.index + match[0].length;
|
|
4993
|
+
}
|
|
4994
|
+
const rest = node.text.slice(last);
|
|
4995
|
+
if (rest) out.push(`{${str2(decodeEntities(rest))}}`);
|
|
4996
|
+
continue;
|
|
4997
|
+
}
|
|
4998
|
+
out.push(elementJsx(node, ctx));
|
|
4999
|
+
}
|
|
5000
|
+
while (out[0] === '{" "}') out.shift();
|
|
5001
|
+
while (out[out.length - 1] === '{" "}') out.pop();
|
|
5002
|
+
return out.join("");
|
|
5003
|
+
}
|
|
5004
|
+
function elementJsx(element, ctx) {
|
|
5005
|
+
const tag = element.tag;
|
|
5006
|
+
const lower = tag.toLowerCase();
|
|
5007
|
+
if (lower === "sc-for") {
|
|
5008
|
+
const list = attr(element, "list") ?? "[]";
|
|
5009
|
+
const as = attr(element, "as") || "item";
|
|
5010
|
+
const expression = (WHOLE.exec(list)?.[1] ?? list).trim();
|
|
5011
|
+
freeNames(expression, ctx.reads);
|
|
5012
|
+
const body = childrenJsx(element.children, ctx, lower);
|
|
5013
|
+
return `{dc.list(${expression}).map((${identifier(as)}, index) => (<dc.Fragment key={index}>${body}</dc.Fragment>))}`;
|
|
5014
|
+
}
|
|
5015
|
+
if (lower === "sc-if") {
|
|
5016
|
+
const value = attr(element, "value") ?? "false";
|
|
5017
|
+
const expression = (WHOLE.exec(value)?.[1] ?? value).trim();
|
|
5018
|
+
freeNames(expression, ctx.reads);
|
|
5019
|
+
return `{dc.flag(${expression}) ? (<>${childrenJsx(element.children, ctx, lower)}</>) : null}`;
|
|
5020
|
+
}
|
|
5021
|
+
if (lower === "dc-import") {
|
|
5022
|
+
const name = attr(element, "name") ?? "";
|
|
5023
|
+
if (!ctx.known.has(name)) {
|
|
5024
|
+
ctx.warn(`imports ${name}${EXT}, which is not in the export`);
|
|
5025
|
+
return "";
|
|
5026
|
+
}
|
|
5027
|
+
const component = `DC_${identifier(name)}`;
|
|
5028
|
+
ctx.imports.add(name);
|
|
5029
|
+
const props = element.attrs.filter(([key]) => key !== "name" && !key.startsWith("hint-")).map(([key, raw]) => {
|
|
5030
|
+
if (raw === null) return `${str2(key)}: true`;
|
|
5031
|
+
const whole = WHOLE.exec(raw);
|
|
5032
|
+
if (whole) {
|
|
5033
|
+
freeNames(whole[1], ctx.reads);
|
|
5034
|
+
return `${str2(key)}: (${whole[1].trim()})`;
|
|
5035
|
+
}
|
|
5036
|
+
return `${str2(key)}: ${interpolated(raw, ctx.reads)}`;
|
|
5037
|
+
});
|
|
5038
|
+
return `<${component} {...{${props.join(", ")}}} />`;
|
|
5039
|
+
}
|
|
5040
|
+
if (lower === "script") {
|
|
5041
|
+
ctx.warn("dropped a <script> inside the template");
|
|
5042
|
+
return "";
|
|
5043
|
+
}
|
|
5044
|
+
if (lower === "title" || lower === "helmet") return "";
|
|
5045
|
+
if (lower === "style") {
|
|
5046
|
+
const css2 = element.children.map((child) => child.type === "text" ? child.text : "").join("");
|
|
5047
|
+
return `<style>{${interpolated(css2, ctx.reads)}}</style>`;
|
|
5048
|
+
}
|
|
5049
|
+
if (lower === "textarea") {
|
|
5050
|
+
const text2 = element.children.map((child) => child.type === "text" ? child.text : "").join("");
|
|
5051
|
+
const extra2 = text2 ? [`defaultValue={${interpolated(text2, ctx.reads)}}`] : [];
|
|
5052
|
+
return `<textarea${attrsJsx(element, ctx, extra2)} />`;
|
|
5053
|
+
}
|
|
5054
|
+
const extra = [];
|
|
5055
|
+
if (lower === "select") {
|
|
5056
|
+
const selected = selectedOption(element);
|
|
5057
|
+
if (selected !== null && attr(element, "value") === void 0)
|
|
5058
|
+
extra.push(`defaultValue={${interpolated(selected, ctx.reads)}}`);
|
|
5059
|
+
}
|
|
5060
|
+
const attrs = attrsJsx(element, ctx, extra);
|
|
5061
|
+
const children = childrenJsx(element.children, ctx, lower);
|
|
5062
|
+
return children ? `<${tag}${attrs}>${children}</${tag}>` : `<${tag}${attrs} />`;
|
|
5063
|
+
}
|
|
5064
|
+
function convertFile(name, source, ctx) {
|
|
5065
|
+
const document2 = parseMarkup(source);
|
|
5066
|
+
const root = findElement(document2, (el) => el.tag.toLowerCase() === "x-dc");
|
|
5067
|
+
const script = findElement(
|
|
5068
|
+
document2,
|
|
5069
|
+
(el) => el.tag.toLowerCase() === "script" && attr(el, "data-dc-script") !== void 0
|
|
5070
|
+
);
|
|
5071
|
+
const file = { ...ctx, name, reads: /* @__PURE__ */ new Set(), imports: /* @__PURE__ */ new Set() };
|
|
5072
|
+
if (!root) throw new Error("no <x-dc> element");
|
|
5073
|
+
const helmet = /<helmet>([\s\S]*?)<\/helmet>/i.exec(source)?.[1]?.trim() ?? "";
|
|
5074
|
+
const body = childrenJsx(root.children, file, "x-dc");
|
|
5075
|
+
let spec = {};
|
|
5076
|
+
const rawSpec = script ? attr(script, "data-props") : null;
|
|
5077
|
+
if (rawSpec) {
|
|
5078
|
+
try {
|
|
5079
|
+
spec = JSON.parse(decodeEntities(rawSpec));
|
|
5080
|
+
} catch {
|
|
5081
|
+
file.warn("has data-props that are not JSON; the props keep no defaults");
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
const code = script?.children.map((child) => child.type === "text" ? child.text : "").join("") ?? "";
|
|
5085
|
+
const className = /class\s+([A-Za-z_$][\w$]*)\s+extends\s+DCLogic/.exec(code)?.[1];
|
|
5086
|
+
const logic = className ? `const __Logic = (() => {
|
|
5087
|
+
${code.trim()}
|
|
5088
|
+
return ${className}
|
|
5089
|
+
})()` : "const __Logic = null";
|
|
5090
|
+
const reads = [...file.reads].filter((read) => !read.startsWith("__") && read !== "dc").sort();
|
|
5091
|
+
const lines = [
|
|
5092
|
+
`// Imported from Claude Design (${name}${EXT}).`,
|
|
5093
|
+
`import * as dc from './dc'`,
|
|
5094
|
+
...[...file.imports].sort().map((dep) => `import DC_${identifier(dep)} from './${dep}'`),
|
|
5095
|
+
`const DCLogic = dc.DCLogic`,
|
|
5096
|
+
"",
|
|
5097
|
+
helmet ? `dc.head(${str2(helmet)})
|
|
5098
|
+
` : "",
|
|
5099
|
+
`const __spec = ${JSON.stringify(spec)}`,
|
|
5100
|
+
"",
|
|
5101
|
+
logic,
|
|
5102
|
+
"",
|
|
5103
|
+
`export default function ${identifier(name)}(__props) {`,
|
|
5104
|
+
` const __scope = dc.useLogic(__Logic, __spec, __props)`,
|
|
5105
|
+
reads.length ? ` const { ${reads.join(", ")} } = __scope` : "",
|
|
5106
|
+
` return (<>${body}</>)`,
|
|
5107
|
+
"}",
|
|
5108
|
+
""
|
|
5109
|
+
];
|
|
5110
|
+
return lines.filter((line, index) => line !== "" || lines[index - 1] !== "").join("\n");
|
|
5111
|
+
}
|
|
5112
|
+
var DC_HELPERS = `// Imported from Claude Design: what the converted screens need. This file is the canvas's own.
|
|
5113
|
+
import { Fragment, useReducer, useRef } from 'react'
|
|
5114
|
+
import { useTheme } from '@design/runtime'
|
|
5115
|
+
|
|
5116
|
+
export { Fragment }
|
|
5117
|
+
|
|
5118
|
+
/** The base of each screen's logic class: props, state and setState, as Claude Design had them. */
|
|
5119
|
+
export class DCLogic {
|
|
5120
|
+
constructor(props) {
|
|
5121
|
+
this.props = props
|
|
5122
|
+
this.state = {}
|
|
5123
|
+
}
|
|
5124
|
+
setState(patch) {
|
|
5125
|
+
const next = typeof patch === 'function' ? patch(this.state, this.props) : patch
|
|
5126
|
+
this.state = { ...this.state, ...next }
|
|
5127
|
+
this.__update?.()
|
|
5128
|
+
}
|
|
5129
|
+
renderVals() {
|
|
5130
|
+
return {}
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
const themed = (spec) => Array.isArray(spec?.theme?.options) && spec.theme.options.includes('dark')
|
|
5135
|
+
|
|
5136
|
+
function convert(field, value) {
|
|
5137
|
+
if (!field || typeof value !== 'string') return value
|
|
5138
|
+
if (field.editor === 'int' || field.editor === 'number') return Number(value)
|
|
5139
|
+
if (field.editor === 'bool' || field.editor === 'boolean') return value === 'true' || value === ''
|
|
5140
|
+
return value
|
|
5141
|
+
}
|
|
5142
|
+
|
|
5143
|
+
/** Props (defaults, then what the canvas or a parent passed), the logic's values, for the template. */
|
|
5144
|
+
export function useLogic(Logic, spec, given) {
|
|
5145
|
+
const theme = useTheme()
|
|
5146
|
+
const [, update] = useReducer((n) => n + 1, 0)
|
|
5147
|
+
const props = {}
|
|
5148
|
+
for (const [key, field] of Object.entries(spec ?? {}))
|
|
5149
|
+
if (!key.startsWith('$') && field && typeof field === 'object' && 'default' in field) props[key] = field.default
|
|
5150
|
+
for (const [key, value] of Object.entries(given ?? {})) props[key] = convert(spec?.[key], value)
|
|
5151
|
+
// A theme prop follows the viewer's light/dark switch unless the canvas pins it.
|
|
5152
|
+
if (themed(spec) && given?.theme === undefined) props.theme = theme
|
|
5153
|
+
const logic = useRef(null)
|
|
5154
|
+
if (Logic && !logic.current) logic.current = new Logic(props)
|
|
5155
|
+
let vals = {}
|
|
5156
|
+
if (logic.current) {
|
|
5157
|
+
logic.current.props = props
|
|
5158
|
+
logic.current.__update = update
|
|
5159
|
+
vals = logic.current.renderVals?.() ?? {}
|
|
5160
|
+
}
|
|
5161
|
+
return { props, ...props, ...vals }
|
|
5162
|
+
}
|
|
5163
|
+
|
|
5164
|
+
const heads = new Set()
|
|
5165
|
+
/** The template's <helmet>: fonts and styles, added to the page once. */
|
|
5166
|
+
export function head(html) {
|
|
5167
|
+
if (heads.has(html) || typeof document === 'undefined') return
|
|
5168
|
+
heads.add(html)
|
|
5169
|
+
document.head.insertAdjacentHTML('beforeend', html)
|
|
5170
|
+
}
|
|
5171
|
+
|
|
5172
|
+
/** An inline style string as a React style object. */
|
|
5173
|
+
export function css(text) {
|
|
5174
|
+
const style = {}
|
|
5175
|
+
let depth = 0
|
|
5176
|
+
let quote = ''
|
|
5177
|
+
let start = 0
|
|
5178
|
+
const declarations = []
|
|
5179
|
+
for (let i = 0; i <= text.length; i++) {
|
|
5180
|
+
const c = text[i]
|
|
5181
|
+
if (quote) {
|
|
5182
|
+
if (c === quote) quote = ''
|
|
5183
|
+
} else if (c === '"' || c === "'") quote = c
|
|
5184
|
+
else if (c === '(') depth++
|
|
5185
|
+
else if (c === ')') depth--
|
|
5186
|
+
else if ((c === ';' && depth === 0) || i === text.length) {
|
|
5187
|
+
declarations.push(text.slice(start, i))
|
|
5188
|
+
start = i + 1
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
for (const declaration of declarations) {
|
|
5192
|
+
const colon = declaration.indexOf(':')
|
|
5193
|
+
if (colon === -1) continue
|
|
5194
|
+
const name = declaration.slice(0, colon).trim()
|
|
5195
|
+
const value = declaration.slice(colon + 1).trim()
|
|
5196
|
+
if (!name || !value) continue
|
|
5197
|
+
const key = name.startsWith('--') ? name : name.toLowerCase().replace(/^-ms-/, 'ms-').replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
5198
|
+
style[key] = value
|
|
5199
|
+
}
|
|
5200
|
+
return style
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
/** A template value in text: nothing for null, undefined and false. */
|
|
5204
|
+
export const text = (value) => (value == null || value === false ? '' : value)
|
|
5205
|
+
/** An attribute value: left out for null, undefined and false. */
|
|
5206
|
+
export const value = (v) => (v == null || v === false ? undefined : v)
|
|
5207
|
+
/** A boolean attribute or condition. */
|
|
5208
|
+
export const flag = (v) => v === true || v === 'true' || (!!v && v !== 'false')
|
|
5209
|
+
/** What a loop runs over. */
|
|
5210
|
+
export const list = (v) => (Array.isArray(v) ? v : v == null ? [] : Array.from(v))
|
|
5211
|
+
`;
|
|
5212
|
+
function boardId(file, used) {
|
|
5213
|
+
let base = kebab(file.slice(0, -EXT.length)) || "screen";
|
|
5214
|
+
if (!ID_PATTERN.test(base)) base = `s-${base}`;
|
|
5215
|
+
let id2 = base;
|
|
5216
|
+
for (let n2 = 2; used.has(id2); n2++) id2 = `${base}-${n2}`;
|
|
5217
|
+
used.add(id2);
|
|
5218
|
+
return id2;
|
|
5219
|
+
}
|
|
5220
|
+
function importClaudeDesign(dir, options = {}) {
|
|
5221
|
+
const canvasFile = path23.join(dir, "canvas.json");
|
|
5222
|
+
if (!fs25.existsSync(canvasFile))
|
|
5223
|
+
throw new Error(`No canvas.json in ${dir}: point at the exported Claude Design project folder`);
|
|
5224
|
+
const source = JSON.parse(fs25.readFileSync(canvasFile, "utf8"));
|
|
5225
|
+
const templates = fs25.readdirSync(dir).filter((file) => file.endsWith(EXT));
|
|
5226
|
+
if (!templates.length) throw new Error(`No ${EXT} files in ${dir}`);
|
|
5227
|
+
const known = new Set(templates.map((file) => file.slice(0, -EXT.length)));
|
|
5228
|
+
const warnings = /* @__PURE__ */ new Map();
|
|
5229
|
+
const boards = source.boards ?? {};
|
|
5230
|
+
const order = [...(source.order ?? []).filter((file) => file in boards), ...Object.keys(boards)];
|
|
5231
|
+
const used = /* @__PURE__ */ new Set();
|
|
5232
|
+
const ids = /* @__PURE__ */ new Map();
|
|
5233
|
+
const routes = /* @__PURE__ */ new Map();
|
|
5234
|
+
for (const file of new Set(order)) {
|
|
5235
|
+
if (!known.has(file.slice(0, -EXT.length))) continue;
|
|
5236
|
+
const id2 = boardId(file, used);
|
|
5237
|
+
ids.set(file, id2);
|
|
5238
|
+
routes.set(file.slice(0, -EXT.length), `/${id2}`);
|
|
5239
|
+
}
|
|
5240
|
+
const files = /* @__PURE__ */ new Map();
|
|
5241
|
+
files.set("dc.jsx", DC_HELPERS);
|
|
5242
|
+
for (const file of templates) {
|
|
5243
|
+
const name = file.slice(0, -EXT.length);
|
|
5244
|
+
const warn2 = (message) => {
|
|
5245
|
+
const set = warnings.get(message) ?? /* @__PURE__ */ new Set();
|
|
5246
|
+
set.add(file);
|
|
5247
|
+
warnings.set(message, set);
|
|
5248
|
+
};
|
|
5249
|
+
try {
|
|
5250
|
+
files.set(
|
|
5251
|
+
`${name}.jsx`,
|
|
5252
|
+
convertFile(name, fs25.readFileSync(path23.join(dir, file), "utf8"), { known, routes, warn: warn2 })
|
|
5253
|
+
);
|
|
5254
|
+
} catch (error) {
|
|
5255
|
+
warn2(`could not be converted: ${error.message}`);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
const pageList = source.pages?.length ? source.pages : [{ id: "main", name: source.title ?? "Main" }];
|
|
5259
|
+
const pages = pageList.map((page, index) => ({
|
|
5260
|
+
id: ID_PATTERN.test(page.id) ? page.id : `page-${index + 1}`,
|
|
5261
|
+
title: page.name || page.id,
|
|
5262
|
+
items: []
|
|
5263
|
+
}));
|
|
5264
|
+
const pageOf = (id2) => pages[Math.max(
|
|
5265
|
+
0,
|
|
5266
|
+
pageList.findIndex((page) => page.id === id2)
|
|
5267
|
+
)];
|
|
5268
|
+
let notes = 0;
|
|
5269
|
+
for (const [key, note] of Object.entries(source.notes ?? {})) {
|
|
5270
|
+
if (!note.text) continue;
|
|
5271
|
+
const heading = note.kind === "title1" ? "# " : note.kind === "title2" ? "## " : note.kind === "title3" ? "### " : "";
|
|
5272
|
+
pageOf(note.page).items.push({
|
|
5273
|
+
id: `note-${boardId(`${key}${EXT}`, used)}`,
|
|
5274
|
+
type: "note",
|
|
5275
|
+
tone: heading ? "plain" : "note",
|
|
5276
|
+
text: `${heading}${note.text}`,
|
|
5277
|
+
width: Math.round(Math.min(Math.max(note.maxW ?? note.w ?? 480, 80), 4e3)),
|
|
5278
|
+
x: note.x ?? 0,
|
|
5279
|
+
y: note.y ?? 0
|
|
5280
|
+
});
|
|
5281
|
+
notes++;
|
|
5282
|
+
}
|
|
5283
|
+
let screens = 0;
|
|
5284
|
+
for (const [file, id2] of ids) {
|
|
5285
|
+
const board = boards[file];
|
|
5286
|
+
if (!files.has(`${file.slice(0, -EXT.length)}.jsx`)) continue;
|
|
5287
|
+
pageOf(board.page).items.push({
|
|
5288
|
+
id: id2,
|
|
5289
|
+
src: `screens/${file.slice(0, -EXT.length)}.jsx`,
|
|
5290
|
+
title: board.title || file.slice(0, -EXT.length),
|
|
5291
|
+
route: routes.get(file.slice(0, -EXT.length)),
|
|
5292
|
+
width: Math.round(Math.min(Math.max(board.w ?? 1440, 80), 8e3)),
|
|
5293
|
+
height: Math.round(Math.min(Math.max(board.h ?? 900, 80), 4e4)),
|
|
5294
|
+
x: board.x ?? 0,
|
|
5295
|
+
y: board.y ?? 0
|
|
5296
|
+
});
|
|
5297
|
+
screens++;
|
|
5298
|
+
}
|
|
5299
|
+
const canvas = {
|
|
5300
|
+
title: options.title ?? source.title ?? path23.basename(path23.resolve(dir)),
|
|
5301
|
+
description: "Imported from Claude Design.",
|
|
5302
|
+
// The templates bring their own styles; the design system's reset would restyle them.
|
|
5303
|
+
system: false,
|
|
5304
|
+
pages: pages.filter((page) => page.items.length)
|
|
5305
|
+
};
|
|
5306
|
+
return { canvas, files, screens, notes, warnings };
|
|
5307
|
+
}
|
|
5308
|
+
|
|
5309
|
+
// src/cli/commands/import.ts
|
|
5310
|
+
var slug = (text2) => text2.normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-").toLowerCase();
|
|
5311
|
+
function runImport(paths, from, options) {
|
|
5312
|
+
const dir = path24.resolve(from);
|
|
5313
|
+
if (!fs26.existsSync(dir) || !fs26.statSync(dir).isDirectory()) throw new CliError(`No folder ${from}`);
|
|
5314
|
+
let result;
|
|
5315
|
+
try {
|
|
5316
|
+
result = importClaudeDesign(dir, { title: options.title });
|
|
5317
|
+
} catch (error) {
|
|
5318
|
+
throw new CliError(error.message);
|
|
5319
|
+
}
|
|
5320
|
+
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(path24.basename(dir)) || "imported");
|
|
5321
|
+
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is no canvas id; pass one with --canvas <id>`);
|
|
5322
|
+
const out = path24.join(paths.canvases, id2);
|
|
5323
|
+
if (fs26.existsSync(out)) throw new CliError(`.design/canvas/${id2} exists; pick another id with --canvas <id>`);
|
|
5324
|
+
fs26.mkdirSync(path24.join(out, "screens"), { recursive: true });
|
|
5325
|
+
for (const [name, content] of result.files) fs26.writeFileSync(path24.join(out, "screens", name), content);
|
|
5326
|
+
fs26.writeFileSync(path24.join(out, "canvas.json"), `${JSON.stringify(result.canvas, null, 2)}
|
|
5327
|
+
`);
|
|
5328
|
+
print(`${green("Imported")} ${bold(String(result.canvas.title))} \u2192 .design/canvas/${id2}`);
|
|
5329
|
+
print(
|
|
5330
|
+
` ${result.screens} screens and ${result.notes} notes on ${result.canvas.pages.length} pages; ${result.files.size - 1} templates as React modules in screens/, helpers in screens/dc.jsx`
|
|
5331
|
+
);
|
|
5332
|
+
if (result.warnings.size) {
|
|
5333
|
+
print();
|
|
5334
|
+
for (const [message, files] of result.warnings) {
|
|
5335
|
+
const list = [...files];
|
|
5336
|
+
print(`${yellow("warning")} ${message}`);
|
|
5337
|
+
print(` ${dim(`${list.slice(0, 6).join(", ")}${list.length > 6 ? ` and ${list.length - 6} more` : ""}`)}`);
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
print();
|
|
5341
|
+
print(`Next: \`design check ${id2} --render\`, then \`design preview --open /c/${id2}\`.`);
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5344
|
+
// src/cli/commands/scaffold.ts
|
|
5345
|
+
import fs27 from "fs";
|
|
5346
|
+
import path25 from "path";
|
|
5347
|
+
|
|
4594
5348
|
// src/cli/templates.ts
|
|
4595
5349
|
var TOKENS_CSS = `/*
|
|
4596
5350
|
* Design tokens. Every screen gets Tailwind v4 with these as utilities:
|
|
@@ -4800,40 +5554,40 @@ function mainScreen(canvasId) {
|
|
|
4800
5554
|
|
|
4801
5555
|
// src/cli/commands/scaffold.ts
|
|
4802
5556
|
function write(file, content, root) {
|
|
4803
|
-
if (
|
|
4804
|
-
print(` ${dim("exists ")} ${
|
|
5557
|
+
if (fs27.existsSync(file)) {
|
|
5558
|
+
print(` ${dim("exists ")} ${path25.relative(root, file)}`);
|
|
4805
5559
|
return false;
|
|
4806
5560
|
}
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
print(` ${green("created")} ${
|
|
5561
|
+
fs27.mkdirSync(path25.dirname(file), { recursive: true });
|
|
5562
|
+
fs27.writeFileSync(file, content);
|
|
5563
|
+
print(` ${green("created")} ${path25.relative(root, file)}`);
|
|
4810
5564
|
return true;
|
|
4811
5565
|
}
|
|
4812
5566
|
function ensureGitignored(root) {
|
|
4813
|
-
const file =
|
|
4814
|
-
const current =
|
|
5567
|
+
const file = path25.join(root, ".gitignore");
|
|
5568
|
+
const current = fs27.existsSync(file) ? fs27.readFileSync(file, "utf8") : "";
|
|
4815
5569
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
4816
5570
|
if (covered) return "present";
|
|
4817
5571
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
4818
|
-
|
|
5572
|
+
fs27.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
4819
5573
|
.design/
|
|
4820
5574
|
`);
|
|
4821
5575
|
return "added";
|
|
4822
5576
|
}
|
|
4823
5577
|
function projectName(root) {
|
|
4824
5578
|
try {
|
|
4825
|
-
const manifest = JSON.parse(
|
|
5579
|
+
const manifest = JSON.parse(fs27.readFileSync(path25.join(root, "package.json"), "utf8"));
|
|
4826
5580
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
4827
5581
|
} catch {
|
|
4828
5582
|
}
|
|
4829
|
-
return
|
|
5583
|
+
return path25.basename(root);
|
|
4830
5584
|
}
|
|
4831
5585
|
function runInit(root, options) {
|
|
4832
5586
|
const paths = designPaths(root);
|
|
4833
5587
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
4834
5588
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
4835
5589
|
`, root);
|
|
4836
|
-
|
|
5590
|
+
fs27.mkdirSync(paths.canvases, { recursive: true });
|
|
4837
5591
|
if (options.gitignore) {
|
|
4838
5592
|
const result = ensureGitignored(root);
|
|
4839
5593
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
@@ -4845,44 +5599,44 @@ function runSystemInit(root, options) {
|
|
|
4845
5599
|
const paths = designPaths(root);
|
|
4846
5600
|
let base = projectName(root);
|
|
4847
5601
|
try {
|
|
4848
|
-
base = JSON.parse(
|
|
5602
|
+
base = JSON.parse(fs27.readFileSync(paths.config, "utf8")).name || base;
|
|
4849
5603
|
} catch {
|
|
4850
5604
|
}
|
|
4851
5605
|
const name = options.name ?? `${base} design system`;
|
|
4852
|
-
print(`Scaffolding the design system in ${
|
|
4853
|
-
write(
|
|
5606
|
+
print(`Scaffolding the design system in ${path25.relative(root, paths.system)}`);
|
|
5607
|
+
write(path25.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
4854
5608
|
`, root);
|
|
4855
|
-
write(
|
|
5609
|
+
write(path25.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
4856
5610
|
if (!options.empty) {
|
|
4857
|
-
write(
|
|
4858
|
-
write(
|
|
4859
|
-
write(
|
|
5611
|
+
write(path25.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
5612
|
+
write(path25.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
5613
|
+
write(path25.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
4860
5614
|
}
|
|
4861
5615
|
}
|
|
4862
5616
|
function runNew(root, id2, options) {
|
|
4863
5617
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
4864
5618
|
const paths = designPaths(root);
|
|
4865
|
-
const dir =
|
|
4866
|
-
if (
|
|
5619
|
+
const dir = path25.join(paths.canvases, id2);
|
|
5620
|
+
if (fs27.existsSync(path25.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
4867
5621
|
print(`Creating canvas "${id2}"`);
|
|
4868
5622
|
const title = options.title ?? titleize(id2);
|
|
4869
5623
|
if (options.empty) {
|
|
4870
5624
|
write(
|
|
4871
|
-
|
|
5625
|
+
path25.join(dir, "canvas.json"),
|
|
4872
5626
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
4873
5627
|
`,
|
|
4874
5628
|
root
|
|
4875
5629
|
);
|
|
4876
|
-
|
|
5630
|
+
fs27.mkdirSync(path25.join(dir, "screens"), { recursive: true });
|
|
4877
5631
|
} else {
|
|
4878
|
-
write(
|
|
4879
|
-
write(
|
|
5632
|
+
write(path25.join(dir, "canvas.json"), canvasJson(title), root);
|
|
5633
|
+
write(path25.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
4880
5634
|
}
|
|
4881
5635
|
}
|
|
4882
5636
|
|
|
4883
5637
|
// src/cli/commands/shot.ts
|
|
4884
|
-
import
|
|
4885
|
-
import
|
|
5638
|
+
import fs28 from "fs";
|
|
5639
|
+
import path26 from "path";
|
|
4886
5640
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
4887
5641
|
async function runShot(paths, target, options) {
|
|
4888
5642
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -4898,15 +5652,15 @@ async function runShot(paths, target, options) {
|
|
|
4898
5652
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
4899
5653
|
}
|
|
4900
5654
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
4901
|
-
const outBase =
|
|
4902
|
-
const outDir =
|
|
4903
|
-
|
|
5655
|
+
const outBase = path26.resolve(options.out ?? paths.shots);
|
|
5656
|
+
const outDir = path26.join(outBase, canvasId);
|
|
5657
|
+
fs28.mkdirSync(outDir, { recursive: true });
|
|
4904
5658
|
const base = await serverBase(paths);
|
|
4905
5659
|
const browser = await launchChrome();
|
|
4906
5660
|
let failed = false;
|
|
4907
5661
|
try {
|
|
4908
5662
|
if (options.overview) {
|
|
4909
|
-
if (!
|
|
5663
|
+
if (!fs28.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
4910
5664
|
const pages = options.page ? [options.page] : screenId ? doc.pages.filter((page) => page.sections.some((section) => section.items.some((item) => item.id === screenId))).map((page) => page.id) : doc.pages.map((page) => page.id);
|
|
4911
5665
|
for (const pageId of pages) {
|
|
4912
5666
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -4922,7 +5676,7 @@ async function runShot(paths, target, options) {
|
|
|
4922
5676
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
4923
5677
|
}
|
|
4924
5678
|
await page.waitForTimeout(400);
|
|
4925
|
-
const file =
|
|
5679
|
+
const file = path26.join(outDir, `${pageId}.overview.png`);
|
|
4926
5680
|
await page.screenshot({ path: file });
|
|
4927
5681
|
await page.close();
|
|
4928
5682
|
print(file);
|
|
@@ -4951,7 +5705,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
4951
5705
|
let failed = false;
|
|
4952
5706
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
4953
5707
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
4954
|
-
const file =
|
|
5708
|
+
const file = path26.join(outDir, `${job.id}.${job.theme}.png`);
|
|
4955
5709
|
try {
|
|
4956
5710
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
4957
5711
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -4983,8 +5737,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4983
5737
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
4984
5738
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
4985
5739
|
}
|
|
4986
|
-
const outDir =
|
|
4987
|
-
|
|
5740
|
+
const outDir = path26.join(path26.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
5741
|
+
fs28.mkdirSync(outDir, { recursive: true });
|
|
4988
5742
|
const base = await serverBase(paths);
|
|
4989
5743
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
4990
5744
|
const browser = await launchChrome();
|
|
@@ -4996,7 +5750,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4996
5750
|
}
|
|
4997
5751
|
|
|
4998
5752
|
// src/cli/commands/cloud-status.ts
|
|
4999
|
-
import
|
|
5753
|
+
import path27 from "path";
|
|
5000
5754
|
var STATE_TEXT = {
|
|
5001
5755
|
in_sync: green,
|
|
5002
5756
|
local_changes: yellow,
|
|
@@ -5019,7 +5773,7 @@ function detail(paths, status) {
|
|
|
5019
5773
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
5020
5774
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
5021
5775
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
5022
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
5776
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path27.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
5023
5777
|
return parts.join(" \xB7 ");
|
|
5024
5778
|
}
|
|
5025
5779
|
async function printCloudStatus(paths, label) {
|
|
@@ -5134,6 +5888,8 @@ ${bold("Project")}
|
|
|
5134
5888
|
init [--name <name>] [--no-gitignore] Create .design and add it to .gitignore
|
|
5135
5889
|
system init [--name <name>] [--empty] Scaffold .design/system: tokens, a guideline, a component
|
|
5136
5890
|
new <canvas> [--title <title>] [--empty] Scaffold .design/canvas/<canvas>
|
|
5891
|
+
import <folder> [--canvas <id>] Turn an exported Claude Design project (canvas.json and
|
|
5892
|
+
[--title <title>] *.dc.html) into a canvas of React screens, once
|
|
5137
5893
|
canvases [--json] Every canvas: pages and screens; with a cloud link also its
|
|
5138
5894
|
revision, sync state, web link and public link
|
|
5139
5895
|
|
|
@@ -5193,7 +5949,7 @@ ${bold("Environment")}
|
|
|
5193
5949
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
5194
5950
|
`;
|
|
5195
5951
|
function projectPaths(rootOption, requireDesign = true) {
|
|
5196
|
-
const found = rootOption ?
|
|
5952
|
+
const found = rootOption ? path28.resolve(rootOption) : findProjectRoot();
|
|
5197
5953
|
if (!found) {
|
|
5198
5954
|
if (!requireDesign) return designPaths(process.cwd());
|
|
5199
5955
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -5210,6 +5966,7 @@ async function main(argv) {
|
|
|
5210
5966
|
root: { type: "string" },
|
|
5211
5967
|
name: { type: "string" },
|
|
5212
5968
|
title: { type: "string" },
|
|
5969
|
+
canvas: { type: "string" },
|
|
5213
5970
|
"no-gitignore": { type: "boolean" },
|
|
5214
5971
|
empty: { type: "boolean" },
|
|
5215
5972
|
open: { type: "boolean" },
|
|
@@ -5239,7 +5996,7 @@ async function main(argv) {
|
|
|
5239
5996
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
5240
5997
|
switch (command) {
|
|
5241
5998
|
case "init":
|
|
5242
|
-
return runInit(values.root ?
|
|
5999
|
+
return runInit(values.root ? path28.resolve(values.root) : process.cwd(), {
|
|
5243
6000
|
name: values.name,
|
|
5244
6001
|
gitignore: !values["no-gitignore"]
|
|
5245
6002
|
});
|
|
@@ -5251,6 +6008,10 @@ async function main(argv) {
|
|
|
5251
6008
|
if (!rest[0]) throw new CliError("Usage: design new <canvas> [--title <title>]");
|
|
5252
6009
|
return runNew(projectPaths(values.root).root, rest[0], { title: values.title, empty: !!values.empty });
|
|
5253
6010
|
}
|
|
6011
|
+
case "import": {
|
|
6012
|
+
if (!rest[0]) throw new CliError("Usage: design import <folder> [--canvas <id>] [--title <title>]");
|
|
6013
|
+
return runImport(projectPaths(values.root), rest[0], { canvas: values.canvas, title: values.title });
|
|
6014
|
+
}
|
|
5254
6015
|
case "preview": {
|
|
5255
6016
|
const openPath = rest[0]?.startsWith("/") ? rest[0] : void 0;
|
|
5256
6017
|
return runPreview(projectPaths(values.root), {
|