foss-design 0.6.0 → 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 +826 -63
- package/dist/runtime/index.js +56 -8
- 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,9 +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
|
}
|
|
3696
|
-
|
|
3704
|
+
if (reloads) errors.push(`the page reloaded itself ${reloads === 1 ? "once" : `${reloads} times`} while it loaded`);
|
|
3705
|
+
const held = ready ? 0 : await page.evaluate(() => window.__DESIGN_HOLDS__ ?? 0).catch(() => 0);
|
|
3697
3706
|
const runtimeErrors = await page.evaluate(() => window.__DESIGN_ERRORS__ ?? []).catch(() => []);
|
|
3698
3707
|
for (const error of runtimeErrors) if (!errors.includes(error)) errors.push(error);
|
|
3699
3708
|
const overlay = await page.evaluate(() => {
|
|
@@ -3704,7 +3713,7 @@ async function openFrame(page, url, options = {}) {
|
|
|
3704
3713
|
const unique2 = errors.filter(
|
|
3705
3714
|
(error, index) => error && !errors.some((other, j) => j !== index && other && other.length < error.length && error.includes(other))
|
|
3706
3715
|
);
|
|
3707
|
-
return { errors: [...new Set(unique2)], ready };
|
|
3716
|
+
return { errors: [...new Set(unique2)], ready, ...held ? { held } : {} };
|
|
3708
3717
|
}
|
|
3709
3718
|
|
|
3710
3719
|
// src/server/state.ts
|
|
@@ -4019,8 +4028,17 @@ async function startDevServer(options) {
|
|
|
4019
4028
|
allow: [searchForWorkspaceRoot(paths.root), paths.root, paths.design, PKG.root, ...links.allow]
|
|
4020
4029
|
},
|
|
4021
4030
|
watch: {
|
|
4022
|
-
|
|
4023
|
-
|
|
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
|
|
4024
4042
|
},
|
|
4025
4043
|
optimizeDeps: {
|
|
4026
4044
|
entries: entries.files().map((file) => toPosix(path21.relative(paths.design, file)))
|
|
@@ -4330,7 +4348,7 @@ async function renderFrames(jobs) {
|
|
|
4330
4348
|
try {
|
|
4331
4349
|
report = await openFrame(page, job.url, { waitForReady: job.waitForReady, settleMs: 300 });
|
|
4332
4350
|
links = await page.evaluate(
|
|
4333
|
-
() => [...document.querySelectorAll("a[href], area[href]")].map((link) => ({
|
|
4351
|
+
() => [...document.querySelectorAll("a[href]:not([download]), area[href]:not([download])")].map((link) => ({
|
|
4334
4352
|
href: link.getAttribute("href") ?? "",
|
|
4335
4353
|
text: (link.textContent || link.getAttribute("aria-label") || "").replace(/\s+/g, " ").trim().slice(0, 40)
|
|
4336
4354
|
}))
|
|
@@ -4341,7 +4359,7 @@ async function renderFrames(jobs) {
|
|
|
4341
4359
|
await page.close().catch(() => {
|
|
4342
4360
|
});
|
|
4343
4361
|
}
|
|
4344
|
-
return { key: job.key, url: job.url, ready: report.ready, errors: report.errors, links };
|
|
4362
|
+
return { key: job.key, url: job.url, ready: report.ready, held: report.held, errors: report.errors, links };
|
|
4345
4363
|
});
|
|
4346
4364
|
} finally {
|
|
4347
4365
|
await browser.close();
|
|
@@ -4384,7 +4402,8 @@ async function runCheck(paths, canvases, options) {
|
|
|
4384
4402
|
for (const frame of frames) {
|
|
4385
4403
|
const ok = frame.ready && !frame.errors.length;
|
|
4386
4404
|
print(`${ok ? green("\u2713") : red("\u2717")} ${frame.key} ${dim(frame.url)}`);
|
|
4387
|
-
if (
|
|
4405
|
+
if (frame.held) print(` ${red(`holdReady() was not released within 20s (${frame.held} still held)`)}`);
|
|
4406
|
+
else if (!frame.ready) print(` ${red("did not finish rendering (no ready signal within 20s)")}`);
|
|
4388
4407
|
for (const error of frame.errors) print(` ${red(error.split("\n")[0])}`);
|
|
4389
4408
|
}
|
|
4390
4409
|
}
|
|
@@ -4585,10 +4604,747 @@ async function runLink(paths, wanted, options) {
|
|
|
4585
4604
|
else if (remote) print(dim("Everything is in sync."));
|
|
4586
4605
|
}
|
|
4587
4606
|
|
|
4588
|
-
// 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
|
|
4589
4612
|
import fs25 from "fs";
|
|
4590
4613
|
import path23 from "path";
|
|
4591
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
|
+
|
|
4592
5348
|
// src/cli/templates.ts
|
|
4593
5349
|
var TOKENS_CSS = `/*
|
|
4594
5350
|
* Design tokens. Every screen gets Tailwind v4 with these as utilities:
|
|
@@ -4798,40 +5554,40 @@ function mainScreen(canvasId) {
|
|
|
4798
5554
|
|
|
4799
5555
|
// src/cli/commands/scaffold.ts
|
|
4800
5556
|
function write(file, content, root) {
|
|
4801
|
-
if (
|
|
4802
|
-
print(` ${dim("exists ")} ${
|
|
5557
|
+
if (fs27.existsSync(file)) {
|
|
5558
|
+
print(` ${dim("exists ")} ${path25.relative(root, file)}`);
|
|
4803
5559
|
return false;
|
|
4804
5560
|
}
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
print(` ${green("created")} ${
|
|
5561
|
+
fs27.mkdirSync(path25.dirname(file), { recursive: true });
|
|
5562
|
+
fs27.writeFileSync(file, content);
|
|
5563
|
+
print(` ${green("created")} ${path25.relative(root, file)}`);
|
|
4808
5564
|
return true;
|
|
4809
5565
|
}
|
|
4810
5566
|
function ensureGitignored(root) {
|
|
4811
|
-
const file =
|
|
4812
|
-
const current =
|
|
5567
|
+
const file = path25.join(root, ".gitignore");
|
|
5568
|
+
const current = fs27.existsSync(file) ? fs27.readFileSync(file, "utf8") : "";
|
|
4813
5569
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
4814
5570
|
if (covered) return "present";
|
|
4815
5571
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
4816
|
-
|
|
5572
|
+
fs27.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
4817
5573
|
.design/
|
|
4818
5574
|
`);
|
|
4819
5575
|
return "added";
|
|
4820
5576
|
}
|
|
4821
5577
|
function projectName(root) {
|
|
4822
5578
|
try {
|
|
4823
|
-
const manifest = JSON.parse(
|
|
5579
|
+
const manifest = JSON.parse(fs27.readFileSync(path25.join(root, "package.json"), "utf8"));
|
|
4824
5580
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
4825
5581
|
} catch {
|
|
4826
5582
|
}
|
|
4827
|
-
return
|
|
5583
|
+
return path25.basename(root);
|
|
4828
5584
|
}
|
|
4829
5585
|
function runInit(root, options) {
|
|
4830
5586
|
const paths = designPaths(root);
|
|
4831
5587
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
4832
5588
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
4833
5589
|
`, root);
|
|
4834
|
-
|
|
5590
|
+
fs27.mkdirSync(paths.canvases, { recursive: true });
|
|
4835
5591
|
if (options.gitignore) {
|
|
4836
5592
|
const result = ensureGitignored(root);
|
|
4837
5593
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
@@ -4843,44 +5599,44 @@ function runSystemInit(root, options) {
|
|
|
4843
5599
|
const paths = designPaths(root);
|
|
4844
5600
|
let base = projectName(root);
|
|
4845
5601
|
try {
|
|
4846
|
-
base = JSON.parse(
|
|
5602
|
+
base = JSON.parse(fs27.readFileSync(paths.config, "utf8")).name || base;
|
|
4847
5603
|
} catch {
|
|
4848
5604
|
}
|
|
4849
5605
|
const name = options.name ?? `${base} design system`;
|
|
4850
|
-
print(`Scaffolding the design system in ${
|
|
4851
|
-
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)}
|
|
4852
5608
|
`, root);
|
|
4853
|
-
write(
|
|
5609
|
+
write(path25.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
4854
5610
|
if (!options.empty) {
|
|
4855
|
-
write(
|
|
4856
|
-
write(
|
|
4857
|
-
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);
|
|
4858
5614
|
}
|
|
4859
5615
|
}
|
|
4860
5616
|
function runNew(root, id2, options) {
|
|
4861
5617
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
4862
5618
|
const paths = designPaths(root);
|
|
4863
|
-
const dir =
|
|
4864
|
-
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}`);
|
|
4865
5621
|
print(`Creating canvas "${id2}"`);
|
|
4866
5622
|
const title = options.title ?? titleize(id2);
|
|
4867
5623
|
if (options.empty) {
|
|
4868
5624
|
write(
|
|
4869
|
-
|
|
5625
|
+
path25.join(dir, "canvas.json"),
|
|
4870
5626
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
4871
5627
|
`,
|
|
4872
5628
|
root
|
|
4873
5629
|
);
|
|
4874
|
-
|
|
5630
|
+
fs27.mkdirSync(path25.join(dir, "screens"), { recursive: true });
|
|
4875
5631
|
} else {
|
|
4876
|
-
write(
|
|
4877
|
-
write(
|
|
5632
|
+
write(path25.join(dir, "canvas.json"), canvasJson(title), root);
|
|
5633
|
+
write(path25.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
4878
5634
|
}
|
|
4879
5635
|
}
|
|
4880
5636
|
|
|
4881
5637
|
// src/cli/commands/shot.ts
|
|
4882
|
-
import
|
|
4883
|
-
import
|
|
5638
|
+
import fs28 from "fs";
|
|
5639
|
+
import path26 from "path";
|
|
4884
5640
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
4885
5641
|
async function runShot(paths, target, options) {
|
|
4886
5642
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -4896,15 +5652,15 @@ async function runShot(paths, target, options) {
|
|
|
4896
5652
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
4897
5653
|
}
|
|
4898
5654
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
4899
|
-
const outBase =
|
|
4900
|
-
const outDir =
|
|
4901
|
-
|
|
5655
|
+
const outBase = path26.resolve(options.out ?? paths.shots);
|
|
5656
|
+
const outDir = path26.join(outBase, canvasId);
|
|
5657
|
+
fs28.mkdirSync(outDir, { recursive: true });
|
|
4902
5658
|
const base = await serverBase(paths);
|
|
4903
5659
|
const browser = await launchChrome();
|
|
4904
5660
|
let failed = false;
|
|
4905
5661
|
try {
|
|
4906
5662
|
if (options.overview) {
|
|
4907
|
-
if (!
|
|
5663
|
+
if (!fs28.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
4908
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);
|
|
4909
5665
|
for (const pageId of pages) {
|
|
4910
5666
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -4920,7 +5676,7 @@ async function runShot(paths, target, options) {
|
|
|
4920
5676
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
4921
5677
|
}
|
|
4922
5678
|
await page.waitForTimeout(400);
|
|
4923
|
-
const file =
|
|
5679
|
+
const file = path26.join(outDir, `${pageId}.overview.png`);
|
|
4924
5680
|
await page.screenshot({ path: file });
|
|
4925
5681
|
await page.close();
|
|
4926
5682
|
print(file);
|
|
@@ -4949,7 +5705,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
4949
5705
|
let failed = false;
|
|
4950
5706
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
4951
5707
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
4952
|
-
const file =
|
|
5708
|
+
const file = path26.join(outDir, `${job.id}.${job.theme}.png`);
|
|
4953
5709
|
try {
|
|
4954
5710
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
4955
5711
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -4981,8 +5737,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4981
5737
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
4982
5738
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
4983
5739
|
}
|
|
4984
|
-
const outDir =
|
|
4985
|
-
|
|
5740
|
+
const outDir = path26.join(path26.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
5741
|
+
fs28.mkdirSync(outDir, { recursive: true });
|
|
4986
5742
|
const base = await serverBase(paths);
|
|
4987
5743
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
4988
5744
|
const browser = await launchChrome();
|
|
@@ -4994,7 +5750,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4994
5750
|
}
|
|
4995
5751
|
|
|
4996
5752
|
// src/cli/commands/cloud-status.ts
|
|
4997
|
-
import
|
|
5753
|
+
import path27 from "path";
|
|
4998
5754
|
var STATE_TEXT = {
|
|
4999
5755
|
in_sync: green,
|
|
5000
5756
|
local_changes: yellow,
|
|
@@ -5017,7 +5773,7 @@ function detail(paths, status) {
|
|
|
5017
5773
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
5018
5774
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
5019
5775
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
5020
|
-
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))}`);
|
|
5021
5777
|
return parts.join(" \xB7 ");
|
|
5022
5778
|
}
|
|
5023
5779
|
async function printCloudStatus(paths, label) {
|
|
@@ -5132,6 +5888,8 @@ ${bold("Project")}
|
|
|
5132
5888
|
init [--name <name>] [--no-gitignore] Create .design and add it to .gitignore
|
|
5133
5889
|
system init [--name <name>] [--empty] Scaffold .design/system: tokens, a guideline, a component
|
|
5134
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
|
|
5135
5893
|
canvases [--json] Every canvas: pages and screens; with a cloud link also its
|
|
5136
5894
|
revision, sync state, web link and public link
|
|
5137
5895
|
|
|
@@ -5191,7 +5949,7 @@ ${bold("Environment")}
|
|
|
5191
5949
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
5192
5950
|
`;
|
|
5193
5951
|
function projectPaths(rootOption, requireDesign = true) {
|
|
5194
|
-
const found = rootOption ?
|
|
5952
|
+
const found = rootOption ? path28.resolve(rootOption) : findProjectRoot();
|
|
5195
5953
|
if (!found) {
|
|
5196
5954
|
if (!requireDesign) return designPaths(process.cwd());
|
|
5197
5955
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -5208,6 +5966,7 @@ async function main(argv) {
|
|
|
5208
5966
|
root: { type: "string" },
|
|
5209
5967
|
name: { type: "string" },
|
|
5210
5968
|
title: { type: "string" },
|
|
5969
|
+
canvas: { type: "string" },
|
|
5211
5970
|
"no-gitignore": { type: "boolean" },
|
|
5212
5971
|
empty: { type: "boolean" },
|
|
5213
5972
|
open: { type: "boolean" },
|
|
@@ -5237,7 +5996,7 @@ async function main(argv) {
|
|
|
5237
5996
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
5238
5997
|
switch (command) {
|
|
5239
5998
|
case "init":
|
|
5240
|
-
return runInit(values.root ?
|
|
5999
|
+
return runInit(values.root ? path28.resolve(values.root) : process.cwd(), {
|
|
5241
6000
|
name: values.name,
|
|
5242
6001
|
gitignore: !values["no-gitignore"]
|
|
5243
6002
|
});
|
|
@@ -5249,6 +6008,10 @@ async function main(argv) {
|
|
|
5249
6008
|
if (!rest[0]) throw new CliError("Usage: design new <canvas> [--title <title>]");
|
|
5250
6009
|
return runNew(projectPaths(values.root).root, rest[0], { title: values.title, empty: !!values.empty });
|
|
5251
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
|
+
}
|
|
5252
6015
|
case "preview": {
|
|
5253
6016
|
const openPath = rest[0]?.startsWith("/") ? rest[0] : void 0;
|
|
5254
6017
|
return runPreview(projectPaths(values.root), {
|