foss-design 0.9.8 → 0.9.9
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 +193 -129
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5
5
|
import fs37 from "fs";
|
|
6
|
-
import
|
|
6
|
+
import path36 from "path";
|
|
7
7
|
import { parseArgs } from "util";
|
|
8
8
|
|
|
9
9
|
// src/core/paths.ts
|
|
@@ -348,8 +348,8 @@ function routeError(pattern) {
|
|
|
348
348
|
}
|
|
349
349
|
return null;
|
|
350
350
|
}
|
|
351
|
-
function split(
|
|
352
|
-
return
|
|
351
|
+
function split(path37) {
|
|
352
|
+
return path37.split("/").filter(Boolean);
|
|
353
353
|
}
|
|
354
354
|
function compileRoute(pattern) {
|
|
355
355
|
return {
|
|
@@ -368,8 +368,8 @@ function decode2(part) {
|
|
|
368
368
|
return part;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
-
function scoreRoute(route2,
|
|
372
|
-
const parts = split(
|
|
371
|
+
function scoreRoute(route2, path37) {
|
|
372
|
+
const parts = split(path37.split(/[?#]/)[0] ?? "").map(decode2);
|
|
373
373
|
const walk2 = (s, p) => {
|
|
374
374
|
if (s === route2.segments.length) return p === parts.length ? 0 : null;
|
|
375
375
|
const segment = route2.segments[s];
|
|
@@ -387,11 +387,11 @@ function scoreRoute(route2, path36) {
|
|
|
387
387
|
};
|
|
388
388
|
return walk2(0, 0);
|
|
389
389
|
}
|
|
390
|
-
function matchRoute(entries,
|
|
390
|
+
function matchRoute(entries, path37) {
|
|
391
391
|
let best = null;
|
|
392
392
|
for (const entry of entries) {
|
|
393
393
|
for (const route2 of entry.routes) {
|
|
394
|
-
const score = scoreRoute(route2,
|
|
394
|
+
const score = scoreRoute(route2, path37);
|
|
395
395
|
if (score !== null && (!best || score > best.score)) best = { score, value: entry.value };
|
|
396
396
|
}
|
|
397
397
|
}
|
|
@@ -518,9 +518,9 @@ var CanvasSchema = z.strictObject({
|
|
|
518
518
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
519
519
|
});
|
|
520
520
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
521
|
-
function formatPath(base,
|
|
521
|
+
function formatPath(base, path37) {
|
|
522
522
|
let out = base;
|
|
523
|
-
for (const part of
|
|
523
|
+
for (const part of path37) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
524
524
|
return out;
|
|
525
525
|
}
|
|
526
526
|
function describeZodError(error, base = "") {
|
|
@@ -2150,10 +2150,23 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2150
2150
|
inputs.push(file);
|
|
2151
2151
|
}
|
|
2152
2152
|
fs12.mkdirSync(out, { recursive: true });
|
|
2153
|
+
const moduleMap = {};
|
|
2153
2154
|
if (inputs.length) {
|
|
2154
2155
|
const base = baseConfig(project);
|
|
2155
2156
|
await build({
|
|
2156
2157
|
...base,
|
|
2158
|
+
plugins: [
|
|
2159
|
+
...base.plugins ?? [],
|
|
2160
|
+
options.moduleMap ? {
|
|
2161
|
+
name: "design-module-map",
|
|
2162
|
+
generateBundle(_, bundle) {
|
|
2163
|
+
for (const [file, chunk2] of Object.entries(bundle)) {
|
|
2164
|
+
if (chunk2.type !== "chunk") continue;
|
|
2165
|
+
moduleMap[file] = (chunk2.moduleIds ?? []).map((id2) => id2.split("?")[0]).filter((id2) => path12.isAbsolute(id2) && !id2.includes(`${path12.sep}node_modules${path12.sep}`)).map((id2) => toPosix(path12.relative(paths.root, id2))).filter((rel) => !rel.startsWith(".."));
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
} : null
|
|
2169
|
+
],
|
|
2157
2170
|
root: staging,
|
|
2158
2171
|
base: "./",
|
|
2159
2172
|
logLevel: "warn",
|
|
@@ -2173,6 +2186,7 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2173
2186
|
fs12.rmSync(out, { recursive: true, force: true });
|
|
2174
2187
|
fs12.mkdirSync(out, { recursive: true });
|
|
2175
2188
|
}
|
|
2189
|
+
if (options.moduleMap) fs12.writeFileSync(path12.join(out, "modules.json"), JSON.stringify(moduleMap));
|
|
2176
2190
|
for (const { source, dir } of htmlScreens) {
|
|
2177
2191
|
copyTree(
|
|
2178
2192
|
path12.dirname(source.file),
|
|
@@ -2634,7 +2648,7 @@ var CloudClient = class {
|
|
|
2634
2648
|
}
|
|
2635
2649
|
host;
|
|
2636
2650
|
token;
|
|
2637
|
-
async send(method,
|
|
2651
|
+
async send(method, path37, init) {
|
|
2638
2652
|
const headers = {
|
|
2639
2653
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2640
2654
|
Accept: "application/json"
|
|
@@ -2642,7 +2656,7 @@ var CloudClient = class {
|
|
|
2642
2656
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2643
2657
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2644
2658
|
try {
|
|
2645
|
-
return await fetch(`${this.host}/api${
|
|
2659
|
+
return await fetch(`${this.host}/api${path37}`, {
|
|
2646
2660
|
method,
|
|
2647
2661
|
headers,
|
|
2648
2662
|
body: init.body,
|
|
@@ -2663,8 +2677,8 @@ var CloudClient = class {
|
|
|
2663
2677
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2664
2678
|
}
|
|
2665
2679
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2666
|
-
async request(method,
|
|
2667
|
-
const response = await this.send(method,
|
|
2680
|
+
async request(method, path37, body) {
|
|
2681
|
+
const response = await this.send(method, path37, {
|
|
2668
2682
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2669
2683
|
type: body === void 0 ? void 0 : "application/json",
|
|
2670
2684
|
timeout: JSON_TIMEOUT
|
|
@@ -2676,7 +2690,7 @@ var CloudClient = class {
|
|
|
2676
2690
|
try {
|
|
2677
2691
|
return JSON.parse(text2);
|
|
2678
2692
|
} catch {
|
|
2679
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2693
|
+
throw new CloudError(`${this.host} answered ${method} ${path37} with something other than JSON`, 0, "bad_response");
|
|
2680
2694
|
}
|
|
2681
2695
|
}
|
|
2682
2696
|
/** POST /device/code. */
|
|
@@ -3031,11 +3045,25 @@ var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word
|
|
|
3031
3045
|
// src/capture/cover.ts
|
|
3032
3046
|
import fs20 from "fs";
|
|
3033
3047
|
import os3 from "os";
|
|
3048
|
+
import path19 from "path";
|
|
3034
3049
|
|
|
3035
3050
|
// src/server/snapshots.ts
|
|
3036
3051
|
import { createHash as createHash5 } from "crypto";
|
|
3037
3052
|
import fs17 from "fs";
|
|
3038
3053
|
import path17 from "path";
|
|
3054
|
+
function depsHash(root, rels, cache = /* @__PURE__ */ new Map()) {
|
|
3055
|
+
const hash = createHash5("sha1");
|
|
3056
|
+
for (const rel of [...rels].sort()) {
|
|
3057
|
+
let one = cache.get(rel);
|
|
3058
|
+
if (one === void 0) {
|
|
3059
|
+
one = sourceHash(path17.join(root, rel)) ?? "missing";
|
|
3060
|
+
cache.set(rel, one);
|
|
3061
|
+
}
|
|
3062
|
+
hash.update(`${rel}\0${one}
|
|
3063
|
+
`);
|
|
3064
|
+
}
|
|
3065
|
+
return hash.digest("hex").slice(0, 16);
|
|
3066
|
+
}
|
|
3039
3067
|
function sourceHash(file) {
|
|
3040
3068
|
try {
|
|
3041
3069
|
return createHash5("sha1").update(fs17.readFileSync(file)).digest("hex").slice(0, 16);
|
|
@@ -3103,6 +3131,11 @@ var SnapshotStore = class {
|
|
|
3103
3131
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return void 0;
|
|
3104
3132
|
return this.load(canvas)[id2]?.sources?.[theme2];
|
|
3105
3133
|
}
|
|
3134
|
+
/** The files the snapshot's frame loaded, when a build told (see `Meta.deps`). */
|
|
3135
|
+
deps(canvas, id2) {
|
|
3136
|
+
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return void 0;
|
|
3137
|
+
return this.load(canvas)[id2]?.deps;
|
|
3138
|
+
}
|
|
3106
3139
|
/** Record `hash` for a snapshot taken before hashes were kept: from now on it counts as that content's. */
|
|
3107
3140
|
adopt(canvas, id2, theme2, hash) {
|
|
3108
3141
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return;
|
|
@@ -3112,7 +3145,7 @@ var SnapshotStore = class {
|
|
|
3112
3145
|
entry.sources = { ...entry.sources, [theme2]: hash };
|
|
3113
3146
|
this.writeMeta(canvas, data);
|
|
3114
3147
|
}
|
|
3115
|
-
save(canvas, id2, theme2, png, height, source) {
|
|
3148
|
+
save(canvas, id2, theme2, png, height, source, deps) {
|
|
3116
3149
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3117
3150
|
const dir = path17.join(this.dir, canvas);
|
|
3118
3151
|
fs17.mkdirSync(dir, { recursive: true });
|
|
@@ -3125,7 +3158,7 @@ var SnapshotStore = class {
|
|
|
3125
3158
|
const sources = { ...previous?.sources };
|
|
3126
3159
|
if (source) sources[theme2] = source;
|
|
3127
3160
|
else delete sources[theme2];
|
|
3128
|
-
data[id2] = { ...previous, [theme2]: version, ...height ? { height } : {}, sources };
|
|
3161
|
+
data[id2] = { ...previous, [theme2]: version, ...height ? { height } : {}, sources, ...deps ? { deps } : {} };
|
|
3129
3162
|
this.writeMeta(canvas, data);
|
|
3130
3163
|
return { url: this.url(canvas, id2, theme2, version) };
|
|
3131
3164
|
}
|
|
@@ -3150,7 +3183,8 @@ async function serveBuild(paths, name, options) {
|
|
|
3150
3183
|
await buildSite(paths, out, {
|
|
3151
3184
|
canvases: options.canvases,
|
|
3152
3185
|
includeSystem: options.includeSystem,
|
|
3153
|
-
includeViewer: false
|
|
3186
|
+
includeViewer: false,
|
|
3187
|
+
moduleMap: options.moduleMap
|
|
3154
3188
|
});
|
|
3155
3189
|
const files = sirv(out, { dev: true });
|
|
3156
3190
|
const server = http.createServer((req, res) => {
|
|
@@ -3371,6 +3405,7 @@ function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
|
3371
3405
|
const store = new SnapshotStore(paths.snapshots);
|
|
3372
3406
|
const project = new DesignProject(paths, DEV_URLS, store.lookup);
|
|
3373
3407
|
const stale = [];
|
|
3408
|
+
const hashes2 = /* @__PURE__ */ new Map();
|
|
3374
3409
|
for (const id2 of canvasIds) {
|
|
3375
3410
|
const canvas = project.canvas(id2);
|
|
3376
3411
|
if (!canvas) continue;
|
|
@@ -3383,7 +3418,8 @@ function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
|
3383
3418
|
if (filter.screens && !filter.screens.has(job.id)) continue;
|
|
3384
3419
|
const file = files.get(job.id);
|
|
3385
3420
|
if (!file || !fs20.existsSync(file)) continue;
|
|
3386
|
-
const
|
|
3421
|
+
const deps = store.deps(id2, job.id);
|
|
3422
|
+
const hash = deps ? depsHash(paths.root, deps, hashes2) : sourceHash(file);
|
|
3387
3423
|
const pinned = inDark.get(job.id) === job.theme;
|
|
3388
3424
|
const themes = pinned ? [job.theme] : THEMES2;
|
|
3389
3425
|
for (const theme2 of themes) {
|
|
@@ -3411,9 +3447,12 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3411
3447
|
let docOf = (id2) => project.canvas(id2).doc;
|
|
3412
3448
|
let stop = async () => {
|
|
3413
3449
|
};
|
|
3450
|
+
let modules = null;
|
|
3451
|
+
const hashes2 = /* @__PURE__ */ new Map();
|
|
3414
3452
|
try {
|
|
3415
|
-
const build2 = await serveBuild(paths, "snapshot-build", { canvases, includeSystem: false });
|
|
3453
|
+
const build2 = await serveBuild(paths, "snapshot-build", { canvases, includeSystem: false, moduleMap: true });
|
|
3416
3454
|
url = build2.base;
|
|
3455
|
+
modules = build2.read("modules.json");
|
|
3417
3456
|
docOf = (id2) => build2.read(`api/canvas/${id2}.json`) ?? project.canvas(id2).doc;
|
|
3418
3457
|
stop = build2.close;
|
|
3419
3458
|
} catch {
|
|
@@ -3434,12 +3473,25 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3434
3473
|
deviceScaleFactor: Math.min(1, SNAPSHOT_WIDTH / job.width),
|
|
3435
3474
|
colorScheme: theme2
|
|
3436
3475
|
});
|
|
3476
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
3477
|
+
page.on("request", (request) => {
|
|
3478
|
+
const at = request.url();
|
|
3479
|
+
if (at.startsWith(url)) loaded.add(decodeURIComponent(new URL(at).pathname.slice(1)));
|
|
3480
|
+
});
|
|
3437
3481
|
try {
|
|
3438
3482
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
3439
3483
|
if (report.ready) {
|
|
3440
3484
|
const png = await page.screenshot({ fullPage: job.fullPage });
|
|
3441
3485
|
const height = job.fullPage ? await page.evaluate(() => document.documentElement.scrollHeight) : void 0;
|
|
3442
|
-
|
|
3486
|
+
const file = project.source(canvas, screen)?.file;
|
|
3487
|
+
const deps = modules ? [
|
|
3488
|
+
.../* @__PURE__ */ new Set([
|
|
3489
|
+
...[...loaded].flatMap((rel) => modules[rel] ?? []),
|
|
3490
|
+
...file ? [toPosix(path19.relative(paths.root, file))] : []
|
|
3491
|
+
])
|
|
3492
|
+
].sort() : [];
|
|
3493
|
+
const hash = deps.length ? depsHash(paths.root, deps, hashes2) : source;
|
|
3494
|
+
store.save(canvas, screen, theme2, png, height, hash, deps.length ? deps : void 0);
|
|
3443
3495
|
} else failed.push(`${canvas}/${screen} (${theme2})`);
|
|
3444
3496
|
} catch {
|
|
3445
3497
|
failed.push(`${canvas}/${screen} (${theme2})`);
|
|
@@ -3466,21 +3518,21 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3466
3518
|
|
|
3467
3519
|
// src/cloud/pull.ts
|
|
3468
3520
|
import fs24 from "fs";
|
|
3469
|
-
import
|
|
3521
|
+
import path23 from "path";
|
|
3470
3522
|
|
|
3471
3523
|
// src/cloud/merge.ts
|
|
3472
3524
|
import fs22 from "fs";
|
|
3473
|
-
import
|
|
3525
|
+
import path21 from "path";
|
|
3474
3526
|
import { merge as diff3 } from "node-diff3";
|
|
3475
3527
|
|
|
3476
3528
|
// src/cloud/write.ts
|
|
3477
3529
|
import fs21 from "fs";
|
|
3478
|
-
import
|
|
3530
|
+
import path20 from "path";
|
|
3479
3531
|
function writeInside(root, rel, data) {
|
|
3480
|
-
const file =
|
|
3532
|
+
const file = path20.join(root, rel);
|
|
3481
3533
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
3482
|
-
fs21.mkdirSync(
|
|
3483
|
-
if (!isInside(fs21.realpathSync(root), fs21.realpathSync(
|
|
3534
|
+
fs21.mkdirSync(path20.dirname(file), { recursive: true });
|
|
3535
|
+
if (!isInside(fs21.realpathSync(root), fs21.realpathSync(path20.dirname(file))))
|
|
3484
3536
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
3485
3537
|
if (fs21.existsSync(file) && fs21.lstatSync(file).isSymbolicLink()) fs21.unlinkSync(file);
|
|
3486
3538
|
fs21.writeFileSync(file, data);
|
|
@@ -3494,16 +3546,16 @@ function pruneEmpty(dir, stop) {
|
|
|
3494
3546
|
} catch {
|
|
3495
3547
|
return;
|
|
3496
3548
|
}
|
|
3497
|
-
current =
|
|
3549
|
+
current = path20.dirname(current);
|
|
3498
3550
|
}
|
|
3499
3551
|
}
|
|
3500
3552
|
|
|
3501
3553
|
// src/cloud/merge.ts
|
|
3502
3554
|
var TEXT = /\.(?:[cm]?[jt]sx?|json|css|scss|html?|mdx?|svg|txt|ya?ml|csv|xml|toml)$/i;
|
|
3503
3555
|
var isText = (rel, ...data) => TEXT.test(rel) && data.every((buffer) => !buffer || !buffer.subarray(0, 8192).includes(0));
|
|
3504
|
-
var mergeDir = (paths, key) =>
|
|
3505
|
-
var stateFile = (paths, key) =>
|
|
3506
|
-
var sideFile = (paths, key, rel, side) =>
|
|
3556
|
+
var mergeDir = (paths, key) => path21.join(cloudCache(paths), "merge", ...key.split("/"));
|
|
3557
|
+
var stateFile = (paths, key) => path21.join(mergeDir(paths, key), "state.json");
|
|
3558
|
+
var sideFile = (paths, key, rel, side) => path21.join(mergeDir(paths, key), side, rel);
|
|
3507
3559
|
function readMergeState(paths, key) {
|
|
3508
3560
|
try {
|
|
3509
3561
|
return JSON.parse(fs22.readFileSync(stateFile(paths, key), "utf8"));
|
|
@@ -3517,10 +3569,10 @@ function saveState(paths, state) {
|
|
|
3517
3569
|
`);
|
|
3518
3570
|
}
|
|
3519
3571
|
function mergingUnits(paths) {
|
|
3520
|
-
const root =
|
|
3572
|
+
const root = path21.join(cloudCache(paths), "merge");
|
|
3521
3573
|
const found = [];
|
|
3522
3574
|
const visit = (dir, prefix) => {
|
|
3523
|
-
if (fs22.existsSync(
|
|
3575
|
+
if (fs22.existsSync(path21.join(dir, "state.json"))) {
|
|
3524
3576
|
const state = readMergeState(paths, prefix);
|
|
3525
3577
|
if (state) found.push(state);
|
|
3526
3578
|
return;
|
|
@@ -3531,7 +3583,7 @@ function mergingUnits(paths) {
|
|
|
3531
3583
|
} catch {
|
|
3532
3584
|
}
|
|
3533
3585
|
for (const entry of entries)
|
|
3534
|
-
if (entry.isDirectory()) visit(
|
|
3586
|
+
if (entry.isDirectory()) visit(path21.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
3535
3587
|
};
|
|
3536
3588
|
visit(root, "");
|
|
3537
3589
|
return found;
|
|
@@ -3541,7 +3593,7 @@ function openConflicts(paths, state) {
|
|
|
3541
3593
|
return state.conflicts.filter((conflict) => {
|
|
3542
3594
|
if (conflict.kind !== "text") return true;
|
|
3543
3595
|
try {
|
|
3544
|
-
return MARKER.test(fs22.readFileSync(
|
|
3596
|
+
return MARKER.test(fs22.readFileSync(path21.join(paths.design, conflict.path), "utf8"));
|
|
3545
3597
|
} catch {
|
|
3546
3598
|
return false;
|
|
3547
3599
|
}
|
|
@@ -3550,19 +3602,19 @@ function openConflicts(paths, state) {
|
|
|
3550
3602
|
function keep(paths, key, rel, side, data) {
|
|
3551
3603
|
if (!data) return;
|
|
3552
3604
|
const file = sideFile(paths, key, rel, side);
|
|
3553
|
-
fs22.mkdirSync(
|
|
3605
|
+
fs22.mkdirSync(path21.dirname(file), { recursive: true });
|
|
3554
3606
|
fs22.writeFileSync(file, data);
|
|
3555
3607
|
}
|
|
3556
3608
|
function mergeUnit(paths, key, headRev, base, here, cloud2, blob) {
|
|
3557
3609
|
const files = { added: 0, changed: 0, removed: 0, merged: 0 };
|
|
3558
3610
|
const conflicts = [];
|
|
3559
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3611
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path21.dirname(unitRoot(paths, key));
|
|
3560
3612
|
fs22.rmSync(mergeDir(paths, key), { recursive: true, force: true });
|
|
3561
|
-
const read = (rel) => fs22.readFileSync(
|
|
3613
|
+
const read = (rel) => fs22.readFileSync(path21.join(paths.design, rel));
|
|
3562
3614
|
const remove = (rel) => {
|
|
3563
|
-
const file =
|
|
3615
|
+
const file = path21.join(paths.design, rel);
|
|
3564
3616
|
fs22.rmSync(file, { force: true });
|
|
3565
|
-
pruneEmpty(
|
|
3617
|
+
pruneEmpty(path21.dirname(file), stop);
|
|
3566
3618
|
};
|
|
3567
3619
|
for (const rel of [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(here), ...Object.keys(cloud2)])].sort()) {
|
|
3568
3620
|
const b = base[rel];
|
|
@@ -3618,11 +3670,11 @@ function resolveConflict(paths, state, rel, side) {
|
|
|
3618
3670
|
const conflict = state.conflicts.find((item) => item.path === rel);
|
|
3619
3671
|
if (!conflict) return;
|
|
3620
3672
|
const kept = sideFile(paths, state.unit, rel, side);
|
|
3621
|
-
const stop = state.unit === SYSTEM_UNIT ? paths.design :
|
|
3673
|
+
const stop = state.unit === SYSTEM_UNIT ? paths.design : path21.dirname(unitRoot(paths, state.unit));
|
|
3622
3674
|
if (fs22.existsSync(kept)) writeInside(paths.design, rel, fs22.readFileSync(kept));
|
|
3623
3675
|
else {
|
|
3624
|
-
fs22.rmSync(
|
|
3625
|
-
pruneEmpty(
|
|
3676
|
+
fs22.rmSync(path21.join(paths.design, rel), { force: true });
|
|
3677
|
+
pruneEmpty(path21.dirname(path21.join(paths.design, rel)), stop);
|
|
3626
3678
|
}
|
|
3627
3679
|
state.conflicts = state.conflicts.filter((item) => item.path !== rel);
|
|
3628
3680
|
saveState(paths, state);
|
|
@@ -3640,7 +3692,7 @@ function finishMerge(paths, link, key, accept = false) {
|
|
|
3640
3692
|
|
|
3641
3693
|
// src/cloud/meta.ts
|
|
3642
3694
|
import fs23 from "fs";
|
|
3643
|
-
import
|
|
3695
|
+
import path22 from "path";
|
|
3644
3696
|
async function syncProjectMeta(paths, client, link) {
|
|
3645
3697
|
const remote = await client.project(link.project);
|
|
3646
3698
|
if (!remote) return [];
|
|
@@ -3659,7 +3711,7 @@ async function syncProjectMeta(paths, client, link) {
|
|
|
3659
3711
|
if (localChanged && !cloudChanged) {
|
|
3660
3712
|
try {
|
|
3661
3713
|
if (file) {
|
|
3662
|
-
const ext =
|
|
3714
|
+
const ext = path22.extname(file).slice(1);
|
|
3663
3715
|
await client.setProjectIcon(link.project, { data: fs23.readFileSync(file), type: ICON_TYPES[ext] });
|
|
3664
3716
|
lines.push("The project icon went up to the cloud.");
|
|
3665
3717
|
} else {
|
|
@@ -3695,12 +3747,12 @@ function checkManifest(key, manifest) {
|
|
|
3695
3747
|
}
|
|
3696
3748
|
function applyUnit(paths, key, local, target, blobs) {
|
|
3697
3749
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
3698
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3750
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path23.dirname(unitRoot(paths, key));
|
|
3699
3751
|
for (const rel of Object.keys(local)) {
|
|
3700
3752
|
if (rel in target) continue;
|
|
3701
|
-
const file =
|
|
3753
|
+
const file = path23.join(paths.design, rel);
|
|
3702
3754
|
fs24.rmSync(file, { force: true });
|
|
3703
|
-
pruneEmpty(
|
|
3755
|
+
pruneEmpty(path23.dirname(file), stop);
|
|
3704
3756
|
counts.removed++;
|
|
3705
3757
|
}
|
|
3706
3758
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -3799,7 +3851,7 @@ async function pull(ctx, options) {
|
|
|
3799
3851
|
}
|
|
3800
3852
|
const byHash = /* @__PURE__ */ new Map();
|
|
3801
3853
|
for (const unit of scan.units.values())
|
|
3802
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
3854
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path23.join(paths.design, rel));
|
|
3803
3855
|
const needed = /* @__PURE__ */ new Set();
|
|
3804
3856
|
const bases = /* @__PURE__ */ new Set();
|
|
3805
3857
|
for (const plan of plans) {
|
|
@@ -3898,7 +3950,7 @@ async function pull(ctx, options) {
|
|
|
3898
3950
|
|
|
3899
3951
|
// src/cloud/push.ts
|
|
3900
3952
|
import fs25 from "fs";
|
|
3901
|
-
import
|
|
3953
|
+
import path24 from "path";
|
|
3902
3954
|
function blockedReport(status, message) {
|
|
3903
3955
|
return {
|
|
3904
3956
|
unit: status.key,
|
|
@@ -4014,7 +4066,7 @@ async function push(ctx, options) {
|
|
|
4014
4066
|
for (const key of toPush) {
|
|
4015
4067
|
const s = status(key);
|
|
4016
4068
|
if (s.conflictRev !== void 0) {
|
|
4017
|
-
const dir =
|
|
4069
|
+
const dir = path24.relative(paths.root, incomingDir(paths, key));
|
|
4018
4070
|
blocked.push(
|
|
4019
4071
|
blockedReport(
|
|
4020
4072
|
s,
|
|
@@ -4063,7 +4115,7 @@ async function push(ctx, options) {
|
|
|
4063
4115
|
const files = /* @__PURE__ */ new Map();
|
|
4064
4116
|
const addFiles = (manifest, dir) => {
|
|
4065
4117
|
for (const [rel, entry] of Object.entries(manifest))
|
|
4066
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
4118
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path24.join(dir, rel));
|
|
4067
4119
|
};
|
|
4068
4120
|
for (const key of pushing) {
|
|
4069
4121
|
const id2 = unitCanvasId(key);
|
|
@@ -4109,7 +4161,7 @@ async function push(ctx, options) {
|
|
|
4109
4161
|
await client.putBlob(link.project, hash, data);
|
|
4110
4162
|
} catch (error) {
|
|
4111
4163
|
if (error instanceof CloudError && error.status === 413)
|
|
4112
|
-
throw new CliError(`${
|
|
4164
|
+
throw new CliError(`${path24.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
4113
4165
|
throw error;
|
|
4114
4166
|
}
|
|
4115
4167
|
sentFiles++;
|
|
@@ -4153,7 +4205,7 @@ function refuseLegacyGo(paths, keys) {
|
|
|
4153
4205
|
for (const key of keys) {
|
|
4154
4206
|
if (!key.startsWith("canvas/")) continue;
|
|
4155
4207
|
const id2 = key.slice("canvas/".length);
|
|
4156
|
-
for (const file of canvasCodeFiles(
|
|
4208
|
+
for (const file of canvasCodeFiles(path24.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
4157
4209
|
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
4158
4210
|
}
|
|
4159
4211
|
}
|
|
@@ -4174,13 +4226,13 @@ import fs29 from "fs";
|
|
|
4174
4226
|
// src/server/index.ts
|
|
4175
4227
|
import fs28 from "fs";
|
|
4176
4228
|
import http2 from "http";
|
|
4177
|
-
import
|
|
4229
|
+
import path27 from "path";
|
|
4178
4230
|
import sirv2 from "sirv";
|
|
4179
4231
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
4180
4232
|
|
|
4181
4233
|
// src/server/api.ts
|
|
4182
4234
|
import fs26 from "fs";
|
|
4183
|
-
import
|
|
4235
|
+
import path25 from "path";
|
|
4184
4236
|
function sendJson(res, status, body) {
|
|
4185
4237
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
4186
4238
|
res.end(JSON.stringify(body));
|
|
@@ -4231,7 +4283,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4231
4283
|
}
|
|
4232
4284
|
if (route2 === "/source") {
|
|
4233
4285
|
const rel = url.searchParams.get("path") ?? "";
|
|
4234
|
-
const file =
|
|
4286
|
+
const file = path25.resolve(project.paths.root, rel);
|
|
4235
4287
|
if (!rel || !isInside(project.paths.root, file) || !fs26.existsSync(file) || fs26.statSync(file).isDirectory()) {
|
|
4236
4288
|
sendJson(res, 404, { error: "not found" });
|
|
4237
4289
|
return true;
|
|
@@ -4271,8 +4323,10 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4271
4323
|
try {
|
|
4272
4324
|
const body = await readBody(req);
|
|
4273
4325
|
if (body.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a") throw new Error("not a PNG");
|
|
4326
|
+
const deps = snapshots.deps(canvas, id2);
|
|
4274
4327
|
const file = project.source(canvas, id2)?.file;
|
|
4275
|
-
const
|
|
4328
|
+
const source = deps ? depsHash(project.paths.root, deps) : file ? sourceHash(file) : null;
|
|
4329
|
+
const { url: snapshotUrl } = snapshots.save(canvas, id2, theme2, body, height, source);
|
|
4276
4330
|
project.invalidateCanvas(canvas);
|
|
4277
4331
|
events.send({ type: "snapshot", canvas, id: id2, theme: theme2, url: snapshotUrl, height });
|
|
4278
4332
|
sendJson(res, 200, { url: snapshotUrl });
|
|
@@ -4331,9 +4385,9 @@ var EventHub = class {
|
|
|
4331
4385
|
// src/server/state.ts
|
|
4332
4386
|
import fs27 from "fs";
|
|
4333
4387
|
import os4 from "os";
|
|
4334
|
-
import
|
|
4335
|
-
var stateFile2 = (paths) =>
|
|
4336
|
-
var logFile = (paths) =>
|
|
4388
|
+
import path26 from "path";
|
|
4389
|
+
var stateFile2 = (paths) => path26.join(paths.cache, "server.json");
|
|
4390
|
+
var logFile = (paths) => path26.join(paths.cache, "server.log");
|
|
4337
4391
|
function readState(paths) {
|
|
4338
4392
|
try {
|
|
4339
4393
|
return JSON.parse(fs27.readFileSync(stateFile2(paths), "utf8"));
|
|
@@ -4342,10 +4396,10 @@ function readState(paths) {
|
|
|
4342
4396
|
}
|
|
4343
4397
|
}
|
|
4344
4398
|
function registryDir() {
|
|
4345
|
-
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA ||
|
|
4346
|
-
return
|
|
4399
|
+
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA || path26.join(os4.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path26.join(os4.homedir(), ".cache");
|
|
4400
|
+
return path26.join(base, "foss-design", "previews");
|
|
4347
4401
|
}
|
|
4348
|
-
var entryFile = (pid) =>
|
|
4402
|
+
var entryFile = (pid) => path26.join(registryDir(), `${pid}.json`);
|
|
4349
4403
|
function writeState(paths, state) {
|
|
4350
4404
|
fs27.mkdirSync(paths.cache, { recursive: true });
|
|
4351
4405
|
fs27.writeFileSync(stateFile2(paths), `${JSON.stringify(state, null, 2)}
|
|
@@ -4372,7 +4426,7 @@ function listPreviews() {
|
|
|
4372
4426
|
}
|
|
4373
4427
|
const found = [];
|
|
4374
4428
|
for (const name of files) {
|
|
4375
|
-
const file =
|
|
4429
|
+
const file = path26.join(registryDir(), name);
|
|
4376
4430
|
let state = null;
|
|
4377
4431
|
try {
|
|
4378
4432
|
state = JSON.parse(fs27.readFileSync(file, "utf8"));
|
|
@@ -4384,7 +4438,7 @@ function listPreviews() {
|
|
|
4384
4438
|
}
|
|
4385
4439
|
let recorded = null;
|
|
4386
4440
|
try {
|
|
4387
|
-
recorded = JSON.parse(fs27.readFileSync(
|
|
4441
|
+
recorded = JSON.parse(fs27.readFileSync(path26.join(state.root, ".design", ".cache", "server.json"), "utf8"));
|
|
4388
4442
|
} catch {
|
|
4389
4443
|
}
|
|
4390
4444
|
found.push({ ...state, tracked: recorded?.pid === state.pid });
|
|
@@ -4399,7 +4453,7 @@ async function stopPreview(entry) {
|
|
|
4399
4453
|
const until = Date.now() + 5e3;
|
|
4400
4454
|
while (Date.now() < until && pidAlive(entry.pid)) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4401
4455
|
fs27.rmSync(entryFile(entry.pid), { force: true });
|
|
4402
|
-
const stateAt =
|
|
4456
|
+
const stateAt = path26.join(entry.root, ".design", ".cache", "server.json");
|
|
4403
4457
|
try {
|
|
4404
4458
|
if (JSON.parse(fs27.readFileSync(stateAt, "utf8")).pid === entry.pid)
|
|
4405
4459
|
fs27.rmSync(stateAt, { force: true });
|
|
@@ -4488,8 +4542,8 @@ async function startDevServer(options) {
|
|
|
4488
4542
|
// `.cache` is ours (server.log, snapshots, builds): a change there must not reload frames. The
|
|
4489
4543
|
// generated entries are modules the frames load, so those stay watched.
|
|
4490
4544
|
ignored: (file) => {
|
|
4491
|
-
const rel = toPosix(
|
|
4492
|
-
const inCache = rel !== "" && !rel.startsWith("..") && !
|
|
4545
|
+
const rel = toPosix(path27.relative(paths.cache, file));
|
|
4546
|
+
const inCache = rel !== "" && !rel.startsWith("..") && !path27.isAbsolute(rel);
|
|
4493
4547
|
return inCache && rel !== "entries" && !rel.startsWith("entries/");
|
|
4494
4548
|
}
|
|
4495
4549
|
},
|
|
@@ -4498,7 +4552,7 @@ async function startDevServer(options) {
|
|
|
4498
4552
|
forwardConsole: false
|
|
4499
4553
|
},
|
|
4500
4554
|
optimizeDeps: {
|
|
4501
|
-
entries: entries.files().map((file) => toPosix(
|
|
4555
|
+
entries: entries.files().map((file) => toPosix(path27.relative(paths.design, file)))
|
|
4502
4556
|
}
|
|
4503
4557
|
});
|
|
4504
4558
|
const publicDir = project.publicDir();
|
|
@@ -4511,7 +4565,7 @@ async function startDevServer(options) {
|
|
|
4511
4565
|
}
|
|
4512
4566
|
};
|
|
4513
4567
|
const viewer = fs28.existsSync(PKG.viewer) ? sirv2(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4514
|
-
const viewerIndex =
|
|
4568
|
+
const viewerIndex = path27.join(PKG.viewer, "index.html");
|
|
4515
4569
|
const serveViewerIndex = (res) => {
|
|
4516
4570
|
const html = fs28.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
4517
4571
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -4519,7 +4573,7 @@ async function startDevServer(options) {
|
|
|
4519
4573
|
};
|
|
4520
4574
|
const ctx = { project, events, snapshots };
|
|
4521
4575
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
4522
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4576
|
+
const bootUrl = `/${relToDesign(paths, path27.join(entries.dir, HTML_BOOT))}`;
|
|
4523
4577
|
const frameHead = (source, extra = {}) => {
|
|
4524
4578
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
4525
4579
|
return {
|
|
@@ -4549,7 +4603,7 @@ async function startDevServer(options) {
|
|
|
4549
4603
|
};
|
|
4550
4604
|
const serveHtmlScreen = async (res, url2) => {
|
|
4551
4605
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
4552
|
-
const file =
|
|
4606
|
+
const file = path27.resolve(paths.design, rel);
|
|
4553
4607
|
if (!isInside(paths.design, file) || !fs28.existsSync(file)) {
|
|
4554
4608
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
4555
4609
|
return;
|
|
@@ -4560,7 +4614,7 @@ async function startDevServer(options) {
|
|
|
4560
4614
|
key: `${canvas}/${id2}`,
|
|
4561
4615
|
canvas,
|
|
4562
4616
|
id: id2,
|
|
4563
|
-
title:
|
|
4617
|
+
title: path27.basename(file),
|
|
4564
4618
|
file,
|
|
4565
4619
|
format: "html",
|
|
4566
4620
|
props: {},
|
|
@@ -4599,7 +4653,7 @@ async function startDevServer(options) {
|
|
|
4599
4653
|
}
|
|
4600
4654
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4601
4655
|
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4602
|
-
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs28.existsSync(
|
|
4656
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs28.existsSync(path27.join(publicDir, rel))) {
|
|
4603
4657
|
notFoundPage(res, "Not found", url2.pathname);
|
|
4604
4658
|
return;
|
|
4605
4659
|
}
|
|
@@ -4611,7 +4665,7 @@ async function startDevServer(options) {
|
|
|
4611
4665
|
const rel = publicRel(url2.pathname.slice(1));
|
|
4612
4666
|
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4613
4667
|
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4614
|
-
else if (
|
|
4668
|
+
else if (path27.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4615
4669
|
else serveViewerIndex(res);
|
|
4616
4670
|
};
|
|
4617
4671
|
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
@@ -4639,7 +4693,7 @@ async function startDevServer(options) {
|
|
|
4639
4693
|
};
|
|
4640
4694
|
vite.watcher.on("all", (event, file) => {
|
|
4641
4695
|
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4642
|
-
const rel = toPosix(
|
|
4696
|
+
const rel = toPosix(path27.relative(paths.design, file));
|
|
4643
4697
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
4644
4698
|
if (rel === "design.json") {
|
|
4645
4699
|
queue("config", () => {
|
|
@@ -5364,7 +5418,7 @@ function printLinkIssues(issues, project) {
|
|
|
5364
5418
|
// src/cli/commands/cloud.ts
|
|
5365
5419
|
import fs30 from "fs";
|
|
5366
5420
|
import os5 from "os";
|
|
5367
|
-
import
|
|
5421
|
+
import path28 from "path";
|
|
5368
5422
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5369
5423
|
async function runLogin() {
|
|
5370
5424
|
const host = cloudHost();
|
|
@@ -5465,7 +5519,7 @@ async function runLink(paths, wanted, options) {
|
|
|
5465
5519
|
print(`${dim("Already linked to")} ${project.name} ${dim(`(${project.id}, ${project.role})`)}`);
|
|
5466
5520
|
} else {
|
|
5467
5521
|
writeLink(paths, { host, project: project.id, units: {}, conflicts: {} });
|
|
5468
|
-
fs30.rmSync(
|
|
5522
|
+
fs30.rmSync(path28.join(cloudCache(paths), "incoming"), { recursive: true, force: true });
|
|
5469
5523
|
const replaced = current ? dim(` (replacing the link to ${current.project})`) : "";
|
|
5470
5524
|
print(`${green("linked")} .design \u2192 ${bold(project.name)} ${dim(`(${project.id}, ${project.role})`)}${replaced}`);
|
|
5471
5525
|
}
|
|
@@ -5493,7 +5547,7 @@ async function runLink(paths, wanted, options) {
|
|
|
5493
5547
|
|
|
5494
5548
|
// src/cli/commands/icon.ts
|
|
5495
5549
|
import fs31 from "fs";
|
|
5496
|
-
import
|
|
5550
|
+
import path29 from "path";
|
|
5497
5551
|
async function toCloud(paths, file) {
|
|
5498
5552
|
const link = readLink(paths);
|
|
5499
5553
|
if (!link) return;
|
|
@@ -5504,7 +5558,7 @@ async function toCloud(paths, file) {
|
|
|
5504
5558
|
}
|
|
5505
5559
|
const client = new CloudClient(link.host, credential.token);
|
|
5506
5560
|
try {
|
|
5507
|
-
const ext = file ?
|
|
5561
|
+
const ext = file ? path29.extname(file).slice(1) : null;
|
|
5508
5562
|
await client.setProjectIcon(
|
|
5509
5563
|
link.project,
|
|
5510
5564
|
file && ext ? { data: fs31.readFileSync(file), type: ICON_TYPES[ext] } : null
|
|
@@ -5520,14 +5574,14 @@ async function runIcon(paths, file, options) {
|
|
|
5520
5574
|
if (options.remove) {
|
|
5521
5575
|
const had = iconFile(paths);
|
|
5522
5576
|
writeIcon(paths, null);
|
|
5523
|
-
print(had ? `${green("Removed")} ${
|
|
5577
|
+
print(had ? `${green("Removed")} ${path29.relative(process.cwd(), had)}` : "The project has no icon.");
|
|
5524
5578
|
await toCloud(paths, null);
|
|
5525
5579
|
return;
|
|
5526
5580
|
}
|
|
5527
5581
|
if (!file) {
|
|
5528
5582
|
const current = iconFile(paths);
|
|
5529
5583
|
if (!current) return print("The project has no icon. Set one with `design icon <file.svg|png|webp>`.");
|
|
5530
|
-
return print(`${
|
|
5584
|
+
return print(`${path29.relative(process.cwd(), current)} ${dim(`${Math.ceil(fs31.statSync(current).size / 1024)} KB`)}`);
|
|
5531
5585
|
}
|
|
5532
5586
|
let data;
|
|
5533
5587
|
try {
|
|
@@ -5535,20 +5589,20 @@ async function runIcon(paths, file, options) {
|
|
|
5535
5589
|
} catch {
|
|
5536
5590
|
throw new CliError(`No file ${file}`);
|
|
5537
5591
|
}
|
|
5538
|
-
const problem = iconProblem(data,
|
|
5592
|
+
const problem = iconProblem(data, path29.extname(file).slice(1).toLowerCase() || void 0);
|
|
5539
5593
|
if (problem) throw new CliError(`${file}: ${problem}`);
|
|
5540
5594
|
const written = writeIcon(paths, data);
|
|
5541
|
-
print(`${green("Set")} the project icon: ${
|
|
5595
|
+
print(`${green("Set")} the project icon: ${path29.relative(process.cwd(), written)}`);
|
|
5542
5596
|
await toCloud(paths, written);
|
|
5543
5597
|
}
|
|
5544
5598
|
|
|
5545
5599
|
// src/cli/commands/import.ts
|
|
5546
5600
|
import fs33 from "fs";
|
|
5547
|
-
import
|
|
5601
|
+
import path31 from "path";
|
|
5548
5602
|
|
|
5549
5603
|
// src/import/claude-design.ts
|
|
5550
5604
|
import fs32 from "fs";
|
|
5551
|
-
import
|
|
5605
|
+
import path30 from "path";
|
|
5552
5606
|
|
|
5553
5607
|
// src/import/markup.ts
|
|
5554
5608
|
var VOID = /* @__PURE__ */ new Set([
|
|
@@ -6156,7 +6210,7 @@ function boardId(file, used) {
|
|
|
6156
6210
|
return id2;
|
|
6157
6211
|
}
|
|
6158
6212
|
function importClaudeDesign(dir, options = {}) {
|
|
6159
|
-
const canvasFile =
|
|
6213
|
+
const canvasFile = path30.join(dir, "canvas.json");
|
|
6160
6214
|
if (!fs32.existsSync(canvasFile))
|
|
6161
6215
|
throw new Error(`No canvas.json in ${dir}: point at the exported Claude Design project folder`);
|
|
6162
6216
|
const source = JSON.parse(fs32.readFileSync(canvasFile, "utf8"));
|
|
@@ -6187,7 +6241,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6187
6241
|
try {
|
|
6188
6242
|
files.set(
|
|
6189
6243
|
`${name}.jsx`,
|
|
6190
|
-
convertFile(name, fs32.readFileSync(
|
|
6244
|
+
convertFile(name, fs32.readFileSync(path30.join(dir, file), "utf8"), { known, routes, warn: warn2 })
|
|
6191
6245
|
);
|
|
6192
6246
|
} catch (error) {
|
|
6193
6247
|
warn2(`could not be converted: ${error.message}`);
|
|
@@ -6235,7 +6289,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6235
6289
|
screens++;
|
|
6236
6290
|
}
|
|
6237
6291
|
const canvas = {
|
|
6238
|
-
title: options.title ?? source.title ??
|
|
6292
|
+
title: options.title ?? source.title ?? path30.basename(path30.resolve(dir)),
|
|
6239
6293
|
description: "Imported from Claude Design.",
|
|
6240
6294
|
// The templates bring their own styles; the design system's reset would restyle them.
|
|
6241
6295
|
system: false,
|
|
@@ -6247,7 +6301,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6247
6301
|
// src/cli/commands/import.ts
|
|
6248
6302
|
var slug = (text2) => text2.normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-").toLowerCase();
|
|
6249
6303
|
function runImport(paths, from, options) {
|
|
6250
|
-
const dir =
|
|
6304
|
+
const dir = path31.resolve(from);
|
|
6251
6305
|
if (!fs33.existsSync(dir) || !fs33.statSync(dir).isDirectory()) throw new CliError(`No folder ${from}`);
|
|
6252
6306
|
let result;
|
|
6253
6307
|
try {
|
|
@@ -6255,13 +6309,13 @@ function runImport(paths, from, options) {
|
|
|
6255
6309
|
} catch (error) {
|
|
6256
6310
|
throw new CliError(error.message);
|
|
6257
6311
|
}
|
|
6258
|
-
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(
|
|
6312
|
+
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(path31.basename(dir)) || "imported");
|
|
6259
6313
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is no canvas id; pass one with --canvas <id>`);
|
|
6260
|
-
const out =
|
|
6314
|
+
const out = path31.join(paths.canvases, id2);
|
|
6261
6315
|
if (fs33.existsSync(out)) throw new CliError(`.design/canvas/${id2} exists; pick another id with --canvas <id>`);
|
|
6262
|
-
fs33.mkdirSync(
|
|
6263
|
-
for (const [name, content] of result.files) fs33.writeFileSync(
|
|
6264
|
-
fs33.writeFileSync(
|
|
6316
|
+
fs33.mkdirSync(path31.join(out, "screens"), { recursive: true });
|
|
6317
|
+
for (const [name, content] of result.files) fs33.writeFileSync(path31.join(out, "screens", name), content);
|
|
6318
|
+
fs33.writeFileSync(path31.join(out, "canvas.json"), `${JSON.stringify(result.canvas, null, 2)}
|
|
6265
6319
|
`);
|
|
6266
6320
|
print(`${green("Imported")} ${bold(String(result.canvas.title))} \u2192 .design/canvas/${id2}`);
|
|
6267
6321
|
print(
|
|
@@ -6281,7 +6335,7 @@ function runImport(paths, from, options) {
|
|
|
6281
6335
|
|
|
6282
6336
|
// src/cli/commands/merge.ts
|
|
6283
6337
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6284
|
-
import
|
|
6338
|
+
import path32 from "path";
|
|
6285
6339
|
import { createInterface } from "readline/promises";
|
|
6286
6340
|
var KIND = {
|
|
6287
6341
|
text: "both sides changed lines, the markers show them",
|
|
@@ -6297,7 +6351,7 @@ function matchTargets(paths, states, targets) {
|
|
|
6297
6351
|
for (const conflict of openConflicts(paths, unit)) picked.push({ state: unit, conflict });
|
|
6298
6352
|
continue;
|
|
6299
6353
|
}
|
|
6300
|
-
const rel = [
|
|
6354
|
+
const rel = [path32.relative(paths.design, path32.resolve(target)), target.replace(/^\.design\//, "")];
|
|
6301
6355
|
const found = states.flatMap(
|
|
6302
6356
|
(state) => state.conflicts.filter((item) => rel.includes(item.path)).map((conflict) => ({ state, conflict }))
|
|
6303
6357
|
);
|
|
@@ -6313,7 +6367,7 @@ function printOpen(paths, states) {
|
|
|
6313
6367
|
print(`${bold(state.unit)} ${dim(`merged with cloud r${state.headRev}`)}`);
|
|
6314
6368
|
for (const conflict of open2) {
|
|
6315
6369
|
print(` ${red("\u2717")} ${conflict.path} ${dim(KIND[conflict.kind])}`);
|
|
6316
|
-
const sides = ["here", "cloud", "base"].map((side) => `${side}: ${
|
|
6370
|
+
const sides = ["here", "cloud", "base"].map((side) => `${side}: ${path32.relative(process.cwd(), sideFile(paths, state.unit, conflict.path, side))}`).join(" \xB7 ");
|
|
6317
6371
|
print(` ${dim(sides)}`);
|
|
6318
6372
|
}
|
|
6319
6373
|
}
|
|
@@ -6394,7 +6448,7 @@ async function runMerge(paths, targets, options) {
|
|
|
6394
6448
|
if (answer === "s") break;
|
|
6395
6449
|
if (answer === "e" && edit) {
|
|
6396
6450
|
const editor = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
6397
|
-
spawnSync2(editor, [
|
|
6451
|
+
spawnSync2(editor, [path32.join(paths.design, conflict.path)], { stdio: "inherit", shell: true });
|
|
6398
6452
|
if (!openConflicts(paths, state).some((item) => item.path === conflict.path)) break;
|
|
6399
6453
|
print(yellow(" Conflict markers are still there."));
|
|
6400
6454
|
}
|
|
@@ -6438,7 +6492,7 @@ async function runRename(paths, name) {
|
|
|
6438
6492
|
|
|
6439
6493
|
// src/cli/commands/scaffold.ts
|
|
6440
6494
|
import fs35 from "fs";
|
|
6441
|
-
import
|
|
6495
|
+
import path33 from "path";
|
|
6442
6496
|
|
|
6443
6497
|
// src/cli/templates.ts
|
|
6444
6498
|
var TOKENS_CSS = `/*
|
|
@@ -6650,16 +6704,16 @@ function mainScreen(canvasId) {
|
|
|
6650
6704
|
// src/cli/commands/scaffold.ts
|
|
6651
6705
|
function write(file, content, root) {
|
|
6652
6706
|
if (fs35.existsSync(file)) {
|
|
6653
|
-
print(` ${dim("exists ")} ${
|
|
6707
|
+
print(` ${dim("exists ")} ${path33.relative(root, file)}`);
|
|
6654
6708
|
return false;
|
|
6655
6709
|
}
|
|
6656
|
-
fs35.mkdirSync(
|
|
6710
|
+
fs35.mkdirSync(path33.dirname(file), { recursive: true });
|
|
6657
6711
|
fs35.writeFileSync(file, content);
|
|
6658
|
-
print(` ${green("created")} ${
|
|
6712
|
+
print(` ${green("created")} ${path33.relative(root, file)}`);
|
|
6659
6713
|
return true;
|
|
6660
6714
|
}
|
|
6661
6715
|
function ensureGitignored(root) {
|
|
6662
|
-
const file =
|
|
6716
|
+
const file = path33.join(root, ".gitignore");
|
|
6663
6717
|
const current = fs35.existsSync(file) ? fs35.readFileSync(file, "utf8") : "";
|
|
6664
6718
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
6665
6719
|
if (covered) return "present";
|
|
@@ -6671,11 +6725,11 @@ function ensureGitignored(root) {
|
|
|
6671
6725
|
}
|
|
6672
6726
|
function projectName(root) {
|
|
6673
6727
|
try {
|
|
6674
|
-
const manifest = JSON.parse(fs35.readFileSync(
|
|
6728
|
+
const manifest = JSON.parse(fs35.readFileSync(path33.join(root, "package.json"), "utf8"));
|
|
6675
6729
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
6676
6730
|
} catch {
|
|
6677
6731
|
}
|
|
6678
|
-
return
|
|
6732
|
+
return path33.basename(root);
|
|
6679
6733
|
}
|
|
6680
6734
|
function runInit(root, options) {
|
|
6681
6735
|
const paths = designPaths(root);
|
|
@@ -6700,40 +6754,40 @@ function runSystemInit(root, options) {
|
|
|
6700
6754
|
} catch {
|
|
6701
6755
|
}
|
|
6702
6756
|
const name = options.name ?? `${base} design system`;
|
|
6703
|
-
print(`Scaffolding the design system in ${
|
|
6704
|
-
write(
|
|
6757
|
+
print(`Scaffolding the design system in ${path33.relative(root, paths.system)}`);
|
|
6758
|
+
write(path33.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
6705
6759
|
`, root);
|
|
6706
|
-
write(
|
|
6760
|
+
write(path33.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
6707
6761
|
if (!options.empty) {
|
|
6708
|
-
write(
|
|
6709
|
-
write(
|
|
6710
|
-
write(
|
|
6762
|
+
write(path33.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
6763
|
+
write(path33.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
6764
|
+
write(path33.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
6711
6765
|
}
|
|
6712
6766
|
}
|
|
6713
6767
|
function runNew(root, id2, options) {
|
|
6714
6768
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
6715
6769
|
const paths = designPaths(root);
|
|
6716
|
-
const dir =
|
|
6717
|
-
if (fs35.existsSync(
|
|
6770
|
+
const dir = path33.join(paths.canvases, id2);
|
|
6771
|
+
if (fs35.existsSync(path33.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
6718
6772
|
print(`Creating canvas "${id2}"`);
|
|
6719
6773
|
const title = options.title ?? titleize(id2);
|
|
6720
6774
|
if (options.empty) {
|
|
6721
6775
|
write(
|
|
6722
|
-
|
|
6776
|
+
path33.join(dir, "canvas.json"),
|
|
6723
6777
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
6724
6778
|
`,
|
|
6725
6779
|
root
|
|
6726
6780
|
);
|
|
6727
|
-
fs35.mkdirSync(
|
|
6781
|
+
fs35.mkdirSync(path33.join(dir, "screens"), { recursive: true });
|
|
6728
6782
|
} else {
|
|
6729
|
-
write(
|
|
6730
|
-
write(
|
|
6783
|
+
write(path33.join(dir, "canvas.json"), canvasJson(title), root);
|
|
6784
|
+
write(path33.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
6731
6785
|
}
|
|
6732
6786
|
}
|
|
6733
6787
|
|
|
6734
6788
|
// src/cli/commands/shot.ts
|
|
6735
6789
|
import fs36 from "fs";
|
|
6736
|
-
import
|
|
6790
|
+
import path34 from "path";
|
|
6737
6791
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
6738
6792
|
async function runShot(paths, target, options) {
|
|
6739
6793
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -6749,8 +6803,8 @@ async function runShot(paths, target, options) {
|
|
|
6749
6803
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
6750
6804
|
}
|
|
6751
6805
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
6752
|
-
const outBase =
|
|
6753
|
-
const outDir =
|
|
6806
|
+
const outBase = path34.resolve(options.out ?? paths.shots);
|
|
6807
|
+
const outDir = path34.join(outBase, canvasId);
|
|
6754
6808
|
fs36.mkdirSync(outDir, { recursive: true });
|
|
6755
6809
|
const base = await serverBase(paths);
|
|
6756
6810
|
const browser = await launchChrome();
|
|
@@ -6773,7 +6827,7 @@ async function runShot(paths, target, options) {
|
|
|
6773
6827
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
6774
6828
|
}
|
|
6775
6829
|
await page.waitForTimeout(400);
|
|
6776
|
-
const file =
|
|
6830
|
+
const file = path34.join(outDir, `${pageId}.overview.png`);
|
|
6777
6831
|
await page.screenshot({ path: file });
|
|
6778
6832
|
await page.close();
|
|
6779
6833
|
print(file);
|
|
@@ -6802,7 +6856,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
6802
6856
|
let failed = false;
|
|
6803
6857
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
6804
6858
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
6805
|
-
const file =
|
|
6859
|
+
const file = path34.join(outDir, `${job.id}.${job.theme}.png`);
|
|
6806
6860
|
try {
|
|
6807
6861
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
6808
6862
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -6834,7 +6888,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
6834
6888
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
6835
6889
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
6836
6890
|
}
|
|
6837
|
-
const outDir =
|
|
6891
|
+
const outDir = path34.join(path34.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
6838
6892
|
fs36.mkdirSync(outDir, { recursive: true });
|
|
6839
6893
|
const base = await serverBase(paths);
|
|
6840
6894
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
@@ -6859,6 +6913,16 @@ async function runSnapshots(paths, targets, options) {
|
|
|
6859
6913
|
}
|
|
6860
6914
|
if (options.page && picked.size !== 1)
|
|
6861
6915
|
throw new CliError("--page needs exactly one canvas: `design snapshots <canvas> --page <id>`");
|
|
6916
|
+
const project = new DesignProject(paths, DEV_URLS);
|
|
6917
|
+
for (const [canvas, screens] of picked) {
|
|
6918
|
+
if (!screens) continue;
|
|
6919
|
+
const ids = new Set(project.canvas(canvas).screens.map((screen) => screen.id));
|
|
6920
|
+
const missing = [...screens].filter((id2) => !ids.has(id2));
|
|
6921
|
+
if (missing.length)
|
|
6922
|
+
throw new CliError(
|
|
6923
|
+
`No screen ${missing.map((id2) => `"${id2}"`).join(", ")} in "${canvas}". Screens: ${[...ids].slice(0, 30).join(", ")}${ids.size > 30 ? ", \u2026" : ""}`
|
|
6924
|
+
);
|
|
6925
|
+
}
|
|
6862
6926
|
const canvases = picked.size ? [...picked.keys()] : known;
|
|
6863
6927
|
const stale = canvases.flatMap(
|
|
6864
6928
|
(canvas) => staleSnapshots(paths, [canvas], { page: options.page, screens: picked.get(canvas) ?? void 0 })
|
|
@@ -6875,7 +6939,7 @@ async function runSnapshots(paths, targets, options) {
|
|
|
6875
6939
|
}
|
|
6876
6940
|
|
|
6877
6941
|
// src/cli/commands/cloud-status.ts
|
|
6878
|
-
import
|
|
6942
|
+
import path35 from "path";
|
|
6879
6943
|
var STATE_TEXT = {
|
|
6880
6944
|
in_sync: green,
|
|
6881
6945
|
local_changes: yellow,
|
|
@@ -6898,7 +6962,7 @@ function detail(paths, status) {
|
|
|
6898
6962
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
6899
6963
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
6900
6964
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
6901
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
6965
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path35.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
6902
6966
|
return parts.join(" \xB7 ");
|
|
6903
6967
|
}
|
|
6904
6968
|
async function printCloudStatus(paths, label) {
|
|
@@ -7093,7 +7157,7 @@ ${bold("Environment")}
|
|
|
7093
7157
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
7094
7158
|
`;
|
|
7095
7159
|
function projectPaths(rootOption, requireDesign = true) {
|
|
7096
|
-
const found = rootOption ?
|
|
7160
|
+
const found = rootOption ? path36.resolve(rootOption) : findProjectRoot();
|
|
7097
7161
|
if (!found) {
|
|
7098
7162
|
if (!requireDesign) return designPaths(process.cwd());
|
|
7099
7163
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -7146,7 +7210,7 @@ async function main(argv) {
|
|
|
7146
7210
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
7147
7211
|
switch (command) {
|
|
7148
7212
|
case "init":
|
|
7149
|
-
return runInit(values.root ?
|
|
7213
|
+
return runInit(values.root ? path36.resolve(values.root) : process.cwd(), {
|
|
7150
7214
|
name: values.name,
|
|
7151
7215
|
gitignore: !values["no-gitignore"]
|
|
7152
7216
|
});
|
|
@@ -7258,7 +7322,7 @@ function relaunchFromRealPath() {
|
|
|
7258
7322
|
} catch {
|
|
7259
7323
|
return false;
|
|
7260
7324
|
}
|
|
7261
|
-
const same = (a, b) =>
|
|
7325
|
+
const same = (a, b) => path36.resolve(a).toLowerCase() === path36.resolve(b).toLowerCase();
|
|
7262
7326
|
if (same(realScript, script) && same(realCwd, process.cwd())) return false;
|
|
7263
7327
|
const result = spawnSync3(process.execPath, [...process.execArgv, realScript, ...process.argv.slice(2)], {
|
|
7264
7328
|
stdio: "inherit",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foss-design",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "Design canvases and design systems for coding agents: live, animated, clickable screens on an infinite canvas, served locally from a .design folder.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|