foss-design 0.9.6 → 0.9.8
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 +276 -179
- package/dist/viewer/assets/{index-Cg9mKtjz.js → index-6h0AwUgn.js} +49 -49
- package/dist/viewer/assets/{index-oN46u7re.css → index-DnxkAHbH.css} +1 -1
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +51 -2
- package/dist/viewer-lib/styles.css +11 -1
- package/dist/viewer-lib/types/viewer/src/ui/menu.d.ts +12 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3029,11 +3029,20 @@ var byUnit = (a, b) => a === SYSTEM_UNIT ? -1 : b === SYSTEM_UNIT ? 1 : a < b ?
|
|
|
3029
3029
|
var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
3030
3030
|
|
|
3031
3031
|
// src/capture/cover.ts
|
|
3032
|
-
import
|
|
3032
|
+
import fs20 from "fs";
|
|
3033
|
+
import os3 from "os";
|
|
3033
3034
|
|
|
3034
3035
|
// src/server/snapshots.ts
|
|
3036
|
+
import { createHash as createHash5 } from "crypto";
|
|
3035
3037
|
import fs17 from "fs";
|
|
3036
3038
|
import path17 from "path";
|
|
3039
|
+
function sourceHash(file) {
|
|
3040
|
+
try {
|
|
3041
|
+
return createHash5("sha1").update(fs17.readFileSync(file)).digest("hex").slice(0, 16);
|
|
3042
|
+
} catch {
|
|
3043
|
+
return null;
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3037
3046
|
var SAFE = /^@?[a-z0-9][a-z0-9_-]*$/i;
|
|
3038
3047
|
function isSafeSegment(value) {
|
|
3039
3048
|
return SAFE.test(value);
|
|
@@ -3049,6 +3058,12 @@ var SnapshotStore = class {
|
|
|
3049
3058
|
metaFile(canvas) {
|
|
3050
3059
|
return path17.join(this.dir, canvas, "meta.json");
|
|
3051
3060
|
}
|
|
3061
|
+
/** Written aside and renamed: a run stopped mid-write leaves the old file, not half of one. */
|
|
3062
|
+
writeMeta(canvas, data) {
|
|
3063
|
+
const temp = `${this.metaFile(canvas)}.${process.pid}.tmp`;
|
|
3064
|
+
fs17.writeFileSync(temp, JSON.stringify(data));
|
|
3065
|
+
fs17.renameSync(temp, this.metaFile(canvas));
|
|
3066
|
+
}
|
|
3052
3067
|
load(canvas) {
|
|
3053
3068
|
const cached = this.meta.get(canvas);
|
|
3054
3069
|
if (cached) return cached;
|
|
@@ -3083,15 +3098,35 @@ var SnapshotStore = class {
|
|
|
3083
3098
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return null;
|
|
3084
3099
|
return this.load(canvas)[id2]?.[theme2] ?? null;
|
|
3085
3100
|
}
|
|
3086
|
-
|
|
3101
|
+
/** The source hash recorded with the snapshot; undefined for snapshots taken before hashes were kept. */
|
|
3102
|
+
source(canvas, id2, theme2) {
|
|
3103
|
+
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return void 0;
|
|
3104
|
+
return this.load(canvas)[id2]?.sources?.[theme2];
|
|
3105
|
+
}
|
|
3106
|
+
/** Record `hash` for a snapshot taken before hashes were kept: from now on it counts as that content's. */
|
|
3107
|
+
adopt(canvas, id2, theme2, hash) {
|
|
3108
|
+
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) return;
|
|
3109
|
+
const data = this.load(canvas);
|
|
3110
|
+
const entry = data[id2];
|
|
3111
|
+
if (!entry?.[theme2]) return;
|
|
3112
|
+
entry.sources = { ...entry.sources, [theme2]: hash };
|
|
3113
|
+
this.writeMeta(canvas, data);
|
|
3114
|
+
}
|
|
3115
|
+
save(canvas, id2, theme2, png, height, source) {
|
|
3087
3116
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3088
3117
|
const dir = path17.join(this.dir, canvas);
|
|
3089
3118
|
fs17.mkdirSync(dir, { recursive: true });
|
|
3090
|
-
|
|
3119
|
+
const file = path17.join(dir, `${id2}.${theme2}.png`);
|
|
3120
|
+
fs17.writeFileSync(`${file}.${process.pid}.tmp`, png);
|
|
3121
|
+
fs17.renameSync(`${file}.${process.pid}.tmp`, file);
|
|
3091
3122
|
const data = this.load(canvas);
|
|
3092
3123
|
const version = Date.now();
|
|
3093
|
-
|
|
3094
|
-
|
|
3124
|
+
const previous = data[id2];
|
|
3125
|
+
const sources = { ...previous?.sources };
|
|
3126
|
+
if (source) sources[theme2] = source;
|
|
3127
|
+
else delete sources[theme2];
|
|
3128
|
+
data[id2] = { ...previous, [theme2]: version, ...height ? { height } : {}, sources };
|
|
3129
|
+
this.writeMeta(canvas, data);
|
|
3095
3130
|
return { url: this.url(canvas, id2, theme2, version) };
|
|
3096
3131
|
}
|
|
3097
3132
|
/** Absolute path of `<id>.<theme>.png`, or of its thumbnail for `<id>.<theme>.thumb.png` (made on first use); null when missing. */
|
|
@@ -3104,8 +3139,54 @@ var SnapshotStore = class {
|
|
|
3104
3139
|
}
|
|
3105
3140
|
};
|
|
3106
3141
|
|
|
3107
|
-
// src/capture/
|
|
3142
|
+
// src/capture/build-server.ts
|
|
3108
3143
|
import fs18 from "fs";
|
|
3144
|
+
import http from "http";
|
|
3145
|
+
import path18 from "path";
|
|
3146
|
+
import sirv from "sirv";
|
|
3147
|
+
async function serveBuild(paths, name, options) {
|
|
3148
|
+
const out = path18.join(paths.cache, name);
|
|
3149
|
+
fs18.rmSync(out, { recursive: true, force: true });
|
|
3150
|
+
await buildSite(paths, out, {
|
|
3151
|
+
canvases: options.canvases,
|
|
3152
|
+
includeSystem: options.includeSystem,
|
|
3153
|
+
includeViewer: false
|
|
3154
|
+
});
|
|
3155
|
+
const files = sirv(out, { dev: true });
|
|
3156
|
+
const server = http.createServer((req, res) => {
|
|
3157
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3158
|
+
const segments = url.pathname.split("/").map((segment) => {
|
|
3159
|
+
try {
|
|
3160
|
+
return decodeURIComponent(segment);
|
|
3161
|
+
} catch {
|
|
3162
|
+
return segment;
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
req.url = segments.join("/") + url.search;
|
|
3166
|
+
files(req, res, () => {
|
|
3167
|
+
res.statusCode = 404;
|
|
3168
|
+
res.end("Not found");
|
|
3169
|
+
});
|
|
3170
|
+
});
|
|
3171
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
3172
|
+
return {
|
|
3173
|
+
base: `http://127.0.0.1:${server.address().port}`,
|
|
3174
|
+
read(rel) {
|
|
3175
|
+
try {
|
|
3176
|
+
return JSON.parse(fs18.readFileSync(path18.join(out, rel), "utf8"));
|
|
3177
|
+
} catch {
|
|
3178
|
+
return null;
|
|
3179
|
+
}
|
|
3180
|
+
},
|
|
3181
|
+
close: () => new Promise((resolve) => {
|
|
3182
|
+
server.closeAllConnections();
|
|
3183
|
+
server.close(() => resolve());
|
|
3184
|
+
})
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// src/capture/chrome.ts
|
|
3189
|
+
import fs19 from "fs";
|
|
3109
3190
|
var CANDIDATES = {
|
|
3110
3191
|
darwin: [
|
|
3111
3192
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -3129,8 +3210,8 @@ var CANDIDATES = {
|
|
|
3129
3210
|
};
|
|
3130
3211
|
function findChrome() {
|
|
3131
3212
|
const configured = process.env.DESIGN_CHROME;
|
|
3132
|
-
if (configured &&
|
|
3133
|
-
for (const candidate of CANDIDATES[process.platform] ?? []) if (
|
|
3213
|
+
if (configured && fs19.existsSync(configured)) return configured;
|
|
3214
|
+
for (const candidate of CANDIDATES[process.platform] ?? []) if (fs19.existsSync(candidate)) return candidate;
|
|
3134
3215
|
return null;
|
|
3135
3216
|
}
|
|
3136
3217
|
async function launchChrome() {
|
|
@@ -3161,6 +3242,8 @@ async function openFrame(page, url, options = {}) {
|
|
|
3161
3242
|
record2(text2);
|
|
3162
3243
|
});
|
|
3163
3244
|
let ready = false;
|
|
3245
|
+
let loadedAt = 0;
|
|
3246
|
+
let readyMs;
|
|
3164
3247
|
let reloads = 0;
|
|
3165
3248
|
const onNavigate = () => {
|
|
3166
3249
|
reloads++;
|
|
@@ -3173,6 +3256,7 @@ async function openFrame(page, url, options = {}) {
|
|
|
3173
3256
|
outdated = () => resolve("outdated");
|
|
3174
3257
|
});
|
|
3175
3258
|
await page.goto(url, { waitUntil: "load", timeout: options.timeoutMs ?? 3e4 });
|
|
3259
|
+
loadedAt = Date.now();
|
|
3176
3260
|
page.on("load", onNavigate);
|
|
3177
3261
|
if (!ready) {
|
|
3178
3262
|
const signal = page.waitForFunction(() => window.__DESIGN_READY__ === true, null, {
|
|
@@ -3181,6 +3265,7 @@ async function openFrame(page, url, options = {}) {
|
|
|
3181
3265
|
signal.catch(() => {
|
|
3182
3266
|
});
|
|
3183
3267
|
ready = await Promise.race([signal, turnedAway]).catch(() => null) === "ready";
|
|
3268
|
+
if (ready) readyMs = Date.now() - loadedAt;
|
|
3184
3269
|
}
|
|
3185
3270
|
if (ready || options.waitForReady === false) await page.waitForTimeout(options.settleMs ?? 700);
|
|
3186
3271
|
page.off("load", onNavigate);
|
|
@@ -3198,7 +3283,12 @@ async function openFrame(page, url, options = {}) {
|
|
|
3198
3283
|
const unique2 = errors.filter(
|
|
3199
3284
|
(error, index) => error && !errors.some((other, j) => j !== index && other && other.length < error.length && error.includes(other))
|
|
3200
3285
|
);
|
|
3201
|
-
return {
|
|
3286
|
+
return {
|
|
3287
|
+
errors: [...new Set(unique2)],
|
|
3288
|
+
ready,
|
|
3289
|
+
...held ? { held } : {},
|
|
3290
|
+
...readyMs !== void 0 ? { readyMs } : {}
|
|
3291
|
+
};
|
|
3202
3292
|
}
|
|
3203
3293
|
|
|
3204
3294
|
// src/capture/frames.ts
|
|
@@ -3275,7 +3365,7 @@ async function mapLimit(items, limit, fn) {
|
|
|
3275
3365
|
|
|
3276
3366
|
// src/capture/cover.ts
|
|
3277
3367
|
var SNAPSHOT_WIDTH = 900;
|
|
3278
|
-
var TABS =
|
|
3368
|
+
var TABS = Math.min(8, Math.max(2, os3.availableParallelism() - 1));
|
|
3279
3369
|
var THEMES2 = ["light", "dark"];
|
|
3280
3370
|
function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
3281
3371
|
const store = new SnapshotStore(paths.snapshots);
|
|
@@ -3292,25 +3382,43 @@ function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
|
3292
3382
|
for (const job of inLight) {
|
|
3293
3383
|
if (filter.screens && !filter.screens.has(job.id)) continue;
|
|
3294
3384
|
const file = files.get(job.id);
|
|
3295
|
-
if (!file || !
|
|
3296
|
-
const
|
|
3385
|
+
if (!file || !fs20.existsSync(file)) continue;
|
|
3386
|
+
const hash = sourceHash(file);
|
|
3297
3387
|
const pinned = inDark.get(job.id) === job.theme;
|
|
3298
3388
|
const themes = pinned ? [job.theme] : THEMES2;
|
|
3299
|
-
const screen = { id: job.id };
|
|
3300
3389
|
for (const theme2 of themes) {
|
|
3301
|
-
|
|
3302
|
-
|
|
3390
|
+
if (!store.version(id2, job.id, theme2)) {
|
|
3391
|
+
stale.push({ canvas: id2, screen: job.id, theme: theme2, source: hash });
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
const recorded = store.source(id2, job.id, theme2);
|
|
3395
|
+
if (recorded === void 0) {
|
|
3396
|
+
if (hash) store.adopt(id2, job.id, theme2, hash);
|
|
3397
|
+
} else if (recorded !== hash) stale.push({ canvas: id2, screen: job.id, theme: theme2, source: hash });
|
|
3303
3398
|
}
|
|
3304
3399
|
}
|
|
3305
3400
|
}
|
|
3306
3401
|
return stale;
|
|
3307
3402
|
}
|
|
3308
|
-
async function takeSnapshots(paths, targets,
|
|
3403
|
+
async function takeSnapshots(paths, targets, preview, progress2) {
|
|
3309
3404
|
if (!targets.length) return [];
|
|
3310
3405
|
if (!findChrome()) return ["No Chrome here, so screens keep the snapshots they have."];
|
|
3311
3406
|
const store = new SnapshotStore(paths.snapshots);
|
|
3312
3407
|
const project = new DesignProject(paths, DEV_URLS, store.lookup);
|
|
3313
|
-
const
|
|
3408
|
+
const canvases = [...new Set(targets.map((target) => target.canvas))];
|
|
3409
|
+
progress2.update(`Building ${canvases.join(", ")} to take snapshots from\u2026`);
|
|
3410
|
+
let url;
|
|
3411
|
+
let docOf = (id2) => project.canvas(id2).doc;
|
|
3412
|
+
let stop = async () => {
|
|
3413
|
+
};
|
|
3414
|
+
try {
|
|
3415
|
+
const build2 = await serveBuild(paths, "snapshot-build", { canvases, includeSystem: false });
|
|
3416
|
+
url = build2.base;
|
|
3417
|
+
docOf = (id2) => build2.read(`api/canvas/${id2}.json`) ?? project.canvas(id2).doc;
|
|
3418
|
+
stop = build2.close;
|
|
3419
|
+
} catch {
|
|
3420
|
+
url = await preview();
|
|
3421
|
+
}
|
|
3314
3422
|
const browser = await launchChrome();
|
|
3315
3423
|
const failed = [];
|
|
3316
3424
|
const started = Date.now();
|
|
@@ -3318,8 +3426,8 @@ async function takeSnapshots(paths, targets, base, progress2) {
|
|
|
3318
3426
|
const line = () => `Snapshots ${done}/${targets.length} (both themes, as the viewer and the cloud show them)`;
|
|
3319
3427
|
progress2.update(line());
|
|
3320
3428
|
try {
|
|
3321
|
-
await mapLimit([...targets], TABS, async ({ canvas, screen, theme: theme2 }) => {
|
|
3322
|
-
const job = canvasFrames(
|
|
3429
|
+
await mapLimit([...targets], TABS, async ({ canvas, screen, theme: theme2, source }) => {
|
|
3430
|
+
const job = canvasFrames(docOf(canvas), url, theme2, { id: screen })[0];
|
|
3323
3431
|
if (job) {
|
|
3324
3432
|
const page = await browser.newPage({
|
|
3325
3433
|
viewport: { width: job.width, height: job.height },
|
|
@@ -3331,7 +3439,7 @@ async function takeSnapshots(paths, targets, base, progress2) {
|
|
|
3331
3439
|
if (report.ready) {
|
|
3332
3440
|
const png = await page.screenshot({ fullPage: job.fullPage });
|
|
3333
3441
|
const height = job.fullPage ? await page.evaluate(() => document.documentElement.scrollHeight) : void 0;
|
|
3334
|
-
store.save(canvas, screen, theme2, png, height);
|
|
3442
|
+
store.save(canvas, screen, theme2, png, height, source);
|
|
3335
3443
|
} else failed.push(`${canvas}/${screen} (${theme2})`);
|
|
3336
3444
|
} catch {
|
|
3337
3445
|
failed.push(`${canvas}/${screen} (${theme2})`);
|
|
@@ -3345,6 +3453,7 @@ async function takeSnapshots(paths, targets, base, progress2) {
|
|
|
3345
3453
|
});
|
|
3346
3454
|
} finally {
|
|
3347
3455
|
await browser.close();
|
|
3456
|
+
await stop();
|
|
3348
3457
|
}
|
|
3349
3458
|
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
3350
3459
|
progress2.done(`Took ${targets.length - failed.length} snapshot${targets.length === 1 ? "" : "s"} in ${seconds2}s`);
|
|
@@ -3356,73 +3465,73 @@ async function takeSnapshots(paths, targets, base, progress2) {
|
|
|
3356
3465
|
}
|
|
3357
3466
|
|
|
3358
3467
|
// src/cloud/pull.ts
|
|
3359
|
-
import
|
|
3360
|
-
import
|
|
3468
|
+
import fs24 from "fs";
|
|
3469
|
+
import path22 from "path";
|
|
3361
3470
|
|
|
3362
3471
|
// src/cloud/merge.ts
|
|
3363
|
-
import
|
|
3364
|
-
import
|
|
3472
|
+
import fs22 from "fs";
|
|
3473
|
+
import path20 from "path";
|
|
3365
3474
|
import { merge as diff3 } from "node-diff3";
|
|
3366
3475
|
|
|
3367
3476
|
// src/cloud/write.ts
|
|
3368
|
-
import
|
|
3369
|
-
import
|
|
3477
|
+
import fs21 from "fs";
|
|
3478
|
+
import path19 from "path";
|
|
3370
3479
|
function writeInside(root, rel, data) {
|
|
3371
|
-
const file =
|
|
3480
|
+
const file = path19.join(root, rel);
|
|
3372
3481
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
3373
|
-
|
|
3374
|
-
if (!isInside(
|
|
3482
|
+
fs21.mkdirSync(path19.dirname(file), { recursive: true });
|
|
3483
|
+
if (!isInside(fs21.realpathSync(root), fs21.realpathSync(path19.dirname(file))))
|
|
3375
3484
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
3376
|
-
if (
|
|
3377
|
-
|
|
3485
|
+
if (fs21.existsSync(file) && fs21.lstatSync(file).isSymbolicLink()) fs21.unlinkSync(file);
|
|
3486
|
+
fs21.writeFileSync(file, data);
|
|
3378
3487
|
}
|
|
3379
3488
|
function pruneEmpty(dir, stop) {
|
|
3380
3489
|
let current = dir;
|
|
3381
3490
|
while (isInside(stop, current) && current !== stop) {
|
|
3382
3491
|
try {
|
|
3383
|
-
if (
|
|
3384
|
-
|
|
3492
|
+
if (fs21.readdirSync(current).length) return;
|
|
3493
|
+
fs21.rmdirSync(current);
|
|
3385
3494
|
} catch {
|
|
3386
3495
|
return;
|
|
3387
3496
|
}
|
|
3388
|
-
current =
|
|
3497
|
+
current = path19.dirname(current);
|
|
3389
3498
|
}
|
|
3390
3499
|
}
|
|
3391
3500
|
|
|
3392
3501
|
// src/cloud/merge.ts
|
|
3393
3502
|
var TEXT = /\.(?:[cm]?[jt]sx?|json|css|scss|html?|mdx?|svg|txt|ya?ml|csv|xml|toml)$/i;
|
|
3394
3503
|
var isText = (rel, ...data) => TEXT.test(rel) && data.every((buffer) => !buffer || !buffer.subarray(0, 8192).includes(0));
|
|
3395
|
-
var mergeDir = (paths, key) =>
|
|
3396
|
-
var stateFile = (paths, key) =>
|
|
3397
|
-
var sideFile = (paths, key, rel, side) =>
|
|
3504
|
+
var mergeDir = (paths, key) => path20.join(cloudCache(paths), "merge", ...key.split("/"));
|
|
3505
|
+
var stateFile = (paths, key) => path20.join(mergeDir(paths, key), "state.json");
|
|
3506
|
+
var sideFile = (paths, key, rel, side) => path20.join(mergeDir(paths, key), side, rel);
|
|
3398
3507
|
function readMergeState(paths, key) {
|
|
3399
3508
|
try {
|
|
3400
|
-
return JSON.parse(
|
|
3509
|
+
return JSON.parse(fs22.readFileSync(stateFile(paths, key), "utf8"));
|
|
3401
3510
|
} catch {
|
|
3402
3511
|
return null;
|
|
3403
3512
|
}
|
|
3404
3513
|
}
|
|
3405
3514
|
function saveState(paths, state) {
|
|
3406
|
-
|
|
3407
|
-
|
|
3515
|
+
fs22.mkdirSync(mergeDir(paths, state.unit), { recursive: true });
|
|
3516
|
+
fs22.writeFileSync(stateFile(paths, state.unit), `${JSON.stringify(state, null, 2)}
|
|
3408
3517
|
`);
|
|
3409
3518
|
}
|
|
3410
3519
|
function mergingUnits(paths) {
|
|
3411
|
-
const root =
|
|
3520
|
+
const root = path20.join(cloudCache(paths), "merge");
|
|
3412
3521
|
const found = [];
|
|
3413
3522
|
const visit = (dir, prefix) => {
|
|
3414
|
-
if (
|
|
3523
|
+
if (fs22.existsSync(path20.join(dir, "state.json"))) {
|
|
3415
3524
|
const state = readMergeState(paths, prefix);
|
|
3416
3525
|
if (state) found.push(state);
|
|
3417
3526
|
return;
|
|
3418
3527
|
}
|
|
3419
3528
|
let entries = [];
|
|
3420
3529
|
try {
|
|
3421
|
-
entries =
|
|
3530
|
+
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
3422
3531
|
} catch {
|
|
3423
3532
|
}
|
|
3424
3533
|
for (const entry of entries)
|
|
3425
|
-
if (entry.isDirectory()) visit(
|
|
3534
|
+
if (entry.isDirectory()) visit(path20.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
3426
3535
|
};
|
|
3427
3536
|
visit(root, "");
|
|
3428
3537
|
return found;
|
|
@@ -3432,7 +3541,7 @@ function openConflicts(paths, state) {
|
|
|
3432
3541
|
return state.conflicts.filter((conflict) => {
|
|
3433
3542
|
if (conflict.kind !== "text") return true;
|
|
3434
3543
|
try {
|
|
3435
|
-
return MARKER.test(
|
|
3544
|
+
return MARKER.test(fs22.readFileSync(path20.join(paths.design, conflict.path), "utf8"));
|
|
3436
3545
|
} catch {
|
|
3437
3546
|
return false;
|
|
3438
3547
|
}
|
|
@@ -3441,19 +3550,19 @@ function openConflicts(paths, state) {
|
|
|
3441
3550
|
function keep(paths, key, rel, side, data) {
|
|
3442
3551
|
if (!data) return;
|
|
3443
3552
|
const file = sideFile(paths, key, rel, side);
|
|
3444
|
-
|
|
3445
|
-
|
|
3553
|
+
fs22.mkdirSync(path20.dirname(file), { recursive: true });
|
|
3554
|
+
fs22.writeFileSync(file, data);
|
|
3446
3555
|
}
|
|
3447
3556
|
function mergeUnit(paths, key, headRev, base, here, cloud2, blob) {
|
|
3448
3557
|
const files = { added: 0, changed: 0, removed: 0, merged: 0 };
|
|
3449
3558
|
const conflicts = [];
|
|
3450
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3451
|
-
|
|
3452
|
-
const read = (rel) =>
|
|
3559
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path20.dirname(unitRoot(paths, key));
|
|
3560
|
+
fs22.rmSync(mergeDir(paths, key), { recursive: true, force: true });
|
|
3561
|
+
const read = (rel) => fs22.readFileSync(path20.join(paths.design, rel));
|
|
3453
3562
|
const remove = (rel) => {
|
|
3454
|
-
const file =
|
|
3455
|
-
|
|
3456
|
-
pruneEmpty(
|
|
3563
|
+
const file = path20.join(paths.design, rel);
|
|
3564
|
+
fs22.rmSync(file, { force: true });
|
|
3565
|
+
pruneEmpty(path20.dirname(file), stop);
|
|
3457
3566
|
};
|
|
3458
3567
|
for (const rel of [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(here), ...Object.keys(cloud2)])].sort()) {
|
|
3459
3568
|
const b = base[rel];
|
|
@@ -3509,11 +3618,11 @@ function resolveConflict(paths, state, rel, side) {
|
|
|
3509
3618
|
const conflict = state.conflicts.find((item) => item.path === rel);
|
|
3510
3619
|
if (!conflict) return;
|
|
3511
3620
|
const kept = sideFile(paths, state.unit, rel, side);
|
|
3512
|
-
const stop = state.unit === SYSTEM_UNIT ? paths.design :
|
|
3513
|
-
if (
|
|
3621
|
+
const stop = state.unit === SYSTEM_UNIT ? paths.design : path20.dirname(unitRoot(paths, state.unit));
|
|
3622
|
+
if (fs22.existsSync(kept)) writeInside(paths.design, rel, fs22.readFileSync(kept));
|
|
3514
3623
|
else {
|
|
3515
|
-
|
|
3516
|
-
pruneEmpty(
|
|
3624
|
+
fs22.rmSync(path20.join(paths.design, rel), { force: true });
|
|
3625
|
+
pruneEmpty(path20.dirname(path20.join(paths.design, rel)), stop);
|
|
3517
3626
|
}
|
|
3518
3627
|
state.conflicts = state.conflicts.filter((item) => item.path !== rel);
|
|
3519
3628
|
saveState(paths, state);
|
|
@@ -3523,15 +3632,15 @@ function finishMerge(paths, link, key, accept = false) {
|
|
|
3523
3632
|
if (state) {
|
|
3524
3633
|
const open2 = openConflicts(paths, state).filter((conflict) => !(accept && conflict.kind !== "text"));
|
|
3525
3634
|
if (open2.length) return open2;
|
|
3526
|
-
|
|
3635
|
+
fs22.rmSync(mergeDir(paths, key), { recursive: true, force: true });
|
|
3527
3636
|
}
|
|
3528
3637
|
delete link.conflicts[key];
|
|
3529
3638
|
return [];
|
|
3530
3639
|
}
|
|
3531
3640
|
|
|
3532
3641
|
// src/cloud/meta.ts
|
|
3533
|
-
import
|
|
3534
|
-
import
|
|
3642
|
+
import fs23 from "fs";
|
|
3643
|
+
import path21 from "path";
|
|
3535
3644
|
async function syncProjectMeta(paths, client, link) {
|
|
3536
3645
|
const remote = await client.project(link.project);
|
|
3537
3646
|
if (!remote) return [];
|
|
@@ -3550,8 +3659,8 @@ async function syncProjectMeta(paths, client, link) {
|
|
|
3550
3659
|
if (localChanged && !cloudChanged) {
|
|
3551
3660
|
try {
|
|
3552
3661
|
if (file) {
|
|
3553
|
-
const ext =
|
|
3554
|
-
await client.setProjectIcon(link.project, { data:
|
|
3662
|
+
const ext = path21.extname(file).slice(1);
|
|
3663
|
+
await client.setProjectIcon(link.project, { data: fs23.readFileSync(file), type: ICON_TYPES[ext] });
|
|
3555
3664
|
lines.push("The project icon went up to the cloud.");
|
|
3556
3665
|
} else {
|
|
3557
3666
|
await client.setProjectIcon(link.project, null);
|
|
@@ -3586,12 +3695,12 @@ function checkManifest(key, manifest) {
|
|
|
3586
3695
|
}
|
|
3587
3696
|
function applyUnit(paths, key, local, target, blobs) {
|
|
3588
3697
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
3589
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3698
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path22.dirname(unitRoot(paths, key));
|
|
3590
3699
|
for (const rel of Object.keys(local)) {
|
|
3591
3700
|
if (rel in target) continue;
|
|
3592
|
-
const file =
|
|
3593
|
-
|
|
3594
|
-
pruneEmpty(
|
|
3701
|
+
const file = path22.join(paths.design, rel);
|
|
3702
|
+
fs24.rmSync(file, { force: true });
|
|
3703
|
+
pruneEmpty(path22.dirname(file), stop);
|
|
3595
3704
|
counts.removed++;
|
|
3596
3705
|
}
|
|
3597
3706
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -3661,7 +3770,7 @@ async function pull(ctx, options) {
|
|
|
3661
3770
|
}
|
|
3662
3771
|
if (s.conflictRev !== void 0) {
|
|
3663
3772
|
finishMerge(paths, link, key, true);
|
|
3664
|
-
|
|
3773
|
+
fs24.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3665
3774
|
}
|
|
3666
3775
|
report.units.push({ ...reportFor("up_to_date"), rev: s.baseRev });
|
|
3667
3776
|
} else if (!s.changed.length) {
|
|
@@ -3690,7 +3799,7 @@ async function pull(ctx, options) {
|
|
|
3690
3799
|
}
|
|
3691
3800
|
const byHash = /* @__PURE__ */ new Map();
|
|
3692
3801
|
for (const unit of scan.units.values())
|
|
3693
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
3802
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path22.join(paths.design, rel));
|
|
3694
3803
|
const needed = /* @__PURE__ */ new Set();
|
|
3695
3804
|
const bases = /* @__PURE__ */ new Set();
|
|
3696
3805
|
for (const plan of plans) {
|
|
@@ -3711,7 +3820,7 @@ async function pull(ctx, options) {
|
|
|
3711
3820
|
const download = [];
|
|
3712
3821
|
for (const hash of needed) {
|
|
3713
3822
|
const file = byHash.get(hash);
|
|
3714
|
-
if (file) blobs.set(hash,
|
|
3823
|
+
if (file) blobs.set(hash, fs24.readFileSync(file));
|
|
3715
3824
|
else download.push(hash);
|
|
3716
3825
|
}
|
|
3717
3826
|
if (download.length) {
|
|
@@ -3747,7 +3856,7 @@ async function pull(ctx, options) {
|
|
|
3747
3856
|
if (plan.remote) link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
3748
3857
|
else delete link.units[key];
|
|
3749
3858
|
delete link.conflicts[key];
|
|
3750
|
-
|
|
3859
|
+
fs24.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3751
3860
|
} else {
|
|
3752
3861
|
const result = mergeUnit(
|
|
3753
3862
|
paths,
|
|
@@ -3758,7 +3867,7 @@ async function pull(ctx, options) {
|
|
|
3758
3867
|
plan.remote.manifest,
|
|
3759
3868
|
(hash) => blobs.get(hash)
|
|
3760
3869
|
);
|
|
3761
|
-
|
|
3870
|
+
fs24.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3762
3871
|
link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
3763
3872
|
entry.rev = plan.remote.headRev;
|
|
3764
3873
|
entry.files = result.files;
|
|
@@ -3788,8 +3897,8 @@ async function pull(ctx, options) {
|
|
|
3788
3897
|
}
|
|
3789
3898
|
|
|
3790
3899
|
// src/cloud/push.ts
|
|
3791
|
-
import
|
|
3792
|
-
import
|
|
3900
|
+
import fs25 from "fs";
|
|
3901
|
+
import path23 from "path";
|
|
3793
3902
|
function blockedReport(status, message) {
|
|
3794
3903
|
return {
|
|
3795
3904
|
unit: status.key,
|
|
@@ -3905,7 +4014,7 @@ async function push(ctx, options) {
|
|
|
3905
4014
|
for (const key of toPush) {
|
|
3906
4015
|
const s = status(key);
|
|
3907
4016
|
if (s.conflictRev !== void 0) {
|
|
3908
|
-
const dir =
|
|
4017
|
+
const dir = path23.relative(paths.root, incomingDir(paths, key));
|
|
3909
4018
|
blocked.push(
|
|
3910
4019
|
blockedReport(
|
|
3911
4020
|
s,
|
|
@@ -3954,7 +4063,7 @@ async function push(ctx, options) {
|
|
|
3954
4063
|
const files = /* @__PURE__ */ new Map();
|
|
3955
4064
|
const addFiles = (manifest, dir) => {
|
|
3956
4065
|
for (const [rel, entry] of Object.entries(manifest))
|
|
3957
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
4066
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path23.join(dir, rel));
|
|
3958
4067
|
};
|
|
3959
4068
|
for (const key of pushing) {
|
|
3960
4069
|
const id2 = unitCanvasId(key);
|
|
@@ -3986,7 +4095,7 @@ async function push(ctx, options) {
|
|
|
3986
4095
|
if (missing.length) {
|
|
3987
4096
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
3988
4097
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
3989
|
-
const bytes = missing.reduce((sum, hash) => sum +
|
|
4098
|
+
const bytes = missing.reduce((sum, hash) => sum + fs25.statSync(files.get(hash)).size, 0);
|
|
3990
4099
|
const bar = ctx.progress();
|
|
3991
4100
|
const started = Date.now();
|
|
3992
4101
|
let sentFiles = 0;
|
|
@@ -3995,12 +4104,12 @@ async function push(ctx, options) {
|
|
|
3995
4104
|
bar.update(line());
|
|
3996
4105
|
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
3997
4106
|
const file = files.get(hash);
|
|
3998
|
-
const data =
|
|
4107
|
+
const data = fs25.readFileSync(file);
|
|
3999
4108
|
try {
|
|
4000
4109
|
await client.putBlob(link.project, hash, data);
|
|
4001
4110
|
} catch (error) {
|
|
4002
4111
|
if (error instanceof CloudError && error.status === 413)
|
|
4003
|
-
throw new CliError(`${
|
|
4112
|
+
throw new CliError(`${path23.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
4004
4113
|
throw error;
|
|
4005
4114
|
}
|
|
4006
4115
|
sentFiles++;
|
|
@@ -4033,7 +4142,7 @@ async function push(ctx, options) {
|
|
|
4033
4142
|
url: webUrl(link, unit.key)
|
|
4034
4143
|
});
|
|
4035
4144
|
}
|
|
4036
|
-
for (const key of resolved)
|
|
4145
|
+
for (const key of resolved) fs25.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
4037
4146
|
writeLink(paths, link);
|
|
4038
4147
|
report.units.sort((a, b) => byUnit(a.unit, b.unit));
|
|
4039
4148
|
return report;
|
|
@@ -4044,7 +4153,7 @@ function refuseLegacyGo(paths, keys) {
|
|
|
4044
4153
|
for (const key of keys) {
|
|
4045
4154
|
if (!key.startsWith("canvas/")) continue;
|
|
4046
4155
|
const id2 = key.slice("canvas/".length);
|
|
4047
|
-
for (const file of canvasCodeFiles(
|
|
4156
|
+
for (const file of canvasCodeFiles(path23.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
4048
4157
|
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
4049
4158
|
}
|
|
4050
4159
|
}
|
|
@@ -4060,18 +4169,18 @@ Still using go():
|
|
|
4060
4169
|
|
|
4061
4170
|
// src/cli/commands/preview.ts
|
|
4062
4171
|
import { spawn } from "child_process";
|
|
4063
|
-
import
|
|
4172
|
+
import fs29 from "fs";
|
|
4064
4173
|
|
|
4065
4174
|
// src/server/index.ts
|
|
4066
|
-
import
|
|
4067
|
-
import
|
|
4068
|
-
import
|
|
4069
|
-
import
|
|
4175
|
+
import fs28 from "fs";
|
|
4176
|
+
import http2 from "http";
|
|
4177
|
+
import path26 from "path";
|
|
4178
|
+
import sirv2 from "sirv";
|
|
4070
4179
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
4071
4180
|
|
|
4072
4181
|
// src/server/api.ts
|
|
4073
|
-
import
|
|
4074
|
-
import
|
|
4182
|
+
import fs26 from "fs";
|
|
4183
|
+
import path24 from "path";
|
|
4075
4184
|
function sendJson(res, status, body) {
|
|
4076
4185
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
4077
4186
|
res.end(JSON.stringify(body));
|
|
@@ -4122,17 +4231,17 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4122
4231
|
}
|
|
4123
4232
|
if (route2 === "/source") {
|
|
4124
4233
|
const rel = url.searchParams.get("path") ?? "";
|
|
4125
|
-
const file =
|
|
4126
|
-
if (!rel || !isInside(project.paths.root, file) || !
|
|
4234
|
+
const file = path24.resolve(project.paths.root, rel);
|
|
4235
|
+
if (!rel || !isInside(project.paths.root, file) || !fs26.existsSync(file) || fs26.statSync(file).isDirectory()) {
|
|
4127
4236
|
sendJson(res, 404, { error: "not found" });
|
|
4128
4237
|
return true;
|
|
4129
4238
|
}
|
|
4130
|
-
if (
|
|
4239
|
+
if (fs26.statSync(file).size > 2 * 1024 * 1024) {
|
|
4131
4240
|
sendJson(res, 413, { error: "file too large to show" });
|
|
4132
4241
|
return true;
|
|
4133
4242
|
}
|
|
4134
4243
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
4135
|
-
res.end(
|
|
4244
|
+
res.end(fs26.readFileSync(file, "utf8"));
|
|
4136
4245
|
return true;
|
|
4137
4246
|
}
|
|
4138
4247
|
if (route2 === "/icon" && (method === "PUT" || method === "DELETE")) {
|
|
@@ -4162,7 +4271,8 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4162
4271
|
try {
|
|
4163
4272
|
const body = await readBody(req);
|
|
4164
4273
|
if (body.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a") throw new Error("not a PNG");
|
|
4165
|
-
const
|
|
4274
|
+
const file = project.source(canvas, id2)?.file;
|
|
4275
|
+
const { url: snapshotUrl } = snapshots.save(canvas, id2, theme2, body, height, file ? sourceHash(file) : null);
|
|
4166
4276
|
project.invalidateCanvas(canvas);
|
|
4167
4277
|
events.send({ type: "snapshot", canvas, id: id2, theme: theme2, url: snapshotUrl, height });
|
|
4168
4278
|
sendJson(res, 200, { url: snapshotUrl });
|
|
@@ -4179,7 +4289,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4179
4289
|
return true;
|
|
4180
4290
|
}
|
|
4181
4291
|
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=31536000, immutable" });
|
|
4182
|
-
|
|
4292
|
+
fs26.createReadStream(file).pipe(res);
|
|
4183
4293
|
return true;
|
|
4184
4294
|
}
|
|
4185
4295
|
return false;
|
|
@@ -4219,62 +4329,62 @@ var EventHub = class {
|
|
|
4219
4329
|
};
|
|
4220
4330
|
|
|
4221
4331
|
// src/server/state.ts
|
|
4222
|
-
import
|
|
4223
|
-
import
|
|
4224
|
-
import
|
|
4225
|
-
var stateFile2 = (paths) =>
|
|
4226
|
-
var logFile = (paths) =>
|
|
4332
|
+
import fs27 from "fs";
|
|
4333
|
+
import os4 from "os";
|
|
4334
|
+
import path25 from "path";
|
|
4335
|
+
var stateFile2 = (paths) => path25.join(paths.cache, "server.json");
|
|
4336
|
+
var logFile = (paths) => path25.join(paths.cache, "server.log");
|
|
4227
4337
|
function readState(paths) {
|
|
4228
4338
|
try {
|
|
4229
|
-
return JSON.parse(
|
|
4339
|
+
return JSON.parse(fs27.readFileSync(stateFile2(paths), "utf8"));
|
|
4230
4340
|
} catch {
|
|
4231
4341
|
return null;
|
|
4232
4342
|
}
|
|
4233
4343
|
}
|
|
4234
4344
|
function registryDir() {
|
|
4235
|
-
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA ||
|
|
4236
|
-
return
|
|
4345
|
+
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA || path25.join(os4.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path25.join(os4.homedir(), ".cache");
|
|
4346
|
+
return path25.join(base, "foss-design", "previews");
|
|
4237
4347
|
}
|
|
4238
|
-
var entryFile = (pid) =>
|
|
4348
|
+
var entryFile = (pid) => path25.join(registryDir(), `${pid}.json`);
|
|
4239
4349
|
function writeState(paths, state) {
|
|
4240
|
-
|
|
4241
|
-
|
|
4350
|
+
fs27.mkdirSync(paths.cache, { recursive: true });
|
|
4351
|
+
fs27.writeFileSync(stateFile2(paths), `${JSON.stringify(state, null, 2)}
|
|
4242
4352
|
`);
|
|
4243
4353
|
try {
|
|
4244
|
-
|
|
4245
|
-
|
|
4354
|
+
fs27.mkdirSync(registryDir(), { recursive: true });
|
|
4355
|
+
fs27.writeFileSync(entryFile(state.pid), `${JSON.stringify(state, null, 2)}
|
|
4246
4356
|
`);
|
|
4247
4357
|
} catch {
|
|
4248
4358
|
}
|
|
4249
4359
|
}
|
|
4250
4360
|
function clearState(paths, pid) {
|
|
4251
4361
|
const state = readState(paths);
|
|
4252
|
-
|
|
4362
|
+
fs27.rmSync(entryFile(pid ?? state?.pid ?? -1), { force: true });
|
|
4253
4363
|
if (pid !== void 0 && state && state.pid !== pid) return;
|
|
4254
|
-
|
|
4364
|
+
fs27.rmSync(stateFile2(paths), { force: true });
|
|
4255
4365
|
}
|
|
4256
4366
|
function listPreviews() {
|
|
4257
4367
|
let files = [];
|
|
4258
4368
|
try {
|
|
4259
|
-
files =
|
|
4369
|
+
files = fs27.readdirSync(registryDir()).filter((name) => name.endsWith(".json"));
|
|
4260
4370
|
} catch {
|
|
4261
4371
|
return [];
|
|
4262
4372
|
}
|
|
4263
4373
|
const found = [];
|
|
4264
4374
|
for (const name of files) {
|
|
4265
|
-
const file =
|
|
4375
|
+
const file = path25.join(registryDir(), name);
|
|
4266
4376
|
let state = null;
|
|
4267
4377
|
try {
|
|
4268
|
-
state = JSON.parse(
|
|
4378
|
+
state = JSON.parse(fs27.readFileSync(file, "utf8"));
|
|
4269
4379
|
} catch {
|
|
4270
4380
|
}
|
|
4271
4381
|
if (!state?.pid || !pidAlive(state.pid)) {
|
|
4272
|
-
|
|
4382
|
+
fs27.rmSync(file, { force: true });
|
|
4273
4383
|
continue;
|
|
4274
4384
|
}
|
|
4275
4385
|
let recorded = null;
|
|
4276
4386
|
try {
|
|
4277
|
-
recorded = JSON.parse(
|
|
4387
|
+
recorded = JSON.parse(fs27.readFileSync(path25.join(state.root, ".design", ".cache", "server.json"), "utf8"));
|
|
4278
4388
|
} catch {
|
|
4279
4389
|
}
|
|
4280
4390
|
found.push({ ...state, tracked: recorded?.pid === state.pid });
|
|
@@ -4288,11 +4398,11 @@ async function stopPreview(entry) {
|
|
|
4288
4398
|
}
|
|
4289
4399
|
const until = Date.now() + 5e3;
|
|
4290
4400
|
while (Date.now() < until && pidAlive(entry.pid)) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4291
|
-
|
|
4292
|
-
const stateAt =
|
|
4401
|
+
fs27.rmSync(entryFile(entry.pid), { force: true });
|
|
4402
|
+
const stateAt = path25.join(entry.root, ".design", ".cache", "server.json");
|
|
4293
4403
|
try {
|
|
4294
|
-
if (JSON.parse(
|
|
4295
|
-
|
|
4404
|
+
if (JSON.parse(fs27.readFileSync(stateAt, "utf8")).pid === entry.pid)
|
|
4405
|
+
fs27.rmSync(stateAt, { force: true });
|
|
4296
4406
|
} catch {
|
|
4297
4407
|
}
|
|
4298
4408
|
}
|
|
@@ -4351,15 +4461,15 @@ function notFoundPage(res, title, text2) {
|
|
|
4351
4461
|
}
|
|
4352
4462
|
async function startDevServer(options) {
|
|
4353
4463
|
const paths = designPaths(options.root);
|
|
4354
|
-
if (!
|
|
4355
|
-
|
|
4464
|
+
if (!fs28.existsSync(paths.design)) throw new Error(`No .design folder in ${options.root}; run \`design init\``);
|
|
4465
|
+
fs28.mkdirSync(paths.cache, { recursive: true });
|
|
4356
4466
|
const snapshots = new SnapshotStore(paths.snapshots);
|
|
4357
4467
|
const project = new DesignProject(paths, DEV_URLS, snapshots.lookup);
|
|
4358
4468
|
const entries = new Entries(project);
|
|
4359
4469
|
entries.sync();
|
|
4360
4470
|
const links = linkShippedPackages(paths, project.appDir());
|
|
4361
4471
|
const events = new EventHub();
|
|
4362
|
-
const httpServer =
|
|
4472
|
+
const httpServer = http2.createServer();
|
|
4363
4473
|
dropStaleDepCache(paths, project.appDir());
|
|
4364
4474
|
const base = baseConfig(project);
|
|
4365
4475
|
const vite = await createServer({
|
|
@@ -4378,8 +4488,8 @@ async function startDevServer(options) {
|
|
|
4378
4488
|
// `.cache` is ours (server.log, snapshots, builds): a change there must not reload frames. The
|
|
4379
4489
|
// generated entries are modules the frames load, so those stay watched.
|
|
4380
4490
|
ignored: (file) => {
|
|
4381
|
-
const rel = toPosix(
|
|
4382
|
-
const inCache = rel !== "" && !rel.startsWith("..") && !
|
|
4491
|
+
const rel = toPosix(path26.relative(paths.cache, file));
|
|
4492
|
+
const inCache = rel !== "" && !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4383
4493
|
return inCache && rel !== "entries" && !rel.startsWith("entries/");
|
|
4384
4494
|
}
|
|
4385
4495
|
},
|
|
@@ -4388,11 +4498,11 @@ async function startDevServer(options) {
|
|
|
4388
4498
|
forwardConsole: false
|
|
4389
4499
|
},
|
|
4390
4500
|
optimizeDeps: {
|
|
4391
|
-
entries: entries.files().map((file) => toPosix(
|
|
4501
|
+
entries: entries.files().map((file) => toPosix(path26.relative(paths.design, file)))
|
|
4392
4502
|
}
|
|
4393
4503
|
});
|
|
4394
4504
|
const publicDir = project.publicDir();
|
|
4395
|
-
const servePublic = publicDir ?
|
|
4505
|
+
const servePublic = publicDir ? sirv2(publicDir, { dev: true }) : null;
|
|
4396
4506
|
const publicRel = (encoded) => {
|
|
4397
4507
|
try {
|
|
4398
4508
|
return decodeURIComponent(encoded);
|
|
@@ -4400,16 +4510,16 @@ async function startDevServer(options) {
|
|
|
4400
4510
|
return null;
|
|
4401
4511
|
}
|
|
4402
4512
|
};
|
|
4403
|
-
const viewer =
|
|
4404
|
-
const viewerIndex =
|
|
4513
|
+
const viewer = fs28.existsSync(PKG.viewer) ? sirv2(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4514
|
+
const viewerIndex = path26.join(PKG.viewer, "index.html");
|
|
4405
4515
|
const serveViewerIndex = (res) => {
|
|
4406
|
-
const html =
|
|
4516
|
+
const html = fs28.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
4407
4517
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
4408
4518
|
res.end(html);
|
|
4409
4519
|
};
|
|
4410
4520
|
const ctx = { project, events, snapshots };
|
|
4411
4521
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
4412
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4522
|
+
const bootUrl = `/${relToDesign(paths, path26.join(entries.dir, HTML_BOOT))}`;
|
|
4413
4523
|
const frameHead = (source, extra = {}) => {
|
|
4414
4524
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
4415
4525
|
return {
|
|
@@ -4430,7 +4540,7 @@ async function startDevServer(options) {
|
|
|
4430
4540
|
notFoundPage(res, "Screen not found", `No screen "${id2}" in "${canvas}". Check canvas.json.`);
|
|
4431
4541
|
return;
|
|
4432
4542
|
}
|
|
4433
|
-
if (!
|
|
4543
|
+
if (!fs28.existsSync(entries.entryFile(source))) entries.sync();
|
|
4434
4544
|
const entryUrl = `/${relToDesign(paths, entries.entryFile(source))}`;
|
|
4435
4545
|
const html = await vite.transformIndexHtml(url2.pathname + url2.search, moduleShell(frameHead(source), entryUrl));
|
|
4436
4546
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -4439,8 +4549,8 @@ async function startDevServer(options) {
|
|
|
4439
4549
|
};
|
|
4440
4550
|
const serveHtmlScreen = async (res, url2) => {
|
|
4441
4551
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
4442
|
-
const file =
|
|
4443
|
-
if (!isInside(paths.design, file) || !
|
|
4552
|
+
const file = path26.resolve(paths.design, rel);
|
|
4553
|
+
if (!isInside(paths.design, file) || !fs28.existsSync(file)) {
|
|
4444
4554
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
4445
4555
|
return;
|
|
4446
4556
|
}
|
|
@@ -4450,13 +4560,13 @@ async function startDevServer(options) {
|
|
|
4450
4560
|
key: `${canvas}/${id2}`,
|
|
4451
4561
|
canvas,
|
|
4452
4562
|
id: id2,
|
|
4453
|
-
title:
|
|
4563
|
+
title: path26.basename(file),
|
|
4454
4564
|
file,
|
|
4455
4565
|
format: "html",
|
|
4456
4566
|
props: {},
|
|
4457
4567
|
system: true
|
|
4458
4568
|
};
|
|
4459
|
-
const raw =
|
|
4569
|
+
const raw = fs28.readFileSync(file, "utf8");
|
|
4460
4570
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
4461
4571
|
const html = injectIntoHtml(
|
|
4462
4572
|
raw,
|
|
@@ -4489,7 +4599,7 @@ async function startDevServer(options) {
|
|
|
4489
4599
|
}
|
|
4490
4600
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4491
4601
|
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4492
|
-
if (publicDir && rel !== null && !project.isPublicFile(rel) &&
|
|
4602
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs28.existsSync(path26.join(publicDir, rel))) {
|
|
4493
4603
|
notFoundPage(res, "Not found", url2.pathname);
|
|
4494
4604
|
return;
|
|
4495
4605
|
}
|
|
@@ -4501,7 +4611,7 @@ async function startDevServer(options) {
|
|
|
4501
4611
|
const rel = publicRel(url2.pathname.slice(1));
|
|
4502
4612
|
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4503
4613
|
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4504
|
-
else if (
|
|
4614
|
+
else if (path26.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4505
4615
|
else serveViewerIndex(res);
|
|
4506
4616
|
};
|
|
4507
4617
|
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
@@ -4529,7 +4639,7 @@ async function startDevServer(options) {
|
|
|
4529
4639
|
};
|
|
4530
4640
|
vite.watcher.on("all", (event, file) => {
|
|
4531
4641
|
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4532
|
-
const rel = toPosix(
|
|
4642
|
+
const rel = toPosix(path26.relative(paths.design, file));
|
|
4533
4643
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
4534
4644
|
if (rel === "design.json") {
|
|
4535
4645
|
queue("config", () => {
|
|
@@ -4640,8 +4750,8 @@ async function ensureServer(paths, options) {
|
|
|
4640
4750
|
const recorded = readState(paths);
|
|
4641
4751
|
if (recorded && pidAlive(recorded.pid)) await stopPreview(recorded);
|
|
4642
4752
|
for (const other of listPreviews()) if (other.root === paths.root) await stopPreview(other);
|
|
4643
|
-
|
|
4644
|
-
const log =
|
|
4753
|
+
fs29.mkdirSync(paths.cache, { recursive: true });
|
|
4754
|
+
const log = fs29.openSync(logFile(paths), "w");
|
|
4645
4755
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
4646
4756
|
if (options.port) args.push("--port", String(options.port));
|
|
4647
4757
|
const child = spawn(process.execPath, args, {
|
|
@@ -4653,7 +4763,7 @@ async function ensureServer(paths, options) {
|
|
|
4653
4763
|
child.unref();
|
|
4654
4764
|
const state = await waitForServer(paths, 4e4);
|
|
4655
4765
|
if (!state) {
|
|
4656
|
-
const tail =
|
|
4766
|
+
const tail = fs29.existsSync(logFile(paths)) ? fs29.readFileSync(logFile(paths), "utf8").split("\n").slice(-25) : [];
|
|
4657
4767
|
throw new CliError(`The preview server did not start.
|
|
4658
4768
|
${tail.join("\n")}`);
|
|
4659
4769
|
}
|
|
@@ -5035,11 +5145,8 @@ async function runMe(paths, options) {
|
|
|
5035
5145
|
}
|
|
5036
5146
|
|
|
5037
5147
|
// src/cli/commands/check.ts
|
|
5038
|
-
import fs29 from "fs";
|
|
5039
|
-
import http2 from "http";
|
|
5040
|
-
import path26 from "path";
|
|
5041
|
-
import sirv2 from "sirv";
|
|
5042
5148
|
var plural2 = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
5149
|
+
var SLOW_MS = 3e3;
|
|
5043
5150
|
function printIssues(issues) {
|
|
5044
5151
|
const byFile = /* @__PURE__ */ new Map();
|
|
5045
5152
|
for (const issue of issues) byFile.set(issue.file, [...byFile.get(issue.file) ?? [], issue]);
|
|
@@ -5072,7 +5179,15 @@ async function renderFrames(jobs) {
|
|
|
5072
5179
|
await page.close().catch(() => {
|
|
5073
5180
|
});
|
|
5074
5181
|
}
|
|
5075
|
-
return {
|
|
5182
|
+
return {
|
|
5183
|
+
key: job.key,
|
|
5184
|
+
url: job.url,
|
|
5185
|
+
ready: report.ready,
|
|
5186
|
+
held: report.held,
|
|
5187
|
+
readyMs: report.readyMs,
|
|
5188
|
+
errors: report.errors,
|
|
5189
|
+
links
|
|
5190
|
+
};
|
|
5076
5191
|
});
|
|
5077
5192
|
} finally {
|
|
5078
5193
|
await browser.close();
|
|
@@ -5117,42 +5232,15 @@ async function runCheck(paths, targets, options) {
|
|
|
5117
5232
|
let close = async () => {
|
|
5118
5233
|
};
|
|
5119
5234
|
if (options.built) {
|
|
5120
|
-
const out = path26.join(paths.cache, "check-build");
|
|
5121
|
-
fs29.rmSync(out, { recursive: true, force: true });
|
|
5122
5235
|
if (!options.json) print(dim("Building the screens as design push does\u2026"));
|
|
5123
|
-
await
|
|
5236
|
+
const build2 = await serveBuild(paths, "check-build", {
|
|
5124
5237
|
canvases: canvases.length ? canvases : void 0,
|
|
5125
|
-
includeSystem: !canvases.length
|
|
5126
|
-
includeViewer: false
|
|
5238
|
+
includeSystem: !canvases.length
|
|
5127
5239
|
});
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
return null;
|
|
5133
|
-
}
|
|
5134
|
-
};
|
|
5135
|
-
docOf = (id2) => read(`api/canvas/${id2}.json`) ?? project.canvas(id2).doc;
|
|
5136
|
-
systemDoc = canvases.length ? null : read("api/system.json");
|
|
5137
|
-
const files = sirv2(out, { dev: true });
|
|
5138
|
-
const server = http2.createServer((req, res) => {
|
|
5139
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
5140
|
-
const segments = url.pathname.split("/").map((segment) => {
|
|
5141
|
-
try {
|
|
5142
|
-
return decodeURIComponent(segment);
|
|
5143
|
-
} catch {
|
|
5144
|
-
return segment;
|
|
5145
|
-
}
|
|
5146
|
-
});
|
|
5147
|
-
req.url = segments.join("/") + url.search;
|
|
5148
|
-
files(req, res, () => {
|
|
5149
|
-
res.statusCode = 404;
|
|
5150
|
-
res.end("Not found");
|
|
5151
|
-
});
|
|
5152
|
-
});
|
|
5153
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
5154
|
-
base = `http://127.0.0.1:${server.address().port}`;
|
|
5155
|
-
close = () => new Promise((resolve) => server.close(() => resolve()));
|
|
5240
|
+
docOf = (id2) => build2.read(`api/canvas/${id2}.json`) ?? project.canvas(id2).doc;
|
|
5241
|
+
systemDoc = canvases.length ? null : build2.read("api/system.json");
|
|
5242
|
+
base = build2.base;
|
|
5243
|
+
close = build2.close;
|
|
5156
5244
|
if (!options.json) print(dim(`Serving the build at ${base}`));
|
|
5157
5245
|
} else {
|
|
5158
5246
|
const wasRunning = await liveServer(paths);
|
|
@@ -5192,7 +5280,16 @@ async function runCheck(paths, targets, options) {
|
|
|
5192
5280
|
if (frame.held) print(` ${red(`holdReady() was not released within 20s (${frame.held} still held)`)}`);
|
|
5193
5281
|
else if (!frame.ready) print(` ${red("did not finish rendering (no ready signal within 20s)")}`);
|
|
5194
5282
|
for (const error of frame.errors) print(` ${red(error.split("\n")[0])}`);
|
|
5283
|
+
if (frame.readyMs !== void 0 && frame.readyMs > SLOW_MS)
|
|
5284
|
+
print(` ${yellow(`slow: ${(frame.readyMs / 1e3).toFixed(1)}s to become ready`)}`);
|
|
5195
5285
|
}
|
|
5286
|
+
const slow = frames.filter((frame) => (frame.readyMs ?? 0) > SLOW_MS);
|
|
5287
|
+
if (slow.length)
|
|
5288
|
+
print(
|
|
5289
|
+
dim(
|
|
5290
|
+
`${plural2(slow.length, "screen")} took over ${SLOW_MS / 1e3}s to become ready; every frame of them costs that in the viewer, check and push snapshots. See what they wait for (retries, timers, polling, entrance gates).`
|
|
5291
|
+
)
|
|
5292
|
+
);
|
|
5196
5293
|
}
|
|
5197
5294
|
if (linkIssues.length) printLinkIssues(linkIssues, project);
|
|
5198
5295
|
const screens = [...picked.values()].reduce((sum, set) => sum + (set?.size ?? 0), 0);
|
|
@@ -5266,13 +5363,13 @@ function printLinkIssues(issues, project) {
|
|
|
5266
5363
|
|
|
5267
5364
|
// src/cli/commands/cloud.ts
|
|
5268
5365
|
import fs30 from "fs";
|
|
5269
|
-
import
|
|
5366
|
+
import os5 from "os";
|
|
5270
5367
|
import path27 from "path";
|
|
5271
5368
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5272
5369
|
async function runLogin() {
|
|
5273
5370
|
const host = cloudHost();
|
|
5274
5371
|
const client = new CloudClient(host, null);
|
|
5275
|
-
const code = await client.deviceCode(
|
|
5372
|
+
const code = await client.deviceCode(os5.hostname());
|
|
5276
5373
|
const url = new URL(code.verification_uri_complete || code.verification_uri, host).toString();
|
|
5277
5374
|
const expiresIn = code.expires_in ?? 600;
|
|
5278
5375
|
print(`${bold("Sign in to foss-design Cloud")} ${dim(host)}`);
|