foss-design 0.3.1 → 0.3.2
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/README.md +1 -1
- package/dist/cli.js +119 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Or install the `design` command globally: `npm i -g foss-design`.
|
|
|
37
37
|
| `build [canvas…] [--out] [--tar]` | Static site. |
|
|
38
38
|
| `login`, `logout` | Sign this machine in to foss-design Cloud (a link with the code in it). |
|
|
39
39
|
| `link [<project>] [--new <name>]` | List cloud projects, or link `.design` to one. |
|
|
40
|
-
| `push [canvas…] [--resolved <unit>]`, `pull [canvas…] [--theirs <unit>]` | Sync with the cloud; exit 2 on a conflict. |
|
|
40
|
+
| `push [canvas…] [--resolved <unit>]`, `pull [canvas…] [--theirs <unit>]` | Sync with the cloud, with progress; push prints the web link of each pushed canvas; exit 2 on a conflict. |
|
|
41
41
|
|
|
42
42
|
Requirements: Node.js 20.19+. `check --render` and `shot` need an installed Chrome, Chromium, Edge or Brave, or `DESIGN_CHROME` pointing at one.
|
|
43
43
|
|
package/dist/cli.js
CHANGED
|
@@ -1440,10 +1440,10 @@ function optsOutOfSystem(html) {
|
|
|
1440
1440
|
}
|
|
1441
1441
|
function injectIntoHtml(html, head) {
|
|
1442
1442
|
const tags = headTags(head);
|
|
1443
|
-
const
|
|
1444
|
-
if (
|
|
1443
|
+
const open2 = /<head(\s[^>]*)?>/i.exec(html);
|
|
1444
|
+
if (open2) return `${html.slice(0, open2.index + open2[0].length)}
|
|
1445
1445
|
${tags}
|
|
1446
|
-
${html.slice(
|
|
1446
|
+
${html.slice(open2.index + open2[0].length)}`;
|
|
1447
1447
|
const htmlOpen = /<html(\s[^>]*)?>/i.exec(html);
|
|
1448
1448
|
if (htmlOpen)
|
|
1449
1449
|
return html.slice(0, htmlOpen.index + htmlOpen[0].length) + `
|
|
@@ -1806,14 +1806,55 @@ for (const stream of [process.stdout, process.stderr]) {
|
|
|
1806
1806
|
throw error;
|
|
1807
1807
|
});
|
|
1808
1808
|
}
|
|
1809
|
+
var live = process.stdout.isTTY === true;
|
|
1810
|
+
var open = false;
|
|
1811
|
+
function endLine() {
|
|
1812
|
+
if (!open) return;
|
|
1813
|
+
process.stdout.write("\n");
|
|
1814
|
+
open = false;
|
|
1815
|
+
}
|
|
1809
1816
|
function print(line = "") {
|
|
1817
|
+
endLine();
|
|
1810
1818
|
process.stdout.write(`${line}
|
|
1811
1819
|
`);
|
|
1812
1820
|
}
|
|
1813
1821
|
function warn(line) {
|
|
1822
|
+
endLine();
|
|
1814
1823
|
process.stderr.write(`${yellow("warning")} ${line}
|
|
1815
1824
|
`);
|
|
1816
1825
|
}
|
|
1826
|
+
var silentProgress = { update() {
|
|
1827
|
+
}, done() {
|
|
1828
|
+
} };
|
|
1829
|
+
function progress() {
|
|
1830
|
+
let last = 0;
|
|
1831
|
+
let latest = "";
|
|
1832
|
+
const show = (line) => {
|
|
1833
|
+
if (live) {
|
|
1834
|
+
process.stdout.write(`\r\x1B[2K${dim(line)}`);
|
|
1835
|
+
open = true;
|
|
1836
|
+
} else process.stdout.write(`${dim(line)}
|
|
1837
|
+
`);
|
|
1838
|
+
};
|
|
1839
|
+
return {
|
|
1840
|
+
update(line) {
|
|
1841
|
+
latest = line;
|
|
1842
|
+
const now = Date.now();
|
|
1843
|
+
if (now - last < (live ? 100 : 5e3)) return;
|
|
1844
|
+
last = now;
|
|
1845
|
+
show(line);
|
|
1846
|
+
},
|
|
1847
|
+
done(line = latest) {
|
|
1848
|
+
if (!line) return;
|
|
1849
|
+
if (live) {
|
|
1850
|
+
process.stdout.write(`\r\x1B[2K${dim(line)}
|
|
1851
|
+
`);
|
|
1852
|
+
open = false;
|
|
1853
|
+
} else process.stdout.write(`${dim(line)}
|
|
1854
|
+
`);
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1817
1858
|
|
|
1818
1859
|
// src/cli/commands/build.ts
|
|
1819
1860
|
function pack(out) {
|
|
@@ -2505,8 +2546,8 @@ function printUrls(paths, url) {
|
|
|
2505
2546
|
async function waitForServer(paths, timeoutMs) {
|
|
2506
2547
|
const until = Date.now() + timeoutMs;
|
|
2507
2548
|
while (Date.now() < until) {
|
|
2508
|
-
const
|
|
2509
|
-
if (
|
|
2549
|
+
const live2 = await liveServer(paths);
|
|
2550
|
+
if (live2) return live2;
|
|
2510
2551
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2511
2552
|
}
|
|
2512
2553
|
return null;
|
|
@@ -2533,12 +2574,12 @@ async function stopServer(paths) {
|
|
|
2533
2574
|
return true;
|
|
2534
2575
|
}
|
|
2535
2576
|
async function ensureServer(paths, options) {
|
|
2536
|
-
const
|
|
2537
|
-
const stale =
|
|
2538
|
-
if (
|
|
2577
|
+
const live2 = await liveServer(paths);
|
|
2578
|
+
const stale = live2 && live2.version !== packageVersion();
|
|
2579
|
+
if (live2 && !options.restart && !stale) return live2;
|
|
2539
2580
|
if (stale)
|
|
2540
|
-
print(dim(`Restarting the preview server: it runs foss-design ${
|
|
2541
|
-
if (
|
|
2581
|
+
print(dim(`Restarting the preview server: it runs foss-design ${live2.version}, this is ${packageVersion()}.`));
|
|
2582
|
+
if (live2) await stopServer(paths);
|
|
2542
2583
|
fs16.mkdirSync(paths.cache, { recursive: true });
|
|
2543
2584
|
const log = fs16.openSync(logFile(paths), "w");
|
|
2544
2585
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
@@ -2866,6 +2907,7 @@ function errorFor(host, status, body, retryAfter) {
|
|
|
2866
2907
|
if (status === 401)
|
|
2867
2908
|
return new CloudError(`Not signed in to ${host}, or the token was revoked. Run \`design login\`.`, status, code);
|
|
2868
2909
|
let message = text(data.message) ?? DEFAULT_MESSAGE[status] ?? `${host} answered ${status}`;
|
|
2910
|
+
if (status === 413 && !str(data.error)) message = `A proxy in front of ${host} refused it as too large (HTTP 413)`;
|
|
2869
2911
|
if (code === "account_banned") message = `Your account is banned: ${message}`;
|
|
2870
2912
|
if (limit) message += ` (limit: ${limit})`;
|
|
2871
2913
|
if (status === 429 && retryAfter) message += `; retry in ${retryAfter}s`;
|
|
@@ -3222,13 +3264,26 @@ function allUnitKeys(link, local, remote) {
|
|
|
3222
3264
|
}
|
|
3223
3265
|
|
|
3224
3266
|
// src/cloud/sync.ts
|
|
3225
|
-
function openSync(paths, log) {
|
|
3267
|
+
function openSync(paths, log, progress2 = () => silentProgress) {
|
|
3226
3268
|
const link = requireLink(paths);
|
|
3227
3269
|
const credential = requireCredential(link.host);
|
|
3228
|
-
return { paths, link, client: new CloudClient(link.host, credential.token), log };
|
|
3270
|
+
return { paths, link, client: new CloudClient(link.host, credential.token), log, progress: progress2 };
|
|
3271
|
+
}
|
|
3272
|
+
var TRANSFERS = 8;
|
|
3273
|
+
var megabytes = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
3274
|
+
function rate(bytes, started) {
|
|
3275
|
+
const elapsed = (Date.now() - started) / 1e3;
|
|
3276
|
+
return elapsed < 1 ? "" : ` \xB7 ${(bytes / 1024 / 1024 / elapsed).toFixed(1)} MB/s`;
|
|
3277
|
+
}
|
|
3278
|
+
var seconds = (started) => `${((Date.now() - started) / 1e3).toFixed(1)}s`;
|
|
3279
|
+
function webUrl(link, unit) {
|
|
3280
|
+
const base = `${link.host.replace(/\/+$/, "")}/p/${encodeURIComponent(link.project)}`;
|
|
3281
|
+
if (!unit) return base;
|
|
3282
|
+
if (unit === SYSTEM_UNIT) return `${base}/system`;
|
|
3283
|
+
return `${base}/c/${encodeURIComponent(unit.slice("canvas/".length))}`;
|
|
3229
3284
|
}
|
|
3230
3285
|
function newReport(command, link) {
|
|
3231
|
-
return { ok: true, command, host: link.host, project: link.project, units: [], hints: [] };
|
|
3286
|
+
return { ok: true, command, host: link.host, project: link.project, url: webUrl(link), units: [], hints: [] };
|
|
3232
3287
|
}
|
|
3233
3288
|
function unitArg(value) {
|
|
3234
3289
|
if (value === SYSTEM_UNIT || value === "@system") return SYSTEM_UNIT;
|
|
@@ -3953,7 +4008,7 @@ function applyUnit(paths, key, local, target, blobs) {
|
|
|
3953
4008
|
return counts;
|
|
3954
4009
|
}
|
|
3955
4010
|
async function pull(ctx, options) {
|
|
3956
|
-
const { paths, client
|
|
4011
|
+
const { paths, client } = ctx;
|
|
3957
4012
|
const link = structuredClone(ctx.link);
|
|
3958
4013
|
const report = newReport("pull", link);
|
|
3959
4014
|
const selected = unique(options.units.map(unitArg));
|
|
@@ -4056,10 +4111,20 @@ async function pull(ctx, options) {
|
|
|
4056
4111
|
else download.push(hash);
|
|
4057
4112
|
}
|
|
4058
4113
|
if (download.length) {
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4114
|
+
const bar = ctx.progress();
|
|
4115
|
+
const started = Date.now();
|
|
4116
|
+
let gotFiles = 0;
|
|
4117
|
+
let gotBytes = 0;
|
|
4118
|
+
const line = () => `Downloading ${gotFiles}/${download.length} files \xB7 ${megabytes(gotBytes)}${rate(gotBytes, started)}`;
|
|
4119
|
+
bar.update(line());
|
|
4120
|
+
await mapLimit(download, TRANSFERS, async (hash) => {
|
|
4121
|
+
const data = await client.getBlob(link.project, hash);
|
|
4122
|
+
blobs.set(hash, data);
|
|
4123
|
+
gotFiles++;
|
|
4124
|
+
gotBytes += data.length;
|
|
4125
|
+
bar.update(line());
|
|
4062
4126
|
});
|
|
4127
|
+
bar.done(`Downloaded ${plural2(download.length, "file")} (${megabytes(gotBytes)}) in ${seconds(started)}`);
|
|
4063
4128
|
}
|
|
4064
4129
|
for (const plan of plans) {
|
|
4065
4130
|
const { key, report: entry } = plan;
|
|
@@ -4095,7 +4160,6 @@ async function pull(ctx, options) {
|
|
|
4095
4160
|
// src/cloud/push.ts
|
|
4096
4161
|
import fs24 from "fs";
|
|
4097
4162
|
import path24 from "path";
|
|
4098
|
-
var megabytes = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
4099
4163
|
function blockedReport(status, message) {
|
|
4100
4164
|
return {
|
|
4101
4165
|
unit: status.key,
|
|
@@ -4234,7 +4298,7 @@ async function push(ctx, options) {
|
|
|
4234
4298
|
const pushing = toPush.filter((key) => {
|
|
4235
4299
|
const s = status(key);
|
|
4236
4300
|
if (systemPush || resolved.includes(key) || s.changed.length || !remote.has(key)) return true;
|
|
4237
|
-
report.units.push({ unit: key, action: "unchanged", rev: s.baseRev });
|
|
4301
|
+
report.units.push({ unit: key, action: "unchanged", rev: s.baseRev, url: webUrl(link, key) });
|
|
4238
4302
|
return false;
|
|
4239
4303
|
});
|
|
4240
4304
|
if (!pushing.length) return report;
|
|
@@ -4277,17 +4341,40 @@ async function push(ctx, options) {
|
|
|
4277
4341
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
4278
4342
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
4279
4343
|
const bytes = missing.reduce((sum, hash) => sum + fs24.statSync(files.get(hash)).size, 0);
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4344
|
+
const bar = ctx.progress();
|
|
4345
|
+
const started = Date.now();
|
|
4346
|
+
let sentFiles = 0;
|
|
4347
|
+
let sentBytes = 0;
|
|
4348
|
+
const line = () => `Uploading ${sentFiles}/${missing.length} files \xB7 ${megabytes(sentBytes)} of ${megabytes(bytes)}${rate(sentBytes, started)}`;
|
|
4349
|
+
bar.update(line());
|
|
4350
|
+
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
4351
|
+
const file = files.get(hash);
|
|
4352
|
+
const data = fs24.readFileSync(file);
|
|
4353
|
+
try {
|
|
4354
|
+
await client.putBlob(link.project, hash, data);
|
|
4355
|
+
} catch (error) {
|
|
4356
|
+
if (error instanceof CloudError && error.status === 413)
|
|
4357
|
+
throw new CliError(`${path24.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
4358
|
+
throw error;
|
|
4359
|
+
}
|
|
4360
|
+
sentFiles++;
|
|
4361
|
+
sentBytes += data.length;
|
|
4362
|
+
bar.update(line());
|
|
4283
4363
|
});
|
|
4364
|
+
bar.done(`Uploaded ${plural2(missing.length, "file")} (${megabytes(bytes)}) in ${seconds(started)}`);
|
|
4284
4365
|
}
|
|
4285
4366
|
log(`Pushing ${plural2(units.length, "unit")}\u2026`);
|
|
4286
4367
|
const revs2 = await client.push(link.project, units);
|
|
4287
4368
|
for (const unit of units) {
|
|
4288
4369
|
const rev = revs2.get(unit.key) ?? unit.baseRev + 1;
|
|
4289
4370
|
link.units[unit.key] = { rev, files: hashes(unit.source) };
|
|
4290
|
-
report.units.push({
|
|
4371
|
+
report.units.push({
|
|
4372
|
+
unit: unit.key,
|
|
4373
|
+
action: unit.baseRev ? "pushed" : "created",
|
|
4374
|
+
rev,
|
|
4375
|
+
baseRev: unit.baseRev,
|
|
4376
|
+
url: webUrl(link, unit.key)
|
|
4377
|
+
});
|
|
4291
4378
|
}
|
|
4292
4379
|
for (const key of resolved) fs24.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
4293
4380
|
writeLink(paths, link);
|
|
@@ -4312,8 +4399,8 @@ function printReport(report) {
|
|
|
4312
4399
|
const shown = report.units.filter((entry) => !QUIET.has(entry.action));
|
|
4313
4400
|
const quiet = report.units.length - shown.length;
|
|
4314
4401
|
const moved = report.units.some((entry) => GOOD.has(entry.action));
|
|
4315
|
-
if (report.ok && !moved) print(`${bold(`Nothing to ${report.command}`)} ${dim(
|
|
4316
|
-
else if (report.ok) print(`${bold(verb)} ${report.
|
|
4402
|
+
if (report.ok && !moved) print(`${bold(`Nothing to ${report.command}`)} ${dim(report.url)}`);
|
|
4403
|
+
else if (report.ok) print(`${bold(verb)} ${report.url}`);
|
|
4317
4404
|
else print(`${bold(report.command === "push" ? "Push stopped" : "Pull finished with conflicts")} ${dim(report.host)}`);
|
|
4318
4405
|
const width = Math.max(0, ...shown.map((entry) => entry.unit.length));
|
|
4319
4406
|
for (const entry of shown) {
|
|
@@ -4327,6 +4414,7 @@ function printReport(report) {
|
|
|
4327
4414
|
) : "";
|
|
4328
4415
|
const detail2 = [entry.action.replaceAll("_", " "), revs(entry), files].filter(Boolean).join(" ");
|
|
4329
4416
|
print(` ${mark} ${entry.unit.padEnd(width)} ${detail2}`);
|
|
4417
|
+
if (entry.url && GOOD.has(entry.action)) print(` ${dim("\u2192")} ${entry.url}`);
|
|
4330
4418
|
if (entry.message) print(` ${dim(entry.message)}`);
|
|
4331
4419
|
}
|
|
4332
4420
|
if (quiet) print(dim(` ${plural2(quiet, "unit")} ${report.command === "push" ? "unchanged" : "up to date"}`));
|
|
@@ -4335,8 +4423,8 @@ function printReport(report) {
|
|
|
4335
4423
|
async function runSync(paths, command, json, run) {
|
|
4336
4424
|
let report;
|
|
4337
4425
|
try {
|
|
4338
|
-
report = await run(openSync(paths,
|
|
4339
|
-
} : (line) => print(dim(line))));
|
|
4426
|
+
report = await run(json ? openSync(paths, () => {
|
|
4427
|
+
}) : openSync(paths, (line) => print(dim(line)), progress));
|
|
4340
4428
|
} catch (error) {
|
|
4341
4429
|
if (!json) throw error;
|
|
4342
4430
|
let link = null;
|
|
@@ -4350,6 +4438,7 @@ async function runSync(paths, command, json, run) {
|
|
|
4350
4438
|
command,
|
|
4351
4439
|
host: link?.host ?? "",
|
|
4352
4440
|
project: link?.project ?? "",
|
|
4441
|
+
url: link ? webUrl(link) : "",
|
|
4353
4442
|
units: [],
|
|
4354
4443
|
hints: [],
|
|
4355
4444
|
error: {
|
|
@@ -4416,7 +4505,8 @@ ${bold("Cloud")} ${dim("units: system (design.json + system/) and canvas/<id>;
|
|
|
4416
4505
|
(linking to another project replaces the link)
|
|
4417
4506
|
push [canvas\u2026] [--json] Build and upload what changed here (or the named units); a
|
|
4418
4507
|
[--resolved <unit>] system change pushes every active canvas with it. Stops if
|
|
4419
|
-
the cloud is ahead; --resolved marks a merged conflict
|
|
4508
|
+
the cloud is ahead; --resolved marks a merged conflict.
|
|
4509
|
+
Prints the web link of every pushed canvas
|
|
4420
4510
|
pull [canvas\u2026] [--json] Take cloud changes into .design; a unit changed on both sides
|
|
4421
4511
|
[--theirs <unit>] goes to .design/.cache/cloud/incoming/<unit> (exit 2);
|
|
4422
4512
|
--theirs takes the cloud version and drops local changes
|
|
@@ -4530,6 +4620,7 @@ async function main(argv) {
|
|
|
4530
4620
|
}
|
|
4531
4621
|
}
|
|
4532
4622
|
main(process.argv.slice(2)).catch((error) => {
|
|
4623
|
+
endLine();
|
|
4533
4624
|
if (error instanceof CliError) {
|
|
4534
4625
|
process.stderr.write(`${red("error")} ${error.message}
|
|
4535
4626
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foss-design",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|