foss-design 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +588 -393
- package/dist/viewer/assets/index-COUW2y60.js +158 -0
- package/dist/viewer/assets/index-CiqSYqfx.css +2 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +82 -4
- package/dist/viewer-lib/styles.css +5 -0
- package/dist/viewer-lib/types/src/shared/types.d.ts +2 -0
- package/dist/viewer-lib/types/viewer/src/lib/source.d.ts +2 -0
- package/package.json +1 -1
- package/dist/viewer/assets/index-CBCJT9KP.js +0 -158
- package/dist/viewer/assets/index-CgFWotIU.css +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import
|
|
4
|
+
import path30 from "path";
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
6
|
|
|
7
7
|
// src/core/paths.ts
|
|
@@ -134,12 +134,12 @@ function resolveFrom(anchor, request) {
|
|
|
134
134
|
|
|
135
135
|
// src/cli/commands/build.ts
|
|
136
136
|
import { spawnSync } from "child_process";
|
|
137
|
-
import
|
|
138
|
-
import
|
|
137
|
+
import fs13 from "fs";
|
|
138
|
+
import path13 from "path";
|
|
139
139
|
|
|
140
140
|
// src/build/static.ts
|
|
141
|
-
import
|
|
142
|
-
import
|
|
141
|
+
import fs12 from "fs";
|
|
142
|
+
import path12 from "path";
|
|
143
143
|
import { build } from "vite";
|
|
144
144
|
|
|
145
145
|
// src/core/png.ts
|
|
@@ -307,8 +307,8 @@ function ensureThumb(file) {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// src/core/project.ts
|
|
310
|
-
import
|
|
311
|
-
import
|
|
310
|
+
import fs8 from "fs";
|
|
311
|
+
import path8 from "path";
|
|
312
312
|
|
|
313
313
|
// src/core/canvas.ts
|
|
314
314
|
import { createHash } from "crypto";
|
|
@@ -346,8 +346,8 @@ function routeError(pattern) {
|
|
|
346
346
|
}
|
|
347
347
|
return null;
|
|
348
348
|
}
|
|
349
|
-
function split(
|
|
350
|
-
return
|
|
349
|
+
function split(path31) {
|
|
350
|
+
return path31.split("/").filter(Boolean);
|
|
351
351
|
}
|
|
352
352
|
function compileRoute(pattern) {
|
|
353
353
|
return {
|
|
@@ -366,8 +366,8 @@ function decode2(part) {
|
|
|
366
366
|
return part;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
function scoreRoute(route2,
|
|
370
|
-
const parts = split(
|
|
369
|
+
function scoreRoute(route2, path31) {
|
|
370
|
+
const parts = split(path31.split(/[?#]/)[0] ?? "").map(decode2);
|
|
371
371
|
const walk2 = (s, p) => {
|
|
372
372
|
if (s === route2.segments.length) return p === parts.length ? 0 : null;
|
|
373
373
|
const segment = route2.segments[s];
|
|
@@ -385,11 +385,11 @@ function scoreRoute(route2, path29) {
|
|
|
385
385
|
};
|
|
386
386
|
return walk2(0, 0);
|
|
387
387
|
}
|
|
388
|
-
function matchRoute(entries,
|
|
388
|
+
function matchRoute(entries, path31) {
|
|
389
389
|
let best = null;
|
|
390
390
|
for (const entry of entries) {
|
|
391
391
|
for (const route2 of entry.routes) {
|
|
392
|
-
const score = scoreRoute(route2,
|
|
392
|
+
const score = scoreRoute(route2, path31);
|
|
393
393
|
if (score !== null && (!best || score > best.score)) best = { score, value: entry.value };
|
|
394
394
|
}
|
|
395
395
|
}
|
|
@@ -426,6 +426,8 @@ var DesignConfigSchema = z.strictObject({
|
|
|
426
426
|
alias: z.record(z.string(), z.string()).optional(),
|
|
427
427
|
/** Extra folders Tailwind scans for class names, relative to the project root. */
|
|
428
428
|
sources: z.array(z.string()).optional(),
|
|
429
|
+
/** A monorepo's app package, relative to the project root: screens import its dependencies and its React. */
|
|
430
|
+
app: z.string().min(1).optional(),
|
|
429
431
|
/** A folder served at the root of every screen, like Vite's `public`, relative to the project root. */
|
|
430
432
|
public: z.string().min(1).optional(),
|
|
431
433
|
port: z.number().int().min(1).max(65535).optional()
|
|
@@ -512,9 +514,9 @@ var CanvasSchema = z.strictObject({
|
|
|
512
514
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
513
515
|
});
|
|
514
516
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
515
|
-
function formatPath(base,
|
|
517
|
+
function formatPath(base, path31) {
|
|
516
518
|
let out = base;
|
|
517
|
-
for (const part of
|
|
519
|
+
for (const part of path31) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
518
520
|
return out;
|
|
519
521
|
}
|
|
520
522
|
function describeZodError(error, base = "") {
|
|
@@ -831,33 +833,80 @@ function summarize(canvas) {
|
|
|
831
833
|
};
|
|
832
834
|
}
|
|
833
835
|
|
|
834
|
-
// src/core/
|
|
836
|
+
// src/core/icon.ts
|
|
835
837
|
import fs4 from "fs";
|
|
836
838
|
import path4 from "path";
|
|
839
|
+
var ICON_EXTENSIONS = ["svg", "png", "webp"];
|
|
840
|
+
var MAX_ICON_BYTES = 256 * 1024;
|
|
841
|
+
var iconName = (ext) => `icon.${ext}`;
|
|
842
|
+
function iconFile(paths) {
|
|
843
|
+
for (const ext of ICON_EXTENSIONS) {
|
|
844
|
+
const file = path4.join(paths.system, iconName(ext));
|
|
845
|
+
if (fs4.existsSync(file) && fs4.statSync(file).isFile()) return file;
|
|
846
|
+
}
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
function sniffIcon(data) {
|
|
850
|
+
if (data.length >= 8 && data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
851
|
+
return "png";
|
|
852
|
+
if (data.length >= 12 && data.toString("latin1", 0, 4) === "RIFF" && data.toString("latin1", 8, 12) === "WEBP")
|
|
853
|
+
return "webp";
|
|
854
|
+
const head = data.subarray(0, 4096).toString("utf8").replace(/^/, "");
|
|
855
|
+
const rest = head.replace(/^(?:\s|<\?xml[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[^>]*>)*/i, "");
|
|
856
|
+
return /^<svg[\s>]/i.test(rest) ? "svg" : null;
|
|
857
|
+
}
|
|
858
|
+
function iconProblem(data, ext) {
|
|
859
|
+
const kind = sniffIcon(data);
|
|
860
|
+
if (!kind) return "the icon must be an SVG, PNG or WebP image";
|
|
861
|
+
if (ext && ext !== kind) return `the file is ${kind.toUpperCase()}, not ${ext.toUpperCase()}`;
|
|
862
|
+
if (data.length > MAX_ICON_BYTES)
|
|
863
|
+
return `the icon is ${Math.ceil(data.length / 1024)} KB; the limit is ${MAX_ICON_BYTES / 1024} KB`;
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
function writeIcon(paths, data) {
|
|
867
|
+
for (const ext2 of ICON_EXTENSIONS) fs4.rmSync(path4.join(paths.system, iconName(ext2)), { force: true });
|
|
868
|
+
if (!data) return null;
|
|
869
|
+
const ext = sniffIcon(data);
|
|
870
|
+
fs4.mkdirSync(paths.system, { recursive: true });
|
|
871
|
+
const file = path4.join(paths.system, iconName(ext));
|
|
872
|
+
fs4.writeFileSync(file, data);
|
|
873
|
+
return file;
|
|
874
|
+
}
|
|
875
|
+
function onlyIcon(dir) {
|
|
876
|
+
try {
|
|
877
|
+
return fs4.readdirSync(dir).filter((name) => !name.startsWith(".")).every((name) => ICON_EXTENSIONS.some((ext) => name === iconName(ext)));
|
|
878
|
+
} catch {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/core/public.ts
|
|
884
|
+
import fs5 from "fs";
|
|
885
|
+
import path5 from "path";
|
|
837
886
|
function publicFiles(dir) {
|
|
838
887
|
const files = [];
|
|
839
888
|
let root;
|
|
840
889
|
try {
|
|
841
|
-
root =
|
|
890
|
+
root = fs5.realpathSync(dir);
|
|
842
891
|
} catch {
|
|
843
892
|
return files;
|
|
844
893
|
}
|
|
845
894
|
const visit = (current) => {
|
|
846
895
|
let entries;
|
|
847
896
|
try {
|
|
848
|
-
entries =
|
|
897
|
+
entries = fs5.readdirSync(current, { withFileTypes: true });
|
|
849
898
|
} catch {
|
|
850
899
|
return;
|
|
851
900
|
}
|
|
852
901
|
for (const entry of entries) {
|
|
853
902
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
854
|
-
const full =
|
|
903
|
+
const full = path5.join(current, entry.name);
|
|
855
904
|
if (entry.isDirectory()) visit(full);
|
|
856
|
-
else if (entry.isFile()) files.push(toPosix(
|
|
905
|
+
else if (entry.isFile()) files.push(toPosix(path5.relative(dir, full)));
|
|
857
906
|
else if (entry.isSymbolicLink()) {
|
|
858
907
|
try {
|
|
859
|
-
const target =
|
|
860
|
-
if (isInside(root, target) &&
|
|
908
|
+
const target = fs5.realpathSync(full);
|
|
909
|
+
if (isInside(root, target) && fs5.statSync(target).isFile()) files.push(toPosix(path5.relative(dir, full)));
|
|
861
910
|
} catch {
|
|
862
911
|
}
|
|
863
912
|
}
|
|
@@ -868,12 +917,12 @@ function publicFiles(dir) {
|
|
|
868
917
|
}
|
|
869
918
|
|
|
870
919
|
// src/core/system.ts
|
|
871
|
-
import
|
|
872
|
-
import
|
|
920
|
+
import fs7 from "fs";
|
|
921
|
+
import path7 from "path";
|
|
873
922
|
|
|
874
923
|
// src/core/tokens.ts
|
|
875
|
-
import
|
|
876
|
-
import
|
|
924
|
+
import fs6 from "fs";
|
|
925
|
+
import path6 from "path";
|
|
877
926
|
import postcss from "postcss";
|
|
878
927
|
var NAMESPACES = [
|
|
879
928
|
["--color-", "color"],
|
|
@@ -1032,12 +1081,12 @@ function readStylesheets(entry) {
|
|
|
1032
1081
|
const seen = /* @__PURE__ */ new Set();
|
|
1033
1082
|
const files = [];
|
|
1034
1083
|
const walk2 = (file) => {
|
|
1035
|
-
if (seen.has(file) || !
|
|
1084
|
+
if (seen.has(file) || !fs6.existsSync(file)) return;
|
|
1036
1085
|
seen.add(file);
|
|
1037
|
-
const css2 =
|
|
1086
|
+
const css2 = fs6.readFileSync(file, "utf8");
|
|
1038
1087
|
const imports = [...css2.matchAll(/@import\s+(?:url\()?\s*["']([^"']+)["']/g)].map((m) => m[1]);
|
|
1039
1088
|
for (const spec of imports) {
|
|
1040
|
-
if (spec.startsWith("./") || spec.startsWith("../")) walk2(
|
|
1089
|
+
if (spec.startsWith("./") || spec.startsWith("../")) walk2(path6.resolve(path6.dirname(file), spec));
|
|
1041
1090
|
}
|
|
1042
1091
|
files.push({ file, css: css2 });
|
|
1043
1092
|
};
|
|
@@ -1173,11 +1222,11 @@ var COMPONENT_EXT = [".tsx", ".jsx", ".ts", ".js", ".vue", ".svelte", ".html"];
|
|
|
1173
1222
|
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".svg", ".ico"]);
|
|
1174
1223
|
var FONT_EXT = /* @__PURE__ */ new Set([".woff", ".woff2", ".ttf", ".otf"]);
|
|
1175
1224
|
function listFiles(dir, deep = false) {
|
|
1176
|
-
if (!
|
|
1225
|
+
if (!fs7.existsSync(dir)) return [];
|
|
1177
1226
|
const out = [];
|
|
1178
|
-
for (const entry of
|
|
1227
|
+
for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
|
|
1179
1228
|
if (entry.name.startsWith(".")) continue;
|
|
1180
|
-
const full =
|
|
1229
|
+
const full = path7.join(dir, entry.name);
|
|
1181
1230
|
if (entry.isDirectory()) {
|
|
1182
1231
|
if (deep) out.push(...listFiles(full, true));
|
|
1183
1232
|
} else out.push(full);
|
|
@@ -1185,16 +1234,16 @@ function listFiles(dir, deep = false) {
|
|
|
1185
1234
|
return out.sort();
|
|
1186
1235
|
}
|
|
1187
1236
|
function guessSources(componentsDir, stem) {
|
|
1188
|
-
if (!
|
|
1237
|
+
if (!fs7.existsSync(componentsDir)) return [];
|
|
1189
1238
|
const wanted = slugify(stem);
|
|
1190
1239
|
for (const file of listFiles(componentsDir)) {
|
|
1191
|
-
const ext =
|
|
1192
|
-
if (COMPONENT_EXT.includes(ext) && slugify(
|
|
1240
|
+
const ext = path7.extname(file);
|
|
1241
|
+
if (COMPONENT_EXT.includes(ext) && slugify(path7.basename(file, ext)) === wanted) return [file];
|
|
1193
1242
|
}
|
|
1194
|
-
for (const entry of
|
|
1243
|
+
for (const entry of fs7.readdirSync(componentsDir, { withFileTypes: true })) {
|
|
1195
1244
|
if (entry.isDirectory() && slugify(entry.name) === wanted) {
|
|
1196
|
-
const files = listFiles(
|
|
1197
|
-
(f) => COMPONENT_EXT.includes(
|
|
1245
|
+
const files = listFiles(path7.join(componentsDir, entry.name)).filter(
|
|
1246
|
+
(f) => COMPONENT_EXT.includes(path7.extname(f))
|
|
1198
1247
|
);
|
|
1199
1248
|
if (files.length) return files;
|
|
1200
1249
|
}
|
|
@@ -1202,14 +1251,14 @@ function guessSources(componentsDir, stem) {
|
|
|
1202
1251
|
return [];
|
|
1203
1252
|
}
|
|
1204
1253
|
function loadSystem(paths, urls) {
|
|
1205
|
-
if (!
|
|
1254
|
+
if (!fs7.existsSync(paths.system) || onlyIcon(paths.system)) return null;
|
|
1206
1255
|
const issues = [];
|
|
1207
|
-
const configFile =
|
|
1256
|
+
const configFile = path7.join(paths.system, "system.json");
|
|
1208
1257
|
const report = (severity, file, message, at) => issues.push({ severity, file: relToRoot(paths, file), message, at });
|
|
1209
1258
|
let config = null;
|
|
1210
|
-
if (
|
|
1259
|
+
if (fs7.existsSync(configFile)) {
|
|
1211
1260
|
try {
|
|
1212
|
-
const parsed = SystemConfigSchema.safeParse(JSON.parse(
|
|
1261
|
+
const parsed = SystemConfigSchema.safeParse(JSON.parse(fs7.readFileSync(configFile, "utf8")));
|
|
1213
1262
|
if (parsed.success) config = parsed.data;
|
|
1214
1263
|
else for (const issue of describeZodError(parsed.error)) report("error", configFile, issue.message, issue.at);
|
|
1215
1264
|
} catch (error) {
|
|
@@ -1218,36 +1267,36 @@ function loadSystem(paths, urls) {
|
|
|
1218
1267
|
} else {
|
|
1219
1268
|
report("warning", configFile, 'system.json is missing; run `design system init` or add { "name": "\u2026" }');
|
|
1220
1269
|
}
|
|
1221
|
-
const tokensFile =
|
|
1270
|
+
const tokensFile = path7.join(paths.system, "tokens.css");
|
|
1222
1271
|
let stylesheet = null;
|
|
1223
1272
|
if (config?.stylesheet) {
|
|
1224
|
-
stylesheet =
|
|
1225
|
-
if (!
|
|
1273
|
+
stylesheet = path7.resolve(paths.system, config.stylesheet);
|
|
1274
|
+
if (!fs7.existsSync(stylesheet)) {
|
|
1226
1275
|
report("error", configFile, `stylesheet not found: ${config.stylesheet}`, "stylesheet");
|
|
1227
1276
|
stylesheet = null;
|
|
1228
1277
|
}
|
|
1229
1278
|
}
|
|
1230
|
-
const tokenEntry = stylesheet ?? (
|
|
1279
|
+
const tokenEntry = stylesheet ?? (fs7.existsSync(tokensFile) ? tokensFile : null);
|
|
1231
1280
|
const tokens = tokenEntry ? parseTokens(readStylesheets(tokenEntry)) : [];
|
|
1232
1281
|
if (!tokenEntry) report("warning", tokensFile, "no tokens.css: screens get the default Tailwind theme");
|
|
1233
|
-
const specimensDir =
|
|
1234
|
-
const componentsDir =
|
|
1282
|
+
const specimensDir = path7.join(paths.system, "specimens");
|
|
1283
|
+
const componentsDir = path7.join(paths.system, "components");
|
|
1235
1284
|
const components = [];
|
|
1236
1285
|
const specimens = [];
|
|
1237
1286
|
const usedIds = /* @__PURE__ */ new Set();
|
|
1238
1287
|
for (const file of listFiles(specimensDir)) {
|
|
1239
|
-
const ext =
|
|
1288
|
+
const ext = path7.extname(file).toLowerCase();
|
|
1240
1289
|
if (!SPECIMEN_EXT.has(ext)) continue;
|
|
1241
|
-
const stem =
|
|
1290
|
+
const stem = path7.basename(file, ext);
|
|
1242
1291
|
let id2 = slugify(stem);
|
|
1243
1292
|
if (usedIds.has(id2)) id2 = `${id2}-${usedIds.size + 1}`;
|
|
1244
1293
|
usedIds.add(id2);
|
|
1245
|
-
const tags = parseDocTags(
|
|
1246
|
-
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) =>
|
|
1294
|
+
const tags = parseDocTags(fs7.readFileSync(file, "utf8"));
|
|
1295
|
+
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) => path7.resolve(path7.dirname(file), value));
|
|
1247
1296
|
for (const source of sources) {
|
|
1248
|
-
if (!
|
|
1297
|
+
if (!fs7.existsSync(source)) report("warning", file, `@source not found: ${path7.relative(paths.system, source)}`);
|
|
1249
1298
|
}
|
|
1250
|
-
const found = sources.filter((source) =>
|
|
1299
|
+
const found = sources.filter((source) => fs7.existsSync(source));
|
|
1251
1300
|
const title = tags.title?.[0] || titleize(stem);
|
|
1252
1301
|
const format = ext === ".html" ? "html" : "module";
|
|
1253
1302
|
components.push({
|
|
@@ -1273,10 +1322,10 @@ function loadSystem(paths, urls) {
|
|
|
1273
1322
|
});
|
|
1274
1323
|
}
|
|
1275
1324
|
const guidelines = [];
|
|
1276
|
-
for (const file of listFiles(
|
|
1277
|
-
if (
|
|
1278
|
-
const stem =
|
|
1279
|
-
const { data, body } = parseFrontmatter(
|
|
1325
|
+
for (const file of listFiles(path7.join(paths.system, "guidelines"))) {
|
|
1326
|
+
if (path7.extname(file).toLowerCase() !== ".md") continue;
|
|
1327
|
+
const stem = path7.basename(file, ".md");
|
|
1328
|
+
const { data, body } = parseFrontmatter(fs7.readFileSync(file, "utf8"));
|
|
1280
1329
|
const prefix = /^(\d+)[-_. ]+(.*)$/.exec(stem);
|
|
1281
1330
|
const order = data.order !== void 0 ? Number(data.order) : prefix ? Number(prefix[1]) : 1e3;
|
|
1282
1331
|
guidelines.push({
|
|
@@ -1288,14 +1337,14 @@ function loadSystem(paths, urls) {
|
|
|
1288
1337
|
}
|
|
1289
1338
|
guidelines.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title));
|
|
1290
1339
|
const assets = [];
|
|
1291
|
-
const assetsDir =
|
|
1340
|
+
const assetsDir = path7.join(paths.system, "assets");
|
|
1292
1341
|
for (const file of listFiles(assetsDir, true)) {
|
|
1293
|
-
const ext =
|
|
1342
|
+
const ext = path7.extname(file).toLowerCase();
|
|
1294
1343
|
assets.push({
|
|
1295
|
-
name:
|
|
1296
|
-
path:
|
|
1344
|
+
name: path7.basename(file),
|
|
1345
|
+
path: path7.relative(paths.system, file).split(path7.sep).join("/"),
|
|
1297
1346
|
url: urls.file(relToDesign(paths, file)),
|
|
1298
|
-
size:
|
|
1347
|
+
size: fs7.statSync(file).size,
|
|
1299
1348
|
kind: IMAGE_EXT.has(ext) ? "image" : FONT_EXT.has(ext) ? "font" : "other"
|
|
1300
1349
|
});
|
|
1301
1350
|
}
|
|
@@ -1331,7 +1380,7 @@ function loadSystem(paths, urls) {
|
|
|
1331
1380
|
},
|
|
1332
1381
|
config,
|
|
1333
1382
|
stylesheet,
|
|
1334
|
-
tokensFile:
|
|
1383
|
+
tokensFile: fs7.existsSync(tokensFile) ? tokensFile : null,
|
|
1335
1384
|
specimens
|
|
1336
1385
|
};
|
|
1337
1386
|
}
|
|
@@ -1348,10 +1397,10 @@ function summarizeSystem(system) {
|
|
|
1348
1397
|
|
|
1349
1398
|
// src/core/project.ts
|
|
1350
1399
|
function publicProblem(root, value) {
|
|
1351
|
-
const dir =
|
|
1352
|
-
if (!
|
|
1353
|
-
const real =
|
|
1354
|
-
const realRoot =
|
|
1400
|
+
const dir = path8.resolve(root, value);
|
|
1401
|
+
if (!fs8.existsSync(dir) || !fs8.statSync(dir).isDirectory()) return `no folder "${value}"`;
|
|
1402
|
+
const real = fs8.realpathSync(dir);
|
|
1403
|
+
const realRoot = fs8.realpathSync(root);
|
|
1355
1404
|
if (real === realRoot || !isInside(realRoot, real)) return `"${value}" is not a folder inside the project`;
|
|
1356
1405
|
return null;
|
|
1357
1406
|
}
|
|
@@ -1373,9 +1422,9 @@ var DesignProject = class {
|
|
|
1373
1422
|
const issues = [];
|
|
1374
1423
|
let config = {};
|
|
1375
1424
|
const file = this.paths.config;
|
|
1376
|
-
if (
|
|
1425
|
+
if (fs8.existsSync(file)) {
|
|
1377
1426
|
try {
|
|
1378
|
-
const parsed = DesignConfigSchema.safeParse(JSON.parse(
|
|
1427
|
+
const parsed = DesignConfigSchema.safeParse(JSON.parse(fs8.readFileSync(file, "utf8")));
|
|
1379
1428
|
if (parsed.success) config = parsed.data;
|
|
1380
1429
|
else
|
|
1381
1430
|
for (const issue of describeZodError(parsed.error))
|
|
@@ -1388,6 +1437,14 @@ var DesignProject = class {
|
|
|
1388
1437
|
});
|
|
1389
1438
|
}
|
|
1390
1439
|
}
|
|
1440
|
+
if (config.app !== void 0) {
|
|
1441
|
+
const dir = path8.resolve(this.paths.root, config.app);
|
|
1442
|
+
const problem = !fs8.existsSync(path8.join(dir, "node_modules")) ? `no node_modules in "${config.app}"; install the app's dependencies there` : isInside(this.paths.root, dir) ? null : `"${config.app}" is not a folder inside the project`;
|
|
1443
|
+
if (problem) {
|
|
1444
|
+
issues.push({ severity: "error", file: relToRoot(this.paths, file), at: "app", message: problem });
|
|
1445
|
+
config = { ...config, app: void 0 };
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1391
1448
|
if (config.public !== void 0) {
|
|
1392
1449
|
const problem = publicProblem(this.paths.root, config.public);
|
|
1393
1450
|
if (problem) {
|
|
@@ -1398,10 +1455,15 @@ var DesignProject = class {
|
|
|
1398
1455
|
this.configCache = { config, issues };
|
|
1399
1456
|
return this.configCache;
|
|
1400
1457
|
}
|
|
1458
|
+
/** The app package from design.json `app`, when it is usable. */
|
|
1459
|
+
appDir() {
|
|
1460
|
+
const configured = this.config().config.app;
|
|
1461
|
+
return configured === void 0 ? null : path8.resolve(this.paths.root, configured);
|
|
1462
|
+
}
|
|
1401
1463
|
/** The public folder from design.json, when it is a folder inside the project. */
|
|
1402
1464
|
publicDir() {
|
|
1403
1465
|
const configured = this.config().config.public;
|
|
1404
|
-
return configured === void 0 ? null :
|
|
1466
|
+
return configured === void 0 ? null : path8.resolve(this.paths.root, configured);
|
|
1405
1467
|
}
|
|
1406
1468
|
/** The public folder's files, relative to it; `invalidatePublic` when they change. */
|
|
1407
1469
|
publicFiles() {
|
|
@@ -1421,11 +1483,11 @@ var DesignProject = class {
|
|
|
1421
1483
|
const configured = this.config().config.name;
|
|
1422
1484
|
if (configured) return configured;
|
|
1423
1485
|
try {
|
|
1424
|
-
const manifest = JSON.parse(
|
|
1486
|
+
const manifest = JSON.parse(fs8.readFileSync(path8.join(this.paths.root, "package.json"), "utf8"));
|
|
1425
1487
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
1426
1488
|
} catch {
|
|
1427
1489
|
}
|
|
1428
|
-
return
|
|
1490
|
+
return path8.basename(this.paths.root);
|
|
1429
1491
|
}
|
|
1430
1492
|
system() {
|
|
1431
1493
|
if (this.systemCache === void 0) this.systemCache = loadSystem(this.paths, this.urls);
|
|
@@ -1437,7 +1499,7 @@ var DesignProject = class {
|
|
|
1437
1499
|
canvas(id2) {
|
|
1438
1500
|
const cached = this.canvasCache.get(id2);
|
|
1439
1501
|
if (cached) return cached;
|
|
1440
|
-
if (!
|
|
1502
|
+
if (!fs8.existsSync(path8.join(this.paths.canvases, id2))) return null;
|
|
1441
1503
|
const canvas = loadCanvas(this.paths, id2, this.urls, this.snapshots);
|
|
1442
1504
|
this.canvasCache.set(id2, canvas);
|
|
1443
1505
|
return canvas;
|
|
@@ -1462,13 +1524,26 @@ var DesignProject = class {
|
|
|
1462
1524
|
version: packageVersion(),
|
|
1463
1525
|
system: system ? summarizeSystem(system) : null,
|
|
1464
1526
|
canvases: this.canvases().map(summarize),
|
|
1465
|
-
issues: this.config().issues
|
|
1527
|
+
issues: this.config().issues,
|
|
1528
|
+
icon: this.iconUrl()
|
|
1466
1529
|
};
|
|
1467
1530
|
}
|
|
1531
|
+
/** The icon's URL, with its change time so a new icon is fetched anew. */
|
|
1532
|
+
iconUrl() {
|
|
1533
|
+
const file = iconFile(this.paths);
|
|
1534
|
+
if (!file) return null;
|
|
1535
|
+
return `${this.urls.file(relToDesign(this.paths, file))}?v=${Math.round(fs8.statSync(file).mtimeMs)}`;
|
|
1536
|
+
}
|
|
1537
|
+
iconIssues() {
|
|
1538
|
+
const file = iconFile(this.paths);
|
|
1539
|
+
const problem = file ? iconProblem(fs8.readFileSync(file), path8.extname(file).slice(1)) : null;
|
|
1540
|
+
return problem ? [{ severity: "error", file: relToRoot(this.paths, file), message: problem }] : [];
|
|
1541
|
+
}
|
|
1468
1542
|
/** Every problem in the project, for `design check`. */
|
|
1469
1543
|
issues() {
|
|
1470
1544
|
return [
|
|
1471
1545
|
...this.config().issues,
|
|
1546
|
+
...this.iconIssues(),
|
|
1472
1547
|
...this.system()?.doc.issues ?? [],
|
|
1473
1548
|
...this.canvases().flatMap((canvas) => canvas.doc.issues)
|
|
1474
1549
|
];
|
|
@@ -1508,21 +1583,21 @@ var STATIC_URLS = {
|
|
|
1508
1583
|
};
|
|
1509
1584
|
|
|
1510
1585
|
// src/server/entries.ts
|
|
1511
|
-
import
|
|
1512
|
-
import
|
|
1586
|
+
import fs9 from "fs";
|
|
1587
|
+
import path9 from "path";
|
|
1513
1588
|
var SYSTEM_CSS = "system.css";
|
|
1514
1589
|
var HTML_BOOT = "html-boot.js";
|
|
1515
1590
|
function writeIfChanged(file, content) {
|
|
1516
1591
|
try {
|
|
1517
|
-
if (
|
|
1592
|
+
if (fs9.readFileSync(file, "utf8") === content) return false;
|
|
1518
1593
|
} catch {
|
|
1519
1594
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1595
|
+
fs9.mkdirSync(path9.dirname(file), { recursive: true });
|
|
1596
|
+
fs9.writeFileSync(file, content);
|
|
1522
1597
|
return true;
|
|
1523
1598
|
}
|
|
1524
1599
|
function importPath(fromDir, file) {
|
|
1525
|
-
const rel = toPosix(
|
|
1600
|
+
const rel = toPosix(path9.relative(fromDir, file));
|
|
1526
1601
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1527
1602
|
}
|
|
1528
1603
|
var css = (file) => JSON.stringify(toPosix(file));
|
|
@@ -1535,10 +1610,10 @@ var Entries = class {
|
|
|
1535
1610
|
return this.project.paths.entries;
|
|
1536
1611
|
}
|
|
1537
1612
|
systemCssFile() {
|
|
1538
|
-
return
|
|
1613
|
+
return path9.join(this.dir, SYSTEM_CSS);
|
|
1539
1614
|
}
|
|
1540
1615
|
entryFile(source) {
|
|
1541
|
-
return
|
|
1616
|
+
return path9.join(this.dir, source.canvas, `${source.id}.js`);
|
|
1542
1617
|
}
|
|
1543
1618
|
/** Tailwind with the design tokens, or the stylesheet system.json names; scans every file in `.design`. */
|
|
1544
1619
|
systemCss() {
|
|
@@ -1555,14 +1630,14 @@ var Entries = class {
|
|
|
1555
1630
|
if (system?.tokensFile) lines.push(`@import ${css(system.tokensFile)};`);
|
|
1556
1631
|
}
|
|
1557
1632
|
lines.push(`@source ${css(paths.design)};`, `@source not ${css(paths.cache)};`);
|
|
1558
|
-
lines.push(`@source not ${css(
|
|
1559
|
-
for (const extra of config.sources ?? []) lines.push(`@source ${css(
|
|
1633
|
+
lines.push(`@source not ${css(path9.join(paths.design, "node_modules"))};`);
|
|
1634
|
+
for (const extra of config.sources ?? []) lines.push(`@source ${css(path9.resolve(paths.root, extra))};`);
|
|
1560
1635
|
return `${lines.join("\n")}
|
|
1561
1636
|
`;
|
|
1562
1637
|
}
|
|
1563
1638
|
entrySource(source) {
|
|
1564
1639
|
const file = this.entryFile(source);
|
|
1565
|
-
const dir =
|
|
1640
|
+
const dir = path9.dirname(file);
|
|
1566
1641
|
const lines = [];
|
|
1567
1642
|
if (source.system) lines.push(`import ${JSON.stringify(importPath(dir, this.systemCssFile()))}`);
|
|
1568
1643
|
if (source.builtin === "typography") {
|
|
@@ -1577,7 +1652,7 @@ var Entries = class {
|
|
|
1577
1652
|
`import * as screen from ${JSON.stringify(importPath(dir, source.file))}`,
|
|
1578
1653
|
"",
|
|
1579
1654
|
`const Screen = screen.default ?? Object.values(screen).find((value) => typeof value === 'function')`,
|
|
1580
|
-
"if (!Screen) throw new Error(" + JSON.stringify(`${
|
|
1655
|
+
"if (!Screen) throw new Error(" + JSON.stringify(`${path9.basename(source.file)} has no component to render: export one as default`) + ")",
|
|
1581
1656
|
"hmr(import.meta.hot)",
|
|
1582
1657
|
"mount(Screen)"
|
|
1583
1658
|
);
|
|
@@ -1590,14 +1665,14 @@ ${lines.join("\n")}
|
|
|
1590
1665
|
sync() {
|
|
1591
1666
|
writeIfChanged(this.systemCssFile(), this.systemCss());
|
|
1592
1667
|
writeIfChanged(
|
|
1593
|
-
|
|
1668
|
+
path9.join(this.dir, HTML_BOOT),
|
|
1594
1669
|
`// Generated by foss-design.
|
|
1595
1670
|
import { boot, hmr } from '@design/runtime'
|
|
1596
1671
|
boot()
|
|
1597
1672
|
hmr(import.meta.hot)
|
|
1598
1673
|
`
|
|
1599
1674
|
);
|
|
1600
|
-
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(),
|
|
1675
|
+
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(), path9.join(this.dir, HTML_BOOT)]);
|
|
1601
1676
|
for (const source of this.project.sources()) {
|
|
1602
1677
|
if (source.format !== "module") continue;
|
|
1603
1678
|
const file = this.entryFile(source);
|
|
@@ -1612,21 +1687,21 @@ hmr(import.meta.hot)
|
|
|
1612
1687
|
* Vite bundle again and fail the requests in flight (504 Outdated Optimize Dep).
|
|
1613
1688
|
*/
|
|
1614
1689
|
files() {
|
|
1615
|
-
const files = [
|
|
1690
|
+
const files = [path9.join(this.dir, HTML_BOOT)];
|
|
1616
1691
|
for (const source of this.project.sources()) {
|
|
1617
1692
|
if (source.format === "module") files.push(this.entryFile(source));
|
|
1618
|
-
else if (!source.builtin &&
|
|
1693
|
+
else if (!source.builtin && fs9.existsSync(source.file)) files.push(source.file);
|
|
1619
1694
|
}
|
|
1620
1695
|
return files;
|
|
1621
1696
|
}
|
|
1622
1697
|
prune(dir, wanted) {
|
|
1623
|
-
if (!
|
|
1624
|
-
for (const entry of
|
|
1625
|
-
const full =
|
|
1698
|
+
if (!fs9.existsSync(dir)) return;
|
|
1699
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
1700
|
+
const full = path9.join(dir, entry.name);
|
|
1626
1701
|
if (entry.isDirectory()) {
|
|
1627
1702
|
this.prune(full, wanted);
|
|
1628
|
-
if (
|
|
1629
|
-
} else if (!wanted.has(full))
|
|
1703
|
+
if (fs9.readdirSync(full).length === 0) fs9.rmdirSync(full);
|
|
1704
|
+
} else if (!wanted.has(full)) fs9.rmSync(full);
|
|
1630
1705
|
}
|
|
1631
1706
|
}
|
|
1632
1707
|
};
|
|
@@ -1694,68 +1769,132 @@ ${html}
|
|
|
1694
1769
|
}
|
|
1695
1770
|
|
|
1696
1771
|
// src/server/links.ts
|
|
1697
|
-
import
|
|
1698
|
-
import
|
|
1772
|
+
import fs10 from "fs";
|
|
1773
|
+
import path10 from "path";
|
|
1699
1774
|
var SHIPPED_PACKAGES = ["react", "react-dom", "motion", "lucide-react", "clsx", "tailwind-merge"];
|
|
1700
1775
|
function findPackageDir(fromDir, name, skip) {
|
|
1701
|
-
let dir =
|
|
1776
|
+
let dir = path10.resolve(fromDir);
|
|
1702
1777
|
for (; ; ) {
|
|
1703
|
-
const modules =
|
|
1704
|
-
const candidate =
|
|
1705
|
-
if (modules !== skip &&
|
|
1706
|
-
const parent =
|
|
1778
|
+
const modules = path10.join(dir, "node_modules");
|
|
1779
|
+
const candidate = path10.join(modules, name);
|
|
1780
|
+
if (modules !== skip && fs10.existsSync(path10.join(candidate, "package.json"))) return candidate;
|
|
1781
|
+
const parent = path10.dirname(dir);
|
|
1707
1782
|
if (parent === dir) return null;
|
|
1708
1783
|
dir = parent;
|
|
1709
1784
|
}
|
|
1710
1785
|
}
|
|
1711
1786
|
function isLink(file) {
|
|
1712
1787
|
try {
|
|
1713
|
-
return
|
|
1788
|
+
return fs10.lstatSync(file).isSymbolicLink();
|
|
1714
1789
|
} catch {
|
|
1715
1790
|
return false;
|
|
1716
1791
|
}
|
|
1717
1792
|
}
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1793
|
+
function ensureLink(link, target) {
|
|
1794
|
+
let current = null;
|
|
1795
|
+
try {
|
|
1796
|
+
current = fs10.realpathSync(link);
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
if (current === target) return;
|
|
1800
|
+
if (isLink(link)) fs10.unlinkSync(link);
|
|
1801
|
+
fs10.mkdirSync(path10.dirname(link), { recursive: true });
|
|
1802
|
+
fs10.symlinkSync(target, link, process.platform === "win32" ? "junction" : "dir");
|
|
1803
|
+
}
|
|
1804
|
+
function appPackages(appDir) {
|
|
1805
|
+
const found = /* @__PURE__ */ new Map();
|
|
1806
|
+
const modules = path10.join(appDir, "node_modules");
|
|
1807
|
+
const add = (name, dir) => {
|
|
1808
|
+
try {
|
|
1809
|
+
if (fs10.existsSync(path10.join(dir, "package.json"))) found.set(name, fs10.realpathSync(dir));
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
let entries = [];
|
|
1814
|
+
try {
|
|
1815
|
+
entries = fs10.readdirSync(modules, { withFileTypes: true });
|
|
1816
|
+
} catch {
|
|
1817
|
+
return found;
|
|
1818
|
+
}
|
|
1819
|
+
for (const entry of entries) {
|
|
1820
|
+
if (entry.name.startsWith(".")) continue;
|
|
1821
|
+
const dir = path10.join(modules, entry.name);
|
|
1822
|
+
if (!entry.name.startsWith("@")) {
|
|
1823
|
+
add(entry.name, dir);
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
try {
|
|
1827
|
+
for (const scoped of fs10.readdirSync(dir))
|
|
1828
|
+
if (!scoped.startsWith(".")) add(`${entry.name}/${scoped}`, path10.join(dir, scoped));
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return found;
|
|
1833
|
+
}
|
|
1834
|
+
var APP_LINKS = ".foss-design-app.json";
|
|
1835
|
+
function linkShippedPackages(paths, appDir = null) {
|
|
1836
|
+
const modules = path10.join(paths.design, "node_modules");
|
|
1720
1837
|
const linked = [];
|
|
1721
1838
|
const allow = /* @__PURE__ */ new Set();
|
|
1839
|
+
const app = appDir ? appPackages(appDir) : /* @__PURE__ */ new Map();
|
|
1840
|
+
const placedByUser = (link) => fs10.existsSync(link) && !isLink(link);
|
|
1722
1841
|
for (const name of SHIPPED_PACKAGES) {
|
|
1723
|
-
const link =
|
|
1724
|
-
|
|
1725
|
-
const own = placedByUser ? link : findPackageDir(paths.design, name, modules);
|
|
1842
|
+
const link = path10.join(modules, name);
|
|
1843
|
+
if (app.has(name) && !placedByUser(link)) continue;
|
|
1844
|
+
const own = placedByUser(link) ? link : findPackageDir(paths.design, name, modules);
|
|
1726
1845
|
if (own) {
|
|
1727
|
-
allow.add(
|
|
1728
|
-
if (isLink(link))
|
|
1846
|
+
allow.add(path10.dirname(fs10.realpathSync(own)));
|
|
1847
|
+
if (isLink(link)) fs10.unlinkSync(link);
|
|
1729
1848
|
continue;
|
|
1730
1849
|
}
|
|
1731
1850
|
const shipped = findPackageDir(PKG_ROOT, name);
|
|
1732
1851
|
if (!shipped) continue;
|
|
1733
|
-
const target2 =
|
|
1734
|
-
allow.add(
|
|
1735
|
-
|
|
1736
|
-
try {
|
|
1737
|
-
current2 = fs9.realpathSync(link);
|
|
1738
|
-
} catch {
|
|
1739
|
-
}
|
|
1740
|
-
if (current2 !== target2) {
|
|
1741
|
-
if (isLink(link)) fs9.unlinkSync(link);
|
|
1742
|
-
fs9.mkdirSync(path9.dirname(link), { recursive: true });
|
|
1743
|
-
fs9.symlinkSync(target2, link, process.platform === "win32" ? "junction" : "dir");
|
|
1744
|
-
}
|
|
1852
|
+
const target2 = fs10.realpathSync(shipped);
|
|
1853
|
+
allow.add(path10.dirname(target2));
|
|
1854
|
+
ensureLink(link, target2);
|
|
1745
1855
|
linked.push(name);
|
|
1746
1856
|
}
|
|
1747
|
-
const
|
|
1748
|
-
const
|
|
1857
|
+
const fromApp = [];
|
|
1858
|
+
for (const [name, target2] of app) {
|
|
1859
|
+
const link = path10.join(modules, name);
|
|
1860
|
+
if (placedByUser(link)) continue;
|
|
1861
|
+
ensureLink(link, target2);
|
|
1862
|
+
allow.add(path10.dirname(target2));
|
|
1863
|
+
fromApp.push(name);
|
|
1864
|
+
}
|
|
1865
|
+
const record2 = path10.join(modules, APP_LINKS);
|
|
1866
|
+
let before = [];
|
|
1867
|
+
try {
|
|
1868
|
+
before = JSON.parse(fs10.readFileSync(record2, "utf8"));
|
|
1869
|
+
} catch {
|
|
1870
|
+
}
|
|
1871
|
+
for (const name of before) {
|
|
1872
|
+
const link = path10.join(modules, name);
|
|
1873
|
+
if (!app.has(name) && !SHIPPED_PACKAGES.includes(name) && isLink(link)) {
|
|
1874
|
+
fs10.unlinkSync(link);
|
|
1875
|
+
if (name.startsWith("@"))
|
|
1876
|
+
try {
|
|
1877
|
+
fs10.rmdirSync(path10.dirname(link));
|
|
1878
|
+
} catch {
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
if (fromApp.length || before.length) {
|
|
1883
|
+
fs10.mkdirSync(modules, { recursive: true });
|
|
1884
|
+
fs10.writeFileSync(record2, JSON.stringify(fromApp.sort()));
|
|
1885
|
+
}
|
|
1886
|
+
const self = path10.join(modules, "foss-design");
|
|
1887
|
+
const target = fs10.realpathSync(PKG_ROOT);
|
|
1749
1888
|
let current = null;
|
|
1750
1889
|
try {
|
|
1751
|
-
current =
|
|
1890
|
+
current = fs10.realpathSync(self);
|
|
1752
1891
|
} catch {
|
|
1753
1892
|
}
|
|
1754
1893
|
if (current !== target) {
|
|
1755
|
-
if (isLink(self))
|
|
1756
|
-
if (!
|
|
1757
|
-
|
|
1758
|
-
|
|
1894
|
+
if (isLink(self)) fs10.unlinkSync(self);
|
|
1895
|
+
if (!fs10.existsSync(self)) {
|
|
1896
|
+
fs10.mkdirSync(modules, { recursive: true });
|
|
1897
|
+
fs10.symlinkSync(target, self, process.platform === "win32" ? "junction" : "dir");
|
|
1759
1898
|
}
|
|
1760
1899
|
}
|
|
1761
1900
|
allow.add(target);
|
|
@@ -1764,8 +1903,8 @@ function linkShippedPackages(paths) {
|
|
|
1764
1903
|
|
|
1765
1904
|
// src/server/vite.ts
|
|
1766
1905
|
import { createHash as createHash2 } from "crypto";
|
|
1767
|
-
import
|
|
1768
|
-
import
|
|
1906
|
+
import fs11 from "fs";
|
|
1907
|
+
import path11 from "path";
|
|
1769
1908
|
import tailwindcss from "@tailwindcss/vite";
|
|
1770
1909
|
import react from "@vitejs/plugin-react";
|
|
1771
1910
|
var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1777,30 +1916,31 @@ function aliases(project) {
|
|
|
1777
1916
|
{ find: /^@system\//, replacement: `${toPosix(paths.system)}/` }
|
|
1778
1917
|
];
|
|
1779
1918
|
for (const [key, target] of Object.entries(project.config().config.alias ?? {})) {
|
|
1780
|
-
const replacement = toPosix(
|
|
1919
|
+
const replacement = toPosix(path11.resolve(paths.root, target));
|
|
1781
1920
|
list.push(
|
|
1782
1921
|
key.endsWith("/") ? { find: new RegExp(`^${escapeRegExp(key)}`), replacement: `${replacement.replace(/\/$/, "")}/` } : { find: new RegExp(`^${escapeRegExp(key)}(?=/|$)`), replacement }
|
|
1783
1922
|
);
|
|
1784
1923
|
}
|
|
1785
1924
|
return list;
|
|
1786
1925
|
}
|
|
1787
|
-
function dropStaleDepCache(paths) {
|
|
1788
|
-
const dir =
|
|
1789
|
-
const stamp =
|
|
1926
|
+
function dropStaleDepCache(paths, appDir = null) {
|
|
1927
|
+
const dir = path11.join(paths.cache, "vite");
|
|
1928
|
+
const stamp = path11.join(paths.cache, "vite-runtime");
|
|
1790
1929
|
let runtime = "";
|
|
1791
1930
|
try {
|
|
1792
|
-
runtime = createHash2("sha256").update(
|
|
1931
|
+
runtime = createHash2("sha256").update(fs11.readFileSync(PKG.runtime)).digest("hex");
|
|
1793
1932
|
} catch {
|
|
1794
1933
|
}
|
|
1934
|
+
if (appDir) runtime += ` ${appDir}`;
|
|
1795
1935
|
let seen = "";
|
|
1796
1936
|
try {
|
|
1797
|
-
seen =
|
|
1937
|
+
seen = fs11.readFileSync(stamp, "utf8");
|
|
1798
1938
|
} catch {
|
|
1799
1939
|
}
|
|
1800
1940
|
if (seen === runtime) return;
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1941
|
+
fs11.rmSync(dir, { recursive: true, force: true });
|
|
1942
|
+
fs11.mkdirSync(paths.cache, { recursive: true });
|
|
1943
|
+
fs11.writeFileSync(stamp, runtime);
|
|
1804
1944
|
}
|
|
1805
1945
|
function baseConfig(project) {
|
|
1806
1946
|
const { paths } = project;
|
|
@@ -1808,7 +1948,7 @@ function baseConfig(project) {
|
|
|
1808
1948
|
configFile: false,
|
|
1809
1949
|
envDir: false,
|
|
1810
1950
|
root: paths.design,
|
|
1811
|
-
cacheDir:
|
|
1951
|
+
cacheDir: path11.join(paths.cache, "vite"),
|
|
1812
1952
|
// design.json `public`: served under the frames' base, so HTML and CSS paths such as `/logo.png` resolve.
|
|
1813
1953
|
publicDir: project.publicDir() ?? false,
|
|
1814
1954
|
logLevel: "warn",
|
|
@@ -1828,7 +1968,7 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts",
|
|
|
1828
1968
|
var BUNDLED_REF = /<(script|link|img|source|video|audio|image|use|input)\b[^>]*>/gi;
|
|
1829
1969
|
var encodePath2 = (rel) => rel.split("/").map(encodeURIComponent).join("/");
|
|
1830
1970
|
function relUrl(fromDir, file) {
|
|
1831
|
-
const rel = toPosix(
|
|
1971
|
+
const rel = toPosix(path12.relative(fromDir, file));
|
|
1832
1972
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1833
1973
|
}
|
|
1834
1974
|
function staticSnapshots(paths) {
|
|
@@ -1838,7 +1978,7 @@ function staticSnapshots(paths) {
|
|
|
1838
1978
|
let data = metaCache.get(canvas);
|
|
1839
1979
|
if (!data) {
|
|
1840
1980
|
try {
|
|
1841
|
-
data = JSON.parse(
|
|
1981
|
+
data = JSON.parse(fs12.readFileSync(path12.join(paths.snapshots, canvas, "meta.json"), "utf8"));
|
|
1842
1982
|
} catch {
|
|
1843
1983
|
}
|
|
1844
1984
|
data ??= {};
|
|
@@ -1852,8 +1992,8 @@ function staticSnapshots(paths) {
|
|
|
1852
1992
|
const urls = {};
|
|
1853
1993
|
const thumbs = {};
|
|
1854
1994
|
for (const theme2 of THEMES) {
|
|
1855
|
-
const file =
|
|
1856
|
-
if (!entry[theme2] || !
|
|
1995
|
+
const file = path12.join(paths.snapshots, canvas, `${id2}.${theme2}.png`);
|
|
1996
|
+
if (!entry[theme2] || !fs12.existsSync(file)) continue;
|
|
1857
1997
|
const rel = `_snap/${canvas}/${id2}.${theme2}.png`;
|
|
1858
1998
|
copies.set(file, rel);
|
|
1859
1999
|
urls[theme2] = `${encodePath2(rel)}?v=${entry[theme2]}`;
|
|
@@ -1877,29 +2017,29 @@ function rewriteRefs(html, originalDir, stagedDir) {
|
|
|
1877
2017
|
(whole, attr2, quote, value) => {
|
|
1878
2018
|
if (!value || /^(?:[a-z][a-z0-9+.-]*:|\/|#|\{)/i.test(value)) return whole;
|
|
1879
2019
|
const [pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value).slice(1);
|
|
1880
|
-
const target =
|
|
1881
|
-
if (!
|
|
2020
|
+
const target = path12.resolve(originalDir, decodeURIComponent(pathname));
|
|
2021
|
+
if (!fs12.existsSync(target)) return whole;
|
|
1882
2022
|
return `${attr2}${quote}${relUrl(stagedDir, target)}${suffix}${quote}`;
|
|
1883
2023
|
}
|
|
1884
2024
|
);
|
|
1885
2025
|
});
|
|
1886
2026
|
}
|
|
1887
2027
|
function copyTree(from, to, filter) {
|
|
1888
|
-
if (!
|
|
1889
|
-
for (const entry of
|
|
2028
|
+
if (!fs12.existsSync(from)) return;
|
|
2029
|
+
for (const entry of fs12.readdirSync(from, { withFileTypes: true })) {
|
|
1890
2030
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1891
|
-
const source =
|
|
1892
|
-
const target =
|
|
2031
|
+
const source = path12.join(from, entry.name);
|
|
2032
|
+
const target = path12.join(to, entry.name);
|
|
1893
2033
|
if (entry.isDirectory()) copyTree(source, target, filter);
|
|
1894
|
-
else if (filter(source) && !
|
|
1895
|
-
|
|
1896
|
-
|
|
2034
|
+
else if (filter(source) && !fs12.existsSync(target)) {
|
|
2035
|
+
fs12.mkdirSync(path12.dirname(target), { recursive: true });
|
|
2036
|
+
fs12.copyFileSync(source, target);
|
|
1897
2037
|
}
|
|
1898
2038
|
}
|
|
1899
2039
|
}
|
|
1900
2040
|
function writeJson(file, value) {
|
|
1901
|
-
|
|
1902
|
-
|
|
2041
|
+
fs12.mkdirSync(path12.dirname(file), { recursive: true });
|
|
2042
|
+
fs12.writeFileSync(file, JSON.stringify(value));
|
|
1903
2043
|
}
|
|
1904
2044
|
async function buildSite(paths, out, options = {}) {
|
|
1905
2045
|
const snapshots = staticSnapshots(paths);
|
|
@@ -1914,21 +2054,21 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1914
2054
|
const system = options.includeSystem === false ? null : loadedSystem;
|
|
1915
2055
|
const entries = new Entries(project);
|
|
1916
2056
|
entries.sync();
|
|
1917
|
-
linkShippedPackages(paths);
|
|
2057
|
+
linkShippedPackages(paths, project.appDir());
|
|
1918
2058
|
const publicDir = project.publicDir();
|
|
1919
2059
|
const publicFiles2 = project.publicFiles();
|
|
1920
|
-
const staging =
|
|
1921
|
-
|
|
2060
|
+
const staging = path12.join(paths.cache, "build", "src");
|
|
2061
|
+
fs12.rmSync(staging, { recursive: true, force: true });
|
|
1922
2062
|
const fonts = loadedSystem?.doc.fonts ?? [];
|
|
1923
2063
|
const sources = [...system?.specimens ?? [], ...canvases.flatMap((canvas) => canvas.screens)];
|
|
1924
2064
|
const inputs = [];
|
|
1925
2065
|
const htmlScreens = [];
|
|
1926
2066
|
for (const source of sources) {
|
|
1927
|
-
if (!source.builtin && !
|
|
1928
|
-
const siteDir =
|
|
1929
|
-
const dir =
|
|
1930
|
-
const file =
|
|
1931
|
-
|
|
2067
|
+
if (!source.builtin && !fs12.existsSync(source.file)) continue;
|
|
2068
|
+
const siteDir = path12.join("_s", source.canvas, source.id);
|
|
2069
|
+
const dir = path12.join(staging, siteDir);
|
|
2070
|
+
const file = path12.join(dir, "index.html");
|
|
2071
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
1932
2072
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
1933
2073
|
const head = {
|
|
1934
2074
|
source,
|
|
@@ -1937,24 +2077,24 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1937
2077
|
snapshots: false,
|
|
1938
2078
|
autoHeight: source.autoHeight ?? false,
|
|
1939
2079
|
// The public folder lands at the site root, which frames reach through `../`.
|
|
1940
|
-
public: publicFiles2.length ? { base: `${toPosix(
|
|
2080
|
+
public: publicFiles2.length ? { base: `${toPosix(path12.relative(dir, staging))}/`, files: publicFiles2 } : void 0
|
|
1941
2081
|
};
|
|
1942
2082
|
if (source.format === "module") {
|
|
1943
|
-
|
|
2083
|
+
fs12.writeFileSync(file, moduleShell(head, relUrl(dir, entries.entryFile(source))));
|
|
1944
2084
|
} else {
|
|
1945
|
-
const raw = rewriteRefs(
|
|
2085
|
+
const raw = rewriteRefs(fs12.readFileSync(source.file, "utf8"), path12.dirname(source.file), dir);
|
|
1946
2086
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
1947
2087
|
const html = injectIntoHtml(raw, {
|
|
1948
2088
|
...head,
|
|
1949
2089
|
systemCss: withSystem ? relUrl(dir, entries.systemCssFile()) : void 0,
|
|
1950
|
-
boot: relUrl(dir,
|
|
2090
|
+
boot: relUrl(dir, path12.join(entries.dir, HTML_BOOT))
|
|
1951
2091
|
});
|
|
1952
|
-
|
|
2092
|
+
fs12.writeFileSync(file, html);
|
|
1953
2093
|
htmlScreens.push({ source, dir: siteDir });
|
|
1954
2094
|
}
|
|
1955
2095
|
inputs.push(file);
|
|
1956
2096
|
}
|
|
1957
|
-
|
|
2097
|
+
fs12.mkdirSync(out, { recursive: true });
|
|
1958
2098
|
if (inputs.length) {
|
|
1959
2099
|
const base = baseConfig(project);
|
|
1960
2100
|
await build({
|
|
@@ -1975,14 +2115,14 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1975
2115
|
}
|
|
1976
2116
|
});
|
|
1977
2117
|
} else {
|
|
1978
|
-
|
|
1979
|
-
|
|
2118
|
+
fs12.rmSync(out, { recursive: true, force: true });
|
|
2119
|
+
fs12.mkdirSync(out, { recursive: true });
|
|
1980
2120
|
}
|
|
1981
2121
|
for (const { source, dir } of htmlScreens) {
|
|
1982
2122
|
copyTree(
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
(file) => !SOURCE_EXT.has(
|
|
2123
|
+
path12.dirname(source.file),
|
|
2124
|
+
path12.join(out, dir),
|
|
2125
|
+
(file) => !SOURCE_EXT.has(path12.extname(file).toLowerCase())
|
|
1986
2126
|
);
|
|
1987
2127
|
}
|
|
1988
2128
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -1992,38 +2132,40 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1992
2132
|
for (const item of section.items) if (item.kind === "image" && item.url.startsWith("_f/")) files.add(item.url);
|
|
1993
2133
|
}
|
|
1994
2134
|
for (const asset of system?.doc.assets ?? []) files.add(asset.url);
|
|
2135
|
+
const icon = project.iconUrl();
|
|
2136
|
+
if (icon) files.add(icon.split("?")[0]);
|
|
1995
2137
|
for (const url of files) {
|
|
1996
2138
|
const rel = url.slice("_f/".length).split("/").map(decodeURIComponent).join("/");
|
|
1997
|
-
const from =
|
|
1998
|
-
if (!
|
|
1999
|
-
const to =
|
|
2000
|
-
|
|
2001
|
-
|
|
2139
|
+
const from = path12.join(paths.design, rel);
|
|
2140
|
+
if (!fs12.existsSync(from)) continue;
|
|
2141
|
+
const to = path12.join(out, "_f", rel);
|
|
2142
|
+
fs12.mkdirSync(path12.dirname(to), { recursive: true });
|
|
2143
|
+
fs12.copyFileSync(from, to);
|
|
2002
2144
|
}
|
|
2003
2145
|
const info = project.info(true);
|
|
2004
2146
|
const snapshotDirs = canvasIds.map((id2) => `_snap/${id2}/`);
|
|
2005
2147
|
for (const [from, rel] of snapshots.copies) {
|
|
2006
2148
|
if (!snapshotDirs.some((dir) => rel.startsWith(dir))) continue;
|
|
2007
|
-
const to =
|
|
2008
|
-
|
|
2009
|
-
|
|
2149
|
+
const to = path12.join(out, rel);
|
|
2150
|
+
fs12.mkdirSync(path12.dirname(to), { recursive: true });
|
|
2151
|
+
fs12.copyFileSync(from, to);
|
|
2010
2152
|
}
|
|
2011
2153
|
if (!system) info.system = null;
|
|
2012
2154
|
info.canvases = info.canvases.filter((canvas) => canvasIds.includes(canvas.id));
|
|
2013
|
-
writeJson(
|
|
2014
|
-
for (const canvas of canvases) writeJson(
|
|
2155
|
+
writeJson(path12.join(out, "api", "project.json"), info);
|
|
2156
|
+
for (const canvas of canvases) writeJson(path12.join(out, "api", "canvas", `${canvas.doc.id}.json`), canvas.doc);
|
|
2015
2157
|
if (system) {
|
|
2016
|
-
writeJson(
|
|
2158
|
+
writeJson(path12.join(out, "api", "system.json"), system.doc);
|
|
2017
2159
|
const texts = {};
|
|
2018
2160
|
for (const component of system.doc.components) {
|
|
2019
2161
|
for (const rel of [component.specimen, ...component.sources]) {
|
|
2020
|
-
const file =
|
|
2021
|
-
if (!(rel in texts) &&
|
|
2162
|
+
const file = path12.join(paths.root, rel);
|
|
2163
|
+
if (!(rel in texts) && fs12.existsSync(file)) texts[rel] = fs12.readFileSync(file, "utf8");
|
|
2022
2164
|
}
|
|
2023
2165
|
}
|
|
2024
|
-
writeJson(
|
|
2166
|
+
writeJson(path12.join(out, "api", "sources.json"), texts);
|
|
2025
2167
|
}
|
|
2026
|
-
|
|
2168
|
+
fs12.rmSync(staging, { recursive: true, force: true });
|
|
2027
2169
|
const errors = [
|
|
2028
2170
|
...only ? [] : project.config().issues,
|
|
2029
2171
|
...system?.doc.issues ?? [],
|
|
@@ -2032,21 +2174,21 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2032
2174
|
const { absoluteAssets } = options.includeViewer === false ? { absoluteAssets: false } : copyViewer(PKG.viewer, out);
|
|
2033
2175
|
if (publicDir) {
|
|
2034
2176
|
for (const rel of publicFiles2) {
|
|
2035
|
-
const to =
|
|
2036
|
-
if (
|
|
2037
|
-
|
|
2038
|
-
|
|
2177
|
+
const to = path12.join(out, rel);
|
|
2178
|
+
if (fs12.existsSync(to)) continue;
|
|
2179
|
+
fs12.mkdirSync(path12.dirname(to), { recursive: true });
|
|
2180
|
+
fs12.copyFileSync(path12.join(publicDir, rel), to);
|
|
2039
2181
|
}
|
|
2040
2182
|
}
|
|
2041
2183
|
return { out, canvases: canvasIds, frames: inputs.length, errors, absoluteAssets };
|
|
2042
2184
|
}
|
|
2043
2185
|
function copyViewer(viewerDir, out) {
|
|
2044
2186
|
copyTree(viewerDir, out, () => true);
|
|
2045
|
-
const index =
|
|
2046
|
-
const html =
|
|
2187
|
+
const index = path12.join(out, "index.html");
|
|
2188
|
+
const html = fs12.readFileSync(index, "utf8");
|
|
2047
2189
|
const flag2 = "<script>window.__DESIGN_STATIC__=true</script>";
|
|
2048
2190
|
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
2049
|
-
|
|
2191
|
+
fs12.writeFileSync(
|
|
2050
2192
|
index,
|
|
2051
2193
|
head ? html.slice(0, head.index + head[0].length) + flag2 + html.slice(head.index + head[0].length) : flag2 + html
|
|
2052
2194
|
);
|
|
@@ -2128,8 +2270,8 @@ function progress() {
|
|
|
2128
2270
|
// src/cli/commands/build.ts
|
|
2129
2271
|
function pack(out) {
|
|
2130
2272
|
const archive = `${out.replace(/[\\/]+$/, "")}.tar.gz`;
|
|
2131
|
-
|
|
2132
|
-
const entries =
|
|
2273
|
+
fs13.rmSync(archive, { force: true });
|
|
2274
|
+
const entries = fs13.readdirSync(out);
|
|
2133
2275
|
const result = spawnSync("tar", ["-czf", archive, "-C", out, ...entries], {
|
|
2134
2276
|
stdio: ["ignore", "ignore", "pipe"],
|
|
2135
2277
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
@@ -2139,10 +2281,10 @@ function pack(out) {
|
|
|
2139
2281
|
return archive;
|
|
2140
2282
|
}
|
|
2141
2283
|
async function runBuild(paths, canvases, options) {
|
|
2142
|
-
if (!
|
|
2284
|
+
if (!fs13.existsSync(path13.join(PKG.viewer, "index.html"))) {
|
|
2143
2285
|
throw new CliError("The viewer is missing from this foss-design install (dist/viewer). Rebuild or reinstall it.");
|
|
2144
2286
|
}
|
|
2145
|
-
const out =
|
|
2287
|
+
const out = path13.resolve(options.out ?? path13.join(paths.cache, "site"));
|
|
2146
2288
|
if (out === paths.root || out === paths.design) throw new CliError(`Refusing to build into ${out}`);
|
|
2147
2289
|
print(dim(`Building ${canvases.length ? canvases.join(", ") : "every canvas"} and the design system\u2026`));
|
|
2148
2290
|
let result;
|
|
@@ -2167,8 +2309,8 @@ import os from "os";
|
|
|
2167
2309
|
|
|
2168
2310
|
// src/cloud/units.ts
|
|
2169
2311
|
import { createHash as createHash3 } from "crypto";
|
|
2170
|
-
import
|
|
2171
|
-
import
|
|
2312
|
+
import fs14 from "fs";
|
|
2313
|
+
import path14 from "path";
|
|
2172
2314
|
var SYSTEM_UNIT = "system";
|
|
2173
2315
|
var CANVAS_PREFIX = "canvas/";
|
|
2174
2316
|
var LOCAL_ONLY = /* @__PURE__ */ new Set(["cloud.json", "node_modules"]);
|
|
@@ -2196,7 +2338,7 @@ function caseClashes(keys) {
|
|
|
2196
2338
|
}
|
|
2197
2339
|
function unitRoot(paths, key) {
|
|
2198
2340
|
const id2 = unitCanvasId(key);
|
|
2199
|
-
return id2 === null ? paths.design :
|
|
2341
|
+
return id2 === null ? paths.design : path14.join(paths.canvases, id2);
|
|
2200
2342
|
}
|
|
2201
2343
|
function unitRelative(key, rel) {
|
|
2202
2344
|
const id2 = unitCanvasId(key);
|
|
@@ -2214,7 +2356,7 @@ function hashBuffer(data) {
|
|
|
2214
2356
|
return createHash3("sha256").update(data).digest("hex");
|
|
2215
2357
|
}
|
|
2216
2358
|
function hashFile(file) {
|
|
2217
|
-
const data =
|
|
2359
|
+
const data = fs14.readFileSync(file);
|
|
2218
2360
|
return { hash: hashBuffer(data), size: data.length };
|
|
2219
2361
|
}
|
|
2220
2362
|
var warnedLinks = /* @__PURE__ */ new Set();
|
|
@@ -2227,34 +2369,34 @@ function warnLink(rel) {
|
|
|
2227
2369
|
function walk(dir, base, out) {
|
|
2228
2370
|
let entries;
|
|
2229
2371
|
try {
|
|
2230
|
-
entries =
|
|
2372
|
+
entries = fs14.readdirSync(dir, { withFileTypes: true });
|
|
2231
2373
|
} catch {
|
|
2232
2374
|
return;
|
|
2233
2375
|
}
|
|
2234
2376
|
for (const entry of entries) {
|
|
2235
2377
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2236
|
-
const full =
|
|
2378
|
+
const full = path14.join(dir, entry.name);
|
|
2237
2379
|
let isFile = entry.isFile();
|
|
2238
2380
|
let isDir = entry.isDirectory();
|
|
2239
2381
|
if (entry.isSymbolicLink()) {
|
|
2240
2382
|
isDir = false;
|
|
2241
2383
|
isFile = false;
|
|
2242
2384
|
try {
|
|
2243
|
-
const target =
|
|
2244
|
-
const root =
|
|
2245
|
-
if (target.startsWith(root +
|
|
2246
|
-
else warnLink(toPosix(
|
|
2385
|
+
const target = fs14.realpathSync(full);
|
|
2386
|
+
const root = fs14.realpathSync(base);
|
|
2387
|
+
if (target.startsWith(root + path14.sep)) isFile = fs14.statSync(target).isFile();
|
|
2388
|
+
else warnLink(toPosix(path14.relative(base, full)));
|
|
2247
2389
|
} catch {
|
|
2248
2390
|
}
|
|
2249
2391
|
}
|
|
2250
2392
|
if (isDir) walk(full, base, out);
|
|
2251
|
-
else if (isFile) out[toPosix(
|
|
2393
|
+
else if (isFile) out[toPosix(path14.relative(base, full))] = hashFile(full);
|
|
2252
2394
|
}
|
|
2253
2395
|
}
|
|
2254
2396
|
function unitManifest(paths, key) {
|
|
2255
2397
|
const manifest = {};
|
|
2256
2398
|
if (key === SYSTEM_UNIT) {
|
|
2257
|
-
if (
|
|
2399
|
+
if (fs14.existsSync(paths.config) && fs14.statSync(paths.config).isFile())
|
|
2258
2400
|
manifest["design.json"] = hashFile(paths.config);
|
|
2259
2401
|
walk(paths.system, paths.design, manifest);
|
|
2260
2402
|
} else walk(unitRoot(paths, key), paths.design, manifest);
|
|
@@ -2263,13 +2405,13 @@ function unitManifest(paths, key) {
|
|
|
2263
2405
|
function buildManifest(dir) {
|
|
2264
2406
|
const manifest = {};
|
|
2265
2407
|
const visit = (current) => {
|
|
2266
|
-
for (const entry of
|
|
2267
|
-
const full =
|
|
2408
|
+
for (const entry of fs14.readdirSync(current, { withFileTypes: true })) {
|
|
2409
|
+
const full = path14.join(current, entry.name);
|
|
2268
2410
|
if (entry.isDirectory()) visit(full);
|
|
2269
|
-
else if (entry.isFile()) manifest[toPosix(
|
|
2411
|
+
else if (entry.isFile()) manifest[toPosix(path14.relative(dir, full))] = hashFile(full);
|
|
2270
2412
|
}
|
|
2271
2413
|
};
|
|
2272
|
-
if (
|
|
2414
|
+
if (fs14.existsSync(dir)) visit(dir);
|
|
2273
2415
|
return sortManifest(manifest);
|
|
2274
2416
|
}
|
|
2275
2417
|
function sortManifest(manifest) {
|
|
@@ -2278,18 +2420,18 @@ function sortManifest(manifest) {
|
|
|
2278
2420
|
function scanLocal(paths) {
|
|
2279
2421
|
const units = /* @__PURE__ */ new Map();
|
|
2280
2422
|
const notSynced = [];
|
|
2281
|
-
if (!
|
|
2423
|
+
if (!fs14.existsSync(paths.design)) return { units, notSynced };
|
|
2282
2424
|
const system = unitManifest(paths, SYSTEM_UNIT);
|
|
2283
2425
|
if (Object.keys(system).length) units.set(SYSTEM_UNIT, { key: SYSTEM_UNIT, kind: "system", manifest: system });
|
|
2284
|
-
for (const entry of
|
|
2426
|
+
for (const entry of fs14.readdirSync(paths.design, { withFileTypes: true })) {
|
|
2285
2427
|
const { name } = entry;
|
|
2286
2428
|
if (name.startsWith(".") || LOCAL_ONLY.has(name)) continue;
|
|
2287
2429
|
if (name === "design.json" && entry.isFile()) continue;
|
|
2288
2430
|
if ((name === "system" || name === "canvas") && entry.isDirectory()) continue;
|
|
2289
2431
|
notSynced.push(entry.isDirectory() ? `${name}/` : name);
|
|
2290
2432
|
}
|
|
2291
|
-
if (
|
|
2292
|
-
for (const entry of
|
|
2433
|
+
if (fs14.existsSync(paths.canvases) && fs14.statSync(paths.canvases).isDirectory()) {
|
|
2434
|
+
for (const entry of fs14.readdirSync(paths.canvases, { withFileTypes: true })) {
|
|
2293
2435
|
if (entry.name.startsWith(".")) continue;
|
|
2294
2436
|
if (!entry.isDirectory() || !ID_PATTERN.test(entry.name)) {
|
|
2295
2437
|
notSynced.push(`canvas/${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
@@ -2303,11 +2445,11 @@ function scanLocal(paths) {
|
|
|
2303
2445
|
return { units, notSynced: notSynced.sort() };
|
|
2304
2446
|
}
|
|
2305
2447
|
function unitBuildDir(paths, key) {
|
|
2306
|
-
return
|
|
2448
|
+
return path14.join(paths.cache, "cloud", "build", ...key.split("/"));
|
|
2307
2449
|
}
|
|
2308
2450
|
async function buildUnit(paths, key, out = unitBuildDir(paths, key)) {
|
|
2309
2451
|
const id2 = unitCanvasId(key);
|
|
2310
|
-
|
|
2452
|
+
fs14.rmSync(out, { recursive: true, force: true });
|
|
2311
2453
|
return buildSite(paths, out, {
|
|
2312
2454
|
canvases: id2 === null ? [] : [id2],
|
|
2313
2455
|
includeSystem: id2 === null,
|
|
@@ -2438,7 +2580,7 @@ var CloudClient = class {
|
|
|
2438
2580
|
}
|
|
2439
2581
|
host;
|
|
2440
2582
|
token;
|
|
2441
|
-
async send(method,
|
|
2583
|
+
async send(method, path31, init) {
|
|
2442
2584
|
const headers = {
|
|
2443
2585
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2444
2586
|
Accept: "application/json"
|
|
@@ -2446,7 +2588,7 @@ var CloudClient = class {
|
|
|
2446
2588
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2447
2589
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2448
2590
|
try {
|
|
2449
|
-
return await fetch(`${this.host}/api${
|
|
2591
|
+
return await fetch(`${this.host}/api${path31}`, {
|
|
2450
2592
|
method,
|
|
2451
2593
|
headers,
|
|
2452
2594
|
body: init.body,
|
|
@@ -2467,8 +2609,8 @@ var CloudClient = class {
|
|
|
2467
2609
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2468
2610
|
}
|
|
2469
2611
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2470
|
-
async request(method,
|
|
2471
|
-
const response = await this.send(method,
|
|
2612
|
+
async request(method, path31, body) {
|
|
2613
|
+
const response = await this.send(method, path31, {
|
|
2472
2614
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2473
2615
|
type: body === void 0 ? void 0 : "application/json",
|
|
2474
2616
|
timeout: JSON_TIMEOUT
|
|
@@ -2480,7 +2622,7 @@ var CloudClient = class {
|
|
|
2480
2622
|
try {
|
|
2481
2623
|
return JSON.parse(text2);
|
|
2482
2624
|
} catch {
|
|
2483
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2625
|
+
throw new CloudError(`${this.host} answered ${method} ${path31} with something other than JSON`, 0, "bad_response");
|
|
2484
2626
|
}
|
|
2485
2627
|
}
|
|
2486
2628
|
/** POST /device/code. */
|
|
@@ -2614,9 +2756,9 @@ var CloudClient = class {
|
|
|
2614
2756
|
};
|
|
2615
2757
|
|
|
2616
2758
|
// src/cloud/credentials.ts
|
|
2617
|
-
import
|
|
2759
|
+
import fs15 from "fs";
|
|
2618
2760
|
import os2 from "os";
|
|
2619
|
-
import
|
|
2761
|
+
import path15 from "path";
|
|
2620
2762
|
var DEFAULT_CLOUD = "https://app.fossdesign.dev";
|
|
2621
2763
|
function normalizeHost(value) {
|
|
2622
2764
|
let url;
|
|
@@ -2633,14 +2775,14 @@ function cloudHost() {
|
|
|
2633
2775
|
return normalizeHost(process.env.FOSS_DESIGN_CLOUD || DEFAULT_CLOUD);
|
|
2634
2776
|
}
|
|
2635
2777
|
function credentialsFile() {
|
|
2636
|
-
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME ||
|
|
2637
|
-
return
|
|
2778
|
+
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME || path15.join(os2.homedir(), ".config");
|
|
2779
|
+
return path15.join(base, "foss-design", "credentials.json");
|
|
2638
2780
|
}
|
|
2639
2781
|
function readAll() {
|
|
2640
2782
|
const file = credentialsFile();
|
|
2641
|
-
if (!
|
|
2783
|
+
if (!fs15.existsSync(file)) return {};
|
|
2642
2784
|
try {
|
|
2643
|
-
const data = JSON.parse(
|
|
2785
|
+
const data = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
2644
2786
|
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
2645
2787
|
} catch (error) {
|
|
2646
2788
|
throw new CliError(`${file} is not valid JSON (${error.message}); fix or delete it`);
|
|
@@ -2648,12 +2790,12 @@ function readAll() {
|
|
|
2648
2790
|
}
|
|
2649
2791
|
function writeAll(data) {
|
|
2650
2792
|
const file = credentialsFile();
|
|
2651
|
-
|
|
2793
|
+
fs15.mkdirSync(path15.dirname(file), { recursive: true, mode: 448 });
|
|
2652
2794
|
const temp = `${file}.${process.pid}.tmp`;
|
|
2653
|
-
|
|
2795
|
+
fs15.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2654
2796
|
`, { mode: 384 });
|
|
2655
|
-
|
|
2656
|
-
|
|
2797
|
+
fs15.chmodSync(temp, 384);
|
|
2798
|
+
fs15.renameSync(temp, file);
|
|
2657
2799
|
}
|
|
2658
2800
|
function getCredential(host) {
|
|
2659
2801
|
const entry = readAll()[host];
|
|
@@ -2676,17 +2818,17 @@ function requireCredential(host) {
|
|
|
2676
2818
|
}
|
|
2677
2819
|
|
|
2678
2820
|
// src/cloud/state.ts
|
|
2679
|
-
import
|
|
2680
|
-
import
|
|
2681
|
-
var linkFile = (paths) =>
|
|
2682
|
-
var cloudCache = (paths) =>
|
|
2683
|
-
var incomingDir = (paths, key) =>
|
|
2821
|
+
import fs16 from "fs";
|
|
2822
|
+
import path16 from "path";
|
|
2823
|
+
var linkFile = (paths) => path16.join(paths.design, "cloud.json");
|
|
2824
|
+
var cloudCache = (paths) => path16.join(paths.cache, "cloud");
|
|
2825
|
+
var incomingDir = (paths, key) => path16.join(cloudCache(paths), "incoming", ...key.split("/"));
|
|
2684
2826
|
function readLink(paths) {
|
|
2685
2827
|
const file = linkFile(paths);
|
|
2686
|
-
if (!
|
|
2828
|
+
if (!fs16.existsSync(file)) return null;
|
|
2687
2829
|
let data;
|
|
2688
2830
|
try {
|
|
2689
|
-
data = JSON.parse(
|
|
2831
|
+
data = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
2690
2832
|
} catch (error) {
|
|
2691
2833
|
throw new CliError(`${file} is not valid JSON (${error.message}); run \`design link <project>\` again`);
|
|
2692
2834
|
}
|
|
@@ -2716,9 +2858,9 @@ function writeLink(paths, link) {
|
|
|
2716
2858
|
units: sorted(link.units),
|
|
2717
2859
|
conflicts: sorted(link.conflicts)
|
|
2718
2860
|
};
|
|
2719
|
-
|
|
2861
|
+
fs16.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2720
2862
|
`);
|
|
2721
|
-
|
|
2863
|
+
fs16.renameSync(temp, file);
|
|
2722
2864
|
}
|
|
2723
2865
|
var hashes = (manifest) => Object.fromEntries(Object.entries(manifest).map(([file, entry]) => [file, entry.hash]));
|
|
2724
2866
|
function diffFiles(a, b) {
|
|
@@ -2796,8 +2938,8 @@ var byUnit = (a, b) => a === SYSTEM_UNIT ? -1 : b === SYSTEM_UNIT ? 1 : a < b ?
|
|
|
2796
2938
|
var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
2797
2939
|
|
|
2798
2940
|
// src/cloud/pull.ts
|
|
2799
|
-
import
|
|
2800
|
-
import
|
|
2941
|
+
import fs17 from "fs";
|
|
2942
|
+
import path17 from "path";
|
|
2801
2943
|
|
|
2802
2944
|
// src/capture/frames.ts
|
|
2803
2945
|
var AUTO_VIEWPORT_HEIGHT = 900;
|
|
@@ -2880,34 +3022,34 @@ function checkManifest(key, manifest) {
|
|
|
2880
3022
|
}
|
|
2881
3023
|
}
|
|
2882
3024
|
function writeInside(root, rel, data) {
|
|
2883
|
-
const file =
|
|
3025
|
+
const file = path17.join(root, rel);
|
|
2884
3026
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
2885
|
-
|
|
2886
|
-
if (!isInside(
|
|
3027
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
3028
|
+
if (!isInside(fs17.realpathSync(root), fs17.realpathSync(path17.dirname(file))))
|
|
2887
3029
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
2888
|
-
if (
|
|
2889
|
-
|
|
3030
|
+
if (fs17.existsSync(file) && fs17.lstatSync(file).isSymbolicLink()) fs17.unlinkSync(file);
|
|
3031
|
+
fs17.writeFileSync(file, data);
|
|
2890
3032
|
}
|
|
2891
3033
|
function pruneEmpty(dir, stop) {
|
|
2892
3034
|
let current = dir;
|
|
2893
3035
|
while (isInside(stop, current) && current !== stop) {
|
|
2894
3036
|
try {
|
|
2895
|
-
if (
|
|
2896
|
-
|
|
3037
|
+
if (fs17.readdirSync(current).length) return;
|
|
3038
|
+
fs17.rmdirSync(current);
|
|
2897
3039
|
} catch {
|
|
2898
3040
|
return;
|
|
2899
3041
|
}
|
|
2900
|
-
current =
|
|
3042
|
+
current = path17.dirname(current);
|
|
2901
3043
|
}
|
|
2902
3044
|
}
|
|
2903
3045
|
function applyUnit(paths, key, local, target, blobs) {
|
|
2904
3046
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
2905
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3047
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path17.dirname(unitRoot(paths, key));
|
|
2906
3048
|
for (const rel of Object.keys(local)) {
|
|
2907
3049
|
if (rel in target) continue;
|
|
2908
|
-
const file =
|
|
2909
|
-
|
|
2910
|
-
pruneEmpty(
|
|
3050
|
+
const file = path17.join(paths.design, rel);
|
|
3051
|
+
fs17.rmSync(file, { force: true });
|
|
3052
|
+
pruneEmpty(path17.dirname(file), stop);
|
|
2911
3053
|
counts.removed++;
|
|
2912
3054
|
}
|
|
2913
3055
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -2970,7 +3112,7 @@ async function pull(ctx, options) {
|
|
|
2970
3112
|
} else if (!s.ahead) {
|
|
2971
3113
|
if (s.conflictRev !== void 0) {
|
|
2972
3114
|
delete link.conflicts[key];
|
|
2973
|
-
|
|
3115
|
+
fs17.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
2974
3116
|
}
|
|
2975
3117
|
report.units.push({ ...reportFor("up_to_date"), rev: s.baseRev });
|
|
2976
3118
|
} else if (!s.changed.length) {
|
|
@@ -3000,14 +3142,14 @@ async function pull(ctx, options) {
|
|
|
3000
3142
|
headRev: unit.headRev,
|
|
3001
3143
|
incoming: incomingDir(paths, key)
|
|
3002
3144
|
};
|
|
3003
|
-
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${
|
|
3004
|
-
if (s.conflictRev === unit.headRev &&
|
|
3145
|
+
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${path17.relative(paths.root, entry.incoming)}`;
|
|
3146
|
+
if (s.conflictRev === unit.headRev && fs17.existsSync(entry.incoming)) report.units.push(entry);
|
|
3005
3147
|
else plans.push({ key, kind: "incoming", remote: unit, report: entry });
|
|
3006
3148
|
}
|
|
3007
3149
|
}
|
|
3008
3150
|
const byHash = /* @__PURE__ */ new Map();
|
|
3009
3151
|
for (const unit of scan.units.values())
|
|
3010
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
3152
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path17.join(paths.design, rel));
|
|
3011
3153
|
const needed = /* @__PURE__ */ new Set();
|
|
3012
3154
|
for (const plan of plans) {
|
|
3013
3155
|
if (!plan.remote) continue;
|
|
@@ -3018,7 +3160,7 @@ async function pull(ctx, options) {
|
|
|
3018
3160
|
const download = [];
|
|
3019
3161
|
for (const hash of needed) {
|
|
3020
3162
|
const file = byHash.get(hash);
|
|
3021
|
-
if (file) blobs.set(hash,
|
|
3163
|
+
if (file) blobs.set(hash, fs17.readFileSync(file));
|
|
3022
3164
|
else download.push(hash);
|
|
3023
3165
|
}
|
|
3024
3166
|
if (download.length) {
|
|
@@ -3044,11 +3186,11 @@ async function pull(ctx, options) {
|
|
|
3044
3186
|
if (plan.remote) link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
3045
3187
|
else delete link.units[key];
|
|
3046
3188
|
delete link.conflicts[key];
|
|
3047
|
-
|
|
3189
|
+
fs17.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3048
3190
|
} else {
|
|
3049
3191
|
const dir = incomingDir(paths, key);
|
|
3050
|
-
|
|
3051
|
-
|
|
3192
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
3193
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
3052
3194
|
for (const [rel, file] of Object.entries(plan.remote.manifest))
|
|
3053
3195
|
writeInside(dir, unitRelative(key, rel), blobs.get(file.hash));
|
|
3054
3196
|
link.conflicts[key] = plan.remote.headRev;
|
|
@@ -3069,8 +3211,8 @@ async function pull(ctx, options) {
|
|
|
3069
3211
|
}
|
|
3070
3212
|
|
|
3071
3213
|
// src/cloud/push.ts
|
|
3072
|
-
import
|
|
3073
|
-
import
|
|
3214
|
+
import fs18 from "fs";
|
|
3215
|
+
import path18 from "path";
|
|
3074
3216
|
function blockedReport(status, message) {
|
|
3075
3217
|
return {
|
|
3076
3218
|
unit: status.key,
|
|
@@ -3176,7 +3318,7 @@ async function push(ctx, options) {
|
|
|
3176
3318
|
for (const key of toPush) {
|
|
3177
3319
|
const s = status(key);
|
|
3178
3320
|
if (s.conflictRev !== void 0) {
|
|
3179
|
-
const dir =
|
|
3321
|
+
const dir = path18.relative(paths.root, incomingDir(paths, key));
|
|
3180
3322
|
blocked.push(
|
|
3181
3323
|
blockedReport(s, `pull left the cloud version in ${dir}; merge it, then \`design push --resolved ${key}\``)
|
|
3182
3324
|
);
|
|
@@ -3220,7 +3362,7 @@ async function push(ctx, options) {
|
|
|
3220
3362
|
const files = /* @__PURE__ */ new Map();
|
|
3221
3363
|
const addFiles = (manifest, dir) => {
|
|
3222
3364
|
for (const [rel, entry] of Object.entries(manifest))
|
|
3223
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
3365
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path18.join(dir, rel));
|
|
3224
3366
|
};
|
|
3225
3367
|
for (const key of pushing) {
|
|
3226
3368
|
const id2 = unitCanvasId(key);
|
|
@@ -3252,7 +3394,7 @@ async function push(ctx, options) {
|
|
|
3252
3394
|
if (missing.length) {
|
|
3253
3395
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
3254
3396
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
3255
|
-
const bytes = missing.reduce((sum, hash) => sum +
|
|
3397
|
+
const bytes = missing.reduce((sum, hash) => sum + fs18.statSync(files.get(hash)).size, 0);
|
|
3256
3398
|
const bar = ctx.progress();
|
|
3257
3399
|
const started = Date.now();
|
|
3258
3400
|
let sentFiles = 0;
|
|
@@ -3261,12 +3403,12 @@ async function push(ctx, options) {
|
|
|
3261
3403
|
bar.update(line());
|
|
3262
3404
|
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
3263
3405
|
const file = files.get(hash);
|
|
3264
|
-
const data =
|
|
3406
|
+
const data = fs18.readFileSync(file);
|
|
3265
3407
|
try {
|
|
3266
3408
|
await client.putBlob(link.project, hash, data);
|
|
3267
3409
|
} catch (error) {
|
|
3268
3410
|
if (error instanceof CloudError && error.status === 413)
|
|
3269
|
-
throw new CliError(`${
|
|
3411
|
+
throw new CliError(`${path18.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
3270
3412
|
throw error;
|
|
3271
3413
|
}
|
|
3272
3414
|
sentFiles++;
|
|
@@ -3288,7 +3430,7 @@ async function push(ctx, options) {
|
|
|
3288
3430
|
url: webUrl(link, unit.key)
|
|
3289
3431
|
});
|
|
3290
3432
|
}
|
|
3291
|
-
for (const key of resolved)
|
|
3433
|
+
for (const key of resolved) fs18.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3292
3434
|
writeLink(paths, link);
|
|
3293
3435
|
report.units.sort((a, b) => byUnit(a.unit, b.unit));
|
|
3294
3436
|
return report;
|
|
@@ -3299,7 +3441,7 @@ function refuseLegacyGo(paths, keys) {
|
|
|
3299
3441
|
for (const key of keys) {
|
|
3300
3442
|
if (!key.startsWith("canvas/")) continue;
|
|
3301
3443
|
const id2 = key.slice("canvas/".length);
|
|
3302
|
-
for (const file of canvasCodeFiles(
|
|
3444
|
+
for (const file of canvasCodeFiles(path18.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
3303
3445
|
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
3304
3446
|
}
|
|
3305
3447
|
}
|
|
@@ -3620,7 +3762,7 @@ async function runMe(paths, options) {
|
|
|
3620
3762
|
}
|
|
3621
3763
|
|
|
3622
3764
|
// src/capture/chrome.ts
|
|
3623
|
-
import
|
|
3765
|
+
import fs19 from "fs";
|
|
3624
3766
|
var CANDIDATES = {
|
|
3625
3767
|
darwin: [
|
|
3626
3768
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -3644,8 +3786,8 @@ var CANDIDATES = {
|
|
|
3644
3786
|
};
|
|
3645
3787
|
function findChrome() {
|
|
3646
3788
|
const configured = process.env.DESIGN_CHROME;
|
|
3647
|
-
if (configured &&
|
|
3648
|
-
for (const candidate of CANDIDATES[process.platform] ?? []) if (
|
|
3789
|
+
if (configured && fs19.existsSync(configured)) return configured;
|
|
3790
|
+
for (const candidate of CANDIDATES[process.platform] ?? []) if (fs19.existsSync(candidate)) return candidate;
|
|
3649
3791
|
return null;
|
|
3650
3792
|
}
|
|
3651
3793
|
async function launchChrome() {
|
|
@@ -3717,26 +3859,26 @@ async function openFrame(page, url, options = {}) {
|
|
|
3717
3859
|
}
|
|
3718
3860
|
|
|
3719
3861
|
// src/server/state.ts
|
|
3720
|
-
import
|
|
3721
|
-
import
|
|
3722
|
-
var stateFile = (paths) =>
|
|
3723
|
-
var logFile = (paths) =>
|
|
3862
|
+
import fs20 from "fs";
|
|
3863
|
+
import path19 from "path";
|
|
3864
|
+
var stateFile = (paths) => path19.join(paths.cache, "server.json");
|
|
3865
|
+
var logFile = (paths) => path19.join(paths.cache, "server.log");
|
|
3724
3866
|
function readState(paths) {
|
|
3725
3867
|
try {
|
|
3726
|
-
return JSON.parse(
|
|
3868
|
+
return JSON.parse(fs20.readFileSync(stateFile(paths), "utf8"));
|
|
3727
3869
|
} catch {
|
|
3728
3870
|
return null;
|
|
3729
3871
|
}
|
|
3730
3872
|
}
|
|
3731
3873
|
function writeState(paths, state) {
|
|
3732
|
-
|
|
3733
|
-
|
|
3874
|
+
fs20.mkdirSync(paths.cache, { recursive: true });
|
|
3875
|
+
fs20.writeFileSync(stateFile(paths), `${JSON.stringify(state, null, 2)}
|
|
3734
3876
|
`);
|
|
3735
3877
|
}
|
|
3736
3878
|
function clearState(paths, pid) {
|
|
3737
3879
|
const state = readState(paths);
|
|
3738
3880
|
if (pid !== void 0 && state && state.pid !== pid) return;
|
|
3739
|
-
|
|
3881
|
+
fs20.rmSync(stateFile(paths), { force: true });
|
|
3740
3882
|
}
|
|
3741
3883
|
function pidAlive(pid) {
|
|
3742
3884
|
try {
|
|
@@ -3761,18 +3903,18 @@ async function liveServer(paths) {
|
|
|
3761
3903
|
|
|
3762
3904
|
// src/cli/commands/preview.ts
|
|
3763
3905
|
import { spawn } from "child_process";
|
|
3764
|
-
import
|
|
3906
|
+
import fs24 from "fs";
|
|
3765
3907
|
|
|
3766
3908
|
// src/server/index.ts
|
|
3767
|
-
import
|
|
3909
|
+
import fs23 from "fs";
|
|
3768
3910
|
import http from "http";
|
|
3769
|
-
import
|
|
3911
|
+
import path22 from "path";
|
|
3770
3912
|
import sirv from "sirv";
|
|
3771
3913
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
3772
3914
|
|
|
3773
3915
|
// src/server/api.ts
|
|
3774
|
-
import
|
|
3775
|
-
import
|
|
3916
|
+
import fs21 from "fs";
|
|
3917
|
+
import path20 from "path";
|
|
3776
3918
|
function sendJson(res, status, body) {
|
|
3777
3919
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
3778
3920
|
res.end(JSON.stringify(body));
|
|
@@ -3823,17 +3965,34 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3823
3965
|
}
|
|
3824
3966
|
if (route2 === "/source") {
|
|
3825
3967
|
const rel = url.searchParams.get("path") ?? "";
|
|
3826
|
-
const file =
|
|
3827
|
-
if (!rel || !isInside(project.paths.root, file) || !
|
|
3968
|
+
const file = path20.resolve(project.paths.root, rel);
|
|
3969
|
+
if (!rel || !isInside(project.paths.root, file) || !fs21.existsSync(file) || fs21.statSync(file).isDirectory()) {
|
|
3828
3970
|
sendJson(res, 404, { error: "not found" });
|
|
3829
3971
|
return true;
|
|
3830
3972
|
}
|
|
3831
|
-
if (
|
|
3973
|
+
if (fs21.statSync(file).size > 2 * 1024 * 1024) {
|
|
3832
3974
|
sendJson(res, 413, { error: "file too large to show" });
|
|
3833
3975
|
return true;
|
|
3834
3976
|
}
|
|
3835
3977
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
3836
|
-
res.end(
|
|
3978
|
+
res.end(fs21.readFileSync(file, "utf8"));
|
|
3979
|
+
return true;
|
|
3980
|
+
}
|
|
3981
|
+
if (route2 === "/icon" && (method === "PUT" || method === "DELETE")) {
|
|
3982
|
+
try {
|
|
3983
|
+
const body = method === "PUT" ? await readBody(req, MAX_ICON_BYTES * 4) : null;
|
|
3984
|
+
const problem = body ? iconProblem(body) : null;
|
|
3985
|
+
if (problem) {
|
|
3986
|
+
sendJson(res, 400, { error: problem });
|
|
3987
|
+
return true;
|
|
3988
|
+
}
|
|
3989
|
+
writeIcon(project.paths, body);
|
|
3990
|
+
project.invalidateSystem();
|
|
3991
|
+
events.send({ type: "project" });
|
|
3992
|
+
sendJson(res, 200, { icon: project.iconUrl() });
|
|
3993
|
+
} catch (error) {
|
|
3994
|
+
sendJson(res, 400, { error: error.message });
|
|
3995
|
+
}
|
|
3837
3996
|
return true;
|
|
3838
3997
|
}
|
|
3839
3998
|
const snapshotPost = /^\/snapshots\/([^/]+)\/([^/]+)$/.exec(route2);
|
|
@@ -3863,7 +4022,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3863
4022
|
return true;
|
|
3864
4023
|
}
|
|
3865
4024
|
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=31536000, immutable" });
|
|
3866
|
-
|
|
4025
|
+
fs21.createReadStream(file).pipe(res);
|
|
3867
4026
|
return true;
|
|
3868
4027
|
}
|
|
3869
4028
|
return false;
|
|
@@ -3903,8 +4062,8 @@ var EventHub = class {
|
|
|
3903
4062
|
};
|
|
3904
4063
|
|
|
3905
4064
|
// src/server/snapshots.ts
|
|
3906
|
-
import
|
|
3907
|
-
import
|
|
4065
|
+
import fs22 from "fs";
|
|
4066
|
+
import path21 from "path";
|
|
3908
4067
|
var SAFE = /^@?[a-z0-9][a-z0-9_-]*$/i;
|
|
3909
4068
|
function isSafeSegment(value) {
|
|
3910
4069
|
return SAFE.test(value);
|
|
@@ -3918,14 +4077,14 @@ var SnapshotStore = class {
|
|
|
3918
4077
|
urlBase;
|
|
3919
4078
|
meta = /* @__PURE__ */ new Map();
|
|
3920
4079
|
metaFile(canvas) {
|
|
3921
|
-
return
|
|
4080
|
+
return path21.join(this.dir, canvas, "meta.json");
|
|
3922
4081
|
}
|
|
3923
4082
|
load(canvas) {
|
|
3924
4083
|
const cached = this.meta.get(canvas);
|
|
3925
4084
|
if (cached) return cached;
|
|
3926
4085
|
let data = {};
|
|
3927
4086
|
try {
|
|
3928
|
-
data = JSON.parse(
|
|
4087
|
+
data = JSON.parse(fs22.readFileSync(this.metaFile(canvas), "utf8"));
|
|
3929
4088
|
} catch {
|
|
3930
4089
|
}
|
|
3931
4090
|
this.meta.set(canvas, data);
|
|
@@ -3951,21 +4110,21 @@ var SnapshotStore = class {
|
|
|
3951
4110
|
};
|
|
3952
4111
|
save(canvas, id2, theme2, png, height) {
|
|
3953
4112
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3954
|
-
const dir =
|
|
3955
|
-
|
|
3956
|
-
|
|
4113
|
+
const dir = path21.join(this.dir, canvas);
|
|
4114
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
4115
|
+
fs22.writeFileSync(path21.join(dir, `${id2}.${theme2}.png`), png);
|
|
3957
4116
|
const data = this.load(canvas);
|
|
3958
4117
|
const version = Date.now();
|
|
3959
4118
|
data[id2] = { ...data[id2], [theme2]: version, ...height ? { height } : {} };
|
|
3960
|
-
|
|
4119
|
+
fs22.writeFileSync(this.metaFile(canvas), JSON.stringify(data));
|
|
3961
4120
|
return { url: this.url(canvas, id2, theme2, version) };
|
|
3962
4121
|
}
|
|
3963
4122
|
/** Absolute path of `<id>.<theme>.png`, or of its thumbnail for `<id>.<theme>.thumb.png` (made on first use); null when missing. */
|
|
3964
4123
|
file(canvas, name) {
|
|
3965
4124
|
const match = /^(@?[a-z0-9][a-z0-9_-]*)\.(light|dark)(\.thumb)?\.png$/i.exec(name);
|
|
3966
4125
|
if (!isSafeSegment(canvas) || !match) return null;
|
|
3967
|
-
const file =
|
|
3968
|
-
if (!
|
|
4126
|
+
const file = path21.join(this.dir, canvas, `${match[1]}.${match[2]}.png`);
|
|
4127
|
+
if (!fs22.existsSync(file)) return null;
|
|
3969
4128
|
return match[3] ? ensureThumb(file) : file;
|
|
3970
4129
|
}
|
|
3971
4130
|
};
|
|
@@ -4004,16 +4163,16 @@ function notFoundPage(res, title, text2) {
|
|
|
4004
4163
|
}
|
|
4005
4164
|
async function startDevServer(options) {
|
|
4006
4165
|
const paths = designPaths(options.root);
|
|
4007
|
-
if (!
|
|
4008
|
-
|
|
4166
|
+
if (!fs23.existsSync(paths.design)) throw new Error(`No .design folder in ${options.root}; run \`design init\``);
|
|
4167
|
+
fs23.mkdirSync(paths.cache, { recursive: true });
|
|
4009
4168
|
const snapshots = new SnapshotStore(paths.snapshots);
|
|
4010
4169
|
const project = new DesignProject(paths, DEV_URLS, snapshots.lookup);
|
|
4011
4170
|
const entries = new Entries(project);
|
|
4012
4171
|
entries.sync();
|
|
4013
|
-
const links = linkShippedPackages(paths);
|
|
4172
|
+
const links = linkShippedPackages(paths, project.appDir());
|
|
4014
4173
|
const events = new EventHub();
|
|
4015
4174
|
const httpServer = http.createServer();
|
|
4016
|
-
dropStaleDepCache(paths);
|
|
4175
|
+
dropStaleDepCache(paths, project.appDir());
|
|
4017
4176
|
const base = baseConfig(project);
|
|
4018
4177
|
const vite = await createServer({
|
|
4019
4178
|
...base,
|
|
@@ -4031,8 +4190,8 @@ async function startDevServer(options) {
|
|
|
4031
4190
|
// `.cache` is ours (server.log, snapshots, builds): a change there must not reload frames. The
|
|
4032
4191
|
// generated entries are modules the frames load, so those stay watched.
|
|
4033
4192
|
ignored: (file) => {
|
|
4034
|
-
const rel = toPosix(
|
|
4035
|
-
const inCache = rel !== "" && !rel.startsWith("..") && !
|
|
4193
|
+
const rel = toPosix(path22.relative(paths.cache, file));
|
|
4194
|
+
const inCache = rel !== "" && !rel.startsWith("..") && !path22.isAbsolute(rel);
|
|
4036
4195
|
return inCache && rel !== "entries" && !rel.startsWith("entries/");
|
|
4037
4196
|
}
|
|
4038
4197
|
},
|
|
@@ -4041,7 +4200,7 @@ async function startDevServer(options) {
|
|
|
4041
4200
|
forwardConsole: false
|
|
4042
4201
|
},
|
|
4043
4202
|
optimizeDeps: {
|
|
4044
|
-
entries: entries.files().map((file) => toPosix(
|
|
4203
|
+
entries: entries.files().map((file) => toPosix(path22.relative(paths.design, file)))
|
|
4045
4204
|
}
|
|
4046
4205
|
});
|
|
4047
4206
|
const publicDir = project.publicDir();
|
|
@@ -4053,16 +4212,16 @@ async function startDevServer(options) {
|
|
|
4053
4212
|
return null;
|
|
4054
4213
|
}
|
|
4055
4214
|
};
|
|
4056
|
-
const viewer =
|
|
4057
|
-
const viewerIndex =
|
|
4215
|
+
const viewer = fs23.existsSync(PKG.viewer) ? sirv(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4216
|
+
const viewerIndex = path22.join(PKG.viewer, "index.html");
|
|
4058
4217
|
const serveViewerIndex = (res) => {
|
|
4059
|
-
const html =
|
|
4218
|
+
const html = fs23.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
4060
4219
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
4061
4220
|
res.end(html);
|
|
4062
4221
|
};
|
|
4063
4222
|
const ctx = { project, events, snapshots };
|
|
4064
4223
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
4065
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4224
|
+
const bootUrl = `/${relToDesign(paths, path22.join(entries.dir, HTML_BOOT))}`;
|
|
4066
4225
|
const frameHead = (source, extra = {}) => {
|
|
4067
4226
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
4068
4227
|
return {
|
|
@@ -4083,7 +4242,7 @@ async function startDevServer(options) {
|
|
|
4083
4242
|
notFoundPage(res, "Screen not found", `No screen "${id2}" in "${canvas}". Check canvas.json.`);
|
|
4084
4243
|
return;
|
|
4085
4244
|
}
|
|
4086
|
-
if (!
|
|
4245
|
+
if (!fs23.existsSync(entries.entryFile(source))) entries.sync();
|
|
4087
4246
|
const entryUrl = `/${relToDesign(paths, entries.entryFile(source))}`;
|
|
4088
4247
|
const html = await vite.transformIndexHtml(url2.pathname + url2.search, moduleShell(frameHead(source), entryUrl));
|
|
4089
4248
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -4092,8 +4251,8 @@ async function startDevServer(options) {
|
|
|
4092
4251
|
};
|
|
4093
4252
|
const serveHtmlScreen = async (res, url2) => {
|
|
4094
4253
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
4095
|
-
const file =
|
|
4096
|
-
if (!isInside(paths.design, file) || !
|
|
4254
|
+
const file = path22.resolve(paths.design, rel);
|
|
4255
|
+
if (!isInside(paths.design, file) || !fs23.existsSync(file)) {
|
|
4097
4256
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
4098
4257
|
return;
|
|
4099
4258
|
}
|
|
@@ -4103,13 +4262,13 @@ async function startDevServer(options) {
|
|
|
4103
4262
|
key: `${canvas}/${id2}`,
|
|
4104
4263
|
canvas,
|
|
4105
4264
|
id: id2,
|
|
4106
|
-
title:
|
|
4265
|
+
title: path22.basename(file),
|
|
4107
4266
|
file,
|
|
4108
4267
|
format: "html",
|
|
4109
4268
|
props: {},
|
|
4110
4269
|
system: true
|
|
4111
4270
|
};
|
|
4112
|
-
const raw =
|
|
4271
|
+
const raw = fs23.readFileSync(file, "utf8");
|
|
4113
4272
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
4114
4273
|
const html = injectIntoHtml(
|
|
4115
4274
|
raw,
|
|
@@ -4142,7 +4301,7 @@ async function startDevServer(options) {
|
|
|
4142
4301
|
}
|
|
4143
4302
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4144
4303
|
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4145
|
-
if (publicDir && rel !== null && !project.isPublicFile(rel) &&
|
|
4304
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs23.existsSync(path22.join(publicDir, rel))) {
|
|
4146
4305
|
notFoundPage(res, "Not found", url2.pathname);
|
|
4147
4306
|
return;
|
|
4148
4307
|
}
|
|
@@ -4154,7 +4313,7 @@ async function startDevServer(options) {
|
|
|
4154
4313
|
const rel = publicRel(url2.pathname.slice(1));
|
|
4155
4314
|
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4156
4315
|
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4157
|
-
else if (
|
|
4316
|
+
else if (path22.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4158
4317
|
else serveViewerIndex(res);
|
|
4159
4318
|
};
|
|
4160
4319
|
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
@@ -4182,7 +4341,7 @@ async function startDevServer(options) {
|
|
|
4182
4341
|
};
|
|
4183
4342
|
vite.watcher.on("all", (event, file) => {
|
|
4184
4343
|
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4185
|
-
const rel = toPosix(
|
|
4344
|
+
const rel = toPosix(path22.relative(paths.design, file));
|
|
4186
4345
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
4187
4346
|
if (rel === "design.json") {
|
|
4188
4347
|
queue("config", () => {
|
|
@@ -4290,15 +4449,15 @@ async function ensureServer(paths, options) {
|
|
|
4290
4449
|
if (stale)
|
|
4291
4450
|
print(dim(`Restarting the preview server: it runs foss-design ${live2.version}, this is ${packageVersion()}.`));
|
|
4292
4451
|
if (live2) await stopServer(paths);
|
|
4293
|
-
|
|
4294
|
-
const log =
|
|
4452
|
+
fs24.mkdirSync(paths.cache, { recursive: true });
|
|
4453
|
+
const log = fs24.openSync(logFile(paths), "w");
|
|
4295
4454
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
4296
4455
|
if (options.port) args.push("--port", String(options.port));
|
|
4297
4456
|
const child = spawn(process.execPath, args, { detached: true, stdio: ["ignore", log, log], cwd: paths.root });
|
|
4298
4457
|
child.unref();
|
|
4299
4458
|
const state = await waitForServer(paths, 4e4);
|
|
4300
4459
|
if (!state) {
|
|
4301
|
-
const tail =
|
|
4460
|
+
const tail = fs24.existsSync(logFile(paths)) ? fs24.readFileSync(logFile(paths), "utf8").split("\n").slice(-25) : [];
|
|
4302
4461
|
throw new CliError(`The preview server did not start.
|
|
4303
4462
|
${tail.join("\n")}`);
|
|
4304
4463
|
}
|
|
@@ -4477,9 +4636,9 @@ function printLinkIssues(issues, project) {
|
|
|
4477
4636
|
}
|
|
4478
4637
|
|
|
4479
4638
|
// src/cli/commands/cloud.ts
|
|
4480
|
-
import
|
|
4639
|
+
import fs25 from "fs";
|
|
4481
4640
|
import os3 from "os";
|
|
4482
|
-
import
|
|
4641
|
+
import path23 from "path";
|
|
4483
4642
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4484
4643
|
async function runLogin() {
|
|
4485
4644
|
const host = cloudHost();
|
|
@@ -4580,7 +4739,7 @@ async function runLink(paths, wanted, options) {
|
|
|
4580
4739
|
print(`${dim("Already linked to")} ${project.name} ${dim(`(${project.id}, ${project.role})`)}`);
|
|
4581
4740
|
} else {
|
|
4582
4741
|
writeLink(paths, { host, project: project.id, units: {}, conflicts: {} });
|
|
4583
|
-
|
|
4742
|
+
fs25.rmSync(path23.join(cloudCache(paths), "incoming"), { recursive: true, force: true });
|
|
4584
4743
|
const replaced = current ? dim(` (replacing the link to ${current.project})`) : "";
|
|
4585
4744
|
print(`${green("linked")} .design \u2192 ${bold(project.name)} ${dim(`(${project.id}, ${project.role})`)}${replaced}`);
|
|
4586
4745
|
}
|
|
@@ -4604,13 +4763,41 @@ async function runLink(paths, wanted, options) {
|
|
|
4604
4763
|
else if (remote) print(dim("Everything is in sync."));
|
|
4605
4764
|
}
|
|
4606
4765
|
|
|
4607
|
-
// src/cli/commands/
|
|
4766
|
+
// src/cli/commands/icon.ts
|
|
4608
4767
|
import fs26 from "fs";
|
|
4609
4768
|
import path24 from "path";
|
|
4769
|
+
function runIcon(paths, file, options) {
|
|
4770
|
+
if (options.remove) {
|
|
4771
|
+
const had = iconFile(paths);
|
|
4772
|
+
writeIcon(paths, null);
|
|
4773
|
+
print(had ? `${green("Removed")} ${path24.relative(process.cwd(), had)}` : "The project has no icon.");
|
|
4774
|
+
return;
|
|
4775
|
+
}
|
|
4776
|
+
if (!file) {
|
|
4777
|
+
const current = iconFile(paths);
|
|
4778
|
+
if (!current) return print("The project has no icon. Set one with `design icon <file.svg|png|webp>`.");
|
|
4779
|
+
return print(`${path24.relative(process.cwd(), current)} ${dim(`${Math.ceil(fs26.statSync(current).size / 1024)} KB`)}`);
|
|
4780
|
+
}
|
|
4781
|
+
let data;
|
|
4782
|
+
try {
|
|
4783
|
+
data = fs26.readFileSync(file);
|
|
4784
|
+
} catch {
|
|
4785
|
+
throw new CliError(`No file ${file}`);
|
|
4786
|
+
}
|
|
4787
|
+
const problem = iconProblem(data, path24.extname(file).slice(1).toLowerCase() || void 0);
|
|
4788
|
+
if (problem) throw new CliError(`${file}: ${problem}`);
|
|
4789
|
+
const written = writeIcon(paths, data);
|
|
4790
|
+
print(`${green("Set")} the project icon: ${path24.relative(process.cwd(), written)}`);
|
|
4791
|
+
print(dim("It syncs with the design system unit: `design push` takes it to the cloud."));
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
// src/cli/commands/import.ts
|
|
4795
|
+
import fs28 from "fs";
|
|
4796
|
+
import path26 from "path";
|
|
4610
4797
|
|
|
4611
4798
|
// src/import/claude-design.ts
|
|
4612
|
-
import
|
|
4613
|
-
import
|
|
4799
|
+
import fs27 from "fs";
|
|
4800
|
+
import path25 from "path";
|
|
4614
4801
|
|
|
4615
4802
|
// src/import/markup.ts
|
|
4616
4803
|
var VOID = /* @__PURE__ */ new Set([
|
|
@@ -5218,11 +5405,11 @@ function boardId(file, used) {
|
|
|
5218
5405
|
return id2;
|
|
5219
5406
|
}
|
|
5220
5407
|
function importClaudeDesign(dir, options = {}) {
|
|
5221
|
-
const canvasFile =
|
|
5222
|
-
if (!
|
|
5408
|
+
const canvasFile = path25.join(dir, "canvas.json");
|
|
5409
|
+
if (!fs27.existsSync(canvasFile))
|
|
5223
5410
|
throw new Error(`No canvas.json in ${dir}: point at the exported Claude Design project folder`);
|
|
5224
|
-
const source = JSON.parse(
|
|
5225
|
-
const templates =
|
|
5411
|
+
const source = JSON.parse(fs27.readFileSync(canvasFile, "utf8"));
|
|
5412
|
+
const templates = fs27.readdirSync(dir).filter((file) => file.endsWith(EXT));
|
|
5226
5413
|
if (!templates.length) throw new Error(`No ${EXT} files in ${dir}`);
|
|
5227
5414
|
const known = new Set(templates.map((file) => file.slice(0, -EXT.length)));
|
|
5228
5415
|
const warnings = /* @__PURE__ */ new Map();
|
|
@@ -5249,7 +5436,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
5249
5436
|
try {
|
|
5250
5437
|
files.set(
|
|
5251
5438
|
`${name}.jsx`,
|
|
5252
|
-
convertFile(name,
|
|
5439
|
+
convertFile(name, fs27.readFileSync(path25.join(dir, file), "utf8"), { known, routes, warn: warn2 })
|
|
5253
5440
|
);
|
|
5254
5441
|
} catch (error) {
|
|
5255
5442
|
warn2(`could not be converted: ${error.message}`);
|
|
@@ -5297,7 +5484,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
5297
5484
|
screens++;
|
|
5298
5485
|
}
|
|
5299
5486
|
const canvas = {
|
|
5300
|
-
title: options.title ?? source.title ??
|
|
5487
|
+
title: options.title ?? source.title ?? path25.basename(path25.resolve(dir)),
|
|
5301
5488
|
description: "Imported from Claude Design.",
|
|
5302
5489
|
// The templates bring their own styles; the design system's reset would restyle them.
|
|
5303
5490
|
system: false,
|
|
@@ -5309,21 +5496,21 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
5309
5496
|
// src/cli/commands/import.ts
|
|
5310
5497
|
var slug = (text2) => text2.normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-").toLowerCase();
|
|
5311
5498
|
function runImport(paths, from, options) {
|
|
5312
|
-
const dir =
|
|
5313
|
-
if (!
|
|
5499
|
+
const dir = path26.resolve(from);
|
|
5500
|
+
if (!fs28.existsSync(dir) || !fs28.statSync(dir).isDirectory()) throw new CliError(`No folder ${from}`);
|
|
5314
5501
|
let result;
|
|
5315
5502
|
try {
|
|
5316
5503
|
result = importClaudeDesign(dir, { title: options.title });
|
|
5317
5504
|
} catch (error) {
|
|
5318
5505
|
throw new CliError(error.message);
|
|
5319
5506
|
}
|
|
5320
|
-
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(
|
|
5507
|
+
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(path26.basename(dir)) || "imported");
|
|
5321
5508
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is no canvas id; pass one with --canvas <id>`);
|
|
5322
|
-
const out =
|
|
5323
|
-
if (
|
|
5324
|
-
|
|
5325
|
-
for (const [name, content] of result.files)
|
|
5326
|
-
|
|
5509
|
+
const out = path26.join(paths.canvases, id2);
|
|
5510
|
+
if (fs28.existsSync(out)) throw new CliError(`.design/canvas/${id2} exists; pick another id with --canvas <id>`);
|
|
5511
|
+
fs28.mkdirSync(path26.join(out, "screens"), { recursive: true });
|
|
5512
|
+
for (const [name, content] of result.files) fs28.writeFileSync(path26.join(out, "screens", name), content);
|
|
5513
|
+
fs28.writeFileSync(path26.join(out, "canvas.json"), `${JSON.stringify(result.canvas, null, 2)}
|
|
5327
5514
|
`);
|
|
5328
5515
|
print(`${green("Imported")} ${bold(String(result.canvas.title))} \u2192 .design/canvas/${id2}`);
|
|
5329
5516
|
print(
|
|
@@ -5342,8 +5529,8 @@ function runImport(paths, from, options) {
|
|
|
5342
5529
|
}
|
|
5343
5530
|
|
|
5344
5531
|
// src/cli/commands/scaffold.ts
|
|
5345
|
-
import
|
|
5346
|
-
import
|
|
5532
|
+
import fs29 from "fs";
|
|
5533
|
+
import path27 from "path";
|
|
5347
5534
|
|
|
5348
5535
|
// src/cli/templates.ts
|
|
5349
5536
|
var TOKENS_CSS = `/*
|
|
@@ -5554,89 +5741,91 @@ function mainScreen(canvasId) {
|
|
|
5554
5741
|
|
|
5555
5742
|
// src/cli/commands/scaffold.ts
|
|
5556
5743
|
function write(file, content, root) {
|
|
5557
|
-
if (
|
|
5558
|
-
print(` ${dim("exists ")} ${
|
|
5744
|
+
if (fs29.existsSync(file)) {
|
|
5745
|
+
print(` ${dim("exists ")} ${path27.relative(root, file)}`);
|
|
5559
5746
|
return false;
|
|
5560
5747
|
}
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
print(` ${green("created")} ${
|
|
5748
|
+
fs29.mkdirSync(path27.dirname(file), { recursive: true });
|
|
5749
|
+
fs29.writeFileSync(file, content);
|
|
5750
|
+
print(` ${green("created")} ${path27.relative(root, file)}`);
|
|
5564
5751
|
return true;
|
|
5565
5752
|
}
|
|
5566
5753
|
function ensureGitignored(root) {
|
|
5567
|
-
const file =
|
|
5568
|
-
const current =
|
|
5754
|
+
const file = path27.join(root, ".gitignore");
|
|
5755
|
+
const current = fs29.existsSync(file) ? fs29.readFileSync(file, "utf8") : "";
|
|
5569
5756
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
5570
5757
|
if (covered) return "present";
|
|
5571
5758
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
5572
|
-
|
|
5759
|
+
fs29.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
5573
5760
|
.design/
|
|
5574
5761
|
`);
|
|
5575
5762
|
return "added";
|
|
5576
5763
|
}
|
|
5577
5764
|
function projectName(root) {
|
|
5578
5765
|
try {
|
|
5579
|
-
const manifest = JSON.parse(
|
|
5766
|
+
const manifest = JSON.parse(fs29.readFileSync(path27.join(root, "package.json"), "utf8"));
|
|
5580
5767
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
5581
5768
|
} catch {
|
|
5582
5769
|
}
|
|
5583
|
-
return
|
|
5770
|
+
return path27.basename(root);
|
|
5584
5771
|
}
|
|
5585
5772
|
function runInit(root, options) {
|
|
5586
5773
|
const paths = designPaths(root);
|
|
5587
5774
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
5588
5775
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
5589
5776
|
`, root);
|
|
5590
|
-
|
|
5777
|
+
fs29.mkdirSync(paths.canvases, { recursive: true });
|
|
5591
5778
|
if (options.gitignore) {
|
|
5592
5779
|
const result = ensureGitignored(root);
|
|
5593
5780
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
5594
5781
|
}
|
|
5595
5782
|
print();
|
|
5596
|
-
print(
|
|
5783
|
+
print(
|
|
5784
|
+
"Next: `design system init` for a design system, `design new <canvas>` for a canvas, `design icon <file.svg>` for the project icon, `design preview` to look."
|
|
5785
|
+
);
|
|
5597
5786
|
}
|
|
5598
5787
|
function runSystemInit(root, options) {
|
|
5599
5788
|
const paths = designPaths(root);
|
|
5600
5789
|
let base = projectName(root);
|
|
5601
5790
|
try {
|
|
5602
|
-
base = JSON.parse(
|
|
5791
|
+
base = JSON.parse(fs29.readFileSync(paths.config, "utf8")).name || base;
|
|
5603
5792
|
} catch {
|
|
5604
5793
|
}
|
|
5605
5794
|
const name = options.name ?? `${base} design system`;
|
|
5606
|
-
print(`Scaffolding the design system in ${
|
|
5607
|
-
write(
|
|
5795
|
+
print(`Scaffolding the design system in ${path27.relative(root, paths.system)}`);
|
|
5796
|
+
write(path27.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
5608
5797
|
`, root);
|
|
5609
|
-
write(
|
|
5798
|
+
write(path27.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
5610
5799
|
if (!options.empty) {
|
|
5611
|
-
write(
|
|
5612
|
-
write(
|
|
5613
|
-
write(
|
|
5800
|
+
write(path27.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
5801
|
+
write(path27.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
5802
|
+
write(path27.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
5614
5803
|
}
|
|
5615
5804
|
}
|
|
5616
5805
|
function runNew(root, id2, options) {
|
|
5617
5806
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
5618
5807
|
const paths = designPaths(root);
|
|
5619
|
-
const dir =
|
|
5620
|
-
if (
|
|
5808
|
+
const dir = path27.join(paths.canvases, id2);
|
|
5809
|
+
if (fs29.existsSync(path27.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
5621
5810
|
print(`Creating canvas "${id2}"`);
|
|
5622
5811
|
const title = options.title ?? titleize(id2);
|
|
5623
5812
|
if (options.empty) {
|
|
5624
5813
|
write(
|
|
5625
|
-
|
|
5814
|
+
path27.join(dir, "canvas.json"),
|
|
5626
5815
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
5627
5816
|
`,
|
|
5628
5817
|
root
|
|
5629
5818
|
);
|
|
5630
|
-
|
|
5819
|
+
fs29.mkdirSync(path27.join(dir, "screens"), { recursive: true });
|
|
5631
5820
|
} else {
|
|
5632
|
-
write(
|
|
5633
|
-
write(
|
|
5821
|
+
write(path27.join(dir, "canvas.json"), canvasJson(title), root);
|
|
5822
|
+
write(path27.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
5634
5823
|
}
|
|
5635
5824
|
}
|
|
5636
5825
|
|
|
5637
5826
|
// src/cli/commands/shot.ts
|
|
5638
|
-
import
|
|
5639
|
-
import
|
|
5827
|
+
import fs30 from "fs";
|
|
5828
|
+
import path28 from "path";
|
|
5640
5829
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
5641
5830
|
async function runShot(paths, target, options) {
|
|
5642
5831
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -5652,15 +5841,15 @@ async function runShot(paths, target, options) {
|
|
|
5652
5841
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
5653
5842
|
}
|
|
5654
5843
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
5655
|
-
const outBase =
|
|
5656
|
-
const outDir =
|
|
5657
|
-
|
|
5844
|
+
const outBase = path28.resolve(options.out ?? paths.shots);
|
|
5845
|
+
const outDir = path28.join(outBase, canvasId);
|
|
5846
|
+
fs30.mkdirSync(outDir, { recursive: true });
|
|
5658
5847
|
const base = await serverBase(paths);
|
|
5659
5848
|
const browser = await launchChrome();
|
|
5660
5849
|
let failed = false;
|
|
5661
5850
|
try {
|
|
5662
5851
|
if (options.overview) {
|
|
5663
|
-
if (!
|
|
5852
|
+
if (!fs30.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
5664
5853
|
const pages = options.page ? [options.page] : screenId ? doc.pages.filter((page) => page.sections.some((section) => section.items.some((item) => item.id === screenId))).map((page) => page.id) : doc.pages.map((page) => page.id);
|
|
5665
5854
|
for (const pageId of pages) {
|
|
5666
5855
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -5676,7 +5865,7 @@ async function runShot(paths, target, options) {
|
|
|
5676
5865
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
5677
5866
|
}
|
|
5678
5867
|
await page.waitForTimeout(400);
|
|
5679
|
-
const file =
|
|
5868
|
+
const file = path28.join(outDir, `${pageId}.overview.png`);
|
|
5680
5869
|
await page.screenshot({ path: file });
|
|
5681
5870
|
await page.close();
|
|
5682
5871
|
print(file);
|
|
@@ -5705,7 +5894,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
5705
5894
|
let failed = false;
|
|
5706
5895
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
5707
5896
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
5708
|
-
const file =
|
|
5897
|
+
const file = path28.join(outDir, `${job.id}.${job.theme}.png`);
|
|
5709
5898
|
try {
|
|
5710
5899
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
5711
5900
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -5737,8 +5926,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
5737
5926
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
5738
5927
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
5739
5928
|
}
|
|
5740
|
-
const outDir =
|
|
5741
|
-
|
|
5929
|
+
const outDir = path28.join(path28.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
5930
|
+
fs30.mkdirSync(outDir, { recursive: true });
|
|
5742
5931
|
const base = await serverBase(paths);
|
|
5743
5932
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
5744
5933
|
const browser = await launchChrome();
|
|
@@ -5750,7 +5939,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
5750
5939
|
}
|
|
5751
5940
|
|
|
5752
5941
|
// src/cli/commands/cloud-status.ts
|
|
5753
|
-
import
|
|
5942
|
+
import path29 from "path";
|
|
5754
5943
|
var STATE_TEXT = {
|
|
5755
5944
|
in_sync: green,
|
|
5756
5945
|
local_changes: yellow,
|
|
@@ -5773,7 +5962,7 @@ function detail(paths, status) {
|
|
|
5773
5962
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
5774
5963
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
5775
5964
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
5776
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
5965
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path29.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
5777
5966
|
return parts.join(" \xB7 ");
|
|
5778
5967
|
}
|
|
5779
5968
|
async function printCloudStatus(paths, label) {
|
|
@@ -5888,6 +6077,9 @@ ${bold("Project")}
|
|
|
5888
6077
|
init [--name <name>] [--no-gitignore] Create .design and add it to .gitignore
|
|
5889
6078
|
system init [--name <name>] [--empty] Scaffold .design/system: tokens, a guideline, a component
|
|
5890
6079
|
new <canvas> [--title <title>] [--empty] Scaffold .design/canvas/<canvas>
|
|
6080
|
+
icon [<file>] [--remove] Show, set or remove the project icon: an SVG, PNG or WebP of
|
|
6081
|
+
at most 256 KB, kept as .design/system/icon.* and pushed
|
|
6082
|
+
with the design system
|
|
5891
6083
|
import <folder> [--canvas <id>] Turn an exported Claude Design project (canvas.json and
|
|
5892
6084
|
[--title <title>] *.dc.html) into a canvas of React screens, once
|
|
5893
6085
|
canvases [--json] Every canvas: pages and screens; with a cloud link also its
|
|
@@ -5949,7 +6141,7 @@ ${bold("Environment")}
|
|
|
5949
6141
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
5950
6142
|
`;
|
|
5951
6143
|
function projectPaths(rootOption, requireDesign = true) {
|
|
5952
|
-
const found = rootOption ?
|
|
6144
|
+
const found = rootOption ? path30.resolve(rootOption) : findProjectRoot();
|
|
5953
6145
|
if (!found) {
|
|
5954
6146
|
if (!requireDesign) return designPaths(process.cwd());
|
|
5955
6147
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -5967,6 +6159,7 @@ async function main(argv) {
|
|
|
5967
6159
|
name: { type: "string" },
|
|
5968
6160
|
title: { type: "string" },
|
|
5969
6161
|
canvas: { type: "string" },
|
|
6162
|
+
remove: { type: "boolean" },
|
|
5970
6163
|
"no-gitignore": { type: "boolean" },
|
|
5971
6164
|
empty: { type: "boolean" },
|
|
5972
6165
|
open: { type: "boolean" },
|
|
@@ -5996,7 +6189,7 @@ async function main(argv) {
|
|
|
5996
6189
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
5997
6190
|
switch (command) {
|
|
5998
6191
|
case "init":
|
|
5999
|
-
return runInit(values.root ?
|
|
6192
|
+
return runInit(values.root ? path30.resolve(values.root) : process.cwd(), {
|
|
6000
6193
|
name: values.name,
|
|
6001
6194
|
gitignore: !values["no-gitignore"]
|
|
6002
6195
|
});
|
|
@@ -6008,6 +6201,8 @@ async function main(argv) {
|
|
|
6008
6201
|
if (!rest[0]) throw new CliError("Usage: design new <canvas> [--title <title>]");
|
|
6009
6202
|
return runNew(projectPaths(values.root).root, rest[0], { title: values.title, empty: !!values.empty });
|
|
6010
6203
|
}
|
|
6204
|
+
case "icon":
|
|
6205
|
+
return runIcon(projectPaths(values.root), rest[0], { remove: !!values.remove });
|
|
6011
6206
|
case "import": {
|
|
6012
6207
|
if (!rest[0]) throw new CliError("Usage: design import <folder> [--canvas <id>] [--title <title>]");
|
|
6013
6208
|
return runImport(projectPaths(values.root), rest[0], { canvas: values.canvas, title: values.title });
|